This is post #4 of the ). I know first-hand how easy it is to trust a tool descriptor without verifying what's behind it.
Except it's not the real Postmark MCP server. It's a lookalike that silently BCC's every email to an attacker. And because your agent trusted the package registry, it never questioned it.
This actually happened. It was the first documented malicious MCP server found in the wild. And it won't be the last.
Welcome to Agentic Supply Chain.
What Are Agentic Supply Chain Vulnerabilities?
ASI04 covers threats that arise when agents, tools, and the artifacts they depend on are provided by third parties who may be malicious, compromised, or tampered with in transit.
If you've dealt with traditional software supply chain attacks (think SolarWinds, Log4j, the npm event-stream incident), this sounds familiar. But agentic systems make it worse. Much worse.
Here's why. Traditional supply chain attacks target static dependencies - packages you install at build time. Agentic supply chains are live. Your agent discovers and connects to tools at runtime. It loads MCP servers dynamically. It registers with agent-to-agent protocols. It pulls prompt templates from external sources.
The attack surface isn't just your package.json. It's every tool, plugin, registry, MCP server, and peer agent your agent connects to during execution. And unlike your build pipeline, there's no lockfile for runtime tool discovery.
The MCP Supply Chain Crisis of 2026
Let me be blunt about the current state of affairs.
Between January and February 2026, security researchers filed - a systemic vulnerability baked into official MCP SDKs across Python, TypeScript, Java, and Rust. It exposed over 150 million downloads and up to 200,000 vulnerable instances to remote code execution.
Four named CVEs in the MCP layer alone during 2025: CVE-2025-6514 (mcp-remote RCE), CVE-2025-49596 (MCP Inspector), CVE-2025-54136 (Cursor), CVE-2025-54994 (create-mcp-server-stdio).
This isn't theoretical anymore. It's a full-blown crisis.
How Agentic Supply Chain Attacks Work
OWASP documents six primary attack vectors. Here are the ones hitting production right now.
1. Malicious MCP Server Impersonation
The Postmark case I mentioned above. An attacker publishes a package with a name that looks like a legitimate MCP server. Your agent (or your developer) installs it. It works... and also exfiltrates your data.
Real incident: The - a researcher showed that a malicious public tool could hide commands in its metadata, causing the assistant to exfiltrate private repo data without the user's knowledge.
3. Compromised Registries and Agent Cards
An attacker compromises an MCP registry server or agent-to-agent protocol registry. Now they can serve tampered manifests, plugins, or agent descriptors at scale. Every agent that trusts that registry gets poisoned.
Real incident: gives you a private registry that sits between your agents and public registries (npm, PyPI). It acts as a gateway with approval controls.
import boto3
codeartifact = boto3.client("codeartifact")
DOMAIN = "my-company"
# 1. Ingest repo. The ONLY repo allowed to talk to public npm.
# Agents never point at this one.
codeartifact.create_repository(
domain=DOMAIN,
repository="npm-ingest",
description="Staging. Pulls from public npm for review.",
)
codeartifact.associate_external_connection(
domain=DOMAIN,
repository="npm-ingest",
externalConnection="public:npmjs",
)
# 2. Agent repo. No external connection, no upstream to the internet.
# Nothing lands here unless we put it here.
codeartifact.create_repository(
domain=DOMAIN,
repository="agent-approved-tools",
description="Reviewed packages only. Agents pull from here.",
)
# 3. Approval IS the copy step. A human or a pipeline runs this after
# review. The agent cannot. And you promote the whole dependency
# tree, not just the top-level name.
def approve_package(name, version, fmt="npm", namespace=None):
kwargs = dict(
domain=DOMAIN,
sourceRepository="npm-ingest",
destinationRepository="agent-approved-tools",
format=fmt,
package=name,
versions=[version],
)
if namespace: # e.g. "modelcontextprotocol" for @modelcontextprotocol/...
kwargs["namespace"] = namespace
codeartifact.copy_package_versions(**kwargs)
Then point the agent at the approved repo, and only that repo:
aws codeartifact login --tool npm --domain my-company --repository agent-approved-tools
Now you have a gate, a real one. A package does not reach an agent until someone copied it across on purpose. Good. But do not oversell it to yourself, because there are three things this setup still will not do for you.
It is not a firewall. That login command sets a preference, not a prison. It edits a config file. If the agent's box can still route to the open internet, it can ignore every word of the above and pull straight from registry.npmjs.org. The egress lockdown is not the optional bonus round. It is the whole point. No egress control, no gateway. You just have a nicely organized repo the agent is free to walk past.
The gate is only as good as the review. copy_package_versions is where a human is supposed to think. If a tired someone rubber-stamps whatever showed up in the ingest repo, congratulations, you have built a very well-architected way to approve malware. The tooling does not do the vetting. You do.
Dependencies are the real bill. Copying some-tool copies some-tool. It does not copy the forty packages it drags in behind it. A hard gate means you promote the entire tree, and you vet the entire tree, and yes, that is more work than letting a pull-through cache grab it all on demand. That friction is exactly why so many people quietly leave the cache wide open. Now you know the trade you are making.
2. Amazon Inspector: Continuous Vulnerability Scanning
keeps traffic on the AWS network.
This matters because MCP servers often expose HTTP endpoints. If those endpoints are public, an attacker can MITM the connection or redirect to a lookalike server. PrivateLink eliminates that entire class of attack.
ec2 = boto3.client("ec2")
# Create a VPC endpoint for your internal MCP server
endpoint = ec2.create_vpc_endpoint(
VpcId="vpc-abc123",
ServiceName="com.amazonaws.vpce.us-east-1.vpce-svc-mcp-internal",
VpcEndpointType="Interface",
SubnetIds=["subnet-123"],
SecurityGroupIds=["sg-agent-tools"],
PrivateDnsEnabled=True
)
# Agent's MCP client connects to the private DNS name
# Traffic never leaves the AWS network
4. Lambda Layers: Controlled, Versioned Dependencies
Instead of letting each Lambda function manage its own dependencies (where drift happens), use lets you sign your Lambda deployment packages and layer versions. Configure Lambda to reject unsigned or untrusted code:
lambda_client = boto3.client("lambda")
# Create a code signing config
config = lambda_client.create_code_signing_config(
AllowedPublishers={
"SigningProfileVersionArns": [
"arn:aws:signer:us-east-1:123456789012:signing-profiles/AgentToolSigner"
]
},
CodeSigningPolicies={
"UntrustedArtifactOnDeployment": "Enforce" # REJECT unsigned code
}
)
# Attach to your agent's Lambda functions
lambda_client.put_function_code_signing_config(
CodeSigningConfigArn=config["CodeSigningConfig"]["CodeSigningConfigArn"],
FunctionName="agent-order-lookup"
)
You flipped it to Enforce. You have a real gate now. Lambda will refuse anything not signed by a publisher you trust. Good. But be precise about what that signature is actually swearing to, because it is easy to hear more than it says.
A signature proves origin, not innocence. All it says is "this is the exact artifact your pipeline built and signed." It does not say the artifact is safe. If a poisoned dependency slipped into your build, you will sign it, Lambda will happily accept it, and everyone will feel great about the malware that just shipped with a valid signature. The signing is only as honest as what you handed the signer. Sound familiar? It should.
It checks at the door, not in the room. Verification happens at deploy time. Once the function is live it runs on every invocation and nothing re-checks it. You have proven what you
shipped. You have not proven how it behaves at 3am.
And the part everyone skips: who can turn it off? This whole control exists to stop an attacker who already has credentials. So ask the obvious question. What stops that same attacker from detaching the code signing config, or flipping Enforce back to Warn, and then deploying whatever they please? Nothing in this snippet does. A lock is only a lock if the intruder cannot reach the off switch. Guard the Lambda config permissions, back them with an SCP, or you have built a very official looking door that opens from the inside.
6. ECR Image Scanning: Container-Level Verification
If your MCP servers run as containers, for deployment changes
If an attacker compromises a public MCP package, they hit the CodeArtifact gate. If they somehow get past that, Signer rejects the unsigned code. If they forge a signature, Inspector catches the vulnerability. If they somehow deploy clean-looking code, the Lambda has no internet access to exfiltrate data.
That's defense in depth applied to supply chain.
Key Takeaway
Your agent's supply chain is not your package.json. It's every tool, MCP server, registry, peer agent, and prompt template your agent touches at runtime. Treat each one as untrusted until verified, signed, scanned, and isolated.
Up Next
or Twitter, or drop them below. If you're running MCP servers in production, I'd genuinely love to hear how you're handling trust.
Onward!!
SOCIAL SHARE CARD GENERATOR