Architecture

Monorepo Security: Dependency Management at Scale

Monorepos centralize code but create unique security challenges. Learn how to manage shared dependencies, enforce security policies, and maintain SBOMs across a monorepo architecture.

Nayan Dey
DevSecOps Architect
8 min read

Monorepos have become the default architecture at companies like Google, Meta, and Microsoft. Smaller organizations are following suit, consolidating dozens of repositories into a single source tree managed by tools like Nx, Turborepo, Bazel, or Lerna. The appeal is obvious: shared tooling, atomic commits across packages, simplified dependency management, and consistent build processes.

But monorepos introduce a distinct set of security challenges that most teams do not anticipate until they are deep into the migration. Dependency management, access control, SBOM generation, and vulnerability remediation all work differently when everything lives in one repository.

The Shared Dependency Problem

In a polyrepo world, each repository has its own package.json and package-lock.json. If Team A uses axios@0.21.1 and Team B uses axios@1.4.0, those are independent decisions with independent risk profiles.

In a monorepo with dependency hoisting (the default for Yarn Workspaces, pnpm workspaces, and npm workspaces), a single version of axios is installed at the root node_modules and shared across all packages. This has a significant security advantage: one version to patch, one version to audit, one version in your SBOM. When a CVE drops for axios, you update it once, and every package in the monorepo gets the fix.

The downside is blast radius. If a compromised dependency enters the monorepo, it is available to every package. In a polyrepo, the compromise is limited to whichever repositories explicitly depend on the malicious package.

Version Conflicts and Hoisting Failures

Hoisting does not always work cleanly. When Package A requires lodash@^4.0.0 and Package B requires lodash@^3.0.0, the package manager cannot hoist a single version. It installs one at the root and the other in the dependent package's local node_modules. These duplicated dependencies are easy to miss in security audits because they do not appear in the root lock file's top-level entries.

Tools like pnpm handle this more explicitly with its content-addressable storage and strict dependency resolution. Every package gets exactly the dependencies it declares, preventing phantom dependencies (packages that are accessible due to hoisting but not explicitly declared).

Access Control in a Single Repository

One of the most underappreciated security concerns with monorepos is access control. In a polyrepo world, repository permissions naturally enforce separation: the frontend team has write access to the frontend repo, the backend team to the backend repo. In a monorepo, everyone with write access can modify anything.

This is a real supply chain risk. A junior developer on the marketing website team can submit a PR that modifies the authentication library used by your payment processing service. Code review should catch this, but the attack surface is larger than it needs to be.

Mitigations

CODEOWNERS files. GitHub, GitLab, and Bitbucket support CODEOWNERS, which requires specific reviewers for changes to designated paths. Set up CODEOWNERS for security-critical packages, shared libraries, CI configurations, and dependency files (package.json, lock files).

Path-based CI restrictions. Configure your CI pipeline to run additional checks (security scanning, senior reviewer approval) when changes touch certain directories.

Git hooks with path awareness. Pre-commit hooks can enforce that certain files (e.g., .env, CI configs, dependency files) require additional validation before commits.

Separate signing requirements. Require commit signing for changes to security-critical paths, even if you do not require it repository-wide.

SBOM Generation for Monorepos

Generating a Software Bill of Materials for a monorepo is not as simple as running syft or cdxgen at the root. A monorepo might produce multiple deployable artifacts -- a web application, a CLI tool, three microservices, and a shared library. Each needs its own SBOM because each has a different dependency tree at runtime.

Per-Package SBOMs

The correct approach is generating one SBOM per deployable unit. For a Node.js monorepo:

  1. Identify which packages are "leaf" packages that get deployed (vs. internal shared libraries)
  2. For each deployable package, resolve its complete dependency tree (including internal packages and their transitive dependencies)
  3. Generate an SBOM that captures the full resolved tree

This is harder than it sounds because internal packages depend on each other, and those internal dependencies have their own external dependencies. A naive approach might miss transitive internal dependencies or double-count shared ones.

The Internal Dependency Challenge

Consider a monorepo with this structure:

