Skip to content

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

Description

@9xj89gzrtw-hue

Summary

resolveFileProxy (lib/utils/resolve-file-proxy.ts) resolves user-controlled path segments against the proxy's disk_path with join(diskPath, relativePath) and no parent-segment or containment check. A request under a configured proxy prefix containing .. makes the server read files anywhere the process can reach (CWE-22).

ctx.db.matchFileProxy only does a plain startsWith(prefix) match, so .. segments survive normalization into relativePath (normalizePath collapses duplicated slashes but not dot segments).

Reproduction (concept)

With a disk proxy { matching_pattern: "local/*", disk_path: "/srv/files-root" } and a secret at /srv/secret.txt (outside the root):

  1. GET /files/download/local/../secret.txt -> relativePath = "../secret.txt" -> join("/srv/files-root", "../secret.txt") = "/srv/secret.txt" -> the secret file is served.
  2. GET /files/download/local/../../etc/passwd -> escapes two levels.

The HTTP proxy flavor forwards the same traversal segments upstream. Impact: read-any-file within the server process privileges on any deployment configuring a disk file proxy.

Proposed fix (small, contained)

  1. In resolveFileProxy: after computing relativePath, refuse requests whose path contains a .. segment -> 400.
  2. In resolveDiskProxy: resolve() the joined path and require that it stays inside resolve(diskPath) (defense in depth across separators/platforms).
  3. Same guard applied to the http flavor so traversal segments are never forwarded upstream.

Tests included with the fix

  • unit: traversal attempts (single, multi, embedded deeper) -> 400 for both disk and http flavors; legit files inside the root and nested subdirectories still serve; containment check exercised independently of the .. guard
  • route-level (raw HTTP via node:http, since clients normalize ..) end-to-end: a secret outside the root never appears in a response; a normal proxy file still serves

Activity

  1. 9xj89gzrtw-hue commented on Oct 23, 2026

    @9xj89gzrtw-hue
    Author

    /attempt #173

  2. darzasassing-hub commented on Oct 25, 2026

    @darzasassing-hub

    /attempt #173

    Submitted PR: #178

    Fix:

    • Rejects paths containing '..' segments early in resolveFileProxy with HTTP 400 (protects both disk and http proxies).
    • Enforces root boundary containment in resolveDiskProxy via path.resolve(), returning HTTP 403 if escaped.
    • Includes full unit and route-level test coverage in tests/routes/path-traversal-proxy.test.ts.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions