DevSecOps & Supply Chain Security

Dependency & SCA Scanning

18 min Lesson 4 of 28

Dependency & SCA Scanning

Every modern application is predominantly third-party code. A typical Node.js service pulls in hundreds of transitive dependencies; a Java Spring Boot application ships thousands of JARs it never directly required. When a critical vulnerability surfaces in one of those packages — Log4Shell in log4j, CVE-2021-44228, shut down production systems at companies across the globe within hours of disclosure — the organisations that survived with minimal damage were the ones that already knew exactly where they were using that library and could ship a patch or mitigation the same day. The ones who spent three days hand-searching codebases had a very different experience.

Software Composition Analysis (SCA) is the discipline of continuously inventorying, analysing, and remediating the open-source and third-party components your software depends on. It answers four questions in an automated, pipeline-integrated way: What do we depend on? What known vulnerabilities exist in those dependencies? Do our licences conflict with how we distribute software? And how quickly can we remediate?

The scale problem: npm's registry resolves a median of 83 transitive packages per direct dependency. "We only use five libraries" is never true at the dependency graph level. SCA tooling works on the full resolved graph, not just what appears in your manifest.

How Vulnerability Databases Work

SCA tools do not discover vulnerabilities — they correlate. They resolve your dependency graph into a precise list of package-name@version tuples, then look those up against one or more vulnerability databases:

  • NVD (National Vulnerability Database) — NIST-maintained, the canonical source. Every CVE eventually lands here with CVSS scoring and affected version ranges.
  • OSV (Open Source Vulnerabilities) — Google-maintained, schema-first, used by many tools as a faster and more machine-readable alternative to NVD. Covers PyPI, npm, Maven, RubyGems, Go modules, and more.
  • GitHub Advisory Database (GHSA) — feeds directly into Dependabot. Often has advisories days before NVD because maintainers coordinate disclosure directly through GitHub's security advisory tooling.
  • Snyk Intel — commercial database with hand-curated exploitability notes, reachability analysis, and remediation PRs. Proprietary but often has higher signal-to-noise for real exploitability.

CVSS score alone is a poor triage signal. A CVSS 9.8 vulnerability in a library you only use for a benign utility function, where the vulnerable code path is never reached, is lower priority than a CVSS 7.0 in a network-facing authentication library. Enterprise-grade SCA tools (Snyk, Mend, Semgrep Supply Chain) add reachability analysis: they trace your actual call graph and tell you whether the vulnerable function is reachable from your application entry points.

Dependabot: Automated Dependency Updates at Scale

GitHub's Dependabot operates in two distinct modes that teams often conflate. Dependabot alerts are passive — they notify you when a dependency in your default branch has a known CVE, using the GHSA database. Dependabot security updates are active — they automatically open a pull request with the minimal version bump that resolves the vulnerability. Dependabot version updates go further: they open PRs on a schedule to keep dependencies current, regardless of CVEs, which is the best long-term strategy because keeping the delta small means each update is low-risk.

Configure Dependabot via .github/dependabot.yml at the repository root:

# .github/dependabot.yml version: 2 updates: # NPM / Node - package-ecosystem: "npm" directory: "/" schedule: interval: "weekly" day: "monday" time: "06:00" timezone: "UTC" open-pull-requests-limit: 10 groups: # Batch minor/patch updates for dev tools — less noise dev-dependencies: patterns: - "*" update-types: - "minor" - "patch" dependency-type: "development" ignore: # Pin a package that requires a manual migration - dependency-name: "webpack" update-types: ["version-update:semver-major"] labels: - "dependencies" - "automated" reviewers: - "platform-team" # Docker base images - package-ecosystem: "docker" directory: "/" schedule: interval: "weekly" labels: - "dependencies" - "docker" # GitHub Actions - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" labels: - "dependencies" - "ci"
Group minor and patch updates for indirect dependencies into a single weekly PR. Reviewing 47 individual PRs for patch bumps is toil that teams ignore — they pile up and become technical debt. Grouping keeps the queue manageable while still keeping dependencies current. Reserve individual PRs for direct dependencies and major version changes that need human review.

Renovate: More Power, More Control

Renovate (Mend Renovate) is the open-source alternative that most large engineering organisations prefer over Dependabot for complex monorepos or multi-ecosystem repositories. It supports more package managers (including Helm charts, Terraform providers, and custom regex), has richer auto-merge rules, and provides a dependency dashboard issue that gives you a single-pane view of all pending updates.

// renovate.json (repository root) { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:base", ":dependencyDashboard", ":separatePatchReleases", "group:allNonMajor" ], "schedule": ["before 7am on monday"], "prConcurrentLimit": 5, "prHourlyLimit": 2, "automerge": true, "automergeType": "pr", "packageRules": [ { "matchPackagePatterns": ["*"], "matchUpdateTypes": ["patch"], "automerge": true, "automergeSchedule": ["after 10pm every weekday"] }, { "matchPackageNames": ["node"], "matchUpdateTypes": ["major"], "enabled": false }, { "matchDepTypes": ["devDependencies"], "matchUpdateTypes": ["minor", "patch"], "groupName": "dev tool patches" } ], "vulnerabilityAlerts": { "enabled": true, "schedule": ["at any time"], "labels": ["security"] } }

