Secret scanning
The default-on plugin, the three-way prompt, and the escapes.
Written By Dustin
Last updated 28 minutes ago
A policy encrypts what you told it to protect. Nothing detects what you forgot — the API key pasted into a config file on a Friday. So TOVIO ships a scanner that is on in every repo by default, with no install and no explicit binding: com.tovio.secret-scan, bound to pre-snapshot, running before the working copy is snapshotted.
It is a real plugin on the ordinary plugin machinery, not a special case bolted to the commit path. That is why it can be swapped, tightened, and audited like anything else here.
How default-on actually works
The binding is synthesized at run time, not stored: an enforcing, required, unscoped binding granting read_clear_paths over everything, injected into the effective binding set for pre-snapshot. A fresh clone scans on its first commit.
One consequence surprises people: the default scanner does not appear as a row in tovio plugin bindings, because that command lists the local binding store and the default binding was never written there. Its absence is not a sign that scanning is off.
The shipped binary carries the sandbox. A lean --no-default-features build injects no default binding at all, so it never scans and never blocks — silently. The shipped product is the fail-closed one; if you build your own CLI, check the feature set before you rely on scanning being there.
When it fires
An interactive human is prompted with three choices, and the sandbox itself applies none of them — it can only propose:
A non-interactive context fails closed. CI, an agent commit with --token regardless of an inherited terminal, --json, --quiet, or piped stdin all block. A machine can never auto-encrypt and never auto-allow. That is the load-bearing guarantee of the whole feature.
In a keyless repo there is no recipient to seal to, so encrypt-in-place is impossible; the prompt says so and re-offers block or allow-once rather than attempting a seal that would fail.
What it looks for
The detector runs inside the sandbox, over the clear content the host marshals in. A protected path is never scanned in the clear — that boundary is the point of the feature, not an exception to it.
- A vendored credential ruleset compiled to embedded data at build time, plus an entropy heuristic, scanned over whole file content so multi-line rules such as private keys match.
- Any file whose base name contains
.env, case-insensitively —.env.local,staging.env,prod.env.json— is flagged regardless of content. A committed.envis the headline threat, so the filename alone is enough.
Every finding carries a redacted preview and a stable fingerprint of the form SECRET:<rule>:<fingerprint>. The raw secret never appears in a finding, a message, a log, JSON error context, or an audit record.
False positives: the two escapes
- Inline — put
tovio:allow-secreton the flagged line. It rides in the tracked file, so it is shareable and reviewable. - Persistent — list the finding's fingerprint in
.tovio/secrets-allow.toml. Local repo state, not a synced object, and keyed on the finding code so it survives swapping the scanner.
Both are fail-safe: a missing allowlist file suppresses nothing, and a malformed one suppresses nothing and warns. A parse error can never quietly disable a finding.
Turning it off, or swapping it
tovio config set secrets.scan offtovio config set secrets.scanner <plugin-id>tovio config set secrets.protect-policy <policy>Opt-out is local and unsynced — each person governs their own scanning. A replacement scanner must declare pre-snapshot and request clear reads. Your own explicit binding for the scanner id suppresses the default only if it is at least as capable: enforcing, required, unfiltered, and granting clear reads over everything. A weaker or content-blind binding does not suppress it; both simply run.
Tightening the limits
Owner caps only ever tighten the always-on ceilings, never raise them, and the most restrictive value wins.
Session capture
Captured agent sessions run a mandatory secret scan of their own — agent prompts routinely contain pasted keys and .env fragments. A capture that fails is refused with TVO-PROV-007 rather than stored.
It is not a guarantee
Scanning is pattern-based. It catches common credential shapes, not everything, and it is not a substitute for policy on paths you already know are sensitive.
The wrong response to a finding is to allow-once and clean up later. Once a secret is in history in the clear, removing it needs tovio obliterate — and if it was pushed, the credential is exposed and must be rotated regardless of what you do to the history.