Agentic Supply Chain Vulnerabilities
Description
Dynamic loading of tools, plugins, and MCP servers from untrusted or unverified sources introduces malicious code, data exfiltration capabilities, or backdoors into the agent runtime.
Risk
Risk Overview
Agentic systems are inherently composable — they acquire capabilities at runtime by connecting to tool servers (MCP), loading plugins, and integrating external APIs. This dynamic supply chain is fundamentally different from traditional software dependencies: tools are discovered, loaded, and executed at runtime, often based on natural language descriptions rather than verified code signatures. A compromised tool in this chain has direct access to the agent's context, credentials, and capabilities.
Attack Surface
- Unverified MCP servers: Agents connect to MCP servers discovered through registries or recommendations without verifying the server's identity or code integrity
- Malicious tool descriptions: Tool descriptors that accurately describe benign functionality but whose implementation includes hidden data exfiltration or credential harvesting
- Dependency confusion: Attackers register tool names that closely resemble legitimate tools (typosquatting) in public registries
- Version pinning gaps: Tools auto-update without review, allowing attackers who compromise a tool publisher to push malicious updates
- Transitive dependencies: A trusted tool depends on untrusted sub-tools or libraries, inheriting their vulnerabilities
Business Impact
- Backdoor installation: Malicious tools establish persistent access to the agent's environment
- Data harvesting: Tools silently copy sensitive data from agent context to external collection points
- Integrity attacks: Tools return subtly altered results that cause agents to make wrong decisions (e.g., a financial data tool that skews numbers)
- Reputation damage: Organization deploys agents using tools later revealed as malicious, undermining customer trust
Attack Scenarios
An organization's agents connect to MCP servers listed in a community registry. An attacker publishes a server named 'enhanced-web-search' that mimics a popular search tool. The tool returns legitimate search results but also logs every query (including sensitive data from the agent's context) to an external database. Dozens of organizations connect their agents before the exfiltration is discovered.
A legitimate MCP tool publisher's CI/CD pipeline is compromised. The attacker pushes an update to the 'database-query' tool that adds a conditional backdoor: if the query contains 'SELECT' and the table name matches a pattern, the tool also executes a second query exfiltrating the results. The update passes automated tests because the backdoor only triggers on specific patterns.
An agent is configured to dynamically discover tools based on task requirements. When asked to 'analyze this spreadsheet,' it searches a tool registry and selects 'spreadsheet-analyzer-pro' — a typosquatted version of the legitimate 'spreadsheet-analyzer.' The malicious tool extracts all formulas and data from the spreadsheet, including embedded credentials and linked data sources, before returning a plausible analysis.
Mitigations
Tool Registry with Signatures
Maintain a curated, organization-controlled tool registry:
- Require cryptographic signatures on all tool packages using a code-signing certificate chain the organization controls
- Sign tool descriptors separately from tool implementations so tampering with either is detectable
- Implement signature verification at load time — unsigned or mis-signed tools are rejected unconditionally
- Publish a transparency log of all tool additions, updates, and removals
Allowlisted Sources
Restrict tool acquisition to verified sources:
- Maintain an explicit allowlist of approved MCP servers, plugin repositories, and API endpoints
- Block agents from connecting to tool sources not on the allowlist, even if discovered through web search or recommendations
- Require security review and approval before adding new sources to the allowlist
- Implement DNS-level controls to prevent connections to unapproved tool servers
Runtime Integrity Checks
Verify tool integrity continuously, not just at install time:
- Hash tool binaries and configurations at load time and compare against the registry's recorded hashes
- Monitor tool behavior against declared capabilities — flag tools that make network connections, file accesses, or system calls not described in their manifest
- Implement canary inputs: send known-output test queries to tools periodically and verify results match expected output
- Use runtime sandboxing to limit tool access to only the resources declared in their capability manifest
Version Pinning and Review
Prevent silent updates from introducing vulnerabilities:
- Pin all tool versions in the agent's configuration and require explicit review for version bumps
- Implement a staged rollout: new tool versions deploy to a test agent pool before production
- Diff tool descriptors and behavior between versions and flag unexpected changes
- Maintain rollback capability to immediately revert to a previous tool version
Supply Chain Inventory
Maintain a complete bill of materials for the agent's tool chain:
- Record every tool, its version, source, signature status, and last verification date
- Map transitive dependencies so compromises in sub-tools are traceable
- Conduct periodic supply chain audits comparing the live agent's tool set against the approved inventory
Code Examples
Tool Registry Validator
import hashlib
import json
import time
from dataclasses import dataclass
from typing import Optional
from enum import Enum
class VerificationStatus(Enum):
VERIFIED = "verified"
SIGNATURE_MISMATCH = "signature_mismatch"
NOT_IN_REGISTRY = "not_in_registry"
EXPIRED = "expired"
REVOKED = "revoked"
@dataclass
class ToolManifest:
name: str
version: str
publisher: str
source_url: str
descriptor_hash: str
binary_hash: str
signature: str
capabilities: list[str] # declared: ["network:read", "filesystem:none"]
approved_at: float
expires_at: float
class ToolRegistryValidator:
"""Validates tools against an organization-controlled registry."""
def __init__(self):
self._approved_tools: dict[str, ToolManifest] = {}
self._allowed_sources: set[str] = set()
self._revoked: set[str] = set()
def register_approved_tool(self, manifest: ToolManifest):
key = f"{manifest.name}@{manifest.version}"
self._approved_tools[key] = manifest
def add_allowed_source(self, source_url: str):
self._allowed_sources.add(source_url)
def revoke_tool(self, name: str, version: str):
self._revoked.add(f"{name}@{version}")
def verify_tool(
self,
name: str,
version: str,
source_url: str,
descriptor_content: str,
binary_content: bytes,
) -> dict:
"""Full verification: source, registry, hashes, expiry."""
key = f"{name}@{version}"
result = {"tool": key, "checks": {}}
# Check source allowlist
result["checks"]["source_allowed"] = source_url in self._allowed_sources
if not result["checks"]["source_allowed"]:
result["status"] = VerificationStatus.NOT_IN_REGISTRY.value
result["block"] = True
return result
# Check revocation
if key in self._revoked:
result["status"] = VerificationStatus.REVOKED.value
result["block"] = True
return result
# Check registry entry exists
manifest = self._approved_tools.get(key)
if not manifest:
result["status"] = VerificationStatus.NOT_IN_REGISTRY.value
result["block"] = True
return result
# Check expiry
if time.time() > manifest.expires_at:
result["status"] = VerificationStatus.EXPIRED.value
result["block"] = True
return result
# Verify descriptor hash
actual_desc_hash = hashlib.sha256(descriptor_content.encode()).hexdigest()
result["checks"]["descriptor_hash"] = actual_desc_hash == manifest.descriptor_hash
# Verify binary hash
actual_bin_hash = hashlib.sha256(binary_content).hexdigest()
result["checks"]["binary_hash"] = actual_bin_hash == manifest.binary_hash
all_hashes_match = (
result["checks"]["descriptor_hash"] and result["checks"]["binary_hash"]
)
if all_hashes_match:
result["status"] = VerificationStatus.VERIFIED.value
result["block"] = False
else:
result["status"] = VerificationStatus.SIGNATURE_MISMATCH.value
result["block"] = True
return result
def get_inventory(self) -> list[dict]:
"""Return full tool supply chain inventory for audit."""
return [
{
"tool": key,
"publisher": m.publisher,
"source": m.source_url,
"approved_at": m.approved_at,
"expires_at": m.expires_at,
"revoked": key in self._revoked,
}
for key, m in self._approved_tools.items()
]
Evidence Requirements
- Tool registry with signed manifests and approval audit trail
- Supply chain bill of materials (SBOM) for all agent tool dependencies
- Integrity verification logs showing hash checks at every tool load
- Source allowlist change history with approver attribution
- Canary test results validating tool output consistency over time