Air Security disclosed Plugin4Shell, a zero-click remote code execution vulnerability affecting Claude Code, Codex, Copilot, and Gemini. The attack bypasses SHA pinning by swapping a trusted plugin for a malicious one during auto-installation. No user interaction is required. The agent inherits the employee's full system permissions, and the plugin inherits those permissions by default.
This is the third disclosure in a series. The first showed that malicious plugins spread virally through open marketplaces (26,000 agents compromised). The second demonstrated repository hijacking (925 skills, 134,000 agents). Plugin4Shell targets the defense mechanism designed to prevent exactly this: SHA pinning.
How the Attack Works
SHA pinning is supposed to lock a plugin to a specific commit. The agent reviews code at commit abc123, pins that hash, and refuses to load anything else. Plugin4Shell defeats this by exploiting the installation flow itself.
Attack sequence:
- Attacker publishes a benign plugin to a marketplace.
- Plugin gains trust and adoption.
- Attacker swaps the repository or redirects the source URL.
- Agent auto-updates or reinstalls the plugin.
- The new malicious code is installed past the SHA pin.
The vulnerability exists because the agent's installation logic does not re-verify the pinned commit against the current repository state. The agent trusts the marketplace metadata or repository pointer, not the actual commit hash at fetch time.
Zero-click means:
- No user approval prompt during plugin updates.
- No diff review between pinned and incoming code.
- Auto-installation on agent startup or background sync.
The agent executes plugin code with the same privileges as the host process. If the employee has access to production databases, internal APIs, or cloud credentials, the plugin does too.
Permission Model Failures
Coding agents do not sandbox plugins. They run in the same process space as the agent runtime. This is not an oversight. It is a design choice driven by two constraints:
- Filesystem access: Plugins need to read and write project files, which requires host-level I/O.
- Tool integration: Plugins call language servers, linters, build tools, and shell commands.
The result is a flat permission model. Once a plugin is installed, it has:
- Full filesystem read/write.
- Ability to spawn subprocesses.
- Access to environment variables (including secrets).
- Network egress without restriction.
No isolation primitives are deployed:
| Primitive | Status in Coding Agents | Why Not Used |
|---|---|---|
| WASM sandbox | Not implemented | Cannot access host filesystem or spawn processes |
| Container per plugin | Not implemented | Latency and complexity for interactive editing |
| Process isolation | Not implemented | Breaks shared state and editor integration |
| Capability tokens | Not implemented | No standard for scoped file or network access |
The agent assumes plugins are trusted at installation time. There is no runtime boundary between plugin code and agent code.
The Installation Trust Boundary
The vulnerability surfaces during installation, not execution. The agent's security model depends on a single gate: the moment a plugin is added to the agent's manifest.
What should happen:
- User requests plugin
fooversion1.2.3. - Agent fetches commit
abc123from the repository. - Agent verifies the commit hash matches the pinned value.
- Agent installs only if the hash matches.
What actually happens:
- User requests plugin
foo. - Agent fetches the latest commit from the repository URL in the marketplace.
- Agent installs without verifying the commit hash against the pin.
- The pin is stored but not enforced during fetch.
The marketplace provides a pointer (repository URL), not a cryptographic proof. The agent trusts the pointer. If the pointer is redirected or the repository is hijacked, the agent installs whatever code is at the new location.
Supply-chain attack surface:
- Marketplace metadata can be edited by the plugin author.
- Repository URLs can be transferred or redirected.
- Git tags and branches can be force-pushed.
- DNS or CDN compromise can redirect fetches.
The agent has no way to distinguish a legitimate update from a hostile swap.
Why Auto-Installation Matters
The "zero-click" classification is critical. It means the attack does not require the user to manually update the plugin or approve a new version. The agent performs the installation automatically in one of three scenarios:
- Agent startup: The agent syncs installed plugins on launch.
- Background update: The agent polls for plugin updates on a schedule.
- Workspace sync: The agent reinstalls plugins when switching projects or machines.
In all three cases, the user is not prompted. The agent assumes the plugin is still trusted because it was trusted at initial installation. The SHA pin is supposed to prevent drift, but the pin is not enforced during the fetch.
Mitigation gap:
Even if the marketplace detects a repository swap and revokes the plugin, the agent has already installed the malicious code. The revocation does not trigger an uninstall. The plugin continues to run until the user manually removes it.
Architectural Fix: Content-Addressable Installation
The root cause is that the agent fetches by pointer (URL) instead of by content (hash). The fix is to make installation content-addressable.
Revised installation flow:
def install_plugin(plugin_id: str, pinned_hash: str):
# Fetch the plugin manifest from the marketplace
manifest = marketplace.get_manifest(plugin_id)
# Fetch the repository at the pinned commit
repo_url = manifest["repository"]
fetched_commit = git.fetch_commit(repo_url, pinned_hash)
# Verify the fetched commit matches the pin
if fetched_commit.hash != pinned_hash:
raise SecurityError("Commit hash mismatch")
# Verify the commit signature (optional but recommended)
if not gpg.verify(fetched_commit):
raise SecurityError("Commit signature invalid")
# Install only if verification passes
install_from_commit(fetched_commit)
This flow ensures the agent installs exactly the code that was reviewed, regardless of repository changes. The marketplace can still provide discovery and metadata, but the agent does not trust the marketplace to deliver the correct code.
Additional hardening:
- Require GPG-signed commits for all plugins.
- Store the public key in the agent's trust store, not the marketplace.
- Fail closed if the signature or hash cannot be verified.
- Log all installation attempts for audit.
Runtime Containment: What It Would Take
Even with content-addressable installation, the plugin still runs with full host privileges. Runtime containment would require a new permission model.
Capability-based approach:
- Plugin declares required capabilities in its manifest (e.g.,
read:src/,exec:npm,net:api.example.com). - Agent prompts the user to approve capabilities at installation time.
- Agent enforces capabilities at runtime using OS-level primitives (seccomp, AppArmor, or a custom syscall filter).
Challenges:
- Filesystem capabilities are coarse-grained. A plugin that needs to read
src/can also read.env. - Process spawning is all-or-nothing. A plugin that needs to run
npm installcan also runcurl | sh. - Network capabilities are hard to scope. A plugin that needs to call an API can also exfiltrate data.
The only robust solution is process-level isolation with a well-defined IPC boundary. Each plugin runs in a separate process with a restricted syscall surface. The agent mediates all filesystem, network, and subprocess access through a capability broker.
This is not how coding agents are built today. The latency and complexity trade-offs are considered unacceptable for interactive editing workflows.
Observability Gaps
The attack is silent. The agent does not log:
- Which commit was fetched during installation.
- Whether the fetched commit matched the pinned hash.
- Which capabilities the plugin exercised at runtime.
An enterprise security team has no visibility into:
- Which plugins are installed across the developer fleet.
- Whether any plugins have been swapped or hijacked.
- What data or systems a plugin accessed.
Minimum observability requirements:
- Centralized plugin inventory with commit hashes.
- Installation and update events streamed to a SIEM.
- Runtime telemetry for filesystem, network, and subprocess access.
- Anomaly detection for plugins that deviate from expected behavior.
None of the four affected agents provide this today.
Likely Failure Modes
Even after patching, the plugin trust boundary remains fragile. Expect these failure modes:
- Developers disable SHA pinning because it breaks their workflow when they want to update a plugin.
- Marketplaces do not enforce signed commits because most plugin authors do not use GPG.
- Agents do not fail closed when verification fails, falling back to installing the latest code.
- Users approve capability prompts without reading them, treating them as click-through dialogs.
- Plugins bundle dependencies that are not covered by the SHA pin, reintroducing supply-chain risk.
The fundamental problem is that coding agents are designed for convenience, not containment. The plugin model assumes trust. The security model is bolted on after the fact.
Technical Verdict
When to use coding agents with plugins:
- You have a centralized plugin allowlist and disable open marketplace access.
- You audit plugin code before installation and pin to reviewed commits.
- You run agents in ephemeral environments (containers, VMs) that are destroyed after each session.
- You have runtime monitoring that alerts on unexpected filesystem or network access.
When to avoid:
- You allow developers to install arbitrary plugins from open marketplaces.
- You run agents on developer workstations with access to production credentials.
- You have no visibility into which plugins are installed or what they do.
- You rely on SHA pinning alone without verifying the hash at fetch time.
The plugin trust boundary is broken by design. Content-addressable installation is necessary but not sufficient. Runtime containment requires a ground-up redesign of the agent permission model. Until then, treat every plugin as untrusted code running with your full privileges.
Top comments (0)