The Plugin Trust Problem in AI Coding Tools

The Plugin Trust Problem in AI Coding Tools

AI coding tools increasingly rely on plugins to extend what agents can inspect, change, and execute. That creates a trust problem: a plugin can pass review, receive approval, and later deliver different code.

The failure is not specific to one assistant. The same supply-chain and integrity question spans Claude Code, Codex, GitHub Copilot, and Gemini CLI: what exactly was reviewed, what does the tool fetch later, and can those two things diverge?

A repository that looked safe at approval time may remain the named source while its contents change underneath the agent. If the tool trusts the reference rather than the reviewed content, user consent becomes stale.

This post focuses on three practical questions. What is pinned—the repository, a movable Git reference, an immutable commit, or the content itself? When is that identity verified—during approval, at fetch time, or immediately before execution? And who controls the resulting risk: the vendor, the plugin maintainer, or the team running the agent?

The intended security model was straightforward: review a plugin, approve it, and then trust the repository or revision associated with that approval. The approval acts like a safety lock. Future runs are expected to retrieve the same plugin rather than silently accepting an arbitrary replacement.

That model fails if it identifies content by location instead of by immutable identity. A repository answers where code comes from. A commit SHA or content digest answers which exact bytes were reviewed. Those are different guarantees.

A branch or tag is only a movable Git reference. Its name can remain unchanged while its target changes. If an agent records main or a tag during approval, then resolves that reference again later, it may receive a different commit without any visible change to the trusted location. The repository still looks familiar; the reviewed code is not.

The resulting problem is not that review has no value. Review can establish confidence in a specific snapshot. The problem is that the trust decision is being applied to a mutable pointer rather than bound to that snapshot. A secure design must preserve the relationship between approval and content across time: record the immutable revision, verify the fetched content against it, and reject unexpected movement. Without that binding, “approved repository” can mean little more than “approved place to look,” leaving the supply chain free to change underneath the lock.

Show a four-panel supply-chain flow from plugin review to agent execution, with Claude Code, Codex, GitHub Copilot, and Gemini CLI represented as para

The reported Git failure is a reference-resolution problem, not a sophisticated payload problem. A plugin’s metadata can identify a repository and a branch or tag that looked safe during approval. Later, an attacker—or anyone who controls that upstream reference—can make the same name resolve to a different commit. The agent then retrieves the replacement code, despite the user having approved another version.

The important distinctions are easy to blur:

  • Plugin metadata describes what to fetch and how to run it.
  • Repository location identifies where the project lives.
  • Branch or tag state identifies a movable name at a particular moment.
  • Commit identity identifies a specific Git snapshot.
  • Fetched bytes are the actual code the agent executes.

Approval is meaningful only if those last bytes remain tied to the reviewed commit. Trusting a repository name or movable reference leaves a gap between review and execution.

That gap can produce a zero-click path. If the coding agent automatically checks for plugin updates, refreshes metadata, or fetches the configured reference during startup, execution can occur without a new approval prompt. The user’s earlier consent becomes an implicit authorization for whatever the reference resolves to later. No interaction is needed at the moment the trusted dependency changes.

This is why the issue is broader than a malicious marketplace listing. The plugin can begin as legitimate, pass review, and become unsafe when its reference moves. The agent’s behavior is then determined by the fetched content, not by the content originally inspected. Without checking the resolved commit or validating the bytes against an expected digest at fetch time, the safety lock records historical approval while execution follows current repository state.

The durable fix is not merely “review the plugin.” It is to bind execution to the exact content that was reviewed. A repository name identifies a location, not a version. A branch or tag is only a movable pointer. An immutable commit SHA, or a digest of the fetched content, gives the agent a stable identity to check.

That check must happen when the plugin is fetched and used, not only when approval is granted. Reviewing a snapshot establishes confidence in that snapshot. It says nothing about bytes returned later if the reference has moved, the repository has been rewritten, or an upstream update was made without going through the expected review path.

