Four codecontext sidecar wrappers — get_file_analysis (required file_path), get_symbol_info, get_dependencies, and get_semantic_neighborhoods (optional) — forwarded file_path to the HTTP sidecar unchanged. The sidecar's internal file index is keyed on absolute paths, so any relative path from the model returned "File not found in graph". Three back-to-back failures observed in one chat on 2026-05-22 17:56 UTC, ~48 s of wasted tool budget. ## Resolver Add resolveProjectPath(projectRoot, rawPath) in codecontext_client.ts: trim check → absolute/relative branch (both go through resolve() so dot-segments normalise) → realpath with ENOENT fallthrough → escape check using the realpathed value. Error shape mirrors the existing target_dir escape error byte-for-byte; only the field name differs. Wired into callCodecontext at the args-spread site, guarded on file_path presence + non-empty. All four wrappers benefit from one call site; wrappers without file_path (overview, framework, watch, search) are unaffected. ## Schema trim .trim() added to all four file_path Zod schemas: get_file_analysis: z.string().trim().min(1) get_symbol_info: z.string().trim().optional() get_dependencies: z.string().trim().optional() get_semantic_neighborhoods: z.string().trim().optional() Absorbs trailing newlines / whitespace from model output before the resolver sees the value. ## Adversarial review fixes Adversarial pass surfaced two P2 findings: 1. Absolute path with `..` resolving outside the project root (e.g. `<projectRoot>/../etc/passwd`) that ENOENTs at realpath would slip through the literal prefix-check: the raw string starts with `<projectRoot>/`. Fix: resolve() the absolute branch's candidate too, so dot-segments normalise before the prefix check. 2. No symlink-escape test coverage. Realpath's stated purpose (catching in-project symlinks pointing outside the project) was never tested. Added: create a tmpdir outside projectRoot, symlink projectRoot/evil-link → outside file, assert rejection. ## Tests codecontext_client.test.ts: 19 tests (10 baseline + 9 new file_path resolution cases). Cases cover: relative→absolute, absolute-inside, relative-escape, absolute-outside, ENOENT-fallthrough, empty-string, wrapper-without-file_path, absolute-with-`..`-ENOENT, symlink-leaving-root. codecontext_tools.test.ts: one assertion updated to expect the resolved-absolute file_path on the wire (previously asserted the raw relative path passed through, which is exactly the bug being fixed). Full suite: 301 passed, 7 skipped. ## Affected / unaffected - get_codebase_overview, get_framework_analysis, watch_changes, search_symbols: no file_path arg → resolver guard skips them. No behavior change. - get_semantic_neighborhoods IS in SYNTHESIS_TOOLS — previously-failing relative-path calls will now successfully synthesize. Desirable, not a regression. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3.4 KiB
3.4 KiB
v1.13.18 tasks
B1 — Backups
apps/server/src/services/codecontext_client.ts.bak-v1.13.18-20260522apps/server/src/services/tools/codecontext/get_file_analysis.ts.bak-v1.13.18-20260522apps/server/src/services/tools/codecontext/get_symbol_info.ts.bak-v1.13.18-20260522apps/server/src/services/tools/codecontext/get_dependencies.ts.bak-v1.13.18-20260522apps/server/src/services/tools/codecontext/get_semantic_neighborhoods.ts.bak-v1.13.18-20260522
B2 — Resolver implementation in codecontext_client.ts
- Import
isAbsolute,resolve,sepfromnode:path(alongside existingjoin) - Add
resolveProjectPath(projectRoot, rawPath)helper — trim check, isAbsolute branch, realpath with ENOENT fallthrough, escape check - Wire into
callCodecontextat args-spread site — guard onfile_path.trim() !== '' - Error-shape parity verified:
file_path <raw> escapes project root <root>mirrorstarget_dir <dir> escapes project root <root>
B3 — Zod .trim() on wrapper schemas
get_file_analysis.ts—z.string().trim().min(1)get_symbol_info.ts—z.string().trim().optional()get_dependencies.ts—z.string().trim().optional()get_semantic_neighborhoods.ts—z.string().trim().optional()
B4 — Tests
- Added
describe('callCodecontext — file_path resolution', ...)tocodecontext_client.test.ts - Case 1: relative path resolves to absolute inside project root
- Case 2: absolute path inside project root passes through
- Case 3: relative escape (
../../etc/passwd) rejected withescapes project root - Case 4: absolute path outside project root rejected
- Case 5: nonexistent file (ENOENT) forwarded as un-realpath'd absolute
- Case 6: empty string skipped by guard (treated as not provided)
- Case 7: wrapper without
file_path— resolver not invoked, nofile_pathin wire body - All 17 tests in
codecontext_client.test.tspass
B5 — Typecheck + smoke
npx tsc --noEmit -p apps/server— 0 errors- Before-fix smoke (relative path):
{"error":"file not found: apps/server/src/services/inference/turn.ts","result":null} - Before-fix smoke (absolute path): returns
Lines: 330 / Symbols: 48as expected
B6 — Test asserting old buggy behavior updated
apps/server/src/services/__tests__/codecontext_tools.test.ts— assertion at line 73 updated fromfile_path: 'apps/server/src/index.ts'tofile_path: join(projectDir, 'apps/server/src/index.ts')to match the new resolved-absolute contract.
B7 — OpenSpec docs
openspec/changes/v1.13.18-codecontext-file-path/proposal.mdopenspec/changes/v1.13.18-codecontext-file-path/tasks.mdopenspec/changes/v1.13.18-codecontext-file-path/design.md
B8 — Review-pass defence-in-depth (P2 fixes from adversarial review)
codecontext_client.ts:71— absolute branch now goes throughresolve()to normalise dot-segments. Closes the ENOENT-fallthrough escape gap where<projectRoot>/../etc/xwould prefix-match<projectRoot>/literally.codecontext_client.test.ts— added Case 8 (absolute path with..resolving outside root, ENOENT branch) and Case 9 (in-project symlink whose target sits outside root). 19 tests pass.- Updated
resolveProjectPathdocstring to reflect the new normalisation step.