Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Snyk: Dependency Vulnerability Scanning

For .NET engineers who know: OWASP Dependency-Check, Dependabot, NuGet audit (dotnet list package --vulnerable), and the NuGet advisory database You’ll learn: How Snyk scans npm dependency trees for vulnerabilities, auto-generates fix PRs, and integrates into CI to block deployments on high-severity findings — and why the npm dependency model makes transitive vulnerability management more complex than NuGet Time: 15-20 min read

The .NET Way (What You Already Know)

In the .NET ecosystem, dependency vulnerabilities come to your attention through several channels. GitHub Dependabot raises PRs to update vulnerable packages. dotnet list package --vulnerable queries the NuGet advisory database from the command line. OWASP Dependency-Check runs in CI and generates an HTML report with CVE references. If you use Azure DevOps, the Microsoft Security DevOps extension runs these scans automatically.

The NuGet package ecosystem has characteristics that make vulnerability management tractable. Package trees tend to be shallow — most .NET packages have few transitive dependencies compared to npm. NuGet’s lock files (packages.lock.json) are deterministic. Vulnerability data comes from a single authoritative source: the NuGet advisory database backed by GitHub’s advisory database.

# Check for vulnerable packages in .NET
dotnet list package --vulnerable --include-transitive

# Output:
# The following sources were used:
#    https://api.nuget.org/v3/index.json
#
# Project `MyApp`
# [net8.0]:
#    Top-level Package      Requested   Resolved   Severity   Advisory URL
#    > Newtonsoft.Json      12.0.1      12.0.1     High       https://github.com/advisories/GHSA-...

The Snyk Way

Snyk covers the same problem — finding vulnerable dependencies — but the npm ecosystem presents it with considerably more complexity. Before looking at the tool, you need to understand why.

The npm Dependency Tree Problem

A NuGet package typically has 5-20 transitive dependencies. An npm package commonly has 50-500. react-scripts alone installs over 1,300 packages. This is not an exaggeration:

# A typical Next.js project
$ npm list --all 2>/dev/null | wc -l
1847

This means vulnerability management in npm is a different class of problem. A vulnerability in a deep transitive dependency — one you have never heard of, in a package three levels down — can appear in your security report. You may not control the upgrade path because you do not directly depend on the vulnerable package.

Snyk addresses this with priority scoring, fix PRs that update the direct dependency responsible for pulling in the vulnerable transitive, and “accept” workflows for vulnerabilities without a fix.

Installing and Running Snyk CLI

# Install globally
npm install -g snyk

# Authenticate (opens browser for OAuth)
snyk auth

# Or authenticate with a token (for CI)
snyk auth $SNYK_TOKEN

Running a scan:

# Scan the current project's package.json and lockfile
snyk test

# Scan and output JSON (for CI parsing)
snyk test --json

# Scan and fail only on high or critical severity
snyk test --severity-threshold=high

# Scan including dev dependencies
snyk test --dev

# Show detailed vulnerability report
snyk test --all-projects  # Scans all package.json files in a monorepo

Example output:

Testing /path/to/project...

✗ High severity vulnerability found in semver
  Description: Regular Expression Denial of Service (ReDoS)
  Info: https://snyk.io/vuln/SNYK-JS-SEMVER-3247795
  Introduced through: node-gyp@9.4.0 > semver@5.7.1
  Fix: Upgrade node-gyp to version 9.4.1 or higher (semver will be upgraded to 5.7.2)
  Fixable? Yes (auto-fixable)

✗ Critical severity vulnerability found in loader-utils
  Description: Prototype Pollution
  Info: https://snyk.io/vuln/SNYK-JS-LOADERUTILS-3043103
  Introduced through: react-scripts@5.0.1 > loader-utils@1.4.0
  Fix: No fix available — loader-utils@1.x is unmaintained
  Fixable? No

✓ Tested 1,847 dependencies for known issues, found 2 issues.

The output is similar to dotnet list package --vulnerable --include-transitive, but with a critical addition: Snyk traces the introduction path (“Introduced through”) and tells you which direct dependency to upgrade to fix the transitive issue.

