Secret redaction: scrub credentials before broker, rehydrate in reply #13
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Suggestion
Add a secret-redaction layer between aish and the broker. Substitute credentials/keys/tokens with stable placeholders before sending to the LLM, then rehydrate the placeholders in the reply before showing the user.
The motivation: as soon as
config.luaincludes free OpenRouter routes (openrouter/*-alpha,*:free), the provider explicitly logs prompts and completions for model training. Any token/key in the conversation — whether typed by the user, expanded via@filename, or surfaced byCMD:output (env dumps,cat /etc/*.conf) — gets baked into someone else's training set. A scrub layer caps that risk without forcing users to maintain a separate "safe" workflow.Mechanism
Vault
~/.aish/secrets.luareturns a list of literal strings to scrub, each optionally named:Pre-broker scrub (in
broker.lua)Before the POST, every outbound
messages[]content gets scanned. Matches are replaced with$AISH_SECRET_001,$AISH_SECRET_002, … and the mapping is kept in a per-turn table.Post-broker rehydrate (in
renderer.lua)Scan the reply for
$AISH_SECRET_NNNand re-substitute literal values before display. User sees real values; only placeholders ever crossed the wire.Optional auto-detect layer
Regex/entropy heuristic for common formats:
sk-...,sk-or-v1-...(OpenAI / OpenRouter)ghp_...,gho_...,ghs_...(GitHub PATs)AKIA...(AWS access key)-----BEGIN ... PRIVATE KEY-----)When auto-detect fires, show the user
[redacted 3 candidates: 'sk-or-…', 'AKIA…', high-entropy 47-char blob]and require:y(or auto-confirm ifconfirm_send=false).Per-broker policy
Ties into the permission DSL in #9:
Default:
vaultif vault exists, elseoff.Gotchas
These need explicit design decisions before implementation:
$AISH_SECRET_01instead of_001, or wrap in backticks/quotes. Unsubstitution must tolerate trailing punctuation, case variations, and short suffix typos — with logging when a placeholder appears in reply but doesn't match the vault.redact = "stealth"mode could replace with plausible decoys (xxxxxx-fake-token-xxxxxx) for true zero-information — at the cost of breaking unsubstitution if the model echoes back..env" workflows break when the model only sees$AISH_SECRET_001=$AISH_SECRET_002. That's a feature forredact=onbrokers, a bug for trusted ones — hence the per-broker policy.cat ~/.config/foo/credentials; output gets piped into next-turn context; secret leaves the box. Scrubbing must run on tool/exec output before context append, not only on user input.:secretsmeta-command that prints contents. Don't include it insessions/*.jsonlexports.Phase target
Phase 4 or 5 — lands cleanly after
safety.luais established (Phase 2 for tool gating) and integrates with permission policy DSL (#9) and per-broker config. Skeleton (vault loader + scrub function + rehydrate function) is small and could ship earlier as Phase 1+ behindredact = "off"default.Out of scope (this issue)
pass,gnome-keyring). Could be a vault backend later.Closed by commits
e4b818b(module) +d852aca(wiring).Coverage vs. the issue body
$mid-prose; reverses on per-delta + flushredact = "stealth"per-broker emits opaquexxxxxx-fake-<label>-NNN-xxxxxxdecoys (one-way; no rehydrate)sk-, OpenRoutersk-or-v1-, GitHubghp_/gho_/ghs_, AWSAKIA<16>, JWTeyJ...x.y.z, SSH-----BEGIN ... PRIVATE KEY-----models[*].redactfield; fallback chaincfg.redact->config.secrets.default->"vault+autodetect"if vault else"off"scrub_messageson outbound;dispatch_tool_callrehydrates args beforesess:call_toolso the trusted MCP server gets real valuessecrets.loadrefuses to load if mode != 0600 (matches ssh);:secretsmeta never prints valuesHook points (egress-only scrub design)
ctx stores PLAIN values throughout. Scrub happens at the broker call site so per-broker policy is naturally honored.
ask_aimain broker callscrub_messages(ctx:to_messages(), mode)dispatch_tool_callrehydrate_args(args)beforesess:call_tool:delegate:memory summarizeMode resolution
New meta
:secrets [status]-- vault entries, placeholder count, active broker mode. Never prints values.:secrets check <text>-- dry-run scrub against the active broker's mode; shows the transformation without sending anywhere.Validation
Module unit tests: 20/20 (vault sub, stable mapping, autodetect across all label kinds, stealth decoys, mode=off, streaming with mid-placeholder splits, non-placeholder dollar-sign pass-through).
E2E on higgs:
~/.aish/secrets.lua(chmod 600 enforced -- 644 rejected with chmod hint):secrets checkagainst vault literal, OpenRouter pattern, GitHub PAT -- all three scrub correctly to$AISH_SECRET_001/002/003Deferred to follow-up (clearly scoped)
safety.luabroker call sites (Norris main loop +is_destructiveLLM second-opinion probe) -- same wiring pattern, butsafetydoesn't currently seesecrets_session. Needs threading through the helpers table. Reasonable narrow follow-up.Out of scope (per the issue body)
pass,gnome-keyring) as vault backends