muaddib-scanner 2.2.5 → 2.2.7

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 (47) hide show
  1. package/bin/muaddib.js +16 -2
  2. package/datasets/benign/packages-npm.txt +576 -77
  3. package/datasets/benign/packages-pypi.txt +146 -31
  4. package/datasets/ground-truth/README.md +54 -0
  5. package/datasets/ground-truth/known-malware.json +622 -0
  6. package/datasets/holdout-v5/callback-exfil/main.js +8 -0
  7. package/datasets/holdout-v5/callback-exfil/package.json +5 -0
  8. package/datasets/holdout-v5/callback-exfil/reader.js +10 -0
  9. package/datasets/holdout-v5/class-method-exfil/collector.js +10 -0
  10. package/datasets/holdout-v5/class-method-exfil/main.js +7 -0
  11. package/datasets/holdout-v5/class-method-exfil/package.json +5 -0
  12. package/datasets/holdout-v5/conditional-split/detector.js +2 -0
  13. package/datasets/holdout-v5/conditional-split/package.json +5 -0
  14. package/datasets/holdout-v5/conditional-split/stealer.js +16 -0
  15. package/datasets/holdout-v5/event-emitter-flow/listener.js +12 -0
  16. package/datasets/holdout-v5/event-emitter-flow/package.json +5 -0
  17. package/datasets/holdout-v5/event-emitter-flow/scanner.js +11 -0
  18. package/datasets/holdout-v5/mixed-inline-split/index.js +6 -0
  19. package/datasets/holdout-v5/mixed-inline-split/package.json +5 -0
  20. package/datasets/holdout-v5/mixed-inline-split/reader.js +3 -0
  21. package/datasets/holdout-v5/mixed-inline-split/sender.js +6 -0
  22. package/datasets/holdout-v5/named-export-steal/main.js +6 -0
  23. package/datasets/holdout-v5/named-export-steal/package.json +5 -0
  24. package/datasets/holdout-v5/named-export-steal/utils.js +1 -0
  25. package/datasets/holdout-v5/reexport-chain/a.js +2 -0
  26. package/datasets/holdout-v5/reexport-chain/b.js +1 -0
  27. package/datasets/holdout-v5/reexport-chain/c.js +11 -0
  28. package/datasets/holdout-v5/reexport-chain/package.json +5 -0
  29. package/datasets/holdout-v5/split-env-exfil/env.js +2 -0
  30. package/datasets/holdout-v5/split-env-exfil/exfil.js +5 -0
  31. package/datasets/holdout-v5/split-env-exfil/package.json +5 -0
  32. package/datasets/holdout-v5/split-npmrc-steal/index.js +2 -0
  33. package/datasets/holdout-v5/split-npmrc-steal/package.json +5 -0
  34. package/datasets/holdout-v5/split-npmrc-steal/reader.js +8 -0
  35. package/datasets/holdout-v5/split-npmrc-steal/sender.js +17 -0
  36. package/datasets/holdout-v5/three-hop-chain/package.json +5 -0
  37. package/datasets/holdout-v5/three-hop-chain/reader.js +8 -0
  38. package/datasets/holdout-v5/three-hop-chain/sender.js +11 -0
  39. package/datasets/holdout-v5/three-hop-chain/transform.js +3 -0
  40. package/package.json +1 -1
  41. package/src/commands/evaluate.js +191 -31
  42. package/src/index.js +20 -1
  43. package/src/response/playbooks.js +5 -0
  44. package/src/rules/index.js +13 -0
  45. package/src/scanner/module-graph.js +883 -0
  46. package/tmp-summary.js +24 -0
  47. package/tmp-test-pack.js +66 -0
