mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-08-22 02:48:15 +00:00
fix(composer): build the font path from the allowlist entry
secure_filename cleared the plugin-directory alerts: CodeQL went from 19
to 5, and from 16 high-severity to 2. The two that remain are in
serve_font, which is gated by a frozenset of three exact filenames -- so
nothing was exploitable -- but the name reaching the filesystem was still
the request value.
It now comes from the matched allowlist entry. Identical strings, so
runtime behaviour is unchanged; the difference is that the filename is
provably a module constant rather than a guarded piece of user input.
The first test I wrote for this proved nothing. It asserted 404 on
traversal payloads, but Flask's router will not match a path segment
containing '/', and the rest 404 simply because no such file exists --
so removing the allowlist entirely still passed. Replaced with a readable
file planted next to the fonts:
fonts/id_rsa.ttf -> 404, body does not contain its contents
which fails with "a readable non-allowlisted file was served" the moment
the gate is removed.
45 tests.
Left alone: three medium py/stack-trace-exposure alerts on the
_generate_plugin_files handlers, which return str(exc) for
ComposerInputError. Its seven raise sites are all authored literals
("Author is required.", "Config variable key X is not a valid Python
identifier."), so no traceback or path is exposed. Clearing them means
either replacing that feedback with a generic string or restructuring
validation to return errors instead of raising -- a change to the
author's design, made blind, since CodeQL cannot be run locally to
confirm it would even work. That is a decision, not a cleanup.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STMbQE4YctTacQXfbYqKuW
This commit is contained in:
co-authored by
Claude Opus 5
parent
5929190e36
commit
5133643600
@@ -758,11 +758,17 @@ def install_locally():
|
||||
@composer_bp.route('/api/fonts/<font_name>')
|
||||
def serve_font(font_name):
|
||||
"""Serve an allowlisted font file for canvas FontFace loading."""
|
||||
if font_name not in _ALLOWED_FONTS:
|
||||
# Build the path from the allowlist entry, not from the request value.
|
||||
# They are equal strings, so this changes nothing at runtime -- but the
|
||||
# name that reaches the filesystem now provably originates in a module
|
||||
# constant, which is the difference between "guarded" and "not derived
|
||||
# from user input at all".
|
||||
allowed_name = next((f for f in sorted(_ALLOWED_FONTS) if f == font_name), None)
|
||||
if allowed_name is None:
|
||||
return '', 404
|
||||
if not composer_bp.project_root:
|
||||
return '', 503
|
||||
font_path = Path(composer_bp.project_root) / 'assets' / 'fonts' / font_name
|
||||
font_path = Path(composer_bp.project_root) / 'assets' / 'fonts' / allowed_name
|
||||
if not font_path.exists():
|
||||
return '', 404
|
||||
return send_file(str(font_path), mimetype='font/ttf')
|
||||
|
||||
Reference in New Issue
Block a user