Skip to main content
BlogSecurity

Hijacked AI Coding Assistant Session Spread Shai-Hulud Worm

Mandiant says a hijacked AI coding assistant session led to a poisoned PyPI package, stolen GitHub tokens and a worm in about 100 repos. What is known, and the defences.

Matthews Wong

Software Engineer

October 202611 min read
Hijacked AI Coding Assistant Session Spread Shai-Hulud Worm

Key Takeaway

Mandiant's 2026 report describes an attacker who took over a developer's live AI coding assistant session at an unnamed SaaS provider, got a poisoned package recommended, stole GitHub OAuth tokens and spread the Shai-Hulud worm across about 100 internal repositories. Defend by verifying assistant-suggested dependencies, shortening token lifetimes and guarding hook files.

The sentence that stopped me in the coverage was not about the worm. It was that the attacker got into a developer's live AI coding assistant session, and the assistant itself then recommended the package that carried the infostealer. The developer accepted a suggestion from a tool they trusted. Everything after that was already-known tradecraft.

This post is a close reading of what is actually public: Cloud Security Alliance and Start with Identity summaries of Mandiant's September 2026 findings, plus Backslash's analysis of a separate Shai-Hulud variant. I did not reach Mandiant's report itself, so I mark every claim as reported or inferred. It is a different event from the Tensorlake npm compromise covered in another post here; this one is about the session and what the stolen tokens could reach.

What was reported, and what was not

The account comes from Mandiant's AI Risk and Resilience Report 2026, relayed by The Hacker News, the Cloud Security Alliance (CSA) and Start with Identity. An attacker took control of an active AI coding assistant session on a developer workstation at an unnamed SaaS provider. The assistant recommended a package the attacker had already poisoned, and the developer accepted it.

The table separates the six steps by how well they are supported. The weak points matter as much as the strong ones: the victim, the date, the package names and the way the session was taken over are all undisclosed.

StepWhat was reportedStatus
1. Session takeoverAttacker controls a live assistant session on a developer workstationReported. How it happened is not disclosed
2. RecommendationThe assistant suggests a package the attacker had poisoned; the developer acceptsReported
3. InfostealerInstalled through a poisoned PyPI package, using the live sessionReported. Package name not given
4. Token theftThe stealer takes the developer's GitHub OAuth tokensReported
5. Worm spreadShai-Hulud copies itself across roughly 100 internal repositories, exfiltrating secrets and sourceReported. The count is approximate
6. Second infectionA poisoned package is republished in the company's own npm namespace and a second employee is infectedReported. CSA's view that it needed no further attacker action is an inference

The new step is trust in the assistant, not the worm

Earlier Shai-Hulud waves spread through stolen registry credentials. CSA's note makes the contrast directly: this case started from a developer's trust in an AI assistant's recommendation. A package found through search gets suspicion. A package suggested inside the editor arrives with borrowed authority, so it is accepted faster.

Start with Identity adds the sharper point: the compromised identity was the developer, not the assistant. Every action the attacker took used credentials already on that machine. The OAuth tokens were broad enough to reach about 100 repositories and to publish into a trusted package namespace. That breadth is the real finding, because it is a property of how the developer's access was set up, not of the AI tool.

How the session was hijacked is the open question. CSA reports that Mandiant tracks the actor UNC6780 (TeamPCP) as using techniques that include prompt injection against AI coding assistants and against LLM-based security scanners, but Mandiant presents that and this intrusion as separate findings, and CSA says it is undisclosed whether they share an operator. I treat prompt injection as one plausible route and nothing more.

What the worm looks for once it has a token

A separate Shai-Hulud campaign shows what these worms hunt for. CSA, citing GitGuardian's reverse engineering, reports that the malicious keyv 6.0.0 npm package was published at 9:35 a.m. UTC on 4 August 2026 and that the variant checks 469 credential locations, up from 189. This is a different incident from the session hijack, and the sources do not say the two share a payload.

PlatformEarlier variantskeyv campaign variant
Linux locations89290
Windows locations1250
All locations checked189469

The added categories include AI development tools (Cursor, OpenClaw, OpenAI Codex, OpenCode, Gemini and Hermes), CI systems (ArgoCD, Jenkins, CircleCI) and several cloud providers. Backslash adds that its analysis found the collector reading ~/.claude/, ~/.codex/ and ~/.cursor/, and that the malware searches every user account, not just the current home directory. CSA flags that reading this as attackers building AI-config enumeration into worms is its interpretation, not a confirmed intent.

