DevelopmentApril 27, 2026

Automate NPM package security fixes with recurring tasks on CI

When we build with Node.js, we use a huge number of open source NPM packages. Each one brings its own advantages and drawbacks to the project. In this article we'll cover:

  • Free options for detecting vulnerabilities in NPM dependencies
  • Setting up Drone CI to run recurring checks
  • Automatically creating Pull Requests with the fixed packages

NPM audit and a bit more

The first thing that comes to mind when we talk about auditing vulnerabilities is the NPM audit tool. It uses a public vulnerability catalog to review your project and suggests updating library versions to fix any issues it finds. You can find more information on the official NPM blog.

Another useful, free report that comes built in is npm outdated. This command checks the registry to see whether any of the installed packages are out of date. It's not essential information day to day, but it's worth keeping an eye on in the long run so you don't fall into the trap of letting the project go stale.


$ npm outdated
Package Current Wanted Latest Location
glob 5.0.15 5.0.15 6.0.1 test-outdated-output
nothingness 0.0.3 git git test-outdated-output
npm 3.5.1 3.5.2 3.5.1 test-outdated-output
local-dev 0.0.3 linked linked test-outdated-output
once 1.3.2 1.3.3 1.3.3 test-outdated-output

Automatic npm outdated reports

These tools are very useful, but automatic reports are even more so. To set them up we'll use Drone CI (free and open source) and its newer Cron Jobs feature to schedule recurring tasks. You can use any other CI you prefer; it will probably offer the same thing. If you're not familiar with Drone CI, check out this getting started article.

Since Drone CI supports multiple pipelines, each report gets its own and doesn't affect the main one. To see them together, check out the example here. In the meantime, let's start with npm outdated.


kind: pipeline
name: npm outdated
steps:
- name: outdated
image: node:10-alpine
commands:
- npm outdated
- name: slack_notification
image: plugins/slack
settings:
webhook: https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX
template: >
NPN detected outdated packages at *{{repo.name}}* for *{{build.branch}}* branch.
Report available by the link {{build.link}}
when:
status:
- failure
trigger:
cron: [weekly]

The yaml syntax pretty much explains itself. In the first step we use node:10-alpine as the base image and run npm outdated. In the second, the Slack notification is only sent if there's something to update (npm outdated exits with an error status code). To get the Slack webhook URL, visit this page

In the last lines, the whole pipeline is triggered by the Cron Job named "outdated." In our projects we schedule it to run weekly, since we don't plan on updating packages every time a new version comes out.

To define the task in Drone, go to Project -> Settings.

From this interface you can choose the job name (used to filter pipelines), the branch, and the frequency, which can be hourly, daily, weekly, monthly, or yearly.

Automatic npm audit and creating the fix PR

The npm audit command checks your app for vulnerabilities and updates packages to whatever version is needed. The pipeline is very similar to the previous one, but with an extra step to create the PR.


kind: pipeline
name: npm audit
steps:
- name: audit
image: node:10-alpine
commands:
- set -o pipefail && npm audit --force 2>&1 | tee audit.log
- name: audit fix
image: node:10-alpine
commands:
- npm audit fix
when:
status:
- failure
- name: create_fix_pr
image: lnikell/github-hub:2.11.2
environment:
GITHUB_TOKEN:
from_secret: github_token
commands:
- git config --global user.email "email@example.com"
- git config --global user.name "example"
- git checkout -b drone/npm-audit-fix-${DRONE_BUILD_NUMBER}
- git add package.json package-lock.json
- git commit -m 'npm audit fix'
- git push origin drone/npm-audit-fix-${DRONE_BUILD_NUMBER}
- hub pull-request -m "[Security] NPM Audit Fix" -m "$(cat audit.log | tail -2)" -m "${DRONE_BUILD_LINK}"
when:
status:
- failure
- name: slack_notification
image: plugins/slack
settings:
webhook: https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX
template: >
NPN detected vulnerable packages at *{{repo.name}}* for *{{build.branch}}* branch.
Report available by the link {{build.link}}
when:
status:
- failure

In the first step we use the same node:10-alpine image and run NPM audit. We also save the results to an audit.log file to show them later in the PR. If npm audit finds vulnerable packages, the step fails and triggers the npm audit fix process and the pull request creation.


-name: audit fix
image: node:10-alpine
commands:
- npm audit fix
when:
status:
- failure

To create the pull request we use hub, the command-line tool for working with the GitHub API. We need to generate a GitHub Personal Token to use in the API call. Go to this page and create a new one

Select the "repo" permission scope and add the generated token to Drone's secrets under the name "github_token."

It's used as an environment variable in the next step.


- name: create_fix_pr
image: lnikell/github-hub:2.11.2
environment:
GITHUB_TOKEN:
from_secret: github_token
commands:
- git config --global user.email "ci@example.com"
- git config --global user.name "drone"
- git checkout -b drone/npm-audit-fix-${DRONE_BUILD_NUMBER}
- git add package.json package-lock.json
- git commit -m 'npm audit fix'
- git push origin drone/npm-audit-fix-${DRONE_BUILD_NUMBER}
- hub pull-request -m "[Security] NPM Audit Fix" -m "$(cat audit.log | tail -2)" -m "${DRONE_BUILD_LINK}"
when:
status:
- failure

In this step we define the pattern for creating the branch and open a pull request with the last two lines of audit.log. The result is a nicely presented PR:

Finally, let's look at the pipeline's trigger section. Since you only want these checks to run inside the Cron job, add the following:


trigger:
cron: [name_of_the_job]

That said, don't forget about the main pipeline. So it doesn't run during Cron tasks, use the exclude option like this:


trigger:
cron:
exclude: [name_of_the_job]

Conclusion

This is just one example of how useful recurring CI tasks can be for running builds, testing, and fixing issues. You set it up once and get information about your project's security every day or every week. The approach in these examples can easily be adapted to Travis CI or GitLab; if you do, share your pipeline.

Tell us about your case

Get in touch