For AI agents: a documentation index is available at the root level at /llms.txt and /llms-full.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
Sign inTry it free
DocsGuidesSDKsIntegrationsAPI docsTutorialsFlagship blog
DocsGuidesSDKsIntegrationsAPI docsTutorialsFlagship blog
  • Flagship blog
    • 52 Blog Posts, Claude, 3 Prompts, Under an Hour
    • Shipping from Oakland: An Observability Hackathon Recap
    • Day 12 | New Year, New Observability
    • Day 11 | What engineering teams really want from Observability
    • Day 10 | Why observability and feature flags go together like milk and cookies
    • Day 9 | The Three Ghosts Haunting Your AI This Holiday Season
    • Day 8 | Observable Multi-Modal Agentic Systems
    • Day 7 | SLOs that actually drive decisions
    • Day 6 | Stop cardinality from stealing your cloud budget
    • Day 5 | Using a Popular Tidying Method to Consolidate Your Observability Stack
    • Day 4 | Tracing the impact of feature flags in your Node.js app
    • Day 3 | Zero-Config Observability with OpenTelemetry
    • Day 2 | Why AI agents need three layers of observability
    • Day 1 | Observability Under the Tree: What Changed in 2025
    • 5 takeaways from my first PyCon JP conference
    • Dungeons & Downtimes: XP gained from our adventure
    • Reverse Proxy for custom domains
    • Adventures in dogfooding: Guarded Releases
    • A quick tool for npm package scanning
    • My DEF CON 33 experience
    • Make every launch a big deal
    • Fun with JS streams
    • Moonshots XXII: Hack to the Future recap
    • A tale of three rate limiters
    • My good friend Claude
    • My approach to React app architecture in 2025
    • Data isolation with ClickHouse row policies
    • Ingest and Visualization for OpenTelemetry Metrics
    • Alert Evaluations: Incremental Merges in ClickHouse
    • Optimizing ClickHouse: The Tactics That Worked for Us
    • Migrating from OpenSearch to ClickHouse
    • Revamping Privacy Mode: A Better Way to Obfuscate Sensitive Data
    • An open-source session replay benchmark
    • LLM-based Grouping of Errors
    • Building GitHub Enhanced Stacktraces
    • Vercel Edge Runtime Support
    • Finding Interesting Sessions with Markov Chains
    • Building Logging Integrations at LaunchDarkly
    • The Network Request Details Panel
    • Using Github as a Headless CMS
    • Your Source Maps Should Be Public
    • Supporting Outside Contributions at LaunchDarkly
    • Managing our design tokens at LaunchDarkly
    • Our Commitment to OpenTelemetry
    • The 5 Best Logging Libraries for Ruby
    • InfluxDB: Visualizing Millions of Customers' Metrics using a Time Series Database
    • 8 Tips to Help You Maximize Chrome DevTools
    • The Debugging Process and Techniques for Web Applications (Part 2/2)
    • 5 Best Node.js Logging Libraries
    • What are rage clicks and how to detect them
    • 5 Best Practices for Maintaining a Clean ReactJS App
    • Is Kafka the Key? The Evolution of LaunchDarkly's Ingest
    • What Is Full Stack Monitoring and How Does It Work?
    • The beauty of contact-first API design
    • What is Frontend Monitoring and What Tools Help You Do It?
    • 5 strategies to monitor the health of your web application
    • Configuring OpenSearch for a Write-Heavy Workload
    • Maximizing Our Machines: Worker Pools At LaunchDarkly
Sign inTry it free
LogoLogo
On this page
  • tl;dr: npm stands for nerfed package manager
  • How to scan for compromised packages
  • Make a local copy of the script
  • Run the script
  • Conclusion
Flagship blog

A quick tool for npm package scanning

Was this page helpful?
Previous

My DEF CON 33 experience

Next
Built with

Published September 15th, 2025

Portrait of Patrick Kaeding.

Patrick Kaeding, LaunchDarkly Engineer

tl;dr: npm stands for nerfed package manager

In case you missed it, two popular npm packages (chalk and debug) were compromised last week.

I wrote a short script for quickly testing if a repo depends on any of the affected libraries. It makes use of CycloneDX to create a Software Bill of Materials (SBOM), and then queries that SBOM using jq. Let’s walk throug how to use it.

How to scan for compromised packages

Make a local copy of the script

Copy the following code into a file named node-guard.sh:

