const assert = require("assert"); const fs = require("fs"); const os = require("os"); const path = require("path"); const { cleanupUploadedFiles, safeDownloadFilename, validateUploadedFile, validateUploadedFiles } = require("../src/services/upload-security"); const sandbox = fs.mkdtempSync(path.join(os.tmpdir(), "lumi-upload-security-")); function upload(name, mime, bytes) { const filePath = path.join(sandbox, `${Date.now()}-${Math.random()}`); fs.writeFileSync(filePath, bytes); return { path: filePath, originalname: name, mimetype: mime, size: bytes.length }; } try { const png = upload("icon.png", "image/png", Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00])); validateUploadedFile(png, { allowedTypes: ["png"], maxBytes: 1024, requireExtension: true }); assert.equal(png.detectedType, "png"); const disguised = upload("not-really.png", "image/png", Buffer.from("not an image")); assert.throws(() => validateUploadedFiles([disguised], { allowedTypes: ["png"], maxBytes: 1024 }), /contents/); assert.equal(fs.existsSync(disguised.path), false); const mismatch = upload("photo.jpg", "image/jpeg", Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])); assert.throws(() => validateUploadedFiles([mismatch], { allowedTypes: ["png", "jpeg"] }), /browser|extension/); assert.equal(fs.existsSync(mismatch.path), false); const unsafeSvg = upload("icon.svg", "image/svg+xml", Buffer.from('')); assert.throws(() => validateUploadedFiles([unsafeSvg], { allowedTypes: ["svg"] }), /contents/); assert.equal(fs.existsSync(unsafeSvg.path), false); const safeSvg = upload("icon.svg", "image/svg+xml", Buffer.from('')); validateUploadedFile(safeSvg, { allowedTypes: ["svg"], requireExtension: true }); const oversized = upload("large.txt", "text/plain", Buffer.from("12345")); assert.throws(() => validateUploadedFiles([oversized], { allowedTypes: ["text"], maxBytes: 4 }), /larger/); assert.equal(fs.existsSync(oversized.path), false); assert.equal(safeDownloadFilename('../../bad\r\n"name.txt'), "badname.txt"); assert.equal(safeDownloadFilename("..\\..\\secret.txt"), "secret.txt"); const serverSource = fs.readFileSync(path.join(__dirname, "..", "src", "web", "server.js"), "utf8"); const moderationSource = fs.readFileSync(path.join(__dirname, "..", "plugins", "moderation", "index.js"), "utf8"); const economySource = fs.readFileSync(path.join(__dirname, "..", "plugins", "economy-framework", "index.js"), "utf8"); assert(serverSource.includes('allowedTypes: ["zip"]')); assert(serverSource.includes('allowedTypes: ["png", "svg"]')); assert(serverSource.includes('safeDownloadFilename(attachment.original_name')); assert(serverSource.includes('"X-Content-Type-Options", "nosniff"')); assert(moderationSource.includes("validateUploadedFiles(req.files")); assert(moderationSource.indexOf('router.post("/actions", (req, res, next)') < moderationSource.indexOf("evidenceUpload, async")); assert(economySource.includes("currencyIconUpload")); assert(economySource.indexOf('router.post("/settings/icon", (req, res, next)') < economySource.indexOf("currencyIconUpload, (req, res)")); cleanupUploadedFiles([png, safeSvg]); console.log("Upload security verification passed."); } finally { fs.rmSync(sandbox, { recursive: true, force: true }); }