pompelmi 0.19.0 → 0.21.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/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,98 @@ 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
58
+ import('@pompelmi/engine-binaryninja').then(({ createBinaryNinjaScanner }) => {
59
+ const binjaScanner = createBinaryNinjaScanner({
60
+ timeout: opts.decompilationTimeout || opts.timeout || 30000,
61
+ depth,
62
+ pythonPath: opts.pythonPath,
63
+ binaryNinjaPath: opts.binaryNinjaPath
64
+ });
65
+ scanners.push(binjaScanner);
66
+ }).catch(() => {
67
+ // Binary Ninja engine not available
68
+ });
69
+ }
70
+ catch {
71
+ // Engine not installed
72
+ }
73
+ }
74
+ if (!opts.decompilationEngine || opts.decompilationEngine === 'ghidra-pcode' || opts.decompilationEngine === 'both') {
75
+ try {
76
+ // Dynamic import for Ghidra engine (when implemented)
77
+ import('@pompelmi/engine-ghidra').then(({ createGhidraScanner }) => {
78
+ const ghidraScanner = createGhidraScanner({
79
+ timeout: opts.decompilationTimeout || opts.timeout || 30000,
80
+ depth,
81
+ ghidraPath: opts.ghidraPath,
82
+ analyzeHeadless: opts.analyzeHeadless
83
+ });
84
+ scanners.push(ghidraScanner);
85
+ }).catch(() => {
86
+ // Ghidra engine not available
87
+ });
88
+ }
89
+ catch {
90
+ // Engine not installed
91
+ }
92
+ }
93
+ }
94
+ // Add other scanners for advanced presets
95
+ if (preset === 'advanced' || preset === 'malware-analysis') {
96
+ // Add heuristics scanner
97
+ try {
98
+ const { CommonHeuristicsScanner } = require('./scanners/common-heuristics');
99
+ scanners.push(new CommonHeuristicsScanner());
100
+ }
101
+ catch {
102
+ // Heuristics not available
103
+ }
104
+ }
105
+ if (scanners.length === 0) {
106
+ // Fallback scanner that returns no matches
107
+ return async (_input, _ctx) => {
108
+ return [];
109
+ };
110
+ }
111
+ return composeScanners(...scanners);
27
112
  }
113
+ // Preset configurations
114
+ const PRESET_CONFIGS = {
115
+ 'basic': {
116
+ timeout: 10000
117
+ },
118
+ 'advanced': {
119
+ timeout: 30000,
120
+ enableDecompilation: false
121
+ },
122
+ 'malware-analysis': {
123
+ timeout: 60000,
124
+ enableDecompilation: true,
125
+ decompilationEngine: 'both',
126
+ decompilationDepth: 'deep'
127
+ },
128
+ 'decompilation-basic': {
129
+ timeout: 30000,
130
+ enableDecompilation: true,
131
+ decompilationDepth: 'basic'
132
+ },
133
+ 'decompilation-deep': {
134
+ timeout: 120000,
135
+ enableDecompilation: true,
136
+ decompilationDepth: 'deep'
137
+ }
138
+ };
28
139
 
29
140
  /** Mappa veloce estensione -> mime (basic) */
