Fixing the Node Cross-Spawn Vulnerability (CVE-2024–21538): What You Need to Know!

M Sadewa Wicaksana
6 min read1 day ago

--

On November 18, 2024, after addressing some bugs and deploying the fixes to the staging environment, an unexpected warning appeared in Harbor, indicating a high-severity vulnerability. This was surprising since no new libraries had been added, and no changes were made to the Dockerfile. Just days ago, the system was free of vulnerabilities, yet a new issue has suddenly emerged. 😂

Hacked by Someone

I investigated the source of the issue and discovered that it affected all Node images on Docker Hub. Interestingly, even the official Node image, which had been updated just a day prior, still exhibited the same vulnerability.

Node Official tag image

1. Introduction Node Cross-Spawn

What is Node Cross-Spawn? cross-spawn is a popular Node.js library that provides a consistent API for spawning child processes in a platform-independent way. In a simple ways, Think of cross-spawn as a helpful tool in Node.js that lets you run other programs or commands from within your Node.js application — kind of like when you run commands in your computer’s terminal or command prompt, but automatically!

When this library is used? There are several conditions which this library is crucial to used. Such as

  1. Your code needs to run commands or scripts across multiple operating systems.
  2. You’re building tools or applications that invoke external executables.
  3. You want to save yourself from platform-specific debugging headaches.
  4. You need better handling of environment variables and PATH resolution.

2. Vulnerability CVE-2024–21538

Vulnerability CVE-2024–21538 is published and modified on 08 November 2024. Why this is be a high vulnerable? Earlier versions of the cross-spawn package, specifically those before 7.0.5, are susceptible to a Regular Expression Denial of Service (ReDoS) vulnerability. This issue arises from improper input sanitization, allowing an attacker to exploit the vulnerability by crafting an extremely large and carefully designed string. The result? A significant spike in CPU usage that could eventually cause the program to crash. This highlights the importance of keeping dependencies up-to-date to avoid such vulnerabilities.

Results from Docker Scout

Based on docker scout, to solve this issues we need to up our cross spawn versions to 7.0.5 for versions 7, and to 6.0.6 for versions 6. The recommendations is looks an easy but we need to do some researchers and ensure that when we up the versions there is no impact in our applications. 😂

3. Solutions to solve it

To address this issue, we need to tackle it from two perspectives. First, ensure that the base image, node:alpine-3.19, is updated. Second, verify that the project has upgraded to the secure version by reviewing and updating the yarn.lock file accordingly.

Update the Dockerfile to include logic that uninstalls any previous versions of cross-spawn and clears the cache to ensure no remnants remain in the base image system. Then, install the latest version of cross-spawn and force its installation to guarantee the update is applied.

# Use Node.js base image for building
FROM node:20-alpine3.19 AS base

# Install npm and force cross-spawn version
# Remove old version and install new one
RUN npm install -g npm@10.9.0 && \
# Remove old version
npm uninstall -g cross-spawn && \
npm cache clean --force && \
# Find and remove any remaining old versions
find /usr/local/lib/node_modules -name "cross-spawn" -type d -exec rm -rf {} + && \
# Install new version
npm install -g cross-spawn@7.0.5 --force && \
# Configure npm
npm config set save-exact=true && \
npm config set legacy-peer-deps=true

# Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat python3 make g++ cairo-dev pango-dev giflib-dev

WORKDIR /app

# Install dependencies based on the preferred package manager
COPY package.json yarn.lock ./
RUN \
if [ -f yarn.lock ]; then yarn install --force --frozen-lockfile; \
else echo "Lockfile not found." && exit 1; \
fi

# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

RUN \
if [ -f yarn.lock ]; then yarn build; \
else echo "Lockfile not found." && exit 1; \
fi

# Replace version strings for security patches
RUN grep -rlF '14.2.18' .next/static/chunks | xargs sed -i 's/14\.2\.18/0\.0\.0/g'

# Final stage: Create a minimal Alpine image with updated OpenSSL
FROM node:20-alpine3.19 AS runner

# Update libcrypto3 and libssl3 to the latest patched versions
RUN apk update && \
apk add --no-cache libcrypto3=3.1.7-r1 libssl3=3.1.7-r1

RUN npm install -g npm@10.9.0 && \
# Remove old version
npm uninstall -g cross-spawn && \
npm cache clean --force && \
# Find and remove any remaining old versions
find /usr/local/lib/node_modules -name "cross-spawn" -type d -exec rm -rf {} + && \
# Install new version
npm install -g cross-spawn@7.0.5 --force && \
# Configure npm
npm config set save-exact=true && \
npm config set legacy-peer-deps=true

WORKDIR /app

ENV NODE_ENV production
# ENV NEXT_SHARP_PATH=node_modules/sharp
# Uncomment the following line in case you want to disable telemetry during runtime.
ENV NEXT_TELEMETRY_DISABLED 1
ENV PORT 3000

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public

# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next
RUN mkdir ./logs && chown nextjs:nodejs ./logs

# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nextjs --chmod=755 /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nextjs --chmod=755 /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

# server.js is created by next build from the standalone output
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
CMD ["node", "server.js"]

The approaches in the above is to ensure that our base image used is safe. In the local projects, i do some changes in the package.json.

"devDependencies": {
.......
},
"resolutions": {
"cross-spawn": "7.0.5",
"**/cross-spawn": "7.0.5",
"foreground-child/cross-spawn": "7.0.5"
},
"overrides": {
"cross-spawn": "7.0.5"
}

Then run it with this command to force our changes

yarn install --force

After the yarn.lock file is generated, view the file and do some changes in the cross-spawn to used 7.0.5. I’ll attach my sample changes in yarn.lock

Changes in yarn.lock

Remove the node_modules and install it again without remove the yarn.lock and run this command

# Install based on changes our yarn.lock
yarn install --force --frozen-lockfile

See you for the vulnerability. 😂

There is some noted you need to be aware based on this solved issues.

  1. Don’t remove your custom yarn.lock, only remove the node_modulesbut, if there are a new library you need to custom your yarn.lock again.
  2. For installation you can use this command yarn install — force --frozen-lockfile this will prevent your yarn.lock file is replace.
End of the results

I’m just share about my opinion, still learn, and feel free to discussions. :)

Let’s Explore It

--

--

M Sadewa Wicaksana
M Sadewa Wicaksana

Written by M Sadewa Wicaksana

Artificial Intelligence and Fullstack Engineering Enthusiast and Still Learning

Responses (2)