pompelmi 0.20.0 → 0.20.1

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 CHANGED
@@ -12,6 +12,10 @@
12
12
  <a href="https://www.detectionengineering.net/p/det-eng-weekly-issue-124-the-defcon"><img alt="Featured in Detection Engineering Weekly #124" src="https://img.shields.io/badge/featured-Detection%20Engineering%20Weekly-0A84FF?logo=substack"></a>
13
13
  <a href="https://nodeweekly.com/issues/594"><img alt="Featured in Node Weekly #594" src="https://img.shields.io/badge/featured-Node%20Weekly%20%23594-FF6600?logo=node.js"></a>
14
14
  <a href="https://bytes.dev/archives/429"><img alt="Featured in Bytes #429" src="https://img.shields.io/badge/featured-Bytes%20%23429-111111"></a>
15
+ <a href="https://dev.to/sonotommy/secure-nodejs-file-uploads-in-minutes-with-pompelmi-3jfe"><img alt="Featured on DEV.to" src="https://img.shields.io/badge/featured-DEV.to-0A0A0A?logo=devdotto"></a>
16
+ <br/>
17
+ <a href="https://github.com/sorrycc/awesome-javascript"><img alt="Mentioned in Awesome JavaScript" src="https://awesome.re/mentioned-badge.svg"></a>
18
+ <a href="https://github.com/dzharii/awesome-typescript"><img alt="Mentioned in Awesome TypeScript" src="https://awesome.re/mentioned-badge-flat.svg"></a>
15
19
  <br/>
16
20
 
17
21
  </p>
@@ -25,13 +29,17 @@
25
29
  <strong>Fast file‑upload malware scanning for Node.js</strong> — optional <strong>YARA</strong> integration, ZIP deep‑inspection, and drop‑in adapters for <em>Express</em>, <em>Koa</em>, and <em>Next.js</em>. Private by design. Typed. Tiny.
26
30
  </p>
27
31
 
32
+ **Keywords:** file upload security · malware detection · YARA · Node.js middleware · Express · Koa · Next.js · ZIP bomb protection
33
+
28
34
 
29
35
 
30
36
  <p align="center">
31
37
  <a href="https://www.npmjs.com/package/pompelmi"><img alt="npm version" src="https://img.shields.io/npm/v/pompelmi?label=version&color=0a7ea4&logo=npm"></a>
32
38
  <a href="https://www.npmjs.com/package/pompelmi"><img alt="npm downloads" src="https://img.shields.io/npm/dm/pompelmi?label=downloads&color=6E9F18&logo=npm"></a>
33
39
  <a href="https://www.npmjs.com/package/pompelmi"><img alt="npm weekly downloads" src="https://img.shields.io/npm/dw/pompelmi?label=weekly&color=blue&logo=npm"></a>
40
+ <a href="https://www.npmjs.com/package/pompelmi"><img alt="npm total downloads" src="https://img.shields.io/npm/dt/pompelmi?label=total%20downloads&color=success&logo=npm"></a>
34
41
  <img alt="npm bundle size" src="https://img.shields.io/bundlephobia/minzip/pompelmi?label=size&color=success">
42
+ <a href="https://snyk.io/test/github/pompelmi/pompelmi"><img alt="Known Vulnerabilities" src="https://snyk.io/test/github/pompelmi/pompelmi/badge.svg"></a>
35
43
  </p>
36
44
 
37
45
  <p align="center">
@@ -51,9 +59,11 @@
51
59
  <p align="center">
52
60
  <a href="https://github.com/pompelmi/pompelmi/stargazers"><img alt="GitHub stars" src="https://img.shields.io/github/stars/pompelmi/pompelmi?style=social"></a>
53
61
  <a href="https://github.com/pompelmi/pompelmi/network/members"><img alt="GitHub forks" src="https://img.shields.io/github/forks/pompelmi/pompelmi?style=social"></a>
