"""Tests for hidden leak (tiered disclosure) logic.""" from __future__ import annotations import pytest import sys, os sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) from server.environment import SecretVisibility, SecretsAuditEnvironment # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- SAMPLE_SECRETS = [ {"id": "s1", "type": "aws_key", "path": "config.py", "line": 3, "visibility": "surface"}, {"id": "s2", "type": "db_passwd", "path": ".env", "line": 1, "visibility": "surface"}, {"id": "s3", "type": "gh_token", "path": "setup.cfg", "line": 7, "visibility": "shallow"}, {"id": "s4", "type": "jwt_secret","path": "auth.py", "line": 5, "visibility": "deep", "requires_action": "inspect_git_history"}, {"id": "s5", "type": "ssh_privkey","path": "deploy.sh", "line": 1, "visibility": "deep", "requires_action": "inspect_encoded"}, {"id": "s6", "type": "db_passwd", "path": "fallback.py", "line": 9, "visibility": "cascading", "trigger_secret_id": "s1"}, ] def _make_env() -> SecretsAuditEnvironment: """Minimal env instance — we only need its visibility helper.""" return SecretsAuditEnvironment.__new__(SecretsAuditEnvironment) # --------------------------------------------------------------------------- # Tests: SURFACE secrets always visible # --------------------------------------------------------------------------- class TestSurfaceSecrets: def test_surface_visible_immediately(self): env = _make_env() visible = env._compute_visible_secrets( SAMPLE_SECRETS, inspected_files=set(), deep_inspected=set(), fixed_secrets=set() ) visible_ids = {s["id"] for s in visible} assert "s1" in visible_ids assert "s2" in visible_ids # --------------------------------------------------------------------------- # Tests: SHALLOW secrets only after inspect_file # --------------------------------------------------------------------------- class TestShallowSecrets: def test_shallow_not_visible_before_inspect(self): env = _make_env() visible = env._compute_visible_secrets( SAMPLE_SECRETS, inspected_files=set(), deep_inspected=set(), fixed_secrets=set() ) visible_ids = {s["id"] for s in visible} assert "s3" not in visible_ids def test_shallow_visible_after_inspect(self): env = _make_env() visible = env._compute_visible_secrets( SAMPLE_SECRETS, inspected_files={"setup.cfg"}, deep_inspected=set(), fixed_secrets=set() ) visible_ids = {s["id"] for s in visible} assert "s3" in visible_ids # --------------------------------------------------------------------------- # Tests: DEEP secrets only after inspect_git_history / inspect_encoded # --------------------------------------------------------------------------- class TestDeepSecrets: def test_deep_not_visible_before_inspect(self): env = _make_env() visible = env._compute_visible_secrets( SAMPLE_SECRETS, inspected_files=set(), deep_inspected=set(), fixed_secrets=set() ) visible_ids = {s["id"] for s in visible} assert "s4" not in visible_ids assert "s5" not in visible_ids def test_deep_git_history_visible_after_scan(self): env = _make_env() visible = env._compute_visible_secrets( SAMPLE_SECRETS, inspected_files=set(), deep_inspected={"."}, fixed_secrets=set() ) visible_ids = {s["id"] for s in visible} assert "s4" in visible_ids def test_deep_encoded_visible_after_decode(self): env = _make_env() visible = env._compute_visible_secrets( SAMPLE_SECRETS, inspected_files=set(), deep_inspected={"deploy.sh:1"}, fixed_secrets=set() ) visible_ids = {s["id"] for s in visible} assert "s5" in visible_ids def test_deep_encoded_not_visible_for_wrong_path(self): env = _make_env() visible = env._compute_visible_secrets( SAMPLE_SECRETS, inspected_files=set(), deep_inspected={"other.py:5"}, fixed_secrets=set() ) visible_ids = {s["id"] for s in visible} assert "s5" not in visible_ids # --------------------------------------------------------------------------- # Tests: CASCADING secrets only after trigger is fixed # --------------------------------------------------------------------------- class TestCascadingSecrets: def test_cascading_not_visible_before_trigger_fixed(self): env = _make_env() visible = env._compute_visible_secrets( SAMPLE_SECRETS, inspected_files=set(), deep_inspected=set(), fixed_secrets=set() ) visible_ids = {s["id"] for s in visible} assert "s6" not in visible_ids def test_cascading_visible_after_trigger_fixed(self): env = _make_env() visible = env._compute_visible_secrets( SAMPLE_SECRETS, inspected_files=set(), deep_inspected=set(), fixed_secrets={"s1"} ) visible_ids = {s["id"] for s in visible} assert "s6" in visible_ids # --------------------------------------------------------------------------- # Tests: hidden_count_hint # --------------------------------------------------------------------------- class TestHiddenCountHint: def test_hidden_count_correct_initial(self): env = _make_env() visible = env._compute_visible_secrets( SAMPLE_SECRETS, inspected_files=set(), deep_inspected=set(), fixed_secrets=set() ) hidden = len(SAMPLE_SECRETS) - len(visible) # 2 surface visible, 4 hidden (1 shallow + 2 deep + 1 cascading) assert hidden == 4 def test_hidden_count_decreases_on_inspect(self): env = _make_env() visible = env._compute_visible_secrets( SAMPLE_SECRETS, inspected_files={"setup.cfg"}, deep_inspected={"."}, fixed_secrets={"s1"} ) hidden = len(SAMPLE_SECRETS) - len(visible) # All except s5 (needs deploy.sh decode) should be visible now: # s1=surface, s2=surface, s3=shallow(inspected), s4=deep(git scanned), # s5=deep(encoded, NOT decoded yet), s6=cascading(s1 fixed) assert hidden == 1