Backslash also documents persistence that matters for the audit below. It reports a VS Code task in .vscode/tasks.json that runs a script from .claude/ when a repository opens, and a SessionStart hook in .claude/settings.json that runs a script from .vscode/ when a Claude Code session begins, with both files committed across reachable branches. Claude Code's own hooks documentation confirms the mechanism is ordinary: SessionStart runs when a session starts or resumes, and .claude/settings.json is the project settings file that can be committed.

// What the reported Claude Code persistence looks like in .claude/settings.json.
// The shape is the documented one; the red flag is the command, which reaches
// from one tool's directory into another's. Review any hook you did not write.
{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          { "type": "command", "command": "node .vscode/setup.mjs" }
        ]
      }
    ]
  }
}

Audit the files a worm can hook, on every branch

Backslash's advice is to audit .vscode/ and .claude/ on all branches, not only main, because the hook files were committed across reachable branches. This script lists every local and remote ref that carries one of the four reported file names, shows who last touched it, and greps for the reported hook shapes. It is read-only.

#!/usr/bin/env bash
# Read-only. Run from the root of a clone, ideally on a machine you trust.
set -euo pipefail
git fetch --all --prune --quiet

# The four file names Backslash reported as hook carriers.
PATHS=(.claude/settings.json .claude/setup.mjs .vscode/tasks.json .vscode/setup.mjs)

for ref in $(git for-each-ref --format='%(refname:short)' refs/heads refs/remotes); do
  for p in "${PATHS[@]}"; do
    # cat-file -e is the cheap existence test: no checkout, no worktree churn.
    if git cat-file -e "$ref:$p" 2>/dev/null; then
      echo "== $ref : $p  (last change: $(git log -1 --format='%h %an %ad' --date=short "$ref" -- "$p"))"
    fi
  done
done

# The two reported hook shapes, searched on every remote branch, not just main.
git grep -nE 'SessionStart|setup\.mjs' \
  $(git for-each-ref --format='%(refname)' refs/remotes) -- .claude .vscode || true

A hit is not proof of compromise. Many teams commit a legitimate .claude/settings.json with hooks. The signal is a hook file nobody on the team remembers adding, a script path pointing from one tool's directory into another's, or a commit author that does not match the team.

Do the audit from a clean machine, not the workstation you suspect. Backslash reports a dead man's switch that wipes parts of local state after stolen credentials are revoked, so rebuild workstations, build machines and CI runners from trusted images rather than cleaning them by hand.

Close the paths the worm used

Mandiant's three controls, per CSA, are verifying AI-recommended dependencies against checksums and allowlists, keeping raw API keys and long-lived OAuth tokens away from assistant extensions, and routing dependency traffic through controlled internal repositories. Below are concrete forms of each, built from documented features: CODEOWNERS for hook files, pip hash-checking, and npm trusted publishing so no write token sits on a laptop.

# .github/CODEOWNERS  (the last matching pattern wins)
# Pair with "Require review from Code Owners" in branch protection or a ruleset.
*                       @acme/platform
/.claude/               @acme/security
/.vscode/               @acme/security
/.github/workflows/     @acme/security
/.github/CODEOWNERS     @acme/security   # protect the file that protects the rest

# requirements.txt, installed with: pip install --require-hashes -r requirements.txt
# One hash anywhere switches hash-checking on for everything, and every
# dependency then needs a pin (==) and a hash, or the install errors out.
# examplepkg is a placeholder; generate the real digest, then review it.
examplepkg==1.2.3 \
    --hash=sha256:REPLACE_WITH_THE_REVIEWED_SHA256_DIGEST

# .github/workflows/publish.yml (npm trusted publishing, no stored write token)
# Needs npm CLI 11.5.1+ and Node 22.14.0+. The workflow file name must match
# the trusted publisher configured on npmjs.com, including the .yml extension.
permissions:
  id-token: write   # required for OIDC
  contents: read
# ...checkout, setup-node, npm ci, build, test, then:
#   - run: npm publish

The pip rule is all-or-nothing: the pip documentation says specifying a hash against any requirement activates hash-checking globally, requirements must be pinned with ==, and every dependency needs a hash. That is exactly the property you want against a suggested package. A package the assistant adds will fail the install until a human generates and reviews its hash.

What each control stops, and what it does not

CSA is blunt that these controls mainly address propagation. They do not prevent a session hijack in the first place. Reading the incident chain, each control cuts a different link.

