Composer Audit & npm Audit in CI (15 Min)

By Bug Circuit Security Team
Composer Audit & npm Audit in CI (15 Min)

The fastest way to stop a known-vulnerable dependency from reaching production is a two-line CI step: run npm audit or composer audit right after install, and fail the build above a severity you choose.

This guide is for developers and small-team founders who ship a Node.js or PHP project through CI and want a real security gate, not a nightly email they ignore. In 15 minutes you'll have a build step that checks every dependency against known advisories, fails loudly when something serious shows up, and stays quiet about the noise that doesn't matter yet.

This applies whether you're shipping a Node.js app, a Shopify app built with their CLI, or a PHP project managed with Composer (Laravel, Symfony, or a custom build). If your site is plain WordPress without Composer, this doesn't apply directly, since there's no composer.lock to audit; it's built for projects that use npm or Composer to manage dependencies.

What "audit" actually checks

npm audit compares your installed dependency tree against known advisories and flags matches. composer audit does the same for the packages listed in your composer.lock file. Neither tool scans your own code. They only check whether a version of a package you depend on has a published vulnerability.

The advisory data comes from the GitHub Advisory Database and from OSV.dev, an open vulnerability database. OSV also lists reports of malicious packages, which carry identifiers starting with MAL instead of a CVE number. That's a different problem from a coding bug: someone deliberately published bad code under a package name, and the fix is always "remove it," not "wait for a patch."

An advisory means a flaw was published, not that your site was attacked. Whether it actually matters depends on how your code uses the affected package: a vulnerability in a function you never call is lower priority than one in code that handles user input directly.

The 15-minute npm setup

  1. Make sure CI installs from the lockfile, not a loose npm install. npm ci installs exactly what's in package-lock.json and fails if package.json and the lockfile disagree, so you're always testing what you'll actually ship.
  2. Add the audit step right after install.
name: Dependency audit
on:
  push:
  pull_request:
  schedule:
    - cron: '0 6 * * *'

jobs:
  npm-audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm audit --audit-level=high

That's the whole job. npm audit --audit-level=high exits with a non-zero code only when vulnerabilities at or above "high" are found, which is exactly what makes it usable as a CI gate instead of a wall of warnings. Lower it to moderate if you want to catch more; raise it to critical if your pipeline is drowning in medium-severity findings you haven't triaged yet. Full command reference is in the npm audit documentation.

The 15-minute Composer setup

composer audit ships in Composer 2.4 and later, so confirm your CI image is on at least that version first (composer --version).

name: Dependency audit
on:
  push:
  pull_request:
  schedule:
    - cron: '0 6 * * *'

jobs:
  composer-audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with:
          php-version: '8.2'
      - run: composer install --no-interaction --no-progress
      - run: composer audit

composer install deploys exactly the versions recorded in composer.lock, the same "test what you ship" principle as npm ci. composer audit then checks those locked packages against advisories and exits with a non-zero code when it finds any, which fails the build automatically. Full details are in the Composer CLI documentation.

Exit codes and severity thresholds, in plain terms

A CI job fails when a command exits with a non-zero code. That's the entire mechanism behind both tools.

npmComposer
Install exactly what's lockednpm cicomposer install
Run the auditnpm auditcomposer audit
Choose a severity thresholdnpm audit --audit-level=high (or moderate, critical)not available; any finding fails the build
Apply fixes within allowed version rangesnpm audit fixcomposer update vendor/package
Find what's pulling in a vulnerable packagenpm ls <package>check composer.lock for the requiring package

Because Composer doesn't give you a severity dial, a composer audit failure means "something was found," full stop. That's fine for most small projects, but every finding still needs a quick triage, not an automatic re-run.

What to do when the build fails

