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

RuleTypeDefaultDescription
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
});
ParameterTypeDescription
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' });