airgap 0.1.1

Security for the modern age of AI: defend against bad AI agents and malicious npm packages
> Written and maintained by AI.

# The attack `airgap` defends against

This document describes the real-world threat that motivates `airgap`: **malicious
software dependencies that steal developer secrets at install time**. It focuses
on the npm ecosystem, where this attack class has become an industrialized,
self-replicating epidemic in 2025–2026, but the same playbook applies to PyPI,
RubyGems, crates, and any package manager that runs code on install.

The connection to `airgap` is direct: the #1 and #2 targets of every campaign
below are exactly what `airgap` redacts — **`.env` files** and **SSH/PGP private
keys**. An AI coding agent that runs `npm install` (or `pip install`, or `cargo
build` with a build script) inside an `airgap` mount namespace exposes only the
redacted versions of those files to whatever the install hook executes.

---

## 1. The kill chain

Modern malicious dependencies follow a remarkably consistent five-stage pattern.

### Stage 1 — Auto-execution on install

The payload runs from an npm **lifecycle script** (`preinstall`, `postinstall`,
or `prepare`) declared in `package.json`. These run automatically during
`npm install`, before the install even completes, with the full privileges of
the user — no `import`/`require` of the package is needed.

Observed in the wild:

| Campaign | Hook | Command |
|---|---|---|
| Red Hat **Miasma** | `preinstall` | `node index.js` |
| **pgserve** | `postinstall` | `node scripts/check-env.cjs \|\| true` |
| **Shai-Hulud 2.0** | `preinstall` | bootstraps `setup_bun.js` |
| **TanStack** brand-squat | `postinstall` | exfil of `.env*` files |

The trailing `|| true` in the pgserve hook is deliberate: it makes the install
report success regardless of whether the malware ran, so nothing looks wrong.

### Stage 2 — Evade Node monitoring

Several modern strains download and install the **Bun** runtime (`setup_bun.js`,
from `bun.sh`) and run the real payload (`bun_environment.js`) under Bun instead
of Node, to slip past tooling that only instruments `node`.

### Stage 3 — Harvest credentials

This is the core of the attack and the part `airgap` is built to blunt. Targets
span files on disk, environment variables, and network endpoints:

**On-disk files**
- `.env`, `.env.local`, `.env.production` — application secrets
- `~/.ssh/`**all** SSH private keys
- `~/.npmrc`, `~/.netrc` — registry/publish tokens
- `~/.aws/credentials`, `~/.config/gcloud/application_default_credentials.json`,
  Azure CLI token cache, GCP service-account JSON keys
- Chrome `Login Data` SQLite DB (decrypted with the Linux key derivation)
- Crypto wallets: MetaMask, Phantom, Exodus, Atomic, `wallet.dat`, Solana
  keypairs, Ethereum keystores

**Environment variables**
- ~40 patterns: `AWS_*`, `AZURE_*`, `GCP_*`, `NPM_TOKEN`, `GITHUB_TOKEN`,
  `GITLAB_*`, Docker creds, DB passwords, generic `*_API_KEY`/`*_SECRET`

**Network / runtime endpoints**
- Cloud **instance metadata services** (IMDS) for temporary workload
  credentials: AWS / Azure / GCP `169.254.169.254`, ECS task metadata
- HashiCorp **Vault** probed at `127.0.0.1:8200`
- Kubernetes service-account tokens from the environment / projected volumes
- CI/CD secrets: GitHub Actions secrets, `ACTIONS_RUNTIME_TOKEN`,
  `ACTIONS_ID_TOKEN_REQUEST_TOKEN`, CircleCI tokens

**Aggressive variants** download **TruffleHog** and run it across the disk to
find anything resembling a secret, and scrape GitHub Actions runner memory via
`/proc/<Runner.Worker>/mem` to pull OIDC tokens.

### Stage 4 — Persistence & privilege escalation

Beyond a one-shot grab, the heavier strains dig in:
- Inject a passwordless sudo rule: `runner ALL=(ALL) NOPASSWD:ALL` into
  `/etc/sudoers.d`
- Modify `/etc/hosts` for DNS redirection
- Install a **self-hosted GitHub Actions runner** under `$HOME/.dev-env/` for
  remote code execution via attacker-triggered workflow events
- Replace the package's legitimate `index.js` with a multi-MB obfuscated dropper
  (4.29 MB in Miasma)

### Stage 5 — Exfiltration & worm propagation

Stolen data is encoded (often double-base64, or AES-256-CBC + RSA-4096) and sent
out via channels chosen to resist takedown:
- **Public GitHub repos** created under the victim's own account, with the loot
  committed to files like `environment.json`, `cloud.json`, `contents.json`,
  `data.json`, or `results/<timestamp>.json`
- Git commits spoofing the `github-actions@github.com` identity; GitHub's commit
  search API used as a covert dead-drop C2
- **Blockchain-hosted endpoints** — Internet Computer Protocol canisters
  (`*.icp0.io/drop`) — explicitly chosen because they cannot be seized
- Webhook ingest endpoints (Svix, `telemetry.api-monitor.com`)

