chore: refresh dependencies and harden the runtime image - #24
Merged
Merged
Conversation
Brings prod audit findings from 31 down to 25. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QTLvrFDeSMkyzs4VPNg2zH
- run apt-get upgrade so later debian security releases are picked up at build time instead of being frozen to whatever the base image shipped with - install the gifsicle binary from the distro, so the service no longer needs the npm package that downloaded its own copy at install time - drop npm/corepack from the final stage: they are only used by the builder stage and carried a critical and 26 high advisories of their own Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QTLvrFDeSMkyzs4VPNg2zH
imagemin-gifsicle was a ~30 line wrapper spawning the gifsicle binary, but it pulled in the abandoned bin-wrapper/download/decompress chain used only to fetch that binary at install time. It carried 21 of the 25 remaining prod advisories, including the only critical one. The binary now comes from the image. puppeteer 25 replaces extract-zip with modern-tar in @puppeteer/browsers, which clears the last 4. Two adjustments were needed: - page.screenshot() now returns a plain Uint8Array rather than a Buffer, and get-pixels switches on Buffer.isBuffer to tell image data from a file path, so animation frames were being read as filenames - headless: 'new' is no longer a valid value; true produces the exact same --headless=new flag (see ChromeLauncher), so behaviour is unchanged npm audit --omit=dev now reports 0 vulnerabilities, down from 31. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QTLvrFDeSMkyzs4VPNg2zH
- delete the context cookies in a single call instead of one CDP round trip per cookie; this runs inside a 2s timeout that a session's worth of cookies could otherwise eat into - capture animation frames with optimizeForSpeed, as each frame is decoded to raw pixels immediately after and the png compression work is thrown away Also switch animation.ts to node: prefixed builtins and to stream/promises instead of promisify(stream.pipeline). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QTLvrFDeSMkyzs4VPNg2zH
- bump dumb-init 1.2.2 (2018) to 1.2.5 - use the ENV key=value form instead of the deprecated space separated one - drop the clean-modules exceljs exclude, exceljs is not a dependency here - drop the obsolete compose file version key, which warns on every run - engines said node v20 while the image has been on node 24 for a while Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QTLvrFDeSMkyzs4VPNg2zH
dumb-init releases since 1.2.3 name their binaries after uname -m (x86_64, aarch64) rather than docker's TARGETARCH (amd64, arm64), so the plain ADD 404s. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QTLvrFDeSMkyzs4VPNg2zH
Dropping imagemin-gifsicle moved the binary to the system: the Dockerfile installs it, but the quality workflow runs npm run quality directly on the runner, where it is absent. Install it there, and list it as a local prerequisite alongside google-chrome. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RyJMSwqMggx1PUJaQDeC47
gifsicle used to be bundled by imagemin-gifsicle in every install; it now comes from the system, so an install missing it would look healthy until the first animated capture returned a 500. Probe it while starting the server instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RyJMSwqMggx1PUJaQDeC47
engines now requires >=24 and the image builds on node:24, but the workflow still pinned 22, so the checks ran on a version the package declares unsupported. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RyJMSwqMggx1PUJaQDeC47
BatLeDev
marked this pull request as ready for review
September 9, 2026 14:27
This was referenced Sep 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refresh the dependencies and harden the runtime image so that every fixable vulnerability is gone, without changing the service's behaviour.
Why:
npm audit --omit=devreported 31 advisories (1 critical) and the image 711 CVEs, 282 of them fixable upstream. Now: 0 advisories, and 362 image CVEs of which 0 are fixable — the rest (libglib, libxml2, perl-base) have no patch published in Debian 13.What carried the vulnerabilities:
imagemin-gifsicle— a ~30 line wrapper that only spawns the gifsicle binary, but drags in the abandonedbin-wrapper/download/decompress/gotchain whose sole purpose is to download that binary at postinstall. On its own: 21 of the 25 remaining prod advisories, including the only critical one. Replaced byapi/utils/gifsicle.ts, which spawns the binary installed via apt; output is byte-identical.apt-get upgrade, freezing 282 OS CVEs./usr/local/lib/node_modules(npm/corepack, unused at runtime): 1 critical + 26 high on their own.Along the way:
executablePath/defaultArgsbecoming promises — none of which this codebase uses. Two behaviour changes did bite, both caught by the tests:page.screenshot()now returns a plainUint8Array, whichget-pixelswas reading as a file path (express serves it correctly, it checksArrayBuffer.isView); andheadless: 'new'is no longer valid,trueproducing the exact same--headless=new.enginesmodernized to match thenode:24base image, and the quality workflow moved to Node 24 with it; dumb-init bumped to 1.2.5, whose binaries are named byuname -mrather than by docker'sTARGETARCH.optimizeForSpeedon animation frames whose PNG is decoded to raw pixels right after.Heads-up: gifsicle is now a system prerequisite rather than an npm dependency. It is provided by the image, the quality workflow and the README, and the server probes it at startup so that an install missing it fails immediately instead of on the first animated capture.