Scanning a monorepo with Snyk is surprisingly simple, but the real magic is how Snyk leverages your existing project structure to give you granular visibility without forcing you to change how you work.

Let’s say you have a monorepo structured like this:

/
├── packages/
│   ├── ui-components/
│   │   ├── package.json
│   │   └── src/
│   ├── api-service/
│   │   ├── package.json
│   │   └── src/
│   └── shared-utils/
│       ├── package.json
│       └── src/
├── apps/
│   ├── web-app/
│   │   ├── package.json
│   │   └── src/
│   └── mobile-app/
│       ├── package.json
│       └── src/
└── package.json

Snyk’s default behavior when you point it at the root of this monorepo is to automatically discover all the individual projects within it. It looks for manifest files like package.json (for Node.js/npm/yarn), pom.xml (for Maven), build.gradle (for Gradle), go.mod (for Go), Gemfile (for Ruby), and requirements.txt (for Python).

When you run snyk test in the root directory, Snyk will output something like this:

Testing /path/to/your/monorepo...

Monorepo detected. Found the following projects:
  - packages/ui-components
  - packages/api-service
  - packages/shared-utils
  - apps/web-app
  - apps/mobile-app

... (vulnerability reports for each project) ...

This means Snyk has identified each subdirectory containing a package manager manifest as a distinct project. It then scans the dependencies of each of these projects independently. This is crucial because it allows you to see exactly which part of your monorepo is introducing a vulnerability, rather than getting a single, overwhelming report for the entire repository.

The core problem Snyk solves here is dependency sprawl in large, multi-project repositories. Without this granular detection, you’d have to either:

  1. Scan each project individually, which is tedious and error-prone in a monorepo.
  2. Aggregate all dependencies into one massive list, losing the context of which project owns which dependency.

Snyk’s monorepo scanning addresses this by understanding common monorepo tooling and structures. It’s not just looking for package.json files; it’s smart enough to infer project boundaries based on your directory layout and the presence of build tools or package managers.

Let’s dive into the internal mechanics. When Snyk starts, it performs a recursive scan of the directory. It maintains a list of known project manifest files. Upon finding one, it checks the directory path. If it’s a root-level manifest (like the one at the very top of the repo), it might treat it differently than a manifest nested within a subdirectory (like packages/ui-components/package.json). Snyk’s internal logic identifies these nested manifests as belonging to separate projects within the monorepo. It then triggers a specific scan for that project’s dependencies, using the appropriate package manager. The results are then aggregated under the project’s path.

You can control which projects Snyk scans. For example, if you only want to scan the apps directory, you can run:

snyk test --exclude-from=packages

Or, if you want to be explicit about what to include:

snyk test --include-path=apps

This works by Snyk filtering its discovered projects based on your provided patterns. --exclude-from tells Snyk to ignore any project whose path matches the given pattern (e.g., anything under packages/). --include-path does the opposite, focusing only on projects whose paths contain the specified string.

The real power comes when you integrate this into your CI/CD pipeline. A single snyk test command at the monorepo root in your CI script can secure all your projects. If a vulnerability is found in apps/web-app, your pipeline fails, and the Snyk report clearly points to apps/web-app as the source.

Here’s a snippet of what a Snyk test output might look like for a single project within the monorepo:

{
  "vulnerabilities": [
    {
      "id": "SNYK-JS-lodash-1018900",
      "severity": "high",
      "packageName": "lodash",
      "version": "4.17.15",
      "via": [
        "dev-null",
        "lodash"
      ],
      "publicationTime": "2020-01-02T17:53:31.000Z",
      "isUpgradable": true,
      "upgradeVersion": "4.17.21",
      "filePath": "packages/shared-utils/package-lock.json",
      "packageNameLine": "      \"lodash\": \"^4.17.15\","
    }
  ],
  "dependencyCount": 125,
  "path": "packages/shared-utils",
  "uniqueCount": 100,
  "ignoreSettings": {
    "ignoreUnsafe": false,
    "patches": {}
  },
  "summary": "1 vulnerability found"
}

Notice the "path": "packages/shared-utils" field. This is what differentiates monorepo scanning; Snyk explicitly tells you which sub-project the vulnerability resides in.

The one thing most people don’t realize is that Snyk’s monorepo detection isn’t limited to just JavaScript projects. It automatically detects and scans other language ecosystems within the same monorepo, provided you have the respective tooling installed in your CI environment. So, if you have a Python service alongside your Node.js apps in the same monorepo, Snyk will find its requirements.txt and scan its Python dependencies too, all from a single command at the root.

The next logical step after getting your monorepo scanning set up is to start managing these findings with Snyk’s remediation capabilities, such as automatic pull requests for upgrades.

Want structured learning?

Take the full Snyk course →