Skip to content

[PR] Prevent path traversal in static file routes (#912) - #919

Open
lackas wants to merge 1 commit into
AnswerDotAI:mainfrom
lackas:fix-static-path-traversal
Open

[PR] Prevent path traversal in static file routes (#912)#919
lackas wants to merge 1 commit into
AnswerDotAI:mainfrom
lackas:fix-static-path-traversal

Conversation

@lackas

@lackas lackas commented Aug 14, 2026

Copy link
Copy Markdown

Related Issue

Fixes #912.

Proposed Changes

static_route and static_route_exts build the served path by interpolating the {fname:path} URL segment straight into FileResponse(f'{static_path}/{fname}…'). Since the path converter matches .., a request such as /%2e%2e/%2e%2e/secret.gz resolves outside static_path and serves any file the worker process can read (confirmed on 0.14.11 — an encoded /%2e%2e/secret.md returns a sibling file that exists outside the static dir).

This adds a small helper _static_fpath, which resolves the requested path against static_path and raises HTTPException(404) when it escapes; both static handlers now route through it. Legitimate use is unchanged — nested paths, and an explicit static_path above the app dir like static_path='../..' (as used in the existing tests), still work; only .. within the request path is rejected.

This does not change the separate discussion in #918 about the default static_path='.' serving the app root; it only closes the traversal escape.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added tests to cover my changes.
  • All new and existing tests passed. (The new test cell passes and the existing static-route tests pass. The full 00_core suite has one pre-existing, unrelated failure — the <title>Default</title> assertion — which also fails on unpatched main in a clean environment.)
  • I am aware that this is an nbdev project, and I have edited, cleaned, and synced the source notebooks instead of editing .py or .md files directly.

Additional Information

The containment check uses os.path.realpath (resolving symlinks), matching Starlette's own StaticFiles behavior (follow_symlink=False) — this also closes a symlink-based escape where a link inside static_path points outside it. If you'd prefer to preserve symlink-following, os.path.abspath + normpath would also close the traversal; happy to switch.

static_route and static_route_exts interpolate the {fname:path} URL
segment straight into FileResponse(f'{static_path}/{fname}...'). The path
converter matches '..', so a request like /%2e%2e/%2e%2e/secret.gz
resolves outside static_path and serves any file the process can read.

Add _static_fpath, which resolves the requested path against static_path
and raises 404 when it escapes, and route both static handlers through
it. Nested paths and an explicit static_path above the app dir still
work; only '..' within the request path is rejected.
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.

[BUG] static_route_exts / static_route allow directory traversal via fname path segments

1 participant