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.
- package/bin/muaddib.js +16 -2
- package/datasets/benign/packages-npm.txt +576 -77
- package/datasets/benign/packages-pypi.txt +146 -31
- package/datasets/ground-truth/README.md +54 -0
- package/datasets/ground-truth/known-malware.json +622 -0
- package/datasets/holdout-v5/callback-exfil/main.js +8 -0
- package/datasets/holdout-v5/callback-exfil/package.json +5 -0
- package/datasets/holdout-v5/callback-exfil/reader.js +10 -0
- package/datasets/holdout-v5/class-method-exfil/collector.js +10 -0
- package/datasets/holdout-v5/class-method-exfil/main.js +7 -0
- package/datasets/holdout-v5/class-method-exfil/package.json +5 -0
- package/datasets/holdout-v5/conditional-split/detector.js +2 -0
- package/datasets/holdout-v5/conditional-split/package.json +5 -0
- package/datasets/holdout-v5/conditional-split/stealer.js +16 -0
- package/datasets/holdout-v5/event-emitter-flow/listener.js +12 -0
- package/datasets/holdout-v5/event-emitter-flow/package.json +5 -0
- package/datasets/holdout-v5/event-emitter-flow/scanner.js +11 -0
- package/datasets/holdout-v5/mixed-inline-split/index.js +6 -0
- package/datasets/holdout-v5/mixed-inline-split/package.json +5 -0
- package/datasets/holdout-v5/mixed-inline-split/reader.js +3 -0
- package/datasets/holdout-v5/mixed-inline-split/sender.js +6 -0
- package/datasets/holdout-v5/named-export-steal/main.js +6 -0
- package/datasets/holdout-v5/named-export-steal/package.json +5 -0
- package/datasets/holdout-v5/named-export-steal/utils.js +1 -0
- package/datasets/holdout-v5/reexport-chain/a.js +2 -0
- package/datasets/holdout-v5/reexport-chain/b.js +1 -0
- package/datasets/holdout-v5/reexport-chain/c.js +11 -0
- package/datasets/holdout-v5/reexport-chain/package.json +5 -0
- package/datasets/holdout-v5/split-env-exfil/env.js +2 -0
- package/datasets/holdout-v5/split-env-exfil/exfil.js +5 -0
- package/datasets/holdout-v5/split-env-exfil/package.json +5 -0
- package/datasets/holdout-v5/split-npmrc-steal/index.js +2 -0
- package/datasets/holdout-v5/split-npmrc-steal/package.json +5 -0
- package/datasets/holdout-v5/split-npmrc-steal/reader.js +8 -0
- package/datasets/holdout-v5/split-npmrc-steal/sender.js +17 -0
- package/datasets/holdout-v5/three-hop-chain/package.json +5 -0
- package/datasets/holdout-v5/three-hop-chain/reader.js +8 -0
- package/datasets/holdout-v5/three-hop-chain/sender.js +11 -0
- package/datasets/holdout-v5/three-hop-chain/transform.js +3 -0
- package/package.json +1 -1
- package/src/commands/evaluate.js +191 -31
- package/src/index.js +20 -1
- package/src/response/playbooks.js +5 -0
- package/src/rules/index.js +13 -0
- package/src/scanner/module-graph.js +883 -0
- package/tmp-summary.js +24 -0
- 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));
|
package/tmp-test-pack.js
ADDED
|
@@ -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
|
+
}
|