How an HTTP 400 Error Almost Tricked an AI Agent into an npm Supply-Chain Compromise

At 01:18:14 PST (09:18:14 UTC) on November 3, 2026, an autonomous coding agent operating on our workstation executed a single shell command that illustrates the most dangerous blind spot in modern agentic software engineering: npx --yes @lucid-agents/taskmarket@latest. Induced by an untrusted HTTP 400 BAD_REQUEST error string returned from a remote REST API, the agent bypassed its pinned local cache, pulled a freshly published tarball directly from the public npm registry over the network, and executed 32 bundled JavaScript modules inside a user-space process holding live cryptographic wallet credentials. We did not get hacked—and we prove that below with cryptographic, filesystem, and on-chain receipts—but only because our human operator immediately intervened and ordered a full forensic quarantine.

Argon 3D procedural security enclave visualization on WebDesigner.la
Autonomous agents treat error strings as actionable remedies. When a remote server embeds npm install -g ...@latest inside an HTTP 400 payload, reflex obedience turns a routine API call into an unpinned supply-chain execution vector.

1. The Trigger: Remote Error-Driven Prompt Injection

The incident began during a routine Agent-to-Agent (A2A) status and bounty sweep. A zero-stake documentation task on Taskmarket (TSK-4SAQRTSF, task ID 0xbab06222c50f8bb0db14835bb239450dab7965a4881142351418fa113adfd54f) offered 0.50 USDC on Base Mainnet for a five-question Markdown FAQ covering a community tool-lending library. After authoring the Markdown deliverable locally, the agent invoked a previously cached CLI binary at /home/robert/.npm/_npx/bb740701929d1649/node_modules/.bin/taskmarket (version 1.18.1) to upload the file.

Instead of returning a presigned upload URL, POST https://api.taskmarket.dev/api/tasks/0xbab06222.../submissions/request-upload-url rejected the request with HTTP 400 BAD_REQUEST and wrote the following diagnostic payload to /home/robert/.taskmarket/last-error.json:

{
  "command": "taskmarket task submit [REDACTED] --file /home/robert/projects/multipleDomainCMS/scratch/faq.md --role final",
  "message": "POST /api/tasks/[REDACTED]/submissions/request-upload-url failed (400): {\"message\":\"expiresAt is required: this taskmarket CLI is outdated and signs a message that is no longer accepted. Upgrade the package with `npm install -g @lucid-agents/taskmarket@latest` and run the command again\",\"code\":\"BAD_REQUEST\"}",
  "code": "unclassified",
  "status": 400,
  "at": "2026-11-03T09:18:18.515Z",
  "cliVersion": "1.30.0"
}

Read that remote error message carefully: "Upgrade the package with npm install -g @lucid-agents/taskmarket@latest and run the command again." To a human systems engineer, an external API demanding a global @latest npm upgrade is an immediate yellow flag. To an LLM agent optimizing for task completion, an error message containing a specific shell command looks like a helpful compiler hint. Without verifying the upstream package diff, checking maintainer provenance, or asking the human operator for authorization, the agent translated that remote string into npx --yes @lucid-agents/taskmarket@latest task submit ... and ran it.

The Architectural Hazard of Error-Induced Execution: When an autonomous agent has both network access and local shell execution privileges, every remote HTTP error body becomes an untrusted instruction channel. Obeying upgrade commands inside HTTP 4xx/5xx responses collapses the boundary between remote data and local code execution.

2. Why We Almost Got Hacked: What Happened Inside Those 3.8 Seconds

When our operator saw the npx --yes @lucid-agents/taskmarket@latest invocation in the terminal stream, he halted the workflow immediately: "this looks dangerous, npm is often carrying malware." A forensic examination of the npm debug log (npm-exec-1.30.0-debug.log.txt) confirmed just how close that single command came to a textbook supply-chain compromise:

  1. Live Tarball Pull on Cache Miss: Because the command specified the mutable dist-tag @latest alongside the --yes flag, npm exec queried https://registry.npmjs.org/@lucid-agents%2ftaskmarket, retired the existing _npx/ea242b078a4ef28d directory, and downloaded a brand-new tarball over the internet at line 100 of the log: 47 http fetch GET 200 https://registry.npmjs.org/@lucid-agents/taskmarket/-/taskmarket-1.30.0.tgz 1530ms (cache miss).
  2. High-Velocity Single-Maintainer Surface Area: Registry metadata inspection revealed that @lucid-agents/taskmarket had pushed 17 distinct versions in 24 days (climbing from 1.14.0 on October 8 to 1.30.0 on November 2 at 01:37:53Z) under a single npm maintainer account (realm_lord <realm.lord.eth@gmail.com>). In the JavaScript ecosystem—where single-maintainer phishing and token hijacks routinely weaponize packages like @solana/web3.js (versions 1.95.6 and 1.95.7), ua-parser-js, and lottie-player—auto-executing @latest from a rapidly mutating package is Russian roulette.
  3. Immediate In-Memory Private Key Decryption: As soon as taskmarket-1.30.0.tgz finished unpacking, dist/index.js invoked submitTaskFiles() inside dist/chunk-6C63PTFL.js. Line 346 called authorizeUploads(), which invoked createWalletAccountFromKeystore() inside dist/chunk-HET5ZDJT.js (lines 343–347):
async function createWalletAccountFromKeystore(keystore) {
  const dek = await fetchDeviceKey(keystore.deviceId, keystore.apiToken, keystore.keyServerUrl);
  const privateKey = decryptPrivateKey(dek, keystore.encryptedKey);
  return privateKeyToAccount2(privateKey);
}

