Log

The gate before the commit

A secret that reaches a remote is public. Rotating it is the only real fix, and every hour between the push and the rotation is exposure you cannot take back…

A secret that reaches a remote is public. Rotating it is the only real fix, and every hour between the push and the rotation is exposure you cannot take back. So the useful place for a scanner is not CI, and not a hook you install per machine — it is inside the command that writes the commit.

staged files the index scanner AKIA… ghp_… glpat- sk_live PEM blocks · JWTs user:pass@host + .toriignore [secrets] *.example · *.sample · *.template skip the gate commit written object enters history commit aborted file and line printed override with --yes torii scan --history runs the same patterns over every commit already written torii history replace-text --redact-secrets rewrites what it finds

Where it sits

torii save scans the staged content before it creates the commit object. Nothing is written when there is a finding: the command aborts and prints the file and the line. There is no hook to install, no per-repository setup, and no way to end up on a new machine with the protection quietly off.

torii scan --history runs the same patterns over every commit already written, for the question "was this ever true?".

The shape that slipped through

Until 0.13.0 the token patterns matched a key that appeared as its own whitespace-delimited word. That is the shape a key has in prose, and almost never the shape it has in a file.

GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx

There, the whole assignment is one word, and it does not start with ghp_. Every .env file, every -e KEY=value container argument, every CI variable pasted into a compose file was invisible. The same hole covered AWS keys, GitLab tokens, Stripe and SendGrid.

It was found by writing the glued shape out and testing it, rather than by reading the pattern list and concluding it looked complete. That is the whole lesson: a detection rule is only as good as the ugliest real input you tried it against.

Placeholders, and why the word list went away

The connection-string rule used to skip anything whose password was password, pass, changeme, xxx or yourpassword, on the theory that those are examples.

They are also passwords people actually use. A rule that skips the weakest real credentials in the corpus is worse than no rule. Only syntactic placeholders are excluded now — ${VAR}, $VAR, <placeholder> — which are shapes no password can be.

The cost is that a documentation example containing user:pass@localhost is now flagged. That is the right trade: a false positive costs one --yes, a false negative costs a rotation and an incident.

Two more that were wrong

A bare scheme://user@host — no password at all, the SSH-style URL — was reported as a leaked credential. It now checks that the userinfo actually carries user:password@.

And the scheme list was missing postgres:// and mongodb+srv://, so a real credential written in either of the two most common spellings was invisible regardless of length.

When the finding is not real

torii save -am "docs: example" --yes

Or, permanently, an allowlist entry in .toriignore, or // torii:allow-secret on the line. --yes is also required when stdin is not a terminal: the scanner cannot prompt in CI or under an agent, and it fails fast pointing at the flag rather than hanging on a question nobody can answer.