v0.2 Layer 1 — SecretMasker: 10-pattern regex redact before LLM + audit log
First layer of v0.2's 5-layer security architecture: a 286-LOC SecretMasker that redacts API keys, credit cards, and Korean RRNs at the AnalyzeCoordinator boundary — before the instruction reaches the external LLM or hits the audit log on disk.
AI 버전
Backfilled entry — original commit shipped with [no-log] and no log file was written at the time.
What was done
Added SecretMasker.swift (91 LOC) plus 168 LOC of tests and wired it into AnalyzeCoordinator.run() so that every AnalyzeRequest.instruction flowing through the system is regex-redacted before two specific things happen:
- The instruction is handed to the dispatcher (which calls the external Vision LLM).
- The instruction is persisted into
SessionAuditEntryon disk.
Ten patterns are applied, in this specific order (specific prefixes before generic ones to avoid false positives):
sk-ant-...→[REDACTED:anthropic-key]sk-proj-...→[REDACTED:openai-key]sk-...→[REDACTED:openai-key]AIza...→[REDACTED:google-key]AKIA...→[REDACTED:aws-access-key]github_pat_...→[REDACTED:github-token]ghp_...→[REDACTED:github-token]xox[abprs]-...→[REDACTED:slack-token]-----BEGIN ... PRIVATE KEY-----→[REDACTED:private-key]- Korean RRN (
901101-1234567shape) →[REDACTED:rrn] - Credit card (4-4-4-4, dash or space separated) →
[REDACTED:card]
Email is deliberately not masked — the product use case "where do I type my email?" requires the instruction to keep the email visible.
Two entry points are exposed:
SecretMasker.mask(_:)— applies all patterns and returns the redacted string.SecretMasker.detect(_:)— returns[(name, matched)]without redacting, for an eventual user-facing alert ("we noticed a secret in your instruction").
Why it was needed
From the commit message: this is the first concrete commit against the product-vision-global-multi-platform memory's 5-layer security plan. The product sends the user's screen and instruction to an external Vision LLM, and the instruction is also persisted to disk as an audit entry. Both surfaces are data boundaries where a leaked API key or card number would survive past the live session — the LLM provider's logs on one side, the local disk on the other.
The masker collapses both boundaries into a single chokepoint at the top of AnalyzeCoordinator.run() so there is exactly one place to reason about "did this secret leave the process boundary?"
Files / functions changed
Sources/ScreenBridge/SecretMasker.swift(NEW, 91 LOC) —SecretMaskerenum withPatternstruct,patternsstatic list,mask(_:),detect(_:).Sources/ScreenBridge/AnalyzeCoordinator.swift(+25 LOC at top ofrun()):- Computes
maskedInstruction = SecretMasker.mask(req.instruction)once per call. - If masked differs from original, logs a
[secret] instruction masked — N pattern(s): ...notice viaLog.dispatcher(pattern names only, not the matched secret). - Constructs a new
maskedReq: AnalyzeRequestand passes it todispatcher.analyze(...)instead of the original. - Writes the masked instruction into both
currentInstruction(so continuation calls don't resurface the secret) andSessionAuditEntry.instruction(so the disk record is already redacted).
- Computes
Tests/ScreenBridgeTests/SecretMaskerTests.swift(NEW, 168 LOC, 17 tests) — 11 mask cases (one per pattern plus safe-text and email-pass-through), 3 detect cases including multiple secrets in one string, plus credit-card dash/space separator coverage.DECISIONS.md(+6 lines) — R9 entries: "Secret mask at AnalyzeCoordinator.run boundary" and "Email mask deferred (instruction visibility)".
Result / verification
swift build— 3.87s.swift test— 133/133 passing (+17 new fromSecretMaskerTests).- The
[secret] instruction maskedlog notice usesprivacy: .publiconly for the pattern name and count — never for the matched substring — so even the persistent unified log on disk doesn't carry the raw secret.
Open / what didn't get done
- Detect-only path (
detect(_:)) exists but is not yet wired into a user-facing alert. Commit message marks this as "v0.3+ TODO". - Image masking is explicitly out of scope: the Vision LLM runs OCR on the captured PNG, and redacting pixels of an in-flight screenshot is a much harder problem. The masker only covers the text instruction, not the image. v0.3+ may revisit.
- Layers 2–5 of the v0.2 plan remain to be built: sensitive-app exclusion by bundleID (1Password, KakaoBank), sensitive-region opt-out, local LLM swap (Apple Foundation Model), and audit-log 7-day rotation.
- Email pattern is intentionally absent — kept as a decision (R9), not an oversight.
Commit — 2ffc163
리뷰 필요
내 시각이 아직 안 들어간 entry.