pompelmi 0.33.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +351 -987
  2. package/dist/pompelmi.audit.cjs +130 -0
  3. package/dist/pompelmi.audit.cjs.map +1 -0
  4. package/dist/pompelmi.audit.esm.js +109 -0
  5. package/dist/pompelmi.audit.esm.js.map +1 -0
  6. package/dist/pompelmi.browser.cjs +1455 -0
  7. package/dist/pompelmi.browser.cjs.map +1 -0
  8. package/dist/pompelmi.browser.esm.js +1429 -0
  9. package/dist/pompelmi.browser.esm.js.map +1 -0
  10. package/dist/pompelmi.cjs +1333 -3044
  11. package/dist/pompelmi.cjs.map +1 -1
  12. package/dist/pompelmi.esm.js +1327 -3042
  13. package/dist/pompelmi.esm.js.map +1 -1
  14. package/dist/pompelmi.hooks.cjs +75 -0
  15. package/dist/pompelmi.hooks.cjs.map +1 -0
  16. package/dist/pompelmi.hooks.esm.js +72 -0
  17. package/dist/pompelmi.hooks.esm.js.map +1 -0
  18. package/dist/pompelmi.policy-packs.cjs +239 -0
  19. package/dist/pompelmi.policy-packs.cjs.map +1 -0
  20. package/dist/pompelmi.policy-packs.esm.js +231 -0
  21. package/dist/pompelmi.policy-packs.esm.js.map +1 -0
  22. package/dist/pompelmi.quarantine.cjs +315 -0
  23. package/dist/pompelmi.quarantine.cjs.map +1 -0
  24. package/dist/pompelmi.quarantine.esm.js +291 -0
  25. package/dist/pompelmi.quarantine.esm.js.map +1 -0
  26. package/dist/pompelmi.react.cjs +1486 -0
  27. package/dist/pompelmi.react.cjs.map +1 -0
  28. package/dist/pompelmi.react.esm.js +1459 -0
  29. package/dist/pompelmi.react.esm.js.map +1 -0
  30. package/dist/types/audit.d.ts +84 -0
  31. package/dist/types/browser-index.d.ts +28 -2
  32. package/dist/types/config.d.ts +3 -2
  33. package/dist/types/hooks.d.ts +89 -0
  34. package/dist/types/index.d.ts +17 -9
  35. package/dist/types/policy-packs.d.ts +98 -0
  36. package/dist/types/quarantine/index.d.ts +18 -0
  37. package/dist/types/quarantine/storage.d.ts +77 -0
  38. package/dist/types/quarantine/types.d.ts +78 -0
  39. package/dist/types/quarantine/workflow.d.ts +97 -0
  40. package/dist/types/react-index.d.ts +13 -0
  41. package/dist/types/types.d.ts +0 -1
  42. package/package.json +54 -3
