From 315ca5e67ca6f75f0c63363b84193d30499fbcc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 7 Aug 2026 14:35:23 +0200 Subject: [PATCH] fix(deps): move the PDF renderer past a script-execution flaw pdfjs-dist 6.1.200 and a transitive 5.6.205 both sit in the affected range of a flaw that lets a crafted PDF run JavaScript inside the renderer. The document vault feeds it every uploaded PDF, so pin the direct dependency to 6.2.108 and add a bounded override so the transitive copy lands there too. The override stops short of pdf-parse's 5.4.296, which predates the flaw and drives the local text-extraction path. The runtime image ships every pdfjs-dist copy in the store, not just the one that gets loaded, so an unused transitive copy still counted. Hoisting in the image picked whichever copy the filesystem listed first, and more than one copy reaches it, so the version named in package.json was not reliably the one the renderer resolved. Carry the resolved version forward from the build stage, select it by name, and fail the build when it is missing rather than leaving another copy in its place. --- CHANGELOG.md | 18 ++++++ Dockerfile | 36 +++++++++-- package.json | 2 +- pnpm-lock.yaml | 151 ++------------------------------------------ pnpm-workspace.yaml | 8 +++ 5 files changed, 64 insertions(+), 151 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69dfb6b15..f94ce3040 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,24 @@ ## [Unreleased] +### Security + +- The PDF renderer behind the document vault moves to a release that closes a + script-execution flaw. A crafted PDF could get JavaScript to run inside the + renderer, and the vault feeds it every PDF that is uploaded. Two things + limited what that reached, and neither is a substitute for the fix: the + renderer runs server-side only, because a PDF is displayed through the + browser's own viewer rather than this library, and it already ran with font + evaluation turned off. A second copy of the same library, pulled in by a + development dependency, is pinned past the flaw as well, since the runtime + image ships every copy it finds rather than only the one that gets used. + +- The container image now runs the version of the PDF renderer the project + pins. The build had been taking whichever copy the filesystem listed first, + and more than one copy reaches the image, so the pinned version was not + reliably the one that ran. It is selected by name now, and a copy that never + arrived fails the build instead of quietly leaving another one in its place. + ## [1.37.0] — 2026-08-07 ### Added diff --git a/Dockerfile b/Dockerfile index 5eeb810ad..24982844a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -48,6 +48,13 @@ ENV NEXT_TELEMETRY_DISABLED=1 # starts fresh and therefore does not inherit this setting. RUN NODE_OPTIONS=--max-old-space-size=8192 pnpm build +# Record which pdfjs-dist the top-level dependency actually resolves to. pnpm +# links a direct dependency at `node_modules/`, so this reads the exact +# pinned copy rather than guessing. The runner stage hoists that version by +# name; see the note there for why guessing was wrong. +RUN node -p "require('/app/node_modules/pdfjs-dist/package.json').version" \ + > /app/.pdfjs-version + # ── Stage 3: Production runner ───────────────────────────── FROM node:22-alpine@sha256:16e22a550f3863206a3f701448c45f7912c6896a62de43add43bb9c86130c3e2 AS runner # `tzdata` is required so Europe/Berlin schedules (pg-boss cron, locale-aware @@ -116,13 +123,30 @@ RUN set -e; \ # path breaks in the standalone image), so the server does a runtime bare # `import('pdfjs-dist/legacy/build/pdf.mjs')`. Node resolves that up to # /app/node_modules, so hoist the real pnpm-store copy to the top level too. +# +# Hoist the version the app pins, by name. More than one pdfjs-dist reaches the +# store — `pdf-parse` carries its own, older copy for the text-extraction path — +# and this step used to take whatever `find | head -1` returned first. That is +# readdir order, so it could hoist the OLDER copy and the version named in +# package.json would never run in the image: the renderer resolving one version +# while every local check exercised another. Name the version and fail the build +# when it is absent, instead of degrading to whichever copy comes back first. +COPY --from=builder /app/.pdfjs-version /tmp/.pdfjs-version RUN set -e; \ - PDFJS_DIR="$(find /app/node_modules/.pnpm -maxdepth 4 -type d -path '*pdfjs-dist@*/node_modules/pdfjs-dist' 2>/dev/null | head -1)"; \ - if [ -n "$PDFJS_DIR" ]; then \ - ln -sfn "$PDFJS_DIR" /app/node_modules/pdfjs-dist; \ - chown -h nextjs:nodejs /app/node_modules/pdfjs-dist; \ - echo "pdfjs-dist hoisted: $PDFJS_DIR"; \ - else echo "WARN: pdfjs-dist not found; PDF rasterization will degrade to local text"; fi + PDFJS_VERSION="$(cat /tmp/.pdfjs-version)"; \ + PDFJS_DIR="/app/node_modules/.pnpm/pdfjs-dist@${PDFJS_VERSION}/node_modules/pdfjs-dist"; \ + if [ ! -d "$PDFJS_DIR" ]; then \ + echo "ERROR: pinned pdfjs-dist@${PDFJS_VERSION} did not reach the traced image."; \ + echo "Present instead:"; \ + find /app/node_modules/.pnpm -maxdepth 4 -type d -path '*pdfjs-dist@*/node_modules/pdfjs-dist' 2>/dev/null; \ + exit 1; \ + fi; \ + ln -sfn "$PDFJS_DIR" /app/node_modules/pdfjs-dist; \ + chown -h nextjs:nodejs /app/node_modules/pdfjs-dist; \ + rm -f /tmp/.pdfjs-version; \ + RESOLVED="$(node -p "require('/app/node_modules/pdfjs-dist/package.json').version")"; \ + [ "$RESOLVED" = "$PDFJS_VERSION" ] || { echo "ERROR: hoisted pdfjs-dist is $RESOLVED, expected $PDFJS_VERSION"; exit 1; }; \ + echo "pdfjs-dist hoisted: $PDFJS_DIR (resolves as $RESOLVED)" # Copy Prisma for migrations (schema, migration SQL, config, engines) COPY --from=builder /app/prisma ./prisma diff --git a/package.json b/package.json index 4aa16d7cd..97506c04f 100644 --- a/package.json +++ b/package.json @@ -126,7 +126,7 @@ "knip": "^6.16.1", "lefthook": "^2.1.10", "pdf-parse": "^2.4.5", - "pdfjs-dist": "6.1.200", + "pdfjs-dist": "6.2.108", "pg": "^8.22.0", "prettier": "^3.9.1", "prettier-plugin-tailwindcss": "^0.8.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dbe7c3e86..92dc14bec 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,7 @@ overrides: fast-uri@<3.1.5: ^3.1.5 ip-address@<10.3.1: ^10.3.1 sharp@<0.35.0: ^0.35.3 + pdfjs-dist@>=5.6.83 <6.2.108: 6.2.108 importers: @@ -234,8 +235,8 @@ importers: specifier: ^2.4.5 version: 2.4.5 pdfjs-dist: - specifier: 6.1.200 - version: 6.1.200 + specifier: 6.2.108 + version: 6.2.108 pg: specifier: ^8.22.0 version: 8.22.0 @@ -1223,12 +1224,6 @@ packages: resolution: {integrity: sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w==} engines: {node: '>=18'} - '@napi-rs/canvas-android-arm64@0.1.100': - resolution: {integrity: sha512-hjhCKhntPv9+t4ckHymdx0phYNcVW+GKQR6Lzw2zE+pOVjOplSmtx9nNNknTjbEDLcuLZqA1y8ufKg1XfgftzQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [android] - '@napi-rs/canvas-android-arm64@0.1.80': resolution: {integrity: sha512-sk7xhN/MoXeuExlggf91pNziBxLPVUqF2CAVnB57KLG/pz7+U5TKG8eXdc3pm0d7Od0WreB6ZKLj37sX9muGOQ==} engines: {node: '>= 10'} @@ -1241,12 +1236,6 @@ packages: cpu: [arm64] os: [android] - '@napi-rs/canvas-darwin-arm64@0.1.100': - resolution: {integrity: sha512-2PcswRaC7Ly645DGt88///zuFDhJxJYdKAs1uU3mfk1atYkXufgcgLfBpk6Tm12nCQBaNt1wpybuPZ4qOhTo8A==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - '@napi-rs/canvas-darwin-arm64@0.1.80': resolution: {integrity: sha512-O64APRTXRUiAz0P8gErkfEr3lipLJgM6pjATwavZ22ebhjYl/SUbpgM0xcWPQBNMP1n29afAC/Us5PX1vg+JNQ==} engines: {node: '>= 10'} @@ -1259,12 +1248,6 @@ packages: cpu: [arm64] os: [darwin] - '@napi-rs/canvas-darwin-x64@0.1.100': - resolution: {integrity: sha512-ePNZtj7pNIva/siZMg+HmbeozkIjqUIYdoymH8HaA3qK7LfzFN4WMBM8G6HQ9ZC+H3+Dnn5pqtiXpgLykaPOhw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - '@napi-rs/canvas-darwin-x64@0.1.80': resolution: {integrity: sha512-FqqSU7qFce0Cp3pwnTjVkKjjOtxMqRe6lmINxpIZYaZNnVI0H5FtsaraZJ36SiTHNjZlUB69/HhxNDT1Aaa9vA==} engines: {node: '>= 10'} @@ -1277,12 +1260,6 @@ packages: cpu: [x64] os: [darwin] - '@napi-rs/canvas-linux-arm-gnueabihf@0.1.100': - resolution: {integrity: sha512-d5cDB48oWFGU8/XPhUOFAlySgb/VAu7D+s8fi55K1Pcfg8aPplHWqMgibhVLU8ky7Pyg/fuiVLz4Nf3JrSTuUA==} - engines: {node: '>= 10'} - cpu: [arm] - os: [linux] - '@napi-rs/canvas-linux-arm-gnueabihf@0.1.80': resolution: {integrity: sha512-eyWz0ddBDQc7/JbAtY4OtZ5SpK8tR4JsCYEZjCE3dI8pqoWUC8oMwYSBGCYfsx2w47cQgQCgMVRVTFiiO38hHQ==} engines: {node: '>= 10'} @@ -1295,13 +1272,6 @@ packages: cpu: [arm] os: [linux] - '@napi-rs/canvas-linux-arm64-gnu@0.1.100': - resolution: {integrity: sha512-rDxgxRu69RvDlX/bh9o22DxLsGr8EqsNgotL9+RwQE1S0b0cqeatqsw6aW45mukm0B42DIAaAacKaYQ8cqS1nw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [glibc] - '@napi-rs/canvas-linux-arm64-gnu@0.1.80': resolution: {integrity: sha512-qwA63t8A86bnxhuA/GwOkK3jvb+XTQaTiVML0vAWoHyoZYTjNs7BzoOONDgTnNtr8/yHrq64XXzUoLqDzU+Uuw==} engines: {node: '>= 10'} @@ -1316,13 +1286,6 @@ packages: os: [linux] libc: [glibc] - '@napi-rs/canvas-linux-arm64-musl@0.1.100': - resolution: {integrity: sha512-K3mDW66N+xT2/V439u1alFANiBUjdEx2gLiNYnCmUsva5jZMxWTjafBYwTzYK+EMFMHrUoabuU+T1BIP5CgbYQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [musl] - '@napi-rs/canvas-linux-arm64-musl@0.1.80': resolution: {integrity: sha512-1XbCOz/ymhj24lFaIXtWnwv/6eFHXDrjP0jYkc6iHQ9q8oXKzUX1Lc6bu+wuGiLhGh2GS/2JlfORC5ZcXimRcg==} engines: {node: '>= 10'} @@ -1337,13 +1300,6 @@ packages: os: [linux] libc: [musl] - '@napi-rs/canvas-linux-riscv64-gnu@0.1.100': - resolution: {integrity: sha512-mooqUBTIsccZpnoQC4NgrC1v6C1vof39etLNMnBwCY+p0gajWJvAHLGQ6g/gGyS5YrpDW+GefSN4+Cvcr08UWw==} - engines: {node: '>= 10'} - cpu: [riscv64] - os: [linux] - libc: [glibc] - '@napi-rs/canvas-linux-riscv64-gnu@0.1.80': resolution: {integrity: sha512-XTzR125w5ZMs0lJcxRlS1K3P5RaZ9RmUsPtd1uGt+EfDyYMu4c6SEROYsxyatbbu/2+lPe7MPHOO/0a0x7L/gw==} engines: {node: '>= 10'} @@ -1358,13 +1314,6 @@ packages: os: [linux] libc: [glibc] - '@napi-rs/canvas-linux-x64-gnu@0.1.100': - resolution: {integrity: sha512-1eCvkDCazm7FFhsT7DfGOdSaHgZVK3bt/dSBl5EWHOWmnz+I7j8tPseJqqD81NF+MH21jKUK4wQSDjN0mdhnTg==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [glibc] - '@napi-rs/canvas-linux-x64-gnu@0.1.80': resolution: {integrity: sha512-BeXAmhKg1kX3UCrJsYbdQd3hIMDH/K6HnP/pG2LuITaXhXBiNdh//TVVVVCBbJzVQaV5gK/4ZOCMrQW9mvuTqA==} engines: {node: '>= 10'} @@ -1379,13 +1328,6 @@ packages: os: [linux] libc: [glibc] - '@napi-rs/canvas-linux-x64-musl@0.1.100': - resolution: {integrity: sha512-20arT6lnI19S68qNlii73TSEDbECNgzMz2EpldC1V3mZFuRkeujXkcebRk0LRJe9SEUAooYiLokfMViY8IX7yA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [musl] - '@napi-rs/canvas-linux-x64-musl@0.1.80': resolution: {integrity: sha512-x0XvZWdHbkgdgucJsRxprX/4o4sEed7qo9rCQA9ugiS9qE2QvP0RIiEugtZhfLH3cyI+jIRFJHV4Fuz+1BHHMg==} engines: {node: '>= 10'} @@ -1400,24 +1342,12 @@ packages: os: [linux] libc: [musl] - '@napi-rs/canvas-win32-arm64-msvc@0.1.100': - resolution: {integrity: sha512-DZFFT1wIAg37LJw37yhMRFfjATd3vTQzjZ1Yki8u2vhO6Hi5VE6BVaGQ1aaDu7xb4iMErz+9EOwjpS7xcxFeBw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - '@napi-rs/canvas-win32-arm64-msvc@1.0.2': resolution: {integrity: sha512-yE0koHCFF4PIbMc2o2SEALhnipz7WBISh5glLvQiomtIoCcW0np3H4Lw93ceJAfJttTTeIIWFbwH84F7EVzjMQ==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@napi-rs/canvas-win32-x64-msvc@0.1.100': - resolution: {integrity: sha512-MyT1j3mHC2+Lu4pBi9mKyMJhtP6U7k7EldY7sj/uS5gJA65gTXt8MefJQXLJo5d/vZbuWmfxzkEUNc/urV3pHA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - '@napi-rs/canvas-win32-x64-msvc@0.1.80': resolution: {integrity: sha512-Z8jPsM6df5V8B1HrCHB05+bDiCxjE9QA//3YrkKIdVDEwn5RKaqOxCJDRJkl48cJbylcrJbW4HxZbTte8juuPg==} engines: {node: '>= 10'} @@ -1430,10 +1360,6 @@ packages: cpu: [x64] os: [win32] - '@napi-rs/canvas@0.1.100': - resolution: {integrity: sha512-xglYA6q3XO5P3BNJYxVZ1IV7DLVjp1Py6nwag88YntrS+3vKHyYcMqXVS4ZztJmwz2uGvz1FWhI/4LgbR5uQDA==} - engines: {node: '>= 10'} - '@napi-rs/canvas@0.1.80': resolution: {integrity: sha512-DxuT1ClnIPts1kQx8FBmkk4BQDTfI5kIzywAaMjQSXfNnra5UFU9PwurXrl+Je3bJ6BGsp/zmshVVFbCmyI+ww==} engines: {node: '>= 10'} @@ -5696,9 +5622,6 @@ packages: resolution: {integrity: sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==} engines: {node: '>= 6.13.0'} - node-readable-to-web-readable-stream@0.4.2: - resolution: {integrity: sha512-/cMZNI34v//jUTrI+UIo4ieHAB5EZRY/+7OmXZgBxaWBMcW2tGdceIw06RFxWxrKZ5Jp3sI2i5TsRo+CBhtVLQ==} - node-releases@2.0.51: resolution: {integrity: sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==} engines: {node: '>=18'} @@ -5918,12 +5841,8 @@ packages: resolution: {integrity: sha512-DlOzet0HO7OEnmUmB6wWGJrrdvbyJKftI1bhMitK7O2N8W2gc757yyYBbINy9IDafXAV9wmKr9t7xsTaNKRG5Q==} engines: {node: '>=20.16.0 || >=22.3.0'} - pdfjs-dist@5.6.205: - resolution: {integrity: sha512-tlUj+2IDa7G1SbvBNN74UHRLJybZDWYom+k6p5KIZl7huBvsA4APi6mKL+zCxd3tLjN5hOOEE9Tv7VdzO88pfg==} - engines: {node: '>=20.19.0 || >=22.13.0 || >=24'} - - pdfjs-dist@6.1.200: - resolution: {integrity: sha512-o8MolyzirkkLrcdsae/HEOiIcXWI7DS5zGpvqW8xTC2YUsW30rltFw2bDGvw/fskUdEMrQm2br68jzDS5BH2vw==} + pdfjs-dist@6.2.108: + resolution: {integrity: sha512-YxFb+SQcodN2rnX9Tn3dHYlqfb7NjlzzfONPpJd+AKoKtUjEdevTfbC07d5TcczzOK6261auRkP/M8OBHs9vFQ==} engines: {node: '>=22.13.0 || >=24'} pend@1.2.0: @@ -8354,117 +8273,69 @@ snapshots: strict-event-emitter: 0.5.1 optional: true - '@napi-rs/canvas-android-arm64@0.1.100': - optional: true - '@napi-rs/canvas-android-arm64@0.1.80': optional: true '@napi-rs/canvas-android-arm64@1.0.2': optional: true - '@napi-rs/canvas-darwin-arm64@0.1.100': - optional: true - '@napi-rs/canvas-darwin-arm64@0.1.80': optional: true '@napi-rs/canvas-darwin-arm64@1.0.2': optional: true - '@napi-rs/canvas-darwin-x64@0.1.100': - optional: true - '@napi-rs/canvas-darwin-x64@0.1.80': optional: true '@napi-rs/canvas-darwin-x64@1.0.2': optional: true - '@napi-rs/canvas-linux-arm-gnueabihf@0.1.100': - optional: true - '@napi-rs/canvas-linux-arm-gnueabihf@0.1.80': optional: true '@napi-rs/canvas-linux-arm-gnueabihf@1.0.2': optional: true - '@napi-rs/canvas-linux-arm64-gnu@0.1.100': - optional: true - '@napi-rs/canvas-linux-arm64-gnu@0.1.80': optional: true '@napi-rs/canvas-linux-arm64-gnu@1.0.2': optional: true - '@napi-rs/canvas-linux-arm64-musl@0.1.100': - optional: true - '@napi-rs/canvas-linux-arm64-musl@0.1.80': optional: true '@napi-rs/canvas-linux-arm64-musl@1.0.2': optional: true - '@napi-rs/canvas-linux-riscv64-gnu@0.1.100': - optional: true - '@napi-rs/canvas-linux-riscv64-gnu@0.1.80': optional: true '@napi-rs/canvas-linux-riscv64-gnu@1.0.2': optional: true - '@napi-rs/canvas-linux-x64-gnu@0.1.100': - optional: true - '@napi-rs/canvas-linux-x64-gnu@0.1.80': optional: true '@napi-rs/canvas-linux-x64-gnu@1.0.2': optional: true - '@napi-rs/canvas-linux-x64-musl@0.1.100': - optional: true - '@napi-rs/canvas-linux-x64-musl@0.1.80': optional: true '@napi-rs/canvas-linux-x64-musl@1.0.2': optional: true - '@napi-rs/canvas-win32-arm64-msvc@0.1.100': - optional: true - '@napi-rs/canvas-win32-arm64-msvc@1.0.2': optional: true - '@napi-rs/canvas-win32-x64-msvc@0.1.100': - optional: true - '@napi-rs/canvas-win32-x64-msvc@0.1.80': optional: true '@napi-rs/canvas-win32-x64-msvc@1.0.2': optional: true - '@napi-rs/canvas@0.1.100': - optionalDependencies: - '@napi-rs/canvas-android-arm64': 0.1.100 - '@napi-rs/canvas-darwin-arm64': 0.1.100 - '@napi-rs/canvas-darwin-x64': 0.1.100 - '@napi-rs/canvas-linux-arm-gnueabihf': 0.1.100 - '@napi-rs/canvas-linux-arm64-gnu': 0.1.100 - '@napi-rs/canvas-linux-arm64-musl': 0.1.100 - '@napi-rs/canvas-linux-riscv64-gnu': 0.1.100 - '@napi-rs/canvas-linux-x64-gnu': 0.1.100 - '@napi-rs/canvas-linux-x64-musl': 0.1.100 - '@napi-rs/canvas-win32-arm64-msvc': 0.1.100 - '@napi-rs/canvas-win32-x64-msvc': 0.1.100 - optional: true - '@napi-rs/canvas@0.1.80': optionalDependencies: '@napi-rs/canvas-android-arm64': 0.1.80 @@ -8707,7 +8578,7 @@ snapshots: fflate: 0.8.2 incur: 0.4.13 papaparse: 5.5.3 - pdfjs-dist: 5.6.205 + pdfjs-dist: 6.2.108 smol-toml: 1.6.1 transitivePeerDependencies: - '@types/node' @@ -12819,9 +12690,6 @@ snapshots: node-forge@1.4.0: {} - node-readable-to-web-readable-stream@0.4.2: - optional: true - node-releases@2.0.51: {} nodemailer@9.0.3: {} @@ -13084,12 +12952,7 @@ snapshots: optionalDependencies: '@napi-rs/canvas': 0.1.80 - pdfjs-dist@5.6.205: - optionalDependencies: - '@napi-rs/canvas': 0.1.100 - node-readable-to-web-readable-stream: 0.4.2 - - pdfjs-dist@6.1.200: + pdfjs-dist@6.2.108: optionalDependencies: '@napi-rs/canvas': 1.0.2 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 15a8dafd2..c7fabca7c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -12,6 +12,14 @@ overrides: # rate limiter where nothing of ours screens it. "ip-address@<10.3.1": "^10.3.1" "sharp@<0.35.0": "^0.35.3" + # A crafted PDF could run arbitrary JavaScript through the renderer. Our own + # copy is pinned past it in `devDependencies`, but a second copy arrives + # transitively, and `outputFileTracingIncludes` ships EVERY `pdfjs-dist@*` in + # the store into the standalone image — so an unused transitive copy is still + # a copy in the runtime image. The range is bounded on both ends deliberately: + # `pdf-parse` resolves 5.4.296, which predates the flaw and drives the local + # text-extraction path, so it must not be dragged onto a new major. + "pdfjs-dist@>=5.6.83 <6.2.108": "6.2.108" allowBuilds: "@prisma/engines": true