Detecting for CVE-2025-55182/66478 vulnerabilities

How I built a critical vulnerability scanner for React Server Components RCE (CVE-2025-55182/66478) using pure Bash, semantic versioning, and a lot of regex. When npm audit isn't enough, you build your own.

Read time is about 22 minutes

Alexander Garcia is an effective JavaScript Engineer who crafts stunning web experiences.

Alexander Garcia is a meticulous Web Architect who creates scalable, maintainable web solutions.

Alexander Garcia is a passionate Software Consultant who develops extendable, fault-tolerant code.

Alexander Garcia is a detail-oriented Web Developer who builds user-friendly websites.

Alexander Garcia is a passionate Lead Software Engineer who builds user-friendly experiences.

Alexander Garcia is a trailblazing UI Engineer who develops pixel-perfect code and design.

The early morning Slack ping

It's December 3rd, 2025. The React team just published a critical security advisory: CVE-2025-55182 / CVE-2025-66478 a Remote Code Execution vulnerability in React Server Components with a CVSS score of 10.0 (Critical). My security-minded CTO asked if we were affected by it early that morning, I made a cup of coffee and started reading.

HOLY CRAP! You know what that means? Every Next.js app using React 19.x could be vulnerable. Including mine. Including yours. Including that side project you haven't touched in 3 months.

So I did what any reasonable developer would do I told my CTO to let me review the CVE while I wrote a 319-line Bash script to check 1. If our projects were vulnerable (they were not) and 2. If my projects were vulnerable (they were ๐Ÿ˜ฑ)

Can't I just use npm audit?

Great question! Here's what npm audit told me:

$ npm audit found 0 vulnerabilities

Cool, cool. Except my project was vulnerable. The issue?

  1. npm audit only checks known vulnerabilities in the npm registry - CVEs this fresh (reported Nov 29, disclosed Dec 3) might not be indexed yet
  2. Version resolution is complex - Next.js 15.1.3 is vulnerable, but 15.1.9 is patched. npm audit doesn't always catch these granular ranges
  3. Multiple affected packages - The vulnerability exists in react-server-dom-webpack, react-server-dom-parcel, and react-server-dom-turbopack versions 19.0, 19.1.0, 19.1.1, and 19.2.0, but also affects frameworks like Next.js, React Router, Waku, and more
  4. Canary versions - Next.js canary builds โ‰ฅ14.3.0-canary.77 are affected, which npm audit doesn't handle well

So I needed something more precise. Something that could:

  • Check exact version ranges for multiple packages
  • Handle semantic versioning including canaries, RCs, and prereleases
  • Provide actionable remediation steps
  • Work offline without depending on npm's vulnerability database
  • Run in CI/CD pipelines to prevent vulnerable deployments

Time to build.

The Architecture (yes, Bash can have architecture)

Here's the game plan:

1. Parse package.json (with and without jq) 2. Extract installed versions from package-lock.json or node_modules 3. Compare versions using semantic versioning logic 4. Check against known vulnerable version ranges 5. Output colored, actionable results 6. Exit with proper status codes for CI/CD

Simple, right? Well...

Challenge #1: Semantic Version Comparison in Bash

