TL;DR: In 2013 I wrote a university diploma on elliptic-curve cryptography, and it went into a drawer. Thirteen years later the same math signs most of what you npm install. I taught croak 🐸, my pre-install gate for AI agents, to check those signatures itself. Last night it checked its own fresh release: verified. Then I gave it the same signature with a different tarball: invalid. Below is what that check actually involves, and why the curve is the easy part.
In 2013: a diploma on elliptic curves and key exchange. Points, groups, a lot of modular arithmetic. Defended, filed, forgotten.
I went into test automation, and the curves stayed in the drawer.
Then AI agents started running npm install on their own.
An install doesn't just download code. It runs it: postinstall, native builds, whatever a package wants. An agent does this all day, in your terminal, usually without reading anything. It also invents package names that sound right, and attackers register those names.
croak sits between the agent and the install. It's a Claude Code hook: before a shell command runs, croak has npm or pnpm plan the install (resolve it, run nothing), then reads that plan as data: install scripts, the lockfile, registry metadata, and the code of every new package, unpacked in memory and never executed. It gives a verdict and explains it to the agent:
agent: npm install reactt-domm
croak: 🐸 croak blocked this command before it ran.
reactt-domm: not on npm; did you mean `react-dom`?
Do not work around this check (another package manager, a different
command, or editing files to install it); tell the user what croak
found and let them decide.
postinstall that downloads a binary lands here.
It also scans an unfamiliar repo before you open it (croak repo: VS Code tasks that run on open, code hidden after hundreds of spaces, obfuscation), because "clone this take-home assignment and run it" is a real attack too.
It's early, and it misses things. On its own eval it flags all 36 attacks but blocks only 27; the other nine go to a human as 🟡. I'd rather publish that number than a prettier one.
Most attacks give themselves away in the code: a postinstall that pipes curl into sh, process.env posted somewhere. And install scripts are losing ground anyway: pnpm 10 and npm 12 no longer run a dependency's scripts unless the project allows them. I checked with a bait package that writes a marker file: npm 11 still runs them; npm 12 doesn't.
That pushes attacks into code you import, and that code can look exactly like the real thing. Someone steals a maintainer's token, publishes from a laptop, and the tarball looks like every release before it.
That is the question code can't answer: who built this tarball, and from what?
npm has an answer. It's called provenance. And it's elliptic curves all the way down.
When a package is published from CI with provenance, a few things happen in a couple of seconds:
release.yml in repo X, at commit Y"
Here's my own release from last night, read straight from the certificate:
leaf key: ec { namedCurve: 'prime256v1' } // P-256
valid: Nov 1 23:13:03 2026 GMT -> Nov 1 23:23:03 2026 GMT // 10 minutes
issuer: O=sigstore.dev, CN=sigstore-intermediate
A key that lives for ten minutes and is then thrown away. The trust comes from the log, not from keeping a secret for years.
Count the curves. The registry's own signature on every package: P-256. The build's ten-minute key: P-256. Rekor's key: P-256. (Fulcio's CA uses its bigger sibling, P-384.)
My diploma, signing my node_modules.
Here is the elliptic-curve part of croak's verifier:
verify("sha256", message, leaf.publicKey, signature)
One line. node:crypto does the curve math, and it does it better than I would. No point addition by hand, no hand-rolled anything. In 2026, writing your own ECDSA would be a red flag, not a skill.
All the hard parts moved into the plumbing around that line. A signature verifies something. The question is whether it is the thing you think. croak's check, in order:
const logged = readLogEntry(entries[0], trust.rekor); // 1. Rekor promised it saw this, and when
const leaf = leafOf(material);
checkCertificate(leaf, trust.fulcio, logged.loggedAt); // 2. Fulcio issued it, valid at that moment
// 3. the envelope's signature verifies with the leaf key
// 4. the log entry is for this exact signature, certificate and payload
checkSubject(payload, subject); // 5. the statement is about THIS tarball
return { status: "verified", identity: fulcioIdentity(leaf.raw) };
A few things I only learned by building it:
"DSSEv1 <type length> <type> <payload length> <payload>". Sign the raw JSON instead and a valid signature could be reinterpreted as a different type.unverifiable, not verified. Only a bundle that claims something its signatures don't back is invalid.
Last night I tagged croak 0.1.4. The release workflow built it, packed it once, and published that exact tarball with provenance. Then I asked croak, through its own verifier, about itself:
latest: 0.1.4
0.1.4: verified https://github.com/vola-trebla/croak/.github/workflows/release.yml@refs/tags/v0.1.4
@ a598a5863c9146429859323e2858421e9db16f21 logged 2026-11-01T23:13:03.000Z
The workflow file, the tag, the commit. Logged at 23:13:03, the same second the ten-minute certificate was born.
Then the check that matters: the same signature, but compared against the previous release's tarball hash.
0.1.4 bundle against 0.1.3's tarball: invalid
A real signature over the wrong bytes. That's the tampered-tarball case, and it has to say no.
The first time croak looked at itself, it flagged itself.
Version 0.1.0 shipped with my source comments in it. One of those comments explained the obfuscation detector by quoting the pattern it hunts for, globalThis[key](…). croak scanned its own installed files and found "obfuscated code".
The fix was one compiler flag, removeComments. Now CI installs the packed tarball and runs croak on it before every release. A security tool that can't pass its own scan doesn't get published.
This is the part that's easy to oversell.
Provenance proves where a tarball came from: this workflow, this repo, this commit. It does not prove the code is safe. A malicious maintainer can publish malware with perfect provenance from their own CI.
So in croak it's one signal among many, and it's used as a comparison:
npm audit signatures verifies registry signatures and provenance for what's in your lockfile. It's already in npm.npm trust github <pkg> --file release.yml --repo you/repo). No long-lived token to steal, provenance for free. npm publish repack after your tests ran.npm i -g @vola-trebla/croak.
Thirteen years ago I thought that diploma was the end of elliptic curves for me. Turns out they were just waiting for npm install.
croak is open source and early: github.com/vola-trebla/croak. Issues, and attacks it misses, are welcome.
🐸