Renovate's vulnerabilityAlerts block is critical: it bypasses the normal schedule and opens security-fix PRs immediately, regardless of other batching or concurrency limits. In a well-configured pipeline, these PRs also auto-merge once CI passes, meaning a critical CVE can be patched, tested, and deployed without human intervention — in minutes rather than days.

Running SCA in CI: Trivy, Grype, and Snyk

Dependabot and Renovate handle remediation. For enforcement — blocking a build when a high-severity vulnerability is introduced — you need a CI-integrated scanner. Three tools dominate production use:

  • Trivy (Aqua Security) — open-source, fast, scans OS packages, language dependencies, container layers, IaC, and SBOMs. Zero configuration for common ecosystems.
  • Grype (Anchore) — open-source, pairs with Syft for SBOM generation. Excellent for policy-as-code pipelines.
  • Snyk — commercial SaaS with free tier, reachability analysis, and developer-facing fix PRs. Best-in-class for teams with budget and a focus on developer experience.
# GitHub Actions: SCA scanning with Trivy on every PR # .github/workflows/sca.yml name: SCA Scan on: pull_request: push: branches: [main] jobs: trivy-fs-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Trivy filesystem scan (language deps) uses: aquasecurity/trivy-action@master with: scan-type: "fs" scan-ref: "." format: "sarif" output: "trivy-results.sarif" severity: "HIGH,CRITICAL" exit-code: "1" # Fail the build on HIGH or CRITICAL ignore-unfixed: true # Skip vulns with no fix yet — reduce noise vuln-type: "library" # Language deps only; OS handled separately - name: Upload SARIF to GitHub Security tab uses: github/codeql-action/upload-sarif@v3 if: always() # Upload even when the step above fails with: sarif_file: "trivy-results.sarif"
Production pitfall — ignore-unfixed: true: This flag suppresses findings where no fixed version exists. In most real pipelines this is correct — blocking on an unfixable CVE just trains developers to ignore the scanner. However, for CRITICAL vulnerabilities with no fix, you still need a compensating control: runtime WAF rules, disabling the vulnerable code path, or accepting and documenting the risk via an exception process. Never suppress silently; always document.

Lockfile Hygiene: The Foundation of Reproducibility

Lockfiles (package-lock.json, yarn.lock, Pipfile.lock, poetry.lock, go.sum, Gemfile.lock, pom.xml with pinned versions) are not optional. They are the primary defence against two classes of supply chain attack:

  • Dependency confusion attacks — an attacker publishes a malicious package with the same name as an internal package to a public registry. Without a locked registry scope, package managers may resolve the public version. Lockfiles record the exact registry URL and integrity hash, so resolution is deterministic.
  • Typosquatting — a malicious package name that closely resembles a popular package (lodahs vs lodash). Lockfiles prevent accidental resolution of the wrong package once a correct install has been done.

Critical lockfile rules for production pipelines: always commit lockfiles; use npm ci (not npm install) in CI — ci fails if the lockfile is out of sync with package.json and never modifies it; and verify integrity hashes. Node's lockfile v2/v3 and Go's go.sum both embed sha512 or sha256 hashes of the resolved tarballs. If a package is tampered with after you locked it, the hash check fails at install time.

SCA Scanning in a CI/CD Pipeline PR / Push Trigger CI Pipeline Build & Test Trivy SCA Scan SARIF Upload HIGH? PASS Deploy Gate passed FAIL Block PR CVE annotated in PR Vuln Databases NVD / OSV GHSA / Snyk Intel hash + version lookup query Dependabot Auto-fix PR opened SCA scan runs on every PR; HIGH/CRITICAL findings block merge; Dependabot auto-opens fix PRs.
SCA scanning gates every pull request while Dependabot continuously proposes fixes — two complementary feedback loops.

Policy as Code: Enforcing Acceptable Risk

Raw CVE counts are not a policy. A mature SCA practice defines explicit acceptance criteria — typically in a .trivyignore file or an OPA/Rego policy — and treats exceptions as time-bounded, reviewed, and audited. At minimum, block:

  • Any CRITICAL CVE with a fix available and a CVSS exploit score above 8.0
  • Licences incompatible with your distribution model (GPL in a proprietary closed-source product)
  • Direct dependencies on packages deprecated or archived by their maintainer

Use trivy fs . --format cyclonedx --output sbom.json to emit a CycloneDX SBOM alongside the vulnerability report — this feeds directly into the SBOM workflow covered in Lesson 7. When a zero-day breaks, an up-to-date SBOM lets you search your entire estate in seconds rather than days.

Set a mean-time-to-patch (MTTP) SLO by severity: CRITICAL with known exploit — patch within 24 hours. HIGH with fix available — patch within 7 days. MEDIUM — patch within 30 days as part of normal dependency update cadence. LOW — best-effort. These numbers are Google's internal targets (roughly) and are widely adopted in enterprise security programmes. Tracking MTTP gives you a metric you can actually improve, rather than a binary pass/fail that hides systemic slowness.