62
+ <a href="https://github.com/pompelmi/pompelmi/watchers"><img alt="GitHub watchers" src="https://img.shields.io/github/watchers/pompelmi/pompelmi?style=social"></a>
54
63
  <a href="https://github.com/pompelmi/pompelmi/issues"><img alt="open issues" src="https://img.shields.io/github/issues/pompelmi/pompelmi?color=orange"></a>
55
64
  <img alt="PRs welcome" src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg">
56
65
  <a href="https://github.com/pompelmi/pompelmi/commits/main"><img alt="last commit" src="https://img.shields.io/github/last-commit/pompelmi/pompelmi?color=blue"></a>
66
+ <a href="https://github.com/pompelmi/pompelmi/graphs/contributors"><img alt="contributors" src="https://img.shields.io/github/contributors/pompelmi/pompelmi?color=purple"></a>
57
67
  </p>
58
68
 
59
69
  <p align="center">
@@ -80,41 +90,33 @@
80
90
 
81
91
  </div>
82
92
 
83
- <table>
84
- <tr>
85
- <td width="33%" align="center">
86
- <h4>🔒 Privacy First</h4>
87
- All scanning happens in-process. No cloud calls, no data leaks. Your files never leave your infrastructure.
88
- </td>
89
- <td width="33%" align="center">
90
- <h4>⚡ Lightning Fast</h4>
91
- In-process scanning with zero network latency. Configurable concurrency for high-throughput scenarios.
92
- </td>
93
- <td width="33%" align="center">
94
- <h4>🎨 Developer Friendly</h4>
95
- TypeScript-first, zero-config defaults, drop-in middleware. Get started in under 5 minutes.
96
- </td>
97
- </tr>
98
- </table>
93
+ | 🔒 Privacy First | ⚡ Lightning Fast | 🎨 Developer Friendly |
94
+ | --- | --- | --- |
95
+ | All scanning happens in-process. No cloud calls, no data leaks. Your files never leave your infrastructure. | In-process scanning with zero network latency. Configurable concurrency for high-throughput scenarios. | TypeScript-first, zero-config defaults, drop-in middleware. Get started in under 5 minutes. |
99
96
 
100
97
  ---
101
98
 
102
- <details>
103
- <summary><strong>Table of contents</strong></summary>
104
-
105
- - [Install](#installation)
106
- - [Quick‑start](#quick-start)
107
- - [Minimal Node usage](#minimal-node-usage)
108
- - [GitHub Action](#github-action)
109
- - [Adapters](#adapters)
110
- - [Diagrams](#diagrams)
111
- - [Config](#configuration)
112
- - [Production checklist](#production-checklist)
113
- - [YARA](#yara-getting-started)
114
- - [Quick test](#quick-test-no-eicar)
115
- - [Security](#security-notes)
99
+ ## Table of Contents
100
+
101
+ - [Overview](#overview)
102
+ - [Highlights](#highlights)
103
+ - [Why pompelmi](#why-pompelmi)
104
+ - [How it compares](#how-it-compares)
105
+ - [What Developers Say](#what-developers-say)
106
+ - [What Makes pompelmi Special](#what-makes-pompelmi-special)
107
+ - [Use Cases](#use-cases)
108
+ - [Installation](#installation)
109
+ - [Quick Start](#quick-start)
110
+ - [Minimal Node usage](#minimal-node-usage)
111
+ - [Express](#express)
112
+ - [Koa](#koa)
113
+ - [Next.js (App Router)](#nextjs-app-router)
114
+ - [Configuration](#configuration)
115
+ - [Security Notes](#security-notes)
116
+ - [Testing & Development](#testing--development)
116
117
  - [FAQ](#faq)
117
- </details>
118
+ - [Contributing](#contributing)
119
+ - [License](#license)
118
120
 
119
121
  ---
120
122
 
@@ -155,7 +157,7 @@ TypeScript-first, zero-config defaults, drop-in middleware. Get started in under
155
157
  ## 🧠 Why pompelmi?
156
158
 
157
159
  - **On‑device, private scanning** – no outbound calls, no data sharing.
158
- - **Blocks early** – runs *before* you write to disk or persist anything.
160
+ - **Blocks early** – runs _before_ you write to disk or persist anything.
159
161
  - **Fits your stack** – drop‑in adapters for Express, Koa, Next.js (Fastify plugin in alpha).
160
162
  - **Defense‑in‑depth** – ZIP traversal limits, ratio caps, server‑side MIME sniffing, size caps.
161
163
  - **Pluggable detection** – bring your own engine (e.g., YARA) via a tiny `{ scan(bytes) }` contract.
@@ -193,7 +195,7 @@ TypeScript-first, zero-config defaults, drop-in middleware. Get started in under
193
195
  > "The YARA integration is seamless. We went from prototype to production in less than a week."
194
196
  > — DevSecOps Engineer
195
197
 
196
- *Want to share your experience? [Open a discussion](https://github.com/pompelmi/pompelmi/discussions)!*
198
+ _Want to share your experience? [Open a discussion](https://github.com/pompelmi/pompelmi/discussions)!_
197
199
 
198
200
  ---
199
201
 
package/dist/pompelmi.cjs CHANGED
@@ -1,5 +1,30 @@
1
1
  'use strict';
2
2
 
3
+ var crypto = require('crypto');
4
+ var os = require('os');
5
+ var path = require('path');
6
+
7
+ function _interopNamespaceDefault(e) {
8
+ var n = Object.create(null);
9
+ if (e) {
10
+ Object.keys(e).forEach(function (k) {
11
+ if (k !== 'default') {
12
+ var d = Object.getOwnPropertyDescriptor(e, k);
13
+ Object.defineProperty(n, k, d.get ? d : {
14
+ enumerable: true,
15
+ get: function () { return e[k]; }
16
+ });
17
+ }
18
+ });
19
+ }
20
+ n.default = e;
21
+ return Object.freeze(n);
22
+ }
23
+
24
+ var crypto__namespace = /*#__PURE__*/_interopNamespaceDefault(crypto);
25
+ var os__namespace = /*#__PURE__*/_interopNamespaceDefault(os);
26
+ var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
27
+
3
28
  function toScanFn(s) {
4
29
  return (typeof s === "function" ? s : s.scan);
5
30
  }
@@ -19,12 +44,100 @@ function composeScanners(...scanners) {
19
44
  return all;
20
45
  };
21
46
  }
22
- function createPresetScanner(_preset, _opts = {}) {
23
- // TODO: wire to real preset registry
24
- return async (_input, _ctx) => {
25
- return [];
26
- };
47
+ function createPresetScanner(preset, opts = {}) {
48
+ const scanners = [];
49
+ // Add decompilation scanners based on preset
50
+ if (preset === 'decompilation-basic' || preset === 'decompilation-deep' ||
51
+ preset === 'malware-analysis' || opts.enableDecompilation) {
52
+ const depth = preset === 'decompilation-deep' ? 'deep' :
53
+ preset === 'decompilation-basic' ? 'basic' :
54
+ opts.decompilationDepth || 'basic';
55
+ if (!opts.decompilationEngine || opts.decompilationEngine === 'binaryninja-hlil' || opts.decompilationEngine === 'both') {
56
+ try {
57
+ // Dynamic import to avoid bundling issues - using Function to bypass TypeScript type checking
58
+ const importModule = new Function('specifier', 'return import(specifier)');
59
+ importModule('@pompelmi/engine-binaryninja').then((mod) => {
60
+ const binjaScanner = mod.createBinaryNinjaScanner({
61
+ timeout: opts.decompilationTimeout || opts.timeout || 30000,
62
+ depth,
63
+ pythonPath: opts.pythonPath,
64
+ binaryNinjaPath: opts.binaryNinjaPath
65
+ });
66
+ scanners.push(binjaScanner);
67
+ }).catch(() => {
68
+ // Binary Ninja engine not available
69
+ });
70
+ }
71
+ catch {
72
+ // Engine not installed
73
+ }
74
+ }
75
+ if (!opts.decompilationEngine || opts.decompilationEngine === 'ghidra-pcode' || opts.decompilationEngine === 'both') {
76
+ try {
77
+ // Dynamic import for Ghidra engine (when implemented) - using Function to bypass TypeScript type checking
78
+ const importModule = new Function('specifier', 'return import(specifier)');
79
+ importModule('@pompelmi/engine-ghidra').then((mod) => {
80
+ const ghidraScanner = mod.createGhidraScanner({
81
+ timeout: opts.decompilationTimeout || opts.timeout || 30000,
82
+ depth,
83
+ ghidraPath: opts.ghidraPath,
84
+ analyzeHeadless: opts.analyzeHeadless
85
+ });
86
+ scanners.push(ghidraScanner);
87
+ }).catch(() => {
88
+ // Ghidra engine not available
89
+ });
90
+ }
91
+ catch {
92
+ // Engine not installed
93
+ }
94
+ }
95
+ }
96
+ // Add other scanners for advanced presets
97
+ if (preset === 'advanced' || preset === 'malware-analysis') {
98
+ // Add heuristics scanner
99
+ try {
100
+ const { CommonHeuristicsScanner } = require('./scanners/common-heuristics');
101
+ scanners.push(new CommonHeuristicsScanner());
102
+ }
103
+ catch {
104
+ // Heuristics not available
105
+ }
106
+ }
107
+ if (scanners.length === 0) {
108
+ // Fallback scanner that returns no matches
109
+ return async (_input, _ctx) => {
110
+ return [];
111
+ };
112
+ }
113
+ return composeScanners(...scanners);
27
114
  }
115
+ // Preset configurations
116
+ const PRESET_CONFIGS = {
117
+ 'basic': {
118
+ timeout: 10000
119
+ },
120
+ 'advanced': {
121
+ timeout: 30000,
122
+ enableDecompilation: false
123
+ },
124
+ 'malware-analysis': {
125
+ timeout: 60000,
126
+ enableDecompilation: true,
127
+ decompilationEngine: 'both',
128
+ decompilationDepth: 'deep'
129
+ },
130
+ 'decompilation-basic': {
131
+ timeout: 30000,
132
+ enableDecompilation: true,
133
+ decompilationDepth: 'basic'
134
+ },
135
+ 'decompilation-deep': {
136
+ timeout: 120000,
137
+ enableDecompilation: true,
138
+ decompilationDepth: 'deep'
139
+ }
140
+ };
28
141
 
29
142
  /** Mappa veloce estensione -> mime (basic) */
30
143
  function guessMimeByExt(name) {
@@ -61,13 +174,13 @@ function toYaraMatches(ms) {
61
174
  /** Scan di bytes (browser/node) usando preset (default: zip-basic) */
62
175
  async function scanBytes(input, opts = {}) {
63
176
  const t0 = Date.now();
64
- opts.preset ?? 'zip-basic';
177
+ const preset = opts.preset ?? 'zip-basic';
65
178
  const ctx = {
66
179
  ...opts.ctx,
67
180
  mimeType: opts.ctx?.mimeType ?? guessMimeByExt(opts.ctx?.filename),
68
181
  size: opts.ctx?.size ?? input.byteLength,
69
182
  };
70
- const scanFn = createPresetScanner();
183
+ const scanFn = createPresetScanner(preset);
71
184
  const matchesH = await (typeof scanFn === "function" ? scanFn : scanFn.scan)(input, ctx);
72
185
  const matches = toYaraMatches(matchesH);
73
186
  const verdict = computeVerdict(matches);
@@ -2110,6 +2223,353 @@ async function scanFilesWithRemoteYara(files, rulesSource, remote) {
2110
2223
  return results;
2111
2224
  }
2112
2225
 
2226
+ /** Decompilation-specific types for Pompelmi */
2227
+ const SUSPICIOUS_PATTERNS = [
2228
+ {
2229
+ name: 'syscall_direct',
2230
+ description: 'Direct system call without library wrapper',
2231
+ severity: 'medium',
2232
+ pattern: /syscall|sysenter|int\s+0x80/i
2233
+ },
2234
+ {
2235
+ name: 'process_injection',
2236
+ description: 'Process injection techniques',
2237
+ severity: 'high',
2238
+ pattern: /CreateRemoteThread|WriteProcessMemory|VirtualAllocEx/i
2239
+ },
2240
+ {
2241
+ name: 'anti_debug',
2242
+ description: 'Anti-debugging techniques',
2243
+ severity: 'medium',
2244
+ pattern: /IsDebuggerPresent|CheckRemoteDebuggerPresent|OutputDebugString/i
2245
+ },
2246
+ {
2247
+ name: 'obfuscation_xor',
2248
+ description: 'XOR-based obfuscation pattern',
2249
+ severity: 'medium',
2250
+ pattern: /xor.*0x[0-9a-f]+.*xor/i
2251
+ },
2252
+ {
2253
+ name: 'crypto_constants',
2254
+ description: 'Cryptographic constants',
2255
+ severity: 'low',
2256
+ pattern: /0x67452301|0xefcdab89|0x98badcfe|0x10325476/i
2257
+ }
2258
+ ];
2259
+
2260
+ /**
2261
+ * HIPAA Compliance Module for Pompelmi
2262
+ *
2263
+ * This module provides comprehensive HIPAA compliance features for healthcare environments
2264
+ * where Pompelmi is used to analyze potentially compromised systems containing PHI.
2265
+ *
2266
+ * Key protections:
2267
+ * - Data sanitization and redaction
2268
+ * - Secure temporary file handling
2269
+ * - Audit logging
2270
+ * - Memory protection
2271
+ * - Error message sanitization
2272
+ */
2273
+ class HipaaComplianceManager {
2274
+ constructor(config) {
2275
+ this.auditEvents = [];
2276
+ this.config = {
2277
+ sanitizeErrors: true,
2278
+ sanitizeFilenames: true,
2279
+ encryptTempFiles: true,
2280
+ memoryProtection: true,
2281
+ requireSecureTransport: true,
2282
+ ...config,
2283
+ enabled: config.enabled !== undefined ? config.enabled : true
2284
+ };
2285
+ this.sessionId = this.generateSessionId();
2286
+ }
2287
+ /**
2288
+ * Sanitize filename to prevent PHI leakage in logs
2289
+ */
2290
+ sanitizeFilename(filename) {
2291
+ if (!this.config.enabled || !this.config.sanitizeFilenames || !filename) {
2292
+ return filename || 'unknown';
2293
+ }
2294
+ // Remove potentially sensitive path information
2295
+ const basename = path__namespace.basename(filename);
2296
+ // Hash the filename to create a consistent but non-revealing identifier
2297
+ const hash = crypto__namespace.createHash('sha256').update(basename).digest('hex').substring(0, 8);
2298
+ // Preserve file extension for analysis purposes
2299
+ const ext = path__namespace.extname(basename);
2300
+ return `file_${hash}${ext}`;
2301
+ }
2302
+ /**
2303
+ * Sanitize error messages to prevent PHI exposure
2304
+ */
2305
+ sanitizeError(error) {
2306
+ if (!this.config.enabled || !this.config.sanitizeErrors) {
2307
+ return typeof error === 'string' ? error : error.message;
2308
+ }
2309
+ const message = typeof error === 'string' ? error : error.message;
2310
+ // Remove common patterns that might contain PHI
2311
+ let sanitized = message
2312
+ // Remove file paths
2313
+ .replace(/[A-Za-z]:\\\\[^\\s]+/g, '[REDACTED_PATH]')
2314
+ .replace(/\/[^\\s]+/g, '[REDACTED_PATH]')
2315
+ // Remove potential patient identifiers (numbers that could be MRNs, SSNs)
2316
+ .replace(/\\b\\d{3}-?\\d{2}-?\\d{4}\\b/g, '[REDACTED_ID]')
2317
+ .replace(/\\b\\d{6,}\\b/g, '[REDACTED_ID]')
2318
+ // Remove email addresses
2319
+ .replace(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/g, '[REDACTED_EMAIL]')
2320
+ // Remove potential names (capitalize words in error messages)
2321
+ .replace(/\\b[A-Z][a-z]+\\s+[A-Z][a-z]+\\b/g, '[REDACTED_NAME]')
2322
+ // Remove IP addresses
2323
+ .replace(/\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b/g, '[REDACTED_IP]');
2324
+ return sanitized;
2325
+ }
2326
+ /**
2327
+ * Create secure temporary file path with encryption if enabled
2328
+ */
2329
+ createSecureTempPath(prefix = 'pompelmi') {
2330
+ if (!this.config.enabled) {
2331
+ return path__namespace.join(os__namespace.tmpdir(), `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
2332
+ }
2333
+ // Use cryptographically secure random names
2334
+ const randomId = crypto__namespace.randomBytes(16).toString('hex');
2335
+ const timestamp = Date.now();
2336
+ // Create path in secure temp directory
2337
+ const secureTempDir = this.getSecureTempDir();
2338
+ const tempPath = path__namespace.join(secureTempDir, `${prefix}-${timestamp}-${randomId}`);
2339
+ this.auditLog('temp_file_created', {
2340
+ action: 'create_temp_file',
2341
+ success: true,
2342
+ metadata: { path: this.sanitizeFilename(tempPath) }
2343
+ });
2344
+ return tempPath;
2345
+ }
2346
+ /**
2347
+ * Get or create secure temporary directory with restricted permissions
2348
+ */
2349
+ getSecureTempDir() {
2350
+ const secureTempPath = path__namespace.join(os__namespace.tmpdir(), 'pompelmi-secure');
2351
+ try {
2352
+ const fs = require('fs');
2353
+ if (!fs.existsSync(secureTempPath)) {
2354
+ fs.mkdirSync(secureTempPath, { mode: 0o700 }); // Owner read/write/execute only
2355
+ }
2356
+ }
2357
+ catch (error) {
2358
+ // Fallback to system temp
2359
+ return os__namespace.tmpdir();
2360
+ }
2361
+ return secureTempPath;
2362
+ }
2363
+ /**
2364
+ * Secure file cleanup with multiple overwrite passes
2365
+ */
2366
+ async secureFileCleanup(filePath) {
2367
+ if (!this.config.enabled) {
2368
+ try {
2369
+ const fs = await import('fs/promises');
2370
+ await fs.unlink(filePath);
2371
+ }
2372
+ catch {
2373
+ // Ignore cleanup errors
2374
+ }
2375
+ return;
2376
+ }
2377
+ try {
2378
+ const fs = await import('fs/promises');
2379
+ const stats = await fs.stat(filePath);
2380
+ if (this.config.memoryProtection) {
2381
+ // Overwrite file with random data multiple times (DoD 5220.22-M standard)
2382
+ const fileSize = stats.size;
2383
+ const buffer = crypto__namespace.randomBytes(Math.min(fileSize, 64 * 1024)); // 64KB chunks
2384
+ for (let pass = 0; pass < 3; pass++) {
2385
+ const handle = await fs.open(filePath, 'r+');
2386
+ try {
2387
+ for (let offset = 0; offset < fileSize; offset += buffer.length) {
2388
+ const chunk = offset + buffer.length > fileSize
2389
+ ? buffer.subarray(0, fileSize - offset)
2390
+ : buffer;
2391
+ await handle.write(chunk, 0, chunk.length, offset);
2392
+ }
2393
+ await handle.sync();
2394
+ }
2395
+ finally {
2396
+ await handle.close();
2397
+ }
2398
+ }
2399
+ }
2400
+ // Final deletion
2401
+ await fs.unlink(filePath);
2402
+ this.auditLog('temp_file_deleted', {
2403
+ action: 'secure_delete',
2404
+ success: true,
2405
+ metadata: {
2406
+ path: this.sanitizeFilename(filePath),
2407
+ overwritePasses: this.config.memoryProtection ? 3 : 0
2408
+ }
2409
+ });
2410
+ }
2411
+ catch (error) {
2412
+ this.auditLog('temp_file_deleted', {
2413
+ action: 'secure_delete',
2414
+ success: false,
2415
+ sanitizedError: this.sanitizeError(error),
2416
+ metadata: { path: this.sanitizeFilename(filePath) }
2417
+ });
2418
+ }
2419
+ }
2420
+ /**
2421
+ * Calculate secure file hash for audit purposes
2422
+ */
2423
+ calculateFileHash(data) {
2424
+ return crypto__namespace.createHash('sha256').update(data).digest('hex');
2425
+ }
2426
+ /**
2427
+ * Log audit event
2428
+ */
2429
+ auditLog(eventType, details) {
2430
+ if (!this.config.enabled)
2431
+ return;
2432
+ const event = {
2433
+ timestamp: new Date().toISOString(),
2434
+ eventType,
2435
+ sessionId: this.sessionId,
2436
+ details: {
2437
+ action: details.action || 'unknown',
2438
+ success: details.success ?? true,
2439
+ ...details
2440
+ }
2441
+ };
2442
+ this.auditEvents.push(event);
2443
+ // Write to audit log file if configured
2444
+ if (this.config.auditLogPath) {
2445
+ this.writeAuditLog(event).catch(() => {
2446
+ // Silent failure to prevent error loops
2447
+ });
2448
+ }
2449
+ }
2450
+ /**
2451
+ * Write audit event to file
2452
+ */
2453
+ async writeAuditLog(event) {
2454
+ if (!this.config.auditLogPath)
2455
+ return;
2456
+ try {
2457
+ const fs = await import('fs/promises');
2458
+ const logLine = JSON.stringify(event) + '\\n';
2459
+ await fs.appendFile(this.config.auditLogPath, logLine, { flag: 'a' });
2460
+ }
2461
+ catch {
2462
+ // Silent failure
2463
+ }
2464
+ }
2465
+ /**
2466
+ * Generate cryptographically secure session ID
2467
+ */
2468
+ generateSessionId() {
2469
+ return crypto__namespace.randomBytes(16).toString('hex');
2470
+ }
2471
+ /**
2472
+ * Get current audit events for this session
2473
+ */
2474
+ getAuditEvents() {
2475
+ return [...this.auditEvents];
2476
+ }
2477
+ /**
2478
+ * Clear sensitive data from memory
2479
+ */
2480
+ clearSensitiveData() {
2481
+ if (!this.config.enabled || !this.config.memoryProtection)
2482
+ return;
2483
+ // Clear audit events
2484
+ this.auditEvents.length = 0;
2485
+ // Force garbage collection if available
2486
+ if (global.gc) {
2487
+ global.gc();
2488
+ }
2489
+ }
2490
+ /**
2491
+ * Validate transport security
2492
+ */
2493
+ validateTransportSecurity(url) {
2494
+ if (!this.config.enabled || !this.config.requireSecureTransport) {
2495
+ return true;
2496
+ }
2497
+ if (!url)
2498
+ return true;
2499
+ try {
2500
+ const urlObj = new URL(url);
2501
+ const isSecure = urlObj.protocol === 'https:' || urlObj.hostname === 'localhost' || urlObj.hostname === '127.0.0.1';
2502
+ if (!isSecure) {
2503
+ this.auditLog('security_violation', {
2504
+ action: 'insecure_transport',
2505
+ success: false,
2506
+ metadata: { protocol: urlObj.protocol, hostname: urlObj.hostname }
2507
+ });
2508
+ }
2509
+ return isSecure;
2510
+ }
2511
+ catch {
2512
+ return false;
2513
+ }
2514
+ }
2515
+ }
2516
+ // Global HIPAA compliance instance
2517
+ let hipaaManager = null;
2518
+ /**
2519
+ * Initialize HIPAA compliance
2520
+ */
2521
+ function initializeHipaaCompliance(config) {
2522
+ hipaaManager = new HipaaComplianceManager(config);
2523
+ return hipaaManager;
2524
+ }
2525
+ /**
2526
+ * Get current HIPAA compliance manager
2527
+ */
2528
+ function getHipaaManager() {
2529
+ return hipaaManager;
2530
+ }
2531
+ /**
2532
+ * HIPAA-compliant error wrapper
2533
+ */
2534
+ function createHipaaError(error, context) {
2535
+ const manager = getHipaaManager();
2536
+ if (!manager) {
2537
+ return typeof error === 'string' ? new Error(error) : error;
2538
+ }
2539
+ const sanitizedMessage = manager.sanitizeError(error);
2540
+ const hipaaError = new Error(sanitizedMessage);
2541
+ manager.auditLog('error_occurred', {
2542
+ action: context || 'error',
2543
+ success: false,
2544
+ sanitizedError: sanitizedMessage
2545
+ });
2546
+ return hipaaError;
2547
+ }
2548
+ /**
2549
+ * HIPAA-compliant temporary file utilities
2550
+ */
2551
+ const HipaaTemp = {
2552
+ createPath: (prefix) => {
2553
+ const manager = getHipaaManager();
2554
+ return manager ? manager.createSecureTempPath(prefix) : path__namespace.join(os__namespace.tmpdir(), `${prefix || 'pompelmi'}-${Date.now()}`);
2555
+ },
2556
+ cleanup: async (filePath) => {
2557
+ const manager = getHipaaManager();
2558
+ if (manager) {
2559
+ await manager.secureFileCleanup(filePath);
2560
+ }
2561
+ else {
2562
+ try {
2563
+ const fs = await import('fs/promises');
2564
+ await fs.unlink(filePath);
2565
+ }
2566
+ catch {
2567
+ // Ignore errors
2568
+ }
2569
+ }
2570
+ }
2571
+ };
2572
+
2113
2573
  function mapMatchesToVerdict(matches = []) {
2114
2574
  if (!matches.length)
2115
2575
  return 'clean';
@@ -2326,10 +2786,17 @@ function definePolicy(input = {}) {
2326
2786
 
2327
2787
  exports.CommonHeuristicsScanner = CommonHeuristicsScanner;
2328
2788
  exports.DEFAULT_POLICY = DEFAULT_POLICY;
2789
+ exports.HipaaComplianceManager = HipaaComplianceManager;
2790
+ exports.HipaaTemp = HipaaTemp;
2791
+ exports.PRESET_CONFIGS = PRESET_CONFIGS;
2792
+ exports.SUSPICIOUS_PATTERNS = SUSPICIOUS_PATTERNS;
2329
2793
  exports.composeScanners = composeScanners;
2794
+ exports.createHipaaError = createHipaaError;
2330
2795
  exports.createPresetScanner = createPresetScanner;
2331
2796
  exports.createZipBombGuard = createZipBombGuard;
2332
2797
  exports.definePolicy = definePolicy;
2798
+ exports.getHipaaManager = getHipaaManager;
2799
+ exports.initializeHipaaCompliance = initializeHipaaCompliance;
2333
2800
  exports.mapMatchesToVerdict = mapMatchesToVerdict;
2334
2801
  exports.scanBytes = scanBytes;
2335
2802
  exports.scanFile = scanFile;