Pinned isn't verified: Plugin4Shell, trojanized hook updates and the update path nobody reviews
Review every plugin update, not just the install. In September 2026, Air Security showed that a pinned plugin commit could be swapped on update. An arXiv study saw malicious hook updates succeed in up to 92.5% of runs per tool, across 7 agent tools. Static scanners missed 47.5% of samples. Patch first. Then verify the resolved commit, lock auto-update in managed settings and gate each update with a diff review.
Outcome, time and access
The Claude Code runbook below makes every plugin, skill or hook update pass a check before it reaches a developer machine. Do a staging dry-run on one machine before the fleet.
Prerequisites:
- Claude Code 2.1.179 or later, or Codex 0.146.0 or later. Air Security lists these as the Plugin4Shell fix versions.
- Role-based access control (RBAC) rights to change managed settings. Server-managed settings need the claude.ai Owner role. MDM or
managed-settings.jsonneeds device admin rights. giton the machine that mirrors your plugin repositories.- A named reviewer for plugin diffs.
- A test machine that receives managed settings.
Stop if any one is missing.
Use this if / Do not use this if
Pick this runbook only if it fits your setup.
Use this if:
- Developers install Claude Code plugins, skills or hooks from a marketplace.
- You can push managed settings to their machines.
- You mirror or vendor plugin repositories you depend on.
Do not use this if:
- You need a Copilot vendor fix. Air Security reports none has shipped.
- You run Gemini CLI. Google will not patch it; Air Security recommends Antigravity.
- You need Codex, Copilot or Gemini CLI settings. We could not confirm their equivalents.
Why the update path needs a gate
Four September 2026 studies show one gap: teams review at install and trust every update after.
Plugin4Shell. Or Nevo, Dor Granat and Niv Hoffman of Air Security published on 17 September. Agents checked out a pinned commit without confirming the result. Git can resolve a name to a branch first, so the repository owner could swap the code. The fix compares HEAD to the full pinned SHA.
Trojanized hook updates. Pengxun Li, Litian Zhang, Jianwei Hou, Shujiang Wu, Song Li, Zifeng Kang and Xi Zhang posted arXiv 2609.03884 on 3 September. All 7 agent harnesses they tested fell, across 1,000 runs. Per-tool success reached 92.5%. Microsoft Defender caught none of the samples. Three static defences together missed 47.5%.
Claude mods. Yuval Fischer of Dash Security wrote on 25 September. Mods, a preview feature, load inside the Claude Code process, so endpoint tools see only Claude Code.
GitSpawn. Francisco Rosales of Manifold Security wrote on 1 September. He found 8 issues in 7 agents, where repository git config could run a program. Four were unpatched at publication.
Adversa's October roundup concludes that update review matters more than scanning.
Pre-flight checks (dry-run)
Run these read-only checks on the test machine before changing any setting.
- Run
claude --version. Expect 2.1.179 or later. If lower, stop and update first. - Run
/statusin a session. ExpectEnterprise managed settingson theSetting sourcesline. If missing, fix delivery first. - Save
claude plugin listoutput as your baseline. - Open
~/.claude/plugins/known_marketplaces.json. Note theautoUpdatevalue for each marketplace. - Search shell profiles and dev containers for
CLAUDE_CODE_ENABLE_FUNCTION_HOOKS, the flag that turns mods on. Search plugin caches and.claude/skillsforhooks.jsonfiles with amoduleskey. Expect none. If found, hold that plugin for review.
Execute the runbook
Run these steps in order, verifying each one.
- Lock auto-update per marketplace. In managed settings, set
"autoUpdate": falseon eachextraKnownMarketplacesentry. Verify: in/plugin, try the marketplace's auto-update toggle. Expect an error startingAuto-update for '<name>' is set by. If the toggle still works, the managed entry did not load. Go back to pre-flight check 2.
- Restrict sources. Set
strictKnownMarketplacesto your approved sources,blockedMarketplacesfor known-bad owners, anddisableSideloadFlagstotrue. Verify: add an unlisted source with/plugin marketplace add. Expectis blocked by enterprise policy. If it succeeds, see failure mode 7.
- Restrict hooks. Set
allowManagedHooksOnlytotrue. SetstrictPluginOnlyCustomizationto coverhooksat least. Verify: add a test hook in user settings. Expect it not to run. If it runs, check/statusagain.
- Block command sources. Set
disableCommandPluginSourcestotrue. Command-source plugins re-run their command every session, even with auto-update off. Verify:claude plugin listshows no command-source plugin loaded. If one loads, block it in managedenabledPluginswithfalse.
- Pin and verify. In your own marketplace, pin each entry with
refandsha. Pinarchivesources withsha256. In your mirror, run this check after every fetch:
test "$(git -C path/to/plugin rev-parse HEAD)" = "<full-40-char-sha>" || echo "MISMATCH: stop"
git ls-remote --heads <plugin-repo-url> | grep -E 'refs/heads/[0-9a-f]{40}$' && echo "HASH-NAMED BRANCH: stop"
git ls-remote --symref <plugin-repo-url> HEAD
Verify: the first line prints nothing. The second prints nothing. The third shows the default branch you expect. If any check fails, do not publish the update.
- Gate each update. Run the threat.cafe Update Review Gate below on every new version. Then check the list on that exact version. Verify: the reviewer signs the gate. If "unknown", hold.
- Release on purpose. Change the entry's
sha, then runclaude plugin update <plugin>@<marketplace>on the test machine. Verify:claude plugin listshows the new version. Run/reload-plugins. If the version did not change, see failure mode 3.
threat.cafe Update Review Gate (run on every plugin, skill or hook update)
--------------------------------------------------------------------------
Artifact / source repo : ________________________________
Old SHA or version -> new : ______________ -> ______________
Resolved HEAD == full pinned SHA : yes / no (no = stop)
Hash-shaped branch / odd default : none / found (found = stop)
Diff read by a person : yes / no (no = stop)
New or changed hooks : none / listed: _________________
New network, process or
credential access in diff : none / listed: _________________
In-process modules (mods) : none / listed: _________________
List check on THIS version : known bad / no known bad / unknown
Decision : approve / hold / reject
Reviewer, date : ________________________________
Rollback SHA (last approved) : ________________________________
Treat "unknown" as hold. Unknown isn't the same as safe.
Failure modes and fixes
Claude Code rollouts of these controls fail in nine common ways, each documented by Anthropic. Read them before you push to the fleet.
- You froze security fixes too.
DISABLE_AUTOUPDATERin the managedenvblock stops plugin auto-update for every marketplace. It also stops Claude Code's own updates. The Plugin4Shell fix arrived that way. Fix: prefer per-marketplace"autoUpdate": false. If you must use the variable, run a scheduled manual update of Claude Code itself.
- Users can still turn auto-update on. A managed entry that leaves
autoUpdateunset lets the user's toggle persist. Fix: set the field explicitly,trueorfalse.
- An approved update does not arrive. Claude Code updates only when the computed version changes. A
versioninplugin.jsonwins. If the author keeps"version": "1.0.0", users stay on the cached copy. The update command printsis already at the latest version. Fix: bumpversionon release, or leave it out so the commit SHA is the version.
- Plugins vanish after the allowlist goes live. An allowlist without
{ "source": "skills-dir" }stops skills-directory plugins loading. No error shows. An empty allowlist,[], also blocks the official marketplace. A machine that saw that block will not re-register the official marketplace on its own. Fix: add theskills-direntry if you need it. Register the official marketplace with an explicitextraKnownMarketplacesentry.
- Your MDM policy is ignored. By default only one managed source applies. Server-managed settings win if they deliver any key. Plugin keys in MDM or a file are then skipped. Fix: check
/status. SetmanagedSourcesBehaviorto"merge"if you need both.
- Hooks run the old version. After a mid-session update, hooks keep the previous version's path. Fix: run
/reload-plugins. Restart for monitors.
- The allowlist rejects an approved source. Allowlist entries match exactly. A trailing slash, a
.gitsuffix orssh://instead ofhttps://counts as a different source. Fix: use ahostPatternentry when a marketplace has more than one clone URL.
- User-enabled plugin hooks stop.
allowManagedHooksOnlydoes not trust hooks from plugins users enable themselves. It also blocksheadersHelpercommands unlessdisableCommandPluginSourcesis set tofalse. Fix: enable approved plugins in managedenabledPlugins.
- Private marketplace updates fail quietly. Background checks do not prompt for git credentials. A helper that needs to prompt fails, and the old checkout stays. Fix: run
gh auth login, thengh auth setup-git, on each machine.
Rollback
Plugin rollback in Claude Code takes one managed setting and one SHA change. Set your rollback trigger in advance, such as any unexpected hook, process or network call after an update.
- Block the plugin fleet-wide. Set it to
falsein managedenabledPlugins. Verify:claude plugin listshows it disabled. If not, check managed source precedence. - Repoint your marketplace entry
shato the rollback SHA from the gate. Verify:claude plugin updateandclaude plugin listshow the old version. If not, see failure mode 3. - Re-enable the plugin. Run
/reload-plugins. Verify against your pre-flight baseline. - To remove a plugin for good, map its name to
nullinrenames. SetforceRemoveDeletedPluginstotrue. Verify:/pluginlists it under Flagged. - To undo a policy change, remove the managed key and restart the session. Verify with
/status.
Claude Code deletes an old plugin version directory 14 days after an update. Keep the approved SHA instead.
The steel-man: pinning is enough
The strongest counterargument says a pinned SHA makes updates immutable, so a gate adds only cost. Plugin4Shell shows otherwise. An unverified pin names the code you asked for, not what landed. Patched agents now check. Your mirrors may not.
Where this would be wrong
The case for this runbook assumes developers install third-party plugins. If you allow only plugins your own team writes, the gate is mostly code review you already do. If every agent tool you run verifies resolved commits and signs releases, step 5 adds less. If no updates pass through your marketplace, the gate does not fire. None of the sources report in-the-wild use of these attacks yet.
What to tell the people who sign off
Each group below gets one line, based on the sources above.
- Finance: the cost is reviewer time per update.
- Security lead: the blast radius of one bad update is every machine that auto-updates from that marketplace.
- Legal and compliance: the signed gate is your evidence of review for each version.
- Operations: rollback is one managed setting and one SHA change.
What we don't know yet
The open questions are about fix status. Copilot's status since Air Security's post is unknown. Claude Code mods may change before general release. GitSpawn patch status beyond Manifold's list is unknown. Pair this runbook with the cadence in "Approved last month, malicious today: a re-verification cadence".
Check each new version against the threat.cafe board before you approve it, not only the first install.
Volatility: VOLATILE (event-triggered review). Review when Microsoft ships a Copilot fix, Claude Code mods reach general release, or Claude Code changes its plugin update settings.
Sources
- Plugin4Shell — Air Security, 17 September 2026
- A Blind Trust, the Bloody Thrust: When Attacker-Controlled Hook Updates Steer AI Agent Harnesses towards Malicious Behaviors — arXiv 2609.03884, 3 September 2026
- Claude mods: the new attack surface, built in — Dash Security, 25 September 2026
- GitSpawn: A Single Flaw Lets Untrusted Repos Run Code in Claude Code, Codex, Cursor, and Grok — Manifold Security, 1 September 2026
- Top AI coding agent security resources, October 2026 — Adversa AI, 2 October 2026
- Plugin loading reference — Claude Code docs
- Manage Claude Code plugins for your organization — Claude Code docs
- Host and maintain a marketplace — Claude Code docs