ControlLink it cutsWhat it does not do
Hash-pinned installs (pip --require-hashes)Steps 2 and 3: an unreviewed suggested package cannot installDoes not help if a reviewer approves a hash for a malicious package
Short-lived tokens and trusted publishingSteps 4 to 6: a stolen laptop token expires and cannot publish to the namespaceDoes not stop theft of whatever is valid at that moment
CODEOWNERS on .claude, .vscode and workflowsStep 5: a worm commit cannot merge into a protected branch unreviewedDoes not cover branches without the protection rule
Internal package proxy and allowlistSteps 2 and 3: unapproved packages are not fetchableCSA notes an allowlist or scanner can itself be targeted
Session timeouts and anomaly monitoring on the assistantStep 1: shortens the window a hijacked session stays usefulCSA's recommendation; neither source says it would have stopped this intrusion

The honest reading is that layers matter. CSA warns that any single check, allowlisting, secret isolation or scanner review, can itself be targeted, so a design that depends on one of them is one bypass away from the same outcome.

Failure modes and open questions

Three things could make a team feel safer than it is. First, reviewing assistant suggestions only on the laptop: SafeDep's write-up says to scan dependencies in CI, not only on laptops, and a laptop check is exactly what a hijacked session can bypass. Second, rotating tokens and declaring victory: Backslash's reported dead man's switch means revocation can trigger damage to local state, so the order of operations matters. Third, auditing only main.

Several things remain unknown and I will not guess at them: the victim's name, the intrusion date, the package names, the assistant product, and how the session was taken over. Until those details are public, the defensible advice is to reduce what any one developer token can reach.

The 100-repository figure is approximate and comes from secondary summaries of Mandiant's report. Do not repeat it as a precise count, and do not assume your assistant vendor was involved: no source names one.

What to do on Monday

Ordered by how much each step shrinks the blast radius for the time it costs.

  1. Find out what one developer token can do. List the repositories and the package namespaces a typical workstation OAuth token can write to; if it can publish, that is the finding.
  2. Run the audit script above across your main repositories and review every hook file it reports.
  3. Add CODEOWNERS entries for .claude, .vscode and workflow directories and enable required code owner review on protected branches.
  4. Move package publishing to OIDC trusted publishing so no write token lives on a laptop.
  5. Adopt hash-pinned installs for Python in CI, so an assistant-suggested package cannot install without a reviewed hash.
  6. Inventory the AI assistants, IDE extensions and MCP servers on developer machines, and move raw API keys and long-lived tokens out of their config directories.

The lesson I take from this incident is that an AI assistant is now part of your supply chain and your authentication surface at once. Treat its suggestions like an external pull request, treat its session like an authenticated resource, and assume the developer token behind it will eventually be taken. Make that token worth less.

FAQ

Frequently Asked Questions

Short answers to what readers ask most about this topic.

What happened in the AI coding assistant session hijack that spread Shai-Hulud?

Mandiant's September 2026 report, as relayed by CSA and others, says an attacker took over a developer's live AI coding assistant session at an unnamed SaaS provider. The assistant recommended a poisoned package, an infostealer from PyPI stole GitHub OAuth tokens, and Shai-Hulud spread across about 100 internal repositories.

How was the AI assistant session hijacked?

It has not been disclosed. CSA lists this as the main open question. Mandiant separately tracks the actor UNC6780 (TeamPCP) using prompt injection against AI coding assistants, but presents that as a distinct finding, so it should not be assumed to be the route here.

Is this the same as the Tensorlake npm Shai-Hulud compromise?

No source ties them together. The session hijack is a Mandiant-reported intrusion at an unnamed SaaS provider, while the keyv campaign and its 469-location credential scan are a separate Shai-Hulud variant. Treat them as related worm lineage, not one event.

How do I stop an AI assistant from installing a malicious package?

Install Python dependencies with pip's hash-checking mode so any package an assistant adds fails until a human reviews its hash. Add an internal package proxy or allowlist and scan dependencies in CI, not only on laptops. No single check is enough, since CSA notes allowlists and scanners can be targeted too.

What files should I audit for worm persistence on AI coding tools?

Backslash reports hooks in .vscode/tasks.json and .claude/settings.json that reference setup scripts, committed across reachable branches. Audit .claude, .vscode and workflow files on every branch, put them under CODEOWNERS, and rebuild machines from trusted images instead of cleaning them by hand.