Redact credentials on the way into a stored chat

A stored transcript is replayed into a later prompt when reopened, so a key typed while
changing a setting would be handed back to the model on every subsequent turn.
This commit is contained in:
Gmer4Lfe
2026-08-09 19:07:34 -04:00
parent 67eabdc17c
commit 500f9d92c8
4 changed files with 117 additions and 2 deletions
+25
View File
@@ -299,6 +299,31 @@ Reopened chats render as plain turns: sources, reasoning and timings describe on
are not stored, because redrawing them beside a transcript that may be continued under a
different profile would be citing evidence for an answer no longer being made.
### Secrets are redacted on the way to disk
A conversation about settings is a conversation containing credentials — asking for an API key to
be changed means typing one. Message bodies are redacted in `vv_ai_chat_save()`, and the question
is redacted again before it reaches `ai.log`.
**On the way out, never in flight.** The live turn keeps the real value, because the model needs
it to carry out what was asked. What it does not need is that value still in the transcript a week
later — and a stored chat is replayed into a later prompt when reopened, so an unredacted one
would hand the credential back on every subsequent turn, indefinitely.
Two passes, because they catch different things:
| Pass | Catches | Method |
|---|---|---|
| Known values | a credential this host already holds | exact match against secret-shaped conf keys, longest first |
| Assignment shapes | a credential arriving that is not in the conf yet | `NAME=value`, `"api_key": value`, "set the token to …" |
The second pass is the one that matters for settings changes: *"change the Emby API key to X"* is
a secret arriving, and X matches nothing on disk until after the write it is requesting.
Ordinary prose is left alone — the patterns anchor on a secret-shaped *name*, so `CACHE_WARN_GB=100`
and "turn off the zfs scrub" pass through untouched. `vv_conf_key_is_secret()` is shared with the
conf audit log, so the two cannot disagree about what counts as a secret.
## ━━━ TOKEN ACCOUNTING ━━━
Every completed `ask` appends one row to `AI_TOKEN_DB` (`data/ai/ai_token_history.db`):