Skip to content

maintenance: npm packages #120

Description

@jherforth

Watch-list (act on trigger, not on schedule)

  • moment (maintenance mode; 2 files + the react-big-calendar localizer) → migrate to a maintained date lib when CalendarWidget is next reworked (the feat: good mobile view - Phase 2 - main application #118 mobile agenda view is the natural moment), not as standalone churn.
  • react-color 2.19.3 (unmaintained since ~2021; 2 files) → swap to @uiw/react-color when the color picker is next touched.
  • ical-generator 10→11 (major; Node 22/24 only — fine on the new node:24-slim image; used only by the ICS export endpoint) → optional, take it with a test pass or leave it.

Suggested standing policy

Act only when: (a) npm audit flags anything, (b) a package on the external-input list above falls a major behind, or (c) a dep actually used in the app goes officially EOL/deprecated. Everything else may drift freely. That replaces this issue's blanket ask — recommend keeping wontfix on the general bump (or closing once Tier 1 lands).


Update 2026-08-12 — audit trigger has fired

Trigger (a) of the standing policy above (npm audit flags something) is now
met: 5 HIGH advisories on server/. Investigated while evaluating #135
(an automated bot PR, closed unmerged — see the trap below). Findings recorded
here so the next person doesn't have to re-derive them.

The queue (one deliberate pass, not five bot PRs)

Package Advisory Current Target Notes
@fastify/static CVE-2026-15074 (HIGH) + CVE-2026-7120 (mod) 9.1.3 ^10.1.3 Major. Requires a code change — see below.
fast-xml-parser GHSA-8r6m-32jq-jx6q (HIGH) 5.9.3 5.10.1 Direct dep, pinned exactly (no caret), so npm audit fix is a no-op. Pin looks deliberate (CalDAV CDATA work in #106) — re-run the CalDAV tests.
find-my-way GHSA-c96f-x56v-gq3h (HIGH) ≤9.6.0 via fastify HTTP/2 DDoS. Transitive through Fastify's router.
brace-expansion GHSA-mh99-v99m-4gvg (HIGH) <5.0.8 auto Unbounded-expansion DoS. Transitive.
fast-uri GHSA-7p8r-x3mc-p8w7 (HIGH) <3.1.5 auto Host confusion via backslash. Transitive (Fastify).

⚠️ The @fastify/static v10 trap (this is what killed #135)

v10.0.0 changed the setHeaders callback to receive a FastifyReply
instead of the raw Node response. Two of our three registrations in
server/index.js (the /Uploads/ and /Uploads/users/ roots, ~lines 477 and
489) call res.setHeader(...), which does not exist on Reply.

Failure mode is nastier than a 500: the TypeError is uncaught, so the
backend process dies on the first request to /Uploads/* — i.e. seconds
after any dashboard load, since every photo, sound, and avatar goes through
that path. Reproduced both in isolation (Fastify 5.10.0 + static 10.1.3) and
by running our suite against #135's lockfile (2 failures, ECONNRESET).

The migration is two lines per registration:

// before (v9)                          // after (v10)
setHeaders: (res, path) => {            setHeaders: (reply) => {
  res.setHeader('Cache-Control', ...);    reply.header('Cache-Control', ...);
}                                       }

The /widgets/ registration passes no setHeaders and is unaffected.
Verified: with that change, server is 114/114 green on static 10.1.2.

Verified compatibility facts (so we don't re-litigate the major bump)

  • Fastify 5 is fine. v10 declares no peerDependencies, installs against
    fastify@5.10.0 with no resolution conflict, and boots cleanly. setHeaders
    is the only breaking change that touches our code.
  • No 9.x backport exists. First patched version is 10.1.1; the whole 9.x
    line (incl. 9.2.0 / 9.3.0) is in the affected range. Staying on 9 means
    carrying a permanent HIGH finding and a recurring stream of bot PRs.
  • Target ^10.1.3, not 10.1.1 — the moderate CVE-2026-7120 affects ≤10.1.1.

Priority: real, but not urgent

CVE-2026-15074 is a route-guard bypass, not a root escape: non-leading
../ / %2E%2E segments match the static catch-all instead of a guarded
route. It cannot read outside the static root (/Uploads/../package.json
404 on v10, and @fastify/send's own traversal check still runs). data/,
the SQLite DB, and the encryption key were never reachable this way.

We have exactly one preHandler hook (a request logger) and no route-based
guards on any path under /Uploads/ or /widgets/
— everything the static
plugin serves is unauthenticated by design. So there is nothing to bypass and
the CVE is not exploitable in our configuration. Do the upgrade to clear
scanners and stop the bot-PR churn, not because there's live risk.

Related gap: CI does not run on PRs

ci-tests.yml triggers only on push, so fork PRs get zero checks#135
showed as "mergeable / CLEAN" with nothing run, even though our own suite
catches its breakage. Adding pull_request to that trigger would have caught
it automatically, and is worth doing before the next contributor PR.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

Status
Needs groomed

Relationships

None yet

Development

No branches or pull requests

Issue actions