"""The filesystem allowlist: which server-side paths a source may ever read. Rejecting traversal, symlinks and relative paths only proves a path is well formed. These tests are about the separate question the allowlist answers -- whether a well-formed path is one an operator ever intended RAGcore to read. """ from __future__ import annotations from pathlib import Path import pytest from ragcore.domain.sources import ( SourceRootError, assert_within_allowed_roots, normalize_allowed_roots, resolve_within_allowed_roots, ) pytestmark = pytest.mark.security _ROOTS = (Path("/mnt/rag-sources"), Path("/srv/corpora")) def test_no_configured_roots_allows_nothing() -> None: """Fail closed. An empty allowlist is a real answer -- "this deployment reads no server-side paths" -- not a missing one to be defaulted away.""" with pytest.raises(SourceRootError, match="no allowed source roots"): assert_within_allowed_roots("/mnt/rag-sources/runbooks", ()) @pytest.mark.parametrize( "requested", [ "/mnt/rag-sources", "/mnt/rag-sources/runbooks", "/mnt/rag-sources/runbooks/fleet", "/srv/corpora/handbook", ], ) def test_a_root_and_anything_below_it_is_allowed(requested: str) -> None: assert assert_within_allowed_roots(requested, _ROOTS) == Path(requested) @pytest.mark.parametrize( "requested", [ "/etc", "/etc/shadow", "/app/src/ragcore/config.py", "/var/lib/postgresql/data", "/mnt", "/", ], ) def test_a_path_outside_every_root_is_refused(requested: str) -> None: with pytest.raises(SourceRootError, match="outside every allowed source root"): assert_within_allowed_roots(requested, _ROOTS) @pytest.mark.parametrize( "requested", [ "/mnt/rag-sources-secret", "/mnt/rag-sources-secret/keys", "/srv/corpora-backup", ], ) def test_a_sibling_sharing_a_string_prefix_is_not_contained(requested: str) -> None: """Containment is decided on path *components*, never on string prefixes. ``/mnt/rag-sources`` does not contain ``/mnt/rag-sources-secret``, however much the two look alike to ``startswith``.""" with pytest.raises(SourceRootError, match="outside every allowed source root"): assert_within_allowed_roots(requested, _ROOTS) @pytest.mark.parametrize( "requested", ["relative/path", "", "rag-sources", "C:x"], ) def test_a_path_that_is_not_absolute_is_refused(requested: str) -> None: with pytest.raises(SourceRootError): assert_within_allowed_roots(requested, _ROOTS) def test_traversal_is_refused_before_containment_is_even_considered() -> None: """``/mnt/rag-sources/../../etc`` normalises to ``/etc``, so a check that normalised first and compared second would be fine either way -- but the path is rejected outright, so a root that happens to be near the top of the tree cannot be walked out of.""" with pytest.raises(SourceRootError, match="must not traverse"): assert_within_allowed_roots("/mnt/rag-sources/../../etc", _ROOTS) def test_a_nul_byte_is_refused() -> None: with pytest.raises(SourceRootError, match="NUL-free"): assert_within_allowed_roots("/mnt/rag-sources/x\x00.txt", _ROOTS) def test_the_stored_spelling_survives_validation() -> None: """The API host validating a POSIX deployment path must hand back exactly what the operator wrote: a Windows-side ``normpath`` would rewrite it into something the Linux worker could never open.""" assert str(assert_within_allowed_roots("/mnt/rag-sources/docs", _ROOTS)).replace( "\\", "/" ) == "/mnt/rag-sources/docs" # -- operator-configured roots ---------------------------------------------- def test_a_filesystem_root_may_not_be_an_allowed_root() -> None: """Allowing ``/`` would make the allowlist meaningless.""" with pytest.raises(SourceRootError, match="must not be a filesystem root"): normalize_allowed_roots(["/"]) def test_a_relative_or_traversing_root_is_refused_at_startup() -> None: with pytest.raises(SourceRootError, match="absolute"): normalize_allowed_roots(["mnt/rag-sources"]) with pytest.raises(SourceRootError, match="traverse"): normalize_allowed_roots(["/mnt/rag-sources/../.."]) def test_duplicate_roots_collapse() -> None: assert normalize_allowed_roots(["/mnt/rag", "/mnt/rag"]) == (Path("/mnt/rag"),) # -- filesystem truth -------------------------------------------------------- def test_resolution_rejects_a_path_that_does_not_exist(tmp_path: Path) -> None: """The worker's check is about the filesystem as it is: a lexically fine path that cannot be resolved is not a path it may read.""" with pytest.raises(SourceRootError, match="cannot be resolved"): resolve_within_allowed_roots(tmp_path / "absent", (tmp_path,)) def test_resolution_accepts_a_real_directory_inside_an_allowed_root(tmp_path: Path) -> None: allowed = tmp_path / "corpora" inside = allowed / "runbooks" inside.mkdir(parents=True) assert resolve_within_allowed_roots(inside, (allowed,)) == inside.resolve(strict=True) def test_resolution_refuses_a_real_directory_outside_every_allowed_root(tmp_path: Path) -> None: allowed = tmp_path / "corpora" allowed.mkdir() elsewhere = tmp_path / "secrets" elsewhere.mkdir() with pytest.raises(SourceRootError, match="outside every allowed source root"): resolve_within_allowed_roots(elsewhere, (allowed,)) def test_an_unmounted_root_contains_nothing_but_does_not_break_the_others( tmp_path: Path, ) -> None: """A deployment may configure roots that only some containers mount. One that is absent here simply cannot contain anything; the roots that are present still get their chance.""" allowed = tmp_path / "corpora" allowed.mkdir() assert resolve_within_allowed_roots( allowed, (tmp_path / "never-mounted", allowed) ) == allowed.resolve(strict=True)