Multi-Engine Scanning
Multi-engine scanning runs a file through several antivirus engines in parallel and combines their verdicts using a configurable consensus mode. Currently supported engines:
- ClamAV — local or remote clamd daemon
- VirusTotal — cloud API (free tier: 4 req/min)
No external npm dependencies. The VirusTotal integration uses Node.js built-in
https only.
createMultiEngine(options)
const { createMultiEngine } = require('pompelmi');
const scanner = createMultiEngine({
engines: [
{ type: 'clamav', host: 'localhost', port: 3310 },
{
type: 'virustotal',
apiKey: process.env.VIRUSTOTAL_API_KEY,
threshold: 2 // flag if ≥2 VT engines detect it
}
],
consensus: 'any' // 'any' | 'all' | 'majority'
});
const result = await scanner.scan('/uploads/file.pdf');
const result = await scanner.scanBuffer(buffer);
Engine types
ClamAV
| Option | Type | Description |
|---|---|---|
type | 'clamav' | Engine type selector. |
host | string | clamd hostname (TCP mode). |
port | number | clamd port (default: 3310). |
socket | string | UNIX socket path. |
timeout | number | Socket timeout in ms. |
VirusTotal
| Option | Type | Description |
|---|---|---|
type | 'virustotal' | Engine type selector. |
apiKey | string | VirusTotal API key. Omitting it returns Verdict.ScanError. |
threshold | number | Minimum number of VT detections to flag as malicious (default: 1). |
Consensus modes
| Mode | Verdict is Malicious when… | Best for |
|---|---|---|
'any' |
At least one engine flags the file. | Highest security (default). Zero false negatives at the cost of more false positives. |
'all' |
Every engine flags the file. | Lowest false-positive rate. Useful when one engine is noisy. |
'majority' |
More than half of all engines flag it. | Balanced: good with 3 or more engines. |
Result shape
const result = await scanner.scanBuffer(buffer);
// result:
// {
// verdict: Verdict.Malicious, // combined verdict
// consensus: 'any', // mode used
// engines: [
// { name: 'clamav', verdict: Verdict.Malicious, virus: 'Win.Malware.Agent' },
// { name: 'virustotal', verdict: Verdict.Clean, detections: 0 }
// ]
// }
The engines array always has one entry per configured engine, in order.
Failed engines include an error string and verdict: Verdict.ScanError.
VirusTotal setup
Sign up for a free VirusTotal account at virustotal.com and copy your API key from the profile page.
Free tier limits: 4 requests/minute, 500 requests/day, and a 32 MB file-size cap. For higher throughput, consider a VirusTotal Premium account.
The integration uploads the file, then polls the analysis endpoint every 5 seconds
for up to 60 seconds. If the analysis does not complete in time,
Verdict.ScanError is returned for the VirusTotal engine.
// Set via environment variable — never hard-code API keys
const scanner = createMultiEngine({
engines: [
{ type: 'virustotal', apiKey: process.env.VIRUSTOTAL_API_KEY }
]
});
Error handling
Engine errors (connection refused, API timeout, etc.) do not throw — they return
Verdict.ScanError for that engine and include an error string.
The consensus is applied to all results, including errored ones.
const result = await scanner.scanBuffer(buffer);
for (const engine of result.engines) {
if (engine.verdict === Verdict.ScanError) {
console.warn(`${engine.name} failed:`, engine.error);
}
}
if (result.verdict === Verdict.Malicious) {
// quarantine or reject
}
TypeScript
import { createMultiEngine } from 'pompelmi';
import type { MultiEngineOptions, MultiEngineResult, EngineResult } from 'pompelmi';
const options: MultiEngineOptions = {
engines: [{ type: 'clamav', host: 'localhost', port: 3310 }],
consensus: 'any',
};
const scanner = createMultiEngine(options);
const result: MultiEngineResult = await scanner.scanBuffer(buffer);
const engine: EngineResult = result.engines[0];