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

attack chain
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 vars

Within 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 PhaseWithout MentatWith Mentat Sandbox
RAT executionRuns with full user privileges on the hostRuns inside PID + mount + IPC namespace. Cannot see host processes
Host filesystemFull read/write to ~/.ssh, ~/.aws, /etcpivot_root enforced — host filesystem invisible. RAT writes to overlay, destroyed on sandbox kill
Credential theftReads SSH keys, AWS tokens, env varsSecrets not injected at build time. Sandbox env is minimal
Lateral movementCan SSH to other machines, scan networkNo host processes visible (PID NS). With CLONE_NEWNET: no egress at all
PersistenceWrites to crontab, .bashrc, systemdBase 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:

immune deploy model
# 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 execute

If 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 1Immutable baseDependencies locked at base build time. No floating ranges
Layer 2No npm install on deployDeploy copies code to overlay, skips postinstall hooks entirely
Layer 3PID namespaceRAT cannot see or signal host processes
Layer 4Mount namespace + pivot_rootHost filesystem invisible. NOT chroot — syscall-level enforcement
Layer 5Capabilities droppedALL Linux capabilities removed. NO_NEW_PRIVS set
Layer 6cgroups v2 limitsMemory, CPU, PID limits. RAT cannot fork-bomb or exhaust resources
Layer 7Network namespaceCLONE_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 allowlistHull 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 LSMpivot_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 modeWith 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 TCBHull 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 install at deploy time.
  • Monitor cgroup metrics for anomalous CPU/memory spikes during builds.
  • Rotate secrets immediately if a build ran during the exposure window.