From d54bb04414302fd3e01fda92d4d65c06d615d12c Mon Sep 17 00:00:00 2001 From: Vuong Hoang Date: Thu, 24 Sep 2026 16:33:27 -0700 Subject: [PATCH] fix(r3): a NUL in the raw file path is a 404, not a 500 Compare's stages load their pictures through the catch-all file route, which caught only OSError around resolve(); an embedded NUL raises ValueError. Same class as resolve_booth's fix in f8d136a (heid bug hunt on the race fix, hulda). The upload route's NUL-in-filename 500 is the same class and is left to booth-dev: it is not on compare's path. --- booth/app.py | 4 +++- tests/mutations/r3.toml | 11 +++++++++++ tests/test_compare.py | 10 ++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/booth/app.py b/booth/app.py index 30b0e11..d78dc33 100644 --- a/booth/app.py +++ b/booth/app.py @@ -2148,7 +2148,9 @@ def create_app( booth = resolve_booth(name) try: target = (booth / filepath).resolve() - except OSError: + except (OSError, ValueError): + # ValueError: an embedded NUL (`/b/g/p%00.png`) is not an OSError, + # and hostile input is a 404, never a 500 (r3 heid bug hunt) raise HTTPException(status_code=404, detail="no such file") if not str(target).startswith(str(booth) + os.sep) or not target.is_file(): raise HTTPException(status_code=404, detail="no such file") diff --git a/tests/mutations/r3.toml b/tests/mutations/r3.toml index eb29e11..0f6399a 100644 --- a/tests/mutations/r3.toml +++ b/tests/mutations/r3.toml @@ -567,6 +567,17 @@ new = ''' if True: partner''' +[[mutation]] +label = "a NUL in the raw file path is a 500 (the stages load through this route)" +file = "booth/app.py" +test = "tests/test_compare.py::test_a_nul_in_a_file_path_is_404_not_500" +old = ''' + target = (booth / filepath).resolve() + except (OSError, ValueError):''' +new = ''' + target = (booth / filepath).resolve() + except OSError:''' + # Refuted, not rowed (bug hunt): "a right-click mid-drag ends the pan" — a # second button pressed and released during a drag arrives as chorded # `pointermove` events, never a `pointerup` (measured 3/3 in the test browser, diff --git a/tests/test_compare.py b/tests/test_compare.py index fa6b883..66943b1 100644 --- a/tests/test_compare.py +++ b/tests/test_compare.py @@ -150,6 +150,16 @@ def test_hostile_booth_names_are_404_not_500(tmp_path): assert c.get(path).status_code == 404, path +def test_a_nul_in_a_file_path_is_404_not_500(tmp_path): + """Compare's stages load their pictures through the raw file route. A NUL + in that path segment raises ValueError from resolve(), which is not an + OSError: still a 404 (heid bug hunt on the race fix, hulda).""" + _four(tmp_path) + c = _client(tmp_path) + for path in ("/b/g/p%00.png", "/b/g/p.png%00?thumb=1", "/b/g/sub%00/p.png?dl=1"): + assert c.get(path).status_code == 404, path + + def test_a_planted_fifo_marker_cannot_hang_a_look(tmp_path): """Recording a look never costs the page: a FIFO planted at `.viewed` must not block the open that touches it (heid bug hunt, hulda)."""