Works with any backend npm install Zero dependencies

Pre-upload virus / malware scanning via a single async hook

One-line integration with any malware scanner - ClamAV, VirusTotal, Microsoft Defender, Sophos, anything that exposes an HTTP API. The virusScan hook runs before any byte leaves the browser; rejected files never hit your storage. Reject with a string and the user sees a clear error in the queue.

This demo's policy: reject any file whose name contains "eicar" (the industry-standard test pattern). All other files are allowed.

Try it: drop a file named eicar.txt (any contents). It appears in the queue then fails with virus_scan_failed: EICAR test pattern detected - upload blocked. No request is ever sent.
Function signature
virusScan: async (file, task, uploader) => boolean | string

// Return true -> allow upload to proceed
// Return false -> reject with generic "rejected by virus scan"
// Return string -> reject with that message (shown to user)
// Throws / rejected Promise -> reject with the thrown error message
Real-world: ClamAV via clamd HTTP wrapper
new MultipleUpload('#uploader', {
 uploadUrl: '/api/upload',
 virusScan: async (file) => {
 const fd = new FormData();
 fd.append('file', file);
 const res = await fetch('/api/scan', { method: 'POST', body: fd });
 const result = await res.json();
 return result.clean === true ? true : `Threat detected: ${result.threatName}`;
 }
});
Real-world: VirusTotal hash lookup (fast pre-check)
// Computes SHA-256 in a Worker, looks up VirusTotal report. Sub-second
// in the cache-hit case; defers to upload-side ClamAV on a miss.

new MultipleUpload('#uploader', {
 uploadUrl: '/api/upload',
 computeHash: true,
 hashAlgorithm: 'sha256',
 virusScan: async (file, task) => {
 // Wait for the hash to finish (set by computeHash).
 await new Promise(r => setTimeout(r, 100)); // tiny grace period
 if (!task.hash) return true; // hash not ready, skip pre-check

 const res = await fetch(`/api/vt/${task.hash}`);
 if (!res.ok) return true; // soft-fail to upload
 const report = await res.json();
 if (report.positives > 3) return `VirusTotal: ${report.positives} engines flagged this file`;
 return true;
 }
});
Real-world: Microsoft Defender (Windows-only, via server proxy)
// Server has Defender's MpCmdRun.exe available; expose POST /api/defender-scan.

new MultipleUpload('#uploader', {
 uploadUrl: '/api/upload',
 virusScan: async (file) => {
 const fd = new FormData();
 fd.append('file', file);
 const res = await fetch('/api/defender-scan', { method: 'POST', body: fd });
 if (res.status === 200) return true;
 if (res.status === 409) {
 const { threat } = await res.json();
 return `Microsoft Defender: ${threat}`;
 }
 return true; // soft-fail: don't block uploads on scanner downtime
 }
});
Where it sits in the pipeline
queue file
 -> virusScan(file, task, uploader) // <- this hook
 -> encrypt (if encrypt: true)
 -> cross-tab acquire (if crossTab: true)
 -> offline guard (if offlineMode + !online)
 -> strategy.upload(task, uploader)
 -> server
Safety + UX notes
  • Fail closed: if your hook throws, the file fails with a clear error - no silent bypass.
  • Async-friendly: return a Promise; the rest of the queue progresses independently while the scan runs.
  • Resume-aware: if a scanned file is paused/resumed, the scan does NOT re-run - the verdict is held with the task.
  • Soft-fail patterns: for scanners that can be unavailable (network blip, service down), return true on errors to keep the queue moving + rely on server-side scanning as the second line of defense.