How the scanner works
KeyDrift reads what a browser would download and nothing else. No agent, no repository access, no credentials.
What it fetches
The page first, in full. Next.js streams server data into the document as self.__next_f.push calls and Nuxt writes window.__NUXT__, so a secret handed to a client component often appears in the HTML and nowhere else — the entry document is scanned whole rather than parsed for script bodies.
Then the JavaScript the page references, including chunks named only in a route manifest. Those matter: the code that calls a paid API is usually behind a route, not on the landing page, so a scanner that only follows <script src> reads the shell and declares the app clean.
Sibling asset hosts are followed — frontend-assets.example.com for example.com — because that is how most Next.js deployments serve chunks. Another tenant of a shared platform is not: *.lovable.app, *.vercel.app and their peers hand out one subdomain per customer, so a shared parent there means nothing.
How a match becomes a finding
A pattern hit is a hypothesis. It survives, in order: a check against published sample keys, a randomness check on the part of the string that is meant to be random, the rule's own refinement — which is where a JWT is decoded to see whether it says service_role or anon — and finally a context adjustment from the surrounding code.
Below 0.5 confidence it is discarded rather than shown with a caveat. A low-confidence finding on a security report is not information, it is homework.
Entropy is measured on the body after the prefix, and normalised against the alphabet the string actually uses. A 40-character hex token tops out at 4.0 bits per character while a base64 token of the same length reaches 5.8; one threshold for both either passes lowercase junk or discards every hex key.
What it refuses to do
Only GET and HEAD requests are ever issued, enforced in the fetch layer rather than at the call sites — so pointing the scanner at a site is impolite, never harmful.
Loopback, RFC1918, link-local, carrier-grade NAT and the cloud metadata endpoints are rejected before the socket opens and re-checked on every redirect hop. A 302 to 169.254.169.254 is the whole attack, and validating only the first URL misses it.
Network failures are mapped to a fixed set of phrases before they reach you, because fetch errors routinely embed resolved addresses and internal hostnames.
What it never keeps
A key exists as a string inside the detection engine and nowhere else. What leaves is a mask — first eight and last four characters, and never more than half the string — plus a salted fingerprint.
The salt matters. An unsalted hash of an AWS access key ID is grindable, and an unsalted hash of a key you already have confirms its presence instantly, which would turn the findings table into an oracle for exactly the question it exists to keep private.
Limits, stated plainly
Detection is regex, entropy and context across 21 secret formats. There is no JavaScript parser, so a key assembled at runtime from concatenated fragments is not found. This is the honest boundary of every bundle scanner, including the paid ones.
The scanner does not authenticate, so code behind a login is not reached. Paste it into the source scanner instead — same engine, no fetch.
When a file cannot be read or gets truncated, the scan is reported as partial. "No findings" from an incomplete read is not a clean bill of health, and is never presented as one.