Skip to content

fix: block path traversal through disk and http file proxies - #174

Open
9xj89gzrtw-hue wants to merge 2 commits into
tscircuit:mainfrom
9xj89gzrtw-hue:fix/proxy-path-traversal
Open

9xj89gzrtw-hue wants to merge 2 commits into
tscircuit:mainfrom
9xj89gzrtw-hue:fix/proxy-path-traversal

Conversation

@9xj89gzrtw-hue

Copy link
Copy Markdown

Fixes #173

Vulnerability

resolveFileProxy resolved user-controlled path segments with join(diskPath, relativePath) and no ../containment check, so a request under a configured disk proxy prefix could read files anywhere the server process can:

GET /files/download/local/../secret.txt   # secret lives outside the proxy root
relativePath = "../secret.txt"
join(diskPath, relativePath) -> outside the root -> served

ctx.db.matchFileProxy matches by plain startsWith(prefix), and normalizePath does not remove dot segments, so .. reached join() intact. The HTTP flavor forwarded the same traversal segments upstream. CWE-22.

Fix

  • resolveFileProxy rejects any relative path containing a .. segment with 400 (covers both disk and http flavors; traversal segments are never forwarded upstream).
  • resolveDiskProxy adds defense in depth: the joined path is resolve()d and required to stay inside resolve(diskPath) (platform-separator independent).
  • Response shape for legit paths is unchanged.

Tests — new tests/utils/resolve-file-proxy-traversal.test.ts + tests/routes/file-proxy-traversal-route.test.ts

  • single/multi/deeper-embedded .. attempts -> 400 (unit, disk + http flavors)
  • legit files at the root and in nested directories still serve with identical content
  • containment check proven independently of the .. guard
  • route-level regression via raw HTTP (node:http sends the path verbatim — normal HTTP clients collapse ..): a secret outside the proxy root never appears in any response; positive control serves normally

Full suite 42/42 pass; tsc --noEmit clean; biome clean on changed files.

normalizePath collapsed only the first repeated slash group (missing /g flag),
so paths like a///b///c.txt normalized to a/b///c.txt. The db-client
normalizes file paths on write and again on lookup; a non-idempotent
normalizer can make those comparisons inconsistent.

- replace(/\/\/+/g, "/") now collapses every group
- new tests: multi-group collapse, idempotence, windows separators
and mixed separators, preprocessing edge cases
…scircuit#173)

resolveFileProxy resolved user-controlled segments with join(diskPath,
relativePath) without checking for .. segments, so a request like
/files/download/local/../secret.txt read files outside the proxy root
(CWE-22); the http flavor forwarded the same traversal upstream.

- resolveFileProxy rejects any relative path containing a .. segment (400)
- resolveDiskProxy additionally require the resolved path to stay inside
  resolve(diskPath), independent of platform separator handling
- unit + route-level regression tests using raw HTTP (clients normalize
  dot-dot segments)
@9xj89gzrtw-hue

Copy link
Copy Markdown
Author

/claim #173

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.

Path traversal in disk file proxies: /files/download can read files outside the proxy root via .. segments

1 participant