Mask Secrets Already Stored
Suvadu masks secrets before it writes them, so a rule added in an upgrade never reaches what was recorded before it. suv redact (since 0.5.3) applies today's rules to the history you already have.
Quick Start
Look first. A dry run lists each changed row's first change, masked, and writes nothing:
suv redact --dry-run
# Dry run — nothing will change.
#
# history.db: 3 rows
# Commands 3
#
# Would become (each row's first change):
# Commands: curl -H "x-api-key: ***REDACTED***" https://api.example.com/v1/items
# Commands: export GITHUB_TOKEN=***REDACTED***
# Commands: psql "postgres://app:***REDACTED***@db.example.com/app" Then mask. A real run asks before it changes anything (-y skips the question), copies the database first, and says what it did:
suv redact
# Backed up to ~/Library/Application Support/tech.appachi.suvadu/backups/preredact-20261111-110100.db before masking.
# ✓ history.db: masked 3 rows. Run it after an upgrade that adds redaction rules, after adding your own extra_patterns, or once you notice a secret in your history. A second run finds nothing left to mask.
What It Masks
Every place Suvadu keeps text that could hold a secret:
- Commands, and the agent prompt and git branch in a command's context
- Failure output kept by Keep Failure Output
- Agent sessions: prompts, replies, saved summaries and git snapshots
- Cached prompt files that link an agent's commands to the prompt behind them
- The backups in the backups directory, which keep the schema they were taken under
It uses the built-in rules, your extra_patterns, and each directory's .suvadu.toml, so a row is masked with the rules of the directory it was recorded in. It runs even with redaction.enabled = false, because asking for it is asking for redaction.
A secret masked once is masked everywhere
When a rule masks a value, that value is then masked wherever else it appears, so a key pasted into an agent prompt as plain prose goes as well as the command that used it. Only a literal value of at least 16 characters spreads: password=hunter2 says nothing about grep hunter2, and ${DB_PASSWORD} is a reference rather than a secret. A secret that no rule recognises anywhere stays where it is.
What it counts but leaves alone
Bookmarks, notes and aliases are counted and left as they are: you wrote them, and masking one would break it. Edit or remove those yourself.
The Copy It Keeps
Masking works by pattern and cannot be undone, so a real run first copies history.db to backups/preredact-<timestamp>.db. If that copy fails, nothing is masked. --no-backup skips it. Older backups are masked in place, with no copy of their own.
How the Old Text Leaves the File
Rewriting a row is not enough to remove the old text from disk. The search index marks a replaced term deleted without removing it, a shorter row leaves the start of the old one in freed space, and stale copies sit in the unused space of pages that split while the row was recorded. So each changed file also has its search indexes rebuilt, is compacted with VACUUM, and has its write-ahead log emptied.
The masked rows are committed with a note in the database header that the file still owes this compaction, so a run that stops in between is finished by the next one rather than reporting nothing to do. If another process is reading the database, the log empties when the last reader exits, and the run says so.
What It Does Not Reach
- the pre-redact copy it just made, until you delete it
suv exportfiles andsuv backup --outcopies written elsewhere- filesystem snapshots and Time Machine
- your shell's own history file, and agents' own transcript files
On a copy-on-write filesystem or an SSD, rewriting a file also does not promise that the old bytes are unrecoverable.
suv redact finds that is still in use.
Afterwards
- Agent session summaries of sessions whose commands were masked read as stale, because the text they were written from has changed. Summarize those sessions again if you need them current.
- Delete the
preredact-*.dbcopy once you are satisfied.
Options
| Flag | Description |
|---|---|
--dry-run | List what would change, masked, and write nothing |
-y, --yes | Skip the confirmation question |
--no-backup | Skip the preredact-*.db copy taken before masking |
Redact or Delete?
suv redact keeps each command and masks only the secret in it, everywhere it is stored. suv delete removes whole entries, which suits a command you never want back but leaves the backups as they were. For a secret, start with suv redact.
SECURITY.md covers what redaction does and does not catch.