Continuous Dependency Monitoring

Permalink to "Continuous Dependency Monitoring"

This workflow is part of Supply Chain Auditing & Dependency Verification. A dependency tree that was clean when you shipped is not clean forever: advisories are published daily against versions that were already in your lockfile, and a package that had no known issues on Monday can carry a critical CVE by Friday without a single line of your code changing. Pinning locks the bytes; it does not tell you when those exact bytes become dangerous.

The workflow on this page keeps your dependencies under continuous watch so that a newly disclosed vulnerability turns into a tracked, time-boxed task instead of a surprise in an incident review. It wires together advisory scanning, automated update pull requests from Dependabot or Renovate, scheduled npm audit and osv-scanner runs in CI, a triage flow, and an alert-to-remediation SLA — a closed loop that runs whether or not anyone is looking.

Prerequisites

Permalink to "Prerequisites"

Conceptual Foundation: Detection, Remediation, and the Loop Between Them

Permalink to "Conceptual Foundation: Detection, Remediation, and the Loop Between Them"

Continuous monitoring is not a single tool. It is the coupling of two independent feeds — a detection feed that tells you when a resolved dependency becomes vulnerable, and a remediation feed that produces the change to fix it — joined by a triage decision and measured against a clock.

The detection feed is grounded in public advisory data. GitHub’s Dependabot reads your lockfile against the GitHub Advisory Database, which mirrors and enriches the CVE list and the Open Source Vulnerabilities (OSV) schema. npm audit queries the npm registry’s /-/npm/v1/security/advisories/bulk endpoint over the same underlying data. osv-scanner reads OSV.dev directly, covering ecosystems beyond npm. All three answer the same question — does a package version in my dependency graph have a known advisory? — but they fire at different times and cover different scopes, which is exactly why a production setup runs more than one.

The remediation feed is an automated update bot. Renovate or Dependabot watches your manifests and lockfile, computes which dependencies have newer versions (or a version that resolves an advisory), and opens a pull request with the bump and an updated lockfile. Crucially, the same bot behaves in two modes: a slow, batched cadence for routine upkeep, and an immediate, out-of-band pull request when the update closes a security advisory.

Between the two feeds sits triage: a human (or a rule) deciding whether a given finding is fixed now, deferred with a written justification, or accepted as not-applicable. The full triage discipline — reachability analysis, severity re-scoring, and compliance mapping — lives in Vulnerability Tracking & Triage; this page is concerned with keeping the feeds flowing into it reliably.

The diagram below shows the loop these pieces form.

Continuous Dependency Monitoring Loop A closed loop diagram. A committed lockfile is watched by two detection sources: an advisory feed and a scheduled CI audit. Both emit alerts into a triage stage. Triage drives an automated update bot that opens a pull request, CI verifies the pull request, and a merge updates the lockfile, closing the loop. Watched lockfile resolved deps Advisory feed Dependabot alerts CI audit npm audit / osv-scanner Triage fix / defer / accept Update PR Renovate Verify + merge CI green ✓ scan gate alert finding open merge closes the loop

Step 1 — Enable Advisory Scanning

Permalink to "Step 1 — Enable Advisory Scanning"

The cheapest signal to turn on is GitHub’s built-in advisory feed. Dependabot alerts read the resolved versions in your committed lockfile against the GitHub Advisory Database and raise an alert whenever a match appears — including retroactively, when a new advisory is published against a version you already shipped.

Enable it declaratively so the setting is auditable in the repository rather than buried in the web UI. Commit a security policy and confirm the feature flags via the API:

# Confirm Dependabot security updates + vulnerability alerts are enabled
gh api -H "Accept: application/vnd.github+json" \
  /repos/OWNER/REPO/automated-security-fixes

gh api -H "Accept: application/vnd.github+json" \
  /repos/OWNER/REPO/vulnerability-alerts

Expected output — the vulnerability-alerts endpoint returns HTTP 204 No Content when enabled, and the automated-security-fixes endpoint returns a JSON object with "enabled": true:

{
  "enabled": true,
  "paused": false
}

To receive the feed as machine-readable events for a dashboard or SIEM, subscribe to the Dependabot alert webhook or poll the alerts endpoint:

# List open advisory alerts, newest first, with severity and package
gh api "/repos/OWNER/REPO/dependabot/alerts?state=open&sort=created&direction=desc" \
  --jq '.[] | {package: .dependency.package.name, severity: .security_advisory.severity, ghsa: .security_advisory.ghsa_id}'
{"package":"lodash","severity":"high","ghsa":"GHSA-jf85-cpcp-j695"}
{"package":"minimist","severity":"critical","ghsa":"GHSA-xvch-5gv4-984h"}

Each line is one open finding that must enter triage. This endpoint is the authoritative list the SLA in Step 5 measures against.


