AI · Supply chain
AI supply chain security: four ways code now enters your estate without a vendor review
Model weights, agent skills, MCP servers and packages your coding assistant invented all execute in your environment, and none of them trigger a vendor assessment. The documented incidents, the measured scale, and the controls that close the gap before CPS 230 makes it an audit finding.
Your third-party risk process has one job: make sure nothing runs in your environment until somebody has looked at who wrote it. It works through a gate. A vendor is proposed, an assessment is raised, a contract is signed, a control owner is named, and the thing goes on a register.
Four categories of executable code now enter Australian organisations without ever touching that gate. Nobody raises a vendor assessment for a set of model weights downloaded from a public hub. Nobody contracts with the author of an agent skill installed from a marketplace. Nobody reviews the MCP server an engineer added to their agent config on a Tuesday afternoon. And nobody assesses a package that a coding assistant invented and then installed on its own.
All four execute code. All four run with the permissions of whatever loaded them. None of them is a vendor in any sense your procurement process would recognise. That is the whole problem, and it is why this is not simply a longer version of the third-party risk conversation you have already had.
The timing matters in Australia. From 1 July 2026, APRA’s CPS 230 applies in respect of all contracted service providers, and APRA’s April 2026 letter to industry named vendor concentration and inadequate change management as specific AI concerns. The awkward question an assessor will ask is not whether you manage your AI vendors. It is what you do about the code that arrived without ever becoming a vendor.
This post covers the four surfaces, the documented incidents on each, the measured scale, and a control set that fits an organisation without a dedicated AI platform team.
Surface one: model weights that execute on load
Start with the one most people assume is data. It is not.
Python’s pickle format is the dominant serialisation mechanism for machine learning model weights, and pickle executes arbitrary code at load time by design. Loading a model is not reading a file. It is running a program. Anything that can be expressed in Python can be embedded in a .pkl, .bin, .pt or .ckpt file, and it runs with the privileges of the process that loaded it, before your application does anything at all.
The scale is not hypothetical. Protect AI, which partnered with Hugging Face to scan the hub, reported that as of 1 April 2025 it had scanned 4.47 million model versions across 1.41 million repositories and identified 352,000 unsafe or suspicious issues across 51,700 models. Not all of those are malicious. The relevant point is that the population of models with executable content in them is large enough that “we only use popular models” is not a control.
Scanning also fails in ways worth understanding. In January 2025 ReversingLabs documented a technique it called nullifAI, found in two live models on the hub. The attacker compressed a PyTorch model with 7z rather than the default ZIP. That broke loading with the standard torch.load() function, which also broke Picklescan, the scanner Hugging Face used: the tool validates the pickle file first and errors out on malformed opcodes before completing its security scan. Deserialisation, however, executes opcodes sequentially. With the payload placed at the start of the stream, the malicious code ran before the corruption that stopped the scanner was ever reached. Hugging Face removed the models within 24 hours of being told, which is a good response to a detection gap that should not have existed.
The defensive position is simple to state and unpopular to implement.
Prefer safetensors and treat pickle-format weights as executables. The safetensors format exists precisely because it stores tensors without a code execution path. Where a model is only published in a pickle-based format, the file belongs in the same risk category as an unsigned binary from the internet, because that is what it is.
Pin models by revision hash, not by name. A model name on a public hub is mutable. org/model-name today and org/model-name next month can be different weights with different behaviour and different content. If you would not run pip install without a lockfile, do not load weights without a commit hash.
Load untrusted weights in an isolated environment first. No network egress, no credentials, no mounted secrets. If a model executes something on load, you want that to happen somewhere it cannot reach anything.
Surface two: agent skills from a marketplace
The second surface barely existed two years ago. Agent skill marketplaces let anyone publish a package of instructions and code that an agent will load and act on. Installation is usually one command, and the review that happens between “I found this skill” and “my agent is running it” is typically nothing.
Snyk published the first large audit of this ecosystem, examining 3,984 skills from ClawHub and skills.sh as of 5 February 2026. The ToxicSkills findings are worth quoting precisely, because the headline number and the serious number are different. Over a third of the corpus, 36.82 percent or 1,467 skills, had at least one security flaw, mostly hardcoded API keys and insecure credential handling. The sharper figure is that 13.4 percent, 534 skills, contained at least one critical-level issue including malware, prompt injection and exposed secrets. Snyk confirmed 76 genuinely malicious payloads through human review, built for credential theft, backdoor installation and data exfiltration, and reported that 8 of those remained publicly available at the time of publication.
Separately, Antiy CERT documented a coordinated campaign, ClawHavoc, that poisoned 1,184 skills. That was a distinct event that came after the Snyk audit, which tells you the ecosystem is being actively targeted rather than merely being sloppy.
Two things make skills worse than an ordinary dependency. First, a skill is partly instructions, so a malicious skill does not need to ship exploit code. It needs to ship persuasive text that redirects an agent already holding your credentials. Second, skills are installed by individual engineers into their own agent configuration, which means they are invisible to any inventory built around repositories and build pipelines. This is shadow AI with an execution path attached.
Surface three: MCP servers you never inventoried
Model Context Protocol servers are how agents reach tools and data. They typically run with high trust and broad permissions, and they are added to a developer’s configuration in seconds.
The first publicly documented malicious MCP server was postmark-mcp. A developer published an npm package that copied the legitimate Postmark library of the same name. Version 1.0.16, released on 17 September 2025, added a single line that BCC’d every email the server processed to an attacker-controlled address. Snyk’s write-up records the package being pulled from npm on 25 September. Koi Security, which found it, reported that the package attracted 1,643 downloads before removal.
Read the mechanism again, because it is the part that generalises. The exfiltration used the organisation’s own legitimate email infrastructure. The messages passed SPF and DKIM because they were genuinely sent by the sender’s own system. There was no anomalous outbound connection to detect, no malware on an endpoint, and no failed authentication. A one-line change in a package that nobody had assessed turned a trusted sending path into a data feed.
Client-side is no better. In July 2025 JFrog disclosed CVE-2025-6514, an OS command injection flaw in mcp-remote, the npm package that bridges clients to remote MCP servers. A malicious or hijacked server could return a crafted authorization_endpoint URL that mcp-remote passed unsanitised to the operating system, giving remote code execution on the client. Rated CVSS 9.6, affecting versions 0.0.5 through 0.1.15, fixed in 0.1.16, in a package with more than 437,000 downloads. Connecting an agent to an untrusted MCP server was enough to compromise the machine the agent ran on.
Keep the two problems separate, because they need different controls. What an MCP server is allowed to do once connected is an authorisation question, and we covered it in MCP and the new authorisation surface and OAuth scopes weren’t built for AI agents. What this post is about is narrower and more basic: who published this server, what changed in the version you are running, and how many are running across your estate right now. Most organisations cannot answer the third question at all, which means the first two cannot be asked.
Surface four: packages your assistant invented
The fourth surface is the strangest, because the attacker does not need to compromise anything. They only need to predict what your tools will make up.
Code-generating models fabricate package names. Joseph Spracklen and colleagues measured this systematically in “We Have a Package for You!”, presented at USENIX Security 2025. Across 576,000 code samples generated by 16 models in two languages using two prompt datasets, they found hallucinated packages in at least 5.2 percent of output from commercial models and 21.7 percent from open-source models, yielding 205,474 unique fabricated package names.
A fabricated name is harmless if it is random. It is a supply chain vector if it is predictable, and it is. The Cloud Security Alliance’s April 2026 research note on slopsquatting records that 61 percent of hallucinated names reappeared across multiple runs, and 43 percent appeared on every run of an identical prompt. An attacker does not have to guess. They can enumerate what the models invent, register those names, and wait.
The documented cases show three different routes to the same outcome:
unused-importson npm. A hallucinated alternative to the legitimateeslint-plugin-unused-imports, still recording roughly 233 weekly downloads despite being security-held.huggingface-cli. Accumulated more than 30,000 downloads in three months after appearing in Alibaba documentation without verification. The failure was not an agent. It was a human copying a name from a document.react-codeshift. Propagated into 237 repositories through AI-generated agent skills, with agents reportedly still attempting to install it daily.
In July 2026 researchers extended this into a directed attack. Reporting on HalluSquatting describes work by Aya Spira and colleagues in Ben Nassi’s group at Tel Aviv University, with Stav Cohen at Technion and Ron Bitton at Intuit, combining hallucinated names with prompt injection: register the name the assistant reliably invents, then put instructions in it that hijack the assistant into running attacker code. They reported consistency of up to 85 percent for repository requests and 100 percent for skill installs, and demonstrated it against Cursor, Windsurf, GitHub Copilot, Cline, Gemini CLI and the OpenClaw family.
The same logic works on domains. Palo Alto Networks Unit 42’s phantom squatting research, published 30 June 2026, ran 685,339 adversarial prompts against 913 global brands across two models, generating 2.1 million unique URLs. The unregistered results collapsed into roughly 250,000 registerable namespaces sitting available, with 13,229 URLs confirmed malicious and 41,313 rated high risk. Worth noting that the defences that write-up recommends are that vendor’s own products, so take the remediation section as marketing and the measurement as useful.
Why your existing controls miss all four
The reason these belong in one post is that they fail the same way. Each one bypasses a different assumption your current process depends on.
| Surface | What enters | Why the vendor gate misses it | Control that would have caught it |
|---|---|---|---|
| Model weights | Executable code in a pickle-format file | Treated as data, not software. No vendor, no contract | Format policy, revision pinning, isolated first load |
| Agent skills | Instructions plus code, installed per engineer | Installed outside repos and pipelines. Invisible to build-time scanning | Allowlist, inventory of installed skills per host |
| MCP servers | A privileged tool bridge, added to local config | Added by an individual in seconds. Never proposed as a vendor | Registry of approved servers, version pinning, egress control |
| Hallucinated packages | A dependency nobody chose | The name came from your own tooling, so it inherits false trust | Registry existence check, lockfiles, no autonomous install |
Three assumptions break across the board.
Your inventory is built around repositories. Skills and MCP servers live in developer configuration files and home directories. If your software bill of materials is generated from your build pipeline, it does not see them, and you will report a clean SBOM while an engineer’s agent runs a marketplace skill with your production credentials.
Your controls assume a human chose the dependency. When an agent resolves and installs a package autonomously, there is no moment where a person evaluates the name. The CI/CD controls you already have assume a pull request with an author. Agentic development removes the author.
Your detection assumes attacker infrastructure. The postmark case used your mail server. A poisoned model executes inside your own inference process. A malicious skill acts through an agent identity you provisioned. There is no beacon to a suspicious IP, which is precisely why monitoring agent activity as privileged activity matters more than adding another network rule.
The control set
Ten controls, ordered by what returns the most for the least effort. None of them requires a platform team.
1. Inventory first. You cannot govern what you have not enumerated. Find the model files, MCP configurations, installed skills and agent tool definitions across developer machines and build agents. This is the step most organisations skip and the one every other control depends on. A starting script is below.
2. Ban pickle-format weights by policy, with a documented exception path. Default to safetensors. Where a model only ships as pickle, it needs a named approver and an isolated load.
3. Pin everything by hash. Model revision hashes, package lockfiles with integrity hashes, MCP server versions. Mutable names are the common factor in three of the four surfaces.
4. Require a registry existence check before any package install. This single control defeats slopsquatting outright, because a hallucinated name does not exist until an attacker registers it. Verify the package existed before your assistant suggested it, and treat first-use of a package registered in the last 90 days as a review trigger.
5. Turn off autonomous install. Agents may propose dependencies. A person approves them. If your coding agent runs in an auto-accept mode with network access and package manager rights, you have delegated dependency selection to a system that measurably invents names.
6. Allowlist MCP servers and agent skills. A short approved list, a named owner for each entry, and a documented process to add one. Anything not on the list does not run. This is unpopular for about two weeks and then becomes unremarkable.
7. Deny network egress by default for agent and inference workloads. The postmark exfiltration worked through a legitimate channel, but most payloads in the Snyk corpus need to reach attacker infrastructure. Default-deny egress with an allowlist turns most of these from a breach into a blocked connection and an alert.
8. Scope credentials to the agent, never to the human. A skill or MCP server inherits whatever the loading process holds. If that is an engineer’s personal access token, the blast radius is the engineer. Distinct, least-privilege, short-lived credentials per agent make a compromised skill a contained event.
9. Extend the SBOM to AI artefacts. Models, skills, MCP servers and their versions belong in the same inventory as your libraries, with the same review cadence. If a CPS 230 assessor asks what runs in your environment, an SBOM that omits four categories of executable code is not an answer.
10. Diff dependency and configuration changes on agent hosts. The postmark backdoor was a one-line change in a point release. Version-to-version diffing of anything privileged catches that class of change, and nothing else will.
A starting inventory script
The first control is the hard one, so here is a way to begin. This walks a path and reports the AI supply chain artefacts that ordinary tooling misses. It has no dependencies and makes no network calls.
#!/usr/bin/env python3
"""Inventory AI supply chain artefacts that repo-based SBOMs miss."""
import json
import os
import sys
from pathlib import Path
# Pickle-based formats execute arbitrary code at load time.
EXECUTES_ON_LOAD = {".pkl", ".pickle", ".bin", ".pt", ".pth", ".ckpt", ".h5"}
SAFE_WEIGHTS = {".safetensors", ".gguf", ".onnx"}
MCP_CONFIGS = {
"mcp.json", ".mcp.json", "claude_desktop_config.json",
"mcp_settings.json", "mcp-config.json",
}
SKILL_MARKERS = {"SKILL.md", "skill.json", "skill.yaml"}
SKIP_DIRS = {".git", "node_modules", "venv", ".venv", "__pycache__", "dist"}
def scan(root: Path) -> dict:
found = {"executable_weights": [], "safe_weights": [],
"mcp_configs": [], "skills": []}
for dirpath, dirnames, filenames in os.walk(root, followlinks=False):
dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]
here = Path(dirpath)
for name in filenames:
path = here / name
suffix = path.suffix.lower()
if suffix in EXECUTES_ON_LOAD:
found["executable_weights"].append(str(path))
elif suffix in SAFE_WEIGHTS:
found["safe_weights"].append(str(path))
elif name in MCP_CONFIGS:
found["mcp_configs"].append(str(path))
elif name in SKILL_MARKERS:
found["skills"].append(str(path.parent))
return found
def servers_in(config_path: Path) -> list:
"""Pull declared server names out of an MCP config, tolerating drift."""
try:
data = json.loads(config_path.read_text(encoding="utf-8"))
except (OSError, ValueError):
return []
servers = data.get("mcpServers") or data.get("servers") or {}
return sorted(servers) if isinstance(servers, dict) else []
def main(argv):
root = Path(argv[1] if len(argv) > 1 else ".").expanduser().resolve()
found = scan(root)
print(f"AI supply chain inventory for {root}\n")
print(f" Weights that execute on load : {len(found['executable_weights'])}")
print(f" Weights in safe formats : {len(found['safe_weights'])}")
print(f" MCP configuration files : {len(found['mcp_configs'])}")
print(f" Agent skills : {len(found['skills'])}")
for path in found["executable_weights"]:
print(f"\n REVIEW {path}\n Pickle-based weights run code on load.")
for path in found["mcp_configs"]:
names = servers_in(Path(path))
listed = ", ".join(names) if names else "none declared"
print(f"\n REVIEW {path}\n Servers: {listed}")
for path in found["skills"]:
print(f"\n REVIEW {path}\n Skill runs with the agent's credentials.")
# Non-zero exit when anything needs a human, so CI can gate on it.
needs_review = (found["executable_weights"]
+ found["mcp_configs"] + found["skills"])
return 1 if needs_review else 0
if __name__ == "__main__":
sys.exit(main(sys.argv))
Run it against a developer laptop and a build agent rather than a repository. The point is to look where your existing tooling does not. The output is a starting list for control 6, not a verdict: an MCP config is not a finding, it is a thing somebody needs to have approved.
What this looks like to an Australian regulator
APRA’s position, set out in its 30 April 2026 letter to industry on artificial intelligence, is that AI is not subject to separate governance requirements. IT services that use AI must be managed under the existing standards, principally CPS 230 and CPS 234. The letter named vendor concentration and change management processes built for static systems as specific concerns, and set the expectation of continuous monitoring across the AI lifecycle rather than point-in-time assessment.
Apply that to the four surfaces and the exposure is uncomfortable. CPS 230 applies in respect of all contracted service providers from 1 July 2026. A malicious MCP server processing your customer email is performing a function that would plainly be in scope had anyone contracted for it. The fact that no contract exists is not a mitigating factor. It is the finding.
The questions to be ready for are concrete, and they are the same questions whether the assessor comes from APRA, an ISO 42001 auditor, or a client’s security team:
- What models run in your environment, in what format, pinned to what revision?
- Which MCP servers and agent skills are approved, and how do you know nothing else is running?
- When a dependency is introduced by an agent rather than a person, who approved it and where is that recorded?
- How would you detect a one-line change in a privileged package between point releases?
- If a model, skill or server were found to be malicious tomorrow, what did it have access to?
That last question is the one worth rehearsing, because it is the first one asked in an incident and it is answerable only if controls 8 and 1 are already in place. It is also the question that connects this work to board reporting: the answer is a blast radius, and a blast radius is something a board can actually govern.
A thirty-day starting position
For an organisation with no AI supply chain controls at all, in order.
| Week | Action | Output |
|---|---|---|
| 1 | Run the inventory across developer machines and build agents | A list of models, MCP servers and skills actually in use |
| 2 | Allowlist what is legitimately needed. Remove the rest | An approved list with a named owner per entry |
| 3 | Turn off autonomous package install. Require registry existence checks | Agents propose, humans approve |
| 4 | Pin models by hash, scope agent credentials, deny egress by default | Blast radius reduced to something you can describe |
Nothing in that sequence needs new tooling or budget approval. It needs someone to own it for a month, and it produces the evidence that every subsequent conversation depends on. If your organisation is deploying agents into production without this baseline, the platform decisions in digital employees on the platform are the natural next step.
Frequently asked questions
What is an AI supply chain attack? An attack that reaches your environment through the AI components you adopt rather than through your network perimeter. In practice that means four things: model weights that execute code when loaded, agent skills from marketplaces, MCP servers that connect agents to tools and data, and software packages introduced by AI coding assistants. Each executes with the permissions of whatever loaded it, and none of them typically passes through a vendor assessment.
Is slopsquatting a real attack or a theoretical one?
Documented and exploited. The underlying behaviour was measured across 576,000 generated code samples in the USENIX Security 2025 study, which found 205,474 unique fabricated package names and hallucination rates of 5.2 percent for commercial models and 21.7 percent for open-source ones. Real packages exploiting it have accumulated real downloads, including huggingface-cli at more than 30,000 downloads in three months and react-codeshift reaching 237 repositories. In 2026 researchers combined the technique with prompt injection and demonstrated it against six commercial coding assistants.
Are safetensors enough to make model loading safe? They remove the code execution path at load time, which is the single largest risk, and they should be your default. They do not tell you the model behaves as advertised, do not protect against a backdoored or poisoned model, and do not remove the need to pin revisions. Safe format, unknown behaviour.
We only use models and packages from well-known publishers. Is that sufficient? No, for two reasons. Names on public hubs and registries are mutable, so the artefact you audited is not necessarily the one you loaded next month. And the postmark case was a package that copied a legitimate publisher’s library name, which is exactly the trust that typosquatting and dependency confusion exploit. Reputation is a useful signal. Pinning is a control.
Does CPS 230 actually cover an MCP server nobody contracted for? CPS 230 is written around service providers, and APRA’s April 2026 letter is explicit that AI-enabled IT services fall under existing standards rather than a separate regime. The practical exposure is that an uncontracted component performing a function that supports a critical operation is harder to defend than a contracted one, not easier. The absence of a contract removes your assurance, not your obligation.
What is the single highest-value first step? The inventory. Every other control on the list depends on knowing what models, skills and MCP servers are actually running, and almost no organisation can answer that today. Start on developer machines and build agents rather than repositories, because that is where the gap is.
Work with Inline Code
Most AI supply chain exposure is not a sophisticated attack. It is a set of components that entered the environment without anyone deciding they should, running with credentials nobody scoped, in a register nobody keeps. That is a solvable problem, and it is cheaper to solve before an assessor or an incident finds it.
Inline Code is a fractional AI and information risk practice for Australian organisations, run by a certified offensive and defensive security practitioner. This work is squarely in scope: inventorying what actually runs, setting the allowlist and pinning policy, scoping agent credentials, and producing the evidence CPS 230 and CPS 234 will ask for.
- Start with an AI Governance Posture Assessment to find out what is running and what it can reach.
- Engage a Fractional AI and Information Risk Officer to own the program without the cost of a full-time hire.
- Or book a thirty-minute discovery call and we will tell you, plainly, whether you have a problem worth paying to fix.
Continue reading
Related pieces
Third-party risk
Third-party risk after the supply-chain attack era
Most third-party risk programs in mid-market financial services are questionnaire factories. They produce paperwork; they do not produce risk reduction. After several years of supply-chain incidents, the realistic position has changed. Here's what actually works.
26 August 2025
Platform engineering
Securing CI/CD pipelines without slowing engineering down
Pipeline security is the gap between policy and reality. Most regulated firms have written rules about code review and signed releases that the actual pipeline does not enforce, and the audit evidence is whatever the runner happened to print to stdout.
25 March 2026
AI · Security operations
The agentic SOC is real, and your logs are now prompts: how AI security monitoring actually works, and where it breaks
Microsoft, Google and CrowdStrike now ship autonomous agents that triage alerts in real time with no analyst in the loop. What agentic SOC tooling actually does, the research showing attackers can prompt-inject it through ordinary log fields, and the control set that holds under APRA scrutiny.
30 August 2026