fix: bound admin image uploads and use a CSPRNG for stored filenames #4

Merged
bermudalamb merged 1 commits from fix/sonarqube-upload-limits-and-weak-rng into main 2026-08-14 09:26:00 -05:00
Owner

Resolves three SonarQube security hotspots in backend/src/routes/admin.ts:

  • Content length limit (x2) — multer() was configured with no limits, so uploads were unbounded on both the diskStorage and multer() calls.
  • Pseudorandom number generator — stored filenames were built from Date.now() plus Math.round(Math.random() * 1e6).

Changes

  • Cap every dimension of the multipart body: 6 files, 8 MiB per image, 8 fields, 64 KiB per field. Without limits a single request could fill the uploads volume.
  • Generate stored filenames with crypto.randomUUID() so paths aren't guessable. Image ordering is unaffected — sort_order already drives it.
  • Wrap the upload middleware as uploadImages, translating MulterError into 413 (oversized file) or 400 (other limit violations) with the { error } JSON shape the rest of the router uses. app.ts mounts no error-handling middleware, so a limit rejection would otherwise have surfaced as an HTML 500.

Verification

  • npx tsc --noEmit — clean
  • npm run test:unit — 16/16 pass
  • Integration tests not run locally (need the Docker Postgres via npm run db:test:up); they contain no admin-upload coverage. CI will run them.

🤖 Generated with Claude Code

Resolves three SonarQube security hotspots in `backend/src/routes/admin.ts`: - **Content length limit** (x2) — `multer()` was configured with no `limits`, so uploads were unbounded on both the `diskStorage` and `multer()` calls. - **Pseudorandom number generator** — stored filenames were built from `Date.now()` plus `Math.round(Math.random() * 1e6)`. ## Changes - Cap every dimension of the multipart body: 6 files, 8 MiB per image, 8 fields, 64 KiB per field. Without limits a single request could fill the uploads volume. - Generate stored filenames with `crypto.randomUUID()` so paths aren't guessable. Image ordering is unaffected — `sort_order` already drives it. - Wrap the upload middleware as `uploadImages`, translating `MulterError` into `413` (oversized file) or `400` (other limit violations) with the `{ error }` JSON shape the rest of the router uses. `app.ts` mounts no error-handling middleware, so a limit rejection would otherwise have surfaced as an HTML 500. ## Verification - `npx tsc --noEmit` — clean - `npm run test:unit` — 16/16 pass - Integration tests not run locally (need the Docker Postgres via `npm run db:test:up`); they contain no admin-upload coverage. CI will run them. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bermudalamb added 1 commit 2026-08-14 09:23:49 -05:00
fix: bound admin image uploads and use a CSPRNG for stored filenames
SonarQube Analysis / sonarqube (pull_request) Successful in 3m49s
Tests / backend-unit (pull_request) Successful in 33s
Tests / backend-integration (pull_request) Successful in 1m6s
Tests / frontend-e2e (pull_request) Failing after 1m7s
aba47c81e3
SonarQube flagged three hotspots in the admin router: multer was
configured with no content length limits, and stored filenames were
derived from Date.now() plus Math.random().

- Cap the multipart body on every dimension: 6 files, 8 MiB per image,
  8 fields, 64 KiB per field. Without limits a single request could
  fill the uploads volume.
- Generate stored filenames with crypto.randomUUID() so paths are not
  predictable. Image ordering is unaffected; sort_order already drives it.
- Wrap the upload middleware to translate MulterError into 413/400 JSON.
  The app mounts no error handler, so a limit rejection would otherwise
  surface as an HTML 500.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
bermudalamb merged commit 827f065bb2 into main 2026-08-14 09:26:00 -05:00
bermudalamb deleted branch fix/sonarqube-upload-limits-and-weak-rng 2026-08-14 09:26:01 -05:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bermudalamb/redefined-designs#4