diff --git a/Dockerfile b/Dockerfile index 7dbbd46..e865aba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -40,6 +40,17 @@ ENV PATH=$PNPM_HOME:$PATH RUN corepack enable +# The npm shipped in the base image bundles libraries with known vulnerabilities. +# Replace them with fixed versions from a separately locked installation. +COPY npm-security /tmp/npm-security +RUN npm ci --prefix /tmp/npm-security --ignore-scripts --no-audit --no-fund \ + && for m in balanced-match brace-expansion http-cache-semantics ip-address tar undici; do \ + rm -rf "/usr/local/lib/node_modules/npm/node_modules/$m" \ + && cp -a "/tmp/npm-security/node_modules/$m" /usr/local/lib/node_modules/npm/node_modules/; \ + done \ + && node /tmp/npm-security/verify.mjs \ + && rm -rf /tmp/npm-security /root/.npm + COPY --from=deps /repo /repo WORKDIR /repo/docusaurus diff --git a/npm-security/package-lock.json b/npm-security/package-lock.json new file mode 100644 index 0000000..a4ba847 --- /dev/null +++ b/npm-security/package-lock.json @@ -0,0 +1,131 @@ +{ + "name": "documentation-npm-security-dependencies", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "documentation-npm-security-dependencies", + "version": "1.0.0", + "dependencies": { + "brace-expansion": "5.0.12", + "http-cache-semantics": "4.3.0", + "ip-address": "10.7.4", + "tar": "7.5.22", + "undici": "6.29.1" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.3.0.tgz", + "integrity": "sha512-M5t5LlJpS1UHMjvwRQVdFHvPISGeLAxNcrWuJkeGh0KxsqCHZ1O3NXZU/8x7cD0BDcGW8kapxMKTvwlqrNkHkA==", + "license": "BSD-2-Clause" + }, + "node_modules/ip-address": { + "version": "10.7.4", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.4.tgz", + "integrity": "sha512-DWBMKyeM25D70nrIiTkgQth59/G+naKOTep6huF9j+GmeK3DMD60Zvxg5d5qQnz+38AHgou6ean2LECX+lrKDg==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/tar": { + "version": "7.5.22", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz", + "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/undici": { + "version": "6.29.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.29.1.tgz", + "integrity": "sha512-EvRlUt/QqAEvXdQeRMYUfwOQsfzpDztykrG2owGEE/rBOBoYv9uF39ZzsNDh4lTFHhTr2XOZjKkYgueD12ZmGA==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + } + } +} diff --git a/npm-security/package.json b/npm-security/package.json new file mode 100644 index 0000000..2573d02 --- /dev/null +++ b/npm-security/package.json @@ -0,0 +1,12 @@ +{ + "name": "documentation-npm-security-dependencies", + "version": "1.0.0", + "private": true, + "dependencies": { + "brace-expansion": "5.0.12", + "http-cache-semantics": "4.3.0", + "ip-address": "10.7.4", + "tar": "7.5.22", + "undici": "6.29.1" + } +} diff --git a/npm-security/verify.mjs b/npm-security/verify.mjs new file mode 100644 index 0000000..1f0df43 --- /dev/null +++ b/npm-security/verify.mjs @@ -0,0 +1,18 @@ +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; + +const root = process.argv[2] || '/usr/local/lib/node_modules/npm/node_modules'; +const expected = { + 'balanced-match': '4.0.4', + 'brace-expansion': '5.0.12', + 'http-cache-semantics': '4.3.0', + 'ip-address': '10.7.4', + tar: '7.5.22', + undici: '6.29.1', +}; + +for (const [name, version] of Object.entries(expected)) { + const { version: actual } = JSON.parse(readFileSync(join(root, name, 'package.json'), 'utf8')); + if (actual !== version) throw new Error(`${name}@${actual} in npm, expected ${version}`); + console.log(`${name}@${actual}`); +}