test(backend): pin the traversal guard against a string-prefix bypass
Review round 1: the two existing safe_abs_path escape tests both pass even against the classic buggy `str(path).startswith(str(root))` containment check, so a regression to that bug would have shipped green. Add the one input that distinguishes the correct `root not in path.parents` guard: a sibling dir whose name prefixes the root (/root vs /rootx). Mutation-verified — swapping the guard to startswith now fails this test. Also corrected the requirements-dev header (installed by Dockerfile.test, not a nonexistent `dev` stage).
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
# Test + lint tooling — NEVER in the prod image (installed only in the Dockerfile's `dev` stage,
|
# Test + lint tooling — NEVER in the prod image. Installed only by backend/Dockerfile.test (the
|
||||||
# which `siftlode publish` does not target). Pinned so the gate is reproducible: the same ruff the
|
# throwaway test image), which the prod build (root Dockerfile) never references. Pinned so the
|
||||||
# `siftlode check` backend lane runs, and pytest for the pure-logic suite. httpx is already an app
|
# suite is reproducible: pytest for the pure-logic tests, and ruff at the SAME version the gate's
|
||||||
# dependency (requirements.txt), so its TestClient needs nothing extra here.
|
# backend lint lane currently runs on the host (see the run_lint note in siftlode.sh). httpx is
|
||||||
|
# already an app dependency (requirements.txt), so its TestClient needs nothing extra here.
|
||||||
pytest==8.4.2
|
pytest==8.4.2
|
||||||
ruff==0.15.21
|
ruff==0.15.21
|
||||||
|
|||||||
@@ -91,5 +91,17 @@ class TestSafeAbsPath:
|
|||||||
outside.write_text("nope")
|
outside.write_text("nope")
|
||||||
assert safe_abs_path(str(root), str(outside)) is None
|
assert safe_abs_path(str(root), str(outside)) is None
|
||||||
|
|
||||||
|
def test_rejects_a_sibling_dir_whose_name_prefixes_the_root(self, tmp_path: Path):
|
||||||
|
# The one input that distinguishes the correct `root not in path.parents` guard from the
|
||||||
|
# classic buggy `str(path).startswith(str(root))`: /root vs a real sibling /rootx. A file in
|
||||||
|
# rootx is OUTSIDE root, but its path DOES start with the root string — a prefix check would
|
||||||
|
# wrongly serve it. This pins the guard against that regression.
|
||||||
|
root = tmp_path / "root"
|
||||||
|
root.mkdir()
|
||||||
|
sibling = tmp_path / "rootx"
|
||||||
|
sibling.mkdir()
|
||||||
|
(sibling / "steal.mp4").write_text("nope")
|
||||||
|
assert safe_abs_path(str(root), "../rootx/steal.mp4") is None
|
||||||
|
|
||||||
def test_returns_none_for_a_nonexistent_file_inside_the_root(self, tmp_path: Path):
|
def test_returns_none_for_a_nonexistent_file_inside_the_root(self, tmp_path: Path):
|
||||||
assert safe_abs_path(str(tmp_path), "does/not/exist.mp4") is None
|
assert safe_abs_path(str(tmp_path), "does/not/exist.mp4") is None
|
||||||
|
|||||||
Reference in New Issue
Block a user