"""Tests for agent.redact -- secret masking in logs and output.""" import logging import pytest from agent.redact import redact_sensitive_text, RedactingFormatter class TestKnownPrefixes: def test_openai_sk_key(self): text = "Using key sk-proj-abc123def456ghi789jkl012" result = redact_sensitive_text(text) assert "sk-pro" in result assert "abc123def456" not in result assert "..." in result def test_openrouter_sk_key(self): text = "OPENROUTER_API_KEY=sk-or-v1-abcdefghijklmnopqrstuvwxyz1234567890" result = redact_sensitive_text(text) assert "abcdefghijklmnop" not in result def test_github_pat_classic(self): result = redact_sensitive_text("token: ghp_abc123def456ghi789jkl") assert "abc123def456" not in result def test_github_pat_fine_grained(self): result = redact_sensitive_text("github_pat_abc123def456ghi789jklmno") assert "abc123def456" not in result def test_slack_token(self): token = "xoxb-" + "0" * 12 + "-" + "a" * 14 result = redact_sensitive_text(token) assert "a" * 14 not in result def test_google_api_key(self): result = redact_sensitive_text("AIzaSyB-abc123def456ghi789jklmno012345") assert "abc123def456" not in result def test_perplexity_key(self): result = redact_sensitive_text("pplx-abcdef123456789012345") assert "abcdef12345" not in result def test_fal_key(self): result = redact_sensitive_text("fal_abc123def456ghi789jkl") assert "abc123def456" not in result def test_short_token_fully_masked(self): result = redact_sensitive_text("key=sk-short1234567") assert "***" in result class TestEnvAssignments: def test_export_api_key(self): text = "export OPENAI_API_KEY=sk-proj-abc123def456ghi789jkl012" result = redact_sensitive_text(text) assert "OPENAI_API_KEY=" in result assert "abc123def456" not in result def test_quoted_value(self): text = 'MY_SECRET_TOKEN="supersecretvalue123456789"' result = redact_sensitive_text(text) assert "MY_SECRET_TOKEN=" in result assert "supersecretvalue" not in result def test_non_secret_env_unchanged(self): text = "HOME=/home/user" result = redact_sensitive_text(text) assert result == text def test_path_unchanged(self): text = "PATH=/usr/local/bin:/usr/bin" result = redact_sensitive_text(text) assert result == text class TestJsonFields: def test_json_api_key(self): text = '{"apiKey": "sk-proj-abc123def456ghi789jkl012"}' result = redact_sensitive_text(text) assert "abc123def456" not in result def test_json_token(self): text = '{"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.longtoken.here"}' result = redact_sensitive_text(text) assert "eyJhbGciOiJSUzI1NiIs" not in result def test_json_non_secret_unchanged(self): text = '{"name": "John", "model": "gpt-4"}' result = redact_sensitive_text(text) assert result == text class TestAuthHeaders: def test_bearer_token(self): text = "Authorization: Bearer sk-proj-abc123def456ghi789jkl012" result = redact_sensitive_text(text) assert "Authorization: Bearer" in result assert "abc123def456" not in result def test_case_insensitive(self): text = "authorization: bearer mytoken123456789012345678" result = redact_sensitive_text(text) assert "mytoken12345" not in result class TestTelegramTokens: def test_bot_token(self): text = "bot123456789:ABCDEfghij-KLMNopqrst_UVWXyz12345" result = redact_sensitive_text(text) assert "ABCDEfghij" not in result assert "123456789:***" in result def test_raw_token(self): text = "12345678901:ABCDEfghijKLMNopqrstUVWXyz1234567890" result = redact_sensitive_text(text) assert "ABCDEfghij" not in result class TestPassthrough: def test_empty_string(self): assert redact_sensitive_text("") == "" def test_none_returns_none(self): assert redact_sensitive_text(None) is None def test_normal_text_unchanged(self): text = "Hello world, this is a normal log message with no secrets." assert redact_sensitive_text(text) == text def test_code_unchanged(self): text = "def main():\n print('hello')\n return 42" assert redact_sensitive_text(text) == text def test_url_without_key_unchanged(self): text = "Connecting to https://api.openai.com/v1/chat/completions" assert redact_sensitive_text(text) == text class TestRedactingFormatter: def test_formats_and_redacts(self): formatter = RedactingFormatter("%(message)s") record = logging.LogRecord( name="test", level=logging.INFO, pathname="", lineno=0, msg="Key is sk-proj-abc123def456ghi789jkl012", args=(), exc_info=None, ) result = formatter.format(record) assert "abc123def456" not in result assert "sk-pro" in result class TestPrintenvSimulation: """Simulate what happens when the agent runs `env` or `printenv`.""" def test_full_env_dump(self): env_dump = """HOME=/home/user PATH=/usr/local/bin:/usr/bin OPENAI_API_KEY=sk-proj-abc123def456ghi789jkl012mno345 OPENROUTER_API_KEY=sk-or-v1-reallyLongSecretKeyValue12345678 FIRECRAWL_API_KEY=fc-shortkey123456789012 TELEGRAM_BOT_TOKEN=bot987654321:ABCDEfghij-KLMNopqrst_UVWXyz12345 SHELL=/bin/bash USER=teknium""" result = redact_sensitive_text(env_dump) # Secrets should be masked assert "abc123def456" not in result assert "reallyLongSecretKey" not in result assert "ABCDEfghij" not in result # Non-secrets should survive assert "HOME=/home/user" in result assert "SHELL=/bin/bash" in result assert "USER=teknium" in result