deniz.in

Markets

Weather

Loading weather

· via Hacker News – Front Page (native)

npm math typosquat hides encrypted implant that wakes on one equation

SafeDep uncovered an encrypted remote-access implant inside mathmain, an npm lookalike of mathjs, that stays dormant until a program solves one specific equation with the library.

npm math typosquat hides encrypted implant that wakes on one equation

SafeDep has disclosed a remote-access implant hidden inside mathmain, an npm package that imitates the popular mathjs library. According to the security firm, the malicious code ships encrypted and does nothing until a program uses the library to solve one particular equation. When the right input arrives, the package decrypts a payload, writes it to disk and loads it, handing the attacker a channel to run commands on the host.

A dormant trigger inside the solver

SafeDep began analyzing mathmain on September 17, 2026, after its tooling flagged the package as a renamed copy of mathjs with obfuscated additions. In the CommonJS build of lusolve(), a routine that solves linear equations, the researchers found one extra call inserted after the result had already been computed. It passes the lower triangular matrix's data to a function named removeSolveValidation(), assigns the return value to a variable that is never read, and returns the correct answer untouched. To any caller, the solver behaves normally.

That call leads to an added isGraph() helper in the package's utility module. The helper turns its input into a JSON string and uses that string as a password. It decrypts a filename with that password, decrypts a file the same way, writes the plaintext beside the encrypted original, and loads it with require(). The decrypted module then runs with the full permissions of the Node.js process.

The trigger is data-dependent by design. A caller must supply, through the object form of lusolve(), matrix data whose lower factor serializes to exactly the right password; SafeDep found no password stored in the visible loader. If the guess is wrong, the AES-GCM authentication check fails before any file is written. There are no install hooks in the manifest, and simply importing the package does nothing — the solver has to pass its validation and calculation steps first.

Encryption and a staged payload

Per SafeDep, the decryption code derives a 256-bit key from the password with scrypt and uses AES-256-GCM. Each blob is base64 text with a fixed layout: a 16-byte salt, a 12-byte initialization vector, a 16-byte authentication tag, then ciphertext. Three files in the package contain encrypted data instead of JavaScript — graph.js at 20,918 bytes of ciphertext, fraction.js at 9,084, and bignumber/type.js at 1,179,416. Only graph.js is referenced by the visible loader, and SafeDeaf suspects the eight-byte encrypted filename resolves to it. Once decrypted, graph.js loads the other two files as later stages. The active payload takes commands from the attacker and runs them on the host, using a public chat service and a blockchain network as its command channel.

Injected at publish time, not in the source

SafeDep located the same loader, trigger code and solver modifications in two more packages, mathsbase and math-universe, spread across five versions. Both point to public GitHub repositories, and neither repository contains the loader — the npm builds carry the extra solver call that the public source lacks. In SafeDep's reading, the malicious code was added when the packages were published rather than developed in the open, though the shared files do not reveal who added it or whether a publisher account was compromised.

Versioning added to the confusion: on September 17 npm served a mathmain release without the loader as the default, so an audit limited to the default version would have missed the code in 1.0.1.

Cracking the trigger showed how narrow it is. SafeDep tested 16,922 candidate passwords against the encrypted filename, then 533 more against all five encrypted blobs, drawing on common passwords and numeric arrays from the solver's own tests. None passed. SafeDep credits JFrog with first reporting the input that produces the trigger, and describes the password as the JSON form of one specific lower-factor matrix.

The blast radius remains unknown. npm's download figures for September 12–18 were undermined by the registry reporting zero downloads for everything on September 17, making mathmain's zero for that day untrustworthy. None of the packages has public dependents, and jsDelivr traffic was negligible, but SafeDep could not determine how many systems installed the packages or executed the encrypted code. Searches across GitHub code, lockfiles and dependency services found no program calling the solver with the trigger, though private codebases stay invisible to such searches.

Why it matters

This is a supply-chain attack tuned to defeat the usual defenses. Because the payload is encrypted and gated behind a precise input, static review sees an ordinary math library and sandbox detonation never hits the trigger. The sharper lesson is the divergence between the npm artifacts and the clean GitHub repositories: auditing source repositories is not enough when malware is injected at publish time and exists only in the published tarballs. It also shows why every version of a package deserves scrutiny, not just the default release. For developers, the practical takeaway is to install well-known packages under their exact names, treat lookalikes such as mathmain, mathsbase and math-universe as red flags, and verify tarball hashes rather than trusting the linked repository.

  • #npm
  • #supply-chain
  • #security
  • #malware
  • #node-js

Related posts