Vulnerability Report Structure

Each Snyk finding includes:

FieldDescription
SeverityCritical, High, Medium, Low
CVE/CWEReference IDs for the vulnerability
CVSS scoreNumeric severity (0-10)
Exploit maturityProof-of-concept, functional, no known exploit
Fix availabilityWhether a patched version exists
Introduction pathChain of packages that introduced this dependency
FixableWhether snyk fix can automatically resolve it

The exploit maturity field is important. A High severity vulnerability with “No known exploit” in a transitive testing dependency is meaningfully different from a Critical with “Functional exploit” in your production HTTP server. Snyk’s priority score combines severity, exploit maturity, and whether the vulnerable code path is actually reachable in your project.

Auto-Fix PRs

Snyk’s most useful feature for large teams is auto-fix PRs. Snyk monitors your repository continuously (via GitHub integration, not CI) and raises PRs when:

  • A new vulnerability is discovered in an existing dependency
  • A fix becomes available for a previously unfixable vulnerability
# Fix all auto-fixable vulnerabilities locally
snyk fix

# What snyk fix does:
# 1. Identifies vulnerable packages with available fixes
# 2. Determines the minimum upgrade to resolve the vulnerability
# 3. Updates package.json and runs pnpm install / npm install
# 4. Verifies the fix does not break your tests (if configured)

To enable automatic PRs in GitHub:

  1. Connect your GitHub repository at app.snyk.io
  2. Navigate to Settings → Integrations → GitHub
  3. Enable “Automatic fix PRs” and “New vulnerabilities”

Snyk will raise PRs like:

[Snyk] Security upgrade axios from 0.21.1 to 0.21.4

This PR was automatically created by Snyk to fix 1 vulnerability.

Vulnerability: Server-Side Request Forgery (SSRF)
Severity: High
CVE: CVE-2023-45857
Fixed in: axios@0.21.4

See https://snyk.io/vuln/SNYK-JS-AXIOS-... for details.

License Compliance

Snyk scans license metadata for every dependency. This is relevant when your project has commercial distribution obligations:

# Check licenses
snyk test --print-deps

# Or configure license policies at the organization level in app.snyk.io
# under Settings → Licenses

You can configure license policies to:

  • Allow: MIT, Apache-2.0, ISC, BSD-2-Clause, BSD-3-Clause (typical commercial use)
  • Warn: LGPL-2.0, LGPL-3.0 (linkage restrictions)
  • Fail: GPL-2.0, GPL-3.0, AGPL-3.0 (copyleft — review required)

Container Scanning

If you ship a Docker image (common for NestJS deployments):

# Scan a local Docker image
snyk container test your-image:latest

# Scan with fix recommendations
snyk container test your-image:latest --file=Dockerfile

# Output includes OS-level CVEs (from the base image) and npm vulnerabilities

This is the equivalent of scanning both the host system dependencies and your application dependencies together — something that dotnet list package --vulnerable does not do because .NET rarely ships in containers with OS-level attack surface.

CI/CD Integration

The standard pattern is to fail the build on high or critical severity vulnerabilities and warn on medium or lower:

# .github/workflows/security.yml
name: Security Scan

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 0 * * 1'   # Also run weekly — new CVEs appear on existing deps

jobs:
  snyk:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Run Snyk vulnerability scan
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
        with:
          args: --severity-threshold=high --all-projects
        # Exit code 1 = vulnerabilities found above threshold → build fails
        # Exit code 0 = clean or only below-threshold issues → build passes

      - name: Upload Snyk results to GitHub Code Scanning
        uses: github/codeql-action/upload-sarif@v3
        if: always()   # Upload even if Snyk found issues
        with:
          sarif_file: snyk.sarif

For the .snyk ignore file (the equivalent of a suppression list):

# .snyk
# Suppress false positives or accepted risks
version: v1.25.0