$#!/usr/bin/env bash
$# sbom-node-guard.sh
$# - Generates a CycloneDX SBOM for Node.js projects using cdxgen in Docker
$# - Scans the SBOM for specific package@version pairs with jq
$# - Exits non-zero if ANY listed pair is present
$
$set -uo pipefail
$
$# -------- Config --------
$DOCKER_IMAGE="${DOCKER_IMAGE:-ghcr.io/cyclonedx/cdxgen:v11.7.0}"
$OUTPUT_BOM="${OUTPUT_BOM:-bom.json}" # You can override with env var if you like
$PROJECT_DIR="${PROJECT_DIR:-$PWD}"
$# ------------------------
$
$error() { printf "ERROR: %s\n" "$*" >&2; }
$info() { printf "INFO: %s\n" "$*\n" >&2; }
$
$# Prereqs
$command -v docker >/dev/null 2>&1 || { error "docker not found in PATH"; exit 2; }
$command -v jq >/dev/null 2>&1 || { error "jq not found in PATH"; exit 2; }
$
$# Detect NodeJS project (common markers)
$if [ ! -f "$PROJECT_DIR/package.json" ] \
> && [ ! -f "$PROJECT_DIR/package-lock.json" ] \
> && [ ! -f "$PROJECT_DIR/yarn.lock" ] \
> && [ ! -f "$PROJECT_DIR/pnpm-lock.yaml" ] \
> && [ ! -f "$PROJECT_DIR/bun.lockb" ]; then
$ info "No Node.js project detected. Doing nothing."
$ exit 0
$fi
$
$info "Node.js project detected. Creating SBOM with cdxgen…"
$
$# Ensure we can write the BOM file with host UID/GID
$USER_FLAG=()
$if command -v id >/dev/null 2>&1; then
$ USER_FLAG=( -u "$(id -u)":"$(id -g)" )
$fi
$
$# Generate SBOM (CycloneDX JSON) with -t nodejs
$# cdxgen writes relative to the working directory we mount as /app
$docker run --rm \
> "${USER_FLAG[@]}" \
> -v "$PROJECT_DIR":/app \
> -w /app \
> "$DOCKER_IMAGE" \
> -t nodejs -o "$OUTPUT_BOM" >/dev/null
$
$BOM_FILE="$PROJECT_DIR/$OUTPUT_BOM"
$if [ ! -s "$BOM_FILE" ]; then
$ error "SBOM file not created or empty: $BOM_FILE"
$ exit 3
$fi
$
$info "SBOM created at: $BOM_FILE"
$info "Scanning for disallowed package versions…"
$
$# List of "name|version" to check
$declare -a CHECKS=(
> "ansi-styles|6.2.2"
> "debug|4.4.2"
> "chalk|5.6.1"
> "supports-color|10.2.1"
> "strip-ansi|7.1.1"
> "ansi-regex|6.2.1"
> "wrap-ansi|9.0.1"
> "color-convert|3.1.1"
> "color-name|2.0.1"
> "is-arrayish|0.3.3"
> "slice-ansi|7.1.1"
> "color|5.0.1"
> "color-string|2.1.1"
> "simple-swizzle|0.2.3"
> "supports-hyperlinks|4.1.1"
> "has-ansi|6.0.1"
> "chalk-template|1.1.1"
> "backslash|0.2.1"
>)
$
$FOUND=0
$
$for pair in "${CHECKS[@]}"; do
$ NAME="${pair%%|*}"
$ VERSION="${pair##*|}"
$
$ # Run the exact jq selection you specified (semantically identical)
$ RESULT="$(jq -c '.components[] | select(.name == "'"$NAME"'" and .version == "'"$VERSION"'")' "$BOM_FILE")"
$
$ if [ -n "$RESULT" ]; then
$ printf "FOUND: %s@%s in SBOM\n" "$NAME" "$VERSION" >&2
$ echo "$RESULT" | jq . >&2
$ FOUND=1
$ fi
$done
$
$if [ "$FOUND" -ne 0 ]; then
$ error "One or more disallowed packages were found."
$ exit 1
$fi
$
$info "No disallowed package versions found."
$exit 2

Run the script

Run the script by using the following command:

$cd /path/to/maybe/infected/project && /path/to/sbom-node-guard.sh

If it is not a Node.js project, the script will exit with code 0, after logging:

$ No Node.js project detected. Doing nothing.

If no compromised package versions are detected, the script will exit with code 0 and output logs similar to the following:

$INFO: Node.js project detected. Creating SBOM with cdxgen…\n
$INFO: SBOM created at: /path/to/maybe/infected/project/bom.json\n
$INFO: Scanning for disallowed package versions…\n
$INFO: No disallowed package versions found.\n

If an affected package was found, the script will exit with code 1 and output logs similar to the following (note that this is flagging version 4.4.1 of debug, which is not actually the compromised version— this is for testing/illustrative purposes):

$INFO: Node.js project detected. Creating SBOM with cdxgen…\n
$INFO: SBOM created at: /path/to/maybe/infected/project/bom.json\n
$INFO: Scanning for disallowed package versions…\n
$FOUND: debug@4.4.1 in SBOM
${
> "group": "",
> "name": "debug",
> "version": "4.4.1",
> "scope": "optional",
> "hashes": [
> {
> "alg": "SHA-512",
> "content": "8e2709b2144f03c7950f8804d01ccb3786373df01e406a0f66928e47001cf2d336cbed9ee137261d4f90d68d8679468c755e3548ed83ddacdc82b194d2468afe"
> }
> ],
> "purl": "pkg:npm/debug@4.4.1",
> "type": "library",
> "bom-ref": "pkg:npm/debug@4.4.1",
> "properties": [
> {
> "name": "SrcFile",
> "value": "yarn.lock"
> }
> ],
> "evidence": {
> "identity": [
> {
> "field": "purl",
> "confidence": 1,
> "methods": [
> {
> "technique": "manifest-analysis",
> "confidence": 1,
> "value": "yarn.lock"
> }
> ],
> "concludedValue": "yarn.lock"
> }
> ]
> }
>}
$ERROR: One or more disallowed packages were found.

If you have a lot of projects to run this on, you can use something like all repos to automate running this script against all your repositories.

Conclusion

Supply chain security has been neglected for too long in the history of the tech industry. It became more important after the SolarWinds cyber attack. The SLSA Framework is the best model we have for thinking about supply chain security, but it doesn’t really address dependency threats.

We must focus on preventing/detecting maliciously-modified dependencies, or managing vulnerabilities in dependencies, since those would be brought into the final software being built. SBOMs help in this regard, as they serve to document what versions of what dependencies go into the software. They can be used with SLSA to make a lot of progress.

Open source maintainers are the target of increasingly sophisticated social engineering attacks, and we must be vigilant. Every software that team relies on open source should be package scanning. Here is one more tool for your toolbox. Feel free to use it the next time a vulnerability arises.