Step 2 — Configure Automated Update Pull Requests

Permalink to "Step 2 — Configure Automated Update Pull Requests"

Detection without remediation is just a growing backlog. Install an update bot to turn each advisory — and each routine version drift — into a reviewable pull request with the lockfile already regenerated. This site uses Renovate; the full production configuration is documented in Automating Dependency Updates with Renovate.

A minimal but production-shaped renovate.json batches routine work to one weekly window while letting security fixes jump the queue:

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": ["config:recommended"],
  "schedule": ["before 6am on monday"],
  "rangeStrategy": "pin",
  "lockFileMaintenance": { "enabled": true, "schedule": ["before 6am on monday"] },
  "vulnerabilityAlerts": {
    "labels": ["security"],
    "schedule": ["at any time"]
  },
  "packageRules": [
    { "matchUpdateTypes": ["patch"], "automerge": true }
  ]
}

The vulnerabilityAlerts block with "schedule": ["at any time"] is the load-bearing line: it overrides the weekly window so a security fix is raised the moment Renovate sees the advisory, not on the next Monday.

Expected output — after Renovate’s first run, the Dependency Dashboard issue lists queued updates and any open security pull request:

## Open

- [ ] Update dependency lodash to v4.17.21 (security)  #142

## Pending (scheduled)

- [ ] Update dependency vite to v5.4.10  — before 6am on monday

If you prefer GitHub’s native bot, the equivalent .github/dependabot.yml is shown in the Renovate page’s variants section.


Step 3 — Gate CI on a Scheduled and Per-PR Audit

Permalink to "Step 3 — Gate CI on a Scheduled and Per-PR Audit"

Advisory alerts and update pull requests are asynchronous — they fire after a change lands. A CI audit gate is the synchronous counterpart: it fails the build if a pull request would introduce a dependency with a known advisory, and a nightly cron re-runs the same audit against the default branch so a newly published advisory is caught within a day even if nobody touched the code.

# .github/workflows/dependency-audit.yml
name: dependency-audit
on:
  pull_request:
  schedule:
    - cron: "0 6 * * *"   # nightly at 06:00 UTC against the default branch

permissions:
  contents: read

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      # Fail the job on any high or critical advisory in the resolved tree
      - name: npm audit (high+)
        run: npm audit --audit-level=high

      # Broader, cross-ecosystem scan of the lockfile via OSV.dev
      - name: osv-scanner
        uses: google/osv-scanner-action@v1
        with:
          scan-args: "--lockfile=package-lock.json"

Expected output — a clean run exits 0; a run against a tree containing a high-severity advisory fails the step:

# npm audit
found 1 high severity vulnerability
  1 high
To address all issues, run: npm audit fix
Error: Process completed with exit code 1.

Choose the --audit-level threshold deliberately. Gating on high blocks the merge for high and critical findings while still recording moderate and low ones in the log for triage. Setting it to critical is appropriate only for repositories where a well-staffed triage rotation handles everything below that line promptly.


Step 4 — Triage Every Finding

Permalink to "Step 4 — Triage Every Finding"

An alert is not a task until someone has decided what to do with it. Every finding — whether it arrived from the advisory feed in Step 1 or the CI gate in Step 3 — routes through a triage decision: fix, defer with justification, or accept as not-applicable. Automate the routing so nothing sits unread.

#!/usr/bin/env bash
# scripts/route-advisories.sh — post new critical/high alerts to the triage channel
set -euo pipefail

