security(deps): bump @sveltejs/kit + vite to clear BODY_SIZE_LIMIT bypass + 5 high devDep CVEs #458
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
Pre-prod audit (Appendix A.2 of
docs/audits/2026-05-07-pre-prod-architectural-review.md) rannpm audit --jsonagainstfrontend/. Production deps are clean (0 vulnerabilities), but dev/transitive deps have 12 advisories — 7 HIGH, 4 MODERATE, 1 LOW. Notably, two HIGHs are in direct production dependencies (@sveltejs/kitandvite).Direct production-dep CVEs (must fix)
@sveltejs/kit): Unvalidated redirect inhandlehook causes Denial-of-Service.@sveltejs/adapter-node):BODY_SIZE_LIMITbypass — combines badly with audit F-12 (no AV scan) to allow large adversarial uploads.vite): Path traversal in optimized-deps.maphandling.vite):server.fs.denybypass with queries.vite): Arbitrary file read via dev-server WebSocket.Transitive moderate/high CVEs cleared by
npm updatekyselySQL injection (transitive via paraglide).picomatchReDoS,flattedprototype pollution,postcssXSS,brace-expansionReDoS,uuidbounds check,yamlstack overflow.Approach
If a dep refuses to update because of a peer constraint, document the residual CVE and the upstream blocker in this issue.
Critical files
frontend/package.jsonfrontend/package-lock.jsonVerification
npm audit --omit=devreturns 0 vulnerabilities.npm audit --audit-level=highreturns 0 advisories at HIGH or CRITICAL.npm run check && npm run test && npm run buildall pass.docker compose build frontend && docker compose up -d frontendhealthy.Acceptance criteria
@sveltejs/kitis at the patched version listed in GHSA-3f6h-2hrp-w5wx (≥ 2.50.0 or current latest).viteis at the patched version listed in GHSA-4w7w-66w2-5vf9 (≥ 7.3.0 or current latest).npm audit --omit=devreports 0 vulnerabilities.npm audit --audit-level=high.Effort
S — 1 hour including verification.
Risk if not addressed
BODY_SIZE_LIMITbypass + missing AV scan = an attacker can upload arbitrarily large malicious files. Vite path-traversal and dev-server WebSocket arbitrary-read are dev-time-only but expose the developer host during local work.Tracked in audit doc as part of F-22 (dependency hygiene).
👨💻 Markus Keller — Application Architect
Observations
package-lock.jsonshows@sveltejs/kit@2.55.0(vulnerable ≤2.57.0) andvite@7.3.1(vulnerable ≤7.3.1). Both are one minor/patch bump away from the fix. No schema changes, no new routes, no new services — the blast radius of this bump is confined tofrontend/package.json+package-lock.json.adapter-nodeships no explicitBODY_SIZE_LIMITindocker-compose.ymlorsvelte.config.js. The built artefact (build/handler.js) readsenv('BODY_SIZE_LIMIT', '512K')— meaning the default 512 KB cap is active, but the bypass (GHSA-2crg-3p73-43xp) makes that cap unenforced until the kit version is updated. The combination with audit finding F-12 (no AV scan) is correctly flagged in the issue..mappath traversal), GHSA-v2wj-q39q-566r (fs.denybypass), and GHSA-p9ff-h696-f583 (WebSocket arbitrary file read) are all in the Vite dev server. In production the adapter-node builds a standalone server that does not use Vite's dev server at all. The production exposure is the@sveltejs/kitpair, not Vite. The Vite fix still matters for developer workstation protection.renovate.jsonexists but is misconfigured for this issue. The file has noplatform: "gitea", noautomerge, and nomatchDepTypesrule that would catch dev-dependency CVEs automatically. This is a systemic gap that caused the 12 advisories to accumulate rather than being caught as they appeared.package.jsontouch none of the doc-update triggers (no new routes, no schema changes, no new packages, no new architecture decisions).Recommendations
npm update @sveltejs/kit @sveltejs/adapter-node vite && npm update && npm audit --omit=dev && npm audit --audit-level=high) is the correct, minimal intervention. Do not pin to a specific version string — let semver do its job.BODY_SIZE_LIMITenv var todocker-compose.ymlunder thefrontendservice, set to a value appropriate for the largest expected upload payload (e.g.50Mif PDFs up to ~50 MB are expected). Making the cap explicit prevents the default from silently changing under a future kit upgrade.renovate.jsonin a follow-up issue. Add"platform": "gitea","automerge": truefor patch updates, and"automergeType": "pr"for minor updates requiring review. This PR should not be blocked on that, but the gap should be tracked.🔐 Nora "NullX" Steiner — Security Engineer
Observations
npm audit --jsonclassifies them as: GHSA-3f6h-2hrp-w5wx (SvelteKit DoS redirect) = moderate; GHSA-2crg-3p73-43xp (BODY_SIZE_LIMIT bypass) = high; GHSA-4w7w-66w2-5vf9 (Vite.mappath traversal) = moderate; GHSA-v2wj-q39q-566r (Vitefs.denybypass) = high; GHSA-p9ff-h696-f583 (Vite WebSocket file read) = high. This doesn't change the remediation priority, but accurate classification matters for future audit comparisons.@sveltejs/kit@2.55.0,@sveltejs/adapter-node@5.5.4) is in the vulnerable range (≤2.57.0). The builthandler.jsshows the default 512 KB limit, but the bypass means this limit is not reliably enforced: a crafted multipart request can exceed it. Combined with the absence of file-type validation at the backend boundary and no AV scan (finding F-12 from the audit), an attacker can upload oversized malicious payloads.vite-plugin-devtools-jsonis in devDependencies; the production adapter runs a Node HTTP server, not Vite's dev server. GHSA-p9ff-h696-f583 (WebSocket arbitrary file read) is only exploitable whenvite devis running. The developer workstation is still an attack surface worth closing, but this is not a production risk.kyselySQL injection (via@inlang/paraglide-js) is worth understanding. Kysely is used by Paraglide for its own internals — it is not a direct query channel for application data. If Paraglide does not pass user input into Kysely queries, this is a packaging artifact, not an exploitable path in this application. Still, bump it withnpm update.flatted(prototype pollution, HIGH) andpicomatch(ReDoS, HIGH) are pure transitive dev-tool deps. Not production-exposed.npm audit --omit=dev). That's appropriate — this vulnerability class doesn't lend itself to a unit test. The acceptance criteria (0 vulnerabilities on--omit=dev, 0 HIGH on--audit-level=high) are the right test oracle here.renovate.jsonhas no Gitea platform config and no security-patch automerge. This is directly why 12 advisories accumulated. A properly configured Renovate would have auto-merged the patch that fixed these CVEs within days of publication.Recommendations
curl -X POST -F "file=@large.pdf" http://localhost:3000/api/documentswith a 10 MB file should return 413 once the patched version is deployed.platform: "gitea"and security-patch automerge torenovate.jsonin a follow-up issue (P2, but create the issue now). Reference the pre-prod audit finding F-22. Every day without Renovate properly configured is a day these CVEs could re-enter undetected.npm audit --audit-level=highas a CI gate in.gitea/workflows/ci.yml. Insert it as a step in theunit-testsjob afternpm ci. It takes under 10 seconds and blocks merges when a HIGH advisory enters the tree. This directly addresses finding F-22 from the architectural review.FileService, no magic-byte check) should be tracked as a distinct issue referencing F-12.Open Decisions
BODY_SIZE_LIMITvalue for production: The current default is 512 KB, which is far below the expected PDF upload size. Should this be set explicitly indocker-compose.ymlbefore going live, and if so, what is the largest document the system should accept? This affects both the node adapter limit and any reverse-proxyclient_max_body_sizethat Tobias configures in Caddy. The two values must be consistent.🛠️ Felix Brandt — Fullstack Developer
Observations
npm update @sveltejs/kit @sveltejs/adapter-node vite && npm updatetouches onlypackage-lock.jsonand the version ranges inpackage.json. There are no API surface changes, no breaking changes in the SvelteKit 2.55.0→2.58.x or vite 7.3.1→7.3.2 changelog that affect application code.@sveltejs/kit@2.55.0,vite@7.3.1,@sveltejs/adapter-node@5.5.4— all in vulnerable ranges.npm updateresolves to the latest semver-compatible version within the declared^ranges.npm run check && npm run test && npm run buildcovers type-checking, unit/component tests, and build. The missing step isnpm run lint— add it betweencheckandtestto catch any formatter regressions in the lock file diff.svelte.config.jsis minimal:adapter({ })with no options. TheBODY_SIZE_LIMITbypass fix ships with the@sveltejs/kitbump, not via config changes. Nosvelte.config.jsedits are needed.renovate.jsonis present but has no automerge or Gitea platform config. This is a dev-workflow gap, not a blocker for this PR. The issue is correctly scoped to the dependency bump itself.npm audit --omit=dev→ 0) are the right test oracle. No failing test to write before the fix here.Recommendations
npm update @sveltejs/kit @sveltejs/adapter-node vitefirst (direct deps), thennpm update(transitive cleanup). Doingnpm updatealone may not fully resolve the direct dep constraints.npm run lintto the verification sequence in the issue — the full verification should be:npm audit --omit=dev && npm audit --audit-level=high && npm run lint && npm run check && npm run test && npm run build. This matches the CI pipeline exactly.security(deps): bump @sveltejs/kit and vite to clear 5 high CVEs. Do not bundle any unrelated change into this commit — the lockfile diff will be large enough on its own (many transitive hashes change).vite.config.tsis unaffected. TheoptimizeDeps.includearray (pdfjs-dist,@tiptap/*) and the proxy config should function identically in vite 7.3.2. Do a quicknpm run devsmoke test before pushing.🚀 Tobias Wendt — DevOps & Platform Engineer
Observations
docker-compose.ymlhas noBODY_SIZE_LIMITenv var on thefrontendservice. The adapter falls back to its compiled default (512 KB as seen inbuild/handler.js). The BODY_SIZE_LIMIT bypass CVE (GHSA-2crg-3p73-43xp) means this default is unenforced until the kit is patched. After the bump, the 512 KB default will be enforced — but 512 KB will reject any normal PDF upload, so this needs an explicit value before production.client_max_body_sizeor equivalent in the reverse proxy config. The Caddy configuration is not in the repo (infra/is a stub), but based on the architectural review, there's no Caddyfile committed. The SvelteKit node adapter'sBODY_SIZE_LIMITand Caddy'smax_bodymust agree, or one layer will reject requests the other would have passed. This is a deployment-time coordination item.renovate.jsonis not configured for Gitea and has no automerge. Without"platform": "gitea", Renovate may not connect to the self-hosted instance at all. The 12 accumulated advisories are a direct consequence — each one had a fix available (allfixAvailable: true) but no automation created a PR. This is the systemic issue behind finding F-22.ci.yml) has nonpm auditgate. Lint, tests, and build run — but a HIGH advisory can merge undetected. Addingnpm audit --audit-level=high --omit=devas a CI step would have flagged these 7 advisories before they merged../frontend/Dockerfile. After the bump, adocker compose build frontendis required to pick up the new packages in the production image. The issue correctly includes this as a verification step.frontend_node_modulesnamed volume indocker-compose.ymlmeans the host-mounted source and the container'snode_modulesare separate. Afternpm updateon the host,docker compose build frontendrebuilds the image with the updated packages — the named volume is a dev convenience, not a concern for production builds.Recommendations
BODY_SIZE_LIMITexplicitly indocker-compose.ymlunderfrontend.environment. Decide on the maximum upload size first (see Open Decisions), then set it consistently here and in the Caddy reverse proxy config when that is committed. A mismatch where Caddy allows 100 MB but the adapter rejects at 512 KB will cause confusing 413 errors.npm audit --audit-level=high --omit=devtoci.ymlas a step in theunit-testsjob, immediately afternpm ci. This is a one-liner that takes under 10 seconds and prevents this class of issue from merging in future:renovate.jsonfor Gitea in a separate issue/PR (reference F-22). Add"platform": "gitea","endpoint": "http://heim-nas:3005/api/v1", and"automerge": truefor patch updates. This is the highest-leverage infrastructure change for long-term dependency hygiene at essentially zero cost.docker compose build frontend && docker compose up -d frontend. Then verify withdocker compose ps frontendthat the container is healthy before closing the issue.Open Decisions
BODY_SIZE_LIMITvalue: What is the maximum document size the system should accept? Historical Kurrent/Sütterlin documents scanned at 300 DPI run 2–15 MB per page as TIFF/PNG, or 500 KB–5 MB as PDF. SettingBODY_SIZE_LIMIT=50Min the adapter andclient_max_body_size 52min Caddy (slightly larger to allow HTTP overhead) is a reasonable production starting point — but this needs to be a deliberate decision, not a default. The two values must be aligned.📋 Elicit — Requirements Engineer
Observations
npm auditreports GHSA-3f6h-2hrp-w5wx and GHSA-4w7w-66w2-5vf9 as moderate, not high. This is not a blocking gap, but misclassification in an issue body can lead to incorrect future triage — if someone re-runsnpm auditand sees "moderate" where the issue says "HIGH," they may underestimate the discrepancy.@sveltejs/adapter-nodeis missing. The issue requires@sveltejs/kit ≥ 2.50.0andvite ≥ 7.3.0but does not state the required version of@sveltejs/adapter-node. Since GHSA-2crg-3p73-43xp (BODY_SIZE_LIMIT bypass) is the primary production CVE and it ships viaadapter-node, a version criterion for that package would make the acceptance criteria complete. Currently@sveltejs/adapter-node@5.5.4is installed; the fix was released in5.6.0— this should be stated.npm run dev, not the production server") would prevent future readers from over- or under-reacting.BODY_SIZE_LIMITexplicit configuration and the CI audit gate (both recommended by other reviewers) are included in this issue's scope, the effort is closer to S+. The issue should either explicitly include or explicitly exclude those tasks from scope.Recommendations
npm auditoutput. This keeps the issue as the source of truth for future auditors.@sveltejs/adapter-node:@sveltejs/adapter-node is at ≥ 5.6.0 (the patched version for GHSA-2crg-3p73-43xp).npm auditto CI, each with their own acceptance criteria.🧪 Sara Holt — QA Engineer
Observations
.gitea/workflows/ci.ymlpipeline runs lint, unit/component tests (Vitest), OCR unit tests, and backend unit/integration tests — but nonpm auditstep. This means all 12 current advisories (7 HIGH, 4 MODERATE, 1 LOW) would have merged into main without any CI warning.npm updatecommands →npm audit --omit=dev→npm audit --audit-level=high→npm run check→npm run test→npm run build→docker compose build/up. This is the right sequence — fast static checks first, expensive build last.npm run lintis absent from the verification steps. The CI pipeline runs lint; the verification steps should match CI exactly. A lockfile change (largepackage-lock.jsondiff) is unlikely to trigger a lint failure, but omitting it means the manual verification diverges from the automated gate.npm audit --omit=dev→ 0 vulnerabilities,npm audit --audit-level=high→ 0 high advisories,npm run check && npm run test && npm run build→ all pass,docker compose build && up→ healthy. These criteria map directly to CI steps and can be verified without human judgment.npm auditgate in CI, not a test file.npm testafter the update is essential before committing.@vitest/browser-playwrightpackage (^4.0.10) and related dev packages are unlikely to be affected by the bump, but the browser component tests should be explicitly confirmed to pass — they run against real Chromium and are the most brittle part of the suite.Recommendations
npm audit --audit-level=high --omit=devas a CI step in.gitea/workflows/ci.yml, in theunit-testsjob afternpm ciand before lint. This is the most valuable QA change this issue can deliver beyond the bump itself, and it closes the systemic gap permanently:npm run lintas step 5, betweennpm run checkandnpm run test. This makes the manual checklist match CI exactly.npm testruns all Vitest projects including the browser project. Confirm the output shows the browser project passing, not just the server project.@sveltejs/kit resolved to 2.58.x) rather than just closing the issue after the PR merges.🎨 Leonie Voss — UI/UX Design Lead
Observations
@sveltejs/kit,@sveltejs/adapter-node, andviteare infrastructure-layer changes. No Svelte components change, no routes change, no visual output changes. From a design perspective, this is a zero-impact issue.documents/new/maps 413 responses explicitly./documents/new/) uses a standard form action. If the SvelteKit adapter rejects the body before it reaches the form action, SvelteKit may return a raw 413 with no localized error message — theGlobalExceptionHandleron the Spring Boot side wouldn't even be invoked, since the rejection happens at the adapter layer.Recommendations
BODY_SIZE_LIMITbefore deploying the patched adapter. The fix must not accidentally enforce 512 KB in production — that would break every PDF upload. Set an explicit value indocker-compose.ymlthat reflects the actual maximum document size (see the open decision raised by Nora and Tobias).BODY_SIZE_LIMITis set to a real value (e.g. 50 MB), test uploading a file that exceeds it. Confirm that the user sees a meaningful error message rather than a blank 413 page. If the 413 is not caught by the form action's error handler, add a mapping in the upload flow.No design review needed beyond these two points — this is primarily a security/infrastructure concern.
Decision Queue — Open Items Requiring Human Judgment
Consolidated from the multi-persona review. Each item needs a decision before this issue can be fully closed. Items are grouped by theme.
Theme 1:
BODY_SIZE_LIMIT— What value, and where?Raised by: Nora (Security), Tobias (DevOps), Leonie (UX)
The problem: Patching the GHSA-2crg-3p73-43xp bypass means the adapter's
BODY_SIZE_LIMITwill be enforced for the first time. The current default is 512 KB, which will reject every normal PDF upload. This limit must be raised before deploying the patched adapter, not after.What needs deciding:
docker-compose.yml:BODY_SIZE_LIMIT=50Munderfrontend.environmentclient_max_body_size(not yet in repo — needs to be present before production go-live)GlobalExceptionHandleron the Spring Boot side is never invoked. The upload route (/documents/new/) may show a raw 413 rather than a localized error. This needs a quick manual test after the limit is set.Recommendation from the review panel (non-binding): Set
BODY_SIZE_LIMIT=50Mindocker-compose.ymlas part of this PR, and create a follow-up issue to test the 413 UX path.Theme 2: Scope of this issue — bump only, or also CI gate + Renovate?
Raised by: Elicit (Requirements), Sara (QA), Tobias (DevOps), Nora (Security)
The problem: All four personas independently recommend adding
npm audit --audit-level=high --omit=devas a CI gate and fixingrenovate.jsonfor Gitea. These are the systemic fixes that prevent this class of issue from recurring. They are currently not in scope for this issue.What needs deciding: Should this issue's scope expand to include:
npm audit --audit-level=high --omit=devto.gitea/workflows/ci.yml(15-minute task)renovate.jsonwith Gitea platform config and patch automerge (30-minute task)Or should these become separate issues that are created and prioritized alongside this one?
Recommendation from the review panel: Include the CI audit gate in this PR (it is a one-liner with no risk). Create a separate issue for Renovate configuration — it has more moving parts (Gitea token, self-hosted runner access, endpoint config) and should not delay this security bump.