The **worm loop**: if a valid npm publish token is found, the malware
re-publishes backdoored versions of every package that token controls (up to 100
at a time), producing exponential spread. Some carry a destructive dead-man's
switch — a decoy token labelled
`IfYouInvalidateThisTokenItWillNukeTheComputerOfTheOwner` whose invalidation
triggers `rm -rf ~/`.

---

## 2. Timeline of major campaigns (2025–2026)

| Campaign | Date | Scope | Distinctive trait |
|---|---|---|---|
| **Shai-Hulud** (original) | Sep 14–18 2025 | 200+ pkgs / 500+ versions; began with `rxnt-authentication@0.0.3`, hit `@ctrl/tinycolor`; seeded by an MFA-phishing campaign against maintainers | First self-replicating npm worm; writes `bundle.js` then republishes. CISA alert issued Sep 23 2025 |
| **Axios** compromise | ~Apr 2026 | Popular HTTP client | Mass-reach package; Microsoft mitigation guidance |
| **TanStack** brand-squat | Apr 2026 | unscoped `tanstack` v2.0.4–2.0.7 | postinstall exfil of `.env*` to a Svix endpoint; chained `pull_request_target` + cache poisoning + OIDC theft from runner memory |
| **pgserve** (CanisterSprawl) | Apr 2026 | v1.1.11–1.1.13 | 1,143-line harvester; AES+RSA; exfil to an ICP canister |
| **node-ipc** | May 14 2026 | v9.1.6, 9.2.3, 12.0.1 | 80 KB obfuscated payload in the CJS bundle |
| **Mini Shai-Hulud** (TeamPCP) | Apr–May 2026 | TanStack, Mistral AI, UiPath, `@antv`, Bitwarden CLI, SAP CAP, 160+ pkgs | Wipes home dirs; full cloud + K8s harvest |
| Typosquat (`vpmdhaj`) | May 28 2026 | 14 pkgs in 4 hours | Squats OpenSearch / ElasticSearch / env-config libs |
| Dependency-confusion sweep | May 29 2026 | 33 / 176 pkgs | Fingerprints host, beats internal package names |
| **Shai-Hulud 2.0** ("The Second Coming") | Nov 2025 → 2026 | 700+ pkgs | `setup_bun.js` + `bun_environment.js`; GitHub Actions C2; home-dir wipe on failure |
| Red Hat **Miasma** | Jun 1 2026 | 32 pkgs / 90+ versions under `@redhat-cloud-services` | `preinstall``node index.js`; sudoers + `/etc/hosts` persistence; ROT-decoded payload |
| **IronWorm** | Jun 2026 | npm | Rust-built, self-replicating |

Attribution note: many 2026 campaigns (TanStack, SAP CAP, Bitwarden CLI) are
linked to the threat actor group **TeamPCP**; Miasma and Mini Shai-Hulud are
derivatives of the original Shai-Hulud worm with cosmetic re-themes (Dune →
Greek mythology).

---

## 3. Indicators of compromise

Useful for hunting, though note that **per-infection encryption defeats
hash-based detection** in the newer strains — behavioural detection is more
reliable.

**File names dropped**
- `setup_bun.js`, `bun_environment.js` (Shai-Hulud 2.0)
- `bundle.js` (Shai-Hulud 1.0)
- `.github/setup.js` injected into victim repos
- `index.js` / `_index.js` replaced with large obfuscated droppers

**Hashes (Shai-Hulud 2.0)**
- `setup_bun.js`: `a3894003ad1d293ba96d77881ccd2071446dc3f65f434669b49b3da92421901a`
- `bun_environment.js`:
  `62ee164b9b306250c1172583f138c9614139264f889fa99614903c12755468d0`,
  `cbb9bc5a8496243e02f3cc080efbe3e4a1430ba0671f2e43a202bf45b05479cd`,
  `f099c5d9ec417d4445a0328ac0ada9cde79fc37410914103ae9c609cbc0ee068`

**Repo / commit signatures**
- Repo descriptions: `"Sha1-Hulud: The Second Coming."`,
  `"Miasma: The Spreading Blight"`
- Exfil repo names matching `[0-9a-z]{18}` or `<adjective>-<creature>-<0-99999>`
- Commit-message marker: `LongLiveTheResistanceAgainstMachines:<base64>`

**Network**
- Outbound to `bun.sh` from an install step
- `*.icp0.io/drop`, `telemetry.api-monitor.com`, Svix ingest endpoints
- IMDS hits to `169.254.169.254`, Vault probes to `127.0.0.1:8200`
- GCP user-agent `google-api-nodejs-client/7.0.0 gl-node/20.11.0 gccl/7.0.0`

**Related advisories:** `GHSA-g7cv-rxg3-hmpx`, `CVE-2026-45321`, CISA alert
(2025-09-23).

---

## 4. How `airgap` fits in

`airgap`'s model — run the agent (and therefore its `npm install` and any install
hooks) inside a mount namespace where secret **files** are FUSE-redacted — directly
neutralizes **Stage 3 on-disk file theft**:

