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 trueon errors to keep the queue moving + rely on server-side scanning as the second line of defense.