package/tmp-summary.js ADDED
@@ -0,0 +1,24 @@
1
+ const m = require('./metrics/v2.2.6.json');
2
+ console.log('=== BENIGN FPR RESULTS (50 packages) ===');
3
+ console.log('TPR:', m.groundTruth.tpr*100+'%');
4
+ console.log('FPR:', m.benign.fpr*100+'%', '('+m.benign.flagged+'/'+m.benign.scanned+')');
5
+ console.log('ADR:', m.adversarial.adr*100+'%');
6
+ console.log();
7
+ console.log('=== FALSE POSITIVES (score > 20) ===');
8
+ const fps = m.benign.details.filter(d => d.flagged).sort((a,b) => b.score - a.score);
9
+ fps.forEach(fp => {
10
+ console.log(fp.name+': score '+fp.score);
11
+ const types = {};
12
+ (fp.threats||[]).forEach(t => { types[t.type] = (types[t.type]||0)+1; });
13
+ Object.entries(types).sort((a,b)=>b[1]-a[1]).forEach(([k,v]) => console.log(' '+k+': '+v));
14
+ });
15
+ console.log();
16
+ console.log('=== NON-FLAGGED BUT ELEVATED (score > 0) ===');
17
+ m.benign.details.filter(d => !d.flagged && d.score > 0).sort((a,b) => b.score - a.score).forEach(d => console.log(d.name+': score '+d.score));
18
+ console.log();
19
+ console.log('=== THREAT TYPE FREQUENCY ===');
20
+ const allTypes = {};
21
+ fps.forEach(fp => {
22
+ (fp.threats||[]).forEach(t => { allTypes[t.type] = (allTypes[t.type]||0)+1; });
23
+ });
24
+ Object.entries(allTypes).sort((a,b)=>b[1]-a[1]).forEach(([k,v]) => console.log(' '+k+': '+v));
@@ -0,0 +1,66 @@
1
+ const { execSync } = require('child_process');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const zlib = require('zlib');
5
+
6
+ const testDir = path.join(__dirname, '.muaddib-cache', 'benign-tarballs', '_test_express');
7
+ fs.mkdirSync(testDir, { recursive: true });
8
+
9
+ // Step 1: npm pack with cwd
10
+ console.log('Step 1: npm pack express (cwd)...');
11
+ try {
12
+ const out = execSync('npm pack express', { cwd: testDir, encoding: 'utf8', timeout: 30000 });
13
+ const tgzFile = out.trim().split('\n').pop().trim();
14
+ console.log(' OK:', tgzFile);
15
+
16
+ // Step 2: extract with native Node.js
17
+ const tgzPath = path.join(testDir, tgzFile);
18
+ console.log('Step 2: native extraction...');
19
+ extractTgz(tgzPath, testDir);
20
+
21
+ const pkgDir = path.join(testDir, 'package');
22
+ const files = fs.readdirSync(pkgDir);
23
+ console.log(' Extracted files:', files.join(', '));
24
+ console.log(' SUCCESS');
25
+ } catch (e) {
26
+ console.log(' FAIL:', e.message.slice(0, 300));
27
+ }
28
+
29
+ // Cleanup
30
+ fs.rmSync(testDir, { recursive: true, force: true });
31
+
32
+ /**
33
+ * Extract a .tgz file using Node.js built-in zlib + minimal tar parser.
34
+ * Only extracts regular files (type '0' or NUL).
35
+ */
36
+ function extractTgz(tgzPath, destDir) {
37
+ const compressed = fs.readFileSync(tgzPath);
38
+ const tarData = zlib.gunzipSync(compressed);
39
+
40
+ let offset = 0;
41
+ while (offset + 512 <= tarData.length) {
42
+ const header = tarData.subarray(offset, offset + 512);
43
+
44
+ // Check for end-of-archive (two zero blocks)
45
+ if (header.every(b => b === 0)) break;
46
+
47
+ // Parse tar header
48
+ const name = header.subarray(0, 100).toString('utf8').replace(/\0+$/, '');
49
+ const sizeOctal = header.subarray(124, 136).toString('utf8').replace(/\0+$/, '').trim();
50
+ const size = parseInt(sizeOctal, 8) || 0;
51
+ const typeFlag = String.fromCharCode(header[156]);
52
+
53
+ offset += 512; // move past header
54
+
55
+ if (name && (typeFlag === '0' || typeFlag === '\0') && size > 0) {
56
+ // Regular file — extract it
57
+ const filePath = path.join(destDir, name);
58
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
59
+ const fileData = tarData.subarray(offset, offset + size);
60
+ fs.writeFileSync(filePath, fileData);
61
+ }
62
+
63
+ // Advance past data blocks (512-byte aligned)
64
+ offset += Math.ceil(size / 512) * 512;
65
+ }
66
+ }