pompelmi 0.34.10 → 0.35.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 (112) hide show
  1. package/README.md +26 -15
  2. package/dist/pompelmi.audit.cjs +13 -15
  3. package/dist/pompelmi.audit.cjs.map +1 -1
  4. package/dist/pompelmi.audit.esm.js +13 -15
  5. package/dist/pompelmi.audit.esm.js.map +1 -1
  6. package/dist/pompelmi.browser.cjs +585 -534
  7. package/dist/pompelmi.browser.cjs.map +1 -1
  8. package/dist/pompelmi.browser.esm.js +585 -534
  9. package/dist/pompelmi.browser.esm.js.map +1 -1
  10. package/dist/pompelmi.cjs +2066 -2016
  11. package/dist/pompelmi.cjs.map +1 -1
  12. package/dist/pompelmi.esm.js +2066 -2016
  13. package/dist/pompelmi.esm.js.map +1 -1
  14. package/dist/pompelmi.hooks.cjs +2 -2
  15. package/dist/pompelmi.hooks.cjs.map +1 -1
  16. package/dist/pompelmi.hooks.esm.js +2 -2
  17. package/dist/pompelmi.hooks.esm.js.map +1 -1
  18. package/dist/pompelmi.policy-packs.cjs +74 -73
  19. package/dist/pompelmi.policy-packs.cjs.map +1 -1
  20. package/dist/pompelmi.policy-packs.esm.js +74 -73
  21. package/dist/pompelmi.policy-packs.esm.js.map +1 -1
  22. package/dist/pompelmi.quarantine.cjs +135 -133
  23. package/dist/pompelmi.quarantine.cjs.map +1 -1
  24. package/dist/pompelmi.quarantine.esm.js +135 -133
  25. package/dist/pompelmi.quarantine.esm.js.map +1 -1
  26. package/dist/pompelmi.react.cjs +585 -534
  27. package/dist/pompelmi.react.cjs.map +1 -1
  28. package/dist/pompelmi.react.esm.js +585 -534
  29. package/dist/pompelmi.react.esm.js.map +1 -1
  30. package/dist/types/audit.d.ts +12 -12
  31. package/dist/types/browser-index.d.ts +12 -12
  32. package/dist/types/config.d.ts +4 -4
  33. package/dist/types/engines/dynamic-taint.d.ts +1 -1
  34. package/dist/types/engines/hybrid-orchestrator.d.ts +1 -1
  35. package/dist/types/engines/hybrid-taint-integration.d.ts +6 -6
  36. package/dist/types/engines/taint-policies.d.ts +4 -4
  37. package/dist/types/hipaa-compliance.d.ts +2 -2
  38. package/dist/types/hooks.d.ts +2 -2
  39. package/dist/types/index.d.ts +20 -20
  40. package/dist/types/node/scanDir.d.ts +5 -5
  41. package/dist/types/policy-packs.d.ts +2 -2
  42. package/dist/types/presets.d.ts +3 -3
  43. package/dist/types/quarantine/index.d.ts +3 -3
  44. package/dist/types/quarantine/storage.d.ts +1 -1
  45. package/dist/types/quarantine/types.d.ts +3 -3
  46. package/dist/types/quarantine/workflow.d.ts +4 -4
  47. package/dist/types/react-index.d.ts +2 -2
  48. package/dist/types/risk.d.ts +1 -1
  49. package/dist/types/scan/remote.d.ts +2 -2
  50. package/dist/types/scan.d.ts +5 -5
  51. package/dist/types/scanners/common-heuristics.d.ts +1 -1
  52. package/dist/types/scanners/zip-bomb-guard.d.ts +1 -1
  53. package/dist/types/src/audit.d.ts +84 -0
  54. package/dist/types/src/browser-index.d.ts +29 -0
  55. package/dist/types/src/config.d.ts +143 -0
  56. package/dist/types/src/engines/dynamic-taint.d.ts +102 -0
  57. package/dist/types/src/engines/hybrid-orchestrator.d.ts +65 -0
  58. package/dist/types/src/engines/hybrid-taint-integration.d.ts +129 -0
  59. package/dist/types/src/engines/taint-policies.d.ts +84 -0
  60. package/dist/types/src/hipaa-compliance.d.ts +110 -0
  61. package/dist/types/src/hooks.d.ts +89 -0
  62. package/dist/types/src/index.d.ts +29 -0
  63. package/dist/types/src/magic.d.ts +7 -0
  64. package/dist/types/src/node/scanDir.d.ts +30 -0
  65. package/dist/types/src/policy-packs.d.ts +98 -0
  66. package/dist/types/src/policy.d.ts +12 -0
  67. package/dist/types/src/presets.d.ts +72 -0
  68. package/dist/types/src/quarantine/index.d.ts +18 -0
  69. package/dist/types/src/quarantine/storage.d.ts +77 -0
  70. package/dist/types/src/quarantine/types.d.ts +78 -0
  71. package/dist/types/src/quarantine/workflow.d.ts +97 -0
  72. package/dist/types/src/react-index.d.ts +13 -0
  73. package/dist/types/src/risk.d.ts +18 -0
  74. package/dist/types/src/scan/remote.d.ts +12 -0
  75. package/dist/types/src/scan.d.ts +17 -0
  76. package/dist/types/src/scanners/common-heuristics.d.ts +14 -0
  77. package/dist/types/src/scanners/zip-bomb-guard.d.ts +9 -0
  78. package/dist/types/src/scanners/zipTraversalGuard.d.ts +19 -0
  79. package/dist/types/src/stream.d.ts +10 -0
  80. package/dist/types/src/types/decompilation.d.ts +96 -0
  81. package/dist/types/src/types/taint-tracking.d.ts +495 -0
  82. package/dist/types/src/types.d.ts +48 -0
  83. package/dist/types/src/useFileScanner.d.ts +15 -0
  84. package/dist/types/src/utils/advanced-detection.d.ts +21 -0
  85. package/dist/types/src/utils/batch-scanner.d.ts +62 -0
  86. package/dist/types/src/utils/cache-manager.d.ts +95 -0
  87. package/dist/types/src/utils/export.d.ts +51 -0
  88. package/dist/types/src/utils/performance-metrics.d.ts +68 -0
  89. package/dist/types/src/utils/threat-intelligence.d.ts +96 -0
  90. package/dist/types/src/validate.d.ts +7 -0
  91. package/dist/types/src/verdict.d.ts +2 -0
  92. package/dist/types/src/yara/browser.d.ts +7 -0
  93. package/dist/types/src/yara/index.d.ts +17 -0
  94. package/dist/types/src/yara/node.d.ts +2 -0
  95. package/dist/types/src/yara/remote.d.ts +10 -0
  96. package/dist/types/src/yara-bridge.d.ts +3 -0
  97. package/dist/types/src/zip.d.ts +13 -0
  98. package/dist/types/types/decompilation.d.ts +4 -4
  99. package/dist/types/types/taint-tracking.d.ts +19 -19
  100. package/dist/types/types.d.ts +3 -3
  101. package/dist/types/useFileScanner.d.ts +1 -1
  102. package/dist/types/utils/advanced-detection.d.ts +1 -1
  103. package/dist/types/utils/batch-scanner.d.ts +3 -3
  104. package/dist/types/utils/cache-manager.d.ts +1 -1
  105. package/dist/types/utils/export.d.ts +2 -2
  106. package/dist/types/utils/threat-intelligence.d.ts +4 -4
  107. package/dist/types/verdict.d.ts +1 -1
  108. package/dist/types/yara/browser.d.ts +1 -1
  109. package/dist/types/yara/index.d.ts +1 -1
  110. package/dist/types/yara/node.d.ts +1 -1
  111. package/dist/types/yara/remote.d.ts +2 -2
  112. package/package.json +6 -6
