screentinker/server
ScreenTinker 6b082cfad0 fix(uploads): derive stored type from file content, and never serve uploads as documents
Uploaded files are served from the SAME ORIGIN as the dashboard, so how a browser
interprets them is a security boundary. Two things decided that interpretation, and
both were caller-controlled: the stored extension came from
`path.extname(originalname)`, and the only type check read `file.mimetype` — a request
header. A caller could therefore choose to have their bytes served as an active
document from the app origin.

Two independent invariants now hold the boundary:

1. INGEST — lib/upload-sniff.js sniffs magic bytes after multer writes a neutral
   `.part` file (diskStorage names the file before any bytes exist, so the sniff cannot
   happen there), maps the result through a hardcoded mime->extension allowlist, renames
   accordingly, and stores the sniffed mime. Unsupported bytes are refused with a 400.

2. SERVING — upload responses carry `Content-Security-Policy: sandbox`, so if a response
   is ever treated as a document it lands in an opaque origin with scripts disabled.
   Anything outside the inline-safe extension set is additionally forced to download.
   This holds regardless of how a file reached disk, so a future gap in (1) is contained
   rather than exploitable.

Applied at every instance of the pattern, not just the first: lib/content-ingest.js,
the /replace route, the four content-serving paths across server.js and routes/content.js
(the latter pair currently shadowed by mount order, which is not a guarantee), and the
ZIP-import path in routes/status.js, which took its extension from the archive entry.

SVG stays accepted and stays inline: white-label logos are SVG, and octet-stream +
nosniff makes <img> fail. Scripts in an SVG never run in an image context, and the
sandbox CSP covers the one case where they would — a direct navigation. SVG is also no
longer handed to sharp, which removes the librsvg path where the open libvips CVEs live.

Existing rows are untouched — no migration. The four upload fixtures in agency.test.js
uploaded `Buffer.from('x')` declared as image/png; that is the exact "declared type is a
lie" case this closes, so the fixtures now use real PNG bytes. No assertion changed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 11:47:09 -05:00
..
config PiP overlay MVP: push image/web overlays to a device or group (#109) (#127) 2026-06-18 14:54:44 -05:00
db feat(content): subtitle/caption support as a content property (#223) 2026-07-23 12:33:35 -05:00
lib fix(uploads): derive stored type from file content, and never serve uploads as documents 2026-07-25 11:47:09 -05:00
middleware fix(uploads): derive stored type from file content, and never serve uploads as documents 2026-07-25 11:47:09 -05:00
player fix(widgets): honest webpage-widget note — blocked sites don't work on device (#230) 2026-07-24 19:17:00 -05:00
routes fix(uploads): derive stored type from file content, and never serve uploads as documents 2026-07-25 11:47:09 -05:00
scripts fix(content+android): rotation-aware media — portrait upright on dashboard AND player (#170) (#172) 2026-07-12 22:05:11 -05:00
services feat(auth,tizen): TOTP 2FA UI, email verification on signup, Tizen SSSP install 2026-07-22 21:08:50 -05:00
test fix(uploads): derive stored type from file content, and never serve uploads as documents 2026-07-25 11:47:09 -05:00
ws refactor(auth): centralise session token resolution across manual verify sites 2026-07-24 20:58:05 -05:00
.gitignore feat(email): Microsoft Graph send + alert spam protection + preferences UI 2026-05-12 18:16:40 -05:00
config.js feat(email): SMTP transport as an alternative to Microsoft Graph [#173] (#179) 2026-07-13 15:56:22 -05:00
package-lock.json chore(release): v1.9.15 2026-07-24 21:12:22 -05:00
package.json chore(release): v1.9.15 2026-07-24 21:12:22 -05:00
server.js fix(uploads): derive stored type from file content, and never serve uploads as documents 2026-07-25 11:47:09 -05:00
version.js chore(version): single-source VERSION, env-configurable data paths, bump tooling 2026-06-10 12:56:03 -05:00