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
+20
View File
@@ -107,6 +107,26 @@ void main() {
expect(r.data['content'], 'hi');
});
test('files.read reads an absolute path under an extra read root (D-80)', () async {
final extra = await Directory.systemTemp.createTemp('clide-extra-claude-');
addTearDown(() async => extra.existsSync() ? extra.deleteSync(recursive: true) : null);
File('${extra.path}/SKILL.md').writeAsStringSync('# peon');
final svc = FilesService(root: sandbox, events: RecordingEventSink(), ignore: IgnoreSet.builtin(), extraReadRoots: [extra]);
final d = DaemonDispatcher();
registerFilesCommands(d, svc);
addTearDown(svc.shutdown);
final r = await d.dispatch(IpcRequest(id: '1', cmd: 'files.read', args: {'path': '${extra.absolute.path}/SKILL.md'}));
expect(r.ok, isTrue);
expect(r.data['content'], '# peon');
});
test('files.read still rejects an absolute path outside all roots', () async {
final r = await call('files.read', const {'path': '/etc/passwd'});
expect(r.ok, isFalse);
expect(r.error!.message, contains('outside workspace'));
});
test('files.read without a path returns toolError', () async {
final r = await call('files.read', const {});
expect(r.ok, isFalse);