[#139] Redact secrets from logs and the diagnostics export - #140
Conversation
QA Confidence Verdict — Story #139 (redact secrets from logs & diagnostics export)Verified by running both suites and inspecting the production wiring. No web UI is in scope for this story (native macOS app + Python service), so Playwright was not applicable; verification is test-execution + code inspection.
Per-AC verdictAC1 — redacting filter in AC2 — covers formatted message + exception/traceback text: PASS AC3 — raw Swift stderr tee ( AC4 — unit test: on-disk log contains placeholder not token: PASS (genuine, not tautological) AC5 — grep exported zip for seeded token, confirm absent: PASS (partially tautological) Extensibility (story note): PASSPattern list centralized per runtime ( What needs human eyes
Risk areas
Suggested QA focusQuick glance. Core behavior is genuinely tested on the Python side (AC1/AC2/AC4). The residual is the Swift tee integration seam (AC3) and the pre-redacted seed in the AC5 exporter test — both low-risk given code inspection, but the tee test gap is the one thing to decide on before closing. |
Closes #139
Summary
Defense-in-depth follow-up to #137 / PR #138. Masks secret-shaped tokens (at minimum HuggingFace
hf_[A-Za-z0-9]+→hf_***) before they are written to any log bundled into the shareable Export Diagnostics… zip, so a token embedded in third-party output (e.g. surfaced in a dependency's traceback) cannot leak.Three sinks are covered:
service.log(Python) — aSecretRedactingFilteron the rotating file handler redacts the formatted message, exception/traceback text, and stack info.service-stderr.log(Swift) — the raw child stderr/stdout tee redacts each chunk before writing.app.log(Swift) — lifecycle lines are redacted too, as it is bundled into the export.Approach
RotatingFileHandler, not the root logger — a logger-level filter only runs for records logged directly on it and would miss records propagated from child loggers (uvicorn). The traceback is pre-rendered and stashed inrecord.exc_textso the formatter reuses the redacted text instead of re-rendering rawexc_info. The filter fails open and dropsargson a malformed-format record, so redaction can never raise back at the log call site.SecretRedaction.redact(_:)mirrors the Python pattern list. Redaction happens at the single tee write site (covering both stdout-post-handshake and stderr). Per-chunk redaction carries one documented limitation: a token split across twoavailableDatareads could evade masking — low risk, since tracebacks arrive as a burst._SECRET_PATTERNS/SecretRedaction), cross-referenced, and both exercised with the sharedhf_TESTTOKEN0123456789abcdefvector so drift surfaces.Plan:
docs/plans/139-redact-secrets-from-logs.md. Architect plan review and code review both passed.Verification
ruff check .andruff format --check .: pass.pytest: 393 passed — new on-disk tests confirm a message token and an exception-traceback token land ashf_***, plus a fail-open unit for malformed records.swift build+swift run MeetingTranscriberKitTests: 270 passed —SecretRedactionunits, a tee-writes-redacted-to-disk check, and an end-to-end AC5 test that exports the diagnostics zip, extracts it, and asserts the seeded token is absent whilehf_***is present.