@@ -0,0 +1,75 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Scan lifecycle hooks for Pompelmi.
5
+ *
6
+ * Hooks let you observe and react to scan events without modifying the scan
7
+ * pipeline itself. They are the recommended integration point for:
8
+ * - logging / metrics collection
9
+ * - alerting on threats
10
+ * - triggering quarantine automatically
11
+ * - OpenTelemetry span creation
12
+ *
13
+ * Usage:
14
+ * ```ts
15
+ * import { scanBytes } from 'pompelmi';
16
+ * import { createScanHooks, withHooks } from 'pompelmi/hooks';
17
+ *
18
+ * const hooks = createScanHooks({
19
+ * onScanComplete(ctx, report) {
20
+ * console.log(ctx.filename, report.verdict, report.durationMs + 'ms');
21
+ * },
22
+ * onThreatDetected(ctx, report) {
23
+ * alertTeam({ file: ctx.filename, verdict: report.verdict });
24
+ * },
25
+ * });
26
+ *
27
+ * const scan = withHooks(scanBytes, hooks);
28
+ * const report = await scan(bytes, { ctx: { filename: 'upload.zip' } });
29
+ * ```
30
+ *
31
+ * @module hooks
32
+ */
33
+ // ── Factory ───────────────────────────────────────────────────────────────────
34
+ /**
35
+ * Create a `ScanHooks` object with optional defaults.
36
+ * This is a thin factory — the value of using it is the inline TS types.
37
+ */
38
+ function createScanHooks(hooks) {
39
+ return hooks;
40
+ }
41
+ /**
42
+ * Wrap a scan function with lifecycle hooks.
43
+ *
44
+ * Returns a new function with the same signature that fires the hooks
45
+ * around each scan call.
46
+ */
47
+ function withHooks(scanFn, hooks) {
48
+ return async (bytes, opts = {}) => {
49
+ const scanId = typeof crypto !== 'undefined' && 'randomUUID' in crypto
50
+ ? crypto.randomUUID()
51
+ : undefined;
52
+ const startedAt = Date.now();
53
+ const ctx = { ...opts.ctx, scanId, startedAt };
54
+ void hooks.onScanStart?.(ctx);
55
+ let report;
56
+ try {
57
+ report = await scanFn(bytes, opts);
58
+ }
59
+ catch (err) {
60
+ void hooks.onScanError?.({ ...ctx }, err);
61
+ throw err;
62
+ }
63
+ const durationMs = Date.now() - startedAt;
64
+ const completeCtx = { ...ctx, durationMs };
65
+ void hooks.onScanComplete?.(completeCtx, report);
66
+ if (report.verdict === 'suspicious' || report.verdict === 'malicious') {
67
+ void hooks.onThreatDetected?.(completeCtx, report);
68
+ }
69
+ return report;
70
+ };
71
+ }
72
+
73
+ exports.createScanHooks = createScanHooks;
74
+ exports.withHooks = withHooks;
75
+ //# sourceMappingURL=pompelmi.hooks.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pompelmi.hooks.cjs","sources":["../src/hooks.ts"],"sourcesContent":["/**\n * Scan lifecycle hooks for Pompelmi.\n *\n * Hooks let you observe and react to scan events without modifying the scan\n * pipeline itself. They are the recommended integration point for:\n * - logging / metrics collection\n * - alerting on threats\n * - triggering quarantine automatically\n * - OpenTelemetry span creation\n *\n * Usage:\n * ```ts\n * import { scanBytes } from 'pompelmi';\n * import { createScanHooks, withHooks } from 'pompelmi/hooks';\n *\n * const hooks = createScanHooks({\n * onScanComplete(ctx, report) {\n * console.log(ctx.filename, report.verdict, report.durationMs + 'ms');\n * },\n * onThreatDetected(ctx, report) {\n * alertTeam({ file: ctx.filename, verdict: report.verdict });\n * },\n * });\n *\n * const scan = withHooks(scanBytes, hooks);\n * const report = await scan(bytes, { ctx: { filename: 'upload.zip' } });\n * ```\n *\n * @module hooks\n */\n\nimport type { ScanContext, ScanReport } from './types';\nimport type { QuarantineEntry } from './quarantine/types';\n\n// ── Event payloads ────────────────────────────────────────────────────────────\n\nexport interface ScanStartContext extends ScanContext {\n /** Unique identifier for this scan invocation (useful for correlating logs). */\n scanId?: string;\n /** Timestamp when the scan started (ms since epoch). */\n startedAt: number;\n}\n\nexport interface ScanCompleteContext extends ScanStartContext {\n /** Duration of the scan in milliseconds. */\n durationMs: number;\n}\n\n// ── Hook interface ────────────────────────────────────────────────────────────\n\n/**\n * Callbacks for the scan lifecycle. All hooks are optional.\n *\n * Hooks MUST NOT throw — wrap logic in try/catch if it can fail.\n * Async hooks are fire-and-forget; they do not block the scan result.\n */\nexport interface ScanHooks {\n /**\n * Called immediately before a scan begins.\n */\n onScanStart?: (ctx: ScanStartContext) => void | Promise<void>;\n\n /**\n * Called when a scan completes successfully (any verdict, including clean).\n */\n onScanComplete?: (ctx: ScanCompleteContext, report: ScanReport) => void | Promise<void>;\n\n /**\n * Called when the scan verdict is 'suspicious' or 'malicious'.\n * Fired in addition to `onScanComplete`.\n */\n onThreatDetected?: (ctx: ScanCompleteContext, report: ScanReport) => void | Promise<void>;\n\n /**\n * Called when a file has been quarantined.\n * Requires wiring with a `QuarantineManager`; not fired automatically by `scanBytes`.\n */\n onQuarantine?: (entry: QuarantineEntry) => void | Promise<void>;\n\n /**\n * Called when a scan throws an unexpected error.\n */\n onScanError?: (ctx: ScanStartContext, error: unknown) => void | Promise<void>;\n}\n\n// ── Factory ───────────────────────────────────────────────────────────────────\n\n/**\n * Create a `ScanHooks` object with optional defaults.\n * This is a thin factory — the value of using it is the inline TS types.\n */\nexport function createScanHooks(hooks: ScanHooks): ScanHooks {\n return hooks;\n}\n\n// ── withHooks wrapper ─────────────────────────────────────────────────────────\n\ntype ScanFn = (bytes: Uint8Array, opts?: { ctx?: ScanContext; [k: string]: unknown }) => Promise<ScanReport>;\n\n/**\n * Wrap a scan function with lifecycle hooks.\n *\n * Returns a new function with the same signature that fires the hooks\n * around each scan call.\n */\nexport function withHooks(scanFn: ScanFn, hooks: ScanHooks): ScanFn {\n return async (bytes, opts = {}) => {\n const scanId = typeof crypto !== 'undefined' && 'randomUUID' in crypto\n ? (crypto as { randomUUID(): string }).randomUUID()\n : undefined;\n\n const startedAt = Date.now();\n const ctx: ScanStartContext = { ...opts.ctx, scanId, startedAt };\n\n void hooks.onScanStart?.(ctx);\n\n let report: ScanReport;\n try {\n report = await scanFn(bytes, opts);\n } catch (err) {\n void hooks.onScanError?.({ ...ctx }, err);\n throw err;\n }\n\n const durationMs = Date.now() - startedAt;\n const completeCtx: ScanCompleteContext = { ...ctx, durationMs };\n\n void hooks.onScanComplete?.(completeCtx, report);\n\n if (report.verdict === 'suspicious' || report.verdict === 'malicious') {\n void hooks.onThreatDetected?.(completeCtx, report);\n }\n\n return report;\n };\n}\n"],"names":[],"mappings":";;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BG;AAwDH;AAEA;;;AAGG;AACG,SAAU,eAAe,CAAC,KAAgB,EAAA;AAC9C,IAAA,OAAO,KAAK;AACd;AAMA;;;;;AAKG;AACG,SAAU,SAAS,CAAC,MAAc,EAAE,KAAgB,EAAA;IACxD,OAAO,OAAO,KAAK,EAAE,IAAI,GAAG,EAAE,KAAI;QAChC,MAAM,MAAM,GAAG,OAAO,MAAM,KAAK,WAAW,IAAI,YAAY,IAAI;AAC9D,cAAG,MAAmC,CAAC,UAAU;cAC/C,SAAS;AAEb,QAAA,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE;AAC5B,QAAA,MAAM,GAAG,GAAqB,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE;AAEhE,QAAA,KAAK,KAAK,CAAC,WAAW,GAAG,GAAG,CAAC;AAE7B,QAAA,IAAI,MAAkB;AACtB,QAAA,IAAI;YACF,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC;QACpC;QAAE,OAAO,GAAG,EAAE;AACZ,YAAA,KAAK,KAAK,CAAC,WAAW,GAAG,EAAE,GAAG,GAAG,EAAE,EAAE,GAAG,CAAC;AACzC,YAAA,MAAM,GAAG;QACX;QAEA,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;QACzC,MAAM,WAAW,GAAwB,EAAE,GAAG,GAAG,EAAE,UAAU,EAAE;QAE/D,KAAK,KAAK,CAAC,cAAc,GAAG,WAAW,EAAE,MAAM,CAAC;AAEhD,QAAA,IAAI,MAAM,CAAC,OAAO,KAAK,YAAY,IAAI,MAAM,CAAC,OAAO,KAAK,WAAW,EAAE;YACrE,KAAK,KAAK,CAAC,gBAAgB,GAAG,WAAW,EAAE,MAAM,CAAC;QACpD;AAEA,QAAA,OAAO,MAAM;AACf,IAAA,CAAC;AACH;;;;;"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Scan lifecycle hooks for Pompelmi.
3
+ *
4
+ * Hooks let you observe and react to scan events without modifying the scan
5
+ * pipeline itself. They are the recommended integration point for:
6
+ * - logging / metrics collection
7
+ * - alerting on threats
8
+ * - triggering quarantine automatically
9
+ * - OpenTelemetry span creation
10
+ *
11
+ * Usage:
12
+ * ```ts
13
+ * import { scanBytes } from 'pompelmi';
14
+ * import { createScanHooks, withHooks } from 'pompelmi/hooks';
15
+ *
16
+ * const hooks = createScanHooks({
17
+ * onScanComplete(ctx, report) {
18
+ * console.log(ctx.filename, report.verdict, report.durationMs + 'ms');
19
+ * },
20
+ * onThreatDetected(ctx, report) {
21
+ * alertTeam({ file: ctx.filename, verdict: report.verdict });
22
+ * },
23
+ * });
24
+ *
25
+ * const scan = withHooks(scanBytes, hooks);
26
+ * const report = await scan(bytes, { ctx: { filename: 'upload.zip' } });
27
+ * ```
28
+ *
29
+ * @module hooks
30
+ */
31
+ // ── Factory ───────────────────────────────────────────────────────────────────
32
+ /**
33
+ * Create a `ScanHooks` object with optional defaults.
34
+ * This is a thin factory — the value of using it is the inline TS types.
35
+ */
36
+ function createScanHooks(hooks) {
37
+ return hooks;
38
+ }
39
+ /**
40
+ * Wrap a scan function with lifecycle hooks.
41
+ *
42
+ * Returns a new function with the same signature that fires the hooks
43
+ * around each scan call.
44
+ */
45
+ function withHooks(scanFn, hooks) {
46
+ return async (bytes, opts = {}) => {
47
+ const scanId = typeof crypto !== 'undefined' && 'randomUUID' in crypto
48
+ ? crypto.randomUUID()
49
+ : undefined;
50
+ const startedAt = Date.now();
51
+ const ctx = { ...opts.ctx, scanId, startedAt };
52
+ void hooks.onScanStart?.(ctx);
53
+ let report;
54
+ try {
55
+ report = await scanFn(bytes, opts);
56
+ }
57
+ catch (err) {
58
+ void hooks.onScanError?.({ ...ctx }, err);
59
+ throw err;
60
+ }
61
+ const durationMs = Date.now() - startedAt;
62
+ const completeCtx = { ...ctx, durationMs };
63
+ void hooks.onScanComplete?.(completeCtx, report);
64
+ if (report.verdict === 'suspicious' || report.verdict === 'malicious') {
65
+ void hooks.onThreatDetected?.(completeCtx, report);
66
+ }
67
+ return report;
68
+ };
69
+ }
70
+
71
+ export { createScanHooks, withHooks };
72
+ //# sourceMappingURL=pompelmi.hooks.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pompelmi.hooks.esm.js","sources":["../src/hooks.ts"],"sourcesContent":["/**\n * Scan lifecycle hooks for Pompelmi.\n *\n * Hooks let you observe and react to scan events without modifying the scan\n * pipeline itself. They are the recommended integration point for:\n * - logging / metrics collection\n * - alerting on threats\n * - triggering quarantine automatically\n * - OpenTelemetry span creation\n *\n * Usage:\n * ```ts\n * import { scanBytes } from 'pompelmi';\n * import { createScanHooks, withHooks } from 'pompelmi/hooks';\n *\n * const hooks = createScanHooks({\n * onScanComplete(ctx, report) {\n * console.log(ctx.filename, report.verdict, report.durationMs + 'ms');\n * },\n * onThreatDetected(ctx, report) {\n * alertTeam({ file: ctx.filename, verdict: report.verdict });\n * },\n * });\n *\n * const scan = withHooks(scanBytes, hooks);\n * const report = await scan(bytes, { ctx: { filename: 'upload.zip' } });\n * ```\n *\n * @module hooks\n */\n\nimport type { ScanContext, ScanReport } from './types';\nimport type { QuarantineEntry } from './quarantine/types';\n\n// ── Event payloads ────────────────────────────────────────────────────────────\n\nexport interface ScanStartContext extends ScanContext {\n /** Unique identifier for this scan invocation (useful for correlating logs). */\n scanId?: string;\n /** Timestamp when the scan started (ms since epoch). */\n startedAt: number;\n}\n\nexport interface ScanCompleteContext extends ScanStartContext {\n /** Duration of the scan in milliseconds. */\n durationMs: number;\n}\n\n// ── Hook interface ────────────────────────────────────────────────────────────\n\n/**\n * Callbacks for the scan lifecycle. All hooks are optional.\n *\n * Hooks MUST NOT throw — wrap logic in try/catch if it can fail.\n * Async hooks are fire-and-forget; they do not block the scan result.\n */\nexport interface ScanHooks {\n /**\n * Called immediately before a scan begins.\n */\n onScanStart?: (ctx: ScanStartContext) => void | Promise<void>;\n\n /**\n * Called when a scan completes successfully (any verdict, including clean).\n */\n onScanComplete?: (ctx: ScanCompleteContext, report: ScanReport) => void | Promise<void>;\n\n /**\n * Called when the scan verdict is 'suspicious' or 'malicious'.\n * Fired in addition to `onScanComplete`.\n */\n onThreatDetected?: (ctx: ScanCompleteContext, report: ScanReport) => void | Promise<void>;\n\n /**\n * Called when a file has been quarantined.\n * Requires wiring with a `QuarantineManager`; not fired automatically by `scanBytes`.\n */\n onQuarantine?: (entry: QuarantineEntry) => void | Promise<void>;\n\n /**\n * Called when a scan throws an unexpected error.\n */\n onScanError?: (ctx: ScanStartContext, error: unknown) => void | Promise<void>;\n}\n\n// ── Factory ───────────────────────────────────────────────────────────────────\n\n/**\n * Create a `ScanHooks` object with optional defaults.\n * This is a thin factory — the value of using it is the inline TS types.\n */\nexport function createScanHooks(hooks: ScanHooks): ScanHooks {\n return hooks;\n}\n\n// ── withHooks wrapper ─────────────────────────────────────────────────────────\n\ntype ScanFn = (bytes: Uint8Array, opts?: { ctx?: ScanContext; [k: string]: unknown }) => Promise<ScanReport>;\n\n/**\n * Wrap a scan function with lifecycle hooks.\n *\n * Returns a new function with the same signature that fires the hooks\n * around each scan call.\n */\nexport function withHooks(scanFn: ScanFn, hooks: ScanHooks): ScanFn {\n return async (bytes, opts = {}) => {\n const scanId = typeof crypto !== 'undefined' && 'randomUUID' in crypto\n ? (crypto as { randomUUID(): string }).randomUUID()\n : undefined;\n\n const startedAt = Date.now();\n const ctx: ScanStartContext = { ...opts.ctx, scanId, startedAt };\n\n void hooks.onScanStart?.(ctx);\n\n let report: ScanReport;\n try {\n report = await scanFn(bytes, opts);\n } catch (err) {\n void hooks.onScanError?.({ ...ctx }, err);\n throw err;\n }\n\n const durationMs = Date.now() - startedAt;\n const completeCtx: ScanCompleteContext = { ...ctx, durationMs };\n\n void hooks.onScanComplete?.(completeCtx, report);\n\n if (report.verdict === 'suspicious' || report.verdict === 'malicious') {\n void hooks.onThreatDetected?.(completeCtx, report);\n }\n\n return report;\n };\n}\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BG;AAwDH;AAEA;;;AAGG;AACG,SAAU,eAAe,CAAC,KAAgB,EAAA;AAC9C,IAAA,OAAO,KAAK;AACd;AAMA;;;;;AAKG;AACG,SAAU,SAAS,CAAC,MAAc,EAAE,KAAgB,EAAA;IACxD,OAAO,OAAO,KAAK,EAAE,IAAI,GAAG,EAAE,KAAI;QAChC,MAAM,MAAM,GAAG,OAAO,MAAM,KAAK,WAAW,IAAI,YAAY,IAAI;AAC9D,cAAG,MAAmC,CAAC,UAAU;cAC/C,SAAS;AAEb,QAAA,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE;AAC5B,QAAA,MAAM,GAAG,GAAqB,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE;AAEhE,QAAA,KAAK,KAAK,CAAC,WAAW,GAAG,GAAG,CAAC;AAE7B,QAAA,IAAI,MAAkB;AACtB,QAAA,IAAI;YACF,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC;QACpC;QAAE,OAAO,GAAG,EAAE;AACZ,YAAA,KAAK,KAAK,CAAC,WAAW,GAAG,EAAE,GAAG,GAAG,EAAE,EAAE,GAAG,CAAC;AACzC,YAAA,MAAM,GAAG;QACX;QAEA,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;QACzC,MAAM,WAAW,GAAwB,EAAE,GAAG,GAAG,EAAE,UAAU,EAAE;QAE/D,KAAK,KAAK,CAAC,cAAc,GAAG,WAAW,EAAE,MAAM,CAAC;AAEhD,QAAA,IAAI,MAAM,CAAC,OAAO,KAAK,YAAY,IAAI,MAAM,CAAC,OAAO,KAAK,WAAW,EAAE;YACrE,KAAK,KAAK,CAAC,gBAAgB,GAAG,WAAW,EAAE,MAAM,CAAC;QACpD;AAEA,QAAA,OAAO,MAAM;AACf,IAAA,CAAC;AACH;;;;"}
@@ -0,0 +1,239 @@
1
+ 'use strict';
2
+
3
+ const MB$1 = 1024 * 1024;
4
+ const DEFAULT_POLICY = {
5
+ includeExtensions: ['zip', 'png', 'jpg', 'jpeg', 'pdf'],
6
+ allowedMimeTypes: ['application/zip', 'image/png', 'image/jpeg', 'application/pdf', 'text/plain'],
7
+ maxFileSizeBytes: 20 * MB$1,
8
+ timeoutMs: 5000,
9
+ concurrency: 4,
10
+ failClosed: true
11
+ };
12
+ function definePolicy(input = {}) {
13
+ const p = { ...DEFAULT_POLICY, ...input };
14
+ if (!Array.isArray(p.includeExtensions))
15
+ throw new TypeError('includeExtensions must be string[]');
16
+ if (!Array.isArray(p.allowedMimeTypes))
17
+ throw new TypeError('allowedMimeTypes must be string[]');
18
+ if (!(Number.isFinite(p.maxFileSizeBytes) && p.maxFileSizeBytes > 0))
19
+ throw new TypeError('maxFileSizeBytes must be > 0');
20
+ if (!(Number.isFinite(p.timeoutMs) && p.timeoutMs > 0))
21
+ throw new TypeError('timeoutMs must be > 0');
22
+ if (!(Number.isInteger(p.concurrency) && p.concurrency > 0))
23
+ throw new TypeError('concurrency must be > 0');
24
+ return p;
25
+ }
26
+
27
+ /**
28
+ * Policy packs for Pompelmi.
29
+ *
30
+ * Pre-configured, named policies for common upload scenarios. Each pack
31
+ * defines the file type allowlist, size limits, and timeout appropriate for
32
+ * its use case.
33
+ *
34
+ * All packs are built on `definePolicy` and are fully overridable:
35
+ *
36
+ * ```ts
37
+ * import { POLICY_PACKS } from 'pompelmi/policy-packs';
38
+ *
39
+ * // Use a pack as-is:
40
+ * const policy = POLICY_PACKS['images-only'];
41
+ *
42
+ * // Or override individual fields:
43
+ * import { definePolicy } from 'pompelmi';
44
+ * const custom = definePolicy({ ...POLICY_PACKS['documents-only'], maxFileSizeBytes: 5 * 1024 * 1024 });
45
+ * ```
46
+ *
47
+ * These packs are *deterministic* and *descriptor-based* — they do not
48
+ * depend on any external threat intelligence feed.
49
+ *
50
+ * @module policy-packs
51
+ */
52
+ const KB = 1024;
53
+ const MB = 1024 * KB;
54
+ // ── Policy packs ──────────────────────────────────────────────────────────────
55
+ /**
56
+ * Documents-only policy.
57
+ *
58
+ * Appropriate for: document management APIs, PDF/Office file upload endpoints,
59
+ * data import pipelines.
60
+ *
61
+ * Allowed: PDF, Word (.docx/.doc), Excel (.xlsx/.xls), PowerPoint (.pptx/.ppt),
62
+ * CSV, plain text, JSON, YAML, ODT/ODS/ODP (OpenDocument).
63
+ * Max size: 25 MB.
64
+ */
65
+ const DOCUMENTS_ONLY = definePolicy({
66
+ includeExtensions: [
67
+ 'pdf',
68
+ 'doc', 'docx',
69
+ 'xls', 'xlsx',
70
+ 'ppt', 'pptx',
71
+ 'odt', 'ods', 'odp',
72
+ 'csv',
73
+ 'txt',
74
+ 'json',
75
+ 'yaml', 'yml',
76
+ 'md',
77
+ ],
78
+ allowedMimeTypes: [
79
+ 'application/pdf',
80
+ 'application/msword',
81
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
82
+ 'application/vnd.ms-excel',
83
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
84
+ 'application/vnd.ms-powerpoint',
85
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
86
+ 'application/vnd.oasis.opendocument.text',
87
+ 'application/vnd.oasis.opendocument.spreadsheet',
88
+ 'application/vnd.oasis.opendocument.presentation',
89
+ 'text/csv',
90
+ 'text/plain',
91
+ 'application/json',
92
+ 'text/yaml',
93
+ 'text/markdown',
94
+ ],
95
+ maxFileSizeBytes: 25 * MB,
96
+ timeoutMs: 10000,
97
+ concurrency: 4,
98
+ failClosed: true,
99
+ });
100
+ /**
101
+ * Images-only policy.
102
+ *
103
+ * Appropriate for: avatar uploads, product image APIs, content platforms with
104
+ * user-generated imagery.
105
+ *
106
+ * Allowed: JPEG, PNG, GIF, WebP, AVIF, TIFF, BMP, ICO.
107
+ * Max size: 10 MB.
108
+ * Note: SVG is intentionally excluded — inline SVGs can contain scripts.
109
+ */
110
+ const IMAGES_ONLY = definePolicy({
111
+ includeExtensions: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'avif', 'tiff', 'tif', 'bmp', 'ico'],
112
+ allowedMimeTypes: [
113
+ 'image/jpeg',
114
+ 'image/png',
115
+ 'image/gif',
116
+ 'image/webp',
117
+ 'image/avif',
118
+ 'image/tiff',
119
+ 'image/bmp',
120
+ 'image/x-icon',
121
+ 'image/vnd.microsoft.icon',
122
+ ],
123
+ maxFileSizeBytes: 10 * MB,
124
+ timeoutMs: 5000,
125
+ concurrency: 8,
126
+ failClosed: true,
127
+ });
128
+ /**
129
+ * Strict public-upload policy.
130
+ *
131
+ * Appropriate for: anonymous or low-trust upload endpoints, public APIs,
132
+ * any surface exposed to untrusted users.
133
+ *
134
+ * Aggressive size limit (5 MB), short timeout, fail-closed, narrow MIME
135
+ * allowlist. Only allows plain images and PDF.
136
+ */
137
+ const STRICT_PUBLIC_UPLOAD = definePolicy({
138
+ includeExtensions: ['jpg', 'jpeg', 'png', 'webp', 'pdf'],
139
+ allowedMimeTypes: [
140
+ 'image/jpeg',
141
+ 'image/png',
142
+ 'image/webp',
143
+ 'application/pdf',
144
+ ],
145
+ maxFileSizeBytes: 5 * MB,
146
+ timeoutMs: 4000,
147
+ concurrency: 2,
148
+ failClosed: true,
149
+ });
150
+ /**
151
+ * Conservative default policy.
152
+ *
153
+ * A hardened version of the built-in `DEFAULT_POLICY` suitable for
154
+ * production without further customisation. Stricter size limit and
155
+ * shorter timeout than the permissive default.
156
+ */
157
+ const CONSERVATIVE_DEFAULT = definePolicy({
158
+ includeExtensions: ['zip', 'png', 'jpg', 'jpeg', 'pdf', 'txt', 'csv', 'docx', 'xlsx'],
159
+ allowedMimeTypes: [
160
+ 'application/zip',
161
+ 'image/png',
162
+ 'image/jpeg',
163
+ 'application/pdf',
164
+ 'text/plain',
165
+ 'text/csv',
166
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
167
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
168
+ ],
169
+ maxFileSizeBytes: 10 * MB,
170
+ timeoutMs: 8000,
171
+ concurrency: 4,
172
+ failClosed: true,
173
+ });
174
+ /**
175
+ * Archives policy.
176
+ *
177
+ * Appropriate for: endpoints that accept ZIP, tar, or compressed archives.
178
+ * Combines a generous size allowance with a longer timeout for deep inspection.
179
+ *
180
+ * NOTE: Pair this policy with `createZipBombGuard()` to defend against
181
+ * decompression-bomb attacks:
182
+ *
183
+ * ```ts
184
+ * import { composeScanners, createZipBombGuard, CommonHeuristicsScanner } from 'pompelmi';
185
+ * const scanner = composeScanners(
186
+ * [['zipGuard', createZipBombGuard()], ['heuristics', CommonHeuristicsScanner]]
187
+ * );
188
+ * ```
189
+ */
190
+ const ARCHIVES = definePolicy({
191
+ includeExtensions: ['zip', 'tar', 'gz', 'tgz', 'bz2', 'xz', '7z', 'rar'],
192
+ allowedMimeTypes: [
193
+ 'application/zip',
194
+ 'application/x-tar',
195
+ 'application/gzip',
196
+ 'application/x-bzip2',
197
+ 'application/x-xz',
198
+ 'application/x-7z-compressed',
199
+ 'application/x-rar-compressed',
200
+ ],
201
+ maxFileSizeBytes: 100 * MB,
202
+ timeoutMs: 30000,
203
+ concurrency: 2,
204
+ failClosed: true,
205
+ });
206
+ /**
207
+ * Named map of all built-in policy packs.
208
+ *
209
+ * ```ts
210
+ * import { POLICY_PACKS } from 'pompelmi/policy-packs';
211
+ * const policy = POLICY_PACKS['strict-public-upload'];
212
+ * ```
213
+ */
214
+ const POLICY_PACKS = {
215
+ 'documents-only': DOCUMENTS_ONLY,
216
+ 'images-only': IMAGES_ONLY,
217
+ 'strict-public-upload': STRICT_PUBLIC_UPLOAD,
218
+ 'conservative-default': CONSERVATIVE_DEFAULT,
219
+ 'archives': ARCHIVES,
220
+ };
221
+ /**
222
+ * Look up a policy pack by name.
223
+ * Throws if the name is not recognised.
224
+ */
225
+ function getPolicyPack(name) {
226
+ const policy = POLICY_PACKS[name];
227
+ if (!policy)
228
+ throw new Error(`Unknown policy pack: '${name}'. Valid names: ${Object.keys(POLICY_PACKS).join(', ')}`);
229
+ return policy;
230
+ }
231
+
232
+ exports.ARCHIVES = ARCHIVES;
233
+ exports.CONSERVATIVE_DEFAULT = CONSERVATIVE_DEFAULT;
234
+ exports.DOCUMENTS_ONLY = DOCUMENTS_ONLY;
235
+ exports.IMAGES_ONLY = IMAGES_ONLY;
236
+ exports.POLICY_PACKS = POLICY_PACKS;
237
+ exports.STRICT_PUBLIC_UPLOAD = STRICT_PUBLIC_UPLOAD;
238
+ exports.getPolicyPack = getPolicyPack;
239
+ //# sourceMappingURL=pompelmi.policy-packs.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pompelmi.policy-packs.cjs","sources":["../src/policy.ts","../src/policy-packs.ts"],"sourcesContent":["export interface Policy {\n includeExtensions: string[];\n allowedMimeTypes: string[];\n maxFileSizeBytes: number;\n timeoutMs: number;\n concurrency: number;\n failClosed: boolean;\n onScanEvent?: (ev: unknown) => void;\n}\nexport type PolicyInput = Partial<Policy>;\n\nconst MB = 1024 * 1024;\n\nexport const DEFAULT_POLICY: Policy = {\n includeExtensions: ['zip','png','jpg','jpeg','pdf'],\n allowedMimeTypes: ['application/zip','image/png','image/jpeg','application/pdf','text/plain'],\n maxFileSizeBytes: 20 * MB,\n timeoutMs: 5000,\n concurrency: 4,\n failClosed: true\n};\n\nexport function definePolicy(input: PolicyInput = {}): Policy {\n const p: Policy = { ...DEFAULT_POLICY, ...input };\n if (!Array.isArray(p.includeExtensions)) throw new TypeError('includeExtensions must be string[]');\n if (!Array.isArray(p.allowedMimeTypes)) throw new TypeError('allowedMimeTypes must be string[]');\n if (!(Number.isFinite(p.maxFileSizeBytes) && p.maxFileSizeBytes > 0)) throw new TypeError('maxFileSizeBytes must be > 0');\n if (!(Number.isFinite(p.timeoutMs) && p.timeoutMs > 0)) throw new TypeError('timeoutMs must be > 0');\n if (!(Number.isInteger(p.concurrency) && p.concurrency > 0)) throw new TypeError('concurrency must be > 0');\n return p;\n}\n","/**\n * Policy packs for Pompelmi.\n *\n * Pre-configured, named policies for common upload scenarios. Each pack\n * defines the file type allowlist, size limits, and timeout appropriate for\n * its use case.\n *\n * All packs are built on `definePolicy` and are fully overridable:\n *\n * ```ts\n * import { POLICY_PACKS } from 'pompelmi/policy-packs';\n *\n * // Use a pack as-is:\n * const policy = POLICY_PACKS['images-only'];\n *\n * // Or override individual fields:\n * import { definePolicy } from 'pompelmi';\n * const custom = definePolicy({ ...POLICY_PACKS['documents-only'], maxFileSizeBytes: 5 * 1024 * 1024 });\n * ```\n *\n * These packs are *deterministic* and *descriptor-based* — they do not\n * depend on any external threat intelligence feed.\n *\n * @module policy-packs\n */\n\nimport { definePolicy, type Policy } from './policy';\n\nconst KB = 1024;\nconst MB = 1024 * KB;\n\n// ── Policy packs ──────────────────────────────────────────────────────────────\n\n/**\n * Documents-only policy.\n *\n * Appropriate for: document management APIs, PDF/Office file upload endpoints,\n * data import pipelines.\n *\n * Allowed: PDF, Word (.docx/.doc), Excel (.xlsx/.xls), PowerPoint (.pptx/.ppt),\n * CSV, plain text, JSON, YAML, ODT/ODS/ODP (OpenDocument).\n * Max size: 25 MB.\n */\nexport const DOCUMENTS_ONLY: Policy = definePolicy({\n includeExtensions: [\n 'pdf',\n 'doc', 'docx',\n 'xls', 'xlsx',\n 'ppt', 'pptx',\n 'odt', 'ods', 'odp',\n 'csv',\n 'txt',\n 'json',\n 'yaml', 'yml',\n 'md',\n ],\n allowedMimeTypes: [\n 'application/pdf',\n 'application/msword',\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n 'application/vnd.ms-excel',\n 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n 'application/vnd.ms-powerpoint',\n 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n 'application/vnd.oasis.opendocument.text',\n 'application/vnd.oasis.opendocument.spreadsheet',\n 'application/vnd.oasis.opendocument.presentation',\n 'text/csv',\n 'text/plain',\n 'application/json',\n 'text/yaml',\n 'text/markdown',\n ],\n maxFileSizeBytes: 25 * MB,\n timeoutMs: 10_000,\n concurrency: 4,\n failClosed: true,\n});\n\n/**\n * Images-only policy.\n *\n * Appropriate for: avatar uploads, product image APIs, content platforms with\n * user-generated imagery.\n *\n * Allowed: JPEG, PNG, GIF, WebP, AVIF, TIFF, BMP, ICO.\n * Max size: 10 MB.\n * Note: SVG is intentionally excluded — inline SVGs can contain scripts.\n */\nexport const IMAGES_ONLY: Policy = definePolicy({\n includeExtensions: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'avif', 'tiff', 'tif', 'bmp', 'ico'],\n allowedMimeTypes: [\n 'image/jpeg',\n 'image/png',\n 'image/gif',\n 'image/webp',\n 'image/avif',\n 'image/tiff',\n 'image/bmp',\n 'image/x-icon',\n 'image/vnd.microsoft.icon',\n ],\n maxFileSizeBytes: 10 * MB,\n timeoutMs: 5_000,\n concurrency: 8,\n failClosed: true,\n});\n\n/**\n * Strict public-upload policy.\n *\n * Appropriate for: anonymous or low-trust upload endpoints, public APIs,\n * any surface exposed to untrusted users.\n *\n * Aggressive size limit (5 MB), short timeout, fail-closed, narrow MIME\n * allowlist. Only allows plain images and PDF.\n */\nexport const STRICT_PUBLIC_UPLOAD: Policy = definePolicy({\n includeExtensions: ['jpg', 'jpeg', 'png', 'webp', 'pdf'],\n allowedMimeTypes: [\n 'image/jpeg',\n 'image/png',\n 'image/webp',\n 'application/pdf',\n ],\n maxFileSizeBytes: 5 * MB,\n timeoutMs: 4_000,\n concurrency: 2,\n failClosed: true,\n});\n\n/**\n * Conservative default policy.\n *\n * A hardened version of the built-in `DEFAULT_POLICY` suitable for\n * production without further customisation. Stricter size limit and\n * shorter timeout than the permissive default.\n */\nexport const CONSERVATIVE_DEFAULT: Policy = definePolicy({\n includeExtensions: ['zip', 'png', 'jpg', 'jpeg', 'pdf', 'txt', 'csv', 'docx', 'xlsx'],\n allowedMimeTypes: [\n 'application/zip',\n 'image/png',\n 'image/jpeg',\n 'application/pdf',\n 'text/plain',\n 'text/csv',\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n ],\n maxFileSizeBytes: 10 * MB,\n timeoutMs: 8_000,\n concurrency: 4,\n failClosed: true,\n});\n\n/**\n * Archives policy.\n *\n * Appropriate for: endpoints that accept ZIP, tar, or compressed archives.\n * Combines a generous size allowance with a longer timeout for deep inspection.\n *\n * NOTE: Pair this policy with `createZipBombGuard()` to defend against\n * decompression-bomb attacks:\n *\n * ```ts\n * import { composeScanners, createZipBombGuard, CommonHeuristicsScanner } from 'pompelmi';\n * const scanner = composeScanners(\n * [['zipGuard', createZipBombGuard()], ['heuristics', CommonHeuristicsScanner]]\n * );\n * ```\n */\nexport const ARCHIVES: Policy = definePolicy({\n includeExtensions: ['zip', 'tar', 'gz', 'tgz', 'bz2', 'xz', '7z', 'rar'],\n allowedMimeTypes: [\n 'application/zip',\n 'application/x-tar',\n 'application/gzip',\n 'application/x-bzip2',\n 'application/x-xz',\n 'application/x-7z-compressed',\n 'application/x-rar-compressed',\n ],\n maxFileSizeBytes: 100 * MB,\n timeoutMs: 30_000,\n concurrency: 2,\n failClosed: true,\n});\n\n// ── Named map ────────────────────────────────────────────────────────────────\n\nexport type PolicyPackName =\n | 'documents-only'\n | 'images-only'\n | 'strict-public-upload'\n | 'conservative-default'\n | 'archives';\n\n/**\n * Named map of all built-in policy packs.\n *\n * ```ts\n * import { POLICY_PACKS } from 'pompelmi/policy-packs';\n * const policy = POLICY_PACKS['strict-public-upload'];\n * ```\n */\nexport const POLICY_PACKS: Record<PolicyPackName, Policy> = {\n 'documents-only': DOCUMENTS_ONLY,\n 'images-only': IMAGES_ONLY,\n 'strict-public-upload': STRICT_PUBLIC_UPLOAD,\n 'conservative-default': CONSERVATIVE_DEFAULT,\n 'archives': ARCHIVES,\n};\n\n/**\n * Look up a policy pack by name.\n * Throws if the name is not recognised.\n */\nexport function getPolicyPack(name: PolicyPackName): Policy {\n const policy = POLICY_PACKS[name];\n if (!policy) throw new Error(`Unknown policy pack: '${name}'. Valid names: ${Object.keys(POLICY_PACKS).join(', ')}`);\n return policy;\n}\n"],"names":["MB"],"mappings":"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"}
@@ -0,0 +1,231 @@
1
+ const MB$1 = 1024 * 1024;
2
+ const DEFAULT_POLICY = {
3
+ includeExtensions: ['zip', 'png', 'jpg', 'jpeg', 'pdf'],
4
+ allowedMimeTypes: ['application/zip', 'image/png', 'image/jpeg', 'application/pdf', 'text/plain'],
5
+ maxFileSizeBytes: 20 * MB$1,
6
+ timeoutMs: 5000,
7
+ concurrency: 4,
8
+ failClosed: true
9
+ };
10
+ function definePolicy(input = {}) {
11
+ const p = { ...DEFAULT_POLICY, ...input };
12
+ if (!Array.isArray(p.includeExtensions))
13
+ throw new TypeError('includeExtensions must be string[]');
14
+ if (!Array.isArray(p.allowedMimeTypes))
15
+ throw new TypeError('allowedMimeTypes must be string[]');
16
+ if (!(Number.isFinite(p.maxFileSizeBytes) && p.maxFileSizeBytes > 0))
17
+ throw new TypeError('maxFileSizeBytes must be > 0');
18
+ if (!(Number.isFinite(p.timeoutMs) && p.timeoutMs > 0))
19
+ throw new TypeError('timeoutMs must be > 0');
20
+ if (!(Number.isInteger(p.concurrency) && p.concurrency > 0))
21
+ throw new TypeError('concurrency must be > 0');
22
+ return p;
23
+ }
24
+
25
+ /**
26
+ * Policy packs for Pompelmi.
27
+ *
28
+ * Pre-configured, named policies for common upload scenarios. Each pack
29
+ * defines the file type allowlist, size limits, and timeout appropriate for
30
+ * its use case.
31
+ *
32
+ * All packs are built on `definePolicy` and are fully overridable:
33
+ *
34
+ * ```ts
35
+ * import { POLICY_PACKS } from 'pompelmi/policy-packs';
36
+ *
37
+ * // Use a pack as-is:
38
+ * const policy = POLICY_PACKS['images-only'];
39
+ *
40
+ * // Or override individual fields:
41
+ * import { definePolicy } from 'pompelmi';
42
+ * const custom = definePolicy({ ...POLICY_PACKS['documents-only'], maxFileSizeBytes: 5 * 1024 * 1024 });
43
+ * ```
44
+ *
45
+ * These packs are *deterministic* and *descriptor-based* — they do not
46
+ * depend on any external threat intelligence feed.
47
+ *
48
+ * @module policy-packs
49
+ */
50
+ const KB = 1024;
51
+ const MB = 1024 * KB;
52
+ // ── Policy packs ──────────────────────────────────────────────────────────────
53
+ /**
54
+ * Documents-only policy.
55
+ *
56
+ * Appropriate for: document management APIs, PDF/Office file upload endpoints,
57
+ * data import pipelines.
58
+ *
59
+ * Allowed: PDF, Word (.docx/.doc), Excel (.xlsx/.xls), PowerPoint (.pptx/.ppt),
60
+ * CSV, plain text, JSON, YAML, ODT/ODS/ODP (OpenDocument).
61
+ * Max size: 25 MB.
62
+ */
63
+ const DOCUMENTS_ONLY = definePolicy({
64
+ includeExtensions: [
65
+ 'pdf',
66
+ 'doc', 'docx',
67
+ 'xls', 'xlsx',
68
+ 'ppt', 'pptx',
69
+ 'odt', 'ods', 'odp',
70
+ 'csv',
71
+ 'txt',
72
+ 'json',
73
+ 'yaml', 'yml',
74
+ 'md',
75
+ ],
76
+ allowedMimeTypes: [
77
+ 'application/pdf',
78
+ 'application/msword',
79
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
80
+ 'application/vnd.ms-excel',
81
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
82
+ 'application/vnd.ms-powerpoint',
83
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
84
+ 'application/vnd.oasis.opendocument.text',
85
+ 'application/vnd.oasis.opendocument.spreadsheet',
86
+ 'application/vnd.oasis.opendocument.presentation',
87
+ 'text/csv',
88
+ 'text/plain',
89
+ 'application/json',
90
+ 'text/yaml',
91
+ 'text/markdown',
92
+ ],
93
+ maxFileSizeBytes: 25 * MB,
94
+ timeoutMs: 10000,
95
+ concurrency: 4,
96
+ failClosed: true,
97
+ });
98
+ /**
99
+ * Images-only policy.
100
+ *
101
+ * Appropriate for: avatar uploads, product image APIs, content platforms with
102
+ * user-generated imagery.
103
+ *
104
+ * Allowed: JPEG, PNG, GIF, WebP, AVIF, TIFF, BMP, ICO.
105
+ * Max size: 10 MB.
106
+ * Note: SVG is intentionally excluded — inline SVGs can contain scripts.
107
+ */
108
+ const IMAGES_ONLY = definePolicy({
109
+ includeExtensions: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'avif', 'tiff', 'tif', 'bmp', 'ico'],
110
+ allowedMimeTypes: [
111
+ 'image/jpeg',
112
+ 'image/png',
113
+ 'image/gif',
114
+ 'image/webp',
115
+ 'image/avif',
116
+ 'image/tiff',
117
+ 'image/bmp',
118
+ 'image/x-icon',
119
+ 'image/vnd.microsoft.icon',
120
+ ],
121
+ maxFileSizeBytes: 10 * MB,
122
+ timeoutMs: 5000,
123
+ concurrency: 8,
124
+ failClosed: true,
125
+ });
126
+ /**
127
+ * Strict public-upload policy.
128
+ *
129
+ * Appropriate for: anonymous or low-trust upload endpoints, public APIs,
130
+ * any surface exposed to untrusted users.
131
+ *
132
+ * Aggressive size limit (5 MB), short timeout, fail-closed, narrow MIME
133
+ * allowlist. Only allows plain images and PDF.
134
+ */
135
+ const STRICT_PUBLIC_UPLOAD = definePolicy({
136
+ includeExtensions: ['jpg', 'jpeg', 'png', 'webp', 'pdf'],
137
+ allowedMimeTypes: [
138
+ 'image/jpeg',
139
+ 'image/png',
140
+ 'image/webp',
141
+ 'application/pdf',
142
+ ],
143
+ maxFileSizeBytes: 5 * MB,
144
+ timeoutMs: 4000,
145
+ concurrency: 2,
146
+ failClosed: true,
147
+ });
148
+ /**
149
+ * Conservative default policy.
150
+ *
151
+ * A hardened version of the built-in `DEFAULT_POLICY` suitable for
152
+ * production without further customisation. Stricter size limit and
153
+ * shorter timeout than the permissive default.
154
+ */
155
+ const CONSERVATIVE_DEFAULT = definePolicy({
156
+ includeExtensions: ['zip', 'png', 'jpg', 'jpeg', 'pdf', 'txt', 'csv', 'docx', 'xlsx'],
157
+ allowedMimeTypes: [
158
+ 'application/zip',
159
+ 'image/png',
160
+ 'image/jpeg',
161
+ 'application/pdf',
162
+ 'text/plain',
163
+ 'text/csv',
164
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
165
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
166
+ ],
167
+ maxFileSizeBytes: 10 * MB,
168
+ timeoutMs: 8000,
169
+ concurrency: 4,
170
+ failClosed: true,
171
+ });
172
+ /**
173
+ * Archives policy.
174
+ *
175
+ * Appropriate for: endpoints that accept ZIP, tar, or compressed archives.
176
+ * Combines a generous size allowance with a longer timeout for deep inspection.
177
+ *
178
+ * NOTE: Pair this policy with `createZipBombGuard()` to defend against
179
+ * decompression-bomb attacks:
180
+ *
181
+ * ```ts
182
+ * import { composeScanners, createZipBombGuard, CommonHeuristicsScanner } from 'pompelmi';
183
+ * const scanner = composeScanners(
184
+ * [['zipGuard', createZipBombGuard()], ['heuristics', CommonHeuristicsScanner]]
185
+ * );
186
+ * ```
187
+ */
188
+ const ARCHIVES = definePolicy({
189
+ includeExtensions: ['zip', 'tar', 'gz', 'tgz', 'bz2', 'xz', '7z', 'rar'],
190
+ allowedMimeTypes: [
191
+ 'application/zip',
192
+ 'application/x-tar',
193
+ 'application/gzip',
194
+ 'application/x-bzip2',
195
+ 'application/x-xz',
196
+ 'application/x-7z-compressed',
197
+ 'application/x-rar-compressed',
198
+ ],
199
+ maxFileSizeBytes: 100 * MB,
200
+ timeoutMs: 30000,
201
+ concurrency: 2,
202
+ failClosed: true,
203
+ });
204
+ /**
205
+ * Named map of all built-in policy packs.
206
+ *
207
+ * ```ts
208
+ * import { POLICY_PACKS } from 'pompelmi/policy-packs';
209
+ * const policy = POLICY_PACKS['strict-public-upload'];
210
+ * ```
211
+ */
212
+ const POLICY_PACKS = {
213
+ 'documents-only': DOCUMENTS_ONLY,
214
+ 'images-only': IMAGES_ONLY,
215
+ 'strict-public-upload': STRICT_PUBLIC_UPLOAD,
216
+ 'conservative-default': CONSERVATIVE_DEFAULT,
217
+ 'archives': ARCHIVES,
218
+ };
219
+ /**
220
+ * Look up a policy pack by name.
221
+ * Throws if the name is not recognised.
222
+ */
223
+ function getPolicyPack(name) {
224
+ const policy = POLICY_PACKS[name];
225
+ if (!policy)
226
+ throw new Error(`Unknown policy pack: '${name}'. Valid names: ${Object.keys(POLICY_PACKS).join(', ')}`);
227
+ return policy;
228
+ }
229
+
230
+ export { ARCHIVES, CONSERVATIVE_DEFAULT, DOCUMENTS_ONLY, IMAGES_ONLY, POLICY_PACKS, STRICT_PUBLIC_UPLOAD, getPolicyPack };
231
+ //# sourceMappingURL=pompelmi.policy-packs.esm.js.map