Skip to content

fix(tree): keep file reads inside the repository - #11

Merged
fiddur merged 1 commit into
developfrom
path-containment
Aug 21, 2026
Merged

fix(tree): keep file reads inside the repository#11
fiddur merged 1 commit into
developfrom
path-containment

Conversation

@fiddur

@fiddur fiddur commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Audit finding P1-4. /api/tree/file/ and /api/tree/raw/ decodeURIComponent the path and joined
it onto the repository root with no containment check:

const fullPath = join(root, filePath);
return { data: readFileSync(fullPath), fullPath };

WHATWG URL parsing collapses a literal ../ before the handler sees it, but percent-encoded
separators survive
/api/tree/raw/..%2f..%2f..%2fetc%2fpasswd decodes to ../../../etc/passwd.
This is not an exotic encoding: the UI sends encodeURIComponent(filePath), so %2F-encoded paths
are the normal code path. Any file the user could read was readable — ~/.ssh/id_rsa,
~/.config/gh/hosts.yml, other repositories' source.

  • resolveInRepo resolves the path and then asserts containment, rejecting .., absolute paths, and the sibling-directory-sharing-a-prefix case
  • both readers and /api/open-in-editor (audit P2-3) go through it
  • raw responses are served inertly: only image types keep their content type, everything else becomes application/octet-stream with Content-Disposition: attachment, and every raw response carries nosniff plus default-src 'none'; sandbox, so a repository's own .html or .svg can no longer execute in this origin and hand an attacker same-origin access to the API

Stacked on #10.

🤖 Generated with Claude Code

https://claude.ai/code/session_018PkYQzbsnMihHesafWvXKs

/api/tree/file/ and /api/tree/raw/ decodeURIComponent the path and joined it onto the
repository root with no containment check. WHATWG URL parsing collapses a literal ../
before the handler sees it, but percent-encoded separators survive - and the UI itself
sends encodeURIComponent(filePath), so %2F-encoded paths are the normal code path. Any
file the user could read was readable.

resolveInRepo resolves and then asserts containment, and both readers plus
/api/open-in-editor go through it.

Raw responses are also served inertly: only image types keep their content type, anything
else is application/octet-stream with Content-Disposition: attachment, and every raw
response carries nosniff and default-src 'none'; sandbox - a repository's own .html or
.svg can no longer execute in this origin.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018PkYQzbsnMihHesafWvXKs
@fiddur
fiddur changed the base branch from argv-git to develop August 21, 2026 13:34
@fiddur
fiddur marked this pull request as ready for review August 21, 2026 13:34
@fiddur
fiddur merged commit ca0870c into develop Aug 21, 2026
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