Skip to content

web: Harden static_url and get_version - #3784

Merged
bdarnell merged 3 commits into
tornadoweb:masterfrom
bdarnell:ccr-45e18b83-a2rmac
Oct 10, 2026
Merged

bdarnell merged 3 commits into
tornadoweb:masterfrom
bdarnell:ccr-45e18b83-a2rmac

Conversation

@bdarnell

Copy link
Copy Markdown
Member

static_url and get_version now check for traversal outside the static root directory via things like .., and prohibit access to anything but regular files (devices, fifos, etc). get_version has a cap on the size of file it will hash to avoid blocking the IOLoop for too long (defaulting to 64MB. Large files can still be accessed, they just won't have the v= argument and associated caching changes).

Document the limitation that because the allowed_symlink_directory setting isn't accessible on this code path, we do not validate symlink targets in get_version (so an attacker who can control symlinks in the static file directory and can cause them to be passed to static_url can determine the existence of files and their hashes).

claude added 3 commits October 8, 2026 11:45
static_url() hashed whatever file get_absolute_path pointed at without
the checks validate_absolute_path applies when serving. A path with ../
or a symlink pointing outside the static directory would be read and
its SHA-512 included in the URL, and a path resolving to a device such
as /dev/zero (or a FIFO) would block the event loop indefinitely.

get_version now checks that the path is inside static_path, resolves
symlinks against allowed_symlink_directory (taken from
static_handler_args, defaulting to the static root), and requires a
regular file before reading it. The root and symlink-directory logic is
factored out of validate_absolute_path so both share it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019nvEV873ZwBncgxPP13Xfd
Rework the previous commit so that get_version is unchanged and keeps
delegating all filesystem knowledge to the methods documented for
replacing filesystem access:

- get_absolute_path now rejects paths that lead outside the root as
  written (e.g. ../), sharing the check with validate_absolute_path.
- get_content refuses anything that is not a regular file (devices such
  as /dev/zero or Windows' CON/NUL, FIFOs, directories), sharing the
  stat check with validate_absolute_path.
- get_content_version stops hashing after MAX_VERSION_CONTENT_SIZE
  (64 MiB by default) so a huge file cannot block the event loop.

Symlinks are still only checked by validate_absolute_path, since
allowed_symlink_directory is per-handler configuration that the class
methods used by static_url cannot see.

Tests that don't need symlinks or FIFOs now run on all platforms, and
the Windows special filenames are checked for static_url as well as
for serving.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019nvEV873ZwBncgxPP13Xfd
make_static_url runs without a handler instance and cannot see
allowed_symlink_directory, so a symlink inside the static directory
can still cause static_url to hash (but not serve) a file outside it.
Fixing that would need a new overridable hook plus heuristics to avoid
breaking subclasses that map paths themselves (jupyter_server, voila),
so document the limitation instead. Also note in static_url the
behavior changes from the previous commits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019nvEV873ZwBncgxPP13Xfd
@bdarnell
bdarnell merged commit 5444106 into tornadoweb:master Oct 10, 2026
17 checks passed
@bdarnell
bdarnell deleted the ccr-45e18b83-a2rmac branch October 10, 2026 16:03
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