ClamAV for humans
A minimal Node.js wrapper around ClamAV that scans any file and returns a typed Verdict Symbol: Verdict.Clean, Verdict.Malicious, or Verdict.ScanError. No daemons. No cloud. No native bindings. Zero runtime dependencies.
- Quickstart
- How it works
- API
- Docker / remote scanning
- Internal utilities
- Supported platforms
- Installing ClamAV manually
- Testing
- Contributing
- Security
- License
npm install pompelmiconst { scan, Verdict } = require('pompelmi');
const result = await scan('/path/to/file.zip');
if (result === Verdict.Malicious) {
throw new Error('File rejected: malware detected');
}- Validate — pompelmi checks that the argument is a string and that the file exists before spawning anything.
- Scan — pompelmi spawns
clamscan --no-summary <filePath>as a child process and reads the exit code. - Map — the exit code is mapped to a result string. Unknown codes and spawn errors reject the Promise.
No stdout parsing. No regex. No surprises.
scan(filePath: string, options?: { host?: string; port?: number; timeout?: number }): Promise<symbol>
// resolves to one of: Verdict.Clean | Verdict.Malicious | Verdict.ScanError| Parameter | Type | Description |
|---|---|---|
filePath |
string |
Absolute or relative path to the file. |
options |
object |
Optional. Omit to use the local clamscan CLI. Pass host / port to scan via a clamd TCP socket instead. See docs/api.html for the full reference. |
Resolves to one of:
| Result | ClamAV exit code | Meaning |
|---|---|---|
Verdict.Clean |
0 | No threats found. |
Verdict.Malicious |
1 | A known virus or malware signature was matched. |
Verdict.ScanError |
2 | The scan itself failed (I/O error, encrypted archive, permission denied). File status is unknown — treat as untrusted. |
Reading the verdict as a string — each Verdict Symbol carries a
.descriptionproperty (Verdict.Clean.description === 'Clean') for logging or serialisation without comparing against raw strings in application logic.
Rejects with an Error in these cases:
| Condition | Error message |
|---|---|
filePath is not a string |
filePath must be a string |
| File does not exist | File not found: <path> |
clamscan is not in PATH |
ENOENT (from the OS) |
| ClamAV returns an unknown exit code | Unexpected exit code: N |
clamscan process is killed by a signal |
Process killed by signal: <SIGNAL> |
Example — full error handling:
const { scan, Verdict } = require('pompelmi');
const path = require('path');
async function safeScan(filePath) {
try {
const result = await scan(path.resolve(filePath));
if (result === Verdict.ScanError) {
// The scan could not complete — treat the file as untrusted.
console.warn('Scan incomplete, rejecting file as precaution.');
return null;
}
return result; // Verdict.Clean or Verdict.Malicious
} catch (err) {
console.error('Scan failed:', err.message);
return null;
}
}If ClamAV runs in a Docker container (or anywhere on the network), pass host and port — everything else stays the same.
const result = await pompelmi.scan('/path/to/upload.zip', {
host: '127.0.0.1',
port: 3310,
});See docs/docker.md for the docker-compose.yml snippet and first-boot notes.
These modules are not part of the public npm API but are used internally to set up the ClamAV environment on a fresh machine.
Installs ClamAV using the platform's native package manager. Skips silently if ClamAV is already installed.
ClamAVInstaller(): Promise<string>- Resolves with a status message string on success or skip.
- Rejects if the install process exits with a non-zero code or if spawning the package manager fails.
| Platform | Package manager | Command |
|---|---|---|
| macOS | Homebrew | brew install clamav |
| Linux | apt-get | sudo apt-get install -y clamav clamav-daemon |
| Windows | Chocolatey | choco install clamav -y |
Downloads or updates the ClamAV virus definition database by running freshclam. Skips if main.cvd is already present on disk.
updateClamAVDatabase(): Promise<string>- Resolves with a status message string on success or skip.
- Rejects if
freshclamexits with a non-zero code or if spawning fails.
| Platform | Database path |
|---|---|
| macOS | /usr/local/share/clamav/main.cvd |
| Linux | /var/lib/clamav/main.cvd |
| Windows | C:\ProgramData\ClamAV\main.cvd |
| OS | ClamAV install | DB path checked |
|---|---|---|
| macOS | brew install clamav |
/usr/local/share/clamav/main.cvd |
| Linux | apt-get install clamav |
/var/lib/clamav/main.cvd |
| Windows | choco install clamav -y |
C:\ProgramData\ClamAV\main.cvd |
ClamAV must be installed on the host system. pompelmi does not bundle or download it.
# macOS
brew install clamav && freshclam
# Linux (Debian / Ubuntu)
sudo apt-get install -y clamav clamav-daemon && sudo freshclam
# Windows (Chocolatey)
choco install clamav -ynpm testThe test suite has two parts:
- Unit tests (
test/unit.test.js) — run with Node's built-in test runner. MocknativeSpawnfromsrc/spawn.jsand platform dependencies via require-cache injection; no ClamAV installation required. - Integration tests (
test/scan.test.js) — spawn realclamscanprocesses against EICAR test files. Skipped automatically ifclamscanis not found in PATH.
- Fork the repository at https://github.com/pompelmi/pompelmi.
- Create a feature branch:
git checkout -b feat/your-change. - Make your changes and run
npm testto verify. - Open a pull request against
main.
Please read CODE_OF_CONDUCT.md before contributing.
To report a vulnerability, see SECURITY.md.
ISC — © pompelmi contributors