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