Technical 8 min read

Suvadu 0.5.3: Mask the Secrets Your AI Agents Typed, Including the Ones Already Stored

Suvadu 0.5.3 masks secrets in the forms AI agents write them, from JSON bodies and auth headers to credential flags and SQL passwords, and suv redact applies the new rules to history you already recorded.

Madhubalan Appachi Madhubalan Appachi · Published

Suvadu masks secrets before it writes a command to disk. Those rules were written around what people type at a prompt: export API_KEY=…, a token after --token, a password in a connection URL. Testing them against what AI agents actually run showed how much they missed. An agent doesn't type export; it sends curl -d '{"password":"…"}', sets an x-api-key header, passes --client-secret, and writes CREATE ROLE app PASSWORD '…' straight into psql.

Suvadu 0.5.3 fixes that in two parts: rules for the forms agents write secrets in, and suv redact, which applies today's rules to the history you recorded before them. It also records commands faster, and fixes the x86_64 Linux binary on older distributions. The full list is in the changelog. The short version for upgrading: install 0.5.3, run suv redact --dry-run, then suv redact.

What agents type that people don't

These were stored as typed before 0.5.3 and are masked now:

  • A value under a secret name in JSON, YAML or code, of any length: curl -d '{"password":"…"}', password = "…" in a heredoc, api_key: ….
  • Credential flags such as --client-secret, --auth-token, --access-token, --passphrase and --oauth2-bearer, and the password flags of redis-cli, the Mongo tools, sqlcmd, ldapsearch, zip, openssl and az login.
  • Headers and cookies: x-api-key, X-Auth-Token and PRIVATE-TOKEN, Authorization under any scheme, Cookie and curl -b.
  • Signed URLs and webhooks: the key=, sig= and X-Amz-Signature= of a URL, Slack and Discord webhook URLs, and Telegram bot URLs.
  • SQL: PASSWORD '…' and IDENTIFIED BY '…'.
  • Secrets set through a CLI: gh secret set --body, kubectl --from-literal and fly secrets set, plus DB_PASS=, MYSQL_PWD= and APIKEY=.
  • More provider tokens: Google, GitLab, OpenRouter, Hugging Face, Groq, xAI, DigitalOcean, SendGrid, Linear, Notion and Sentry.

One fix was a mask that only looked like one. {"access_token":"<jwt>"} was stored as ***REDACTED***.<payload>.<signature>: the header was hidden, and it is the one part of a JWT that isn't sensitive. A JWT is now masked whole, and so is a JWT with a short header or one not under a key name, which wasn't masked at all.

And one went the other way. In grep "PASSWORD=" .env | grep "foo", the quote that closes the pattern was read as opening a value, so everything up to the next quote disappeared. Searching for a secret's name now keeps the whole command.

Cleaning up what is already stored

New rules apply to commands recorded from now on. Everything recorded before them is still on disk as it was, and an agent that has been running for months has written a lot of it. That is what suv redact is for. Look first:

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"

The dry run prints no secret value, only what each row would become. A real run asks first, then copies history.db to backups/preredact-<timestamp>.db before it changes anything. That copy still holds every secret; no later run touches it, and every run names it until you delete it.

It covers every place Suvadu keeps text: commands and their context, failure output, agent prompts, replies and summaries, git snapshots, cached prompt files, and the backups in the backups directory. A secret masked in history.db but readable in last week's snapshot isn't masked. It uses each project's .suvadu.toml, so a row gets the rules of the directory it was recorded in.

Two details took most of the work:

  • A secret masked once is masked everywhere. When a rule masks a value, that value is masked wherever else it appears, so a key that was also pasted into an agent prompt as prose goes too. Only a literal of at least 16 characters spreads: password=hunter2 says nothing about grep hunter2, and ${DB_PASSWORD} is a reference, not a secret.
  • Rewriting a row doesn't remove the old text from the file. SQLite's full-text 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 pages that split while the row was written. So each changed file also has its search indexes rebuilt and is compacted, and a run that stops halfway is finished by the next one.

Bookmarks, notes and aliases are counted and left alone: you wrote them, and masking one would break it. Agent summaries of sessions whose commands were masked read as stale afterwards, because the text they were written from changed.

Faster recording, with twice the rules

Every recorded command starts a new suv process, and each one compiled every redaction rule, even for ls. That was most of what recording a command cost, and 0.5.3 has about twice as many rules. Now a command with none of the words the rules need skips them entirely. Measured on one Apple M4 Max, 200 interleaved rounds of a whole suv add in a fresh process (BENCHMARKS.md):

Command recorded0.5.2 p50 / p950.5.3 p50 / p95
git status --short, no trigger word13.5 ms / 15.1 ms7.6 ms / 8.9 ms
curl https://example.com, has one13.1 ms / 14.7 ms15.0 ms / 16.5 ms

The second line is slower, and it's the cost of the new rules. How often you pay it depends on what you run: across one developer's 37,192 recorded commands, 19% of agent commands and 6% of the rest held a trigger word.

Linux binaries for older distributions

The x86_64 Linux binary of 0.5.2 was built on GitHub's Ubuntu 24.04 runner and linked against its glibc 2.39. On Ubuntu 22.04, Debian 12, RHEL/Rocky 9, Amazon Linux 2023 and anything older, suv refused to start with GLIBC_2.39 not found, whether it came from the install script, Homebrew, suv update or cargo binstall; cargo install builds from source and was not affected. It is now built like the arm64 binary, in an old-glibc image, and needs glibc 2.28 at most. A release now fails rather than publish a binary that needs more, and before each release the x86_64 binary is run on Ubuntu 18.04 to 24.04, Debian 11 and 12, Rocky Linux 8 and 9, and Amazon Linux 2023, and the arm64 one on Ubuntu 20.04 and Debian 12.

Smaller things worth knowing

  • Open a command's session from recall. Ctrl+K in suv search or Ctrl+R opens the timeline of the session the highlighted command ran in: the prompt and replies around an agent's command, or the commands before and after one of yours. Esc comes back to recall.
  • Rename or move a skill. suv skills edit <name> --rename <new-name> and --move-to global|here|<path> keep its content and history; the next sync cleans up the files under the old name.
  • suv sessions on a wide terminal shows the whole session ID and when each session started.
  • OpenCode sub-sessions are linked to the session that started them, as Codex forks already were.
  • SQLite 3.54.1 is now bundled, for CVE-2026-119375. Suvadu only opens databases it wrote, so its exposure was small, but a release about secrets shouldn't ship a known one.

Upgrading

brew upgrade suvadu      # or: suv update, or cargo install suvadu
suv redact --dry-run     # see what history you already have would change
suv redact               # mask it; a copy is kept first
suv init opencode        # only if you use OpenCode, then relaunch it

Once you have checked the result, delete the preredact-*.db copy from the backups directory. Then rotate any secret suv redact found that is still in use: masking a stored secret doesn't make it safe, because anything that could read the file before could have copied it.

What 0.5.3 still doesn't do

  • Redaction works by pattern. A secret no rule recognises is stored as typed, and stays that way after suv redact. Add your own extra_patterns for in-house formats, and keep secrets out of command lines where you can.
  • suv redact reaches only Suvadu's own files. Not suv export files or suv backup --out copies written elsewhere, not filesystem snapshots or Time Machine, not your shell's own history file, and not agents' transcript files.
  • Old bytes may survive on the disk itself. On a copy-on-write filesystem or an SSD, rewriting a file doesn't promise the old bytes are unrecoverable.
  • Fish and Nushell still have no recording hooks; Zsh and Bash do.

Try Suvadu with your own history

For macOS or Linux with Zsh 5.1+ or Bash 4+. Install the CLI, enable your shell, then try Ctrl+R.

1. Install the CLI

With Homebrew:

brew tap AppachiTech/suvadu && brew install suvadu
Other install methods

Install script. Works on macOS and Linux, Intel and ARM. Downloads a binary for your operating system and architecture, verifies its checksum, and installs to /usr/local/bin, using sudo only if that directory is not writable. End the command with bash -s -- --user instead to install into ~/.local/bin without sudo.

curl -fsSL https://downloads.appachi.tech/suvadu/install.sh | bash

Read the installer source.

Cargo. Requires Rust 1.95 or newer. Compiles from source, which can take a few minutes; cargo binstall suvadu installs the pre-built binaries instead. Make sure ~/.cargo/bin is on your PATH.

cargo install suvadu

2. Enable recording in your shell

Homebrew and Cargo do not activate shell recording, and the install script adds the hook line only if you answer y when it offers. Open the instructions for the shell you use. If another history tool owns Ctrl+R, disable its history binding before enabling Suvadu; keep your existing history data.

Zsh setup

Add this line once to ~/.zshrc. Keep the existing line if it is already present:

eval "$(suv init zsh)"

Save the file, then activate it in your Zsh terminal:

source ~/.zshrc
Bash setup

Add this line once to ~/.bashrc. Keep the existing line if it is already present:

eval "$(suv init bash)"

Save the file, then activate it in your Bash terminal:

source ~/.bashrc

Bash 4+ is required. For a login shell, make sure ~/.bash_profile sources ~/.bashrc, or use the startup file your shell reads. Shell setup details.

3. Check setup and try a search

suv doctor

Run echo hello, press Ctrl + R, and search for hello. Select a result to put it in your prompt; press Enter to run it. Agent and MCP setup are optional.

Import existing Zsh or Bash history, an Atuin database (read-only), or a Suvadu JSONL export.

Installation help · Troubleshooting · Update or uninstall

Already on Suvadu? Run suv update, brew upgrade suvadu or cargo install suvadu, depending on how you installed it, then follow the upgrade steps above.

Madhubalan Appachi
Madhubalan Appachi

Builder of Suvadu. Writes Rust, thinks about shell history more than most people, and believes developer tools should be local-first.