@@ -46,7 +46,7 @@ function createScanHooks(hooks) {
46
46
  */
47
47
  function withHooks(scanFn, hooks) {
48
48
  return async (bytes, opts = {}) => {
49
- const scanId = typeof crypto !== 'undefined' && 'randomUUID' in crypto
49
+ const scanId = typeof crypto !== "undefined" && "randomUUID" in crypto
50
50
  ? crypto.randomUUID()
51
51
  : undefined;
52
52
  const startedAt = Date.now();
@@ -63,7 +63,7 @@ function withHooks(scanFn, hooks) {
63
63
  const durationMs = Date.now() - startedAt;
64
64
  const completeCtx = { ...ctx, durationMs };
65
65
  void hooks.onScanComplete?.(completeCtx, report);
66
- if (report.verdict === 'suspicious' || report.verdict === 'malicious') {
66
+ if (report.verdict === "suspicious" || report.verdict === "malicious") {
67
67
  void hooks.onThreatDetected?.(completeCtx, report);
68
68
  }
69
69
  return report;
@@ -1 +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;;;;;"}
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 { QuarantineEntry } from \"./quarantine/types\";\nimport type { ScanContext, ScanReport } from \"./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 = (\n bytes: Uint8Array,\n opts?: { ctx?: ScanContext; [k: string]: unknown },\n) => 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 =\n 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;AASA;;;;;AAKG;AACG,SAAU,SAAS,CAAC,MAAc,EAAE,KAAgB,EAAA;IACxD,OAAO,OAAO,KAAK,EAAE,IAAI,GAAG,EAAE,KAAI;QAChC,MAAM,MAAM,GACV,OAAO,MAAM,KAAK,WAAW,IAAI,YAAY,IAAI;AAC/C,cAAG,MAAmC,CAAC,UAAU;cAC/C,SAAS;AAEf,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;;;;;"}
@@ -44,7 +44,7 @@ function createScanHooks(hooks) {
44
44
  */
45
45
  function withHooks(scanFn, hooks) {
46
46
  return async (bytes, opts = {}) => {
47
- const scanId = typeof crypto !== 'undefined' && 'randomUUID' in crypto
47
+ const scanId = typeof crypto !== "undefined" && "randomUUID" in crypto
48
48
  ? crypto.randomUUID()
49
49
  : undefined;
50
50
  const startedAt = Date.now();
@@ -61,7 +61,7 @@ function withHooks(scanFn, hooks) {
61
61
  const durationMs = Date.now() - startedAt;
62
62
  const completeCtx = { ...ctx, durationMs };
63
63
  void hooks.onScanComplete?.(completeCtx, report);
64
- if (report.verdict === 'suspicious' || report.verdict === 'malicious') {
64
+ if (report.verdict === "suspicious" || report.verdict === "malicious") {
65
65
  void hooks.onThreatDetected?.(completeCtx, report);
66
66
  }
67
67
  return report;
@@ -1 +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;;;;"}
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 { QuarantineEntry } from \"./quarantine/types\";\nimport type { ScanContext, ScanReport } from \"./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 = (\n bytes: Uint8Array,\n opts?: { ctx?: ScanContext; [k: string]: unknown },\n) => 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 =\n 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;AASA;;;;;AAKG;AACG,SAAU,SAAS,CAAC,MAAc,EAAE,KAAgB,EAAA;IACxD,OAAO,OAAO,KAAK,EAAE,IAAI,GAAG,EAAE,KAAI;QAChC,MAAM,MAAM,GACV,OAAO,MAAM,KAAK,WAAW,IAAI,YAAY,IAAI;AAC/C,cAAG,MAAmC,CAAC,UAAU;cAC/C,SAAS;AAEf,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;;;;"}
@@ -2,25 +2,25 @@
2
2
 
3
3
  const MB$1 = 1024 * 1024;
4
4
  const DEFAULT_POLICY = {
5
- includeExtensions: ['zip', 'png', 'jpg', 'jpeg', 'pdf'],
6
- allowedMimeTypes: ['application/zip', 'image/png', 'image/jpeg', 'application/pdf', 'text/plain'],
5
+ includeExtensions: ["zip", "png", "jpg", "jpeg", "pdf"],
6
+ allowedMimeTypes: ["application/zip", "image/png", "image/jpeg", "application/pdf", "text/plain"],
7
7
  maxFileSizeBytes: 20 * MB$1,
8
8
  timeoutMs: 5000,
9
9
  concurrency: 4,
10
- failClosed: true
10
+ failClosed: true,
11
11
  };
12
12
  function definePolicy(input = {}) {
13
13
  const p = { ...DEFAULT_POLICY, ...input };
14
14
  if (!Array.isArray(p.includeExtensions))
15
- throw new TypeError('includeExtensions must be string[]');
15
+ throw new TypeError("includeExtensions must be string[]");
16
16
  if (!Array.isArray(p.allowedMimeTypes))
17
- throw new TypeError('allowedMimeTypes must be string[]');
17
+ throw new TypeError("allowedMimeTypes must be string[]");
18
18
  if (!(Number.isFinite(p.maxFileSizeBytes) && p.maxFileSizeBytes > 0))
19
- throw new TypeError('maxFileSizeBytes must be > 0');
19
+ throw new TypeError("maxFileSizeBytes must be > 0");
20
20
  if (!(Number.isFinite(p.timeoutMs) && p.timeoutMs > 0))
21
- throw new TypeError('timeoutMs must be > 0');
21
+ throw new TypeError("timeoutMs must be > 0");
22
22
  if (!(Number.isInteger(p.concurrency) && p.concurrency > 0))
23
- throw new TypeError('concurrency must be > 0');
23
+ throw new TypeError("concurrency must be > 0");
24
24
  return p;
25
25
  }
26
26
 
@@ -64,33 +64,39 @@ const MB = 1024 * KB;
64
64
  */
65
65
  const DOCUMENTS_ONLY = definePolicy({
66
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',
67
+ "pdf",
68
+ "doc",
69
+ "docx",
70
+ "xls",
71
+ "xlsx",
72
+ "ppt",
73
+ "pptx",
74
+ "odt",
75
+ "ods",
76
+ "odp",
77
+ "csv",
78
+ "txt",
79
+ "json",
80
+ "yaml",
81
+ "yml",
82
+ "md",
77
83
  ],
78
84
  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',
85
+ "application/pdf",
86
+ "application/msword",
87
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
88
+ "application/vnd.ms-excel",
89
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
90
+ "application/vnd.ms-powerpoint",
91
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation",
92
+ "application/vnd.oasis.opendocument.text",
93
+ "application/vnd.oasis.opendocument.spreadsheet",
94
+ "application/vnd.oasis.opendocument.presentation",
95
+ "text/csv",
96
+ "text/plain",
97
+ "application/json",
98
+ "text/yaml",
99
+ "text/markdown",
94
100
  ],
95
101
  maxFileSizeBytes: 25 * MB,
96
102
  timeoutMs: 10000,
@@ -108,17 +114,17 @@ const DOCUMENTS_ONLY = definePolicy({
108
114
  * Note: SVG is intentionally excluded — inline SVGs can contain scripts.
109
115
  */
110
116
  const IMAGES_ONLY = definePolicy({
111
- includeExtensions: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'avif', 'tiff', 'tif', 'bmp', 'ico'],
117
+ includeExtensions: ["jpg", "jpeg", "png", "gif", "webp", "avif", "tiff", "tif", "bmp", "ico"],
112
118
  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',
119
+ "image/jpeg",
120
+ "image/png",
121
+ "image/gif",
122
+ "image/webp",
123
+ "image/avif",
124
+ "image/tiff",
125
+ "image/bmp",
126
+ "image/x-icon",
127
+ "image/vnd.microsoft.icon",
122
128
  ],
123
129
  maxFileSizeBytes: 10 * MB,
124
130
  timeoutMs: 5000,
@@ -135,13 +141,8 @@ const IMAGES_ONLY = definePolicy({
135
141
  * allowlist. Only allows plain images and PDF.
136
142
  */
137
143
  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
- ],
144
+ includeExtensions: ["jpg", "jpeg", "png", "webp", "pdf"],
145
+ allowedMimeTypes: ["image/jpeg", "image/png", "image/webp", "application/pdf"],
145
146
  maxFileSizeBytes: 5 * MB,
146
147
  timeoutMs: 4000,
147
148
  concurrency: 2,
@@ -155,16 +156,16 @@ const STRICT_PUBLIC_UPLOAD = definePolicy({
155
156
  * shorter timeout than the permissive default.
156
157
  */
157
158
  const CONSERVATIVE_DEFAULT = definePolicy({
158
- includeExtensions: ['zip', 'png', 'jpg', 'jpeg', 'pdf', 'txt', 'csv', 'docx', 'xlsx'],
159
+ includeExtensions: ["zip", "png", "jpg", "jpeg", "pdf", "txt", "csv", "docx", "xlsx"],
159
160
  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',
161
+ "application/zip",
162
+ "image/png",
163
+ "image/jpeg",
164
+ "application/pdf",
165
+ "text/plain",
166
+ "text/csv",
167
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
168
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
168
169
  ],
169
170
  maxFileSizeBytes: 10 * MB,
170
171
  timeoutMs: 8000,
@@ -188,15 +189,15 @@ const CONSERVATIVE_DEFAULT = definePolicy({
188
189
  * ```
189
190
  */
190
191
  const ARCHIVES = definePolicy({
191
- includeExtensions: ['zip', 'tar', 'gz', 'tgz', 'bz2', 'xz', '7z', 'rar'],
192
+ includeExtensions: ["zip", "tar", "gz", "tgz", "bz2", "xz", "7z", "rar"],
192
193
  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',
194
+ "application/zip",
195
+ "application/x-tar",
196
+ "application/gzip",
197
+ "application/x-bzip2",
198
+ "application/x-xz",
199
+ "application/x-7z-compressed",
200
+ "application/x-rar-compressed",
200
201
  ],
201
202
  maxFileSizeBytes: 100 * MB,
202
203
  timeoutMs: 30000,
@@ -212,11 +213,11 @@ const ARCHIVES = definePolicy({
212
213
  * ```
213
214
  */
214
215
  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,
216
+ "documents-only": DOCUMENTS_ONLY,
217
+ "images-only": IMAGES_ONLY,
218
+ "strict-public-upload": STRICT_PUBLIC_UPLOAD,
219
+ "conservative-default": CONSERVATIVE_DEFAULT,
220
+ archives: ARCHIVES,
220
221
  };
221
222
  /**
222
223
  * Look up a policy pack by name.
@@ -225,7 +226,7 @@ const POLICY_PACKS = {
225
226
  function getPolicyPack(name) {
226
227
  const policy = POLICY_PACKS[name];
227
228
  if (!policy)
228
- throw new Error(`Unknown policy pack: '${name}'. Valid names: ${Object.keys(POLICY_PACKS).join(', ')}`);
229
+ throw new Error(`Unknown policy pack: '${name}'. Valid names: ${Object.keys(POLICY_PACKS).join(", ")}`);
229
230
  return policy;
230
231
  }
231
232
 
@@ -1 +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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"}
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))\n 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))\n throw new TypeError(\"maxFileSizeBytes must be > 0\");\n if (!(Number.isFinite(p.timeoutMs) && p.timeoutMs > 0))\n throw new TypeError(\"timeoutMs must be > 0\");\n if (!(Number.isInteger(p.concurrency) && p.concurrency > 0))\n 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\",\n \"docx\",\n \"xls\",\n \"xlsx\",\n \"ppt\",\n \"pptx\",\n \"odt\",\n \"ods\",\n \"odp\",\n \"csv\",\n \"txt\",\n \"json\",\n \"yaml\",\n \"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: [\"image/jpeg\", \"image/png\", \"image/webp\", \"application/pdf\"],\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)\n throw new Error(\n `Unknown policy pack: '${name}'. Valid names: ${Object.keys(POLICY_PACKS).join(\", \")}`,\n );\n return policy;\n}\n"],"names":["MB"],"mappings":"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"}
@@ -1,24 +1,24 @@
1
1
  const MB$1 = 1024 * 1024;
2
2
  const DEFAULT_POLICY = {
3
- includeExtensions: ['zip', 'png', 'jpg', 'jpeg', 'pdf'],
4
- allowedMimeTypes: ['application/zip', 'image/png', 'image/jpeg', 'application/pdf', 'text/plain'],
3
+ includeExtensions: ["zip", "png", "jpg", "jpeg", "pdf"],
4
+ allowedMimeTypes: ["application/zip", "image/png", "image/jpeg", "application/pdf", "text/plain"],
5
5
  maxFileSizeBytes: 20 * MB$1,
6
6
  timeoutMs: 5000,
7
7
  concurrency: 4,
8
- failClosed: true
8
+ failClosed: true,
9
9
  };
10
10
  function definePolicy(input = {}) {
11
11
  const p = { ...DEFAULT_POLICY, ...input };
12
12
  if (!Array.isArray(p.includeExtensions))
13
- throw new TypeError('includeExtensions must be string[]');
13
+ throw new TypeError("includeExtensions must be string[]");
14
14
  if (!Array.isArray(p.allowedMimeTypes))
15
- throw new TypeError('allowedMimeTypes must be string[]');
15
+ throw new TypeError("allowedMimeTypes must be string[]");
16
16
  if (!(Number.isFinite(p.maxFileSizeBytes) && p.maxFileSizeBytes > 0))
17
- throw new TypeError('maxFileSizeBytes must be > 0');
17
+ throw new TypeError("maxFileSizeBytes must be > 0");
18
18
  if (!(Number.isFinite(p.timeoutMs) && p.timeoutMs > 0))
19
- throw new TypeError('timeoutMs must be > 0');
19
+ throw new TypeError("timeoutMs must be > 0");
20
20
  if (!(Number.isInteger(p.concurrency) && p.concurrency > 0))
21
- throw new TypeError('concurrency must be > 0');
21
+ throw new TypeError("concurrency must be > 0");
22
22
  return p;
23
23
  }
24
24
 
@@ -62,33 +62,39 @@ const MB = 1024 * KB;
62
62
  */
63
63
  const DOCUMENTS_ONLY = definePolicy({
64
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',
65
+ "pdf",
66
+ "doc",
67
+ "docx",
68
+ "xls",
69
+ "xlsx",
70
+ "ppt",
71
+ "pptx",
72
+ "odt",
73
+ "ods",
74
+ "odp",
75
+ "csv",
76
+ "txt",
77
+ "json",
78
+ "yaml",
79
+ "yml",
80
+ "md",
75
81
  ],
76
82
  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',
83
+ "application/pdf",
84
+ "application/msword",
85
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
86
+ "application/vnd.ms-excel",
87
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
88
+ "application/vnd.ms-powerpoint",
89
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation",
90
+ "application/vnd.oasis.opendocument.text",
91
+ "application/vnd.oasis.opendocument.spreadsheet",
92
+ "application/vnd.oasis.opendocument.presentation",
93
+ "text/csv",
94
+ "text/plain",
95
+ "application/json",
96
+ "text/yaml",
97
+ "text/markdown",
92
98
  ],
93
99
  maxFileSizeBytes: 25 * MB,
94
100
  timeoutMs: 10000,
@@ -106,17 +112,17 @@ const DOCUMENTS_ONLY = definePolicy({
106
112
  * Note: SVG is intentionally excluded — inline SVGs can contain scripts.
107
113
  */
108
114
  const IMAGES_ONLY = definePolicy({
109
- includeExtensions: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'avif', 'tiff', 'tif', 'bmp', 'ico'],
115
+ includeExtensions: ["jpg", "jpeg", "png", "gif", "webp", "avif", "tiff", "tif", "bmp", "ico"],
110
116
  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',
117
+ "image/jpeg",
118
+ "image/png",
119
+ "image/gif",
120
+ "image/webp",
121
+ "image/avif",
122
+ "image/tiff",
123
+ "image/bmp",
124
+ "image/x-icon",
125
+ "image/vnd.microsoft.icon",
120
126
  ],
121
127
  maxFileSizeBytes: 10 * MB,
122
128
  timeoutMs: 5000,
@@ -133,13 +139,8 @@ const IMAGES_ONLY = definePolicy({
133
139
  * allowlist. Only allows plain images and PDF.
134
140
  */
135
141
  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
- ],
142
+ includeExtensions: ["jpg", "jpeg", "png", "webp", "pdf"],
143
+ allowedMimeTypes: ["image/jpeg", "image/png", "image/webp", "application/pdf"],
143
144
  maxFileSizeBytes: 5 * MB,
144
145
  timeoutMs: 4000,
145
146
  concurrency: 2,
@@ -153,16 +154,16 @@ const STRICT_PUBLIC_UPLOAD = definePolicy({
153
154
  * shorter timeout than the permissive default.
154
155
  */
155
156
  const CONSERVATIVE_DEFAULT = definePolicy({
156
- includeExtensions: ['zip', 'png', 'jpg', 'jpeg', 'pdf', 'txt', 'csv', 'docx', 'xlsx'],
157
+ includeExtensions: ["zip", "png", "jpg", "jpeg", "pdf", "txt", "csv", "docx", "xlsx"],
157
158
  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',
159
+ "application/zip",
160
+ "image/png",
161
+ "image/jpeg",
162
+ "application/pdf",
163
+ "text/plain",
164
+ "text/csv",
165
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
166
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
166
167
  ],
167
168
  maxFileSizeBytes: 10 * MB,
168
169
  timeoutMs: 8000,
@@ -186,15 +187,15 @@ const CONSERVATIVE_DEFAULT = definePolicy({
186
187
  * ```
187
188
  */
188
189
  const ARCHIVES = definePolicy({
189
- includeExtensions: ['zip', 'tar', 'gz', 'tgz', 'bz2', 'xz', '7z', 'rar'],
190
+ includeExtensions: ["zip", "tar", "gz", "tgz", "bz2", "xz", "7z", "rar"],
190
191
  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',
192
+ "application/zip",
193
+ "application/x-tar",
194
+ "application/gzip",
195
+ "application/x-bzip2",
196
+ "application/x-xz",
197
+ "application/x-7z-compressed",
198
+ "application/x-rar-compressed",
198
199
  ],
199
200
  maxFileSizeBytes: 100 * MB,
200
201
  timeoutMs: 30000,
@@ -210,11 +211,11 @@ const ARCHIVES = definePolicy({
210
211
  * ```
211
212
  */
212
213
  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,
214
+ "documents-only": DOCUMENTS_ONLY,
215
+ "images-only": IMAGES_ONLY,
216
+ "strict-public-upload": STRICT_PUBLIC_UPLOAD,
217
+ "conservative-default": CONSERVATIVE_DEFAULT,
218
+ archives: ARCHIVES,
218
219
  };
219
220
  /**
220
221
  * Look up a policy pack by name.
@@ -223,7 +224,7 @@ const POLICY_PACKS = {
223
224
  function getPolicyPack(name) {
224
225
  const policy = POLICY_PACKS[name];
225
226
  if (!policy)
226
- throw new Error(`Unknown policy pack: '${name}'. Valid names: ${Object.keys(POLICY_PACKS).join(', ')}`);
227
+ throw new Error(`Unknown policy pack: '${name}'. Valid names: ${Object.keys(POLICY_PACKS).join(", ")}`);
227
228
  return policy;
228
229
  }
229
230