packages/
  auth-service/     (deploys as a container)
  api-gateway/      (deploys as a container)
  shared-utils/     (internal library, never deployed alone)
  shared-crypto/    (internal library, never deployed alone)

auth-service depends on shared-utils and shared-crypto. api-gateway depends on shared-utils. Both shared-utils and shared-crypto have their own external dependencies.

The SBOM for auth-service must include: its own direct external dependencies, shared-utils' external dependencies, shared-crypto's external dependencies, and all transitive dependencies of all of the above. Simply running an SBOM tool at the auth-service directory might not capture the dependencies of the internal shared packages.

CI/CD Pipeline Security

Monorepo CI pipelines are complex. Tools like Nx and Turborepo provide "affected" analysis -- only building and testing packages that changed or whose dependencies changed. This optimization is essential for performance but has security implications.

Secret scoping. If your CI pipeline has access to deployment credentials for all packages, a malicious PR modifying any package could exfiltrate secrets. Use environment-scoped secrets so that the web application build cannot access the payment service's deployment keys.

Build cache poisoning. Turborepo and Nx support remote build caches. If the cache is compromised, an attacker could inject malicious build artifacts that bypass source-level code review. Ensure your remote cache uses content-addressable storage with integrity verification.

Dependency installation in CI. Use npm ci or pnpm install --frozen-lockfile to ensure the lock file is respected. In a monorepo, an accidental npm install can modify the root lock file, affecting every package in the repository.

Vulnerability Remediation at Scale

When a vulnerability is disclosed in a dependency used by your monorepo, the remediation workflow differs from polyrepo:

Advantage: Single update. In a hoisted monorepo, updating the vulnerable dependency in one place fixes it for all packages. No need to submit PRs to 15 different repositories.

Challenge: Testing blast radius. That single update needs to be tested against every package that uses the dependency. In a large monorepo, this might mean running hundreds of test suites. "Affected" analysis helps but is not perfect -- a dependency update might affect packages in ways the dependency graph does not capture (e.g., behavioral changes rather than API changes).

Challenge: Partial upgrades. Sometimes one package is ready for the patched version of a dependency, but another package has a compatibility issue. In a polyrepo, you upgrade the compatible one immediately and work on the incompatible one separately. In a hoisted monorepo, you might be stuck waiting for the slowest package.

Workaround: Per-package overrides. npm overrides, Yarn resolutions, and pnpm overrides allow per-package version pinning within a monorepo. Use these sparingly -- they add complexity and can create inconsistencies that are hard to track.

Security Scanning Configuration

Most SCA (Software Composition Analysis) tools were designed for single-project repositories. Running them on a monorepo requires configuration adjustments:

  • Point the scanner at each package's manifest and lock file, not just the root
  • Configure exclusion rules so that test fixtures, documentation examples, and development tools are not flagged as production dependencies
  • Set up separate security policies per package based on risk profile (e.g., stricter SLA for the payment service than the internal admin tool)
  • Ensure the scanner handles internal package references without flagging them as unknown dependencies

Dependency Update Strategies

Centralized Renovate/Dependabot. Configure your dependency update bot with monorepo-aware settings. Group updates for the same dependency across all packages into a single PR. Set different update schedules for security patches (immediate) vs. feature updates (weekly or monthly).

Internal dependency versioning. Decide whether internal packages use fixed versions ("shared-utils": "2.3.1") or workspace protocols ("shared-utils": "workspace:*"). Workspace protocols ensure internal packages always use the latest local version, which simplifies development but can mask version incompatibilities.

Lockfile maintenance. Schedule regular lock file regeneration (quarterly, with thorough testing) to pick up improvements in dependency resolution and remove stale entries.

How Safeguard.sh Helps

Safeguard.sh is built to handle the complexity of monorepo supply chain security. The platform can generate per-package SBOMs from a single monorepo scan, correctly resolving internal dependency graphs and mapping each deployable unit to its full dependency tree. Vulnerability alerts are contextualized per package, so you know whether a CVE in jsonwebtoken affects your auth service, your API gateway, or both. Safeguard.sh also provides a unified dashboard across all packages, letting you track remediation SLAs and dependency health metrics for the entire monorepo from one place.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.