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
First boot downloads ~300 MB of virus definitions. Wait for 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:

npm
npm install pompelmi
yarn
yarn add pompelmi
pnpm
pnpm add pompelmi
bun
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'));
Set 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.