Cloudflare Workers
@pompelmi/cloudflare is the official pompelmi adapter for Cloudflare Workers. It scans file uploads for malware by streaming them to a remote ClamAV (clamd) instance using the INSTREAM protocol.
The package uses Web APIs only — fetch, FormData,
ArrayBuffer, and Cloudflare's connect() socket API from
cloudflare:sockets. No Node.js built-ins, no native bindings.
Requirements
Cloudflare Workers cannot run clamd locally. You need a publicly reachable clamd instance. Options:
| Option | Description |
|---|---|
| VPS + open port | Run clamd on a virtual machine and expose port 3310. Add firewall rules to restrict access to Cloudflare's egress IPs. |
| Cloudflare Tunnel | Use cloudflared to expose a private clamd instance without opening a port. Most secure option. |
Installation
npm i @pompelmi/cloudflare
Basic Usage
Scan an ArrayBuffer directly from a multipart form upload:
import { scanBuffer } from '@pompelmi/cloudflare';
export default {
async fetch(request, env) {
const formData = await request.formData();
const file = formData.get('file');
const buffer = await file.arrayBuffer();
const result = await scanBuffer(buffer, {
host: env.CLAMAV_HOST,
port: parseInt(env.CLAMAV_PORT),
});
if (result !== 'clean') {
return new Response('File rejected', { status: 422 });
}
return new Response('OK');
},
};
scanRequest helper
scanRequest(request, options) handles the full form-read → scan → response cycle.
It returns null for clean files, a Response(422) for malicious files,
and a Response(500) on scan errors.
import { scanRequest } from '@pompelmi/cloudflare';
export default {
async fetch(request, env) {
const rejection = await scanRequest(request, {
host: env.CLAMAV_HOST,
port: parseInt(env.CLAMAV_PORT),
});
if (rejection) return rejection;
// File is clean — proceed with your upload logic
return new Response('File accepted');
},
};
To scan a specific form field other than file:
const rejection = await scanRequest(request, {
host: env.CLAMAV_HOST,
port: parseInt(env.CLAMAV_PORT),
field: 'attachment',
});
Wrangler Configuration
Copy wrangler.toml.example from the package and fill in your clamd details:
name = "my-worker" main = "src/worker.js" compatibility_date = "2024-01-01" [vars] CLAMAV_HOST = "your-clamd-host.example.com" CLAMAV_PORT = "3310"
Use Wrangler secrets for production to avoid committing credentials:
wrangler secret put CLAMAV_HOST wrangler secret put CLAMAV_PORT
API Reference
scanBuffer(buffer, options)
| Parameter | Type | Description |
|---|---|---|
buffer | ArrayBuffer | The file bytes to scan |
options.host | string | clamd hostname or IP (required) |
options.port | number | clamd port, typically 3310 (required) |
options.timeout | number | Read timeout in ms (default: 15000) |
Returns Promise<'clean' | 'malicious' | 'error'>.
scanRequest(request, options)
| Parameter | Type | Description |
|---|---|---|
request | Request | Cloudflare Workers Request object |
options.host | string | clamd hostname or IP (required) |
options.port | number | clamd port (required) |
options.field | string | Form field name (default: 'file') |
options.timeout | number | Read timeout in ms (default: 15000) |
Returns Promise<Response | null> — null if clean, HTTP Response otherwise.
Remote clamd Setup
Start a clamd instance accessible over TCP using Docker:
docker run -d \ --name clamd \ -p 3310:3310 \ clamav/clamav:latest
For a production deployment, use the Docker guide and restrict clamd to your Cloudflare Worker's outbound IP range.
For a zero-port-forwarding setup, use Cloudflare Tunnel to securely expose a private clamd instance.