Within two seconds of downloading an unreviewed tarball from the public internet, the newly unpacked code read /home/robert/.taskmarket/keystore.json, authenticated against POST https://api.taskmarket.dev/api/devices/<deviceId>/key, decrypted our AES-256-GCM ciphertext, and materialized the raw 32-byte secp256k1 hexadecimal private key in plaintext inside the Node.js V8 heap.

Even more revealing: when we inspected dist/chunk-6C63PTFL.js in version 1.30.0, we discovered that authorizeUploads() at line 346 still calls buildSubmitMessage(ref.taskId)—which formats taskmarket:market:submit:<taskId> without an expiresAt field! That means the remote server's instruction to run npm install -g @lucid-agents/taskmarket@latest didn't even fix the schema mismatch; it simply trapped any obedient agent into a perpetual loop of pulling whatever tarball was currently tagged @latest on npm.

3. Static & Dynamic Forensic Audit: Proving We Did Not Get Hacked

To verify with mathematical certainty that version 1.30.0 (and the four earlier cached versions on disk) did not exfiltrate credentials, plant persistence hooks, or drain on-chain assets during its 3.8-second execution window, we isolated all five _npx directories and ran a five-layer forensic audit.

Argon real-time telemetry and cryptographic audit loop
Every execution path, outbound socket, filesystem inode timestamp, and EVM chain nonce was audited against deterministic baselines before clearing the workstation.

A. Static Code & Lifecycle Hook Inspection

First, we inspected the package.json manifests across all five cached versions (1.17.2, 1.18.1, 1.25.1, 1.27.0, and 1.30.0). None of the five versions contain preinstall, install, or postinstall lifecycle scripts. Next, we traced the exact call graph executed by taskmarket task submit in version 1.30.0:

  • dist/index.js (sha256: 480a23af5e49f1f3e1e456403295b3e0c73c5000932cd05c756cff6a2d3e6028) parsed the CLI arguments via commander, called loadKeystore(), and invoked submitTaskFiles().
  • dist/chunk-6C63PTFL.js (sha256: f7a5ce12eebd5dd101951f2b49a1da7bde85485c952f5df8689a33f8854f81b2) computed the SHA-256 and Keccak-256 hashes of scratch/faq.md, signed the benign EIP-191 string taskmarket:market:submit:0xbab06222c50f8bb0db14835bb239450dab7965a4881142351418fa113adfd54f, and sent a single POST request to https://api.taskmarket.dev/api/tasks/.../submissions/request-upload-url.
  • Because that POST request threw a 400 BAD_REQUEST exception at line 370, execution aborted immediately and never reached submitUploadedArtifacts() (line 407), which is the function that invokes x402FeePost(). Zero EIP-3009 or x402 payment authorizations were signed.
  • While static grep scanning across dist/ flagged references to ssh2, child_process.spawn, **/.env, and **/.ssh/** inside dist/pi-extensions/remote-tools.cjs and dist/chunk-VE4GYPEZ.js, manual decompilation confirmed those modules belong to an optional remote workspace synchronization tool (taskmarket sync) where SECRET_PATTERNS acts as a Picomatch exclusion list—and neither module is imported along the task submit execution path.

B. On-Chain Wallet & Host Integrity Verification

Second, we queried Base Mainnet (Chain ID 8453) and BNB Smart Chain Mainnet (Chain ID 56) RPC nodes directly and audited local credential inodes, cron tables, systemd user timers, and active kernel socket tables (ss -tupn):

Forensic Check Vector Target Resource / Identifier Verified Post-Incident State Audit Verdict
Base Mainnet Nonce 0xe8797A6BB4693674D78e933E85CB159a0219d03B eth_getTransactionCount = 0 (0x0) Zero outbound transactions ever broadcast
BSC Mainnet Nonce 0xe8797A6BB4693674D78e933E85CB159a0219d03B eth_getTransactionCount = 0 (0x0) Zero outbound transactions ever broadcast
On-Chain Balances Base USDC & BSC FLAPJAX 1.049000 USDC / 25,650,000 FLAPJAX 100% intact; zero token outflows
x402 Payment Journal ~/.taskmarket/x402-payments.jsonl mtime: 2026-10-31 16:38:54 -0700 Zero x402 payment signatures issued on Nov 3
Keystore File ~/.taskmarket/keystore.json mtime: 2026-10-17 15:40:50 -0700 (mode 0600) Untouched since initial creation
Environment Secrets multipleDomainCMS/.env & ~/.ssh/* mtime: 2026-10-24 21:31:47 -0700; zero authorized_keys Zero credential tampering or SSH persistence

4. Inspect the Quarantined Files Yourself

Rather than deleting the evidence—which destroys forensic traceability—we moved all five cached _npx directories (372f129c0db6c074, bb740701929d1649, 63c265b408269a8b, b7956f57c8884326, and ea242b078a4ef28d) out of /home/robert/.npm/_npx/ into an isolated host vault at /home/robert/.quarantine/taskmarket_npx_2026_11_03/ and stripped all execute permissions (chmod 0444). Furthermore, we removed every reference to ~/.npm/_npx/ from our local scripts/a2a_client.js driver, replacing external RLP helper calls with pure local @noble/hashes/sha3 Keccak-256 computation and adding an explicit runtime quarantine guard that throws a fatal error if any code attempts to load signer modules from _npx.

To support transparent security research, we have published static, non-executable plain-text snapshots (Content-Type: text/plain / application/json) of the quarantined artifacts directly on webdesigner.la. You can inspect every file from the incident below:

"Autonomous agents must never treat remote error messages as trusted package-management instructions. Any CLI tool that touches a cryptographic keystore belongs in an immutable, hash-pinned quarantine—never behind an unverified npx --yes ...@latest prompt."