Files
multica/server
Eve fbc37b0fe0 MUL-3132: harden /uploads/* (auth, no listing, nosniff, tight CSP)
Closes the open hardening items from the SVG XSS disclosure
(security-findings-2026-06-02). The primary chain (PR #3023 / #3050)
is intact; this PR addresses every remaining recommendation from the
disclosure's hardening list except 'serve uploads from a separate
origin' (a structural change beyond this fix).

Changes:

- /uploads/* now requires authentication. The route is wrapped in
  middleware.Auth so anonymous internet users can no longer fetch
  workspace attachments by guessing the URL. A new ServeLocalUpload
  handler then enforces the second layer:
    - workspaces/{wsID}/* paths require membership in wsID (uses
      MembershipCache for the hot path);
    - users/{userID}/* paths allow any authenticated user (avatars
      are referenced cross-workspace);
    - any other prefix returns 404, so a future feature cannot drop
      content under /uploads/<other-prefix>/ and inherit a relaxed
      policy by accident.
  Non-members see 404 (not 403) so the route does not act as an IDOR
  oracle for workspace IDs.

- Directory listing on /uploads/* is rejected at the storage layer:
  empty keys, trailing-slash keys, and any key that resolves to a
  directory return 404 before http.ServeFile would render an HTML
  index. UUID filenames were obscurity, but enumerating them
  shouldn't be free.

- Every successful /uploads/* response carries
  X-Content-Type-Options: nosniff and a tight per-response CSP
  (default-src 'none'; sandbox; frame-ancestors 'none'), overriding
  the application-wide CSP. This is belt-and-suspenders if a future
  regression weakens the Content-Disposition: attachment path.

- UploadFile rejects HTML-family uploads at the edge (.html, .htm,
  .xhtml, .shtml, .xht, .phtml, plus a content-type denylist for
  text/html and application/xhtml+xml so renamed payloads cannot
  bypass the extension check). SVG and JS remain allowed because
  their existing serve-side defenses neutralize them and source-code
  attachments preview as text/plain via /api/attachments/{id}/content.

Tests:

- storage: TestLocalStorage_ServeFile_RejectsDirectoryListing,
  TestLocalStorage_ServeFile_HardeningHeaders.
- handler: TestIsUploadDenied (pure), TestUploadFile_RejectsHTMLByExtension,
  TestUploadFile_RejectsHTMLByContentType, TestUploadFile_AllowsLegitimateImage,
  and the full ServeLocalUpload matrix (RequiresAuth, MemberCanRead,
  NonMemberDenied, RejectsDirectoryInPath, UnknownPrefixDenied,
  UserPrefixAllowsAnyAuthedUser).
- Full server test suite passes.

Co-authored-by: multica-agent <github@multica.ai>
2026-06-08 17:14:22 +08:00
..