read user-scope Claude config files via a read allow-list (D-80)

The reader opened repo-local .claude markdown but rejected user-scope
files under ~/.claude with "path outside workspace" — that dir is
global, outside the repo, and files.read was repo-confined (T-102).

Per D-76 the Claude config surface is clide-managed, so files.read now
resolves a path under an allow-list: the workspace root plus trusted
extra read roots (FilesService.extraReadRoots), wired in main.dart to
~/.claude when present. Reads widen; writes stay repo-confined, and the
symlink re-check still refuses a config-root symlink that escapes. Off-
root paths and `..` traversal are rejected as before.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-01 09:08:32 +02:00
co-authored by Claude Opus 4.8
parent ade8a88b75
commit 0bb89a2e58
10 changed files with 143 additions and 3 deletions
+11 -1
View File
@@ -166,7 +166,17 @@ Future<void> main() async {
final eventSink = _BusEventSink(events);
final paneRegistry = PaneRegistry(events: eventSink);
registerPaneCommands(dispatcher, paneRegistry);
final filesService = FilesService(root: workRoot, events: eventSink);
// Trusted read-only roots beyond the workspace: the global Claude
// config dir (~/.claude), so the reader can open user-scope skill /
// agent / command markdown the Config tab surfaces (D-80, T-195).
// The repo-local .claude is already under workRoot.
final extraReadRoots = <Directory>[];
final claudeHome = Platform.environment['HOME'];
if (claudeHome != null) {
final globalClaude = Directory('$claudeHome/.claude');
if (globalClaude.existsSync()) extraReadRoots.add(globalClaude);
}
final filesService = FilesService(root: workRoot, events: eventSink, extraReadRoots: extraReadRoots);
registerFilesCommands(dispatcher, filesService);
// Search reuses the files service's resolved ignore set so the
// grep honours the same ignore_files: layering (D-4 / D-79).