pompelmi: ClamAV scanning for Node.js in one function
ClamAV antivirus scanning for Node.js — scan file uploads with a single function call. Zero dependencies. Typed Symbol verdicts. Local or Docker/clamd.
At a glance
- What is it?
- pompelmi wraps ClamAV behind a single async function that returns one of three typed verdicts, clean, malicious, or a scan error. In local mode it runs clamscan as a child process and maps the exit code, with no output parsing and no regular expression. In remote mode it streams the file to a clamd daemon over TCP or a UNIX socket. There is a command line tool, a GitHub Action, and adapters for the common Node frameworks.
- Who is it for?
- pompelmi fits a Node service that accepts file uploads and has to decide whether to keep them, and that would rather have three named outcomes than a parsed string. It does not fit a deployment with no antivirus engine available at all, since the library does not include one, and it does not fit a team that needs the scanning to be a policy decision rather than a technical one, because the Pro edition is where the audit trail and the policy features live.
- Can I use it commercially?
- Yes. ISC is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 153 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One function, three verdicts
pompelmi describes itself as a minimal Node.js wrapper around ClamAV, and the whole public surface is one asynchronous function called scan, plus a set of verdict symbols.
The function returns one of three typed verdict symbols: clean, malicious, or a scan error. The third one is the interesting design decision. Most wrappers in this space return a boolean or a string, and a failure to scan is indistinguishable from a clean file, which is the worst possible failure mode for an upload handler. Here the failure is a first class outcome, and because the values are symbols rather than strings, a caller cannot accidentally compare a verdict against a typo.
The package description in the manifest puts the promise plainly: scan any file and get back clean, malicious, or a scan error, with no daemons, no cloud, and no native bindings.
The use case it is built for is stated in the same place: if you need to scan file uploads for viruses in Node.js, integrate ClamAV with Express or Fastify, or add antivirus scanning to any upload pipeline, this is the simplest path.
The pitch against the alternatives is about the awkward parts rather than the features. Most integrations require parsing the output of ClamAV with a regular expression, managing a clamd daemon, or working around unmaintained packages. pompelmi does none of those things. One function call, verdicts mapped from exit codes, and no dependencies to audit.
The project is ISC licensed, published on npm, and the site is pompelmi.app. The last push was on 2026-05-23, and the release history shows three versions in the first half of May 2026, with the newest at 1.20.0.
Local mode, where the exit code is the answer
There are two scanning modes, and the first one is the reason to choose this library over a shell script.
In local mode the library spawns clamscan as a child process and maps its exit code to a verdict. The readme is explicit about the two things this avoids: there is no output parsing, and there is no regular expression.
That matters more than it first appears. The usual shell approach is to run the scanner, capture its output, and look for a pattern in it, and that approach breaks in two ways. The output format has changed between versions and between locales, so a pattern that matches one release can silently stop matching another. And a pattern that fails to match is indistinguishable from a clean file, so the failure mode is a false clean.
Mapping the exit code has neither problem. The exit status is a small, stable contract with the tool, it is the same in every locale, and the failure modes are separate: a crash or a missing database is a different status from a clean result, and that difference survives the mapping.
The consequence for the caller is that the dependency is an external program rather than a library. There is no native binding to compile and no ClamAV build to link against, which is why the package claims no native bindings, and the installation of the scanner itself is a separate concern that the project handles in its own examples rather than hiding.
For a service that has to scan uploads on every request, the trade is between the cost of starting a process per file and the cost of running a daemon. The library supports both, and picks the cheaper one according to the mode you choose.
Remote mode, and the INSTREAM protocol
The second mode is the one to use when the scanner runs somewhere else, and the readme calls it remote, Docker, or UNIX socket mode.
In that mode the library streams the file to a running clamd daemon, over TCP or over a UNIX domain socket, using the ClamAV INSTREAM protocol. The protocol is the simple one: the file is sent as a stream with a length, and the daemon answers with a verdict.
The distinction that matters is where the engine lives relative to the application. A container that runs the application and a sidecar that runs clamd over a shared socket is the pattern the documentation is written around, and there is a whole guide for it, including the compose patterns and the TCP sidecar approach. The other deployment is a single host with a local clamscan per request.
That choice has a real cost difference, and the library is built so that you can make it per deployment rather than per project. Spawning a process for every upload is simple and has no daemon to operate, which matters in a small deployment or a test environment. A daemon is faster under load and centralises the signature database, which matters in a busy production service or where the scanner is shared between several services.
The promise in the overview is that neither mode needs the cloud and neither mode needs a native binding. The engine is ClamAV either way, and the library is the part that speaks to it in a way that a Node application can use without string parsing.
The command line, and the documentation set
There is a command line tool, and the quick start shows three invocations that cover the shapes of use.
# Scan a file
npx pompelmi scan ./uploads/file.pdf
# Scan a directory
npx pompelmi scan ./uploads --recursive
# Output as JSON
npx pompelmi scan ./uploads --jsonA single file, a directory with recursion, and machine readable output. The third one is what makes the tool usable from a pipeline, since the verdicts can be piped into whatever processes the results.
The documentation is organised as a set of guides rather than one long page, and the table of contents in the readme is the map. There is a getting started guide with installation, prerequisites, and examples, an API reference with the function signatures, the options, the verdicts, and the error conditions, and a command line reference. Then three that are about using it in a particular place: a guide for scanning S3 objects directly with the IAM setup and a Lambda pattern, a guide for Docker and remote scanning covering the TCP sidecar, the UNIX socket mount, and the compose patterns, and a guide for the GitHub Action with its inputs and outputs, caching, and example workflows.
That list tells you how the project thinks about its users. The API reference and the command line reference cover the library itself, and the other three cover the three ways an application usually meets an upload: an object store, a container, and a pipeline. Each of those has its own failure modes around credentials, file handles, and timeouts, and a guide for each is worth more than the same information in the general reference.
The tool also ships as a GitHub Action, with an action directory and an action manifest at the root, so continuous integration can scan artefacts without installing anything.
Framework adapters and the example set
The repository is organised around the idea that scanning an upload is the same problem in every framework and a different piece of glue in each.
There is a packages directory with adapters for NestJS, Fastify, Next.js, Hono, Remix, SvelteKit, Cloudflare, and a testing helper, and the test script runs the unit tests, the ES module tests, and then a separate test file for each of those packages in turn.
That test script is a good indicator of the project's intent. Each framework package has its own test file, and each is run explicitly in the test command rather than through a glob, so a failure names the adapter that broke.
The examples directory is broader still, and the file names describe the use cases rather than the API. There is a basic scan, a command line scan, a scan of a buffer, of a stream, of an image, of a PDF, of a zip archive, of a directory, of several files at once, and one with options and one with a timeout. There is an upload handler for Express and one for Fastify, and directories for Express, NestJS, and Next.js. There is a REST API server, a handler for the scan error case, and two that show what to do with a malicious file: one that deletes it and one that quarantines it. There is an S3 scan before upload, a script that updates the virus database, and a TypeScript usage example.
The three scripts that describe a whole security decision are the ones worth reading. Deleting on a malicious verdict, quarantining on a malicious verdict, and updating the virus database are the three things every upload handler needs and the three things no scanner library can decide for you.
The manifest, the licence, and the dependency claim
The package manifest is where the design shows up as numbers, and a couple of them are worth reading closely.
The package ships both module systems, with a CommonJS entry point and an ES module entry point, and an exports map that resolves each import condition to the right file, plus its own type declarations. The binary is the command line tool, and the licence is ISC, one of the shortest and most permissive licences there is.
The test script is one long chain of test runs, and the lint step covers the source directory, and there is a postinstall script that runs from the scripts directory. The postinstall is worth a look in a package you install in production, because a script that runs on install is code that runs with your user's privileges before you have read it.
The dependency claim needs a qualifier. The readme advertises zero dependencies and a badge says zero dependencies, and the runtime dependency list in the manifest contains exactly one entry, the terminal image library. That is the package used to render the thumbnail of a scanned image in the terminal, which is the kind of feature that turns a zero dependency claim into a nearly zero one. The difference matters less for the security argument than the count does, and the manifest is where you can check it.
The repository is otherwise careful: there is a security policy, a disclaimer, a code of conduct, a changelog, two release notes files for earlier versions, an editor configuration, and a package ignore file so the source tree is not published. There is also a wiki directory, a context7 configuration, and an llms.txt file, which are the two conventions that let a code assistant or a documentation site read the project the way a human would.
And there is a Pro edition, described in a note above the documentation table, which adds audit logs, priority support, and enterprise features. The open source project is the scanning; the commercial one is the record of who scanned what and when.
Editorial conclusion
pompelmi fits a Node service that accepts file uploads and has to decide whether to keep them, and that would rather have three named outcomes than a parsed string. It does not fit a deployment with no antivirus engine available at all, since the library does not include one, and it does not fit a team that needs the scanning to be a policy decision rather than a technical one, because the Pro edition is where the audit trail and the policy features live. Verify first that ClamAV is installed and its signature database is current, that the mode you choose matches where the engine runs relative to your application, and that the signature updates are a scheduled job, because an antivirus scanner with a stale database reports a clean file that is not clean.
Frequently asked questions
What does pompelmi do?
It is a Node.js wrapper around ClamAV that exposes a single async function, scan, and returns one of three typed verdict symbols: clean, malicious, or a scan error. The separate scan error verdict means a failed scan is never mistaken for a clean file.
What is the difference between the local and remote scanning modes?
The local mode spawns clamscan as a child process and maps its exit code to a verdict, with no output parsing and no regular expression. The remote mode streams the file to a running clamd daemon over TCP or a UNIX domain socket using the ClamAV INSTREAM protocol, and suits a container with a sidecar or a shared scanner.
How do I scan a file with pompelmi from the command line?
With the scan subcommand, pointing it at a file, at a directory with the recursive flag, or at a directory with the JSON flag for machine readable output. The examples in the repository also cover scanning a buffer, a stream, an image, a PDF, a zip archive, and several files at once.
What frameworks does pompelmi support?
There are adapter packages for NestJS, Fastify, Next.js, Hono, Remix, SvelteKit, and Cloudflare, each with its own test file that the test script runs explicitly. The examples directory adds Express and Next.js upload handlers, a REST API server, and scripts that delete or quarantine a file when the verdict is malicious.
Is pompelmi really dependency free?
The readme and its badge say zero dependencies, and the runtime dependency list in the package manifest holds one entry, the terminal image library used to render a scanned image in the terminal. The published package also runs a postinstall script from the scripts directory, which is worth reading before installing it in a production environment.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/pompelmi-pompelmi)Was this useful?