Skip to main content
Glama
test_security_secrets.py35.7 kB
"""Comprehensive tests for security.secrets module.""" import io import sys import tempfile from pathlib import Path import pytest from mnemex.security.secrets import ( SecretMatch, detect_secrets, format_secret_warning, main, redact_secrets, scan_file_for_secrets, should_warn_about_secrets, ) class TestSecretMatch: """Tests for SecretMatch dataclass.""" def test_create_secret_match_instance(self): """Test creating a SecretMatch instance.""" match = SecretMatch( secret_type="api_key", position=10, context="API key: sk-...", ) assert match.secret_type == "api_key" assert match.position == 10 assert match.context == "API key: sk-..." def test_secret_match_attributes(self): """Test SecretMatch has correct attributes.""" match = SecretMatch( secret_type="aws_access_key", position=42, context="AWS key: AKIA...", ) assert hasattr(match, "secret_type") assert hasattr(match, "position") assert hasattr(match, "context") def test_secret_match_with_different_types(self): """Test creating SecretMatch with various secret types.""" types = ["openai_key", "github_token", "database_url", "jwt_token"] for secret_type in types: match = SecretMatch( secret_type=secret_type, position=0, context="test context", ) assert match.secret_type == secret_type class TestDetectSecretsAPIKeys: """Tests for detect_secrets function - API keys.""" def test_detect_generic_api_key_uppercase(self): """Test detecting generic API_KEY pattern.""" text = "API_KEY=abcd1234efgh5678ijkl9012mnop3456qrst" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "api_key" for m in matches) def test_detect_generic_api_key_lowercase(self): """Test detecting lowercase api_key pattern.""" text = "api_key: abcd1234efgh5678ijkl9012mnop3456qrst" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "api_key" for m in matches) def test_detect_api_token(self): """Test detecting API_TOKEN pattern.""" text = "API_TOKEN = xyz123abc456def789ghi012jkl345mno" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "api_key" for m in matches) def test_detect_access_key(self): """Test detecting ACCESS_KEY pattern.""" text = 'ACCESS_KEY="test1234567890abcdefghij"' matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "api_key" for m in matches) class TestDetectSecretsAWS: """Tests for detect_secrets function - AWS credentials.""" def test_detect_aws_access_key_id(self): """Test detecting AWS access key (AKIA...).""" text = "AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "aws_access_key" for m in matches) def test_detect_aws_access_key_in_sentence(self): """Test detecting AWS access key embedded in text.""" text = "My access key is AKIAI44QH8DHBEXAMPLE and it's secret" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "aws_access_key" for m in matches) def test_detect_aws_secret_access_key(self): """Test detecting AWS secret access key.""" text = "aws_secret_access_key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "aws_secret_key" for m in matches) def test_detect_aws_secret_with_equals(self): """Test detecting AWS secret with equals sign.""" text = "AWS_SECRET_ACCESS_KEY=abcd1234efgh5678ijkl9012mnop3456qrst7890" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "aws_secret_key" for m in matches) class TestDetectSecretsGitHub: """Tests for detect_secrets function - GitHub tokens.""" def test_detect_github_personal_access_token(self): """Test detecting GitHub personal access token (ghp_).""" text = "GITHUB_TOKEN=ghp_1234567890abcdefghijklmnopqrstuvwxyzABCDEF" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type in ("github_token", "github_classic") for m in matches) def test_detect_github_oauth_token(self): """Test detecting GitHub OAuth token (gho_).""" text = "token: gho_abcdefghijklmnopqrstuvwxyz1234567890ABCDEF" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "github_token" for m in matches) def test_detect_github_user_to_server_token(self): """Test detecting GitHub user-to-server token (ghu_).""" text = "Authorization: ghu_1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "github_token" for m in matches) def test_detect_github_server_to_server_token(self): """Test detecting GitHub server-to-server token (ghs_).""" text = "GH_TOKEN=ghs_abcdefghijklmnopqrstuvwxyz1234567890ABCD" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "github_token" for m in matches) def test_detect_github_refresh_token(self): """Test detecting GitHub refresh token (ghr_).""" text = "refresh_token=ghr_1234567890abcdefghijklmnopqrstuvwxyzABCDEF" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "github_token" for m in matches) class TestDetectSecretsOpenAI: """Tests for detect_secrets function - OpenAI keys.""" def test_detect_openai_api_key(self): """Test detecting OpenAI API key (sk-).""" text = "OPENAI_API_KEY=sk-1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "openai_key" for m in matches) def test_detect_openai_key_in_code(self): """Test detecting OpenAI key in code snippet.""" text = 'client = OpenAI(api_key="sk-abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNO")' matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "openai_key" for m in matches) class TestDetectSecretsAnthropic: """Tests for detect_secrets function - Anthropic keys.""" def test_detect_anthropic_api_key(self): """Test detecting Anthropic API key (sk-ant-).""" text = "ANTHROPIC_API_KEY=sk-ant-api03-1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqr" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "anthropic_key" for m in matches) def test_detect_anthropic_key_with_hyphens(self): """Test detecting Anthropic key with hyphens.""" text = "api_key: sk-ant-api03-abcd-efgh-ijkl-mnop-qrst-uvwx-yzAB-CDEF-GHIJ-KLMN-OPQR-STUV-WXYZ-1234-5678-90ab-cdef-ghij-klmn" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "anthropic_key" for m in matches) class TestDetectSecretsGoogle: """Tests for detect_secrets function - Google API keys.""" def test_detect_google_api_key(self): """Test detecting Google API key (AIza...).""" text = "GOOGLE_API_KEY=AIzaSyD1234567890abcdefghijklmnopqrstuv" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "google_api_key" for m in matches) def test_detect_google_key_in_url(self): """Test detecting Google API key in URL.""" text = "https://maps.googleapis.com/maps/api/geocode/json?key=AIzaSyAbCdEfGhIjKlMnOpQrStUvWxYz1234567&address=test" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "google_api_key" for m in matches) class TestDetectSecretsSlack: """Tests for detect_secrets function - Slack tokens.""" def test_detect_slack_bot_token(self): """Test detecting Slack bot token (xoxb-).""" text = "SLACK_BOT_TOKEN=xoxb-1234567890-1234567890123-abcdefghijklmnopqrstuvwx" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "slack_token" for m in matches) def test_detect_slack_user_token(self): """Test detecting Slack user token (xoxp-).""" text = "token: xoxp-1234567890-1234567890-abcdefghijklmnopqrstuvwx" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "slack_token" for m in matches) def test_detect_slack_app_token(self): """Test detecting Slack app token (xoxa-).""" text = "SLACK_APP_TOKEN=xoxa-1234567890-1234567890123-abcdefghijklmnopqrstuvwx" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "slack_token" for m in matches) class TestDetectSecretsBearerTokens: """Tests for detect_secrets function - Bearer tokens.""" def test_detect_bearer_token_uppercase(self): """Test detecting Bearer token (uppercase).""" text = "Authorization: Bearer abcdef1234567890ghijkl" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "bearer_token" for m in matches) def test_detect_bearer_token_lowercase(self): """Test detecting bearer token (lowercase).""" text = "auth: bearer xyz123456789012345678901234567890" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "bearer_token" for m in matches) def test_detect_bearer_token_with_underscores_hyphens(self): """Test detecting bearer token with underscores and hyphens.""" text = "Bearer abc-def_123-456_789-012_345-678_901-234" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "bearer_token" for m in matches) class TestDetectSecretsJWT: """Tests for detect_secrets function - JWT tokens.""" def test_detect_jwt_token(self): """Test detecting JWT token.""" text = "token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "jwt_token" for m in matches) def test_detect_jwt_in_header(self): """Test detecting JWT in Authorization header.""" text = "Authorization: Bearer eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL2V4YW1wbGUuYXV0aDAuY29tLyJ9.abc123def456ghi789jkl012mno345pqr678stu901vwx234yz" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "jwt_token" for m in matches) class TestDetectSecretsPrivateKeys: """Tests for detect_secrets function - Private keys.""" def test_detect_rsa_private_key(self): """Test detecting RSA private key.""" text = """-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEA1234567890abcdefghijklmnop -----END RSA PRIVATE KEY-----""" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "private_key" for m in matches) def test_detect_private_key_generic(self): """Test detecting generic private key.""" text = """-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC -----END PRIVATE KEY-----""" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "private_key" for m in matches) def test_detect_private_key_lowercase(self): """Test detecting private key with lowercase.""" text = "-----begin private key-----\ndata\n-----end private key-----" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "private_key" for m in matches) class TestDetectSecretsDatabaseURLs: """Tests for detect_secrets function - Database URLs.""" def test_detect_postgres_url(self): """Test detecting PostgreSQL connection string.""" text = "DATABASE_URL=postgres://user:password123@localhost:5432/mydb" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "database_url" for m in matches) def test_detect_mysql_url(self): """Test detecting MySQL connection string.""" text = "DB_URL: mysql://admin:secretpass@db.example.com:3306/production" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "database_url" for m in matches) def test_detect_mongodb_url(self): """Test detecting MongoDB connection string.""" text = "MONGO_URI=mongodb://dbuser:dbpass123@mongo.example.com:27017/myapp" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "database_url" for m in matches) def test_detect_redis_url(self): """Test detecting Redis connection string.""" text = "REDIS_URL=redis://default:redispass@redis.local:6379/0" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "database_url" for m in matches) def test_detect_postgres_scheme(self): """Test detecting postgres:// scheme (not postgresql).""" text = "postgres://user:pass@host/db" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "database_url" for m in matches) class TestDetectSecretsPasswords: """Tests for detect_secrets function - Password assignments.""" def test_detect_password_equals(self): """Test detecting password with equals sign.""" text = "password=MySecretPass123!" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "password_assignment" for m in matches) def test_detect_passwd_colon(self): """Test detecting passwd with colon.""" text = "passwd: SuperSecret456" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "password_assignment" for m in matches) def test_detect_pwd_quoted(self): """Test detecting pwd in quotes.""" text = 'PWD="MyPassword789"' matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "password_assignment" for m in matches) def test_detect_password_case_insensitive(self): """Test detecting PASSWORD in uppercase.""" text = "PASSWORD = AdminPass2023" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "password_assignment" for m in matches) class TestDetectSecretsSecretAssignments: """Tests for detect_secrets function - Secret assignments.""" def test_detect_secret_assignment(self): """Test detecting secret assignment.""" text = "secret=abc123def456ghi789jkl012mno" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "secret_assignment" for m in matches) def test_detect_token_assignment(self): """Test detecting token assignment.""" text = "token: xyz987wvu654tsr321qpo098nml" matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type in ("secret_assignment", "api_key") for m in matches) def test_detect_credential_assignment(self): """Test detecting credential assignment.""" text = 'credential="abcdefghijklmnopqrstuvwxyz123456"' matches = detect_secrets(text) assert len(matches) >= 1 assert any(m.secret_type == "secret_assignment" for m in matches) class TestDetectSecretsMultipleAndEdgeCases: """Tests for detect_secrets function - Multiple secrets and edge cases.""" def test_multiple_secrets_in_same_text(self): """Test detecting multiple different secrets in same text.""" text = """ API_KEY=abcd1234efgh5678ijkl9012mnop AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE OPENAI_API_KEY=sk-1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN password=MySecretPass123 """ matches = detect_secrets(text) assert len(matches) >= 4 secret_types = {m.secret_type for m in matches} assert "api_key" in secret_types or "secret_assignment" in secret_types assert "aws_access_key" in secret_types assert "openai_key" in secret_types assert "password_assignment" in secret_types def test_text_with_no_secrets(self): """Test text containing no secrets.""" text = "This is just normal text without any secrets. Hello world!" matches = detect_secrets(text) assert len(matches) == 0 def test_empty_text(self): """Test empty text input.""" text = "" matches = detect_secrets(text) assert len(matches) == 0 def test_max_matches_parameter(self): """Test max_matches parameter limits results.""" text = "\n".join([f"api_key_{i}=abcd1234efgh5678ijkl9012mnop{i:04d}" for i in range(20)]) matches = detect_secrets(text, max_matches=5) assert len(matches) <= 5 def test_context_chars_parameter(self): """Test context_chars parameter affects context length.""" text = "x" * 100 + "API_KEY=abcd1234efgh5678ijkl9012mnop" + "y" * 100 matches_small = detect_secrets(text, context_chars=10) matches_large = detect_secrets(text, context_chars=50) assert len(matches_small) >= 1 assert len(matches_large) >= 1 assert len(matches_large[0].context) > len(matches_small[0].context) def test_secret_position_tracking(self): """Test that position is correctly tracked.""" text = "prefix text API_KEY=abcd1234efgh5678ijkl9012mnop suffix" matches = detect_secrets(text) assert len(matches) >= 1 assert matches[0].position >= 0 assert matches[0].position < len(text) def test_context_includes_redaction(self): """Test that context includes redacted secret.""" text = "API_KEY=abcd1234efgh5678ijkl9012mnop3456qrst" matches = detect_secrets(text) assert len(matches) >= 1 assert "..." in matches[0].context def test_short_secret_redaction(self): """Test redaction of short secrets.""" text = "secret=abcd1234efgh5678ijkl9012" matches = detect_secrets(text) assert len(matches) >= 1 assert "***" in matches[0].context or "..." in matches[0].context class TestScanFileForSecrets: """Tests for scan_file_for_secrets function.""" def test_scan_file_with_secrets(self): """Test scanning a file containing secrets.""" with tempfile.TemporaryDirectory() as tmpdir: test_file = Path(tmpdir) / "config.txt" test_file.write_text("API_KEY=abcd1234efgh5678ijkl9012mnop\nOTHER=value") matches = scan_file_for_secrets(str(test_file)) assert len(matches) >= 1 assert any(m.secret_type == "api_key" for m in matches) def test_scan_file_without_secrets(self): """Test scanning a file with no secrets.""" with tempfile.TemporaryDirectory() as tmpdir: test_file = Path(tmpdir) / "normal.txt" test_file.write_text("This is normal content\nNo secrets here") matches = scan_file_for_secrets(str(test_file)) assert len(matches) == 0 def test_scan_nonexistent_file_raises_error(self): """Test scanning non-existent file raises FileNotFoundError.""" with pytest.raises(FileNotFoundError, match="File not found"): scan_file_for_secrets("/nonexistent/path/file.txt") def test_scan_file_max_matches(self): """Test max_matches parameter in file scanning.""" with tempfile.TemporaryDirectory() as tmpdir: test_file = Path(tmpdir) / "many_secrets.txt" content = "\n".join( [f"api_key_{i}=abcd1234efgh5678ijkl9012mnop{i:04d}" for i in range(20)] ) test_file.write_text(content) matches = scan_file_for_secrets(str(test_file), max_matches=3) assert len(matches) <= 3 def test_scan_file_with_encoding_errors(self): """Test scanning file with encoding errors is handled.""" with tempfile.TemporaryDirectory() as tmpdir: test_file = Path(tmpdir) / "binary.txt" test_file.write_bytes(b"API_KEY=test1234567890abcdef\xff\xfe") matches = scan_file_for_secrets(str(test_file)) assert isinstance(matches, list) class TestFormatSecretWarning: """Tests for format_secret_warning function.""" def test_format_warning_with_single_secret(self): """Test formatting warning with one secret.""" matches = [SecretMatch(secret_type="api_key", position=10, context="key=abc...xyz")] warning = format_secret_warning(matches) assert "WARNING" in warning assert "1 potential secret" in warning assert "api_key" in warning def test_format_warning_with_multiple_secrets(self): """Test formatting warning with multiple secrets.""" matches = [ SecretMatch(secret_type="api_key", position=10, context="key1"), SecretMatch(secret_type="aws_access_key", position=50, context="key2"), SecretMatch(secret_type="openai_key", position=100, context="key3"), ] warning = format_secret_warning(matches) assert "WARNING" in warning assert "3 potential secrets" in warning assert "api_key" in warning assert "aws_access_key" in warning assert "openai_key" in warning def test_format_warning_with_no_secrets(self): """Test formatting warning with empty list.""" matches = [] warning = format_secret_warning(matches) assert warning == "" def test_format_warning_groups_by_type(self): """Test warning groups secrets by type.""" matches = [ SecretMatch(secret_type="api_key", position=10, context="key1"), SecretMatch(secret_type="api_key", position=20, context="key2"), SecretMatch(secret_type="aws_access_key", position=30, context="key3"), ] warning = format_secret_warning(matches) assert "api_key: 2" in warning assert "aws_access_key: 1" in warning def test_format_warning_includes_recommendations(self): """Test warning includes security recommendations.""" matches = [SecretMatch(secret_type="api_key", position=10, context="key")] warning = format_secret_warning(matches) assert "environment variables" in warning assert "secrets manager" in warning assert "MNEMEX_DETECT_SECRETS" in warning class TestShouldWarnAboutSecrets: """Tests for should_warn_about_secrets function.""" def test_warn_for_high_confidence_aws_key(self): """Test warning for high-confidence AWS key.""" matches = [SecretMatch(secret_type="aws_access_key", position=10, context="key")] assert should_warn_about_secrets(matches) is True def test_warn_for_high_confidence_openai_key(self): """Test warning for high-confidence OpenAI key.""" matches = [SecretMatch(secret_type="openai_key", position=10, context="key")] assert should_warn_about_secrets(matches) is True def test_warn_for_high_confidence_github_token(self): """Test warning for high-confidence GitHub token.""" matches = [SecretMatch(secret_type="github_token", position=10, context="key")] assert should_warn_about_secrets(matches) is True def test_warn_for_private_key(self): """Test warning for private key.""" matches = [SecretMatch(secret_type="private_key", position=10, context="key")] assert should_warn_about_secrets(matches) is True def test_warn_for_database_url(self): """Test warning for database URL.""" matches = [SecretMatch(secret_type="database_url", position=10, context="url")] assert should_warn_about_secrets(matches) is True def test_no_warn_for_single_low_confidence_match(self): """Test no warning for single low-confidence match.""" matches = [SecretMatch(secret_type="api_key", position=10, context="key")] assert should_warn_about_secrets(matches) is False def test_warn_for_multiple_low_confidence_matches(self): """Test warning for multiple low-confidence matches.""" matches = [ SecretMatch(secret_type="api_key", position=10, context="key1"), SecretMatch(secret_type="password_assignment", position=20, context="key2"), ] assert should_warn_about_secrets(matches) is True def test_no_warn_for_empty_matches(self): """Test no warning for empty matches.""" matches = [] assert should_warn_about_secrets(matches) is False def test_custom_min_confidence_types(self): """Test custom min_confidence_types parameter.""" matches = [SecretMatch(secret_type="api_key", position=10, context="key")] custom_types = {"api_key"} assert should_warn_about_secrets(matches, min_confidence_types=custom_types) is True def test_custom_min_confidence_excludes_others(self): """Test custom min_confidence_types excludes non-listed types.""" matches = [SecretMatch(secret_type="aws_access_key", position=10, context="key")] custom_types = {"api_key"} assert should_warn_about_secrets(matches, min_confidence_types=custom_types) is False class TestRedactSecrets: """Tests for redact_secrets function.""" def test_redact_api_key(self): """Test redacting API key.""" text = "API_KEY=abcd1234efgh5678ijkl9012mnop" redacted = redact_secrets(text) assert "abcd1234efgh5678ijkl9012mnop" not in redacted assert "***REDACTED***" in redacted def test_redact_aws_access_key(self): """Test redacting AWS access key.""" text = "AWS key: AKIAIOSFODNN7EXAMPLE" redacted = redact_secrets(text) assert "AKIAIOSFODNN7EXAMPLE" not in redacted assert "***REDACTED***" in redacted def test_redact_openai_key(self): """Test redacting OpenAI key.""" text = "OPENAI_API_KEY=sk-1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN" redacted = redact_secrets(text) assert "sk-1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN" not in redacted assert "***REDACTED***" in redacted def test_redact_github_token(self): """Test redacting GitHub token.""" text = "token: ghp_1234567890abcdefghijklmnopqrstuv" redacted = redact_secrets(text) assert "ghp_1234567890abcdefghijklmnopqrstuv" not in redacted assert "***REDACTED***" in redacted def test_redact_jwt_token(self): """Test redacting JWT token.""" text = "JWT: eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U" redacted = redact_secrets(text) assert "eyJhbGciOiJIUzI1NiJ9" not in redacted assert "***REDACTED***" in redacted def test_redact_database_url(self): """Test redacting database URL.""" text = "DB: postgres://user:pass@localhost/db" redacted = redact_secrets(text) assert "postgres://user:pass@localhost/db" not in redacted assert "***REDACTED***" in redacted def test_redact_private_key(self): """Test redacting private key.""" text = "Key: -----BEGIN PRIVATE KEY-----\ndata" redacted = redact_secrets(text) assert "-----BEGIN PRIVATE KEY-----" not in redacted assert "***REDACTED***" in redacted def test_redact_multiple_secrets(self): """Test redacting multiple secrets.""" text = "API_KEY=abc123def456ghi789jkl012 and PASSWORD=MySecret123" redacted = redact_secrets(text) assert "abc123def456ghi789jkl012" not in redacted assert "MySecret123" not in redacted assert redacted.count("***REDACTED***") >= 2 def test_redact_with_custom_replacement(self): """Test redacting with custom replacement string.""" text = "API_KEY=abcd1234efgh5678ijkl9012mnop" redacted = redact_secrets(text, replacement="[HIDDEN]") assert "abcd1234efgh5678ijkl9012mnop" not in redacted assert "[HIDDEN]" in redacted assert "***REDACTED***" not in redacted def test_redact_text_without_secrets(self): """Test redacting text with no secrets.""" text = "This is normal text without secrets" redacted = redact_secrets(text) assert redacted == text def test_redact_preserves_text_structure(self): """Test redacting preserves overall text structure.""" text = "Before API_KEY=abc123def456ghi789jkl012 after" redacted = redact_secrets(text) assert redacted.startswith("Before") assert redacted.endswith("after") assert "***REDACTED***" in redacted def test_redact_empty_text(self): """Test redacting empty text.""" text = "" redacted = redact_secrets(text) assert redacted == "" class TestCLIMain: """Tests for main() CLI function.""" def test_cli_scan_file_with_secrets(self, monkeypatch, capsys): """Test CLI scanning a file with secrets.""" with tempfile.TemporaryDirectory() as tmpdir: test_file = Path(tmpdir) / "config.txt" test_file.write_text("API_KEY=abcd1234efgh5678ijkl9012mnop") monkeypatch.setattr(sys, "argv", ["secrets", str(test_file)]) exit_code = main() assert exit_code == 1 captured = capsys.readouterr() assert "WARNING" in captured.err assert "api_key" in captured.err def test_cli_scan_file_without_secrets(self, monkeypatch, capsys): """Test CLI scanning a file without secrets.""" with tempfile.TemporaryDirectory() as tmpdir: test_file = Path(tmpdir) / "normal.txt" test_file.write_text("This is normal content") monkeypatch.setattr(sys, "argv", ["secrets", str(test_file)]) exit_code = main() assert exit_code == 0 captured = capsys.readouterr() assert "No secrets detected" in captured.err def test_cli_scan_stdin_with_secrets(self, monkeypatch, capsys): """Test CLI scanning stdin with secrets.""" stdin_data = "OPENAI_API_KEY=sk-1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN" monkeypatch.setattr(sys, "stdin", io.StringIO(stdin_data)) monkeypatch.setattr(sys, "argv", ["secrets"]) exit_code = main() assert exit_code == 1 captured = capsys.readouterr() assert "WARNING" in captured.err def test_cli_scan_stdin_without_secrets(self, monkeypatch, capsys): """Test CLI scanning stdin without secrets.""" stdin_data = "Normal text without secrets" monkeypatch.setattr(sys, "stdin", io.StringIO(stdin_data)) monkeypatch.setattr(sys, "argv", ["secrets"]) exit_code = main() assert exit_code == 0 captured = capsys.readouterr() assert "No secrets detected" in captured.err def test_cli_redact_mode_file(self, monkeypatch, capsys): """Test CLI redact mode with file.""" with tempfile.TemporaryDirectory() as tmpdir: test_file = Path(tmpdir) / "config.txt" test_file.write_text("API_KEY=abcd1234efgh5678ijkl9012mnop and more text") monkeypatch.setattr(sys, "argv", ["secrets", str(test_file), "--redact"]) exit_code = main() assert exit_code == 0 captured = capsys.readouterr() assert "***REDACTED***" in captured.out assert "abcd1234efgh5678ijkl9012mnop" not in captured.out def test_cli_redact_mode_stdin(self, monkeypatch, capsys): """Test CLI redact mode with stdin.""" stdin_data = "password=MySecret123 and other data" monkeypatch.setattr(sys, "stdin", io.StringIO(stdin_data)) monkeypatch.setattr(sys, "argv", ["secrets", "--redact"]) exit_code = main() assert exit_code == 0 captured = capsys.readouterr() assert "***REDACTED***" in captured.out assert "MySecret123" not in captured.out def test_cli_quiet_mode_with_secrets(self, monkeypatch, capsys): """Test CLI quiet mode when secrets are found.""" stdin_data = "API_KEY=abcd1234efgh5678ijkl9012mnop" monkeypatch.setattr(sys, "stdin", io.StringIO(stdin_data)) monkeypatch.setattr(sys, "argv", ["secrets", "--quiet"]) exit_code = main() assert exit_code == 1 captured = capsys.readouterr() assert captured.err == "" def test_cli_quiet_mode_without_secrets(self, monkeypatch, capsys): """Test CLI quiet mode when no secrets found.""" stdin_data = "Normal text" monkeypatch.setattr(sys, "stdin", io.StringIO(stdin_data)) monkeypatch.setattr(sys, "argv", ["secrets", "--quiet"]) exit_code = main() assert exit_code == 0 captured = capsys.readouterr() assert captured.err == "" def test_cli_max_matches_parameter(self, monkeypatch, capsys): """Test CLI with --max-matches parameter.""" with tempfile.TemporaryDirectory() as tmpdir: test_file = Path(tmpdir) / "many.txt" content = "\n".join([f"API_KEY=abcd1234efgh5678ijkl9012mnop{i:04d}" for i in range(10)]) test_file.write_text(content) monkeypatch.setattr(sys, "argv", ["secrets", str(test_file), "--max-matches", "3"]) exit_code = main() assert exit_code == 1 def test_cli_file_not_found_error(self, monkeypatch, capsys): """Test CLI with non-existent file.""" monkeypatch.setattr(sys, "argv", ["secrets", "/nonexistent/file.txt"]) exit_code = main() assert exit_code == 1 captured = capsys.readouterr() assert "Error:" in captured.err def test_cli_combined_redact_and_quiet(self, monkeypatch, capsys): """Test CLI with both --redact and --quiet flags.""" stdin_data = "API_KEY=abcd1234efgh5678ijkl9012mnop" monkeypatch.setattr(sys, "stdin", io.StringIO(stdin_data)) monkeypatch.setattr(sys, "argv", ["secrets", "--redact", "--quiet"]) exit_code = main() assert exit_code == 0 captured = capsys.readouterr() assert "***REDACTED***" in captured.out

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mnemexai/mnemex'

If you have feedback or need assistance with the MCP directory API, please join our Discord server