Don't just re-run the pipeline and hope it passes. Work through findings in this order:

  1. Try the automatic fix first. npm audit fix applies fixes that stay within the version ranges your package.json already allows, so it won't silently jump you to a breaking major version. There's no Composer equivalent that updates everything at once; update the specific package instead.
  2. Update the specific package. composer update vendor/package updates just that one dependency and leaves everything else locked.
  3. Trace transitive dependencies. If the vulnerable package isn't one you require directly, run npm ls <package> to see which of your direct dependencies pulled it in. You may need to wait on that parent package's own update, or replace it.
  4. If there's genuinely no fix yet, accept the risk in writing. Note which advisory, why it doesn't apply to how your app uses the package, who signed off, and a date to re-check. A silent --force flag or a suppressed warning is how real findings get buried and forgotten.

Why a clean result today doesn't mean clean tomorrow

New advisories get published every day, often for versions of packages that were clean the day before. Your dependency tree didn't change overnight, but the known-vulnerability list did. That's why the audit has to run on every build and on a schedule, like the cron: '0 6 * * *' line in both workflows above, not just the one time someone remembers to check.

If you want to sanity-check a lockfile without wiring up CI yet, a quick way to start is pasting it into a free website security check: only the package names and versions are sent out for comparison against the OSV database, and the lockfile itself never leaves your browser.

Where this fits with a real security review

Dependency audits are automated pattern-matching against known, published flaws. They won't catch a logic bug in your own code, a misconfigured server, or a vulnerability nobody's reported yet. That's the gap between automated scanning and a manual penetration test: a human reviewer tests how your specific application actually behaves, not just which package versions it lists.

If your site is already showing signs of compromise, start with what to do when your website's been hacked instead of a dependency audit, since the two problems need different first steps.

Key takeaways

  • Add npm audit --audit-level=high (or composer audit) right after npm ci / composer install in every CI run, and also on a daily schedule, since new advisories appear for versions that were clean yesterday.
  • A non-zero exit code is the whole mechanism: npm lets you set a severity floor, Composer fails on any finding.
  • Work findings in order: automatic fix, update the specific package, trace the parent with npm ls <package>, or document an accepted risk with a reason and a review date.
  • An advisory is a published flaw, not proof your site was attacked; check whether your code actually exercises the vulnerable path before panicking.
  • Automated audits catch known, published issues in dependencies only; they're not a substitute for a human reviewing your actual application.

A 15-minute CI gate closes off one real risk: shipping a dependency with a known, published hole. It won't tell you about a bug in your own code or a misconfigured setting, which is exactly what a $49 manual audit is built to catch. Worth doing both.

Want certainty, not guesswork?

A real human security engineer audits your whole site by hand and sends a full report: every issue, its severity, and the exact fix. From $49, with a 14-day money-back guarantee.

See pricing

Frequently asked questions

Is composer audit safe to run in CI?
Yes. composer audit only reads your composer.lock file and compares package versions against known advisories; it doesn't change anything in your project. It exits with a non-zero code when it finds a match, which fails the build, so you can add it as a normal step right after composer install. It requires Composer 2.4 or later.
How do I run npm audit in CI?
Install from the lockfile with npm ci, then run npm audit --audit-level=high (or moderate or critical) right after. The --audit-level flag makes npm audit exit with a non-zero code only at or above the severity you pick, which is what turns it into a usable CI gate instead of failing on every low-severity finding.
Will npm audit fix break my app?
npm audit fix only applies updates that stay within the version ranges already allowed by your package.json, so it shouldn't jump you to a breaking major version on its own. Still, run your test suite after it, since even a minor or patch bump can occasionally change behavior your app was relying on.
Does composer audit work in GitHub Actions?
Yes. Install PHP and Composer in the job (for example with the shivammathur/setup-php action), run composer install, then run composer audit. If it finds a matching advisory for a package in composer.lock, it exits with a non-zero code and the job fails automatically.
What does a dependency vulnerability scan in CI actually check?
It compares the exact versions of the packages your lockfile specifies against advisory databases like the GitHub Advisory Database and OSV, which also tracks reports of malicious packages with IDs starting with MAL. It only flags known, published issues in your dependencies, not bugs in your own code.

Keep reading

See what attackers see, for free

Run the free passive check on your website. No login, no impact on your site, results in seconds.

Ready for the full manual audit? See pricing

Published by Bug Circuit. Written with AI assistance and reviewed for accuracy before publishing.