Files
cleveragents-core/benchmarks/security_secrets_bench.py
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

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)