gh api "/repos/${GITHUB_REPOSITORY}/dependabot/alerts?state=open" \
  --jq '.[] | select(.security_advisory.severity == "critical" or .security_advisory.severity == "high")
        | "\(.security_advisory.severity | ascii_upcase)\t\(.dependency.package.name)\t\(.security_advisory.ghsa_id)\t\(.html_url)"' \
| while IFS=$'\t' read -r sev pkg ghsa url; do
    curl -sf -X POST "$SLACK_WEBHOOK" \
      -H 'Content-Type: application/json' \
      -d "$(jq -n --arg t ":rotating_light: *${sev}* — \`${pkg}\` (${ghsa})\n${url}" '{text: $t}')"
  done

Expected output — one message per unrouted critical or high finding, e.g.:

:rotating_light: *CRITICAL* — `minimist` (GHSA-xvch-5gv4-984h)
https://github.com/OWNER/REPO/security/dependabot/17

The triage decision itself — reachability, whether the vulnerable code path is even imported, re-scoring against your deployment context, and recording the justification for a deferral — is the subject of Vulnerability Tracking & Triage. The output of triage is always one of: an approved update pull request from Step 2, or a documented, dated exception.


Step 5 — Track the Alert-to-Remediation SLA

Permalink to "Step 5 — Track the Alert-to-Remediation SLA"

The purpose of the loop is to bound the time a known vulnerability lives in production. Attach a service level to each severity and measure every open finding against it. The clock starts at advisory publication (created_at on the alert), not at the moment someone noticed.

#!/usr/bin/env bash
# scripts/sla-report.sh — flag alerts that have aged past their SLA
set -euo pipefail

# SLA in days by severity
declare -A SLA=( [critical]=3 [high]=7 [moderate]=30 [low]=90 )
NOW=$(date -u +%s)

gh api "/repos/${GITHUB_REPOSITORY}/dependabot/alerts?state=open" \
  --jq '.[] | "\(.security_advisory.severity)\t\(.created_at)\t\(.dependency.package.name)"' \
| while IFS=$'\t' read -r sev created pkg; do
    age_days=$(( (NOW - $(date -u -d "$created" +%s)) / 86400 ))
    budget=${SLA[$sev]:-90}
    if (( age_days > budget )); then
      echo "BREACH: $pkg ($sev) open ${age_days}d, SLA ${budget}d"
    fi
  done

Expected output — a clean posture prints nothing and exits 0; a breach is explicit:

BREACH: log4js-node (high) open 11d, SLA 7d

Wire this into the same nightly workflow as Step 3 and fail the job (or page an on-call) on any breach. The SLA table below is a defensible default; align the exact numbers with your compliance obligations — mapping CVE severity to a remediation deadline is a control that auditors will ask to see, and the PCI DSS 6.4.3 mapping is covered under Vulnerability Tracking & Triage.

Severity Remediation SLA Escalation on breach
Critical 3 days Page on-call; block next release
High 7 days Notify service owner; add to sprint
Moderate 30 days Track on backlog
Low 90 days Batch into routine maintenance

Configuration Reference

Permalink to "Configuration Reference"

The table maps each moving part of the loop to its cadence, the scope of the dependency graph it inspects, and the signal it emits.

Tool Cadence Scope Signal
Dependabot alerts Continuous (on advisory publish) Full resolved lockfile, incl. transitive Alert + webhook event per new CVE match
Dependabot / Renovate PRs Weekly window; immediate for security Direct + pinned transitive deps Pull request with bumped lockfile
npm audit --audit-level=high Every pull request npm resolved tree Non-zero exit → CI gate failure
osv-scanner Every pull request + nightly cron Lockfile, cross-ecosystem (OSV.dev) Non-zero exit + SARIF findings
SLA report script Nightly cron Open advisory alerts Breach line → page / job failure
Renovate lockFileMaintenance Weekly window Whole lockfile refresh Pull request refreshing transitive pins

Integration with Adjacent Tooling

Permalink to "Integration with Adjacent Tooling"

Continuous monitoring is the feed that keeps the rest of the supply-chain program supplied with current data. Its outputs flow into two adjacent workflows:

  • Vulnerability Tracking & Triage — the destination for every alert this loop raises. Monitoring answers what changed and when; triage answers does it matter here and by when must it be fixed. The SLA table in Step 5 is the contract between the two.
  • Automated SBOM Generation — a Software Bill of Materials is the inventory the advisory feed is matched against. Each time an update pull request merges, regenerate the SBOM so the CycloneDX component list reflects the new resolved versions; a stale SBOM causes the audit and the inventory to disagree, which fails the “maintained inventory” element of most compliance regimes.

Upstream, the loop depends on a stable lockfile. If pins drift or a floating range resolves differently on each install, the advisory feed and the CI audit inspect different trees on different days and the SLA clock becomes meaningless — which is why Dependency Pinning Best Practices is a hard prerequisite rather than an optional companion.


Troubleshooting

Permalink to "Troubleshooting"

Dependabot alerts are enabled but no alerts appear for a package you know is vulnerable

The advisory database is matched against the committed lockfile, not the manifest ranges. If the repository has no lockfile, or the lockfile is .gitignored, Dependabot has nothing to resolve against. Commit the lockfile and re-run the dependency graph via Insights → Dependency graph to force a re-scan.

npm audit reports vulnerabilities that osv-scanner does not, or vice versa

They read different databases. npm audit uses the npm registry advisory endpoint; osv-scanner uses OSV.dev. Advisories are published to one before the other, and OSV covers non-npm ecosystems npm audit ignores. This divergence is expected — run both and treat the union as the finding set, not the intersection.

Renovate opens a security pull request but CI’s npm audit gate still fails on the base branch nightly run

The fix pull request has not merged yet, so the default branch still contains the vulnerable version. The nightly gate is doing its job. Merge the Renovate pull request; the next nightly run will pass. If the fix pull request itself fails CI, the advisory has no non-breaking fix available and the finding must move to a deferred-with-justification state in triage.

npm audit fails the build on a low-severity advisory in a dev-only dependency

npm audit scans devDependencies by default and does not distinguish runtime reachability. Scope the gate with --audit-level=high so build-time-only low/moderate findings are logged but non-blocking, and record the accepted dev-only findings in triage. Do not use npm audit --production alone as a silencer — it hides genuinely reachable build-tool risk.

The SLA report shows a breach for a finding that was already fixed

The alert state is stale. Dependabot marks an alert fixed only after the fix merges to the default branch and the dependency graph re-resolves, which can lag a merge by several minutes. Confirm with gh api /repos/OWNER/REPO/dependabot/alerts/NUMBER --jq .state; if it still reads open an hour after the merge, the merged version does not actually satisfy the advisory’s patched range — re-check the fix.

Automerge is configured but patch updates still wait for manual review

Automerge requires that every branch-protection status check pass and that the platform token has permission to merge. A required check that Renovate’s pull request does not trigger (for example, a check that only runs on push to main, not on pull_request) will block automerge indefinitely. Align the required checks with the events Renovate’s pull requests emit.


Security Boundary

Permalink to "Security Boundary"

Continuous dependency monitoring bounds the time between a vulnerability becoming known and it being fixed. It does not:

  • Detect unknown (zero-day) vulnerabilities. The entire loop is driven by public advisory databases. A malicious change that has not been reported to CVE/GHSA/OSV is invisible to it. Behavioural analysis and provenance checks — see Provenance Verification Workflows — address what advisory scanning cannot.
  • Verify that an update is itself trustworthy. An automated update bot pulls whatever the registry now serves for a version. If the registry account was compromised and a malicious patch published, the bot will happily open a pull request for it. Pin exact versions, keep automerge to patch-level, and pair with provenance verification.
  • Protect assets already delivered to the browser. Monitoring watches the build-time dependency graph. It does nothing about a compromised script served from a CDN at runtime — that is the domain of Subresource Integrity and its byte-level hash check.
  • Confirm a fix is reachable or complete. A merged version-bump closes the advisory record but does not prove the vulnerable code path was ever reachable, nor that the new version has no other open advisory. Reachability and re-scoring belong to triage.
  • Replace a lockfile discipline. If pins float, the tree the scanner inspects differs from the tree that ships. Monitoring assumes a deterministic, committed lockfile as its ground truth.

Frequently asked questions

Permalink to "Frequently asked questions"
What is the difference between Dependabot alerts and Dependabot version updates?

They are two separate features that are often conflated. Dependabot alerts read your lockfile against the GitHub Advisory Database and notify you when a resolved dependency has a known CVE — they detect, they do not change code. Dependabot version updates (configured in dependabot.yml) open pull requests to bump dependencies on a schedule regardless of whether a vulnerability exists. You enable alerts for the detection feed and either Dependabot version updates or Renovate for the remediation feed.

Should I run npm audit in CI if I already have Dependabot alerts?

Yes — they fire at different times. Dependabot alerts are asynchronous and surface after a merge, when the advisory database next matches your lockfile. A CI audit gate is synchronous: it blocks a pull request from introducing a newly-known-vulnerable dependency at merge time. One watches the default branch continuously; the other guards the entry point. A complete loop uses both.

How often should automated update pull requests run?

Security fixes should be raised immediately and independently of any schedule — that is what the vulnerabilityAlerts block with an always-on schedule does. Routine version bumps are best batched into a single weekly window so review load is predictable and grouped. A steady trickle of individual bumps trains reviewers to rubber-stamp, which defeats the point of review.

Does osv-scanner replace npm audit?

Not exactly. npm audit queries the npm registry advisory endpoint and only covers npm. osv-scanner reads OSV.dev, which aggregates advisories across npm, PyPI, Go, Maven, and more, and scans lockfiles directly. For a polyglot repository, osv-scanner is the stronger single choice; running both gives the widest coverage because advisories land in each database at different times.

How do I stop automated update pull requests from overwhelming reviewers?

Group related updates into one pull request with packageRules, restrict the schedule to a single weekly window, enable automerge only for patch-level updates that pass CI, and let the dependency dashboard hold non-urgent bumps in a queue rather than opening them all at once. The Automating Dependency Updates with Renovate page shows each of these settings in a production config.

What starts the alert-to-remediation SLA clock?

Advisory publication, captured as the alert’s created_at timestamp — not the moment a human noticed. Measuring from discovery lets a slow detection feed hide a long exposure window. Anchoring the clock to publication makes the metric honest and is what an auditor will expect to see when reviewing your PCI DSS or SOC 2 remediation timelines.


Permalink to "Related"

Articles in This Topic

Automating Dependency Updates with Renovate
Back to Supply Chain Auditing & Dependency Verification