fix(collab): close the image upload hole and the guard trap

The chat image filter believed the Content-Type the client put on the part.
The stored name keeps the extension of the name the client sent, and the
download route derives what it serves from that extension and sends it inline,
so pwn.html declared as image/png came back as HTML on our own origin. The
filter now checks the extension as well, against the blocked list and against
the types it claims to accept, because a per-route filter fully replaces the
module-level one it looked like it was adding to.

The multipart message route also kept its guard decorators. Guards run before
the interceptor, so a refusal went out while the client was still streaming up
to four images and the socket died as ECONNRESET rather than carrying the
error. The checks moved into the handler, where the other upload route in this
file already does them, and a test pins both refusals.

The three collab:link events were missing from the shared registry, which the
server typecheck refuses, and the admin Links switch could never be saved
because the Zod contract had no key for it. Both are in now. Migrations moved
to the end of the list and the column add uses the pragma guard rather than a
caught duplicate-column error.

The message-required string keeps its old wording for a request without a file
part, so a client from before this route took images sees what it always saw.
This commit is contained in:
Maurice
2026-09-08 22:18:36 +02:00
committed by Maurice
parent 9543fdc3f2
commit 07bb899c50
5 changed files with 78 additions and 13 deletions
+1
View File
@@ -84,6 +84,7 @@ export type AdminAddonUpdateRequest = z.infer<typeof adminAddonUpdateRequestSche
export const adminCollabFeaturesRequestSchema = z.object({
chat: z.boolean().optional(),
notes: z.boolean().optional(),
links: z.boolean().optional(),
polls: z.boolean().optional(),
whatsnext: z.boolean().optional(),
});