ignore:
  SNYK-JS-SEMVER-3247795:
    - '*':
        reason: >
          This vulnerability is in a test-only devDependency and is not
          reachable from production code. Risk accepted until the dependency
          ships a fix.
        expires: '2026-06-01T00:00:00.000Z'  # Review this date

Monitoring and Continuous Scanning

Beyond CI, Snyk can monitor your deployed application for new vulnerabilities discovered after your last scan:

# Register your project for ongoing monitoring
snyk monitor

# This uploads a snapshot of your dependency tree to Snyk's servers.
# When a new CVE is disclosed for any of your dependencies, Snyk
# sends an alert and (if configured) raises a fix PR.

Run snyk monitor at the end of your deployment pipeline to keep the snapshot current.

Key Differences

Concern.NET (Dependabot / OWASP DC)SnykNotes
Vulnerability sourceGitHub Advisory Database, NuGet advisoriesSnyk’s own database (broader than NVD)Snyk often has vulnerabilities before NVD
Transitive depthManageable — NuGet trees are shallowComplex — npm trees are 100s of packages deepSnyk traces introduction paths
Auto-fix PRsDependabot raises version bump PRsSnyk raises targeted fix PRs with contextSnyk PRs include CVE details and rationale
License scanningNot built into DependabotBuilt into SnykLicense policies configurable per org
Container scanningSeparate tool (Trivy, etc.)Integrated into SnykSame tool, one license
Exploit maturityNot provided by DependabotProvided and factored into priority scoreUseful for triaging large backlogs
Suppression mechanismdependabot.yml ignore rules.snyk ignore file with expiry datesExpiry dates enforce review cadence
Fix mechanismUpdates packages.json versionsnyk fix command + PR automationEquivalent outcomes
CostFree (GitHub-native)Free tier (200 tests/month), paid for teamsEvaluate based on repo count

Gotchas for .NET Engineers

Gotcha 1: The npm Dependency Tree Makes “Fix This Vulnerability” Non-Trivial

In NuGet, if Newtonsoft.Json 12.0.1 is vulnerable, you upgrade it to 13.0.1 in your .csproj. Done. The package is yours to control.

In npm, the vulnerable package may be four levels deep in the dependency tree, owned by a package you do not control. Consider this chain:

your-app
└── react-scripts@5.0.1
    └── webpack@5.88.0
        └── loader-utils@1.4.0   ← vulnerable

You cannot upgrade loader-utils directly because you do not depend on it directly. Your options are:

  1. Upgrade react-scripts to a version that pulls in a fixed loader-utils
  2. Use npm’s overrides (or pnpm’s overrides) to force a specific version of a transitive dependency
  3. Accept the risk if the code path is not reachable

Snyk will tell you which option is available for each vulnerability. Option 2 looks like:

// package.json — pnpm overrides
{
  "pnpm": {
    "overrides": {
      "loader-utils": "^3.2.1"
    }
  }
}

Use overrides carefully — they can cause peer dependency conflicts. Snyk’s fix PRs handle this automatically when it is safe to do so.

Gotcha 2: High Vulnerability Count Is Normal — Triage by Reachability

When you first run snyk test on an existing npm project, you may see 20-50 vulnerabilities. This is not a crisis. Most of them are in devDependencies (build tools, test runners) that never execute in production. Many are in deeply transitive packages that are not reachable from your code paths.

Triage strategy:

  1. Filter by --severity-threshold=high first — only High and Critical matter immediately
  2. Check if the vulnerable package is in dependencies vs. devDependencies — devDep vulnerabilities are lower priority
  3. Check exploit maturity — “No known exploit” is lower priority than “Functional exploit”
  4. Check reachability — Snyk’s paid tier includes reachability analysis to confirm whether the vulnerable function is actually called

Do not aim for zero vulnerabilities immediately. Aim for zero High/Critical in production dependencies. Set a quality gate for --severity-threshold=high in CI and accept the rest with documented rationale and expiry dates in .snyk.

Gotcha 3: pnpm install Does Not Automatically Fix Vulnerabilities