30
141
  function guessMimeByExt(name) {
@@ -61,13 +172,13 @@ function toYaraMatches(ms) {
61
172
  /** Scan di bytes (browser/node) usando preset (default: zip-basic) */
62
173
  async function scanBytes(input, opts = {}) {
63
174
  const t0 = Date.now();
64
- opts.preset ?? 'zip-basic';
175
+ const preset = opts.preset ?? 'zip-basic';
65
176
  const ctx = {
66
177
  ...opts.ctx,
67
178
  mimeType: opts.ctx?.mimeType ?? guessMimeByExt(opts.ctx?.filename),
68
179
  size: opts.ctx?.size ?? input.byteLength,
69
180
  };
70
- const scanFn = createPresetScanner();
181
+ const scanFn = createPresetScanner(preset);
71
182
  const matchesH = await (typeof scanFn === "function" ? scanFn : scanFn.scan)(input, ctx);
72
183
  const matches = toYaraMatches(matchesH);
73
184
  const verdict = computeVerdict(matches);
@@ -2110,6 +2221,353 @@ async function scanFilesWithRemoteYara(files, rulesSource, remote) {
2110
2221
  return results;
2111
2222
  }
2112
2223
 
2224
+ /** Decompilation-specific types for Pompelmi */
2225
+ const SUSPICIOUS_PATTERNS = [
2226
+ {
2227
+ name: 'syscall_direct',
2228
+ description: 'Direct system call without library wrapper',
2229
+ severity: 'medium',
2230
+ pattern: /syscall|sysenter|int\s+0x80/i
2231
+ },
2232
+ {
2233
+ name: 'process_injection',
2234
+ description: 'Process injection techniques',
2235
+ severity: 'high',
2236
+ pattern: /CreateRemoteThread|WriteProcessMemory|VirtualAllocEx/i
2237
+ },
2238
+ {
2239
+ name: 'anti_debug',
2240
+ description: 'Anti-debugging techniques',
2241
+ severity: 'medium',
2242
+ pattern: /IsDebuggerPresent|CheckRemoteDebuggerPresent|OutputDebugString/i
2243
+ },
2244
+ {
2245
+ name: 'obfuscation_xor',
2246
+ description: 'XOR-based obfuscation pattern',
2247
+ severity: 'medium',
2248
+ pattern: /xor.*0x[0-9a-f]+.*xor/i
2249
+ },
2250
+ {
2251
+ name: 'crypto_constants',
2252
+ description: 'Cryptographic constants',
2253
+ severity: 'low',
2254
+ pattern: /0x67452301|0xefcdab89|0x98badcfe|0x10325476/i
2255
+ }
2256
+ ];
2257
+
2258
+ /**
2259
+ * HIPAA Compliance Module for Pompelmi
2260
+ *
2261
+ * This module provides comprehensive HIPAA compliance features for healthcare environments
2262
+ * where Pompelmi is used to analyze potentially compromised systems containing PHI.
2263
+ *
2264
+ * Key protections:
2265
+ * - Data sanitization and redaction
2266
+ * - Secure temporary file handling
2267
+ * - Audit logging
2268
+ * - Memory protection
2269
+ * - Error message sanitization
2270
+ */
2271
+ class HipaaComplianceManager {
2272
+ constructor(config) {
2273
+ this.auditEvents = [];
2274
+ this.config = {
2275
+ enabled: true,
2276
+ sanitizeErrors: true,
2277
+ sanitizeFilenames: true,
2278
+ encryptTempFiles: true,
2279
+ memoryProtection: true,
2280
+ requireSecureTransport: true,
2281
+ ...config
2282
+ };
2283
+ this.sessionId = this.generateSessionId();
2284
+ }
2285
+ /**
2286
+ * Sanitize filename to prevent PHI leakage in logs
2287
+ */
2288
+ sanitizeFilename(filename) {
2289
+ if (!this.config.enabled || !this.config.sanitizeFilenames || !filename) {
2290
+ return filename || 'unknown';
2291
+ }
2292
+ // Remove potentially sensitive path information
2293
+ const basename = path__namespace.basename(filename);
2294
+ // Hash the filename to create a consistent but non-revealing identifier
2295
+ const hash = crypto__namespace.createHash('sha256').update(basename).digest('hex').substring(0, 8);
2296
+ // Preserve file extension for analysis purposes
2297
+ const ext = path__namespace.extname(basename);
2298
+ return `file_${hash}${ext}`;
2299
+ }
2300
+ /**
2301
+ * Sanitize error messages to prevent PHI exposure
2302
+ */
2303
+ sanitizeError(error) {
2304
+ if (!this.config.enabled || !this.config.sanitizeErrors) {
2305
+ return typeof error === 'string' ? error : error.message;
2306
+ }
2307
+ const message = typeof error === 'string' ? error : error.message;
2308
+ // Remove common patterns that might contain PHI
2309
+ let sanitized = message
2310
+ // Remove file paths
2311
+ .replace(/[A-Za-z]:\\\\[^\\s]+/g, '[REDACTED_PATH]')
2312
+ .replace(/\/[^\\s]+/g, '[REDACTED_PATH]')
2313
+ // Remove potential patient identifiers (numbers that could be MRNs, SSNs)
2314
+ .replace(/\\b\\d{3}-?\\d{2}-?\\d{4}\\b/g, '[REDACTED_ID]')
2315
+ .replace(/\\b\\d{6,}\\b/g, '[REDACTED_ID]')
2316
+ // Remove email addresses
2317
+ .replace(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/g, '[REDACTED_EMAIL]')
2318
+ // Remove potential names (capitalize words in error messages)
2319
+ .replace(/\\b[A-Z][a-z]+\\s+[A-Z][a-z]+\\b/g, '[REDACTED_NAME]')
2320
+ // Remove IP addresses
2321
+ .replace(/\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b/g, '[REDACTED_IP]');
2322
+ return sanitized;
2323
+ }
2324
+ /**
2325
+ * Create secure temporary file path with encryption if enabled
2326
+ */
2327
+ createSecureTempPath(prefix = 'pompelmi') {
2328
+ if (!this.config.enabled) {
2329
+ return path__namespace.join(os__namespace.tmpdir(), `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
2330
+ }
2331
+ // Use cryptographically secure random names
2332
+ const randomId = crypto__namespace.randomBytes(16).toString('hex');
2333
+ const timestamp = Date.now();
2334
+ // Create path in secure temp directory
2335
+ const secureTempDir = this.getSecureTempDir();
2336
+ const tempPath = path__namespace.join(secureTempDir, `${prefix}-${timestamp}-${randomId}`);
2337
+ this.auditLog('temp_file_created', {
2338
+ action: 'create_temp_file',
2339
+ success: true,
2340
+ metadata: { path: this.sanitizeFilename(tempPath) }
2341
+ });
2342
+ return tempPath;
2343
+ }
2344
+ /**
2345
+ * Get or create secure temporary directory with restricted permissions
2346
+ */
2347
+ getSecureTempDir() {
2348
+ const secureTempPath = path__namespace.join(os__namespace.tmpdir(), 'pompelmi-secure');
2349
+ try {
2350
+ const fs = require('fs');
2351
+ if (!fs.existsSync(secureTempPath)) {
2352
+ fs.mkdirSync(secureTempPath, { mode: 0o700 }); // Owner read/write/execute only
2353
+ }
2354
+ }
2355
+ catch (error) {
2356
+ // Fallback to system temp
2357
+ return os__namespace.tmpdir();
2358
+ }
2359
+ return secureTempPath;
2360
+ }
2361
+ /**
2362
+ * Secure file cleanup with multiple overwrite passes
2363
+ */
2364
+ async secureFileCleanup(filePath) {
2365
+ if (!this.config.enabled) {
2366
+ try {
2367
+ const fs = await import('fs/promises');
2368
+ await fs.unlink(filePath);
2369
+ }
2370
+ catch {
2371
+ // Ignore cleanup errors
2372
+ }
2373
+ return;
2374
+ }
2375
+ try {
2376
+ const fs = await import('fs/promises');
2377
+ const stats = await fs.stat(filePath);
2378
+ if (this.config.memoryProtection) {
2379
+ // Overwrite file with random data multiple times (DoD 5220.22-M standard)
2380
+ const fileSize = stats.size;
2381
+ const buffer = crypto__namespace.randomBytes(Math.min(fileSize, 64 * 1024)); // 64KB chunks
2382
+ for (let pass = 0; pass < 3; pass++) {
2383
+ const handle = await fs.open(filePath, 'r+');
2384
+ try {
2385
+ for (let offset = 0; offset < fileSize; offset += buffer.length) {
2386
+ const chunk = offset + buffer.length > fileSize
2387
+ ? buffer.subarray(0, fileSize - offset)
2388
+ : buffer;
2389
+ await handle.write(chunk, 0, chunk.length, offset);
2390
+ }
2391
+ await handle.sync();
2392
+ }
2393
+ finally {
2394
+ await handle.close();
2395
+ }
2396
+ }
2397
+ }
2398
+ // Final deletion
2399
+ await fs.unlink(filePath);
2400
+ this.auditLog('temp_file_deleted', {
2401
+ action: 'secure_delete',
2402
+ success: true,
2403
+ metadata: {
2404
+ path: this.sanitizeFilename(filePath),
2405
+ overwritePasses: this.config.memoryProtection ? 3 : 0
2406
+ }
2407
+ });
2408
+ }
2409
+ catch (error) {
2410
+ this.auditLog('temp_file_deleted', {
2411
+ action: 'secure_delete',
2412
+ success: false,
2413
+ sanitizedError: this.sanitizeError(error),
2414
+ metadata: { path: this.sanitizeFilename(filePath) }
2415
+ });
2416
+ }
2417
+ }
2418
+ /**
2419
+ * Calculate secure file hash for audit purposes
2420
+ */
2421
+ calculateFileHash(data) {
2422
+ return crypto__namespace.createHash('sha256').update(data).digest('hex');
2423
+ }
2424
+ /**
2425
+ * Log audit event
2426
+ */
2427
+ auditLog(eventType, details) {
2428
+ if (!this.config.enabled)
2429
+ return;
2430
+ const event = {
2431
+ timestamp: new Date().toISOString(),
2432
+ eventType,
2433
+ sessionId: this.sessionId,
2434
+ details: {
2435
+ action: details.action || 'unknown',
2436
+ success: details.success ?? true,
2437
+ ...details
2438
+ }
2439
+ };
2440
+ this.auditEvents.push(event);
2441
+ // Write to audit log file if configured
2442
+ if (this.config.auditLogPath) {
2443
+ this.writeAuditLog(event).catch(() => {
2444
+ // Silent failure to prevent error loops
2445
+ });
2446
+ }
2447
+ }
2448
+ /**
2449
+ * Write audit event to file
2450
+ */
2451
+ async writeAuditLog(event) {
2452
+ if (!this.config.auditLogPath)
2453
+ return;
2454
+ try {
2455
+ const fs = await import('fs/promises');
2456
+ const logLine = JSON.stringify(event) + '\\n';
2457
+ await fs.appendFile(this.config.auditLogPath, logLine, { flag: 'a' });
2458
+ }
2459
+ catch {
2460
+ // Silent failure
2461
+ }
2462
+ }
2463
+ /**
2464
+ * Generate cryptographically secure session ID
2465
+ */
2466
+ generateSessionId() {
2467
+ return crypto__namespace.randomBytes(16).toString('hex');
2468
+ }
2469
+ /**
2470
+ * Get current audit events for this session
2471
+ */
2472
+ getAuditEvents() {
2473
+ return [...this.auditEvents];
2474
+ }
2475
+ /**
2476
+ * Clear sensitive data from memory
2477
+ */
2478
+ clearSensitiveData() {
2479
+ if (!this.config.enabled || !this.config.memoryProtection)
2480
+ return;
2481
+ // Clear audit events
2482
+ this.auditEvents.length = 0;
2483
+ // Force garbage collection if available
2484
+ if (global.gc) {
2485
+ global.gc();
2486
+ }
2487
+ }
2488
+ /**
2489
+ * Validate transport security
2490
+ */
2491
+ validateTransportSecurity(url) {
2492
+ if (!this.config.enabled || !this.config.requireSecureTransport) {
2493
+ return true;
2494
+ }
2495
+ if (!url)
2496
+ return true;
2497
+ try {
2498
+ const urlObj = new URL(url);
2499
+ const isSecure = urlObj.protocol === 'https:' || urlObj.hostname === 'localhost' || urlObj.hostname === '127.0.0.1';
2500
+ if (!isSecure) {
2501
+ this.auditLog('security_violation', {
2502
+ action: 'insecure_transport',
2503
+ success: false,
2504
+ metadata: { protocol: urlObj.protocol, hostname: urlObj.hostname }
2505
+ });
2506
+ }
2507
+ return isSecure;
2508
+ }
2509
+ catch {
2510
+ return false;
2511
+ }
2512
+ }
2513
+ }
2514
+ // Global HIPAA compliance instance
2515
+ let hipaaManager = null;
2516
+ /**
2517
+ * Initialize HIPAA compliance
2518
+ */
2519
+ function initializeHipaaCompliance(config) {
2520
+ hipaaManager = new HipaaComplianceManager(config);
2521
+ return hipaaManager;
2522
+ }
2523
+ /**
2524
+ * Get current HIPAA compliance manager
2525
+ */
2526
+ function getHipaaManager() {
2527
+ return hipaaManager;
2528
+ }
2529
+ /**
2530
+ * HIPAA-compliant error wrapper
2531
+ */
2532
+ function createHipaaError(error, context) {
2533
+ const manager = getHipaaManager();
2534
+ if (!manager) {
2535
+ return typeof error === 'string' ? new Error(error) : error;
2536
+ }
2537
+ const sanitizedMessage = manager.sanitizeError(error);
2538
+ const hipaaError = new Error(sanitizedMessage);
2539
+ manager.auditLog('error_occurred', {
2540
+ action: context || 'error',
2541
+ success: false,
2542
+ sanitizedError: sanitizedMessage
2543
+ });
2544
+ return hipaaError;
2545
+ }
2546
+ /**
2547
+ * HIPAA-compliant temporary file utilities
2548
+ */
2549
+ const HipaaTemp = {
2550
+ createPath: (prefix) => {
2551
+ const manager = getHipaaManager();
2552
+ return manager ? manager.createSecureTempPath(prefix) : path__namespace.join(os__namespace.tmpdir(), `${prefix || 'pompelmi'}-${Date.now()}`);
2553
+ },
2554
+ cleanup: async (filePath) => {
2555
+ const manager = getHipaaManager();
2556
+ if (manager) {
2557
+ await manager.secureFileCleanup(filePath);
2558
+ }
2559
+ else {
2560
+ try {
2561
+ const fs = await import('fs/promises');
2562
+ await fs.unlink(filePath);
2563
+ }
2564
+ catch {
2565
+ // Ignore errors
2566
+ }
2567
+ }
2568
+ }
2569
+ };
2570
+
2113
2571
  function mapMatchesToVerdict(matches = []) {
2114
2572
  if (!matches.length)
2115
2573
  return 'clean';
@@ -2326,10 +2784,17 @@ function definePolicy(input = {}) {
2326
2784
 
2327
2785
  exports.CommonHeuristicsScanner = CommonHeuristicsScanner;
2328
2786
  exports.DEFAULT_POLICY = DEFAULT_POLICY;
2787
+ exports.HipaaComplianceManager = HipaaComplianceManager;
2788
+ exports.HipaaTemp = HipaaTemp;
2789
+ exports.PRESET_CONFIGS = PRESET_CONFIGS;
2790
+ exports.SUSPICIOUS_PATTERNS = SUSPICIOUS_PATTERNS;
2329
2791
  exports.composeScanners = composeScanners;
2792
+ exports.createHipaaError = createHipaaError;
2330
2793
  exports.createPresetScanner = createPresetScanner;
2331
2794
  exports.createZipBombGuard = createZipBombGuard;
2332
2795
  exports.definePolicy = definePolicy;
2796
+ exports.getHipaaManager = getHipaaManager;
2797
+ exports.initializeHipaaCompliance = initializeHipaaCompliance;
2333
2798
  exports.mapMatchesToVerdict = mapMatchesToVerdict;
2334
2799
  exports.scanBytes = scanBytes;
2335
2800
  exports.scanFile = scanFile;