repl: wire #13 secrets — scrub outbound, rehydrate stream + tool args

Plumbs the secrets.lua module (commit e4b818b) into the conversation
pipeline. Hook points:

  ask_ai          — scrub_messages(ctx:to_messages(), mode) before
                    call_broker; rehydrate streamed deltas via
                    streaming_rehydrator so the user sees real values
                    while text_parts accumulates rehydrated chunks
                    (final_resp is plain — CMD: / DELEGATE: extractors
                    see plain values)

  MCP dispatch    — dispatch_tool_call rehydrates the args table before
                    sess:call_tool so the trusted MCP server receives
                    real values (the model emitted placeholders because
                    it saw a scrubbed context)

  DELEGATE: & :delegate
                  — scrub sub_msgs before broker.chat; rehydrate sub_text
                    before appending to context, so future turns see
                    real values restored

  Phase 5 summarize-on-evict
                  — scrub sum_msgs before broker.chat; rehydrate the
                    reply that becomes ctx.summary

  :memory summarize
                  — same scrub + rehydrate pair

Mode resolution per call: model_cfg.redact → config.secrets.default →
"vault+autodetect" if vault loaded, else "off".

ctx storage convention: PLAIN values throughout. The scrub happens at
the egress (broker call) per the active redact mode; ctx.turns never
holds placeholders for content the user typed or executor produced.
The model's own emissions (assistant tool_call arguments) may carry
placeholders because the model saw the scrubbed context — rehydrated
at MCP dispatch and otherwise harmless on re-serialization (idempotent
re-scrubbing).

New meta:
  :secrets [status]         vault entries, placeholders allocated this
                            session, active broker mode. Never prints
                            actual values (vault file is itself a
                            secret per gotcha 7).
  :secrets check <text>     dry-run scrub against the active broker's
                            mode — shows the output transformation.

Documented in config.lua with a commented-out block + per-broker
redact field example.

Deferred to a follow-up issue (clearly scoped):
  - safety.lua broker call sites (Norris main loop, is_destructive
    LLM second-opinion probe) — same wiring pattern, but they don't
    currently see secrets_session; needs threading through helpers.
  - @-mention file content is appended PLAIN to ctx and scrubbed at
    egress alongside the rest of the user turn (covered by the
    ask_ai scrub).
  - exec output streamed live to terminal is pre-scrub (user sees
    real values in their own shell — by design); the captured-for-
    context copy is scrubbed at egress alongside the rest.

This is the "full scope" implementation chosen via AskUserQuestion.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-16 21:38:23 +00:00
parent e4b818b0e9
commit d852acadc2
2 changed files with 178 additions and 9 deletions
+18
View File
@@ -71,6 +71,24 @@ return {
-- post_cmd = (os.getenv("HOME") or ".") .. "/.aish/hooks/post-cmd",
-- },
-- Issue #13: secret redaction. Vault is a separate file at ~/.aish/
-- secrets.lua (mode 0600 enforced). When set, outbound broker messages
-- are scrubbed: vault literals + autodetect heuristics (OpenAI sk-,
-- OpenRouter sk-or-v1-, GitHub ghp_/gho_/ghs_, AWS AKIA, JWT eyJ...,
-- SSH/GPG PRIVATE KEY headers) become $AISH_SECRET_NNN placeholders.
-- The streamed reply is rehydrated before display so the user sees
-- real values. Per-broker override via models[*].redact:
-- "off" -- no scrubbing (trusted local)
-- "vault" -- vault literals only
-- "vault+autodetect" -- + heuristics (default when vault loaded)
-- "stealth" -- + heuristics, opaque decoys, no rehydrate
-- Default per-broker is the global config.secrets.default, falling
-- back to "vault+autodetect" when vault loaded, else "off".
-- secrets = {
-- vault = "~/.aish/secrets.lua",
-- default = "vault+autodetect", -- applies when models[*].redact is nil
-- },
-- Issue #8: background CMD (CMD&: marker). Requires history.dir set
-- (logs land at <history.dir>/bg/<id>.log + .status sidecar). The
-- feature is always-on once history.dir exists — no config flag — but