Prerequisites
| Requirement | Minimum version | Notes |
|---|---|---|
| Node.js | 18.x | 18+ required (uses built-in node:test runner). Test with node --version. |
| npm | 6.x | Comes with Node.js. |
| ClamAV | 0.103+ | Must be installed on the host system. See Step 1. |
Step 1 — Install ClamAV
pompelmi delegates to the clamscan system binary. Install ClamAV
for your operating system before proceeding.
brew install clamavLinux — Debian / Ubuntu
sudo apt-get update sudo apt-get install -y clamav clamav-daemonLinux — RHEL / CentOS / Fedora
sudo dnf install -y clamav clamav-updateWindows — Chocolatey
choco install clamav -yWindows — manual
Download the installer from clamav.net/downloads
and follow the setup wizard. Make sure the ClamAV bin directory is added to
PATH.
Verify the installation:
clamscan --version
clamscan to /opt/homebrew/bin
(Apple Silicon) or /usr/local/bin (Intel). Both are on the default PATH
after a standard Homebrew setup.
Step 2 — Update virus definitions
ClamAV ships with no virus definitions. You must download them before the first scan.
This is done with freshclam.
freshclamLinux
sudo freshclam
clamav-freshclam service may be running in the background
and will lock the database files. Stop it before running freshclam manually:
sudo systemctl stop clamav-freshclam sudo freshclam sudo systemctl start clamav-freshclam
freshclam downloads the main.cvd, daily.cvd, and bytecode.cvd definition files. The download is several hundred megabytes on first run. Subsequent runs are incremental.
freshclam to run daily (cron, systemd timer, or Task Scheduler)
to keep definitions current. Stale definitions reduce detection quality.
Step 3 — Install pompelmi
npm install pompelmi
Or with yarn:
yarn add pompelmi
Verify it installed:
node -e "const p = require('pompelmi'); console.log(typeof p.scan)"
# function
Step 4 — Run your first scan
Create a file called scan.js:
const pompelmi = require('pompelmi');
pompelmi.scan(process.argv[2])
.then(result => {
console.log('Result:', result);
process.exit(result === 'Malicious' ? 1 : 0);
})
.catch(err => {
console.error('Error:', err.message);
process.exit(2);
});
Run it against any file:
node scan.js /etc/hosts # Result: Clean
echo 'X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*' > /tmp/eicar.txt node scan.js /tmp/eicar.txt # Result: Malicious
Step 5 — Handle errors properly
scan() rejects the Promise in three situations. Always attach a
.catch() or use try/catch in an async function.
const pompelmi = require('pompelmi');
const path = require('path');
async function safeScan(filePath) {
// Resolve to absolute path to avoid ambiguity
const abs = path.resolve(filePath);
let result;
try {
result = await pompelmi.scan(abs);
} catch (err) {
if (err.message.startsWith('File not found')) {
console.error('The file does not exist:', abs);
return null;
}
if (err.message.startsWith('Unexpected exit code')) {
console.error('ClamAV returned an unknown exit code.');
return null;
}
// clamscan binary not found or other OS error
console.error('ClamAV error:', err.message);
return null;
}
return result;
}
safeScan('./upload.zip').then(console.log);
More examples
Scanning uploaded files in Express
const express = require('express');
const multer = require('multer');
const pompelmi = require('pompelmi');
const path = require('path');
const fs = require('fs');
const upload = multer({ dest: '/tmp/uploads/' });
const app = express();
app.post('/upload', upload.single('file'), async (req, res) => {
const filePath = req.file.path;
try {
const result = await pompelmi.scan(filePath);
if (result === 'Malicious') {
fs.unlinkSync(filePath);
return res.status(400).json({ error: 'Malware detected. Upload rejected.' });
}
if (result === 'ScanError') {
// Scan failed — treat as untrusted, reject upload
fs.unlinkSync(filePath);
return res.status(422).json({ error: 'Scan incomplete. Upload rejected as precaution.' });
}
// Clean — move to permanent storage
const dest = path.join('/var/app/uploads', req.file.filename);
fs.renameSync(filePath, dest);
res.json({ status: 'ok', file: req.file.filename });
} catch (err) {
fs.unlinkSync(filePath);
res.status(500).json({ error: 'Scan failed: ' + err.message });
}
});
app.listen(3000);
Scanning multiple files in sequence
const pompelmi = require('pompelmi');
const fs = require('fs');
async function scanDirectory(dirPath) {
const files = fs.readdirSync(dirPath)
.map(f => `${dirPath}/${f}`)
.filter(f => fs.statSync(f).isFile());
const results = [];
for (const file of files) {
const result = await pompelmi.scan(file);
results.push({ file, result });
if (result !== 'Clean') {
console.warn(`[${result}] ${file}`);
}
}
return results;
}
scanDirectory('./downloads').then(results => {
const malicious = results.filter(r => r.result === 'Malicious');
console.log(`Scanned ${results.length} files. ${malicious.length} malicious.`);
});
Parallel scanning with Promise.all
const pompelmi = require('pompelmi');
async function scanAll(filePaths) {
const settled = await Promise.allSettled(
filePaths.map(f => pompelmi.scan(f).then(r => ({ file: f, result: r })))
);
return settled.map(s =>
s.status === 'fulfilled'
? s.value
: { file: '?', result: 'Error', error: s.reason.message }
);
}
scanAll(['/tmp/a.zip', '/tmp/b.pdf', '/tmp/c.exe'])
.then(results => console.table(results));
clamscan process per file simultaneously.
On systems with limited RAM, scanning tens of files in parallel may cause memory
pressure because each process loads ClamAV definitions independently. Prefer
sequential scanning for large batches.
Common errors
| Error message | Cause | Fix |
|---|---|---|
File not found: /path/to/file |
The path passed to scan() does not exist on disk. |
Check the path. Use path.resolve() to avoid relative-path bugs. |
ENOENT |
clamscan is not in PATH. |
Install ClamAV (Step 1) and ensure it is on the system PATH. |
Unexpected exit code: N |
ClamAV returned an exit code other than 0, 1, or 2. | Run clamscan --version to check if ClamAV is healthy. Exit code 50+ usually indicates a config or permission error. |
Scan always returns ScanError |
ClamAV definitions are missing, corrupted, or the process lacks read permissions. | Run freshclam to download/repair definitions. Check file permissions. |
LibClamAV Error: cli_loaddbdir() |
Virus database not found by ClamAV. | Run freshclam to populate the database directory. |
| Permission denied on the file | The process does not have read access to the file. | Check file permissions. pompelmi runs clamscan as the current user. |