Skip to content

Stop guessing whether a clip is a secret - #21

Merged
willkotheimer merged 1 commit into
clip-selectionfrom
drop-secret-heuristics
Sep 11, 2026
Merged

willkotheimer merged 1 commit into
clip-selectionfrom
drop-secret-heuristics

Conversation

@willkotheimer

Copy link
Copy Markdown
Owner

Removes the content heuristics. Keeps the declared signal.

Why the guessing went

It interrupted an ordinary workflow to report something the user already knew. Copying a credential is a normal thing to do, and the prompt arrived every time — asking permission for the thing the person had just deliberately done.

The premise was weaker than it looked. The heuristics defended against exposure, but nothing Spool holds leaves the machine; that is the whole product. What Spool genuinely changes is persistence — a clipboard entry that would have lived until the next copy instead lives in an encrypted file with a visible preview. That is a real difference, and it is the honest case for asking. It is not a strong enough one to justify asking about every API key a developer copies.

It was the entire cost of capture.

before: classify 147ms per 1 MiB clip
after : classify 0.003ms

Each needle — sk-, AKIA, -----BEGIN, Password=, and the rest — walked the whole buffer separately. classify no longer takes the bytes at all, which is the strongest form the "Spool does not read your clips" claim can take.

Two tests that failed intermittently at a five-second timeout stopped being flaky as a side effect. That was the symptom that started this, not the reason.

Why the declared signal stays

CanIncludeInClipboardHistory = 0 is not a guess. It is an explicit statement from the application that owns the secret, saying do not persist this, and Windows' own Clipboard History obeys it. Spool makes a transient thing durable, so ignoring that request would persist exactly what a password manager asked it not to — and leave Spool behaving worse than the OS feature sitting beside it. It costs a flag check and no scanning.

The prompt keeps one voice now: it always names the application, because the application is the only thing that can raise it. There is no softer wording for a guess, because there are no guesses.

What went

  • looksLikeSecret, the entropy machinery, HEURISTIC_RULES, and the Tier type
  • Nine functions from detect/bytes.ts that existed only to feed them — ascii, startsWith, includes, indexOf, trim, isDigit, hasWhitespace, characterClasses, shannonEntropy
  • wipe stays: a declined clip must not be left in memory

The privacy screens now say plainly that Spool does not inspect what you copy, which is true in a way it previously was not.

Verification

352 tests, typecheck, lint, and the zero-network gate pass.

Note

Branched off clip-selection (#20), which is based on repeat-unspool (#19). Merge order: #19, #20, then this.

🤖 Generated with Claude Code

The heuristics scanned every copy for PEM blocks, JWTs, key prefixes, connection
strings and high-entropy text, and prompted on a match. They went for three
reasons.

They interrupted an ordinary workflow to report something the user already knew.
Copying a credential is a normal thing to do, and the prompt arrived every time,
asking permission for the thing the person had just deliberately done.

The premise was weaker than it looked. The guessing defended against exposure,
but nothing Spool holds leaves the machine. What Spool does change is
persistence: a clipboard entry that would have lived until the next copy instead
lives in an encrypted file with a visible preview. That is a real difference and
it is the honest case for asking — it is not a strong enough one to justify
asking about every API key a developer copies.

And it was the entire cost of capture: 147ms per MiB, because each needle walked
the whole buffer separately. Classification is now 0.003ms, because it no longer
reads the content at all — classify does not take the bytes any more, which is
the strongest form that claim can take. Two tests that failed intermittently at a
five-second timeout stopped being flaky as a side effect.

What is kept is not a guess. CanIncludeInClipboardHistory = 0 is an explicit
statement from the application that owns the secret, and Windows' own Clipboard
History obeys it. Spool makes a transient thing durable, so ignoring it would
persist exactly what a password manager asked it not to, and behave worse than
the OS feature beside it. It costs a flag check.

detect/bytes.ts loses nine functions that existed only to feed the heuristics.
wipe stays: a declined clip must not be left in memory.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@willkotheimer
willkotheimer merged commit 5bd2635 into clip-selection Sep 11, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant