Case Study: axios Supply Chain Attack
On March 31, 2026, a compromised axios maintainer account published backdoored versions (1.14.1 and 0.30.4) that injected a cross-platform RAT via plain-crypto-js. Microsoft attributed the attack to Sapphire Sleet, a North Korean state actor. The exposure window was approximately 3 hours.
The Attack Vector
npm install axios@1.14.1
└─ postinstall hook
└─ downloads plain-crypto-js@4.2.1
└─ RAT calls C2 server (sfrclak[.]com)
└─ exfiltrates credentials, SSH keys, env varsWithin two seconds of npm install, the malware was already calling the attacker's server — before npm finished resolving dependencies. Any machine that ran npm install with a floating range like ^1.14.0 was compromised.
What Mentat Prevents
| Attack Phase | Without Mentat | With Mentat Sandbox |
| RAT execution | Runs with full user privileges on the host | Runs inside PID + mount + IPC namespace. Cannot see host processes |
| Host filesystem | Full read/write to ~/.ssh, ~/.aws, /etc | pivot_root enforced — host filesystem invisible. RAT writes to overlay, destroyed on sandbox kill |
| Credential theft | Reads SSH keys, AWS tokens, env vars | Secrets not injected at build time. Sandbox env is minimal |
| Lateral movement | Can SSH to other machines, scan network | No host processes visible (PID NS). With CLONE_NEWNET: no egress at all |
| Persistence | Writes to crontab, .bashrc, systemd | Base is read-only overlayfs. Sandbox dies, RAT dies. Next deploy starts clean |
The Base Model: Complete Immunity
Mentat's overlayfs base system provides a stronger defense than runtime isolation alone. Instead of running npm install on every deploy, dependencies are baked into an immutable base:
# Vulnerable: npm install runs on every deploy
# RAT enters via postinstall hook
git push → npm install → build → deploy
↑
malicious package runs here
# Immune: base has pinned deps, deploy = code only
base node22 = npm install with exact lockfile (once)
git push → copy code to overlay → no npm install → deploy
↑
no postinstall hooks executeIf the base was built before March 31 with axios@1.14.0 in the lockfile, and no npm install runs during deploy, the backdoored version never enters the system.
Defense Layers
| Layer 1 | Immutable base | Dependencies locked at base build time. No floating ranges |
| Layer 2 | No npm install on deploy | Deploy copies code to overlay, skips postinstall hooks entirely |
| Layer 3 | PID namespace | RAT cannot see or signal host processes |
| Layer 4 | Mount namespace + pivot_root | Host filesystem invisible. NOT chroot — syscall-level enforcement |
| Layer 5 | Capabilities dropped | ALL Linux capabilities removed. NO_NEW_PRIVS set |
| Layer 6 | cgroups v2 limits | Memory, CPU, PID limits. RAT cannot fork-bomb or exhaust resources |
| Layer 7 | Network namespace | CLONE_NEWNET + veth pair: RAT cannot reach C2 server. Three modes: none (loopback only), veth (isolated NAT), host (legacy) |
All 7 Layers: Production Ready
As of v1.0, all seven isolation layers are implemented and deployed. Network namespace isolation (CLONE_NEWNET) uses veth pairs with iptables NAT for controlled egress. Build sandboxes use network: none — loopback only, no route to the internet. The RAT executes, finds no network, and dies silently.
Harder with Hull: syscall allowlist + Landlock + ~3 MB TCB
The sandbox driver stops the RAT at the namespace + pivot_root boundary. Running the same Node.js workload on the Hull driver adds three more layers that directly neutralize this exact attack class:
| seccomp-bpf allowlist | Hull installs a node profile just before execve: only the syscalls a real Node.js process needs are allowed, everything else triggers KILL_PROCESS. A RAT calling ptrace, process_vm_readv, bpf, add_key, or userfaultfd is dead the instant it tries. Even socket(AF_NETLINK, ...) (used for host reconnaissance) is blocked. |
| Landlock LSM | pivot_root hides the host; Landlock goes further and confines the RAT inside the container rootfs. Default policy: rootfs read+exec only, /tmp read+write, everything else denied. The RAT cannot drop a persistence file in /opt/app/node_modules, cannot overwrite the app binary, cannot even stat other directories. |
| --rootless mode | With hull run --rootless the workload runs as uid 0 inside a NEWUSER mapped to an unprivileged host uid. Even if the RAT somehow escapes every other layer, the host uid it lands on has no write access to the host filesystem, no CAP_SYS_ADMIN, nothing to escalate from. Defense in depth with zero sudo. |
| Minimal TCB | Hull is a single ~3 MB static-musl binary. No dockerd, no containerd, no shim. A local-privilege-escalation CVE in container tooling has no surface area here because there is no daemon running as root in the first place — hull exits as soon as it has forked the workload. |
In short: the sandbox driver stops the RAT from seeing the host. Hull also stops it from doing things a normal Node.js workload never needs to do — even inside its own container.
Practical Recommendations
- Pin exact versions in
package-lock.json. Never use^or~ranges for production deps. - Build bases with
npm ci(respects lockfile exactly). - Never inject production secrets into the build phase.
- Deploy as code-only overlay — no
npm installat deploy time. - Monitor cgroup metrics for anomalous CPU/memory spikes during builds.
- Rotate secrets immediately if a build ran during the exposure window.