mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-18 04:29:27 +00:00
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]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
3fd0057e5f
commit
b5e40318db
@@ -149,7 +149,7 @@ def _run_scan(path: Path, config_path: Path, mode: str, fail_on: str) -> ScanOut
|
||||
runtime = HarnessRuntime(
|
||||
scan_id=run.scan_id,
|
||||
config=config,
|
||||
trace_writer=HarnessTraceWriter(run.root / "trace" / "events.jsonl"),
|
||||
trace_writer=HarnessTraceWriter(run.root / "trace" / "events.jsonl", redact=config.hardening.redact_secrets),
|
||||
)
|
||||
write_harness_config(config=config, processors=[], contract_mode="strict", path=run.root / "harness.json")
|
||||
runtime.start(mode=mode, target=run.target)
|
||||
@@ -219,6 +219,14 @@ def _run_scan(path: Path, config_path: Path, mode: str, fail_on: str) -> ScanOut
|
||||
if shadow_findings:
|
||||
findings = [finding for finding in findings if finding.status != "shadow"]
|
||||
write_findings(shadow_findings, run.root / "shadow_findings.json")
|
||||
# Bounded-CI budget: cap the reported finding count (0 = unlimited). Findings are
|
||||
# severity/priority-sorted, so truncation keeps the most severe and is disclosed.
|
||||
max_findings = config.hardening.max_findings
|
||||
if max_findings and len(findings) > max_findings:
|
||||
runtime.state["degradations"].append(
|
||||
{"stage": "budget", "reason": "max_findings_exceeded", "requested": max_findings, "actual": len(findings)}
|
||||
)
|
||||
findings = findings[:max_findings]
|
||||
findings_path = run.root / "findings.json"
|
||||
verification_path = run.root / "verification.json"
|
||||
markdown_path = run.root / "report.md"
|
||||
@@ -291,7 +299,9 @@ def _run_scan(path: Path, config_path: Path, mode: str, fail_on: str) -> ScanOut
|
||||
)
|
||||
score_path = run.root / "score.json"
|
||||
score_path.write_text(json.dumps(score_artifact.to_dict(), indent=2, sort_keys=True) + "\n")
|
||||
runtime.run_stage("report", lambda: write_markdown_report(findings, markdown_path, verifications))
|
||||
runtime.run_stage(
|
||||
"report", lambda: write_markdown_report(findings, markdown_path, verifications, redact=config.hardening.redact_secrets)
|
||||
)
|
||||
runtime.run_stage("sarif", lambda: write_sarif_report(findings, verifications, sarif_path))
|
||||
runtime.run_stage(
|
||||
"manifest",
|
||||
|
||||
Reference in New Issue
Block a user