DevSecOps

Turborepo Monorepo Supply Chain Security

Turborepo makes large JavaScript monorepos fast, and speed changes how teams think about dependencies. The supply chain implications are subtle enough that a fast-moving team can be in trouble before anyone notices.

Nayan Dey
Senior Security Engineer
8 min read

A platform team at a 900-engineer startup showed me their Turborepo in November 2024. Forty-seven workspaces. Three Next.js apps, two Remix apps, a React Native shell, a Vite-based admin dashboard, twelve TypeScript libraries, and a long tail of internal tools. Their pnpm-lock.yaml was 47,000 lines. Their turbo build ran in ninety seconds thanks to remote caching. And their last dependency audit had been eight months earlier, before the monorepo had even been consolidated.

Turborepo changes the math of monorepo management. The speed wins are real — incremental builds, task pipelining, remote cache sharing across CI and developer machines. The side effect is that teams add dependencies faster, update them less consistently, and end up with a supply chain that is both huge and inconsistently patched. This piece covers what to actually do about that.

The shared-dependency reality

In a Turborepo-managed monorepo using pnpm (or Yarn Berry, or npm workspaces), many dependencies are hoisted and shared across workspaces. That is efficient for disk usage and install time. It is also how a single vulnerability in a shared transitive dependency touches every workspace at once.

The complication is that different workspaces often target different runtimes — a Next.js web app, a React Native mobile app, and a Node.js service have different security-relevant contexts for the same package. A ReDoS vulnerability in a shared regex library might be critical for the Node.js service (where it processes user input) and irrelevant for the mobile app (where it runs on the user's device). Your SBOM needs workspace-level resolution, not just monorepo-level.

I recommend generating per-workspace SBOMs in addition to a monorepo-level one. Tools like cyclonedx-npm and Syft support this. The extra detail is what lets you triage CVEs based on where the code actually runs.

Remote caching and the trust boundary

Turborepo's remote cache is a productivity feature with security implications. When a task output is cached remotely, other machines — CI runners, other developers — can skip the work and pull the output directly. The cached artifact is trusted to be the same as if the task had run locally.

The trust model depends on the cache integrity. If an attacker can write to the cache — by compromising a developer's cache token, for instance — they can plant malicious artifacts that downstream consumers will treat as legitimate. Vercel's hosted remote cache has its own security posture; self-hosted remote caches (via turborepo-remote-cache or a custom implementation) have whatever security you configure.

The controls worth mandating: write access to the cache should require short-lived tokens, not long-lived API keys; cache hash inputs should include all relevant environment variables so a build with different env produces a different hash; and the cache server should log every write with attribution. I have seen teams skip the logging because it seemed unnecessary — until they needed to investigate a suspicious artifact and had no way to tell who had produced it.

Workspace protocol and the internal package graph

pnpm's workspace: protocol (and Yarn Berry's equivalent) lets workspaces depend on each other through a semver-like mechanism without needing real versions or the npm registry. This is great ergonomics. It is also a risk if your workspace graph ever needs to be published.

Internal libraries that are never published stay fine. Libraries that become public — either because you open-source them or because a separate team wants to consume them outside the monorepo — need to have their workspace: dependencies flattened to real npm versions at publish time. Turborepo's --dry-run and turbo prune command help, but I have seen teams accidentally publish libraries with unresolvable workspace: references, which is a fast path to a broken install for every downstream consumer.

Cross-workspace license contamination

A subtle monorepo risk: a workspace with a permissive license (say, your public SDK under MIT) can end up depending transitively on a copyleft package pulled in through a sibling workspace's hoisted dependency tree. When you ship the public SDK, you might be redistributing code you do not have the right to redistribute under MIT.

License auditing in a monorepo has to be workspace-aware. A global license report is not enough; you need to know which licenses are in the effective dependency closure of each publishable workspace. This is the kind of thing a legal team asks about at exactly the wrong moment — the week before a major customer deal.

Node version drift across workspaces

Turborepo lets you specify Node versions in package.json engines fields and in .nvmrc files. I have audited monorepos where different workspaces declared Node 16 and Node 20 compatibility with no consistency. When the CI runner used Node 20, the Node 16 assumptions in some workspaces were technically unmet, and certain native dependencies would compile against Node 20 headers while the code expected Node 16 behaviors.

The specific security risk is that old Node versions carry old OpenSSL versions, old HTTP parsers, and old V8 engines with their own CVEs. Node 16 reached EOL in September 2023; Node 18 in April 2025. If your monorepo has workspaces claiming compatibility with EOL versions, consolidate to a supported minimum.

CI caching and the secret leak surface

Turborepo caches task outputs. If a task produces output that includes secrets — a build log with environment variables, a test artifact with connection strings — that output is now in the cache. A compromised cache reveals the secret.

Sanitize task outputs. Never log environment variables in build scripts. If you must log for debugging, redact. And never include .env files in cached paths. I have found monorepos where turbo build cached an output directory that contained a copy of the built app, which in turn embedded environment variables including a production database URL. The URL was rotated promptly. It should not have been there in the first place.

The pre-install script problem

pnpm and Yarn both execute lifecycle scripts from dependencies by default (though pnpm 7+ restricts them slightly). In a monorepo with 1,800 transitive packages, the number of lifecycle scripts that run on pnpm install is not small. A malicious package published to npm and pulled in through a transitive dependency executes arbitrary code on every developer and CI machine that runs install.

pnpm has the onlyBuiltDependencies config, which lets you allowlist packages that are permitted to run install scripts. Yarn Berry has enableScripts: false as a starting point. Use these. The cost is minor (a few dependencies need explicit opt-in) and the benefit is that a post-install supply chain attack does not automatically succeed.

Turbo.json task configuration as a security control

Your turbo.json declares task dependencies and cached outputs. I treat it as a security config file. Changes to cache keys, to task dependencies, to environment variable allowlists — all of these have security implications. Require review for turbo.json changes the same way you require review for CI configuration changes.

The specific hygiene items: use the env field in task definitions to declare which environment variables affect the task's output (this controls cache key generation); use passThroughEnv for variables that need to be present but should not affect caching; and restrict globalDependencies to files that genuinely affect all tasks.

Dependency updates in monorepo scale

A monorepo with 47 workspaces and 1,800 transitive packages gets hundreds of dependency updates per month. Dependabot and Renovate both support monorepos, but configuring them well is its own project. I have audited monorepos where Renovate was set up, had been ignoring every PR for six months because they required manual merge, and had accumulated 400 open PRs.

Pick a dependency update cadence you will actually keep: weekly batched updates grouped by workspace, with auto-merge for patch versions that pass CI, manual review for minor and major. Staying current is the single most important supply chain control, and it is also the one that slips first when a team is busy.

How Safeguard Helps

Safeguard generates per-workspace SBOMs and a consolidated monorepo view, so you can triage CVEs based on which workspace they actually affect and whether the vulnerable code is reachable. Griffin AI summarizes dependency changes across PRs and highlights the subset worth human review, cutting through Renovate fatigue. Policy gates block builds that introduce abandoned packages, add copyleft licenses to publishable workspaces, or weaken the remote cache trust model. Continuous monitoring watches the full monorepo dependency graph for new advisories and surfaces them with workspace attribution, so your platform team knows exactly which application needs an upgrade the moment an upstream CVE drops.

Never miss an update

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