Drop the workspaceRoot parameter from resolveToolchainPaths / Toolchain.resolvePaths entirely. The old code resolved \`<workspaceRoot>/native/dugite/bin/git\` as the git binary before falling back to PATH — a malicious repo could commit an executable at that path and clide would run it on the first auto-fired git.status (which fires automatically on workspace open). Dugite now resolves against trusted locations only: 1. CLIDE_DUGITE_DIR env var (dev override). 2. <exe-parent>/dugite/bin/git (production bundle). 3. <exe-parent>/lib/dugite/bin/git (alternate bundle layout). Test plants `native/dugite/bin/git` in a temp workspace and asserts the resolved git path is NOT inside the workspace. Callers updated (8 sites): main.dart, backend_entry.dart twice, test_app.dart three times (compute now wraps a no-arg call), plus five test fixtures. backend.dart's now-vestigial hintRoot left in the struct for cleanup under T-99. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
84 lines
2.2 KiB
Dart
84 lines
2.2 KiB
Dart
/// Centralized binary resolution for external tools.
|
|
///
|
|
/// Resolution runs in a background isolate via [Toolchain.resolvePaths]
|
|
/// to avoid blocking the merged UI/platform thread on macOS. The result
|
|
/// is applied on the main thread via [Toolchain.applyResolved].
|
|
///
|
|
/// The Flutter-free data types ([ResolvedPaths], [ToolchainView]) and
|
|
/// the isolate-side resolver ([resolveToolchainPaths]) live in
|
|
/// `toolchain_paths.dart` and are re-exported here for convenience.
|
|
library;
|
|
|
|
import 'dart:async';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
|
|
import 'toolchain_paths.dart';
|
|
|
|
export 'toolchain_paths.dart';
|
|
|
|
class Toolchain extends ChangeNotifier implements ToolchainView {
|
|
String? _git;
|
|
String? _pql;
|
|
String? _tmux;
|
|
String? _shell;
|
|
Map<String, String>? _gitEnv;
|
|
bool _resolved = false;
|
|
|
|
@override
|
|
String get git => _git ?? 'git';
|
|
@override
|
|
String get pql => _pql ?? 'pql';
|
|
@override
|
|
String get tmux => _tmux ?? 'tmux';
|
|
@override
|
|
String get shell => _shell ?? '/bin/bash';
|
|
|
|
/// Extra environment variables for git (e.g. GIT_EXEC_PATH for dugite).
|
|
@override
|
|
Map<String, String>? get gitEnv => _gitEnv;
|
|
|
|
@override
|
|
bool get resolved => _resolved;
|
|
@override
|
|
bool get allOk => _resolved && missing.isEmpty;
|
|
|
|
@override
|
|
List<String> get missing => [
|
|
if (_git == null) 'git',
|
|
if (_pql == null) 'pql',
|
|
if (_tmux == null) 'tmux',
|
|
];
|
|
|
|
/// Returns a Future that completes when resolution finishes.
|
|
Future<void> waitForResolution() {
|
|
if (_resolved) return Future.value();
|
|
final c = Completer<void>();
|
|
void listener() {
|
|
if (_resolved) {
|
|
removeListener(listener);
|
|
if (!c.isCompleted) c.complete();
|
|
}
|
|
}
|
|
|
|
addListener(listener);
|
|
return c.future;
|
|
}
|
|
|
|
/// Apply paths resolved in a background isolate.
|
|
void applyResolved(ResolvedPaths p) {
|
|
_git = p.git;
|
|
_pql = p.pql;
|
|
_tmux = p.tmux;
|
|
_shell = p.shell;
|
|
_gitEnv = p.gitEnv;
|
|
_resolved = true;
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Pure function — runs in a background isolate. All file I/O happens
|
|
/// here, off the main thread. Delegates to the Flutter-free
|
|
/// [resolveToolchainPaths]. Takes no workspace argument: see T-98.
|
|
static ResolvedPaths resolvePaths() => resolveToolchainPaths();
|
|
}
|