Security: resolve npm audit high-severity advisories (esbuild/vite, cookie) failing the CI audit gate #817

Closed
opened 2026-06-12 23:45:59 +02:00 by marcel · 2 comments
Owner

Context

The CI step "Security audit (no dev deps)" (npm audit --audit-level=high --omit=dev, .gitea/workflows/ci.yml:32-33) fails with exit 1. This is repo-wide and not caused by any one PRfrontend/package-lock.json was last changed on main at fab2930c (2026-06-03), and the failure is driven by newly-published advisories landing against already-pinned versions. main itself is red on this gate today; it surfaced on the #774 PR run but #774 touched zero frontend files. Net effect: the audit gate blocks every PR until remediated.

Decision (2026-06-12, post-review): Primary fix is the overrides block (Option 1 below). If the override breaks npm run build or npm run dev, escalate to the Vite 8 migration (Option 2 / fallback). The allowlist stopgap is rejected — we fix by version, not by suppression. Prevention/early-warning work (nightly audit gate + Renovate vulnerability surfacing) is tracked separately in its own issue, not here.

Current state (verified on frontend/package.json)

  • @sveltejs/kit ^2.60.1 · vite ^7.3.3 · @sveltejs/vite-plugin-svelte ^6.2.1 · @sentry/sveltekit ^10.53.1 · svelte ^5.43.8
  • No overrides block exists.
  • Installed esbuild resolves to 0.27.7 (transitively via vite).

Advisories reported (verified by running the exact gate command — 7 findings: 4 high, 3 low)

High (4 — these fail --audit-level=high):

  • esbuildMissing binary integrity verification in Deno module enables RCE via NPM_CONFIG_REGISTRYGHSA-gv7w-rqvm-qjhr
  • esbuildArbitrary file read when running the dev server on WindowsGHSA-g7r4-m6w7-qqqr
  • Vulnerable range: esbuild 0.17.0 - 0.28.0 (inclusive) — so installed 0.27.7 is affected; first fixed release is esbuild@0.28.1. Pulled in transitively via vite@sveltejs/vite-plugin-svelte.

Low (3):

  • cookie <0.7.0accepts out-of-bounds characters in name/path/domainGHSA-pxg6-pf52-xh8x — via @sveltejs/kit, reaching the prod audit tree through @sentry/sveltekit's peer dependency. Below the high gate threshold (does not block CI), but cookie@0.7.0 is a drop-in, so clear it in the same pass.

Exploitability note (honest prioritisation — confirmed accurate in review)

Both high findings are build/dev-time in this project's context: the esbuild dev-server file-read is Windows-only and not exposed in CI/prod; the Deno-module RCE requires a Deno toolchain we don't use. Nothing vulnerable ships to end users at runtime. The urgency is about unblocking the CI gate on all PRs, not a live production exposure — hence P1-high (workflow friction), not P0.

Why the obvious paths don't work

  • Do NOT run npm audit fix --force. It wants @sveltejs/kit@0.0.30 (a nonsensical downgrade that reintroduces years of patched framework CVEs) and vite@8.0.16 (a major breaking bump). It would wreck the toolchain.
  • A scoped vite@7 minor bump cannot help (verified dead-end). No vite@7.x up to the current 7.3.5 accepts a patched esbuild — they all pin esbuild: "^0.27.0" (<0.28.0), and the fix only exists at 0.28.1. There is no patched esbuild within ^0.27.

Primary fix — Option 1: overrides pins

Add to frontend/package.json:

"overrides": {
  "esbuild": "0.28.1",
  "cookie": ">=0.7.0"
}

Pin esbuild exact (no caret) so a future 0.29.x can't silently float in on the next npm install and re-break vite — let Renovate propose bumps explicitly.

Known risk (must be validated, not assumed): this forces esbuild@0.28.1 outside vite@7.3.3's declared ^0.27.0 range. esbuild is pre-1.0; minor bumps can break. The override is only acceptable if both npm run build and a npm run dev boot succeed. If either breaks → escalate (below).

Then regenerate the lockfile deterministically and commit both files in one atomic commit:

cd frontend
npm install        # rewrites package-lock.json with the overridden resolutions

