Skip to content

fix(security): block path traversal via symlinks in /api/file and read_file (RAN-8)#65

Merged
aksOps merged 1 commit into
mainfrom
fix/ran-8-symlink-path-traversal
Apr 25, 2026
Merged

fix(security): block path traversal via symlinks in /api/file and read_file (RAN-8)#65
aksOps merged 1 commit into
mainfrom
fix/ran-8-symlink-path-traversal

Conversation

@aksOps
Copy link
Copy Markdown
Contributor

@aksOps aksOps commented Apr 24, 2026

Summary

  • Harden /api/file (REST) and read_file (MCP) against path-traversal via symbolic links: the previous lexical normalize() + startsWith(root) guard did not resolve symlinks, so an in-repo link pointing at e.g. /etc/passwd would pass the check and then leak the target's contents.
  • Replace the lexical-only guard with a two-stage check: lexical reject for ../ traversal, then Path.toRealPath() and re-check against a canonicalized codebase root.
  • NoSuchFileException now maps to 404 on REST (was previously "isRegularFile false → 404"), preserving existing behaviour.

Changes

  • api/GraphController.java — canonicalize root with toRealPath(), lexical guard, then candidate.toRealPath() and re-check. 403 on escape, 404 on missing.
  • mcp/McpTools.java — same guard; returns {"error":"Path traversal detected"} on escape.
  • Tests:
    • GraphControllerTest: readFileShouldRejectSymlinkEscapingRoot (positive reject) + readFileShouldAllowInRepoSymlink (legit symlink still works).
    • McpToolsTest: same pair for the MCP side. Both skip cleanly on filesystems that don't support symlinks.

Test plan

  • mvn -Dskip.npm=true test → 3399 run, 0 failures, 0 errors
  • Targeted readFile* runs pass (7 GraphController + 9 McpTools)
  • Existing ../../../etc/passwd lexical traversal tests still pass
  • Reviewer: consider a manual smoke against a real repo with a planted escape symlink (optional — unit tests cover it)

Fixes RAN-8.

🤖 Generated with Claude Code

…d_file (RAN-8)

Resolve symlinks with `Path.toRealPath()` and re-check the resolved path
against the codebase root on both the REST `/api/file` endpoint and the
MCP `read_file` tool. `Path.normalize()` is purely lexical and left
symlinks inside the indexed repo usable for exfiltrating off-tree files
(e.g. `link -> /etc/passwd`).

- GraphController: canonicalize root, lexical guard, then toRealPath()
  and re-check; 404 on NoSuchFileException, 403 on out-of-root.
- McpTools: same two-stage guard, returns "Path traversal detected".
- Tests: positive (escape symlink rejected) + negative (in-repo symlink
  read succeeds) for both REST and MCP. Skip gracefully on filesystems
  without symlink support.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment on lines +265 to +267
return ResponseEntity.status(500)
.contentType(MediaType.TEXT_PLAIN)
.body("Failed to resolve codebase root: " + e.getMessage());
Comment thread src/main/java/io/github/randomcodespace/iq/api/GraphController.java Dismissed
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
74.3% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@aksOps aksOps merged commit f7c4401 into main Apr 25, 2026
7 of 8 checks passed
@aksOps aksOps deleted the fix/ran-8-symlink-path-traversal branch April 25, 2026 13:19
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.

2 participants