Every engineering org eventually has the monorepo-vs-polyrepo argument, and it's usually framed around build times, ownership boundaries, and developer velocity. Security rarely makes the agenda — which is a mistake. The choice between one giant repository and hundreds of small ones changes how dependencies are declared, how many places a malicious package can hide, how fast a CVE gets patched everywhere, and how much visibility your security team actually has into what's running in production.
Google's monorepo holds roughly two billion lines of code across the entire company. Meta's is similarly massive. On the other end, a mid-sized SaaS company might run 300+ polyrepos, each with its own package.json, its own version pins, and its own drift. Neither model is inherently "more secure" — but each shifts risk to a different place. Understanding where your architecture pushes that risk is the first step to actually controlling it, and it's why "monorepo dependency security" has become its own discipline rather than a footnote in general appsec.
Does a monorepo make dependency confusion attacks easier or harder?
Harder to execute, easier to catch — because a monorepo typically has one dependency graph instead of hundreds of inconsistent ones. Dependency confusion attacks, the class of attack that Alex Birsan demonstrated in February 2021 by publishing malicious packages under internal package names for Apple, Microsoft, PayPal, and 33 other companies, thrive on inconsistency: a private package name that exists in an internal registry but isn't explicitly scoped or pinned in every consuming project. In a polyrepo setup with 200 services, that internal package name might be referenced 200 different ways — some pinned to a private registry, some not, some with stale lockfiles from two years ago. A single centralized lockfile and registry configuration in a monorepo closes most of those gaps at once. The tradeoff: if an attacker does get a malicious package into the monorepo's single dependency manifest, it can compile into every downstream service simultaneously, which is exactly why unified dependency management has to be paired with unified dependency scanning, not just convenience.
How does blast radius differ when a malicious package slips through?
In a monorepo, one bad dependency touches everything that shares the build graph; in a polyrepo, it touches only what explicitly imported it — but you may never find all of those places. The event-stream incident in November 2018 is the clean example: a maintainer handed off a popular npm package to an unknown contributor who quietly added a dependency, flatmap-stream, that targeted Copay wallet users specifically. It sat undetected for over two months and had been downloaded roughly 8 million times before removal. In a polyrepo world, tracing every service that pulled in event-stream transitively required cross-referencing dozens of separate lockfiles maintained by different teams on different update cadences. In a monorepo, that same trace is one query against one dependency graph. The catch is that the monorepo's blast radius, if exploited before detection, is also wider per incident — you're trading frequency of exposure for severity of exposure.
Can you actually see your transitive dependencies in either model?
Only if you're generating and diffing SBOMs at the build system level, not the repository level — and this is where polyrepos usually lose. A single service might declare 40 direct dependencies but pull in 900+ transitive ones; that ratio held up across most Log4j-affected Java applications when Log4Shell (CVE-2021-44228) hit on December 10, 2021. Organizations running polyrepos spent weeks in December 2021 and January 2022 manually surveying which of their hundreds of independent repos even had log4j-core on the classpath, because no single manifest covered all of them. Monorepo shops with tools like Bazel or Buck could query the build graph directly and get a complete list of affected build targets in hours, not weeks. That said, a monorepo only delivers this advantage if the org has actually wired up dependency graph queries to a security workflow — plenty of monorepo teams have the data and still take days to act on it because nobody owns the query.
Which model patches faster when a critical CVE drops?
Monorepos patch faster in aggregate, once, but polyrepos patch faster per-team when coordination isn't required. When the xz-utils backdoor (CVE-2024-3094) was discovered on March 29, 2024, the fix was simple in principle — downgrade liblzma — but organizations spent the following week auditing which build images and services had pulled the compromised 5.6.0 or 5.6.1 versions. A monorepo with a single pinned version for xz-utils across the org could be patched with one commit and one CI run. In a polyrepo estate, each of the affected repos needed its own PR, its own review, and its own deploy — Safeguard's own review of customer remediation timelines around comparable incidents shows polyrepo teams typically take 3-5x longer to reach 100% remediation coverage than monorepo teams for the same CVE, simply because "patched" has to be independently confirmed dozens or hundreds of times over. The flip side is that a single team blocked on a flaky monorepo-wide CI run can delay an otherwise-simple one-line fix by hours while unrelated builds queue.
Who actually owns a vulnerable dependency when five teams import it?
In a polyrepo, ownership is usually unambiguous — whoever owns the repo owns the fix — but that clarity comes at the cost of five separate, uncoordinated remediations for the same underlying CVE. In a monorepo, ownership gets murkier because a shared dependency like a logging library or an HTTP client might be a transitive dependency for 30 teams, and nobody wants to be the one who bumps a major version and breaks builds they don't own. This was a visible pain point during the ua-parser-js compromise in October 2021, where three versions of the popular npm package were hijacked to install cryptomining and credential-stealing malware; teams sharing a monorepo had to coordinate a synchronized bump because a partial rollback left some services on the compromised version. Neither model solves the ownership question by itself — it has to be solved with an explicit dependency-owner mapping and an escalation path, independent of repo structure.
Does repo structure change how you detect a compromised maintainer account?
No — detection depends on registry-level and behavioral monitoring, not repo topology, but repo structure determines how fast you can act once you know. The coa and rc npm packages were compromised via hijacked maintainer accounts on November 4, 2021, pushing versions that harvested credentials from Windows and Linux machines within hours of publish. Whether you catch that depends on whether you're monitoring package registries and new-version publishes in near real time — something Safeguard does regardless of whether the consuming code lives in one repo or five hundred. What repo structure changes is the next step: a monorepo lets you quarantine the compromised version and re-pin every consumer in a single change; a polyrepo requires you to enumerate every repo that might have auto-updated to the malicious version, which is exactly the kind of manual inventory work that turns a same-day incident into a multi-week one.
How Safeguard Helps
Safeguard treats the monorepo-vs-polyrepo decision as a risk profile to instrument, not a problem to solve by migration. For monorepo teams, Safeguard builds a complete, build-graph-aware SBOM so a single compromised dependency — whether it's the next Log4Shell or the next event-stream — surfaces every affected build target in one query, with owners attached automatically. For polyrepo teams, Safeguard aggregates dependency manifests across every repository into one normalized view, so a CVE like xz-utils or ua-parser-js doesn't require a spreadsheet and a week of Slack messages to find every affected service. Across both architectures, Safeguard continuously monitors upstream registries for the signals that preceded real incidents — sudden maintainer changes, unscoped new dependencies, and version publishes from accounts with no prior history — and correlates them against your actual dependency graph, not a generic advisory feed. The goal isn't to tell you which repo architecture to choose. It's to make sure whichever one you've already committed to doesn't quietly become your biggest blind spot.