Skip to content

chore: refresh dependencies and harden the runtime image - #24

Merged
albanm merged 9 commits into
masterfrom
chore-deps-refresh
Sep 10, 2026
Merged

albanm merged 9 commits into
masterfrom
chore-deps-refresh

Conversation

@BatLeDev

@BatLeDev BatLeDev commented Sep 9, 2026 •

Copy link
Copy Markdown
Member

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=dev reported 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 abandoned bin-wrapper/download/decompress/got chain 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 by api/utils/gifsicle.ts, which spawns the binary installed via apt; output is byte-identical.
  • The Dockerfile never ran apt-get upgrade, freezing 282 OS CVEs.
  • The final image shipped /usr/local/lib/node_modules (npm/corepack, unused at runtime): 1 critical + 26 high on their own.

Along the way:

  • puppeteer 24 → 25. Its breaking changes are ESM-only packages, a minimum of Node 22, and executablePath/defaultArgs becoming promises — none of which this codebase uses. Two behaviour changes did bite, both caught by the tests: page.screenshot() now returns a plain Uint8Array, which get-pixels was reading as a file path (express serves it correctly, it checks ArrayBuffer.isView); and headless: 'new' is no longer valid, true producing the exact same --headless=new.
  • Dockerfile, compose file and engines modernized to match the node:24 base image, and the quality workflow moved to Node 24 with it; dumb-init bumped to 1.2.5, whose binaries are named by uname -m rather than by docker's TARGETARCH.
  • Two micro-optimizations per capture: cookies cleared in a single CDP call instead of one per cookie, and optimizeForSpeed on 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.

BatLeDev and others added 7 commits September 9, 2026 14:36
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
BatLeDev and others added 2 commits September 9, 2026 16:11
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants