Files
cleveragents-core/features/security_secrets.feature
T
khyari hamza d815339c52 feat(security): add secrets masking and validation (H-21/SEC5)
Implement centralized redaction utility that masks API keys, tokens,
and credentials across CLI output, structlog logs, and error messages.

- Add shared/redaction.py with pattern-based secret detection (sk-*,
  sk-ant-*, tok_*, Bearer tokens), sensitive key name detection,
  database URL masking, custom pattern registration, and thread-safe
  global show_secrets flag
- Add config/logging.py with structlog configuration integrating the
  secrets_masking_processor into the processor chain
- Add --show-secrets global CLI option to reveal secrets when needed
- Redact error details in main.py, project.py, and auto_debug.py
  error handlers before printing
- Wrap format_output() in formatting.py with automatic dict redaction
- Add show_secrets field and safe __repr__ to Settings model
- Add 43-scenario Behave feature (features/security_secrets.feature)
- Add 10 Robot Framework smoke tests (robot/security_secrets.robot)
- Add ASV benchmarks (benchmarks/security_secrets_bench.py)
- Add reference docs (docs/reference/secrets_handling.md)
2026-02-19 11:15:18 +00:00

189 lines
7.0 KiB
Gherkin

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 "<key>" 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 "<key>" 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"