This was the hardest part. I needed to compare versions like:

  • 15.1.3 < 15.1.9 โœ… (vulnerable)
  • 15.1.9 >= 15.1.9 โœ… (patched)
  • 14.3.0-canary.77 < 15.6.0-canary.58 โœ… (vulnerable canary)
  • 19.0.0 vs 19.0 (they're the same!)

Bash doesn't have native semantic versioning. So I wrote my own:

version_compare() { local ver1=$1 local op=$2 local ver2=$3 # Remove 'v' prefix if present ver1=${ver1#v} ver2=${ver2#v} # Extract prerelease info (canary, rc, etc.) local ver1_main=$(echo "$ver1" | cut -d'-' -f1) local ver1_pre=$(echo "$ver1" | cut -d'-' -f2) local ver2_main=$(echo "$ver2" | cut -d'-' -f1) local ver2_pre=$(echo "$ver2" | cut -d'-' -f2) # Compare main version numbers IFS='.' read -ra VER1 <<< "$ver1_main" IFS='.' read -ra VER2 <<< "$ver2_main" for i in 0 1 2; do local v1=${VER1[$i]:-0} local v2=${VER2[$i]:-0} if [ "$v1" -gt "$v2" ]; then [ "$op" = ">" ] || [ "$op" = ">=" ] && return 0 [ "$op" = "<" ] || [ "$op" = "<=" ] && return 1 elif [ "$v1" -lt "$v2" ]; then [ "$op" = "<" ] || [ "$op" = "<=" ] && return 0 [ "$op" = ">" ] || [ "$op" = ">=" ] && return 1 fi done # Versions are equal in main numbers [ "$op" = "=" ] || [ "$op" = ">=" ] || [ "$op" = "<=" ] && return 0 return 1 }

This handles:

  • Different version formats (19.0 vs 19.0.0)
  • Prerelease versions (-canary, -rc, -beta)
  • All comparison operators (<, >, >=, <=, =)
  • Missing patch/minor versions (defaults to 0)

Challenge #2: Next.js versioning is interesting

The vulnerability affects specific Next.js version ranges:

  • 15.0.0 to 15.0.4 (vulnerable, patch to 15.0.5)
  • 15.1.0 to 15.1.8 (vulnerable, patch to 15.1.9)
  • 15.2.0 to 15.2.5 (vulnerable, patch to 15.2.6)
  • 15.3.0 to 15.3.5 (vulnerable, patch to 15.3.6)
  • 15.4.0 to 15.4.7 (vulnerable, patch to 15.4.8)
  • 15.5.0 to 15.5.6 (vulnerable, patch to 15.5.7)
  • 16.0.0 to 16.0.6 (vulnerable, patch to 16.0.7)

But also: Next.js canary versions โ‰ฅ14.3.0-canary.77 are vulnerable and should be downgraded to stable 14.x!

Here's how I handled it:

check_nextjs_vulnerable() { local version=$1 local main_version=$(echo "$version" | cut -d'-' -f1) # Check for canary versions >= 14.3.0-canary.77 if [[ "$version" == *"canary"* ]]; then if version_compare "$main_version" ">=" "14.3.0"; then local canary_num=$(echo "$version" | grep -oP 'canary\.\K\d+' || echo "0") if [ "$canary_num" -ge 77 ] && ! version_compare "$version" ">=" "15.6.0-canary.58"; then return 0 # Vulnerable canary fi fi fi # Check vulnerable stable versions if version_compare "$main_version" ">=" "15.0.0" && version_compare "$main_version" "<" "15.0.5"; then return 0 fi if version_compare "$main_version" ">=" "15.1.0" && version_compare "$main_version" "<" "15.1.9"; then return 0 fi # ... more ranges ... return 1 # Not vulnerable }

The canary logic is wild:

  1. Extract the canary number from 14.3.0-canary.77 (result: 77)
  2. Check if it's >= 77 (the first vulnerable canary)
  3. Per React's advisory, vulnerable canaries should downgrade to stable 14.x
  4. This required grep -oP (Perl regex) because Bash regex is... not great

Challenge #3: Finding the actual installed version

Here's a fun gotcha: package.json might say "next": "^15.1.0", but what version is actually installed?

Could be 15.1.0. Could be 15.1.3 (vulnerable). Could be 15.1.9 (patched).

I needed to check three places:

# 1. Try package-lock.json with jq if [ -f "package-lock.json" ] && command -v jq &> /dev/null; then INSTALLED_VERSION=$(jq -r '.packages["node_modules/next"].version // empty' package-lock.json) fi # 2. Try node_modules/next/package.json if [ -f "node_modules/next/package.json" ]; then INSTALLED_VERSION=$(jq -r '.version' node_modules/next/package.json 2>/dev/null || echo "") fi # 3. Fall back to the version in package.json (cleaned) CLEAN_VERSION=$(echo "$NEXTJS_VERSION" | sed 's/[\^~>=<]//g')

This ensures we're checking the real installed version, not just what's declared.

Challenge #4: Working without jq

Not every system has jq installed. So I added fallback parsing:

if command -v jq &> /dev/null; then # Use jq (fast, reliable) NEXTJS_VERSION=$(jq -r '.dependencies.next // .devDependencies.next // "not-found"' package.json) else # Fall back to grep + regex (slower, but works everywhere) NEXTJS_VERSION=$(grep -A1 '"next"' package.json | grep -oP '"\K[^"]+' | tail -1 || echo "not-found") fi

The grep version:

  1. Find the line with "next"
  2. Get the next line (-A1)
  3. Extract the quoted version string
  4. Take the last match (in case there are multiple)

It's not pretty, but it works on every Unix system.

Challenge #5: Actionable output that looked good

Security scanners that just say "VULNERABLE" are useless. I needed:

  • Color-coded output (red for vulnerable, green for safe)
  • Clear vulnerability descriptions
  • Specific remediation steps
  • Links to security advisories

Here's the money shot:

RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color if [ ${#WARNINGS[@]} -gt 0 ]; then echo -e "${RED}VULNERABILITIES DETECTED:${NC}" echo "" for warning in "${WARNINGS[@]}"; do echo -e "$warning" done echo "" echo -e "${RED}โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•${NC}" echo -e "${RED}STATUS: VULNERABLE${NC}" echo -e "${RED}โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•${NC}" echo "" echo -e "${YELLOW}Recommended Actions:${NC}" echo "1. Update Next.js to a patched version (15.0.5+ or appropriate version)" echo "2. Update React to a patched version (19.0.1, 19.1.2, or 19.2.1)" echo "3. Update all react-server-dom-* packages to patched versions" echo "4. For Next.js canary โ‰ฅ14.3.0-canary.77, downgrade to stable 14.x" echo "5. Run: npm audit fix --force (or yarn upgrade)" echo "" echo -e "${YELLOW}References:${NC}" echo "- https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components" echo "- https://github.com/vercel/next.js/security/advisories/GHSA-9qr9-h5gf-34mp" echo "" echo -e "${YELLOW}Note:${NC}" echo "- Reported: November 29, 2025 (Lachlan Davidson via Meta Bug Bounty)" echo "- Disclosed: December 3, 2025" echo "- Attack vector: Malicious HTTP requests to Server Function endpoints" echo "- Impact: Unauthenticated RCE via deserialization flaw" exit 1 else echo -e "${GREEN}โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•${NC}" echo -e "${GREEN}STATUS: NOT VULNERABLE${NC}" echo -e "${GREEN}โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•${NC}" exit 0 fi

The output looks like this:

======================================== React Server Components RCE Checker CVE-2025-55182 / CVE-2025-66478 ======================================== Checking package.json... โ„น Next.js found: ^15.1.3 โ„น Installed Next.js version: 15.1.3 โœ— VULNERABLE: Next.js 15.1.3 is affected Patch to: 15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7, 16.0.7, or 15.6.0-canary.58+ โ„น React found: ^19.1.0 โ„น Installed React version: 19.1.0 โœ— VULNERABLE: React 19.1.0 is affected Patch to: 19.0.1, 19.1.2, or 19.2.1 โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• STATUS: VULNERABLE โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• Recommended Actions: 1. Update Next.js to a patched version (15.0.5+ or appropriate version) 2. Update React to a patched version (19.0.1, 19.1.2, or 19.2.1) ...

Clean. Actionable. Impossible to miss. ๐Ÿ˜Ž

Using the script in CI/CD

The script exits with status code 1 if vulnerabilities are found, 0 if safe. This makes it perfect for CI/CD:

# .github/workflows/security.yml name: Security Check on: [push, pull_request] jobs: check-cve: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Check for CVE-2025-55182/66478 run: | curl -O https://raw.githubusercontent.com/asg5704/check-cve/main/check-cve-2025-66478.sh chmod +x check-cve-2025-66478.sh ./check-cve-2025-66478.sh

If the build is vulnerable, the CI pipeline fails. Simple.

What I learned

1. Bash is pretty useful anywhere

I've written TypeScript apps, Python scripts, Rust macros. But sometimes a Bash script is the right tool:

  • โœ… Runs everywhere (no runtime dependencies)
  • โœ… Fast (no compilation, no startup time)
  • โœ… Easy to distribute (just curl and chmod +x)
  • โœ… Perfect for CI/CD integration

2. Semantic Versioning is difficult

I thought version comparison would be trivial. Nope. Edge cases everywhere:

  • Prerelease versions
  • Missing patch numbers
  • Leading zeros
  • v-prefixes
  • Canary/RC/beta naming schemes

If you're building something that compares versions, test the hell out of it!

3. Security can't wait for the perfect solution

I could have:

  • Built a Node.js CLI with proper package management
  • Created a web service with a database
  • Waited for npm audit to catch up

But vulnerabilities don't wait especially with it being actively exploited. Sometimes you need a quick, reliable solution right now. This script:

  • Works offline
  • Has zero dependencies (except optional jq)
  • Runs in seconds
  • Can be audited in minutes (it's just Bash)

Perfect is the enemy of good. Especially in security.

4. Good error messaging = Better user/developer experience

Half the script is dedicated to output formatting and error messages. Because:

  • Developers need to know what is vulnerable
  • They need to know why it's vulnerable
  • They need to know how to fix it
  • They need links to official documentation

A security tool that just says "FAIL" is worse than useless it's frustrating.

The results

After running this on my projects:

Personal portfolio: โœ… Safe (didn't use Next.js and using React 18.x) The Tiki Social: โŒ Vulnerable (Next.js 15.1.3, React 19.1.0) Client project #1: โŒ Vulnerable (Next.js 15.0.2) Client project #2: โœ… Safe (using React 18.x)

Fixed all vulnerable projects within an hour. Updated dependencies, ran tests, deployed.

That 9AM coffee was worth it.

Try it yourself

The script is available on GitHub:

Bash script
#!/bin/bash # CVE-2025-55182 / CVE-2025-66478: RCE in React Server Components Checker # CVSS Score: 10.0 (Critical) # Checks for vulnerable React, Next.js, React Router, Waku, Expo, and react-server-dom packages set -e RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color echo -e "${BLUE}========================================${NC}" echo -e "${BLUE}React Server Components RCE Checker${NC}" echo -e "${BLUE}CVE-2025-55182 / CVE-2025-66478${NC}" echo -e "${BLUE}========================================${NC}" echo "" VULNERABLE=false WARNINGS=() INFO=() # Function to compare semantic versions version_compare() { local ver1=$1 local op=$2 local ver2=$3 # Remove 'v' prefix if present ver1=${ver1#v} ver2=${ver2#v} # Extract prerelease info (canary, rc, etc.) local ver1_main=$(echo "$ver1" | cut -d'-' -f1) local ver1_pre=$(echo "$ver1" | cut -d'-' -f2) local ver2_main=$(echo "$ver2" | cut -d'-' -f1) local ver2_pre=$(echo "$ver2" | cut -d'-' -f2) # Compare main version numbers IFS='.' read -ra VER1 <<< "$ver1_main" IFS='.' read -ra VER2 <<< "$ver2_main" for i in 0 1 2; do local v1=${VER1[$i]:-0} local v2=${VER2[$i]:-0} if [ "$v1" -gt "$v2" ]; then [ "$op" = ">" ] || [ "$op" = ">=" ] && return 0 [ "$op" = "<" ] || [ "$op" = "<=" ] && return 1 elif [ "$v1" -lt "$v2" ]; then [ "$op" = "<" ] || [ "$op" = "<=" ] && return 0 [ "$op" = ">" ] || [ "$op" = ">=" ] && return 1 fi done # Versions are equal in main numbers [ "$op" = "=" ] || [ "$op" = ">=" ] || [ "$op" = "<=" ] && return 0 return 1 } # Function to check if Next.js version is vulnerable check_nextjs_vulnerable() { local version=$1 local main_version=$(echo "$version" | cut -d'-' -f1) # Check for canary versions >= 14.3.0-canary.77 if [[ "$version" == *"canary"* ]]; then if version_compare "$main_version" ">=" "14.3.0"; then local canary_num=$(echo "$version" | grep -oP 'canary\.\K\d+' || echo "0") if [ "$canary_num" -ge 77 ] && ! version_compare "$version" ">=" "15.6.0-canary.58"; then return 0 # Vulnerable canary fi fi fi # Check vulnerable stable versions if version_compare "$main_version" ">=" "15.0.0" && version_compare "$main_version" "<" "15.0.5"; then return 0 fi if version_compare "$main_version" ">=" "15.1.0" && version_compare "$main_version" "<" "15.1.9"; then return 0 fi if version_compare "$main_version" ">=" "15.2.0" && version_compare "$main_version" "<" "15.2.6"; then return 0 fi if version_compare "$main_version" ">=" "15.3.0" && version_compare "$main_version" "<" "15.3.6"; then return 0 fi if version_compare "$main_version" ">=" "15.4.0" && version_compare "$main_version" "<" "15.4.8"; then return 0 fi if version_compare "$main_version" ">=" "15.5.0" && version_compare "$main_version" "<" "15.5.7"; then return 0 fi if version_compare "$main_version" ">=" "16.0.0" && version_compare "$main_version" "<" "16.0.7"; then return 0 fi return 1 # Not vulnerable } # Function to check if React version is vulnerable check_react_vulnerable() { local version=$1 local main_version=$(echo "$version" | cut -d'-' -f1) # Vulnerable React versions: 19.0.0, 19.1.0, 19.1.1, 19.2.0 # Also handle "19.0" (same as 19.0.0) if [ "$main_version" = "19.0" ] || [ "$main_version" = "19.0.0" ] || \ [ "$main_version" = "19.1.0" ] || [ "$main_version" = "19.1.1" ] || \ [ "$main_version" = "19.2.0" ]; then return 0 fi return 1 } # Check if package.json exists if [ ! -f "package.json" ]; then echo -e "${YELLOW}โš  No package.json found in current directory${NC}" echo "This script must be run from the root of a Node.js project." exit 1 fi echo -e "${BLUE}Checking package.json...${NC}" echo "" # Check for Next.js NEXTJS_VERSION="" if command -v jq &> /dev/null; then NEXTJS_VERSION=$(jq -r '.dependencies.next // .devDependencies.next // "not-found"' package.json) else NEXTJS_VERSION=$(grep -A1 '"next"' package.json | grep -oP '"\K[^"]+' | tail -1 || echo "not-found") fi if [ "$NEXTJS_VERSION" != "not-found" ] && [ -n "$NEXTJS_VERSION" ]; then # Remove ^ ~ >= etc from version CLEAN_VERSION=$(echo "$NEXTJS_VERSION" | sed 's/[\^~>=<]//g') INFO+=("${BLUE}โ„น${NC} Next.js found: $NEXTJS_VERSION") # Get actual installed version from package-lock.json or node_modules if [ -f "package-lock.json" ] && command -v jq &> /dev/null; then INSTALLED_VERSION=$(jq -r '.packages["node_modules/next"].version // empty' package-lock.json) if [ -n "$INSTALLED_VERSION" ]; then INFO+=(" ${BLUE}โ„น${NC} Installed Next.js version: $INSTALLED_VERSION") CLEAN_VERSION="$INSTALLED_VERSION" fi elif [ -f "node_modules/next/package.json" ]; then INSTALLED_VERSION=$(jq -r '.version' node_modules/next/package.json 2>/dev/null || echo "") if [ -n "$INSTALLED_VERSION" ]; then INFO+=(" ${BLUE}โ„น${NC} Installed Next.js version: $INSTALLED_VERSION") CLEAN_VERSION="$INSTALLED_VERSION" fi fi if check_nextjs_vulnerable "$CLEAN_VERSION"; then VULNERABLE=true WARNINGS+=("${RED}โœ— VULNERABLE: Next.js $CLEAN_VERSION is affected${NC}") WARNINGS+=(" Patch to: 15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7, 16.0.7, or 15.6.0-canary.58+") else INFO+=("${GREEN}โœ“${NC} Next.js version $CLEAN_VERSION is not vulnerable") fi else INFO+=("โ—‹ Next.js: Not found") fi # Check for React REACT_VERSION="" if command -v jq &> /dev/null; then REACT_VERSION=$(jq -r '.dependencies.react // .devDependencies.react // "not-found"' package.json) else REACT_VERSION=$(grep -A1 '"react"' package.json | grep -oP '"\K[^"]+' | tail -1 || echo "not-found") fi if [ "$REACT_VERSION" != "not-found" ] && [ -n "$REACT_VERSION" ]; then CLEAN_REACT=$(echo "$REACT_VERSION" | sed 's/[\^~>=<]//g') INFO+=("${BLUE}โ„น${NC} React found: $REACT_VERSION") # Get actual installed version if [ -f "package-lock.json" ] && command -v jq &> /dev/null; then INSTALLED_REACT=$(jq -r '.packages["node_modules/react"].version // empty' package-lock.json) if [ -n "$INSTALLED_REACT" ]; then INFO+=(" ${BLUE}โ„น${NC} Installed React version: $INSTALLED_REACT") CLEAN_REACT="$INSTALLED_REACT" fi elif [ -f "node_modules/react/package.json" ]; then INSTALLED_REACT=$(jq -r '.version' node_modules/react/package.json 2>/dev/null || echo "") if [ -n "$INSTALLED_REACT" ]; then INFO+=(" ${BLUE}โ„น${NC} Installed React version: $INSTALLED_REACT") CLEAN_REACT="$INSTALLED_REACT" fi fi if check_react_vulnerable "$CLEAN_REACT"; then VULNERABLE=true WARNINGS+=("${RED}โœ— VULNERABLE: React $CLEAN_REACT is affected${NC}") WARNINGS+=(" Patch to: 19.0.1, 19.1.2, or 19.2.1") else INFO+=("${GREEN}โœ“${NC} React version $CLEAN_REACT is not vulnerable") fi else INFO+=("โ—‹ React: Not found") fi # Check for vulnerable react-server-dom packages VULNERABLE_PACKAGES=("react-server-dom-parcel" "react-server-dom-turbopack" "react-server-dom-webpack") for pkg in "${VULNERABLE_PACKAGES[@]}"; do if command -v jq &> /dev/null; then PKG_VERSION=$(jq -r ".dependencies.\"$pkg\" // .devDependencies.\"$pkg\" // \"not-found\"" package.json) else PKG_VERSION=$(grep -A1 "\"$pkg\"" package.json | grep -oP '"\K[^"]+' | tail -1 || echo "not-found") fi if [ "$PKG_VERSION" != "not-found" ] && [ -n "$PKG_VERSION" ]; then CLEAN_PKG=$(echo "$PKG_VERSION" | sed 's/[\^~>=<]//g') INFO+=("${BLUE}โ„น${NC} $pkg found: $PKG_VERSION") # Get actual installed version if [ -f "package-lock.json" ] && command -v jq &> /dev/null; then INSTALLED_PKG=$(jq -r ".packages[\"node_modules/$pkg\"].version // empty" package-lock.json) if [ -n "$INSTALLED_PKG" ]; then INFO+=(" ${BLUE}โ„น${NC} Installed $pkg version: $INSTALLED_PKG") CLEAN_PKG="$INSTALLED_PKG" fi fi if check_react_vulnerable "$CLEAN_PKG"; then VULNERABLE=true WARNINGS+=("${RED}โœ— VULNERABLE: $pkg $CLEAN_PKG is affected${NC}") WARNINGS+=(" Update to patched React 19.x version (19.0.1, 19.1.2, or 19.2.1)") else INFO+=("${GREEN}โœ“${NC} $pkg version $CLEAN_PKG is not vulnerable") fi fi done # Check for other affected frameworks and bundlers OTHER_FRAMEWORKS=("react-router" "react-router-dom" "waku" "expo" "@parcel/rsc" "@vitejs/plugin-rsc" "rwsdk") FRAMEWORKS_LIST=() for framework in "${OTHER_FRAMEWORKS[@]}"; do if command -v jq &> /dev/null; then FW_VERSION=$(jq -r ".dependencies.\"$framework\" // .devDependencies.\"$framework\" // \"not-found\"" package.json) else FW_VERSION=$(grep -A1 "\"$framework\"" package.json | grep -oP '"\K[^"]+' | tail -1 || echo "not-found") fi if [ "$FW_VERSION" != "not-found" ] && [ -n "$FW_VERSION" ]; then FRAMEWORKS_LIST+=(" ${BLUE}โ„น${NC} $framework: $FW_VERSION") # These frameworks are vulnerable if using React 19.x with vulnerable versions if [ -n "$REACT_VERSION" ] && [ "$REACT_VERSION" != "not-found" ]; then CLEAN_FW_REACT=$(echo "$REACT_VERSION" | sed 's/[\^~>=<]//g') if version_compare "$CLEAN_FW_REACT" ">=" "19.0.0" && version_compare "$CLEAN_FW_REACT" "<" "20.0.0"; then if check_react_vulnerable "$CLEAN_FW_REACT"; then WARNINGS+=("${YELLOW}โš  WARNING: $framework found with vulnerable React $CLEAN_FW_REACT${NC}") WARNINGS+=(" This framework uses React Server Components and may be vulnerable") fi fi fi else FRAMEWORKS_LIST+=(" โ—‹ $framework: Not found") fi done # Always show frameworks section INFO+=("") INFO+=("Other frameworks/bundlers:") for fw in "${FRAMEWORKS_LIST[@]}"; do INFO+=("$fw") done # Display results echo "" echo -e "${BLUE}========================================${NC}" echo -e "${BLUE}Results:${NC}" echo -e "${BLUE}========================================${NC}" echo "" for info in "${INFO[@]}"; do echo -e "$info" done echo "" if [ ${#WARNINGS[@]} -gt 0 ]; then echo -e "${RED}VULNERABILITIES DETECTED:${NC}" echo "" for warning in "${WARNINGS[@]}"; do echo -e "$warning" done echo "" echo -e "${RED}โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•${NC}" echo -e "${RED}STATUS: VULNERABLE${NC}" echo -e "${RED}โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•${NC}" echo "" echo -e "${YELLOW}Recommended Actions:${NC}" echo "1. Update Next.js to a patched version (15.0.5+ or appropriate version)" echo "2. Update React to a patched version (19.0.1, 19.1.2, or 19.2.1)" echo "3. Update all react-server-dom-* packages to patched versions" echo "4. Run: npm audit fix --force (or yarn upgrade)" echo "" echo -e "${YELLOW}References:${NC}" echo "- https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components" echo "- https://github.com/vercel/next.js/security/advisories/GHSA-9qr9-h5gf-34mp" echo "" exit 1 else echo -e "${GREEN}โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•${NC}" echo -e "${GREEN}STATUS: NOT VULNERABLE${NC}" echo -e "${GREEN}โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•${NC}" echo "" echo "No vulnerable packages detected for CVE-2025-55182/CVE-2025-66478." echo "" exit 0 fi

Key takeaways

  1. โœ… npm audit isn't always enough - Fresh CVEs need custom tooling
  2. โœ… Semantic versioning is complex - Account for prereleases, canaries, and edge cases
  3. โœ… Security tools need good UX - Clear output and remediation steps are critical
  4. โœ… Bash is underrated - Sometimes a shell script is exactly what you need
  5. โœ… Check installed versions, not declared ones - package.json lies, package-lock.json tells the truth

What's next?

This script is specific to CVE-2025-55182/66478. But the pattern is reusable:

  • Generic CVE checker framework
  • Support for other package managers (pnpm, yarn)
  • JSON output for programmatic usage
  • Integration with GitHub Actions marketplace

Maybe I'll build that next. Or maybe I'll just keep writing 319-line Bash scripts at 9AM.

Either way, stay safe out there. And check your dependencies.


Related Posts:


Questions? Issues? Open an issue on GitHub or reach out on LinkedIn.