Adds doc/rotation.md: what is already in place for optional rotation (RotationHint, Status::Rotated, fingerprint-as-unit-of-work), what it would take, and the four constraints a future executor does not get to relax — chiefly that nanny holding provisioning credentials is the real cost of the feature and must be opt-in per service. Broadens the noise exclusions after a first sweep of a real ~/git produced 1030 working-tree findings, 273 of them from one crate's vendored target/container/cargo/registry. Excluding all of target/ rather than just debug/ and release/, plus vendored dependencies, build output, tool caches and minified assets. .git/config stays readable: a password in a remote URL is a genuine spill. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqHWfdMAsYu1o36tgeima
4.7 KiB
Rotation
Status: design note. Not implemented. nanny records and alerts; the operator decides what to do. This document exists so that when optional rotation is built, it lands on a seam that is already in the right place rather than being retrofitted through the middle of the detection path.
Why it is not built yet
The expensive mistake would be to build rotation before the detection is trusted. A tool that revokes a credential on a false positive costs more than one that misses a real leak, because the false positive breaks something that was working and the operator learns to distrust the tool. Detection quality has to be demonstrated on a real corpus first — and the first sweep of a real workstation produced 1538 findings, most of them fixtures in vendored dependencies, which is exactly the state in which automated revocation would have been a disaster.
So the order is: detect, tune until the findings are believable, then automate the response.
What is already in place
RotationHint, carried by a rule rather than by a finding, because what you
do about a leaked forge token does not depend on which transcript it turned up
in:
pub struct RotationHint {
pub service: String, // "anthropic", "gitea", "aws" — the dispatch key
pub instructions: String, // what a human does, right now
pub docs: Option<String>,
pub rotation_suffices: bool, // false ⇒ incident response, not rotation
}
rotation_suffices is the field that will matter most. A leaked API key is
undone by revoking it. A leaked private key is not: the key has to be replaced
and the old public key removed from every trust store it reached, and no
amount of automation makes that a one-button operation. Rules where this is
false must never be handed to an automatic executor.
Status::Rotated, distinct from Status::Acknowledged. Acknowledged means
the operator has seen it and decided; rotated means the leaked value is now
worthless. Only the second closes the exposure, and the UI keeps them apart so
that a triaged backlog is never mistaken for a clean one. An executor sets
Rotated; nothing else should.
The Fingerprint. Because the same secret in five places shares one
fingerprint, a single rotation can resolve every finding carrying it. That is
the unit of work an executor should operate on — a secret, not a location.
What it would take
An Remediator port in nanny-core, alongside Alerter, dispatching on
RotationHint::service to per-service implementations. Sketch:
#[async_trait]
pub trait Remediator: Send + Sync {
fn service(&self) -> &str;
/// What this would do, without doing it.
async fn plan(&self, finding: &Finding) -> Result<RemediationPlan>;
/// Do it. Only ever reached after explicit operator confirmation.
async fn execute(&self, plan: &RemediationPlan) -> Result<Remediated>;
}
Four constraints that follow from the rest of the design, and that a future implementation does not get to relax:
- An executor cannot read the leaked value, because nothing stores it. It
therefore cannot revoke "the credential that leaked" by presenting it. It
revokes by identity at the service — the key with this prefix, the token
named for this consumer — which is why
agent-credentials.md§5's practice of naming issued credentials after their consumer matters here. - Rotation needs credentials, and nanny currently has none. This is the
real cost of the feature: nanny becomes a process holding provisioning
credentials, which is the thing the patterns-only detection design exists to
avoid. The mitigation is the mint-don't-share pattern of
agent-credentials.md§2 — a narrow provisioning credential per service that can revoke and reissue and nothing else — and it should be opt-in per service, off by default, never a blanket capability. - Never automatic.
planthen confirm thenexecute. A monitor that revokes credentials on its own judgement is a denial-of-service with good intentions. - Still never edits a harness's files. Rotating makes the leaked copy
inert, which is the correct remedy. Rewriting a live session transcript to
scrub it risks breaking the harness that is writing it, and leaves the
operator believing a copy is gone when backups, the model provider's logs and
file-history/still hold it.
The thing rotation does not fix
A credential that reached a model provider's servers is gone, whatever happens
locally afterwards. Rotation is the only remedy that works, which is the whole
argument for detecting the spill in the first place — and for nanny scan
exiting non-zero, so it can gate something before the transcript is uploaded
rather than after.