Skip to content

fix(mcp): close the symlink escape in the write guard + more #171 validation - #174

Merged
c2j merged 1 commit into
mainfrom
fix/mcp-confinement-and-validation
Sep 20, 2026
Merged

c2j merged 1 commit into
mainfrom
fix/mcp-confinement-and-validation

Conversation

@c2j

@c2j c2j commented Sep 20, 2026

Copy link
Copy Markdown
Owner

Follow-up hardening + extra validation on the #171 work (which is already merged and closed).

1. Real gap found and fixed: symlink escape in the write guard

confine_to_root only normalized paths lexically. A .codeweb symlink inside the project pointing elsewhere looked like an in-root path, so the store write landed outside the permitted directory.

Verified as Red before fixing: confine_rejects_symlinked_subdir_escaping_root failed on the old guard (lexical check passed).

Fix: after the lexical check, canonicalize the deepest existing ancestor of the target and compare it with the canonicalized root. Both sides are canonicalized, so a path that merely traverses a symlink (e.g. /tmp/private/tmp on macOS) is still accepted — covered by confine_accepts_real_dirs_and_symlinked_root.

Integration proof: test_mcp_analyze_rejects_symlinked_store_dir builds a project whose .codeweb is a symlink to an outside dir, calls codeweb_analyze, and asserts status:"error" and that the symlink target stayed empty.

2. Extra validation of the finished work (behaviors that already worked, now locked down)

Check Test
Corrupt store is reported explicitly, then codeweb_analyze self-heals it test_mcp_analyze_heals_corrupt_store
Pipelined analyze+analyze+stats all answer, no lock deadlock, consistent state test_mcp_handles_pipelined_requests
--project pointing at a not-yet-existing directory works with codeweb_init test_mcp_init_creates_missing_project_directory
init_at: empty dir list defaults to ["."]; absolute source dirs preserved verbatim init_at_defaults_to_root_when_no_source_dirs_given, init_at_keeps_absolute_source_dirs_absolute

The last two init_at tests are characterization tests for previously untested branches (they pass on the existing implementation); they are not Red→Green cycles.

3. Docs

Plan doc and DeveloperGuide updated to describe the two-layer guard and the new loops.

Verification

  • cargo fmt --all -- --check clean
  • cargo clippy --features full -- -D warnings clean
  • cargo test --features full -- --skip test_path_mapping_applied --skip test_serve_ → 40/40 suites ok, 0 failures (mcp_test 17 passed)
  • End-to-end acceptance re-run against the rebuilt binary: 40/40 assertions pass

…on (#171)

confine_to_root only normalized paths lexically, so a `.codeweb` symlink
inside the project pointing elsewhere passed the check and the store write
landed outside the permitted directory. Verified as Red first: with a
symlinked .codeweb the guard accepted the path.

The guard now also canonicalizes the deepest existing ancestor of the target
and compares it against the canonicalized root. Both sides are canonicalized,
so legitimate paths that merely traverse a symlink (e.g. /tmp on macOS) are
still accepted.

Additional validation added while checking the finished work:
- corrupt store: queries report it explicitly and codeweb_analyze self-heals
- pipelined analyze+analyze+stats requests all answer (no lock deadlock)
- --project pointing at a not-yet-existing directory works with codeweb_init
- init_at path branches: empty dir list, absolute source dirs preserved
@c2j
c2j merged commit 887e6d9 into main Sep 20, 2026
2 checks passed
@c2j
c2j deleted the fix/mcp-confinement-and-validation branch September 20, 2026 04:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant