← Blog

The Day I Rotated 15 Credentials

On 2026-08-25, a command that was supposed to print secret names printed secret values instead. By the end of that day, fifteen credentials had to be rotated. Six days later, the rule written because of it was broken again. Both incidents are logged. Only one of them is closed, and it isn't the one you'd expect.

What happened

The incident is tracked as P33-0580, and its state as of this writing is PARKED — not closed. An agent session ran a command meant to produce a names-only listing of stored secrets. Instead, KEY=VALUE pairs for three environments went straight into the session transcript, where a human or log could read them.

Why the fix made it worse

Why doesn't redacting secrets after they're printed work?

Redaction only filters output after it has reached its destination. A positional filter also cannot split name=value: the equals sign makes one token, so "the first field" returns the whole line, secret included.

LEAK PATH command KEY=VALUE terminal transcript log CORRECTED PATH vault environment variable process no readable output
Printing contaminates every readable destination; reference-only handling keeps the value inside the execution path.
FIELD PARSING name VALUE field one field two first field = name name=VALUE one token, no separator first field = name=VALUE
A field selector needs a separator; an equals sign inside the token does not provide one.

Fifteen credentials

What should you do the moment a credential leaks?

Rotate it immediately, and rotate every credential the exposure could have reached — not just the ones you can prove were used. Log the exposure as an incident that stays open until the response is verified, rather than treating a rotated value as the same thing as a closed case.

The total — fifteen credentials requiring rotation — isn't a number from the issue tracker. It's recorded in the operator's own global rules file, in the changelog entry that introduced the rule this incident produced. That entry attributes it to two sessions: the first is the one described above, later itemized as a GitHub personal access token exposed across three environments, five connector keys, a VPS root SSH password, and two production database connection strings. The second — the redaction failure — leaked ten more the same day. The same entry notes that the exposed root password reached a host running seven unrelated stacks: one value, seven systems downstream of it.

The rule: never produce the output

How do I stop AI agents leaking secrets into terminal output?

Stop the value from ever being printed in the first place — a command that might emit KEY=VALUE output has no safe redaction step after the fact. Route secret values through an environment variable set and consumed by reference, and prefer a tool's names-only listing mode whenever one exists.

The rule is INV-0.001, NO_SECRET_IN_THE_OPEN: names, never values, anywhere a human or log can read them. Set and consume a value through an environment variable in the same statement; never echo or pipe it into output. Prefer a tool's names-only mode:

How should a script handle an API key safely?

Assign the value to an environment variable in the same statement that fetches it, then reference that variable by name — never echo or interpolate it directly into a command line.

credential_cli.py resolve --project P56 --format env

I looked for a hook that enforces this the way a hook enforces a blocked deploy — something that would stop a command like the one on 2026-08-25 before it ran. I didn't find one. There's a hook that scans a deploy payload for secret-shaped files before it ships, which is a different check for a different moment. There is nothing today that inspects a command's output for a value mid-session and stops it. This rule, right now, is prompt-level: written down, and binding only because it's followed.

Six days later

On 2026-08-31, two new rows were filed:

P28-0319 OPEN — secret values stored in Claude memory files P0-0003 OPEN — a hosting API token and a VPS root password stored as values in a memory file

Both are open as I write this — not resolved, not closed, no fixer or verifier recorded against either one yet. The rule was written on 2026-08-26. It held for five days before the next violation, filed six days after the original incident.

I'm not going to describe that as fixed, because it isn't. A prompt-level rule with no hook behind it lasted less than a week against ordinary work before it was broken again, in a shape the first incident hadn't covered — not a command's output this time, but a file written to disk and left there. A related row, P33-0704, filed the same day and currently TRIAGED, notes that the secrets tool itself changed underneath the safe pattern — it now prepends multi-line notices to its output, which breaks any script that was capturing a value by its position in that output. Even the workaround needs to be re-verified against a moving target.

What to copy

Prefer names-only output. Otherwise move values by reference through an environment variable; do not dump and filter afterward. A prompt-level rule is only as durable as the next person's attention span, and mine lasted six days.

Meharban Singh

Meharban Singh

AI systems / delivery architect. I build software with AI agents governed by rules, hooks, gates and independent review — and watch it in production after go-live.