// An attachment has to be the thing it says it is. The MIME type used to be // taken on trust here, so anything could be posted as image/png: stored in the // saved chat, and handed to a provider as a data URI. Documents and S3 uploads // were always sniffed; this was the path that was not. const test = require('node:test'); const assert = require('node:assert/strict'); const { validateAttachments } = require('../src/utils/clinicalConversation'); const b64 = buf => Buffer.from(buf).toString('base64'); const PNG = Buffer.from('89504e470d0a1a0a0000000d49484452', 'hex'); const JPEG = Buffer.from('ffd8ffe000104a464946', 'hex'); const WEBP = Buffer.concat([Buffer.from('RIFF'), Buffer.alloc(4), Buffer.from('WEBP'), Buffer.alloc(4)]); const attach = (mimeType, buf) => validateAttachments([{ mimeType, dataBase64: b64(buf) }]); const refuses = (mimeType, buf, why) => assert.throws( () => attach(mimeType, buf), err => { assert.match(err.message, /not the image type it claims to be/); return true; }, why); test('a genuine image of each accepted type passes', () => { assert.equal(attach('image/png', PNG).length, 1); assert.equal(attach('image/jpeg', JPEG).length, 1); assert.equal(attach('image/webp', WEBP).length, 1); }); test('executable and script payloads labelled as images are refused', () => { refuses('image/png', '', 'php'); refuses('image/png', '#!/bin/sh\nrm -rf /', 'shell script'); refuses('image/jpeg', '', 'html/js'); refuses('image/png', Buffer.from('4d5a90000300', 'hex'), 'a Windows executable'); refuses('image/png', Buffer.from('7f454c46', 'hex'), 'an ELF binary'); }); test('a real file of the wrong type is refused, not just junk', () => { refuses('image/png', '%PDF-1.4 trailing', 'a PDF called a PNG'); refuses('image/png', Buffer.from('504b0304', 'hex'), 'a zip/docx called a PNG'); refuses('image/webp', PNG, 'a PNG called a WebP'); refuses('image/jpeg', PNG, 'a PNG called a JPEG'); }); test('a polyglot that merely starts with image bytes is still only that image', () => { // A PNG header followed by script text sniffs as PNG and is accepted — which // is correct: it IS a PNG. The protection is that it is never executed and // never served as anything but an image, not that payloads cannot be hidden // inside valid image bytes, which no sniffer can promise. assert.equal(attach('image/png', Buffer.concat([PNG, Buffer.from('')])).length, 1); }); test('the size and count limits still hold, and are checked before the bytes are read', () => { assert.throws(() => validateAttachments(new Array(5).fill({ mimeType: 'image/png', dataBase64: b64(PNG) })), /maximum of 4 images/); const big = Buffer.concat([PNG, Buffer.alloc(5 * 1024 * 1024)]); assert.throws(() => attach('image/png', big), /limited to 5 MiB/); }); test('a type outside the allowlist never reaches the sniffer', () => { assert.throws(() => attach('application/pdf', Buffer.from('%PDF-1.4')), /Only PNG, JPEG and WebP/); assert.throws(() => attach('image/svg+xml', Buffer.from('')), /Only PNG, JPEG and WebP/); });