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.
- package/README.md +26 -15
- 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
|
@@ -94,16 +94,31 @@ if (report.verdict !== "clean") {
|
|
|
94
94
|
}
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
##
|
|
98
|
-
|
|
99
|
-
- [
|
|
97
|
+
## Start Here
|
|
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/)
|
|
100
115
|
- [Examples index](./examples/README.md)
|
|
101
116
|
- [Demo example](./examples/demo)
|
|
102
117
|
- [Contributing](./CONTRIBUTING.md)
|
|
103
118
|
- [Security](./SECURITY.md)
|
|
104
119
|
- [Roadmap](./ROADMAP.md)
|
|
105
120
|
|
|
106
|
-
## What
|
|
121
|
+
## What It Checks
|
|
107
122
|
|
|
108
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.
|
|
109
124
|
|
|
@@ -116,12 +131,6 @@ Pompelmi adds checks at the upload boundary for:
|
|
|
116
131
|
|
|
117
132
|
The goal is simple: inspect first, store later.
|
|
118
133
|
|
|
119
|
-
## Why This Shape
|
|
120
|
-
|
|
121
|
-
- Plain Markdown, readable in GitHub and in a terminal
|
|
122
|
-
- Fast path first: install, example, then deeper links
|
|
123
|
-
- Minimal top-level detail, with docs and examples for everything else
|
|
124
|
-
|
|
125
134
|
## Ecosystem
|
|
126
135
|
|
|
127
136
|
- `pompelmi`
|
|
@@ -139,7 +148,7 @@ The goal is simple: inspect first, store later.
|
|
|
139
148
|
- `packages/` framework adapters and supporting packages
|
|
140
149
|
- `examples/` runnable examples
|
|
141
150
|
- `tests/` test coverage
|
|
142
|
-
- `website/`
|
|
151
|
+
- `website/` public docs, blog, and discovery site
|
|
143
152
|
|
|
144
153
|
## Development
|
|
145
154
|
|
|
@@ -151,16 +160,18 @@ pnpm build
|
|
|
151
160
|
|
|
152
161
|
<!-- MENTIONS:START -->
|
|
153
162
|
|
|
154
|
-
##
|
|
163
|
+
## Featured In
|
|
164
|
+
|
|
165
|
+
Full page: [pompelmi.github.io/pompelmi/featured-in](https://pompelmi.github.io/pompelmi/featured-in/)
|
|
155
166
|
|
|
156
167
|
*Last updated: March 20, 2026*
|
|
157
168
|
|
|
158
|
-
###
|
|
169
|
+
### Awesome Lists & Curated Collections
|
|
159
170
|
|
|
160
171
|
- [Awesome JavaScript](https://github.com/sorrycc/awesome-javascript) — sorrycc
|
|
161
172
|
- [Awesome TypeScript](https://github.com/dzharii/awesome-typescript) — dzharii
|
|
162
173
|
|
|
163
|
-
###
|
|
174
|
+
### Newsletters & Roundups
|
|
164
175
|
|
|
165
176
|
- [The Overflow Issue 319: Dogfooding your SDLC](https://stackoverflow.blog/newsletter/issue-319-dogfooding-your-sdlc/) — Stack Overflow (2026-03-04)
|
|
166
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)
|
|
@@ -168,7 +179,7 @@ pnpm build
|
|
|
168
179
|
- [Node Weekly Issue 594](https://nodeweekly.com/issues/594) — Node Weekly (2025-09-30)
|
|
169
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)
|
|
170
181
|
|
|
171
|
-
###
|
|
182
|
+
### Other Mentions
|
|
172
183
|
|
|
173
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)
|
|
174
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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"}
|