From 59d58ee647e3ff5a0b053d0a4ac36726173e4899 Mon Sep 17 00:00:00 2001 From: Tommaso Bertocchi <148005572+SonoTommy@users.noreply.github.com> Date: Wed, 22 Apr 2026 15:33:05 +0200 Subject: [PATCH 1/2] Document malware scanning for uploaded files Added documentation on scanning uploaded files for malware using ClamAV in Express. --- sections/security/scanfilesuploads.md | 73 +++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 sections/security/scanfilesuploads.md diff --git a/sections/security/scanfilesuploads.md b/sections/security/scanfilesuploads.md new file mode 100644 index 000000000..03281bfc6 --- /dev/null +++ b/sections/security/scanfilesuploads.md @@ -0,0 +1,73 @@ +# Scan uploaded files for malware before storing them + +### One Paragraph Explainer + +File upload endpoints are a common attack vector. A file that appears harmless +at the HTTP layer can carry a known malware signature, an embedded script, or +a crafted archive designed to exploit downstream consumers. Validating the +filename or MIME type alone is not enough — those values come from the client +and are trivially spoofed. The only reliable check happens on the file bytes, +server-side, before the file reaches permanent storage. + +ClamAV is the standard open-source antivirus engine for server-side scanning. +Integrating it at the upload route means every file is inspected in memory +before it is written to disk, S3, or any other store. + +### Code Example – scanning an upload in Express before saving + +```javascript +const multer = require('multer'); +const { scan, Verdict } = require('pompelmi'); +const path = require('path'); +const os = require('os'); +const fs = require('fs/promises'); + +const upload = multer({ dest: os.tmpdir() }); + +app.post('/upload', upload.single('file'), async (req, res) => { + const tmpPath = req.file.path; + + try { + const result = await scan(tmpPath); + + if (result === Verdict.Malicious) { + await fs.unlink(tmpPath); + return res.status(422).json({ error: 'File rejected: malware detected' }); + } + + if (result === Verdict.ScanError) { + await fs.unlink(tmpPath); + return res.status(422).json({ error: 'Scan incomplete: file rejected as precaution' }); + } + + // Verdict.Clean — move to permanent storage + await fs.rename(tmpPath, path.join('./uploads', req.file.originalname)); + res.json({ status: 'ok' }); + } catch (err) { + await fs.unlink(tmpPath).catch(() => {}); + res.status(500).json({ error: 'Upload failed' }); + } +}); +``` + +### What to look for in a scanning library + +- **Typed verdicts** — distinguish `Clean`, `Malicious`, and `ScanError` (scan + failure should fail closed, not silently pass) +- **No daemon required** — simpler ops; `clamscan` CLI is enough for most + upload volumes +- **Zero runtime dependencies** — nothing to audit beyond ClamAV itself +- **Works in Docker** — ClamAV can run in a sidecar container and be reached + via TCP socket + +### Otherwise + +Malicious files stored on your infrastructure can be served to other users, +trigger vulnerabilities in downstream parsers (PDF renderers, image processors, +archive extractors), or be used as staging for further attacks. + +### External references + +- 🔗 [ClamAV official documentation](https://docs.clamav.net/) +- 🔗 [OWASP – Unrestricted File Upload](https://owasp.org/www-community/vulnerabilities/Unrestricted_File_Upload) +- 🔗 [pompelmi – ClamAV wrapper for Node.js](https://github.com/pompelmi/pompelmi) From db11fd2440715caaf3ee307b4cb9c2da44a9f066 Mon Sep 17 00:00:00 2001 From: Tommaso Bertocchi <148005572+SonoTommy@users.noreply.github.com> Date: Wed, 22 Apr 2026 15:35:37 +0200 Subject: [PATCH 2/2] Add malware scanning for uploaded files section Added a new section on scanning uploaded files for malware, including a TL;DR summary and example code. --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index 065d37585..d266278f5 100644 --- a/README.md +++ b/README.md @@ -1391,6 +1391,32 @@ This style ensures that there is no ambiguity with global npm packages and makes

⬆ Return to top

+## ![✔] 6.28. Scan uploaded files for malware before storing them +### `🌟 #new` + + + +**TL;DR:** Validate uploaded files server-side against a malware engine before writing them to disk or object storage. Filename and MIME type come from the client and are trivially spoofed — only inspecting the actual bytes is reliable. ClamAV is the standard open-source engine for this; wrap it at the route level so a clean verdict is required before any storage operation proceeds: + +```javascript +const { scan, Verdict } = require('pompelmi'); // ClamAV wrapper for Node.js + +app.post('/upload', upload.single('file'), async (req, res) => { + const result = await scan(req.file.path); + + if (result !== Verdict.Clean) { + await fs.unlink(req.file.path); + return res.status(422).json({ error: 'File rejected', verdict: result.description }); + } + + // safe to move to permanent storage +}); +``` + +**Otherwise:** Malicious files stored on your infrastructure can be served to other users, exploit vulnerabilities in downstream parsers (PDF renderers, image processors, archive extractors), or act as a staging point for further attacks + +

+ # `7. Draft: Performance Best Practices` ## Our contributors are working on this section. [Would you like to join?](https://github.com/goldbergyoni/nodebestpractices/issues/256)