Skip to content

False positives in Node.js image vulnerability scans? Separate app packages from npm built-ins first

Mar 20, 2026 1 min
TL;DR When reviewing vulnerability scan results for a Node.js Docker image, you can't just look at package names. First distinguish between project dependencies and the packages bundled with npm inside the base image — otherwise you'll fix the wrong thing.

🌏 中文版

TL;DR

Upgrading package.json doesn’t mean your Docker image is clean. Vulnerability scan results for Node.js images typically fall into at least two categories: your project’s own packages, and packages bundled with npm inside the base image. The glob, minimatch, tar, and diff hits in this scan weren’t coming from the app’s node_modules at all — they were brought in by npm@10.9.4, which is bundled inside node:22-alpine. The fix isn’t just updating package.json; you also need to upgrade npm inside the image to 11.11.1.

Bottom Line First

When you hit a Node.js image vulnerability scan, don’t immediately suspect the lockfile. Work through this order first:

  1. Check the app dependency tree
  2. Then check npm’s own global dependency tree inside the container
  3. Only then decide whether to update package.json / the lockfile, or the Dockerfile

This quickly tells you whether you’re dealing with an application package issue or a base image toolchain issue.

Context

While cleaning up vulnerability scan results for a Node.js project, I upgraded everything possible in package.json and yarn.lock — both runtime and dev dependencies.

The project side looked clean:

  • package.json updated to newer dependency versions
  • yarn.lock regenerated
  • No old versions of the flagged diff, glob, or tar packages visible in the local dependency tree

But after rebuilding the Docker image and scanning again, the same findings kept appearing:

  • diff@5.2.0
  • glob@10.4.5
  • minimatch@9.0.5
  • tar@6.2.1
  • tar@7.4.3

The Problem

It looked like “the packages just didn’t upgrade” — but the project dependency tree didn’t match what the image scanner was finding.

If you only stare at package.json and yarn.lock, it’s easy to get stuck here:

npm ls diff glob minimatch tar --all --depth=6

Running this in the project shows very few results, and versions that don’t match what the vulnerability report listed — yet the image scan is clearly reporting that those vulnerable versions exist inside the container.

Investigation

In this kind of situation, I now always split the investigation into two layers.

Layer 1 — app packages:

  • Check package.json
  • Check yarn.lock
  • Check the actual dependency tree in node_modules

Layer 2 — npm’s own package tree:

  • Check the Node / npm version in the base image
  • Inspect the global npm dependency tree directly inside the container

The base image used here was:

FROM node:22-alpine

Running this inside the container made things clear immediately:

docker run --rm node:22-alpine sh -lc "node -v && npm -v && npm ls -g diff glob minimatch tar --all --depth=6 || true"

The output matched the vulnerability report exactly:

v22.22.1
10.9.4

/usr/local/lib
└─┬ npm@10.9.4
  ├── glob@10.4.5
  ├── minimatch@9.0.5
  ├── tar@6.2.1
  ├── tar@7.4.3
  └── diff@5.2.0

The scan hits weren’t from the app’s installed dependencies at all — they were coming from the built-in npm package tree inside the base image.

Fix

Since the root cause is npm itself, upgrading project dependencies alone isn’t enough. You also need to upgrade npm inside the image.

Here’s what was added to the Dockerfile:

FROM node:22-alpine

RUN apk update && apk upgrade --available && sync
RUN apk add --update ca-certificates openssl && update-ca-certificates

# Upgrade npm to fix bundled tar CVEs (tar@6.x bundled in npm@10, fixed in npm@11.11.1+)
RUN npm install -g npm@11.11.1

After upgrading, verify it worked:

docker run --rm <your-image> sh -lc "npm -v && npm ls -g tar --depth=2 || true"

You should see something like:

11.11.1
/usr/local/lib
└─┬ npm@11.11.1
  └── tar@7.5.11

At this point, the old tar@6.x is gone from the image, and the vulnerability findings will actually disappear.

Why This Happens

Docker image vulnerability scanners don’t just scan your application directory — they scan the entire filesystem for installed packages.

For Node.js images, there are at least two common sources:

  1. Project dependencies From package.json / lockfile / node_modules

  2. Base image toolchain Things like npm itself, and npm’s internal dependencies: glob, minimatch, tar, diff

So seeing tar in the scan doesn’t necessarily mean some library in your project brought it in. It might just be npm’s own internal dependency it uses to handle package archives.

Without separating these two categories upfront, you’ll likely run into one of two false assumptions:

  • The app packages are already upgraded, but you keep suspecting the lockfile isn’t up to date
  • The problem is in the base image, but you keep blindly adding overrides / resolutions to package.json

Quick Triage Flow

Next time I hit something similar, here’s the order I’ll follow:

  1. Check package.json and the lockfile — confirm whether the project is actually pinned to an old version
  2. Run the app dependency tree — confirm whether the issue is coming from node_modules
  3. Run npm ls -g ... directly inside the base image or target image
  4. If the hit is from npm’s built-in dependencies, fix the Dockerfile
  5. If the hit is from an app dependency, then go back and update the package version or lockfile

Key Takeaway

When reviewing Node.js Docker image vulnerability scans, ask yourself first: Is this vulnerability in an app package, or in npm’s own packages? Identify the source before deciding whether to fix package.json or the Dockerfile. It’s faster, and you’re much less likely to fix the wrong thing.

References