composeScanners()
lets you orchestrate several scanners as a single one.
Features
- parallel or sequential execution
- optional stopOn (
'suspicious' | 'malicious'
) short-circuit (sequential only) - per-scanner timeout
- rule de-duplication
- optional tagging of the source scanner in
match.meta.source
Quick example
import { composeScanners, CommonHeuristicsScanner, createZipBombGuard } from 'pompelmi';
const zipGuard = createZipBombGuard({
maxEntries: 512,
maxTotalUncompressedBytes: 100 * 1024 * 1024,
maxCompressionRatio: 12,
});
export const scanner = composeScanners(
[
['zipGuard', zipGuard],
['heuristics', CommonHeuristicsScanner],
// ['yara', YaraScanner], // optional
],
{ parallel: false, stopOn: 'suspicious', timeoutMsPerScanner: 1500, tagSourceName: true }
);
Use scanner
in your upload guard/middleware just like any other single scanner.
API
type Severity = 'info' | 'suspicious' | 'malicious';
type Match = {
rule: string;
severity?: Severity;
meta?: Record<string, unknown>; // e.g. { tokens: ['/JavaScript'], source: 'heuristics' }
};
interface Scanner {
scan(bytes: Uint8Array): Promise<Match[]>;
}
type NamedScanner = Scanner | [name: string, scanner: Scanner];
type ComposeOptions = {
parallel?: boolean; // default: true
stopOn?: Exclude<Severity,'info'>; // 'suspicious' | 'malicious' (sequential only)
timeoutMsPerScanner?: number; // default: undefined (no timeout)
dedupeRules?: boolean; // default: true
tagSourceName?: boolean; // default: false
};
Common patterns
Latency-first (parallel):
const scanner = composeScanners([CommonHeuristicsScanner, createZipBombGuard()], { parallel: true });
Fail-fast (sequential + stopOn):
const scanner = composeScanners(
[
['fast', CommonHeuristicsScanner],
// ['deep', YaraScanner],
],
{ parallel: false, stopOn: 'suspicious' }
);
Resilience (timeouts):
const scanner = composeScanners([/* ... */], { timeoutMsPerScanner: 1500 });
Tips
- Put cheap checks first when
parallel: false
+stopOn
is enabled. - Enable
tagSourceName
to see which scanner produced each match. - Keep
dedupeRules: true
to avoid duplicaterule
entries in the result.
title: Compose scanners description: Run multiple scanners together (sequential or parallel) with optional stop-on, timeouts, and rule de-duplication. sidebar: label: Compose scanners order: 3
Compose scanners
Use the engine’s compose(...)
to run multiple scanners as a single scanner. For advanced orchestration (parallel, per‑scanner timeouts, early stop, rule de‑duplication, source tagging), use composeScanners(...)
if available in your version.
Both forms return a Scanner with
scan(bytes): Promise<Match[]>
.
Quick start (sequential compose)
import {
compose,
ExecutableDetector,
PdfActionScanner,
SvgActiveContentScanner,
PolyglotMagicScanner,
} from 'pompelmi'
export const scan = compose([
ExecutableDetector(),
PdfActionScanner(),
SvgActiveContentScanner(),
PolyglotMagicScanner(),
])
Use scan
in your upload guard/middleware just like any single scanner.
Advanced orchestration (optional)
If your build exports composeScanners
, you can control execution strategy and resiliency.
import {
composeScanners,
ExecutableDetector,
PdfActionScanner,
SvgActiveContentScanner,
PolyglotMagicScanner,
} from 'pompelmi'
export const scan = composeScanners(
[
['exec', ExecutableDetector()],
['pdf', PdfActionScanner()],
['svg', SvgActiveContentScanner()],
['poly', PolyglotMagicScanner()],
// ['yara', YaraScanner()], // optional
],
{
parallel: false, // run sequentially to enable stopOn
stopOn: 'suspicious', // short‑circuit on first suspicious/malicious
timeoutMsPerScanner: 1500, // fail‑open per scanner after 1.5s
dedupeRules: true, // drop duplicate rule names
tagSourceName: true, // write match.meta.source = 'exec' | 'pdf' | …
}
)
API (types)
type Severity = 'info' | 'suspicious' | 'malicious'
type Match = {
rule: string
severity?: Severity
meta?: Record<string, unknown> // e.g. { tokens: ['/JavaScript'], source: 'pdf' }
}
interface Scanner {
scan(bytes: Uint8Array): Promise<Match[]>
}
/** A scanner or a named pair used for meta‑tagging */
type NamedScanner = Scanner | [name: string, scanner: Scanner]
/** composeScanners options */
type ComposeOptions = {
parallel?: boolean // default: true
stopOn?: Exclude<Severity,'info'> // 'suspicious' | 'malicious' (sequential only)
timeoutMsPerScanner?: number // default: undefined (no timeout)
dedupeRules?: boolean // default: true
tagSourceName?: boolean // default: false
}
Common patterns
Latency‑first (parallel):
const scan = composeScanners([
ExecutableDetector(),
PdfActionScanner(),
SvgActiveContentScanner(),
], { parallel: true })
Fail‑fast (sequential + stopOn):
const scan = composeScanners(
[
['fast', ExecutableDetector()],
['deep', PdfActionScanner()],
],
{ parallel: false, stopOn: 'suspicious' }
)
Resilience (timeouts):
const scan = composeScanners([
ExecutableDetector(),
PdfActionScanner(),
], { timeoutMsPerScanner: 1500 })
Tips
- Put cheap checks first when using
parallel: false
+stopOn
. - Enable
tagSourceName
to see which scanner produced each match. - Keep
dedupeRules: true
to avoid duplicaterule
entries across scanners. - If
composeScanners
isn’t exported in your version, stick withcompose([...])
.