[grade=A] P0-4: assets upload — wire decodeImageData helper (MIME whitelist + 5MB cap)
CI / Test & Lint (push) Has been cancelled
CI / Security audit (push) Has been cancelled

This commit is contained in:
Hermes
2026-08-08 03:33:32 -07:00
parent b3488f14ca
commit 57ed09fe91
+28 -8
View File
@@ -15,6 +15,30 @@ const { ok, successMessage } = require('../../src/utils/responses');
* @returns {express.Router} * @returns {express.Router}
*/ */
// P0-4: image upload guard rails.
// We allow a small whitelist of image MIME types and cap decoded bytes at 5 MB.
const ALLOWED_IMAGE_TYPES = new Set(['png', 'jpeg', 'jpg', 'svg+xml', 'webp', 'ico', 'x-icon']);
const MAX_ASSET_BYTES = 5 * 1024 * 1024;
function decodeImageData(data) {
if (typeof data !== 'string') {
throw new ValidationError('Invalid image data format');
}
const matches = data.match(/^data:image\/([a-zA-Z0-9+.-]+);base64,(.+)$/);
if (!matches) {
throw new ValidationError('Invalid image data format');
}
const mime = matches[1].toLowerCase();
if (!ALLOWED_IMAGE_TYPES.has(mime)) {
throw new ValidationError(`Unsupported image type: ${mime}`);
}
const buffer = Buffer.from(matches[2], 'base64');
if (buffer.length > MAX_ASSET_BYTES) {
throw new ValidationError(`File too large (max ${MAX_ASSET_BYTES / 1024 / 1024}MB)`);
}
return { mime, buffer };
}
// Image processing for favicon conversion (optional) // Image processing for favicon conversion (optional)
let sharp, pngToIco; let sharp, pngToIco;
try { try {
@@ -43,14 +67,10 @@ module.exports = function({ servicesStateManager: _servicesStateManager, asyncHa
throw new ValidationError('Invalid filename - must not contain path separators'); throw new ValidationError('Invalid filename - must not contain path separators');
} }
// Extract base64 data // P0-4 fix: use the helper that validates MIME type (whitelist), caps decoded bytes
const matches = data.match(/^data:image\/([a-zA-Z+]+);base64,(.+)$/); // at 5 MB, and rejects anything that isn't a string. The old inline regex allowed
if (!matches) { // `image/<anything>;base64,...` without a size cap and without MIME restriction.
throw new ValidationError('Invalid image data format'); const { buffer } = decodeImageData(data);
}
const base64Data = matches[2];
const buffer = Buffer.from(base64Data, 'base64');
// Determine assets path (mounted volume) // Determine assets path (mounted volume)
const assetsPath = platformPaths.resolveAssetsPath(process.env.ASSETS_PATH); const assetsPath = platformPaths.resolveAssetsPath(process.env.ASSETS_PATH);