d815339c52
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)
109 lines
3.2 KiB
Python
109 lines
3.2 KiB
Python
"""ASV benchmarks for SEC5 secrets redaction throughput.
|
|
|
|
Measures the performance of the core redaction functions to ensure
|
|
masking overhead stays within acceptable bounds.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# Ensure the local *source* tree is importable even when ASV has an
|
|
# older build of the package installed.
|
|
_SRC = str(Path(__file__).resolve().parents[1] / "src")
|
|
if _SRC not in sys.path:
|
|
sys.path.insert(0, _SRC)
|
|
|
|
import cleveragents # noqa: E402
|
|
|
|
importlib.reload(cleveragents)
|
|
|
|
from cleveragents.shared.redaction import ( # noqa: E402
|
|
is_sensitive_key,
|
|
mask_database_url,
|
|
redact_dict,
|
|
redact_value,
|
|
secrets_masking_processor,
|
|
)
|
|
|
|
# ── Sample data ───────────────────────────────────────────────────
|
|
|
|
_SECRET_STRING = "My API key is sk-proj-abc123def456ghi789jkl012mno345"
|
|
_CLEAN_STRING = "This is a perfectly normal log message with no secrets"
|
|
_MIXED_DICT: dict[str, object] = {
|
|
"api_key": "sk-proj-abc123def456ghi789",
|
|
"username": "alice",
|
|
"password": "hunter2",
|
|
"config": {
|
|
"token": "tok_01HXYZ1234567890ABCDEF",
|
|
"endpoint": "https://api.example.com",
|
|
},
|
|
"message": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWI",
|
|
}
|
|
_POSTGRES_URL = "postgresql://admin:s3cret@db.prod.internal:5432/mydb"
|
|
_SQLITE_URL = "sqlite:///data/cleveragents.db"
|
|
|
|
|
|
# ── Benchmarks ────────────────────────────────────────────────────
|
|
|
|
|
|
class RedactValue:
|
|
"""Benchmark ``redact_value`` for secret and clean strings."""
|
|
|
|
def time_redact_secret_string(self) -> None:
|
|
redact_value(_SECRET_STRING)
|
|
|
|
def time_redact_clean_string(self) -> None:
|
|
redact_value(_CLEAN_STRING)
|
|
|
|
def time_redact_empty_string(self) -> None:
|
|
redact_value("")
|
|
|
|
|
|
class RedactDict:
|
|
"""Benchmark ``redact_dict`` for nested dicts."""
|
|
|
|
def time_redact_mixed_dict(self) -> None:
|
|
redact_dict(_MIXED_DICT) # type: ignore[arg-type]
|
|
|
|
def time_redact_empty_dict(self) -> None:
|
|
redact_dict({})
|
|
|
|
|
|
class IsSensitiveKey:
|
|
"""Benchmark ``is_sensitive_key`` lookups."""
|
|
|
|
def time_sensitive_key(self) -> None:
|
|
is_sensitive_key("api_key")
|
|
|
|
def time_non_sensitive_key(self) -> None:
|
|
is_sensitive_key("username")
|
|
|
|
def time_false_positive_key(self) -> None:
|
|
is_sensitive_key("token_count")
|
|
|
|
|
|
class MaskDatabaseUrl:
|
|
"""Benchmark ``mask_database_url`` for various URL schemes."""
|
|
|
|
def time_mask_postgres_url(self) -> None:
|
|
mask_database_url(_POSTGRES_URL)
|
|
|
|
def time_mask_sqlite_url(self) -> None:
|
|
mask_database_url(_SQLITE_URL)
|
|
|
|
|
|
class StructlogProcessor:
|
|
"""Benchmark the structlog masking processor."""
|
|
|
|
def time_processor_with_secrets(self) -> None:
|
|
event = dict(_MIXED_DICT)
|
|
event["event"] = "Request failed with key sk-ant-api03-xyz123abc456"
|
|
secrets_masking_processor(None, "info", event)
|
|
|
|
def time_processor_clean_event(self) -> None:
|
|
event = {"event": "User logged in", "user": "alice", "status": "ok"}
|
|
secrets_masking_processor(None, "info", event)
|