/
← Voltar ao feed

Npm packages mathmain, mathsbase and math-universe hide a password-triggered encrypted RAT loader in a mathjs copy

1 fonte(s) · 1 atualização(ões) · publicado 17/09/2026 21:00 · coletado há 1 dia

SafeDep found a remote access implant in mathmain, an npm package that copies mathjs, and in two sibling packages, mathsbase and math-universe. The malicious code ships AES-256-GCM encrypted and activates only when lusolve() is called with matrix data whose LU lower factor serializes to a specific password. Once decrypted, the implant takes encrypted code from an operator over Slack, Telegram and a smart contract on the Base Sepolia test network, then writes it to disk and runs it with Node.js. The article does not attribute the campaign to any actor. The loader does not appear in the public GitHub sources of mathsbase and math-universe, and no victim or caller using the trigger was found.

Technical Details

  • Loader: The CommonJS lusolve() build makes an extra call to removeSolveValidation(), which leads to an added isGraph() in lib/cjs/utils/is.js. isGraph() uses JSON.stringify of its input as a password and decrypts a filename blob. The decryption is scrypt, producing a 256-bit key, followed by AES-256-GCM. The layout is a 16-byte salt, a 12-byte IV and a 16-byte tag, followed by the ciphertext. The event() helper writes the decrypted file to disk, and require() then loads it.
  • Trigger: The password is the JSON form of the LU lower factor [[1,0,0],[1,1,0],[1,0.5,1]]. A 3x3 Pascal matrix [[1,1,1],[1,2,3],[1,3,6]] produces it. Passing L itself as the coefficient matrix, or supplying it through the object form of lusolve(), gives the same password. JFrog first reported the input, and SafeDep reproduced it. SafeDep's own password guessing (16,922 and 533 candidates) failed.
  • Stage 1, graph.js: It reads host data, generates an X25519 key pair, spawns detached Node.js processes, and reads a Base Sepolia smart contract through a bundled ethers 5.7.2 copy (bignumber/type.js). It reports to Slack chat.postMessage and api.telegram.org.
  • Stage 2, fraction.js: This is the Slack agent. It polls conversations.history every 10 seconds and decrypts accepted messages into start, chunk and end packets. It reassembles the file as lib/cjs/utils/subwatcher and launches it detached with Node.js. The blockchain path also decrypts remote data, writes subwatcher and runs it. The later code delivered through these channels was not recovered.
  • Spread: Five versions across the three packages share identical loader files, with one different graph.js blob in one version. Both deployments share the Alchemy project key D2-TbkB2m05WXSnSDOCDI. The default npm version of mathmain on September 17 did not contain the loader, but version 1.0.1 did.
  • Other findings: The manifest has no install hooks, and a plain import does not activate the loader. With a wrong password, authentication fails before any file is written.

Remediation

  • Search lockfiles, SBOMs and registries for mathmain, mathsbase and math-universe, checking all versions and not only the default.
  • Hunt for graph.js, bignumber/type.js, fraction.js and subwatcher under lib/cjs/utils/, and for the published hashes.
  • Block or alert on the listed Base Sepolia RPC endpoints and on unexpected Slack or Telegram API traffic from build or application hosts.
  • If a host may have run the loader, treat it as compromised and rotate its credentials.
  • A package or hash match alone does not prove execution, because a caller must trigger the loader.

Linha do tempo

  1. safedep.io ↗ · pub. 17/09/2026 21:00 · coletado 19/10 18:03 primeiro registro

    Npm packages mathmain, mathsbase and math-universe hide a password-triggered encrypted RAT loader in a mathjs copy

    ler a análise desta fonte

    SafeDep found a remote access implant in mathmain, an npm package that copies mathjs, and in two sibling packages, mathsbase and math-universe. The malicious code ships AES-256-GCM encrypted and activates only when lusolve() is called with matrix data whose LU lower factor serializes to a specific password. Once decrypted, the implant takes encrypted code from an operator over Slack, Telegram and a smart contract on the Base Sepolia test network, then writes it to disk and runs it with Node.js. The article does not attribute the campaign to any actor. The loader does not appear in the public GitHub sources of mathsbase and math-universe, and no victim or caller using the trigger was found.

    Technical Details

    • Loader: The CommonJS lusolve() build makes an extra call to removeSolveValidation(), which leads to an added isGraph() in lib/cjs/utils/is.js. isGraph() uses JSON.stringify of its input as a password and decrypts a filename blob. The decryption is scrypt, producing a 256-bit key, followed by AES-256-GCM. The layout is a 16-byte salt, a 12-byte IV and a 16-byte tag, followed by the ciphertext. The event() helper writes the decrypted file to disk, and require() then loads it.
    • Trigger: The password is the JSON form of the LU lower factor [[1,0,0],[1,1,0],[1,0.5,1]]. A 3x3 Pascal matrix [[1,1,1],[1,2,3],[1,3,6]] produces it. Passing L itself as the coefficient matrix, or supplying it through the object form of lusolve(), gives the same password. JFrog first reported the input, and SafeDep reproduced it. SafeDep's own password guessing (16,922 and 533 candidates) failed.
    • Stage 1, graph.js: It reads host data, generates an X25519 key pair, spawns detached Node.js processes, and reads a Base Sepolia smart contract through a bundled ethers 5.7.2 copy (bignumber/type.js). It reports to Slack chat.postMessage and api.telegram.org.
    • Stage 2, fraction.js: This is the Slack agent. It polls conversations.history every 10 seconds and decrypts accepted messages into start, chunk and end packets. It reassembles the file as lib/cjs/utils/subwatcher and launches it detached with Node.js. The blockchain path also decrypts remote data, writes subwatcher and runs it. The later code delivered through these channels was not recovered.
    • Spread: Five versions across the three packages share identical loader files, with one different graph.js blob in one version. Both deployments share the Alchemy project key D2-TbkB2m05WXSnSDOCDI. The default npm version of mathmain on September 17 did not contain the loader, but version 1.0.1 did.
    • Other findings: The manifest has no install hooks, and a plain import does not activate the loader. With a wrong password, authentication fails before any file is written.

    Remediation

    • Search lockfiles, SBOMs and registries for mathmain, mathsbase and math-universe, checking all versions and not only the default.
    • Hunt for graph.js, bignumber/type.js, fraction.js and subwatcher under lib/cjs/utils/, and for the published hashes.
    • Block or alert on the listed Base Sepolia RPC endpoints and on unexpected Slack or Telegram API traffic from build or application hosts.
    • If a host may have run the loader, treat it as compromised and rotate its credentials.
    • A package or hash match alone does not prove execution, because a caller must trigger the loader.