Files
Open_Ultra_SAST/tests/test_redaction.py
T
Marius CiepluchandClaude Opus 4.8 b5e40318db feat(hardening): Phase 15 — redaction, retry/backoff, budgets, threat-model docs
Release-readiness hardening.

- redaction.py (zero-dep): redact_secrets masks provider API keys, AWS/GitHub/Slack/
  Google tokens, bearer tokens, URL-embedded credentials, PEM private keys, and
  sensitive key=value assignments. Wired into the trace writer and the markdown report
  (on by default), so scan artifacts never leak a credential quoted from source.
- provider/openrouter.py: call_with_retry — exponential backoff on transient failures
  (HTTP 429/5xx, connection errors, timeouts); non-transient errors fail fast. Both the
  chat and embedding clients use it (max_attempts/retry_base_delay configurable).
- config [hardening]: redact_secrets (default true) + max_findings (bounded-CI output
  cap, 0 = unlimited; truncation is severity-ordered and disclosed as a `budget`
  degradation). Agentic spend stays bounded by [harnessx] max_cost_usd/token_threshold.
- docs/threat-model.md (trust boundaries, controls, sandbox limits) + docs/examples.md
  (six end-to-end walkthroughs); README "Security & hardening" section links both.
- CI lint/typecheck/test/detection-gate/dagger gates already existed.

tests/test_redaction.py, test_provider_retry.py, test_hardening_pipeline.py + config
tests. 206 passed, 4 skipped; ruff + mypy clean; detection gate PASS.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-22 20:02:34 +02:00

56 lines
2.1 KiB
Python

"""Secret redaction for traces and reports (Phase 15).
Example secrets are assembled at runtime (string concatenation) so no scannable token
literal lives in the repository, while still exercising the redaction patterns.
"""
from openultrasast.redaction import REDACTED, redact_secrets
def test_redacts_provider_api_keys() -> None:
secret = "sk-" + "abcdEFGH1234567890zzzz"
out = redact_secrets(f"key {secret} used")
assert secret not in out and REDACTED in out
def test_redacts_aws_github_slack_google_keys() -> None:
aws = "AKIA" + "IOSFODNN7EXAMPLE"
github = "ghp_" + "0123456789abcdefghij" + "ABCDEFGHIJ012345"
slack = "xoxb-" + "123456789012" + "-abcdefghijklmno"
google = "AIza" + "SyA1234567890_abcdefghijklmnopqrst"
assert aws not in redact_secrets(aws)
assert github not in redact_secrets(github)
assert slack not in redact_secrets(slack)
assert google not in redact_secrets(google)
def test_redacts_bearer_token_but_keeps_scheme() -> None:
out = redact_secrets("Authorization: Bearer " + "abcDEF123456ghiJKL")
assert out == f"Authorization: Bearer {REDACTED}"
def test_redacts_url_embedded_credentials() -> None:
password = "s3cret" + "Pass"
out = redact_secrets("postgres://admin:" + password + "@db.internal:5432/app")
assert password not in out and "admin" in out # user kept, password masked
def test_redacts_key_value_assignments() -> None:
pw = "hunter2" + "value"
tok = "tok_" + "abcdefgh"
assert pw not in redact_secrets('password = "' + pw + '"')
assert tok not in redact_secrets("api_key: " + tok)
def test_redacts_private_key_block() -> None:
body = "MIIE" + "abc"
pem = "-----BEGIN RSA PRIVATE KEY-----\n" + body + "\n-----END RSA PRIVATE KEY-----"
assert body not in redact_secrets(pem)
def test_leaves_benign_text_untouched_and_is_idempotent() -> None:
benign = "return eval(request.data) # CWE-95 code injection"
assert redact_secrets(benign) == benign
once = redact_secrets("sk-" + "abcdEFGH1234567890zzzz")
assert redact_secrets(once) == once # idempotent