Prerequisites
pompelmi supports three scanning modes. Choose the one that fits your deployment:
| Mode | How it works | When to use |
|---|---|---|
| TCP | Connects to a running clamd instance over host:port. |
Docker sidecar, remote ClamAV, production environments. |
| UNIX socket | Connects to clamd via a local .sock file. Lower latency than TCP. |
clamd running as a system daemon on the same machine. |
| Local clamscan | Spawns a clamscan child process. No daemon needed. |
Development machines, CI pipelines, simple setups. |
For local mode, install ClamAV on your machine (brew install clamav on
macOS, apt-get install clamav on Debian/Ubuntu, or the
ClamAV installer
on Windows).
For TCP or UNIX socket mode, the fastest way to get a clamd instance running is the official Docker image:
docker run -d --name clamav -p 3310:3310 clamav/clamav:stable
docker inspect --format='{{.State.Health.Status}}' clamav
to return healthy before scanning. See
Docker → First boot for details.
Installation
Install the pompelmi package from npm:
npmnpm install pompelmiyarn
yarn add pompelmipnpm
pnpm add pompelmibun
bun add pompelmi
pompelmi has zero runtime dependencies. The package ships CommonJS and ESM entry points and bundled TypeScript declarations. Works with Node.js and Bun.
Quickstart
Scan a file via TCP (clamd in Docker)
const { scan, Verdict } = require('pompelmi');
const result = await scan('/path/to/upload.pdf', {
host: '127.0.0.1',
port: 3310,
});
if (result === Verdict.Clean) {
console.log('File is clean');
} else if (result === Verdict.Malicious) {
console.log('Malware detected!');
} else {
console.log('Scan error — treat as untrusted');
}
Scan a file via UNIX socket
const { scan, Verdict } = require('pompelmi');
const result = await scan('/path/to/upload.pdf', {
socket: '/run/clamav/clamd.sock',
});
console.log(result.description); // 'Clean' | 'Malicious' | 'ScanError'
Scan an in-memory Buffer (multer memoryStorage)
const { scanBuffer, Verdict } = require('pompelmi');
// req.file.buffer from multer memoryStorage
const result = await scanBuffer(req.file.buffer, {
host: '127.0.0.1',
port: 3310,
});
if (result === Verdict.Malicious) {
return res.status(400).json({ error: 'Malware detected' });
}
Scan a stream (S3 / HTTP)
const { scanStream, Verdict } = require('pompelmi');
const { S3Client, GetObjectCommand } = require('@aws-sdk/client-s3');
const s3 = new S3Client({ region: 'us-east-1' });
const { Body } = await s3.send(new GetObjectCommand({
Bucket: 'my-uploads',
Key: 'incoming/report.pdf',
}));
const result = await scanStream(Body, {
host: '127.0.0.1',
port: 3310,
});
if (result === Verdict.Malicious) {
// delete the object or quarantine
}
Verdicts
Every scan function returns a Promise that resolves to one of three Symbol constants.
Always compare with === — never against raw strings.
| Constant | .description |
Meaning |
|---|---|---|
Verdict.Clean |
'Clean' |
No threats detected. The file is safe to use. |
Verdict.Malicious |
'Malicious' |
A virus or malware signature was matched. Reject the file. |
Verdict.ScanError |
'ScanError' |
The scan failed. The file's safety is unknown — treat as untrusted. |
Use result.description for logging and serialisation —
it returns a plain string such as 'Clean' or 'Malicious'.
const { scan, Verdict } = require('pompelmi');
const result = await scan('/tmp/upload.zip', { host: '127.0.0.1', port: 3310 });
console.log(result.description); // 'Clean'
console.log(result === Verdict.Clean); // true
console.log(result === Verdict.Malicious); // false
Express file upload example
The following is a complete Express + multer example that scans every uploaded file and returns a 400 if malware is detected.
const express = require('express');
const multer = require('multer');
const { scanBuffer, Verdict } = require('pompelmi');
const app = express();
const upload = multer({ storage: multer.memoryStorage() });
app.post('/upload', upload.single('file'), async (req, res) => {
if (!req.file) {
return res.status(400).json({ error: 'No file uploaded' });
}
let result;
try {
result = await scanBuffer(req.file.buffer, {
host: process.env.CLAMAV_HOST || '127.0.0.1',
port: Number(process.env.CLAMAV_PORT) || 3310,
});
} catch (err) {
console.error('Scan error:', err.message);
return res.status(500).json({ error: 'Scan failed' });
}
if (result === Verdict.Malicious) {
return res.status(400).json({ error: 'Malware detected — upload rejected' });
}
if (result === Verdict.ScanError) {
return res.status(500).json({ error: 'Scan inconclusive — upload rejected' });
}
// Verdict.Clean — proceed with saving the file
res.json({ ok: true, filename: req.file.originalname });
});
app.listen(3000, () => console.log('Server on http://localhost:3000'));
CLAMAV_HOST and CLAMAV_PORT environment variables
in your deployment. In a Docker Compose setup, use the service name as the host
(e.g. clamav). See Docker → App integration.
Next steps
-
API Reference — full documentation for
scan(),scanBuffer(),scanStream(),scanDirectory(),middleware(),scanS3(),createPool(),watch(), and all ScanOptions. - Docker & Remote Scanning — docker-compose.yml, UNIX socket mounts, healthcheck, and troubleshooting.
- GitHub Action GitHub App — scan your repository on every push or pull request with zero setup.