Feature: SEC5 - Secrets masking and validation As a security-conscious developer I want secrets automatically masked in logs and CLI output So that API keys and tokens are never leaked # --- Core redaction utility --- Scenario: Redact OpenAI API key pattern Given a string containing "sk-proj-abc123def456ghi789" When I redact the string Then the redacted value should be "***REDACTED***" Scenario: Redact Anthropic API key pattern Given a string containing "sk-ant-api03-xyzzy1234567890abcdef" When I redact the string Then the redacted value should be "***REDACTED***" Scenario: Redact token pattern Given a string containing "tok_01HXYZ1234567890ABCDEF" When I redact the string Then the redacted value should be "***REDACTED***" Scenario: Redact generic bearer token Given a string containing "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJz" When I redact the string Then the redacted value should contain "***REDACTED***" Scenario: Non-secret strings are not redacted Given a string containing "hello world this is normal text" When I redact the string Then the redacted value should be "hello world this is normal text" Scenario: Mixed text with embedded secret is partially redacted Given a string containing "My key is sk-proj-abc123def456ghi789 and name is Bob" When I redact the string Then the redacted value should contain "***REDACTED***" And the redacted value should contain "My key is" And the redacted value should contain "and name is Bob" Scenario: Empty string is returned unchanged Given a string containing "" When I redact the string Then the redacted value should be empty # --- Dict redaction --- Scenario: Dict with sensitive key names are masked Given a dict with key "api_key" and value "sk-proj-abc123" When I redact the dict Then the key "api_key" should have value "***REDACTED***" Scenario: Dict with password key is masked Given a dict with key "password" and value "mysecretpass" When I redact the dict Then the key "password" should have value "***REDACTED***" Scenario: Dict with secret key is masked Given a dict with key "secret" and value "topsecretvalue" When I redact the dict Then the key "secret" should have value "***REDACTED***" Scenario: Dict with token key is masked Given a dict with key "token" and value "sometoken123" When I redact the dict Then the key "token" should have value "***REDACTED***" Scenario: Dict with credential key is masked Given a dict with key "credential" and value "cred-value" When I redact the dict Then the key "credential" should have value "***REDACTED***" Scenario: Dict with non-sensitive key is not masked Given a dict with key "username" and value "bob" When I redact the dict Then the key "username" should have value "bob" Scenario: Nested dict values are redacted recursively Given a nested dict with inner key "api_key" and value "sk-proj-nested" When I redact the dict Then the nested key "api_key" should have value "***REDACTED***" Scenario: Dict with pattern-matched value is masked regardless of key name Given a dict with key "config_value" and value "sk-ant-api03-somekey123" When I redact the dict Then the key "config_value" should have value "***REDACTED***" Scenario: Dict redaction respects show_secrets flag Given a dict with key "api_key" and value "sk-proj-abc123" When I redact the dict with show_secrets enabled Then the key "api_key" should have value "sk-proj-abc123" # --- Sensitive key detection --- Scenario Outline: Sensitive key names are detected When I check if "" is a sensitive key Then the sensitivity check should be true Examples: | key | | api_key | | API_KEY | | password | | secret | | token | | credential | | openai_api_key | | ANTHROPIC_API_KEY | | access_token | | auth_token | | private_key | Scenario Outline: Non-sensitive key names are not flagged When I check if "" is a sensitive key Then the sensitivity check should be false Examples: | key | | username | | email | | name | | description | | project_id | | token_count | # --- Database URL masking --- Scenario: SQLite URL is not masked Given a database URL "sqlite:///path/to/db.sqlite" When I mask the database URL Then the masked URL should be "sqlite:///path/to/db.sqlite" Scenario: PostgreSQL URL with credentials is masked Given a database URL "postgresql://admin:s3cret@localhost:5432/mydb" When I mask the database URL Then the masked URL should contain "***" And the masked URL should contain "localhost" And the masked URL should not contain "s3cret" # --- Global show_secrets flag --- Scenario: Global show_secrets defaults to false When I check the global show_secrets flag Then the flag should be false Scenario: Global show_secrets can be toggled When I set the global show_secrets flag to true Then the flag should be true When I set the global show_secrets flag to false Then the flag should be false # --- structlog processor --- Scenario: Structlog masking processor redacts event dict values Given a structlog event dict with key "api_key" and value "sk-proj-secret123" When the masking processor is applied Then the event dict key "api_key" should be "***REDACTED***" Scenario: Structlog masking processor redacts event message Given a structlog event dict with event "Failed with key sk-ant-api03-xyz" When the masking processor is applied Then the event message should contain "***REDACTED***" And the event message should not contain "sk-ant-api03-xyz" Scenario: Structlog masking processor passes non-secret events Given a structlog event dict with event "User logged in successfully" When the masking processor is applied Then the event message should be "User logged in successfully" # --- CLI error detail redaction --- Scenario: Error details with secret values are redacted Given error details with key "api_key" and value "sk-proj-leaked" When I redact the error details for CLI display Then the redacted details key "api_key" should be "***REDACTED***" Scenario: Error details with non-secret values are preserved Given error details with key "plan_id" and value "01HXR123" When I redact the error details for CLI display Then the redacted details key "plan_id" should be "01HXR123" # --- Secret pattern registration --- Scenario: Custom secret pattern can be registered Given the default secret patterns When I register a custom pattern "custom_[a-z0-9]{16}" And I redact the string "my custom_abcdef1234567890 key" Then the redacted value should contain "***REDACTED***" And the redacted value should not contain "custom_abcdef1234567890"