fix(skillopt-sleep): redact secrets before persisting cycle diagnostics
PR #92 added a per-cycle diagnostics.json that surfaces backend stderr, optimizer replies, and task responses so a 0.0 night is self-diagnosing. Those free-text fields can carry credentials (e.g. a codex 401 stderr dump containing an auth token), so persisting them verbatim was a new on-disk leak surface. - Add a shared redact_secrets() in staging.py and route diagnostics.json's call_error / reflect_raw_head / holdout_detail through it before writing. - Redact the codex and Claude auth-error log lines too (a secondary sink when a file log handler is attached); last_call_error stays raw in memory so _AUTH_MARKERS matching is unaffected. - Centralize _SECRET_PATTERNS in staging.py (harvest_codex now reuses them) and extend coverage to AWS / GitHub / Slack / Google / JWT token shapes. - Tests: secret-shape coverage, private-key blocks, recursive/scalar passthrough, no over-redaction of plain prose, fail-fast auth-error log redaction, and an end-to-end check that diagnostics.json has no secret. Observability-only; the gate and learning algorithm are unchanged. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,7 @@ from skillopt_sleep.harvest_sources import harvest_for_config
|
||||
from skillopt_sleep.memory import ensure_skill_scaffold
|
||||
from skillopt_sleep.mine import mine
|
||||
from skillopt_sleep.staging import adopt as adopt_staging
|
||||
from skillopt_sleep.staging import redact_secrets
|
||||
from skillopt_sleep.staging import write_staging
|
||||
from skillopt_sleep.state import SleepState, _now_iso
|
||||
from skillopt_sleep.types import SessionDigest, SleepReport, TaskRecord
|
||||
@@ -281,6 +282,9 @@ def run_sleep_cycle(
|
||||
# cycle previously captured none of this, making the gate a black box (#learning-stall).
|
||||
try:
|
||||
import json as _json
|
||||
# Backend stderr / optimizer replies / task responses can carry
|
||||
# credentials (e.g. a codex 401 stderr dump), so scrub secret-looking
|
||||
# substrings before persisting them to the on-disk diagnostics.
|
||||
with open(os.path.join(staging_dir, "diagnostics.json"), "w", encoding="utf-8") as _fh:
|
||||
_json.dump({
|
||||
"night": night,
|
||||
@@ -292,9 +296,11 @@ def run_sleep_cycle(
|
||||
"accepted": result.accepted,
|
||||
"n_applied_edits": len(result.applied_edits),
|
||||
"n_rejected_edits": len(result.rejected_edits),
|
||||
"call_error": getattr(result, "call_error", ""),
|
||||
"reflect_raw_head": (getattr(result, "reflect_raw", "") or "")[:1200],
|
||||
"holdout_detail": getattr(result, "holdout_detail", []),
|
||||
"call_error": redact_secrets(getattr(result, "call_error", "")),
|
||||
"reflect_raw_head": redact_secrets(
|
||||
(getattr(result, "reflect_raw", "") or "")[:1200]
|
||||
),
|
||||
"holdout_detail": redact_secrets(getattr(result, "holdout_detail", [])),
|
||||
}, _fh, indent=2)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user