(The CI cache key is hashFiles('frontend/package-lock.json'), so the lockfile change cleanly invalidates the node_modules cache.)

Repo landmines (don't disturb): postinstall runs patch-package; @vitest/browser-playwright@4.1.6 is an intentional exact pin with a backported patch (ADR-012). Neither should be touched by this change.

Fallback — Option 2 (escalate only if Option 1 breaks the build/dev server): Vite 8 migration

This is not "bump vite" — it's a coordinated multi-package migration, because @sveltejs/vite-plugin-svelte@6 peers vite: ^6.3.0 || ^7.0.0 and will not accept vite@8:

  • vite^8
  • @sveltejs/vite-plugin-svelte^7 (peer-requires vite ^8 and svelte ^5.46.4)
  • svelte≥5.46.4
  • @sveltejs/kit resolves to 2.65 (its peers already accept vite^8)

Spin this out into its own issue + a short ADR in docs/adr/ if it's needed. Its verification is heavier than Option 1 (see below).


Acceptance criteria

  • cd frontend && npm audit --audit-level=high --omit=dev exits 0 with 0 high in the report (the exact command the CI step runs).
  • package.json overrides and the regenerated package-lock.json are committed together.
  • The verification checklist below passes (every line binary).
  • No functional regression — defined concretely as: npm run build succeeds and npm run dev boots and serves one route without an esbuild transform error.

Verification checklist (paste into the PR)

[ ] cd frontend && npm install
[ ] npm audit --audit-level=high --omit=dev   -> exit 0, "0 high"
[ ] npm run build                              -> succeeds (incl. postbuild wasm assert)
[ ] npm run check                              -> NO NEW errors vs the known baseline (CI doesn't gate on check)
[ ] npm run lint                               -> clean
[ ] npm run test                               -> green
[ ] npm run dev                                -> boots, one route renders, no esbuild transform error -> kill

The last line is what actually de-risks Option 1 — do not drop it.

Out of scope

  • Backend dependencies (this is a frontend/npm toolchain issue).
  • A blanket dependency-freshness sweep.
  • Prevention/early-warning (nightly audit gate + Renovate vulnerability surfacing) — tracked in its own follow-up issue so this stays a small, reviewable security fix.
## Context The CI step **"Security audit (no dev deps)"** (`npm audit --audit-level=high --omit=dev`, `.gitea/workflows/ci.yml:32-33`) fails with exit 1. This is **repo-wide and not caused by any one PR** — `frontend/package-lock.json` was last changed on `main` at `fab2930c` (2026-06-03), and the failure is driven by **newly-published advisories** landing against already-pinned versions. `main` itself is red on this gate today; it surfaced on the #774 PR run but #774 touched zero frontend files. Net effect: the audit gate blocks **every** PR until remediated. > **Decision (2026-06-12, post-review):** Primary fix is the `overrides` block (Option 1 below). **If the override breaks `npm run build` or `npm run dev`, escalate to the Vite 8 migration (Option 2 / fallback).** The allowlist stopgap is **rejected** — we fix by version, not by suppression. Prevention/early-warning work (nightly audit gate + Renovate vulnerability surfacing) is **tracked separately in its own issue**, not here. ## Current state (verified on `frontend/package.json`) - `@sveltejs/kit` `^2.60.1` · `vite` `^7.3.3` · `@sveltejs/vite-plugin-svelte` `^6.2.1` · `@sentry/sveltekit` `^10.53.1` · `svelte` `^5.43.8` - No `overrides` block exists. - Installed `esbuild` resolves to **`0.27.7`** (transitively via `vite`). ## Advisories reported (verified by running the exact gate command — 7 findings: 4 high, 3 low) **High (4 — these fail `--audit-level=high`):** - `esbuild` — *Missing binary integrity verification in Deno module enables RCE via `NPM_CONFIG_REGISTRY`* — [GHSA-gv7w-rqvm-qjhr](https://github.com/advisories/GHSA-gv7w-rqvm-qjhr) - `esbuild` — *Arbitrary file read when running the dev server on Windows* — [GHSA-g7r4-m6w7-qqqr](https://github.com/advisories/GHSA-g7r4-m6w7-qqqr) - Vulnerable range: **`esbuild 0.17.0 - 0.28.0` (inclusive)** — so installed `0.27.7` is affected; **first fixed release is `esbuild@0.28.1`**. Pulled in transitively via `vite` → `@sveltejs/vite-plugin-svelte`. **Low (3):** - `cookie <0.7.0` — *accepts out-of-bounds characters in name/path/domain* — [GHSA-pxg6-pf52-xh8x](https://github.com/advisories/GHSA-pxg6-pf52-xh8x) — via `@sveltejs/kit`, reaching the prod audit tree through `@sentry/sveltekit`'s peer dependency. **Below the `high` gate threshold** (does not block CI), but `cookie@0.7.0` is a drop-in, so clear it in the same pass. ## Exploitability note (honest prioritisation — confirmed accurate in review) Both high findings are **build/dev-time** in this project's context: the esbuild dev-server file-read is **Windows-only** and not exposed in CI/prod; the Deno-module RCE requires a **Deno toolchain we don't use**. Nothing vulnerable ships to end users at runtime. The urgency is about **unblocking the CI gate on all PRs**, not a live production exposure — hence **P1-high** (workflow friction), not P0. ## Why the obvious paths don't work - **Do NOT run `npm audit fix --force`.** It wants `@sveltejs/kit@0.0.30` (a nonsensical downgrade that reintroduces years of patched framework CVEs) and `vite@8.0.16` (a major breaking bump). It would wreck the toolchain. - **A scoped `vite@7` minor bump cannot help (verified dead-end).** No `vite@7.x` up to the current `7.3.5` accepts a patched esbuild — they all pin `esbuild: "^0.27.0"` (`<0.28.0`), and the fix only exists at `0.28.1`. There is no patched esbuild *within* `^0.27`. --- ## Primary fix — Option 1: `overrides` pins Add to `frontend/package.json`: ```json "overrides": { "esbuild": "0.28.1", "cookie": ">=0.7.0" } ``` Pin `esbuild` **exact** (no caret) so a future `0.29.x` can't silently float in on the next `npm install` and re-break vite — let Renovate propose bumps explicitly. **Known risk (must be validated, not assumed):** this forces `esbuild@0.28.1` *outside* `vite@7.3.3`'s declared `^0.27.0` range. esbuild is pre-1.0; minor bumps can break. The override is only acceptable if **both** `npm run build` and a `npm run dev` boot succeed. If either breaks → escalate (below). Then regenerate the lockfile deterministically and commit **both** files in one atomic commit: ```bash cd frontend npm install # rewrites package-lock.json with the overridden resolutions ``` (The CI cache key is `hashFiles('frontend/package-lock.json')`, so the lockfile change cleanly invalidates the node_modules cache.) > **Repo landmines** (don't disturb): `postinstall` runs `patch-package`; `@vitest/browser-playwright@4.1.6` is an intentional exact pin with a backported patch (ADR-012). Neither should be touched by this change. ## Fallback — Option 2 (escalate only if Option 1 breaks the build/dev server): Vite 8 migration This is **not** "bump vite" — it's a coordinated multi-package migration, because `@sveltejs/vite-plugin-svelte@6` peers `vite: ^6.3.0 || ^7.0.0` and will **not** accept vite@8: - `vite` → `^8` - `@sveltejs/vite-plugin-svelte` → `^7` (peer-requires `vite ^8` **and** `svelte ^5.46.4`) - `svelte` → `≥5.46.4` - `@sveltejs/kit` resolves to `2.65` (its peers already accept vite^8) Spin this out into **its own issue + a short ADR** in `docs/adr/` if it's needed. Its verification is heavier than Option 1 (see below). --- ## Acceptance criteria - `cd frontend && npm audit --audit-level=high --omit=dev` exits `0` with **`0 high`** in the report (the exact command the CI step runs). - `package.json` `overrides` **and** the regenerated `package-lock.json` are committed together. - The verification checklist below passes (every line binary). - No functional regression — defined concretely as: `npm run build` succeeds and `npm run dev` boots and serves one route without an esbuild transform error. ## Verification checklist (paste into the PR) ``` [ ] cd frontend && npm install [ ] npm audit --audit-level=high --omit=dev -> exit 0, "0 high" [ ] npm run build -> succeeds (incl. postbuild wasm assert) [ ] npm run check -> NO NEW errors vs the known baseline (CI doesn't gate on check) [ ] npm run lint -> clean [ ] npm run test -> green [ ] npm run dev -> boots, one route renders, no esbuild transform error -> kill ``` The last line is what actually de-risks Option 1 — do not drop it. ## Out of scope - Backend dependencies (this is a frontend/npm toolchain issue). - A blanket dependency-freshness sweep. - **Prevention/early-warning** (nightly audit gate + Renovate vulnerability surfacing) — tracked in its own follow-up issue so this stays a small, reviewable security fix.
marcel added the P1-highdevopssecurity labels 2026-06-12 23:46:03 +02:00
Author
Owner

escelate if this does not work. I think we need a new issue to correctly setup renovate

escelate if this does not work. I think we need a new issue to correctly setup renovate
Author
Owner

Implemented — Option 1 (overrides), PR #819

Option 1 held; no escalation to the Vite 8 migration was needed. Build and dev-server both succeed with the forced esbuild@0.28.1.

Change (frontend/package.json + regenerated package-lock.json, one atomic commit d11378c2):

"overrides": {
  "esbuild": "0.28.1",
  "cookie": ">=0.7.0"
}
  • esbuild exact-pinned (no caret) — Renovate proposes future bumps explicitly.
  • cookie floats to 1.1.1 (satisfies >=0.7.0), clearing the low cookie finding in the prod tree.
  • patch-package/postinstall and the @vitest/browser-playwright@4.1.6 pin (ADR-012) untouched.

Verification (red→green proof):

  • Before: gate exits 1, 7 vulns (4 high, 3 low).
  • After: npm audit --audit-level=high --omit=devexit 0, "found 0 vulnerabilities".
  • npm ls esbuildesbuild@0.28.1 overridden.
  • npm run build → adapter-node ✔ done + postbuild pdfjs-wasm assert pass.
  • npm run lint → clean.
  • npm run check → 801 errors / 38 warnings (matches known baseline; 0 in the files touched).
  • npm run dev → boots, /hilfe/transkription renders 200, no esbuild transform error → killed.
  • npm run test → deferred to CI (full Vitest browser sweep OOMs this dev box; change touches only dep versions, zero source).

Follow-up: prevention/early-warning (nightly audit gate + Renovate vulnerability surfacing) remains for its own issue, per the decision above — I have not opened it yet; say the word and I'll spin it up with a proper Renovate spec.

Review at PR #819.

## Implemented — Option 1 (`overrides`), PR #819 Option 1 held; **no escalation to the Vite 8 migration was needed.** Build and dev-server both succeed with the forced `esbuild@0.28.1`. **Change** (`frontend/package.json` + regenerated `package-lock.json`, one atomic commit `d11378c2`): ```json "overrides": { "esbuild": "0.28.1", "cookie": ">=0.7.0" } ``` - `esbuild` exact-pinned (no caret) — Renovate proposes future bumps explicitly. - `cookie` floats to `1.1.1` (satisfies `>=0.7.0`), clearing the low `cookie` finding in the prod tree. - `patch-package`/`postinstall` and the `@vitest/browser-playwright@4.1.6` pin (ADR-012) untouched. **Verification** (red→green proof): - Before: gate exits `1`, **7 vulns (4 high, 3 low)**. - After: `npm audit --audit-level=high --omit=dev` → **exit 0, "found 0 vulnerabilities"**. - `npm ls esbuild` → `esbuild@0.28.1 overridden`. - `npm run build` → adapter-node `✔ done` + postbuild pdfjs-wasm assert pass. - `npm run lint` → clean. - `npm run check` → 801 errors / 38 warnings (matches known baseline; 0 in the files touched). - `npm run dev` → boots, `/hilfe/transkription` renders 200, **no esbuild transform error** → killed. - `npm run test` → deferred to CI (full Vitest browser sweep OOMs this dev box; change touches only dep versions, zero source). **Follow-up:** prevention/early-warning (nightly audit gate + Renovate vulnerability surfacing) remains for its own issue, per the decision above — I have **not** opened it yet; say the word and I'll spin it up with a proper Renovate spec. Review at PR #819.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#817