Moq bills itself as "the most popular and friendly mocking library for .NET," and by August 2023 it had earned that reputation the hard way: 475 million downloads and 5,300 GitHub stars, embedded in test suites across the .NET ecosystem including Microsoft PowerToys and Jellyfin. Then, on August 8, 2023, version 4.20.0 shipped with new behavior nobody had asked for. At build time, it ran a git config command to read the developer's configured email address, hashed it with SHA-256, and sent it as part of an HTTP HEAD request to Azure blob storage — routed through a third-party package called Devlooped.SponsorLink that was meant to detect whether a developer had installed the GitHub Sponsors app. The release lasted under 24 hours before the community forced its removal, but in that window it recorded 10,356 installs. For scale, the prior version, 4.18.4, had logged 6,765,006 downloads in six weeks alone — this is a library sitting in an enormous number of build pipelines. The maintainer's own SponsorLink documentation from January 2023 suggests the underlying experiment had been quietly running across multiple projects for months before anyone noticed. This is the story of how it was caught, what it actually did, and what it means for vetting dependencies you never directly chose.
What exactly did Moq 4.20.0 do?
At build time — not runtime, not install time, but every time a developer or CI system compiled a project referencing Moq 4.20.0 — the package's SponsorLink-derived logic shelled out to git config to read the local git profile's configured email address. That email was hashed with SHA-256 and embedded into the URL path of an HTTP HEAD request sent to Azure blob storage. No payload body, no plaintext email over the wire — just a hashed identifier baked into a request destined for infrastructure the developer never explicitly agreed to talk to. The stated purpose, per SponsorLink's own documentation, was to detect whether the developer had installed GitHub's Sponsors app, presumably to nudge or gate sponsorship prompts. Whatever the intent, the mechanism amounted to silent telemetry collection triggered by compiling code, with no opt-in, no disclosure in Moq's own release notes, and no way for a downstream consumer to know it was happening short of watching outbound network traffic during a dotnet build.
How was it discovered, and how fast did the fix ship?
The behavior surfaced through community scrutiny in GitHub issue #1372 on the Moq repository, where developers who'd noticed the unexpected git config invocation and outbound network call during builds started asking what SponsorLink was actually doing. The discussion moved quickly: PR #1375 was merged to strip the offending behavior, and a remediated release, 4.20.2, followed. Snyk's own writeup on the incident, published shortly after, noted that full confirmation of complete remediation was still pending at the time — a reminder that "the fix shipped" and "the fix is verified clean" are two different claims, especially for a change buried in a build-time hook rather than an obviously reviewable runtime code path. The entire arc — introduction, discovery, and yanked release — played out in under a day, which is both reassuring (open source self-corrected fast) and alarming (10,356 installs still happened before anyone could react).
Why did a mocking library need a telemetry dependency at all?
It didn't — and that's the core lesson. Moq is a unit-testing tool; nothing about mocking interfaces in a test suite requires reading a developer's git email or talking to Azure blob storage. The telemetry arrived because Moq took a dependency on Devlooped.SponsorLink, a separate package built by the same maintainer to serve an unrelated business goal (detecting GitHub Sponsors installs). Nobody consuming Moq chose to install SponsorLink; it arrived transitively, bundled in as an implementation detail of a library people trusted for an entirely different purpose. This is precisely the failure mode that pure "scan your direct dependencies" tooling misses: moq shows up clean in a top-level manifest review, because the concerning behavior lives one hop down, in a package whose name most developers had never heard of and had no reason to audit independently.
What did this reveal about vetting transitive dependencies?
It revealed that trust in a popular package doesn't transfer to whatever that package silently pulls in. Standard dependency review workflows — reading changelogs, checking the top-level package's reputation, glancing at download counts — all would have passed Moq 4.20.0 with flying colors, since 475 million cumulative downloads and a maintained GitHub repo are exactly the signals teams are taught to trust. The actual red flag was behavioral: a build-time process making an unexpected outbound network call and invoking git config for data it had no functional need for. That's not a signature any CVE database would have pattern-matched on the day it shipped, because it wasn't a known-bad indicator yet — it was a new behavior in a trusted package. Catching it requires either watching for behavior divergence between consecutive versions of a dependency, or having deep enough visibility into the dependency graph to notice when a widely-used package quietly adds a network-and-credential-adjacent transitive dependency it didn't have before.
How does this connect to how Safeguard approaches the same class of problem?
Safeguard's deep dependency scanning resolves transitive graphs to depth 100 — well past the roughly 60-level ceiling common in comparable SCA tooling — specifically because incidents like this one live in the packages nobody thought to check, not the one named in the manifest. A scan that stopped at Moq itself would have missed SponsorLink entirely; a scan that walks the full resolved graph surfaces it as a distinct node with its own provenance. Separately, Safeguard's Eagle classification model scores artifacts against indicator classes that include install-script and build-time behavior, egress patterns to unfamiliar hosts, and credential-harvesting signals such as reads from developer-identity sources — the same category of behavior SponsorLink exhibited by shelling out to git config and beaconing outward. Neither capability requires having seen this specific incident before; both are built to flag the behavioral pattern — unexpected build-time network egress paired with local-identity access — that made SponsorLink worth investigating in the first place, regardless of which ecosystem or which maintainer's package it's hiding inside.