Skip to content

feat(web): guild logo branding with hardened upload (slice 8f)#57

Merged
Musiker15 merged 1 commit into
mainfrom
feat/slice-8f-logo-branding
Jun 20, 2026
Merged

feat(web): guild logo branding with hardened upload (slice 8f)#57
Musiker15 merged 1 commit into
mainfrom
feat/slice-8f-logo-branding

Conversation

@Musiker15

Copy link
Copy Markdown
Member

Phase 1 finish — Task 6/6: Logo branding (hardened upload)

Completes Phase 1 branding (§26 Basis-Branding: Farben, Logo). Guilds can upload a logo shown on their public form and status pages.

Security (the logo is served publicly as <img>, so the upload is hardened)

  • Raster-only allowlist via magic-byte sniff (lib/image.ts) — SVG is rejected (no inline script → no stored XSS); the client-declared MIME is never trusted.
  • The image is decoded and re-encoded with sharp to WebP (resize ≤512, metadata stripped, input-pixel cap) — only pixels survive, neutralising polyglots / hidden payloads / EXIF / decompression bombs.
  • 1 MB limit, manager-only.
  • Served via GET /api/guilds/[guildId]/logo with a fixed image/webp Content-Type + X-Content-Type-Options: nosniff, so it can't be reinterpreted as anything executable.

Other changes

  • shared: brandingSchema gains logoKey; a separate brandingColorSchema keeps the color endpoint from touching the logo (read-merge-write preserves it).
  • dashboard branding page gains a logo upload / preview / remove form.
  • form + status pages render the logo in the header when set (img-src 'self' already covers it).
  • adds sharp (already in pnpm allowBuilds; added to serverExternalPackages so the native module isn't bundled).
  • i18n: branding logo labels (DE/EN); magic-byte unit tests.

Verification

  • pnpm typecheck ✅ · pnpm lint ✅ · pnpm test ✅ (10, incl. image sniff) · pnpm build ✅ (sharp ok; logo routes registered) · sharp re-encode smoke ✅

This is the last Phase-1 item — Phase 1 is now 100% per the concept.

Completes Phase 1 branding (§26 'Basis-Branding: Farben, Logo'). Guilds can
upload a logo shown on their public form + status pages.

Security (logos are served publicly as <img>, so the upload is hardened):
- raster-only allowlist via magic-byte sniff (lib/image.ts) — SVG is rejected
  (no inline script / stored XSS), client MIME is never trusted.
- the image is decoded and re-encoded with sharp to WebP (resize <=512,
  metadata stripped, input-pixel cap) — only pixels survive, neutralising
  polyglots / hidden payloads / EXIF / decompression bombs.
- 1 MB limit, manager-only.
- served via /api/guilds/[guildId]/logo with a fixed image/webp Content-Type
  + X-Content-Type-Options: nosniff, so it can't be reinterpreted.

- shared: brandingSchema gains logoKey; brandingColorSchema keeps the color
  endpoint from touching it (read-merge-write preserves the logo).
- dashboard branding page gains a logo upload/preview/remove form.
- form + status pages render the logo in the header when set.
- adds sharp (already allowed in pnpm allowBuilds; serverExternalPackages).
- i18n: branding logo labels (DE/EN); image magic-byte unit tests.
@Musiker15 Musiker15 merged commit fd9b772 into main Jun 20, 2026
3 checks passed
@Musiker15 Musiker15 deleted the feat/slice-8f-logo-branding branch June 20, 2026 16:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant