""" Unit tests for the pre-commit secret leak scanner. Follows TDD: tests were written before implementation. """ import re import sys import unittest from pathlib import Path # Add .githooks to path so we can import pre-commit.py as a module sys.path.insert(0, str(Path(__file__).resolve().parent.parent / ".githooks")) # The module name contains a hyphen, so we import via importlib import importlib.util _spec = importlib.util.spec_from_file_location( "pre_commit_secret_leak", str(Path(__file__).resolve().parent.parent / ".githooks" / "pre-commit.py"), ) pre_commit = importlib.util.module_from_spec(_spec) _spec.loader.exec_module(pre_commit) class TestSecretPatterns(unittest.TestCase): """Tests for individual secret detection patterns.""" # ------------------------------------------------------------------ # API keys # ------------------------------------------------------------------ def test_detects_openai_sk_key(self): line = 'api_key = "sk-abcdefghijklmnopqrstuvwxyz1234"' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertTrue(findings) self.assertIn("sk-", findings[0].message) def test_detects_bearer_token(self): line = 'headers = {"Authorization": "Bearer abcdefghijklmnopqrstuvwxyz1234"}' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertTrue(findings) self.assertIn("Bearer", findings[0].message) def test_short_bearer_ignored(self): line = 'Authorization: Bearer short' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertFalse(findings) # ------------------------------------------------------------------ # Environment variable assignments # ------------------------------------------------------------------ def test_detects_openai_api_key_assignment(self): line = 'OPENAI_API_KEY=sk-abcdefghijklmnopqrstuvwxyz1234' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertTrue(findings) def test_detects_gitea_token_assignment(self): line = 'GITEA_TOKEN=gtl_abcdefghijklmnopqrstuvwxyz1234' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertTrue(findings) def test_detects_anthropic_key_assignment(self): line = 'ANTHROPIC_API_KEY=sk-ant-abcdefghijklmnopqrstuvwxyz1234' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertTrue(findings) def test_detects_kimi_key_assignment(self): line = 'KIMI_API_KEY=abcdef1234567890abcdef1234567890' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertTrue(findings) def test_detects_telegram_token_assignment(self): line = 'TELEGRAM_BOT_TOKEN=123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertTrue(findings) def test_detects_discord_token_assignment(self): line = 'DISCORD_TOKEN=MzIwNDE5MzA1NjUyNDgzMjY0.DSDsdQ.oM6WmR2i_uIvJhMZZZz0' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertTrue(findings) # ------------------------------------------------------------------ # Safe env reads / placeholders # ------------------------------------------------------------------ def test_os_environ_get_is_safe(self): line = 'key = os.environ.get("OPENAI_API_KEY")' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertFalse(findings) def test_placeholder_your_api_key_is_safe(self): line = 'OPENAI_API_KEY=' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertFalse(findings) def test_placeholder_stars_is_safe(self): line = 'OPENAI_API_KEY=***' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertFalse(findings) def test_placeholder_redacted_is_safe(self): line = 'OPENAI_API_KEY=REDACTED' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertFalse(findings) def test_env_var_reference_is_safe(self): line = 'OPENAI_API_KEY=$OPENAI_API_KEY' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertFalse(findings) def test_empty_env_assignment_is_safe(self): line = 'OPENAI_API_KEY=' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertFalse(findings) # ------------------------------------------------------------------ # Token file paths # ------------------------------------------------------------------ def test_detects_dotenv_path(self): line = 'load_dotenv(".env")' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertTrue(findings) def test_detects_secrets_json_path(self): line = 'with open("secrets.json") as f:' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertTrue(findings) def test_detects_keystore_json_path(self): line = 'keystore = "/root/nostr-relay/keystore.json"' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertTrue(findings) def test_detects_hermes_credentials_path(self): line = 'creds_path = "~/.hermes/credentials/default.json"' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertTrue(findings) def test_detects_credentials_json(self): line = 'with open("credentials.json") as f:' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertTrue(findings) def test_detects_token_json(self): line = 'token_file = "token.json"' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertTrue(findings) def test_detects_api_keys_json(self): line = 'keys = "api_keys.json"' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertTrue(findings) # ------------------------------------------------------------------ # Private key blocks # ------------------------------------------------------------------ def test_detects_begin_private_key(self): line = '-----BEGIN PRIVATE KEY-----' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertTrue(findings) def test_detects_begin_rsa_private_key(self): line = '-----BEGIN RSA PRIVATE KEY-----' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertTrue(findings) def test_detects_begin_openssh_private_key(self): line = '-----BEGIN OPENSSH PRIVATE KEY-----' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertTrue(findings) # ------------------------------------------------------------------ # Passwords in URLs # ------------------------------------------------------------------ def test_detects_password_in_https_url(self): line = 'url = "https://user:secretpassword@example.com/repo.git"' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertTrue(findings) self.assertIn("password", findings[0].message.lower()) def test_detects_password_in_http_url(self): line = 'http://admin:password123@internal.local' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertTrue(findings) # ------------------------------------------------------------------ # Raw token patterns in strings # ------------------------------------------------------------------ def test_detects_raw_token_in_json(self): line = '{"token": "abcdefghijklmnopqrstuvwxyz"}' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertTrue(findings) self.assertIn("token", findings[0].message.lower()) def test_detects_raw_api_key_in_json(self): line = '{"api_key": "1234567890abcdef"}' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertTrue(findings) self.assertIn("api_key", findings[0].message.lower()) def test_short_token_ignored(self): line = '{"token": "short"}' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertFalse(findings) # ------------------------------------------------------------------ # Documentation / example safe patterns # ------------------------------------------------------------------ def test_documentation_reference_is_safe(self): line = 'See the documentation at https://docs.example.com' findings = list(pre_commit.scan_line(line, "test.py", 1)) # No specific pattern should match a doc URL without a password self.assertFalse(findings) def test_example_code_comment_is_safe(self): line = '# Example: OPENAI_API_KEY=' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertFalse(findings) def test_doc_string_with_placeholder_is_safe(self): line = '"""Set ANTHROPIC_API_KEY to $ANTHROPIC_API_KEY in production."""' findings = list(pre_commit.scan_line(line, "test.py", 1)) self.assertFalse(findings) class TestScanContent(unittest.TestCase): """Tests for scanning multi-line content.""" def test_scan_content_finds_multiple_leaks(self): content = """ OPENAI_API_KEY=sk-12345678901234567890 Some normal code here GITEA_TOKEN=gtl_12345678901234567890 """ findings = pre_commit.scan_content(content, "test.py") self.assertEqual(len(findings), 2) # Should have line numbers self.assertIn(2, [f.line for f in findings]) self.assertIn(4, [f.line for f in findings]) def test_scan_content_returns_empty_when_clean(self): content = "print('hello world')\n" findings = pre_commit.scan_content(content, "test.py") self.assertEqual(findings, []) class TestScanFiles(unittest.TestCase): """Tests for the file-list scanning entrypoint.""" def test_scan_files_skips_binary(self): files = ["image.png", "test.py"] content_map = { "image.png": b"\x89PNG\r\n\x1a\n", "test.py": "OPENAI_API_KEY=sk-12345678901234567890\n", } findings = pre_commit.scan_files(files, lambda f: content_map.get(f, b"")) self.assertEqual(len(findings), 1) self.assertEqual(findings[0].filename, "test.py") def test_scan_files_ignores_safe_lines(self): files = ["test.py"] content_map = { "test.py": "key = os.environ.get('OPENAI_API_KEY')\n", } findings = pre_commit.scan_files(files, lambda f: content_map.get(f, b"")) self.assertEqual(findings, []) class TestCliHelpers(unittest.TestCase): """Tests for CLI helper functions.""" def test_color_codes_present(self): self.assertIn("\033[", pre_commit.RED) self.assertIn("\033[", pre_commit.GREEN) def test_is_binary_content_true(self): self.assertTrue(pre_commit.is_binary_content(b"\x00\x01\x02")) def test_is_binary_content_false(self): self.assertFalse(pre_commit.is_binary_content(b"hello world\n")) if __name__ == "__main__": unittest.main()