npm audit fix (and its pnpm equivalent pnpm audit --fix) attempts to automatically fix vulnerabilities by upgrading packages. But unlike snyk fix, it does not understand introduction paths or minimum required upgrades — it can introduce breaking changes by upgrading to major versions unexpectedly.

Prefer snyk fix over npm audit fix because Snyk’s fix understands the minimum upgrade required and verifies that the fix is safe before applying it. If you use pnpm, configure Snyk with:

snyk test --package-manager=pnpm
snyk fix --package-manager=pnpm

Gotcha 4: SNYK_TOKEN Must Never Be in Source Code

This is obvious but worth stating explicitly because the failure mode is severe. If SNYK_TOKEN is committed to the repository, anyone who forks the repository can exhaust your Snyk API quota and access your vulnerability reports (which describe exactly what is vulnerable in your application).

Store it in GitHub Actions Secrets (${{ secrets.SNYK_TOKEN }}), never in .env or CI configuration files committed to the repository. Rotate it immediately if it is ever exposed.

Gotcha 5: npm Supply Chain Attacks Are Not the Same as CVE-Based Vulnerabilities

Snyk scans against known CVE databases. npm supply chain attacks — where a malicious package impersonates a legitimate one (typosquatting), or a legitimate maintainer account is compromised and a malicious version is published — are not in the CVE database by definition. They are zero-day events.

Snyk’s snyk monitor provides some protection by alerting when a dependency’s hash changes unexpectedly. The more complete mitigations are:

  • Lock files committed to the repository (pnpm-lock.yaml)
  • --frozen-lockfile in CI (pnpm) or npm ci — never install without a lockfile
  • Package provenance verification (available in npm v9+ and pnpm v8+)
  • Private registry with vetted package mirrors for high-security environments

These mitigations are beyond what Snyk provides out of the box, but Snyk’s documentation covers them.

Hands-On Exercise

Run a full Snyk audit on your project and establish a security baseline.

  1. Install the Snyk CLI and authenticate with your Snyk account.

  2. Run snyk test and review the full output. Note the total count of vulnerabilities by severity.

  3. Run snyk test --severity-threshold=high to see only High and Critical findings. This is your actionable baseline.

  4. For each High/Critical finding:

    • Determine whether it is in dependencies or devDependencies
    • Read the Snyk advisory link to understand the vulnerability
    • Check if a fix is available (Snyk will indicate this)
    • Either apply the fix with snyk fix or add a suppression to .snyk with a rationale and expiry date
  5. Add the Snyk GitHub Actions workflow to your repository. Create a SNYK_TOKEN secret from your Snyk account settings. Push and verify the CI job runs and passes.

  6. Connect your repository in the Snyk UI at app.snyk.io and enable automatic fix PRs. Verify a PR appears if Snyk has any auto-fixable issues.

  7. Run snyk monitor at the end of your local workflow to register your project for ongoing monitoring.

Quick Reference

TaskCommand
Authenticatesnyk auth
Scan projectsnyk test
Scan with thresholdsnyk test --severity-threshold=high
Scan all projects (monorepo)snyk test --all-projects
Auto-fix vulnerabilitiessnyk fix
Register for monitoringsnyk monitor
Scan container imagesnyk container test image:tag
Output JSONsnyk test --json > snyk-report.json
Scan dev deps toosnyk test --dev
Ignore a vulnerabilityAdd to .snyk with reason and expiry

.snyk Ignore Template

version: v1.25.0
ignore:
  SNYK-JS-EXAMPLE-1234567:
    - '*':
        reason: >
          This vulnerability is in a devDependency not reachable in production.
          Review when the dependency ships a fix.
        expires: '2026-09-01T00:00:00.000Z'

pnpm Override Template

{
  "pnpm": {
    "overrides": {
      "vulnerable-transitive-package": ">=safe-version"
    }
  }
}

GitHub Actions Snippet

- name: Snyk Security Scan
  uses: snyk/actions/node@master
  env:
    SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
  with:
    args: --severity-threshold=high --all-projects

Further Reading