# Stop your AI agent from being weaponized by a malicious package — in three steps

The Nx s1ngularity attack used local Claude, Gemini, and Q CLIs to recon for SSH keys, .env files, and GitHub tokens. 2,349 secrets were exfiltrated. The control plane your AI agent needs is the same one that catches this — but you have to install it before the next compromise lands.

On August 26, 2025, a [compromised version of Nx](https://snyk.io/blog/weaponizing-ai-coding-agents-for-malware-in-the-nx-malicious-package/) shipped via npm. The malicious `postinstall` script didn't try to do the recon itself — it called the local `claude`, `gemini`, and `q` CLIs already installed on developer machines and asked *them* to recon the filesystem for SSH keys, `.env` files, GitHub tokens, and crypto wallets. The agents complied. Researchers (GitGuardian, Wiz) later counted ~2,349 leaked secrets, 1,000+ GitHub tokens, and ~20,000 files exfiltrated to public repos under victims' names.

This is a new class of attack: malicious packages don't write the malware themselves — they prompt your AI agent to write the exfiltration code for them. The agent has filesystem access, credentials in its environment, and an instruction to "find anything sensitive and send it to this URL." From the OS's perspective, this looks indistinguishable from a legitimate user-driven session.

It will happen again. The supply-chain attack surface for AI-agent-having developers is now strictly larger than for everyone else.

## Why the agent is the perfect malware vector

The Nx attack worked because of three properties that your AI-agent setup probably has:

1. **Local AI CLIs are everywhere.** If you run Cursor, Claude Code, Codex CLI, or any agent SDK, your machine has a binary that can read files, make HTTP requests, and execute arbitrary code on your behalf.
2. **Those CLIs are typically authenticated.** A logged-in `claude` or `gh` CLI carries the credentials needed to read your repos, post to gists, and call your cloud APIs.
3. **Postinstall scripts run in your shell.** When you `npm install` a compromised package, its `postinstall` runs as you, with your full environment loaded — every API key in `.env`, every cached credential in `~/.aws` or `~/.config`.

A malicious package combines those three: it shells out to your local AI CLI, uses your credentials, exfiltrates whatever the agent can find. The agent does what it always does — read files, make HTTP calls — except the instructions came from the package, not from you.

## What ACP can — and can't — prevent

Honest scope:

- **ACP cannot prevent the malicious package from being installed.** That's a supply-chain problem. Use Socket, Snyk, GitHub Dependabot, npm provenance, lockfile audits.
- **ACP cannot prevent your AI CLI from being invoked by a postinstall script.** That's an OS / shell problem.
- **ACP can prevent the AI CLI from reading sensitive paths or calling unauthorized URLs once invoked.** That's the layer that turns *"the agent exfiltrated everything"* into *"the agent attempted to read `~/.ssh/id_rsa` and was denied — log row written, alert fired."*

The Nx attack succeeded because no policy stood between *"the malicious package told the agent to read `~/.ssh`"* and *"the agent read `~/.ssh`."*

## Three steps that turn your agent into a hardened target

### Step 1 — Install the hook

For Cursor, Claude Code, or Codex CLI:

```bash
curl -sf https://agenticcontrolplane.com/install.sh | bash
```

The hook intercepts every tool call before it executes. It runs regardless of *who* prompted the agent — you, a `postinstall` script, a Jira ticket the agent ingested, a README file with hidden instructions. The hook doesn't trust the source of the prompt.

### Step 2 — Deny reads on sensitive paths and unknown egress hosts

In your dashboard ([cloud.agenticcontrolplane.com](https://cloud.agenticcontrolplane.com)) → Policies:

```json
{
  "mode": "enforce",
  "tools": {
    "Read.credentials":   { "background": { "permission": "deny" }, "interactive": { "permission": "deny" } },
    "Read.env":           { "background": { "permission": "deny" }, "interactive": { "permission": "ask"  } },
    "Bash.cat": {
      "patterns": [
        { "match": "cat .*\\.ssh.*",     "permission": "deny" },
        { "match": "cat .*\\.aws.*",     "permission": "deny" },
        { "match": "cat .*\\.config.*",  "permission": "deny" },
        { "match": "cat .*\\.env.*",     "permission": "ask"  }
      ]
    },
    "Bash.curl": {
      "patterns": [
        { "match": "curl.*", "permission": "deny", "unless": "host IN egressAllowList" }
      ]
    },
    "WebFetch": {
      "if": "url.host NOT IN egressAllowList",
      "permission": "deny"
    }
  },
  "egressAllowList": [
    "api.anthropic.com",
    "api.openai.com",
    "api.github.com",
    "registry.npmjs.org"
  ]
}
```

The semantic: agents (regardless of who prompted them) cannot read SSH keys, AWS credentials, or arbitrary config without explicit permission. Outbound HTTP can only reach hosts on the allow list — calls to attacker-controlled exfil endpoints are denied at the hook layer, before the request leaves the machine.

The Nx attackers hard-coded their exfil URL into the prompt. With this policy, the agent attempts the call, the hook resolves the host against the allow list, the call is denied, the secret stays on the machine, and the audit log captures the attempt.

### Step 3 — Bind end-user identity, per request

For server-side agents (CI runners, scheduled jobs, multi-tenant services), bind the actual end user's identity. This matters during a supply-chain attack because the policy lookup is keyed by user — even if the malicious code somehow ends up running, the policy that applies is the *user's* policy, not a service account's.

```python
from acp_governance import set_context

set_context(
    user_token=request.headers["Authorization"],
    agent_name="ci-build",
    agent_tier="background",
)
```

A compromised `postinstall` running in CI under a service account can still read whatever the service account's policy allows. With end-user identity propagation, the policy is your scoped IdP role — much smaller blast radius.

### (Free fourth step) — Audit + anomaly alerts

Every denied read of a sensitive path or denied egress call writes a structured row. The pattern of a supply-chain attack is *visible*: a sudden burst of `Read.credentials` denials, all from the same agent, all within minutes of an `npm install` event. Set an alert for *"more than N denied sensitive reads in any 5-minute window"* and the next Nx-class attack wakes you up at minute six instead of week six.

## The total time investment

- One `curl` command (Step 1): ~30 seconds
- Sensitive-path + egress-allow-list config (Step 2): ~5 minutes
- End-user identity binding (Step 3, server-side only): ~1 minute

Five to six minutes from blank slate to *"a malicious package that calls my local AI CLI cannot read SSH keys, AWS credentials, or arbitrary config files, and cannot exfiltrate to anywhere not on a known allow list."*

The Nx attack happened in August 2025. The next AI-CLI-weaponized supply-chain attack is not a hypothetical — it's an inevitability of how the ecosystem now works. The fix lives one `curl` command and one policy entry away.

A compromised package inherits whatever surface your agent has standing open — which is the argument for knowing that surface exactly. The [Tool Surface Index](/tool-surfaces) lists it per client, grouped by blast radius, and [getting started](/getting-started) covers the install.

[AgenticControlPlane.com](https://agenticcontrolplane.com)
