pompelmi 0.14.0-dev.25 → 0.14.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.
Files changed (2) hide show
  1. package/README.md +119 -0
  2. package/package.json +3 -2
package/README.md CHANGED
@@ -38,6 +38,7 @@
38
38
  <a href="#diagrams">Diagrams</a> ·
39
39
  <a href="#configuration">Config</a> ·
40
40
  <a href="#production-checklist">Production checklist</a> ·
41
+ <a href="#yara-getting-started">YARA</a> ·
41
42
  <a href="#quick-test-eicar">Quick test</a> ·
42
43
  <a href="#security-notes">Security</a> ·
43
44
  <a href="#faq">FAQ</a>
@@ -401,6 +402,124 @@ failClosed: true,
401
402
 
402
403
  ---
403
404
 
405
+ ## YARA Getting Started
406
+
407
+ YARA lets you detect suspicious or malicious content using pattern‑matching rules.
408
+ **pompelmi** treats YARA matches as signals that you can map to your own verdicts
409
+ (e.g., mark high‑confidence rules as `malicious`, heuristics as `suspicious`).
410
+
411
+ > **Status:** Optional. You can run without YARA. If you adopt it, keep your rules small, time‑bound, and tuned to your threat model.
412
+
413
+ ### Starter rules
414
+
415
+ Below are three example rules you can adapt:
416
+
417
+ `rules/starter/eicar.yar`
418
+ ```yar
419
+ rule EICAR_Test_File
420
+ {
421
+ meta:
422
+ description = "EICAR antivirus test string (safe)"
423
+ reference = "https://www.eicar.org"
424
+ confidence = "high"
425
+ verdict = "malicious"
426
+ strings:
427
+ $eicar = "X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*"
428
+ condition:
429
+ $eicar
430
+ }
431
+ ```
432
+
433
+ `rules/starter/pdf_js.yar`
434
+ ```yar
435
+ rule PDF_JavaScript_Embedded
436
+ {
437
+ meta:
438
+ description = "PDF contains embedded JavaScript (heuristic)"
439
+ confidence = "medium"
440
+ verdict = "suspicious"
441
+ strings:
442
+ $magic = { 25 50 44 46 } // "%PDF"
443
+ $js1 = "/JavaScript" ascii
444
+ $js2 = "/JS" ascii
445
+ $open = "/OpenAction" ascii
446
+ $aa = "/AA" ascii
447
+ condition:
448
+ uint32(0) == 0x25504446 and ( $js1 or $js2 ) and ( $open or $aa )
449
+ }
450
+ ```
451
+
452
+ `rules/starter/office_macros.yar`
453
+ ```yar
454
+ rule Office_Macro_Suspicious_Words
455
+ {
456
+ meta:
457
+ description = "Heuristic: suspicious VBA macro keywords"
458
+ confidence = "medium"
459
+ verdict = "suspicious"
460
+ strings:
461
+ $s1 = /Auto(Open|Close)/ nocase
462
+ $s2 = "Document_Open" nocase ascii
463
+ $s3 = "CreateObject(" nocase ascii
464
+ $s4 = "WScript.Shell" nocase ascii
465
+ $s5 = "Shell(" nocase ascii
466
+ $s6 = "Sub Workbook_Open()" nocase ascii
467
+ condition:
468
+ 2 of ($s*)
469
+ }
470
+ ```
471
+
472
+ > These are **examples**. Expect some false positives; tune to your app.
473
+
474
+ ### Minimal integration (adapter contract)
475
+
476
+ If you use a YARA binding (e.g., `@automattic/yara`), wrap it behind the `scanner` contract:
477
+
478
+ ```ts
479
+ // Example YARA scanner adapter (pseudo‑code)
480
+ import * as Y from '@automattic/yara';
481
+
482
+ // Compile your rules from disk at boot (recommended)
483
+ // const sources = await fs.readFile('rules/starter/*.yar', 'utf8');
484
+ // const compiled = await Y.compile(sources);
485
+
486
+ export const YourYaraScanner = {
487
+ async scan(bytes: Uint8Array) {
488
+ // const matches = await compiled.scan(bytes, { timeout: 1500 });
489
+ const matches = []; // plug your engine here
490
+ // Map to the structure your app expects; return [] when clean.
491
+ return matches.map((m: any) => ({
492
+ rule: m.rule,
493
+ meta: m.meta ?? {},
494
+ tags: m.tags ?? [],
495
+ }));
496
+ }
497
+ };
498
+ ```
499
+
500
+ Then include it in your composed scanner:
501
+
502
+ ```ts
503
+ import { composeScanners, CommonHeuristicsScanner } from 'pompelmi';
504
+ // import { YourYaraScanner } from './yara-scanner';
505
+
506
+ export const scanner = composeScanners(
507
+ [
508
+ ['heuristics', CommonHeuristicsScanner],
509
+ // ['yara', YourYaraScanner],
510
+ ],
511
+ { parallel: false, stopOn: 'suspicious', timeoutMsPerScanner: 1500, tagSourceName: true }
512
+ );
513
+ ```
514
+
515
+ ### Policy suggestion (mapping matches → verdict)
516
+
517
+ - **malicious**: high‑confidence rules (e.g., `EICAR_Test_File`)
518
+ - **suspicious**: heuristic rules (e.g., PDF JavaScript, macro keywords)
519
+ - **clean**: no matches
520
+
521
+ Combine YARA with MIME sniffing, ZIP safety limits, and strict size/time caps.
522
+
404
523
  ## Quick test (no EICAR)
405
524
 
406
525
  Use the examples above, then send a **minimal PDF** that contains risky tokens (this triggers the built‑in heuristics).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pompelmi",
3
- "version": "0.14.0-dev.25",
3
+ "version": "0.14.0",
4
4
  "description": "RFI-safe file uploads for Node.js — Express/Koa/Next.js middleware with deep ZIP inspection, MIME/size checks, and optional YARA scanning.",
5
5
  "main": "dist/pompelmi.cjs.js",
6
6
  "module": "dist/pompelmi.esm.js",
@@ -37,7 +37,8 @@
37
37
  "dev:remote": "tsx examples/remote-yara-server.ts",
38
38
  "docs:build": "hugo -s docs -D -d docs",
39
39
  "predocs:deploy": "npm run docs:build",
40
- "docs:deploy": "gh-pages -d docs -b gh-pages"
40
+ "docs:deploy": "gh-pages -d docs -b gh-pages",
41
+ "yara:check": "node scripts/yara-quick-check-cli.mjs"
41
42
  },
42
43
  "license": "MIT",
43
44
  "devDependencies": {