The Version Number Didn’t Change; the Code Did: Corelight on MCP Servers That Re-Resolve Dependencies Every Few Days

On 10 November 2026 Corelight’s James Lagermann (Director of Technical Alliances) published “The Supply-Chain Risk Hiding in Your MCP Servers” — a lab study that starts from an uncomfortable observation and gets worse from there. Mapping the blast radius of a hypothetical compromise with an AI agent, Lagermann’s team found other AI agents inside the blast radius: on an administrator’s workstation, AI coding assistants were running MCP servers holding privileged credentials for security tools and administrative resources. Nothing was compromised. But asking where that code comes from and how it updates produced the post’s central finding: pinning a package’s version does not pin the packages it depends on — and the launchers the ecosystem recommends re-resolve those dependencies from public registries every few days, silently, under credentials that can reach the management plane.

Same commit, different dependencies

The workstation’s assistants launched MCP servers through the one-step runner uvx — a single config line that downloads a Python package and runs it. One community network-gateway server was pinned to a specific release tag, i.e. one commit; others named exact PyPI versions or exact commits. The community author had done everything right: seven direct dependencies pinned exactly, plus a hash-pinned lockfile. It didn’t matter, because the launcher installs from the package’s declared dependencies, not the lockfile — so roughly 60 transitive packages resolved fresh from PyPI within whatever ranges the tree allowed. Over five weeks the launcher’s cache accumulated 19 builds of the same release and commit producing 15 distinct environments, with 23 of 69 packages changing versions underneath — including the cryptography library, the JWT library (four versions), and the MCP SDK itself. The assistant started the server 162 times in one week; each launch checked the Git tag (~24 KB), and whenever PyPI showed newer in-range releases, the environment rebuilt — at one point pulling in two dependencies released the previous day, unasked. A commit-pinned server fared no better: 19 builds, 17 distinct environments, its MCP framework running two major versions ahead of the project’s own lockfile. And it isn’t only Python: several vendors’ hosted MCP servers are reached through a local proxy started per official instructions as npx <proxy>@latest, re-asking npm for the newest version every launch — one proxy shipped six versions in a month, four within 14 hours — and one vendor’s setup additionally disabled TLS certificate verification for the whole proxy process. Across 35 servers reviewed from two vendor catalogs plus the lab, 23 had a recommended install that resolves dependencies fresh from a public registry at launch, install, or image build. One security vendor’s official server, granted broad read-write platform access, declared direct dependencies with no upper version bound at all.

Why MCP servers are the worst place for this behavior

Corelight is careful to say none of this is a flaw in MCP — every case was a choice in setup instructions or configuration. But two properties of MCP servers make the pattern dangerous. First, every dependency runs with the server’s access: dependencies load into the same process, so code in any of ~70 packages — including ones nobody chose — runs with the server’s API tokens, the user’s files, and the workstation’s network paths, and some known payload styles execute at import time without being called. Second, MCP servers start automatically: the admin writes one config line, and the assistant relaunches the server every session, so the launcher runs far more often than anyone realizes and nobody ever types an install command. Combine that with attackers’ demonstrated appetite — xz Utils (CVE-2024-3094), the Ultralytics December 2024 hijacked releases, Nx “s1ngularity” (August 2025), and Shai-Hulud (September 2025–June 2026) reaching 187 packages with a June 2026 PyPI variant hiding in MCP-themed packages, six malicious releases in under a minute — plus the observation that malicious windows last minutes to hours, and a launcher rebuilding every couple of days gets many chances to land inside one. Corelight’s endpoint telemetry closed the loop: the workstation’s servers made 1,600+ connections in a week to 11 management systems, including the firewall, the SIEM, and sensor management — code that can change without review, holding credentials to the management plane.

Finding it and freezing it

The detection half of the post is a genuinely reusable hunt: Corelight DNS logs for registry domains over seven days (excluding resolver-forwarded queries), SSL logs to catch hosts whose DNS never crosses the wire, rhythm analysis (bursts of short sessions per launch vs. human browsing), then EDR attribution by source port through NAT to find the process chain — with honest caveats throughout (ECH hides server names, JA3 failed because the client randomized handshakes, Windows DNS events attribute to the system service). The fix is to freeze the environment once and stop rebuilding at launch: pin to a commit, generate a platform lockfile constrained to the author’s versions, install into a dedicated environment with hash checking, launch the binary directly, and kill startup update checks (the frozen server still phoned PyPI once at startup — an MCP-framework self-update check with a 12-hour cache — until a documented setting silenced it). Verification was thorough: five servers frozen, 457 starts over two weeks, byte-identical package lists, handshakes and tools intact, startup down from 4–11s to ~2s, and silence on the wire. But Corelight refuses the victory lap: freezing swaps drift for aging — 40 packages were older than the day’s fresh resolve, including security-sensitive ones — so a named owner must re-lock on a schedule and vuln-check the pins, and credentials must be scoped down regardless (read-only where supported, expiring tokens, per-server accounts). The post closes with maintainer guidance worth quoting in spirit: publish per-platform hash-pinned requirements with each release, make the frozen install the default example, pin to commits or digests, bound your ranges, document startup network behavior and least access — and never tell users to disable TLS verification.

What this means next to everything else we track

This is the execution half of a supply-chain story this site has covered from the manifest half. Our unpinned-config census found two-thirds of public MCP servers running unpinned; Corelight shows that even the pinned third can drift underneath its pins. It is the runtime twin of MCPhantom’s training-memory poisoning — where the poison arrives via what the model learned — except here the poison arrives via what the launcher fetched this morning. The Shai-Hulud thread runs through both: the worm’s MCP-themed PyPI wave (see the StrUtil/Core RubyGems incident and the TensorLake valid-provenance case) already demonstrated that attackers aim at AI tooling supply chains specifically. And the credential posture — management-plane tokens sitting in the environment of auto-updating code — is the same pattern behind the reference-servers CVE batch and the serverless traversal flaws: the server’s reach keeps exceeding its review.

What to actually do

  1. Freeze MCP server environments; don’t just pin the top-level package. Generate a hash-pinned lockfile for your platform, install into a dedicated environment with hash checking enforced, and launch the installed server directly instead of through uvx/npx one-step runners.
  2. Run Corelight’s hunt in your own estate. Seven days of DNS+SSL logs for registry domains, burst-rhythm analysis, EDR process attribution — and rank by credential reach, not volume. The dev server pulling the most code was fine; the admin workstation pulling PyPI through an assistant was the finding.
  3. Assign an owner and a re-lock schedule to every frozen environment. Frozen code ages; 40 stale packages on day one proves it. Re-lock on a cadence, review the diff, and scan pins for known vulnerabilities — scheduled updates you review beat silent drift you don’t.
  4. Scope each server’s credentials to its tools. Per-server accounts or API clients, read-only where supported, limited to the systems actually used, with expiring tokens. A frozen server with full-admin gateway credentials is still one bug away from the management plane.
  5. If you maintain a server, make the frozen path the default. Ship per-platform hash-pinned requirements, show the dedicated-environment install first, pin examples to commits or digests, bound dependency ranges, document startup network calls and how to disable them — and pin any local proxy version instead of @latest.

Our verification was read-only. We read the full Corelight blog post first-hand (James Lagermann, 10 November 2026: uvx launcher behavior, 19-builds/15-environments and 23-of-69 figures, 162 weekly starts, cryptography/JWT/SDK drift, commit-pin 19/17 case, no-upper-bound vendor server, npx @latest proxies and TLS-verification-disabled setup, JFrog CVSS 9.6 proxy reference, 35-server/23-affected survey, xz/Ultralytics/Nx/Shai-Hulud precedent chain with CVE-2024-3094, DNS+SSL+EDR hunt methodology with ECH/JA3/DNS-attribution caveats, 1,600 connections to 11 management systems, five-server freeze with 457 starts verified, 40-stale-package aging caveat, maintainer guidance, reader hunt prompt). GBHackers’s and CyberPress’s 11 November 2026 summaries are carried as reported. We installed no server, ran no launcher, froze nothing, and contacted no vendor. Brave Search was used for discovery only.

Sources: