""" Path traversal security tests. Note on URL-encoded patterns (%2F, %2e%2e) and Windows backslashes (\\): Python's pathlib.Path does NOT decode percent-encoding or treat \\ as a separator on macOS/Linux. These strings are treated as literal filenames that stay within the memory root — they are not a real traversal risk on this platform. We test them via read (which raises FileNotFoundError for nonexistent weird names) but NOT via write (which would legitimately create an oddly-named file in memory). """ import pytest from pyra.security.boundaries import VaultAccessError # Patterns that genuinely escape the memory root — must be blocked for both read AND write REAL_TRAVERSAL_PATTERNS = [ "../../../../vault/secrets/api_keys.json", "../../../vault/secrets/api_keys.json", "../../vault/secrets/api_keys.json", "../vault/secrets/api_keys.json", "a/b/c/../../../../vault/secrets/api_keys.json", "context/../../vault/secrets/api_keys.json", "user/../../../vault/secrets/api_keys.json", "knowledge/../../../../vault/secrets/api_keys.json", "user/notes/../../../../../../vault/secrets/api_keys.json", # Absolute paths "/etc/passwd", "/root/.ssh/id_rsa", "/tmp/evil", # Home-relative (rejected by writer, FileNotFoundError on reader) "~/secret", "~/.ssh/id_rsa", # Null bytes "valid\x00../../vault", ] # Patterns that look suspicious but are harmless on Python/macOS because # Path does not decode percent-encoding or treat \\ as a separator. # They raise FileNotFoundError on read (nonexistent file with odd name). READ_ONLY_SAFE_PATTERNS = [ "..%2Fvault%2Fsecrets%2Fapi_keys.json", "%2e%2e/vault/secrets/api_keys.json", "..\\vault\\secrets\\api_keys.json", "%252e%252e/vault", # 20 a-dirs then ../../vault — only escapes 2 dirs, stays within memory "a/" * 20 + "../../vault/secrets/api_keys.json", ] ALL_READ_PATTERNS = REAL_TRAVERSAL_PATTERNS + READ_ONLY_SAFE_PATTERNS @pytest.mark.parametrize("name", ALL_READ_PATTERNS) def test_memory_read_blocks_traversal(tmp_pyra_home, name): from pyra.memory.reader import read_memory with pytest.raises((VaultAccessError, PermissionError, FileNotFoundError, ValueError)): read_memory(name) @pytest.mark.parametrize("name", REAL_TRAVERSAL_PATTERNS) def test_memory_write_blocks_traversal(tmp_pyra_home, name): from pyra.memory.writer import write_memory with pytest.raises((VaultAccessError, PermissionError, FileNotFoundError, ValueError)): write_memory(name, "evil")