"""Tests for taint tracking system (Phase 14.5).""" from __future__ import annotations import pytest from openjarvis.security.taint import ( SINK_POLICY, TaintLabel, TaintSet, auto_detect_taint, check_taint, declassify, propagate_taint, ) class TestTaintSet: def test_empty_taint(self): ts = TaintSet() assert not ts assert not ts.labels def test_from_labels(self): ts = TaintSet.from_labels(TaintLabel.PII, TaintLabel.SECRET) assert ts.has(TaintLabel.PII) assert ts.has(TaintLabel.SECRET) assert not ts.has(TaintLabel.EXTERNAL) def test_union(self): a = TaintSet.from_labels(TaintLabel.PII) b = TaintSet.from_labels(TaintLabel.SECRET) merged = a.union(b) assert merged.has(TaintLabel.PII) assert merged.has(TaintLabel.SECRET) def test_frozen(self): ts = TaintSet.from_labels(TaintLabel.PII) # TaintSet is frozen dataclass with pytest.raises(AttributeError): ts.labels = frozenset() def test_bool_true_when_has_labels(self): ts = TaintSet.from_labels(TaintLabel.PII) assert bool(ts) def test_bool_false_when_empty(self): ts = TaintSet() assert not bool(ts) class TestCheckTaint: def test_clean_data_passes(self): ts = TaintSet() assert check_taint("web_search", ts) is None def test_pii_blocked_for_web_search(self): ts = TaintSet.from_labels(TaintLabel.PII) result = check_taint("web_search", ts) assert result is not None assert "pii" in result.lower() def test_secret_blocked_for_web_search(self): ts = TaintSet.from_labels(TaintLabel.SECRET) result = check_taint("web_search", ts) assert result is not None assert "secret" in result.lower() def test_secret_blocked_for_channel_send(self): ts = TaintSet.from_labels(TaintLabel.SECRET) result = check_taint("channel_send", ts) assert result is not None def test_external_allowed_for_web_search(self): ts = TaintSet.from_labels(TaintLabel.EXTERNAL) assert check_taint("web_search", ts) is None def test_unknown_tool_allowed(self): ts = TaintSet.from_labels(TaintLabel.PII, TaintLabel.SECRET) assert check_taint("calculator", ts) is None def test_sink_policy_has_expected_tools(self): assert "web_search" in SINK_POLICY assert "channel_send" in SINK_POLICY assert "code_interpreter" in SINK_POLICY class TestDeclassify: def test_remove_label(self): ts = TaintSet.from_labels(TaintLabel.PII, TaintLabel.SECRET) result = declassify(ts, TaintLabel.PII, "User consent given") assert not result.has(TaintLabel.PII) assert result.has(TaintLabel.SECRET) def test_remove_nonexistent_label(self): ts = TaintSet.from_labels(TaintLabel.PII) result = declassify(ts, TaintLabel.SECRET, "Not present") assert result.has(TaintLabel.PII) class TestAutoDetect: def test_detect_email(self): ts = auto_detect_taint("Contact: user@example.com") assert ts.has(TaintLabel.PII) def test_detect_ssn(self): ts = auto_detect_taint("SSN: 123-45-6789") assert ts.has(TaintLabel.PII) def test_detect_api_key(self): ts = auto_detect_taint("Key: sk-abc123def456ghi789jkl012mno") assert ts.has(TaintLabel.SECRET) def test_detect_github_token(self): ts = auto_detect_taint("Token: ghp_abcdefghijklmnopqrstuvwxyz0123456789") assert ts.has(TaintLabel.SECRET) def test_clean_text(self): ts = auto_detect_taint("Hello, this is a normal message.") assert not ts def test_detect_private_key(self): ts = auto_detect_taint("-----BEGIN RSA PRIVATE KEY-----\nMIIE...") assert ts.has(TaintLabel.SECRET) class TestPropagate: def test_propagate_input_taint(self): input_taint = TaintSet.from_labels(TaintLabel.EXTERNAL) result = propagate_taint(input_taint, "Normal output") assert result.has(TaintLabel.EXTERNAL) def test_propagate_detects_new_taint(self): input_taint = TaintSet() result = propagate_taint(input_taint, "Found: user@example.com") assert result.has(TaintLabel.PII) def test_propagate_merges(self): input_taint = TaintSet.from_labels(TaintLabel.EXTERNAL) result = propagate_taint(input_taint, "Key: sk-abc123def456ghi789jkl012mno") assert result.has(TaintLabel.EXTERNAL) assert result.has(TaintLabel.SECRET)