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:
| Field | Description |
|---|---|
| Severity | Critical, High, Medium, Low |
| CVE/CWE | Reference IDs for the vulnerability |
| CVSS score | Numeric severity (0-10) |
| Exploit maturity | Proof-of-concept, functional, no known exploit |
| Fix availability | Whether a patched version exists |
| Introduction path | Chain of packages that introduced this dependency |
| Fixable | Whether 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:
- Connect your GitHub repository at app.snyk.io
- Navigate to Settings → Integrations → GitHub
- 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) | Snyk | Notes |
|---|---|---|---|
| Vulnerability source | GitHub Advisory Database, NuGet advisories | Snyk’s own database (broader than NVD) | Snyk often has vulnerabilities before NVD |
| Transitive depth | Manageable — NuGet trees are shallow | Complex — npm trees are 100s of packages deep | Snyk traces introduction paths |
| Auto-fix PRs | Dependabot raises version bump PRs | Snyk raises targeted fix PRs with context | Snyk PRs include CVE details and rationale |
| License scanning | Not built into Dependabot | Built into Snyk | License policies configurable per org |
| Container scanning | Separate tool (Trivy, etc.) | Integrated into Snyk | Same tool, one license |
| Exploit maturity | Not provided by Dependabot | Provided and factored into priority score | Useful for triaging large backlogs |
| Suppression mechanism | dependabot.yml ignore rules | .snyk ignore file with expiry dates | Expiry dates enforce review cadence |
| Fix mechanism | Updates packages.json version | snyk fix command + PR automation | Equivalent outcomes |
| Cost | Free (GitHub-native) | Free tier (200 tests/month), paid for teams | Evaluate 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:
- Upgrade
react-scriptsto a version that pulls in a fixedloader-utils - Use npm’s
overrides(or pnpm’soverrides) to force a specific version of a transitive dependency - 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:
- Filter by
--severity-threshold=highfirst — only High and Critical matter immediately - Check if the vulnerable package is in
dependenciesvs.devDependencies— devDep vulnerabilities are lower priority - Check exploit maturity — “No known exploit” is lower priority than “Functional exploit”
- 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-lockfilein CI (pnpm) ornpm 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.
-
Install the Snyk CLI and authenticate with your Snyk account.
-
Run
snyk testand review the full output. Note the total count of vulnerabilities by severity. -
Run
snyk test --severity-threshold=highto see only High and Critical findings. This is your actionable baseline. -
For each High/Critical finding:
- Determine whether it is in
dependenciesordevDependencies - 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 fixor add a suppression to.snykwith a rationale and expiry date
- Determine whether it is in
-
Add the Snyk GitHub Actions workflow to your repository. Create a
SNYK_TOKENsecret from your Snyk account settings. Push and verify the CI job runs and passes. -
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.
-
Run
snyk monitorat the end of your local workflow to register your project for ongoing monitoring.
Quick Reference
| Task | Command |
|---|---|
| Authenticate | snyk auth |
| Scan project | snyk test |
| Scan with threshold | snyk test --severity-threshold=high |
| Scan all projects (monorepo) | snyk test --all-projects |
| Auto-fix vulnerabilities | snyk fix |
| Register for monitoring | snyk monitor |
| Scan container image | snyk container test image:tag |
| Output JSON | snyk test --json > snyk-report.json |
| Scan dev deps too | snyk test --dev |
| Ignore a vulnerability | Add 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
- Snyk CLI Documentation — Complete CLI reference including all flags and output formats
- Snyk npm Fix Documentation — How
snyk fixdetermines safe upgrades and applies them - npm Package Provenance — npm’s supply chain security for package publishers
- pnpm Overrides — Forcing specific versions of transitive dependencies in pnpm projects