pompelmi 0.34.9 → 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.
- package/README.md +26 -14
- package/dist/pompelmi.audit.cjs +13 -15
- package/dist/pompelmi.audit.cjs.map +1 -1
- package/dist/pompelmi.audit.esm.js +13 -15
- package/dist/pompelmi.audit.esm.js.map +1 -1
- package/dist/pompelmi.browser.cjs +585 -534
- package/dist/pompelmi.browser.cjs.map +1 -1
- package/dist/pompelmi.browser.esm.js +585 -534
- package/dist/pompelmi.browser.esm.js.map +1 -1
- package/dist/pompelmi.cjs +2066 -2016
- package/dist/pompelmi.cjs.map +1 -1
- package/dist/pompelmi.esm.js +2066 -2016
- package/dist/pompelmi.esm.js.map +1 -1
- package/dist/pompelmi.hooks.cjs +2 -2
- package/dist/pompelmi.hooks.cjs.map +1 -1
- package/dist/pompelmi.hooks.esm.js +2 -2
- package/dist/pompelmi.hooks.esm.js.map +1 -1
- package/dist/pompelmi.policy-packs.cjs +74 -73
- package/dist/pompelmi.policy-packs.cjs.map +1 -1
- package/dist/pompelmi.policy-packs.esm.js +74 -73
- package/dist/pompelmi.policy-packs.esm.js.map +1 -1
- package/dist/pompelmi.quarantine.cjs +135 -133
- package/dist/pompelmi.quarantine.cjs.map +1 -1
- package/dist/pompelmi.quarantine.esm.js +135 -133
- package/dist/pompelmi.quarantine.esm.js.map +1 -1
- package/dist/pompelmi.react.cjs +585 -534
- package/dist/pompelmi.react.cjs.map +1 -1
- package/dist/pompelmi.react.esm.js +585 -534
- package/dist/pompelmi.react.esm.js.map +1 -1
- package/dist/types/audit.d.ts +12 -12
- package/dist/types/browser-index.d.ts +12 -12
- package/dist/types/config.d.ts +4 -4
- package/dist/types/engines/dynamic-taint.d.ts +1 -1
- package/dist/types/engines/hybrid-orchestrator.d.ts +1 -1
- package/dist/types/engines/hybrid-taint-integration.d.ts +6 -6
- package/dist/types/engines/taint-policies.d.ts +4 -4
- package/dist/types/hipaa-compliance.d.ts +2 -2
- package/dist/types/hooks.d.ts +2 -2
- package/dist/types/index.d.ts +20 -20
- package/dist/types/node/scanDir.d.ts +5 -5
- package/dist/types/policy-packs.d.ts +2 -2
- package/dist/types/presets.d.ts +3 -3
- package/dist/types/quarantine/index.d.ts +3 -3
- package/dist/types/quarantine/storage.d.ts +1 -1
- package/dist/types/quarantine/types.d.ts +3 -3
- package/dist/types/quarantine/workflow.d.ts +4 -4
- package/dist/types/react-index.d.ts +2 -2
- package/dist/types/risk.d.ts +1 -1
- package/dist/types/scan/remote.d.ts +2 -2
- package/dist/types/scan.d.ts +5 -5
- package/dist/types/scanners/common-heuristics.d.ts +1 -1
- package/dist/types/scanners/zip-bomb-guard.d.ts +1 -1
- package/dist/types/src/audit.d.ts +84 -0
- package/dist/types/src/browser-index.d.ts +29 -0
- package/dist/types/src/config.d.ts +143 -0
- package/dist/types/src/engines/dynamic-taint.d.ts +102 -0
- package/dist/types/src/engines/hybrid-orchestrator.d.ts +65 -0
- package/dist/types/src/engines/hybrid-taint-integration.d.ts +129 -0
- package/dist/types/src/engines/taint-policies.d.ts +84 -0
- package/dist/types/src/hipaa-compliance.d.ts +110 -0
- package/dist/types/src/hooks.d.ts +89 -0
- package/dist/types/src/index.d.ts +29 -0
- package/dist/types/src/magic.d.ts +7 -0
- package/dist/types/src/node/scanDir.d.ts +30 -0
- package/dist/types/src/policy-packs.d.ts +98 -0
- package/dist/types/src/policy.d.ts +12 -0
- package/dist/types/src/presets.d.ts +72 -0
- package/dist/types/src/quarantine/index.d.ts +18 -0
- package/dist/types/src/quarantine/storage.d.ts +77 -0
- package/dist/types/src/quarantine/types.d.ts +78 -0
- package/dist/types/src/quarantine/workflow.d.ts +97 -0
- package/dist/types/src/react-index.d.ts +13 -0
- package/dist/types/src/risk.d.ts +18 -0
- package/dist/types/src/scan/remote.d.ts +12 -0
- package/dist/types/src/scan.d.ts +17 -0
- package/dist/types/src/scanners/common-heuristics.d.ts +14 -0
- package/dist/types/src/scanners/zip-bomb-guard.d.ts +9 -0
- package/dist/types/src/scanners/zipTraversalGuard.d.ts +19 -0
- package/dist/types/src/stream.d.ts +10 -0
- package/dist/types/src/types/decompilation.d.ts +96 -0
- package/dist/types/src/types/taint-tracking.d.ts +495 -0
- package/dist/types/src/types.d.ts +48 -0
- package/dist/types/src/useFileScanner.d.ts +15 -0
- package/dist/types/src/utils/advanced-detection.d.ts +21 -0
- package/dist/types/src/utils/batch-scanner.d.ts +62 -0
- package/dist/types/src/utils/cache-manager.d.ts +95 -0
- package/dist/types/src/utils/export.d.ts +51 -0
- package/dist/types/src/utils/performance-metrics.d.ts +68 -0
- package/dist/types/src/utils/threat-intelligence.d.ts +96 -0
- package/dist/types/src/validate.d.ts +7 -0
- package/dist/types/src/verdict.d.ts +2 -0
- package/dist/types/src/yara/browser.d.ts +7 -0
- package/dist/types/src/yara/index.d.ts +17 -0
- package/dist/types/src/yara/node.d.ts +2 -0
- package/dist/types/src/yara/remote.d.ts +10 -0
- package/dist/types/src/yara-bridge.d.ts +3 -0
- package/dist/types/src/zip.d.ts +13 -0
- package/dist/types/types/decompilation.d.ts +4 -4
- package/dist/types/types/taint-tracking.d.ts +19 -19
- package/dist/types/types.d.ts +3 -3
- package/dist/types/useFileScanner.d.ts +1 -1
- package/dist/types/utils/advanced-detection.d.ts +1 -1
- package/dist/types/utils/batch-scanner.d.ts +3 -3
- package/dist/types/utils/cache-manager.d.ts +1 -1
- package/dist/types/utils/export.d.ts +2 -2
- package/dist/types/utils/threat-intelligence.d.ts +4 -4
- package/dist/types/verdict.d.ts +1 -1
- package/dist/types/yara/browser.d.ts +1 -1
- package/dist/types/yara/index.d.ts +1 -1
- package/dist/types/yara/node.d.ts +1 -1
- package/dist/types/yara/remote.d.ts +2 -2
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
<p>
|
|
6
6
|
<a href="https://www.npmjs.com/package/pompelmi"><img alt="npm version" src="https://img.shields.io/npm/v/pompelmi"></a>
|
|
7
7
|
<a href="https://github.com/pompelmi/pompelmi/actions/workflows/ci.yml"><img alt="CI" src="https://img.shields.io/github/actions/workflow/status/pompelmi/pompelmi/ci.yml?label=ci"></a>
|
|
8
|
+
<a href="https://codecov.io/gh/pompelmi/pompelmi"><img alt="Codecov" src="https://codecov.io/gh/pompelmi/pompelmi/branch/main/graph/badge.svg?flag=core"></a>
|
|
8
9
|
<a href="https://github.com/pompelmi/pompelmi/stargazers"><img alt="GitHub stars" src="https://img.shields.io/github/stars/pompelmi/pompelmi"></a>
|
|
9
10
|
<a href="https://www.npmjs.com/package/pompelmi"><img alt="npm downloads" src="https://img.shields.io/npm/dm/pompelmi"></a>
|
|
10
11
|
</p>
|
|
@@ -93,16 +94,31 @@ if (report.verdict !== "clean") {
|
|
|
93
94
|
}
|
|
94
95
|
```
|
|
95
96
|
|
|
96
|
-
##
|
|
97
|
+
## Start Here
|
|
97
98
|
|
|
98
|
-
- [
|
|
99
|
+
- Express: [Docs](https://pompelmi.github.io/pompelmi/how-to/express/) · [Examples](./examples/express-minimal)
|
|
100
|
+
- Next.js: [Docs](https://pompelmi.github.io/pompelmi/how-to/nextjs/) · [Examples](./examples/next-app-router)
|
|
101
|
+
- NestJS: [Docs](https://pompelmi.github.io/pompelmi/how-to/nestjs/) · [Example app](./examples/nestjs-app)
|
|
102
|
+
- Fastify: [Docs](https://pompelmi.github.io/pompelmi/how-to/fastify/) · [Package](./packages/fastify-plugin)
|
|
103
|
+
- Koa: [Docs](https://pompelmi.github.io/pompelmi/how-to/koa/) · [Package](./packages/koa-middleware)
|
|
104
|
+
- CI/CD: [Use case](https://pompelmi.github.io/pompelmi/use-cases/cicd-artifact-scanning/) · [Blog](https://pompelmi.github.io/pompelmi/blog/cicd-scan-build-artifacts/)
|
|
105
|
+
- S3 / object storage: [Tutorial](https://pompelmi.github.io/pompelmi/tutorials/secure-s3-presigned-uploads-with-malware-scanning/) · [Use case](https://pompelmi.github.io/pompelmi/use-cases/s3-presigned-upload-security/)
|
|
106
|
+
|
|
107
|
+
## Go Deeper
|
|
108
|
+
|
|
109
|
+
- [Docs home](https://pompelmi.github.io/pompelmi/)
|
|
110
|
+
- [Use cases](https://pompelmi.github.io/pompelmi/use-cases/)
|
|
111
|
+
- [Comparisons](https://pompelmi.github.io/pompelmi/comparisons/)
|
|
112
|
+
- [Tutorials](https://pompelmi.github.io/pompelmi/tutorials/)
|
|
113
|
+
- [Featured in](https://pompelmi.github.io/pompelmi/featured-in/)
|
|
114
|
+
- [Translations](https://pompelmi.github.io/pompelmi/translations/)
|
|
99
115
|
- [Examples index](./examples/README.md)
|
|
100
116
|
- [Demo example](./examples/demo)
|
|
101
117
|
- [Contributing](./CONTRIBUTING.md)
|
|
102
118
|
- [Security](./SECURITY.md)
|
|
103
119
|
- [Roadmap](./ROADMAP.md)
|
|
104
120
|
|
|
105
|
-
## What
|
|
121
|
+
## What It Checks
|
|
106
122
|
|
|
107
123
|
Upload endpoints are part of your attack surface. A renamed executable, a risky PDF, or a hostile archive can look harmless until it is stored, unpacked, served, or parsed by another system.
|
|
108
124
|
|
|
@@ -115,12 +131,6 @@ Pompelmi adds checks at the upload boundary for:
|
|
|
115
131
|
|
|
116
132
|
The goal is simple: inspect first, store later.
|
|
117
133
|
|
|
118
|
-
## Why This Shape
|
|
119
|
-
|
|
120
|
-
- Plain Markdown, readable in GitHub and in a terminal
|
|
121
|
-
- Fast path first: install, example, then deeper links
|
|
122
|
-
- Minimal top-level detail, with docs and examples for everything else
|
|
123
|
-
|
|
124
134
|
## Ecosystem
|
|
125
135
|
|
|
126
136
|
- `pompelmi`
|
|
@@ -138,7 +148,7 @@ The goal is simple: inspect first, store later.
|
|
|
138
148
|
- `packages/` framework adapters and supporting packages
|
|
139
149
|
- `examples/` runnable examples
|
|
140
150
|
- `tests/` test coverage
|
|
141
|
-
- `website/`
|
|
151
|
+
- `website/` public docs, blog, and discovery site
|
|
142
152
|
|
|
143
153
|
## Development
|
|
144
154
|
|
|
@@ -150,16 +160,18 @@ pnpm build
|
|
|
150
160
|
|
|
151
161
|
<!-- MENTIONS:START -->
|
|
152
162
|
|
|
153
|
-
##
|
|
163
|
+
## Featured In
|
|
164
|
+
|
|
165
|
+
Full page: [pompelmi.github.io/pompelmi/featured-in](https://pompelmi.github.io/pompelmi/featured-in/)
|
|
154
166
|
|
|
155
167
|
*Last updated: March 20, 2026*
|
|
156
168
|
|
|
157
|
-
###
|
|
169
|
+
### Awesome Lists & Curated Collections
|
|
158
170
|
|
|
159
171
|
- [Awesome JavaScript](https://github.com/sorrycc/awesome-javascript) — sorrycc
|
|
160
172
|
- [Awesome TypeScript](https://github.com/dzharii/awesome-typescript) — dzharii
|
|
161
173
|
|
|
162
|
-
###
|
|
174
|
+
### Newsletters & Roundups
|
|
163
175
|
|
|
164
176
|
- [The Overflow Issue 319: Dogfooding your SDLC](https://stackoverflow.blog/newsletter/issue-319-dogfooding-your-sdlc/) — Stack Overflow (2026-03-04)
|
|
165
177
|
- [Hottest cybersecurity open-source tools of the month: February 2026](https://www.helpnetsecurity.com/2026/02/26/hottest-cybersecurity-open-source-tools-of-the-month-february-2026/) — Help Net Security (2026-02-26)
|
|
@@ -167,7 +179,7 @@ pnpm build
|
|
|
167
179
|
- [Node Weekly Issue 594](https://nodeweekly.com/issues/594) — Node Weekly (2025-09-30)
|
|
168
180
|
- [Det. Eng. Weekly Issue #124 - The DEFCON hangover is real](https://www.detectionengineering.net/p/det-eng-weekly-issue-124-the-defcon) — Detection Engineering (2025-08-13)
|
|
169
181
|
|
|
170
|
-
###
|
|
182
|
+
### Other Mentions
|
|
171
183
|
|
|
172
184
|
- [Defense against uploads: Q&A with OSS file scanner, pompelmi](https://stackoverflow.blog/2026/02/23/defense-against-uploads-oss-file-scanner-pompelmi/) — Stack Overflow (2026-02-23)
|
|
173
185
|
- [Pompelmi: Open-source secure file upload scanning for Node.js](https://www.helpnetsecurity.com/2026/02/02/pompelmi-open-source-secure-file-upload-scanning-node-js/) — Help Net Security (2026-02-02)
|
package/dist/pompelmi.audit.cjs
CHANGED
|
@@ -45,7 +45,7 @@ var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
|
|
|
45
45
|
class AuditTrail {
|
|
46
46
|
constructor(options = {}) {
|
|
47
47
|
this.options = {
|
|
48
|
-
output: options.output ?? { dest:
|
|
48
|
+
output: options.output ?? { dest: "console" },
|
|
49
49
|
pretty: options.pretty ?? false,
|
|
50
50
|
};
|
|
51
51
|
}
|
|
@@ -53,7 +53,7 @@ class AuditTrail {
|
|
|
53
53
|
logScanComplete(report, extra) {
|
|
54
54
|
const record = {
|
|
55
55
|
timestamp: new Date().toISOString(),
|
|
56
|
-
event: report.verdict !==
|
|
56
|
+
event: report.verdict !== "clean" ? "threat.detected" : "scan.complete",
|
|
57
57
|
verdict: report.verdict,
|
|
58
58
|
matchCount: report.matches?.length ?? 0,
|
|
59
59
|
durationMs: report.durationMs,
|
|
@@ -67,8 +67,8 @@ class AuditTrail {
|
|
|
67
67
|
logScanError(error, extra) {
|
|
68
68
|
const record = {
|
|
69
69
|
timestamp: new Date().toISOString(),
|
|
70
|
-
event:
|
|
71
|
-
verdict:
|
|
70
|
+
event: "scan.error",
|
|
71
|
+
verdict: "clean", // unknown at this point
|
|
72
72
|
matchCount: 0,
|
|
73
73
|
error: error instanceof Error ? error.message : String(error),
|
|
74
74
|
...extra,
|
|
@@ -79,7 +79,7 @@ class AuditTrail {
|
|
|
79
79
|
logQuarantine(entry, correlationId) {
|
|
80
80
|
const record = {
|
|
81
81
|
timestamp: new Date().toISOString(),
|
|
82
|
-
event:
|
|
82
|
+
event: "quarantine.created",
|
|
83
83
|
quarantineId: entry.id,
|
|
84
84
|
filename: entry.file.originalName,
|
|
85
85
|
sha256: entry.file.sha256,
|
|
@@ -92,11 +92,11 @@ class AuditTrail {
|
|
|
92
92
|
logQuarantineResolved(entry, correlationId) {
|
|
93
93
|
const record = {
|
|
94
94
|
timestamp: new Date().toISOString(),
|
|
95
|
-
event: entry.status ===
|
|
95
|
+
event: entry.status === "deleted" ? "quarantine.deleted" : "quarantine.resolved",
|
|
96
96
|
quarantineId: entry.id,
|
|
97
97
|
filename: entry.file.originalName,
|
|
98
98
|
sha256: entry.file.sha256,
|
|
99
|
-
decision: entry.status ===
|
|
99
|
+
decision: entry.status === "promoted" ? "promote" : "delete",
|
|
100
100
|
reviewedBy: entry.reviewedBy,
|
|
101
101
|
reviewNote: entry.reviewNote,
|
|
102
102
|
correlationId,
|
|
@@ -104,19 +104,17 @@ class AuditTrail {
|
|
|
104
104
|
void this.write(record);
|
|
105
105
|
}
|
|
106
106
|
async write(record) {
|
|
107
|
-
const line = this.options.pretty
|
|
108
|
-
? JSON.stringify(record, null, 2)
|
|
109
|
-
: JSON.stringify(record);
|
|
107
|
+
const line = this.options.pretty ? JSON.stringify(record, null, 2) : JSON.stringify(record);
|
|
110
108
|
const { output } = this.options;
|
|
111
109
|
try {
|
|
112
|
-
if (output.dest ===
|
|
113
|
-
process.stdout.write(line +
|
|
110
|
+
if (output.dest === "console") {
|
|
111
|
+
process.stdout.write(line + "\n");
|
|
114
112
|
}
|
|
115
|
-
else if (output.dest ===
|
|
113
|
+
else if (output.dest === "file") {
|
|
116
114
|
// Append a newline-delimited JSON (NDJSON) record.
|
|
117
|
-
await fs__namespace.promises.appendFile(output.path, line +
|
|
115
|
+
await fs__namespace.promises.appendFile(output.path, line + "\n", "utf8");
|
|
118
116
|
}
|
|
119
|
-
else if (output.dest ===
|
|
117
|
+
else if (output.dest === "custom") {
|
|
120
118
|
await output.write(record);
|
|
121
119
|
}
|
|
122
120
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pompelmi.audit.cjs","sources":["
|
|
1
|
+
{"version":3,"file":"pompelmi.audit.cjs","sources":["../../src/audit.ts"],"sourcesContent":["/**\n * Audit trail for Pompelmi scan and quarantine events.\n *\n * Produces structured, append-only audit records suitable for:\n * - compliance logging (HIPAA, SOC 2, ISO 27001)\n * - SIEM ingestion\n * - operational dashboards\n * - incident response\n *\n * Usage:\n * ```ts\n * import { AuditTrail } from 'pompelmi/audit';\n *\n * const audit = new AuditTrail({ dest: 'file', path: './audit.jsonl' });\n * audit.logScanComplete({ filename: 'upload.zip', verdict: 'suspicious', ... });\n * audit.logQuarantine({ entryId: '...', sha256: '...', ... });\n * ```\n *\n * @module audit\n */\n\nimport * as fs from \"fs\";\nimport type { QuarantineEntry } from \"./quarantine/types\";\nimport type { ScanReport } from \"./types\";\n\n// ── Record types ──────────────────────────────────────────────────────────────\n\nexport type AuditEventType =\n | \"scan.complete\"\n | \"scan.error\"\n | \"threat.detected\"\n | \"quarantine.created\"\n | \"quarantine.resolved\"\n | \"quarantine.deleted\";\n\ninterface BaseAuditRecord {\n /** ISO-8601 timestamp. */\n timestamp: string;\n /** Event type for structured log routing. */\n event: AuditEventType;\n /** Application-assigned session or request id for correlation. */\n correlationId?: string;\n /** Uploader identity. */\n uploadedBy?: string;\n}\n\nexport interface ScanAuditRecord extends BaseAuditRecord {\n event: \"scan.complete\" | \"scan.error\" | \"threat.detected\";\n filename?: string;\n mimeType?: string;\n sizeBytes?: number;\n sha256?: string;\n verdict: ScanReport[\"verdict\"];\n matchCount: number;\n durationMs?: number;\n engine?: string;\n error?: string;\n}\n\nexport interface QuarantineAuditRecord extends BaseAuditRecord {\n event: \"quarantine.created\" | \"quarantine.resolved\" | \"quarantine.deleted\";\n quarantineId: string;\n filename?: string;\n sha256: string;\n decision?: \"promote\" | \"delete\";\n reviewedBy?: string;\n reviewNote?: string;\n}\n\nexport type AuditRecord = ScanAuditRecord | QuarantineAuditRecord;\n\n// ── Destination ───────────────────────────────────────────────────────────────\n\nexport type AuditDest =\n | { dest: \"console\" }\n | { dest: \"file\"; path: string }\n | { dest: \"custom\"; write: (record: AuditRecord) => void | Promise<void> };\n\nexport interface AuditTrailOptions {\n /** Where to write audit records. Default: 'console'. */\n output?: AuditDest;\n /** If true, pretty-print JSON. Useful for debugging. Default: false. */\n pretty?: boolean;\n}\n\n// ── AuditTrail ────────────────────────────────────────────────────────────────\n\nexport class AuditTrail {\n private readonly options: Required<AuditTrailOptions>;\n\n constructor(options: AuditTrailOptions = {}) {\n this.options = {\n output: options.output ?? { dest: \"console\" },\n pretty: options.pretty ?? false,\n };\n }\n\n /** Log a completed scan. */\n logScanComplete(\n report: ScanReport,\n extra?: Pick<\n ScanAuditRecord,\n \"filename\" | \"sizeBytes\" | \"sha256\" | \"correlationId\" | \"uploadedBy\"\n >,\n ): void {\n const record: ScanAuditRecord = {\n timestamp: new Date().toISOString(),\n event: report.verdict !== \"clean\" ? \"threat.detected\" : \"scan.complete\",\n verdict: report.verdict,\n matchCount: report.matches?.length ?? 0,\n durationMs: report.durationMs,\n engine: report.engine,\n mimeType: report.file?.mimeType,\n ...extra,\n };\n void this.write(record);\n }\n\n /** Log a scan error. */\n logScanError(\n error: unknown,\n extra?: Pick<ScanAuditRecord, \"filename\" | \"correlationId\" | \"uploadedBy\">,\n ): void {\n const record: ScanAuditRecord = {\n timestamp: new Date().toISOString(),\n event: \"scan.error\",\n verdict: \"clean\", // unknown at this point\n matchCount: 0,\n error: error instanceof Error ? error.message : String(error),\n ...extra,\n };\n void this.write(record);\n }\n\n /** Log a new quarantine entry. */\n logQuarantine(entry: QuarantineEntry, correlationId?: string): void {\n const record: QuarantineAuditRecord = {\n timestamp: new Date().toISOString(),\n event: \"quarantine.created\",\n quarantineId: entry.id,\n filename: entry.file.originalName,\n sha256: entry.file.sha256,\n uploadedBy: entry.file.uploadedBy,\n correlationId,\n };\n void this.write(record);\n }\n\n /** Log a quarantine resolution (promote or delete). */\n logQuarantineResolved(entry: QuarantineEntry, correlationId?: string): void {\n const record: QuarantineAuditRecord = {\n timestamp: new Date().toISOString(),\n event: entry.status === \"deleted\" ? \"quarantine.deleted\" : \"quarantine.resolved\",\n quarantineId: entry.id,\n filename: entry.file.originalName,\n sha256: entry.file.sha256,\n decision: entry.status === \"promoted\" ? \"promote\" : \"delete\",\n reviewedBy: entry.reviewedBy,\n reviewNote: entry.reviewNote,\n correlationId,\n };\n void this.write(record);\n }\n\n private async write(record: AuditRecord): Promise<void> {\n const line = this.options.pretty ? JSON.stringify(record, null, 2) : JSON.stringify(record);\n\n const { output } = this.options;\n\n try {\n if (output.dest === \"console\") {\n process.stdout.write(line + \"\\n\");\n } else if (output.dest === \"file\") {\n // Append a newline-delimited JSON (NDJSON) record.\n await fs.promises.appendFile(output.path, line + \"\\n\", \"utf8\");\n } else if (output.dest === \"custom\") {\n await output.write(record);\n }\n } catch {\n // Audit failures must never interrupt the upload pipeline.\n }\n }\n}\n"],"names":["fs"],"mappings":"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"}
|
|
@@ -24,7 +24,7 @@ import * as fs from 'fs';
|
|
|
24
24
|
class AuditTrail {
|
|
25
25
|
constructor(options = {}) {
|
|
26
26
|
this.options = {
|
|
27
|
-
output: options.output ?? { dest:
|
|
27
|
+
output: options.output ?? { dest: "console" },
|
|
28
28
|
pretty: options.pretty ?? false,
|
|
29
29
|
};
|
|
30
30
|
}
|
|
@@ -32,7 +32,7 @@ class AuditTrail {
|
|
|
32
32
|
logScanComplete(report, extra) {
|
|
33
33
|
const record = {
|
|
34
34
|
timestamp: new Date().toISOString(),
|
|
35
|
-
event: report.verdict !==
|
|
35
|
+
event: report.verdict !== "clean" ? "threat.detected" : "scan.complete",
|
|
36
36
|
verdict: report.verdict,
|
|
37
37
|
matchCount: report.matches?.length ?? 0,
|
|
38
38
|
durationMs: report.durationMs,
|
|
@@ -46,8 +46,8 @@ class AuditTrail {
|
|
|
46
46
|
logScanError(error, extra) {
|
|
47
47
|
const record = {
|
|
48
48
|
timestamp: new Date().toISOString(),
|
|
49
|
-
event:
|
|
50
|
-
verdict:
|
|
49
|
+
event: "scan.error",
|
|
50
|
+
verdict: "clean", // unknown at this point
|
|
51
51
|
matchCount: 0,
|
|
52
52
|
error: error instanceof Error ? error.message : String(error),
|
|
53
53
|
...extra,
|
|
@@ -58,7 +58,7 @@ class AuditTrail {
|
|
|
58
58
|
logQuarantine(entry, correlationId) {
|
|
59
59
|
const record = {
|
|
60
60
|
timestamp: new Date().toISOString(),
|
|
61
|
-
event:
|
|
61
|
+
event: "quarantine.created",
|
|
62
62
|
quarantineId: entry.id,
|
|
63
63
|
filename: entry.file.originalName,
|
|
64
64
|
sha256: entry.file.sha256,
|
|
@@ -71,11 +71,11 @@ class AuditTrail {
|
|
|
71
71
|
logQuarantineResolved(entry, correlationId) {
|
|
72
72
|
const record = {
|
|
73
73
|
timestamp: new Date().toISOString(),
|
|
74
|
-
event: entry.status ===
|
|
74
|
+
event: entry.status === "deleted" ? "quarantine.deleted" : "quarantine.resolved",
|
|
75
75
|
quarantineId: entry.id,
|
|
76
76
|
filename: entry.file.originalName,
|
|
77
77
|
sha256: entry.file.sha256,
|
|
78
|
-
decision: entry.status ===
|
|
78
|
+
decision: entry.status === "promoted" ? "promote" : "delete",
|
|
79
79
|
reviewedBy: entry.reviewedBy,
|
|
80
80
|
reviewNote: entry.reviewNote,
|
|
81
81
|
correlationId,
|
|
@@ -83,19 +83,17 @@ class AuditTrail {
|
|
|
83
83
|
void this.write(record);
|
|
84
84
|
}
|
|
85
85
|
async write(record) {
|
|
86
|
-
const line = this.options.pretty
|
|
87
|
-
? JSON.stringify(record, null, 2)
|
|
88
|
-
: JSON.stringify(record);
|
|
86
|
+
const line = this.options.pretty ? JSON.stringify(record, null, 2) : JSON.stringify(record);
|
|
89
87
|
const { output } = this.options;
|
|
90
88
|
try {
|
|
91
|
-
if (output.dest ===
|
|
92
|
-
process.stdout.write(line +
|
|
89
|
+
if (output.dest === "console") {
|
|
90
|
+
process.stdout.write(line + "\n");
|
|
93
91
|
}
|
|
94
|
-
else if (output.dest ===
|
|
92
|
+
else if (output.dest === "file") {
|
|
95
93
|
// Append a newline-delimited JSON (NDJSON) record.
|
|
96
|
-
await fs.promises.appendFile(output.path, line +
|
|
94
|
+
await fs.promises.appendFile(output.path, line + "\n", "utf8");
|
|
97
95
|
}
|
|
98
|
-
else if (output.dest ===
|
|
96
|
+
else if (output.dest === "custom") {
|
|
99
97
|
await output.write(record);
|
|
100
98
|
}
|
|
101
99
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pompelmi.audit.esm.js","sources":["
|
|
1
|
+
{"version":3,"file":"pompelmi.audit.esm.js","sources":["../../src/audit.ts"],"sourcesContent":["/**\n * Audit trail for Pompelmi scan and quarantine events.\n *\n * Produces structured, append-only audit records suitable for:\n * - compliance logging (HIPAA, SOC 2, ISO 27001)\n * - SIEM ingestion\n * - operational dashboards\n * - incident response\n *\n * Usage:\n * ```ts\n * import { AuditTrail } from 'pompelmi/audit';\n *\n * const audit = new AuditTrail({ dest: 'file', path: './audit.jsonl' });\n * audit.logScanComplete({ filename: 'upload.zip', verdict: 'suspicious', ... });\n * audit.logQuarantine({ entryId: '...', sha256: '...', ... });\n * ```\n *\n * @module audit\n */\n\nimport * as fs from \"fs\";\nimport type { QuarantineEntry } from \"./quarantine/types\";\nimport type { ScanReport } from \"./types\";\n\n// ── Record types ──────────────────────────────────────────────────────────────\n\nexport type AuditEventType =\n | \"scan.complete\"\n | \"scan.error\"\n | \"threat.detected\"\n | \"quarantine.created\"\n | \"quarantine.resolved\"\n | \"quarantine.deleted\";\n\ninterface BaseAuditRecord {\n /** ISO-8601 timestamp. */\n timestamp: string;\n /** Event type for structured log routing. */\n event: AuditEventType;\n /** Application-assigned session or request id for correlation. */\n correlationId?: string;\n /** Uploader identity. */\n uploadedBy?: string;\n}\n\nexport interface ScanAuditRecord extends BaseAuditRecord {\n event: \"scan.complete\" | \"scan.error\" | \"threat.detected\";\n filename?: string;\n mimeType?: string;\n sizeBytes?: number;\n sha256?: string;\n verdict: ScanReport[\"verdict\"];\n matchCount: number;\n durationMs?: number;\n engine?: string;\n error?: string;\n}\n\nexport interface QuarantineAuditRecord extends BaseAuditRecord {\n event: \"quarantine.created\" | \"quarantine.resolved\" | \"quarantine.deleted\";\n quarantineId: string;\n filename?: string;\n sha256: string;\n decision?: \"promote\" | \"delete\";\n reviewedBy?: string;\n reviewNote?: string;\n}\n\nexport type AuditRecord = ScanAuditRecord | QuarantineAuditRecord;\n\n// ── Destination ───────────────────────────────────────────────────────────────\n\nexport type AuditDest =\n | { dest: \"console\" }\n | { dest: \"file\"; path: string }\n | { dest: \"custom\"; write: (record: AuditRecord) => void | Promise<void> };\n\nexport interface AuditTrailOptions {\n /** Where to write audit records. Default: 'console'. */\n output?: AuditDest;\n /** If true, pretty-print JSON. Useful for debugging. Default: false. */\n pretty?: boolean;\n}\n\n// ── AuditTrail ────────────────────────────────────────────────────────────────\n\nexport class AuditTrail {\n private readonly options: Required<AuditTrailOptions>;\n\n constructor(options: AuditTrailOptions = {}) {\n this.options = {\n output: options.output ?? { dest: \"console\" },\n pretty: options.pretty ?? false,\n };\n }\n\n /** Log a completed scan. */\n logScanComplete(\n report: ScanReport,\n extra?: Pick<\n ScanAuditRecord,\n \"filename\" | \"sizeBytes\" | \"sha256\" | \"correlationId\" | \"uploadedBy\"\n >,\n ): void {\n const record: ScanAuditRecord = {\n timestamp: new Date().toISOString(),\n event: report.verdict !== \"clean\" ? \"threat.detected\" : \"scan.complete\",\n verdict: report.verdict,\n matchCount: report.matches?.length ?? 0,\n durationMs: report.durationMs,\n engine: report.engine,\n mimeType: report.file?.mimeType,\n ...extra,\n };\n void this.write(record);\n }\n\n /** Log a scan error. */\n logScanError(\n error: unknown,\n extra?: Pick<ScanAuditRecord, \"filename\" | \"correlationId\" | \"uploadedBy\">,\n ): void {\n const record: ScanAuditRecord = {\n timestamp: new Date().toISOString(),\n event: \"scan.error\",\n verdict: \"clean\", // unknown at this point\n matchCount: 0,\n error: error instanceof Error ? error.message : String(error),\n ...extra,\n };\n void this.write(record);\n }\n\n /** Log a new quarantine entry. */\n logQuarantine(entry: QuarantineEntry, correlationId?: string): void {\n const record: QuarantineAuditRecord = {\n timestamp: new Date().toISOString(),\n event: \"quarantine.created\",\n quarantineId: entry.id,\n filename: entry.file.originalName,\n sha256: entry.file.sha256,\n uploadedBy: entry.file.uploadedBy,\n correlationId,\n };\n void this.write(record);\n }\n\n /** Log a quarantine resolution (promote or delete). */\n logQuarantineResolved(entry: QuarantineEntry, correlationId?: string): void {\n const record: QuarantineAuditRecord = {\n timestamp: new Date().toISOString(),\n event: entry.status === \"deleted\" ? \"quarantine.deleted\" : \"quarantine.resolved\",\n quarantineId: entry.id,\n filename: entry.file.originalName,\n sha256: entry.file.sha256,\n decision: entry.status === \"promoted\" ? \"promote\" : \"delete\",\n reviewedBy: entry.reviewedBy,\n reviewNote: entry.reviewNote,\n correlationId,\n };\n void this.write(record);\n }\n\n private async write(record: AuditRecord): Promise<void> {\n const line = this.options.pretty ? JSON.stringify(record, null, 2) : JSON.stringify(record);\n\n const { output } = this.options;\n\n try {\n if (output.dest === \"console\") {\n process.stdout.write(line + \"\\n\");\n } else if (output.dest === \"file\") {\n // Append a newline-delimited JSON (NDJSON) record.\n await fs.promises.appendFile(output.path, line + \"\\n\", \"utf8\");\n } else if (output.dest === \"custom\") {\n await output.write(record);\n }\n } catch {\n // Audit failures must never interrupt the upload pipeline.\n }\n }\n}\n"],"names":[],"mappings":"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"}
|