| Target | Covered by `airgap` today? |
|---|---|
| `.env` files | ✅ Redacted (value-level; keys preserved) |
| SSH / PGP private keys | ✅ Redacted (body removed; markers kept) |
| `~/.npmrc`, `~/.netrc` | ❌ Not yet (file-by-name, would extend cleanly) |
| `~/.aws/credentials`, gcloud/Azure creds, SA keys | ❌ Not yet |
| Browser `Login Data`, crypto wallets | ❌ Not yet |
| Environment variables (`AWS_*`, `*_TOKEN`, …) | ❌ Out of model — env is passed through unchanged |
| IMDS / Vault / K8s SA token endpoints | ❌ Out of model — network not mediated |

**Key honest limitation.** `airgap` redacts file *content*; it does not scrub the
**environment** or mediate the **network**. The campaigns above read tokens
straight from env vars and from `169.254.169.254` / `127.0.0.1:8200`, so
file-content redaction alone does not stop those vectors. Stages 4 (persistence:
sudoers, `/etc/hosts`, self-hosted runner) and 5 (exfiltration) likewise happen
outside the file-redaction boundary — though the mount namespace does mean writes
to host paths are confined.

**Plausible roadmap implications**, in rough priority order:
1. Extend the by-name file set to `~/.npmrc`, `~/.netrc`, `~/.aws/credentials`,
   gcloud/Azure credential caches, and SA-key JSON — same handler shape as `.env`.
   The home directory is now overlaid alongside the working directory, so these
   `~/...` paths already flow through FUSE; adding the matching handlers is the
   only remaining step (they pass through unredacted until then).
2. Redact/strip secret-looking **environment variables** before exec (a clear
   extension of "pass argv/env through" toward "pass a sanitized env through").
3. Consider blocking or faking the **IMDS** address and `127.0.0.1:8200` inside
   the namespace, since no legitimate `npm install` needs them.

These keep `airgap` a *transparent wrapper* while closing the exact gaps this
threat class exploits.

---

## Sources

- [Microsoft — Preinstall to persistence: Red Hat npm Miasma campaign]https://www.microsoft.com/en-us/security/blog/2026/06/02/preinstall-persistence-inside-red-hat-npm-miasma-credential-stealing-campaign/
- [Microsoft — Mini Shai-Hulud / @antv CI/CD credential theft]https://www.microsoft.com/en-us/security/blog/2026/05/20/mini-shai-hulud-compromised-antv-npm-packages-enable-ci-cd-credential-theft/
- [Microsoft — Typosquatted npm packages steal cloud & CI/CD secrets]https://www.microsoft.com/en-us/security/blog/2026/05/28/typosquatted-npm-packages-used-steal-cloud-ci-cd-secrets/
- [Microsoft — 33 malicious npm packages abuse dependency confusion]https://www.microsoft.com/en-us/security/blog/2026/05/29/33-malicious-npm-packages-abuse-dependency-confusion-profile-developer-environments/
- [Microsoft — Mitigating the Axios npm supply chain compromise]https://www.microsoft.com/en-us/security/blog/2026/04/01/mitigating-the-axios-npm-supply-chain-compromise/
- [Datadog Security Labs — Shai-Hulud 2.0 worm analysis]https://securitylabs.datadoghq.com/articles/shai-hulud-2.0-npm-worm/
- [Unit42 — The npm Threat Landscape: Attack Surface and Mitigations]https://unit42.paloaltonetworks.com/monitoring-npm-supply-chain-attacks/
- [Unit42 — "Shai-Hulud" Worm Compromises npm Ecosystem]https://unit42.paloaltonetworks.com/npm-supply-chain-attack/
- [Wiz — Miasma supply chain attack targeting Red Hat npm packages]https://www.wiz.io/blog/miasma-supply-chain-attack-targeting-redhat-npm-packages
- [StepSecurity — pgserve / CanisterSprawl ICP canister exfiltration]https://www.stepsecurity.io/blog/pgserve-compromised-on-npm-malicious-versions-harvest-credentials
- [StepSecurity — node-ipc supply chain attack]https://www.stepsecurity.io/blog/node-ipc-npm-supply-chain-attack
- [Socket/Hendry — TanStack brand-squat exfiltrates env files]https://www.hendryadrian.com/malicious-npm-package-brand-squats-tanstack-to-exfiltrate-environment-variables/
- [ReversingLabs — New Shai-Hulud worm spreads]https://www.reversinglabs.com/blog/new-shai-hulud-worm-spreads-what-to-know
- [CISA — Widespread Supply Chain Compromise Impacting npm Ecosystem]https://www.cisa.gov/news-events/alerts/2025/09/23/widespread-supply-chain-compromise-impacting-npm-ecosystem
- [Mondoo — npm Supply Chain Security in 2026]https://mondoo.com/blog/npm-supply-chain-security-package-manager-defenses-2026
- [The Hacker News — Miasma compromises Red Hat npm packages]https://thehackernews.com/2026/06/miasma-supply-chain-attack-compromises.html