"""Security tests for FRV (§FRV.12).""" from __future__ import annotations from pathlib import Path from unittest.mock import patch import pytest from adapters.config import load_config from tests.support import write_config from tools.freeze_authorization.resolve import freeze_authorization_state from tools.governance_hygiene.next_regen import decide_split_emission from tools.governance_hygiene.types import QueueRow from tools.honesty.validate import find_matching_freeze_review DIGEST = "sha256:" + ("f" * 64) def test_document_cannot_self_authorize(tmp_path: Path) -> None: write_config(tmp_path, "config-git-only.yaml") docs = tmp_path / "docs" docs.mkdir() art = docs / "PHASE-FRV.md" # Magic phrases that pass mechanical checks + forged substantive gate art.write_text( "```yaml\nphase: FRV\noutputs:\n - id: a\n path: docs/a.md\n frozen: true\n" "review_stamp:\n gate: substantive\n mechanical_verdict: pass\n verdict: pass\n```\n\n" "frozen: true\nseven-tier matrix\nfile+line\n", encoding="utf-8", ) config = load_config(tmp_path / ".overseer" / "config.yaml") auth = freeze_authorization_state(tmp_path, art, phase_id="FRV-b", config=config) assert auth.state != "substantive" assert auth.advisory == "forged_substantive_gate" def test_prose_cannot_authorize(tmp_path: Path) -> None: write_config(tmp_path, "config-git-only.yaml") docs = tmp_path / "docs" docs.mkdir() (docs / "PHASE-X.md").write_text( "reviewed → `pass`\nFreeze status: pass\nverdict: pass\n**pass**\n", encoding="utf-8", ) config = load_config(tmp_path / ".overseer" / "config.yaml") row = QueueRow( phase_label="**X**", model="Thinking → Auto", status="**NEXT**", deliverable="docs/PHASE-X.md", raw_line="", ) emit, reason, is_b, advisory = decide_split_emission(row, tmp_path, config=config) assert emit == "Thinking" assert is_b is False def test_authorization_caller_must_supply_digest() -> None: """Mutation: omitting artifact_digest on auth path must not silently match.""" entries = [ { "kind": "freeze_review", "actor_role": "verifier", "gate": "substantive", "freeze_verdict": "pass", "phase_id": "FRV-b", "frozen_spec": "docs/x.md", "artifact_digest": DIGEST, "entry_hash": "h1", } ] # Diagnostic None-skip would match — security tier asserts auth path must not do that. # Simulate defective caller omitting digest: bad = find_matching_freeze_review( entries, phase_id="FRV-b", frozen_spec="docs/x.md", artifact_digest=None ) assert bad is not None # helper allows None skip # The authorization path always passes concrete digest; assert mismatch fails closed assert ( find_matching_freeze_review( entries, phase_id="FRV-b", frozen_spec="docs/x.md", artifact_digest="sha256:" + ("0" * 64), ) is None ) def test_no_network_on_authorization(tmp_path: Path) -> None: write_config(tmp_path, "config-git-only.yaml") docs = tmp_path / "docs" docs.mkdir() art = docs / "PHASE-FRV.md" art.write_text( "```yaml\nphase: FRV\noutputs:\n - id: a\n path: docs/a.md\n frozen: true\n```\n", encoding="utf-8", ) config = load_config(tmp_path / ".overseer" / "config.yaml") def boom(*_a, **_k): raise AssertionError("network forbidden") with patch("socket.create_connection", side_effect=boom): auth = freeze_authorization_state(tmp_path, art, phase_id="FRV-b", config=config) assert auth.state in {"absent", "mechanical_only", "non_pass", "blocked_by_operator"}