fix(web): sanitize the latin1_safe_filename fallback too

Review follow-up: the helper returned `fallback` verbatim when the name
sanitized to empty, so a future caller passing an unsafe fallback (non-latin-1,
control chars, quote, backslash) could reintroduce the header crash/corruption
the helper exists to prevent. Not reachable today — all call sites pass safe
ASCII literals — but the helper is a shared safety primitive whose contract is
wire-safe output.

Run the fallback through the same cleaning, backed by a safe constant if even
that is empty, so the return is always wire-safe and never filename="". Adds a
test.
This commit is contained in:
Patrick Buckley
2026-07-07 22:46:36 -07:00
parent fd3aed1eca
commit 2c5adb7aca
2 changed files with 22 additions and 5 deletions
+12
View File
@@ -184,3 +184,15 @@ class TestLatin1SafeFilename:
# An all-CJK name folds to '???' (truthy) — must NOT hit the fallback.
assert latin1_safe_filename("日本語", fallback="preview") == "???"
def test_fallback_is_also_sanitized(self):
from turnstone.core.web_helpers import latin1_safe_filename
# The fallback fires only when the name sanitizes to empty, and it is
# cleaned by the SAME rules — a caller can't reintroduce the crash /
# corruption through an unsafe fallback.
assert latin1_safe_filename("", fallback="\x00.txt") == "?.txt"
self._assert_wire_safe(latin1_safe_filename("", fallback="bad\\\x00name"))
# If even the fallback sanitizes to empty, a safe constant backs it —
# never filename="".
assert latin1_safe_filename("", fallback='"\x00') == "download"
+10 -5
View File
@@ -33,12 +33,17 @@ def latin1_safe_filename(name: str, *, fallback: str = "attachment") -> str:
C0 / C1 control ranges, and zero-width / bidi format chars) and both
quoted-string metacharacters, then fold any surviving non-ASCII codepoint
to ``?``. The result is pure printable ASCII with no ``"`` or ``\\`` —
latin-1 clean, control-free, and safely quotable. Falls back to
``fallback`` when nothing survives, so the header never emits
``filename=""``.
latin-1 clean, control-free, and safely quotable. ``fallback`` is run
through the same cleaning (so a caller can't reintroduce the crash via an
unsafe fallback), backed by a safe constant if even that is empty — the
return is always wire-safe and never ``filename=""``.
"""
kept = "".join(c for c in name if c.isprintable() and c not in '"\\')
return kept.encode("ascii", errors="replace").decode("ascii") or fallback
def _clean(s: str) -> str:
kept = "".join(c for c in s if c.isprintable() and c not in '"\\')
return kept.encode("ascii", errors="replace").decode("ascii")
return _clean(name) or _clean(fallback) or "download"
def skill_summary_rows(storage: Any) -> list[dict[str, Any]]: