Back to Blog

Pinning the MCP server version does not pin what runs

Mechanical keyboard on a dark desk with monitor stands, the workstation an MCP server is launched from

There is a sentence you hear a lot, and it is false: "I pinned the MCP server version, so I always run the same code". On 10 November Corelight published the lab work that takes it apart, and the finding is more uncomfortable than a plain "you pinned it wrong". They measured every MCP server launched the same way on one workstation, and the conclusion is written without anaesthetic: "It wasn't one server. Every MCP server which was launched in the same way on that workstation showed the same pattern, whether it was pinned to a release tag, an exact PyPI version, or an exact Git commit". A community server pinned to its release tag: 19 builds over five weeks, 15 distinct environments, and 23 of 69 packages changed version. Another on that same workstation, pinned to a commit: 19 builds, 17 environments.

We are writing this with the caveat up front: we use agents and MCP servers ourselves, and we will carry on using them. This is not a "AI is dangerous, do not touch it" piece. It is a piece about something far more boring and far more serious: an MCP server is a process launched on somebody's workstation, with that somebody's credentials, and its code can change without anyone editing a single line of configuration. In any other context that would have a name and a procedure attached. Here it does not, yet.

The myth: "I put a version on it"

The mechanics are simple to explain, which is what makes them sting. MCP servers are nearly always launched with a one-step runner: npx in the Node world, uvx in the Python world. You write something like uvx --from git+https://github.com/<owner>/<server>@<tag> <server> into your client config and feel fine about it, because there is a tag in there. What that tag pins is the top-level package. Its dependencies — and their dependencies — are resolved against the public registry again at launch time. Corelight puts it in a line worth reading twice: "pinning a package's version doesn't necessarily pin the packages it depends on".

And it is worth saying this was not maintainer sloppiness, because that is the part that really disarms you. The community server's author had done exactly the right thing: pinned all seven direct dependencies exactly and published a hash-pinned lock file. What happened is that the launcher installed from the package's declared dependencies and not from the lock file, so the roughly 60 packages underneath were resolved fresh from PyPI on every start. On the other server, the commit-pinned one, the drift went so far that the MCP framework it was built on was running two major versions ahead of the version in the project's own lock file. You pinned the part you can see. The part that runs was on its own.

The numbers, in one table

Corelight measurement (10-11-2026) Result
Community server pinned to its release tag, 19 builds over five weeks 15 distinct environments
Another server on the same workstation, pinned to a commit, 19 builds 17 distinct environments
Packages that changed version underneath the community server that looked pinned 23 / 69
MCP servers reviewed (two vendor catalogues + their lab) whose recommended install resolves from a public registry 23 / 35
Server starts on one administrator's workstation, in one week 162
Starts of servers with a frozen install, over two weeks 457, identical package list

The last row is the good news, and it deserves saying: freezing works. 457 starts, same package list, no surprises. This is not an unsolvable problem; it is a problem with a fix almost nobody applies, because the documentation you copy and paste from does not show it.

Not just a lab: it is what is published out there

A lab is a lab. What turns this into an industry problem is a census dated 13 October 2026 covering 2,999 public GitHub repositories that ship agent skills, plugin manifests or MCP server configuration. Of those, 346 configure a local MCP server. And of those 346, 159 (46.0%) start at least one with no pinned version: 100 (28.9%) launch an npm package via npx with no exact version, 63 (18.2%) ask for latest outright, and 38 (11.0%) run a Python package with no version. As a bonus, 2,459 of the 2,999 (82.0%) have no SECURITY.md: there is nowhere to report anything you find.

And here is the part we find most telling, because it is nobody's fault in particular and that is exactly why nobody fixes it. Since 13 October 2026 there has been an open issue in the MCP specification's own repository pointing out that the official quickstart contradicts the official security guide: the launch examples use npx -y with no version, while the Local Server Security guide says, verbatim, "Install a specific version rather than latest". As of today the issue is still open, with agreement among participants on what needs changing and no merged change. The 18.2% of repositories asking for latest did not invent that pattern: they copied it from where you are supposed to copy it.

The drift is not the problem. The permission is.

An environment changing from one day to the next is, on its own, a reproducibility nuisance. What turns it into a security matter is where that process lives. When an MCP server starts, it loads its dependencies into the same process, and that process carries whatever the user who launched it carries: API tokens, user files, network paths. Corelight's sentence is dry and exact: "An MCP server acts for a user, so it's often set up with that user's credentials and, as this workstation showed, an administrator's credentials reach a lot". And the reach is not hypothetical: one of the servers they measured was a security vendor's official MCP server, added later, with broad read and write access to that vendor's platform, and it declared its direct dependencies with no upper version limit.

Add the window. The same article recalls that in one wave of the Shai-Hulud campaign that reached PyPI in June 2026 — "including MCP-themed packages", the article specifies — six malicious releases were published in under a minute and quarantined within thirty minutes. Half an hour. If your workstation resolves fresh dependencies 162 times a week, your exposure is not measured in patching days: it is measured by whether you happened to launch inside that half hour. We have told this story from another angle before, when a 10.0 carried a product's name and the bug belonged to its interpreter: what breaks you is almost never the package you chose, but the one that came along behind it.

And when one of these servers has a hole in it

There is a case with a date and a number on it, and it is worth telling in full because it teaches something else. On 5 November 2026, CVE-2026-123022 was published against AgentScope: OS command injection in the subprocess.Popen call in src/agentscope/mcp/_config.py, in the MCP Workspace API component. The CVE record says "up to 2.0.11" and scores it 7.3 (CVSS 3.1) and 6.9 (CVSS 4.0). The original reporter's advisory, published on 14 October 2026, covers versions ≤ 2.0.4 and scores it 9.8. Two readings of the same flaw, nearly three points apart.

The score gap does not interest us. What interests us is why the advisory calls it "unauthenticated", and it is written into the code itself: the /workspace/mcp endpoint only checked that the X-User-ID header was non-empty, with a comment that confesses as much — "Temporary header-based identity; will be replaced by JWT auth". That is: you send a request with a header you make up, put /bin/sh in the command field of an MCP configuration, and the server spawns the process. And here is where precision matters, because it is easy to file this as closed and it is not: pull request 3448, opened on 23 October 2026, adds an optional allowlist of stdio commands that rejects anything unmatched with a 403 before spawning the process. It is still open, it is opt-in, and its own author marks it as partial — it references the issue rather than closing it — because it does not touch the header-based authentication, which is handled elsewhere. The researcher's advisory, for its part, still records "Patched versions: None". The lesson is not "AgentScope is bad": the lesson is that the piece which spawns processes sat behind an authentication its own author had labelled provisional, and that this lived inside the surface you are adding to your network.

What we do, in order

None of this is exotic. It is the same work we do with anything else that runs on a network we manage, applied to a new piece that is not yet in almost anybody's procedures.

  • Inventory first, always. Which MCP servers run, on which workstations, launched by whom, carrying which credentials. If the answer is "I do not know", that is this week's work, not next week's.
  • Frozen install by default. A hash-pinned lock file, and the launcher forced to use it; npm ci instead of npx -y; in containers, image pinned by digest, not by tag. That is the 457-starts-identical-list row.
  • Its own identity for the agent. A separate account, permissions scoped to what it needs, with an expiry. The agent does not work with the administrator's credentials because that is convenient; it works with its own, which reach less far.
  • Read-only where you can. Most MCP servers people use daily read far more than they write. If the server supports a read-only mode, turn it on.
  • Watch it on the network, which is where it shows. The article itself gives the detection hint, and it is a good one: bursts of connections to GitHub, PyPI and npm from workstations, very short TLS sessions (0.1 to 1.5 seconds), initiated by the assistant rather than by a scheduled service. That is visible in the DNS and TLS logs you already have.

And the point that is hardest to sell, because it is the one that forces a habit to change: assume what is inside the process has already been read. It is exactly the reasoning we applied when we wrote that you should treat every environment variable as decrypted on a platform that runs your code. An MCP server is a platform running third-party code on your people's workstations. The rules do not change because the component is new and has good press.

When none of this is needed

Because if we do not say it, this reads as fear-selling. If the MCP server runs on a personal laptop, against a toy repository, with no company credentials and no route into any corporate network, freezing the install is ceremony. Let it run on latest and update itself; it is more convenient and you are risking nothing that matters. The line between the two cases is not company size or enthusiasm for AI: it is whether that process holds credentials and has reach. The moment it does — an admin account, a cloud token, access to a production repository — it joins the same league as the rest of your privileged software, and the same rules apply.

The uncomfortable question

If someone on your team asked to install on their machine a program that (a) updates itself from the internet without warning, (b) runs with their administrator credentials and (c) can spawn processes, you would have a conversation. You might well end up saying yes, but there would be a conversation, and it would be written down. That is exactly what is already installed, and in most places it arrived without a conversation because it came with the word "agent" in front of it. That the answer is still yes seems reasonable to us. That there was no conversation does not.

We keep that conversation in writing when we set up automation and AI inside a company: which agent, which server, which credential, what reach, and who reviews it. And the principle underneath is the usual one in Zero Trust: the agent is not a tool belonging to the administrator, it is one more identity, and as such it gets its own account, its least privilege and its logging. We sell neither MCP servers nor models; we build the perimeter around them.

Sources (checked on 11-11-2026): the environment-drift figures (19 builds / 15 and 17 environments, 23 of 69 packages, 23 of 35 servers, 162 and 457 starts), the quotes, the Shai-Hulud wave and the network-detection guidance come from "The Supply-Chain Risk Hiding in Your MCP Servers", by James Lagermann (Corelight), published 10-11-2026. The census of 2,999 public repositories — 346 with a local MCP server, 159 with no pinned version (46.0%), 63 asking for latest (18.2%), 2,459 with no SECURITY.md — comes from the independent agent-skills-state-2026 study, snapshot of 13-10-2026, with method and data published under CC BY 4.0. The contradiction between the quickstart and the Local Server Security guide is in issue 3431 of the MCP specification repository, opened 13-10-2026 and with no merged change as of today. Provenance note: the census and issue 3431 come from the same person, so they count as one source, not as two that corroborate each other. The AgentScope data come from the CVE-2026-123022 record (published 05-11-2026, "up to 2.0.11", 7.3 under CVSS 3.1 and 6.9 under CVSS 4.0) and from the researcher's original advisory of 14-10-2026 (≤ 2.0.4, 9.8, X-User-ID header, "Patched versions: None"); pull request 3448 (23-10-2026) is still open and partial. Where two sources disagree we say so, instead of picking the number that reads best. Cover photo by Shixart1985, CC BY 2.0, via Wikimedia Commons.

Do you know which agents run on your machines, and with what permissions?

We build the inventory, freeze the installs, give the agent its own identity with least privilege, and leave the watching in place on DNS and TLS. It comes out as a table with names and credentials, not as a slide. That is the work of automation and AI and Zero Trust.

Talk to everyWAN
Tags: IA Cybersecurity Secrets DevOps Vulnerabilities

Subscribe to our newsletter

To receive IT stories, everyWAN news and exclusive subscriber offers, sign up to our mailing list

Minorisa de Sistemas Informaticos y Gestión S.L. © 2026
everyWAN
everyWAN