The agent should therefore record the approved identity, resolve the reference at fetch time, and compare the result before loading code. Unexpected ref movement should be visible rather than silently accepted. If the resolved commit or content digest differs, execution should stop. Failing closed is inconvenient for legitimate updates, but continuing automatically turns an integrity check into a notification.

This also separates two decisions that are often conflated: whether an update is available and whether it is trusted. Updates can be deliberate, but they need a new review or an explicit controlled update workflow. A mutable reference cannot serve as both a convenient update channel and a security boundary. Once approval is treated as authorization to execute future ref contents, the reviewed snapshot no longer protects the user.

Present an exploded-view comparison of weak versus strong plugin pinning. The weak stack uses a repository plus branch or tag and ends at attacker-con

Four vendors, four trust assumptions

The responses were not interchangeable. Anthropic and OpenAI patched the reported behavior, treating a trusted plugin reference that later resolves to different code as an integrity failure. Their fix reflects a straightforward assumption: approval applies to the reviewed content, not merely to the repository named in the configuration.

Microsoft had not patched the issue at the time described. That leaves GitHub Copilot exposed to a different interpretation of compatibility and update behavior: mutable upstream references may remain acceptable if they preserve the expected plugin workflow. The tradeoff is explicit, even if the product does not present it that way. Convenient updates and broad compatibility can conflict with reproducible execution.

Google said it would not change its approach because it viewed the behavior differently. That position treats the observed substitution as outside the security boundary—or otherwise as the responsibility of the upstream repository and its users, rather than the CLI. Under that model, the tool can trust the location supplied by the user without guaranteeing that the bytes fetched later match the bytes approved earlier.

Those distinctions matter more than the patch count. A patch says the vendor owns the integrity check. No patch can mean mutable content is an accepted dependency risk. Rejecting a change says the vendor draws the boundary elsewhere. Teams evaluating coding agents should therefore ask not only whether a vulnerability was fixed, but what each vendor believes a plugin approval actually guarantees, who controls updates, and who absorbs the impact when that guarantee fails.

Evaluate a coding agent’s plugin system as a supply-chain control, not a marketplace feature. Ask:

  • Provenance: Who publishes the plugin, where is its source hosted, and can ownership or distribution change without review?
  • Version identity: Does approval bind to an immutable commit SHA or content digest, or merely to a branch, tag, or repository URL?
  • Manifest behavior: Is the exact revision recorded in a lockfile or manifest, and is that record enforced on every fetch?
  • Verification: Are signatures or digests checked before execution, with mismatches treated as errors rather than update prompts?
  • Updates and revocation: How are upgrades approved, old versions revoked, and compromised references blocked? Is every change visible in an audit log?
  • Execution boundaries: Does the plugin run in a sandbox? What filesystem, credential, and network permissions does it receive? Can it contact arbitrary hosts?
  • User agency: Can a changed dependency execute during startup, sync, or another automatic workflow, or is explicit user action required?

The minimum acceptable behavior for this failure mode is straightforward: review a specific content identity, store it, verify it again at fetch time, and fail closed on any mismatch. Sandboxing and restricted network access reduce impact but do not repair a broken pin. A plugin that silently follows mutable Git state remains an integrity risk even when its repository is reputable and its initial review was sound.

Show a lifecycle timeline for a plugin: publish, review, approval, later update, fetch, verification, and execution. Place a red fork where a mutable

Plugin trust is not established once, at approval time. Review creates confidence in a specific set of bytes; it does not make every future version of a repository trustworthy. That confidence survives only if execution remains bound to the reviewed commit or content digest, and the agent verifies that binding whenever it fetches or uses the plugin.

That makes pinning and repeat verification integrity controls, not marketplace features. A mutable dependency left unverified is an accepted supply-chain risk, regardless of the consent screen that preceded it. Whether that risk remains acceptable is ultimately a vendor policy decision: vendors choose what references they trust, what mismatches block execution, and whether users bear the consequences.

← All posts