Scan Policy
A scan policy combines multiple upload rules into a single reusable object: file size limits, MIME type allowlists, extension allowlists, encrypted archive rejection, and ClamAV virus scanning. Any failing rule immediately returns a structured rejection — without running the remaining checks.
Policies integrate with Express, Fastify, and NestJS via built-in adapter methods.
createPolicy(rules?)
const { createPolicy } = require('pompelmi');
const policy = createPolicy({
scan: { host: 'localhost', port: 3310, timeout: 5000 },
maxSize: 10 * 1024 * 1024, // 10 MB
allowedMimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'application/pdf'],
allowedExtensions:['.jpg', '.jpeg', '.png', '.gif', '.webp', '.pdf'],
rejectEncrypted: true,
onScannerUnavailable: 'reject'
});
All rules are optional. Omitting scan skips virus scanning entirely.
Rules reference
| Rule | Type | Default | Description |
|---|---|---|---|
scan |
ScanOptions |
undefined |
ClamAV connection options. When omitted, virus scanning is skipped. |
maxSize |
number |
null |
Maximum file size in bytes. Checked against meta.size or buffer.length. |
allowedMimeTypes |
string[] |
null |
Allowlist of MIME types. Only checked when meta.mimeType is provided. |
allowedExtensions |
string[] |
null |
Allowlist of file extensions (e.g. ['.pdf', '.jpg']). Only checked when meta.filename is provided. |
rejectEncrypted |
boolean |
false |
Reject encrypted ZIP archives (detected via magic bytes and the general-purpose bit flag). |
onScannerUnavailable |
'reject' | 'allow' | 'throw' |
'reject' |
What to do when the ClamAV connection fails.
'reject' returns allowed: false;
'allow' lets the file through;
'throw' re-throws the underlying error.
|
policy.check(buffer, meta?)
Evaluates all rules against a buffer. Checks run in order: size → MIME → extension → encrypted → virus.
const result = await policy.check(buffer, {
filename: 'photo.jpg',
mimeType: 'image/jpeg',
size: buffer.length
});
| Parameter | Type | Description |
|---|---|---|
buffer |
Buffer |
File content to evaluate. |
meta.filename |
string | null |
Original filename — used for extension checking. |
meta.mimeType |
string | null |
Declared MIME type — used for MIME allowlist checking. |
meta.size |
number |
File size in bytes. Defaults to buffer.length. |
PolicyResult
// Allowed
{ allowed: true, reason: null, verdict: Verdict.Clean, details: { ... } }
// Rejected
{ allowed: false, reason: 'file_too_large', verdict: Verdict.ScanError, details: { ... } }
{ allowed: false, reason: 'mime_not_allowed', verdict: Verdict.ScanError, details: { ... } }
{ allowed: false, reason: 'extension_not_allowed', verdict: Verdict.ScanError, details: { ... } }
{ allowed: false, reason: 'encrypted_archive', verdict: Verdict.ScanError, details: { ... } }
{ allowed: false, reason: 'virus_detected', verdict: Verdict.Malicious, details: { ... } }
{ allowed: false, reason: 'scanner_unavailable', verdict: Verdict.ScanError, details: { ... } }
The details object always includes size, mimeType, extension, and virusName.
policy.middleware()
Returns an Express / Fastify middleware. Place it after multer (or any multipart parser) and before your route handler.
const express = require('express');
const multer = require('multer');
const { createPolicy } = require('pompelmi');
const policy = createPolicy({
maxSize: 5 * 1024 * 1024,
allowedMimeTypes: ['image/jpeg', 'image/png'],
scan: { host: 'localhost', port: 3310 },
});
const upload = multer({ storage: multer.memoryStorage() });
const app = express();
app.post('/avatar',
upload.single('file'),
policy.middleware(), // rejects with HTTP 403 if any rule fails
(req, res) => res.json({ ok: true })
);
policy.nestGuard()
Returns a NestJS CanActivate-compatible guard object.
import { UseGuards, Controller, Post, UseInterceptors } from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { createPolicy } from 'pompelmi';
const policy = createPolicy({
maxSize: 10 * 1024 * 1024,
scan: { host: 'localhost', port: 3310 },
});
@Controller('upload')
export class UploadController {
@Post()
@UseInterceptors(FileInterceptor('file'))
@UseGuards(policy.nestGuard())
upload() { return { ok: true }; }
}
Examples
Images only
const policy = createPolicy({
maxSize: 2 * 1024 * 1024,
allowedMimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
allowedExtensions: ['.jpg', '.jpeg', '.png', '.gif', '.webp'],
scan: { host: 'localhost', port: 3310 },
});
Documents only
const policy = createPolicy({
maxSize: 20 * 1024 * 1024,
allowedMimeTypes: ['application/pdf', 'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
allowedExtensions: ['.pdf', '.doc', '.docx'],
scan: { host: 'localhost', port: 3310 },
});
Checking the result manually
const result = await policy.check(req.file.buffer, {
filename: req.file.originalname,
mimeType: req.file.mimetype,
});
if (!result.allowed) {
return res.status(403).json({ error: result.reason });
}
// proceed with upload
TypeScript
import { createPolicy } from 'pompelmi';
import type { PolicyRules, PolicyResult, ScanPolicy } from 'pompelmi';
const rules: PolicyRules = {
maxSize: 5 * 1024 * 1024,
allowedMimeTypes: ['image/png'],
scan: { host: 'localhost', port: 3310 },
};
const policy: ScanPolicy = createPolicy(rules);
const result: PolicyResult = await policy.check(buffer, { mimeType: 'image/png' });