muaddib-scanner 2.10.91 → 2.10.93
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/README.md +3 -3
- package/package.json +1 -1
- package/src/monitor/classify.js +7 -1
- package/src/response/playbooks.js +5 -0
- package/src/rules/index.js +12 -0
- package/src/scanner/ast-detectors/constants.js +10 -1
- package/src/scanner/ast-detectors/handle-post-walk.js +20 -0
- package/src/scanner/ast.js +4 -0
- package/src/scanner/package.js +33 -3
- package/src/scoring.js +6 -1
package/README.md
CHANGED
|
@@ -292,7 +292,7 @@ repos:
|
|
|
292
292
|
| **FPR** (Benign random) | **7.5%** (15/200) | 200 random npm packages, stratified sampling |
|
|
293
293
|
| **ADR** (Adversarial + Holdout) | **96.3%** (103/107) | 67 adversarial + 40 holdout (107 available on disk), global threshold=20 |
|
|
294
294
|
|
|
295
|
-
**
|
|
295
|
+
**3230 tests** across 66 files. **207 rules** (202 RULES + 5 PARANOID).
|
|
296
296
|
|
|
297
297
|
> **ML retrain methodology (v2.10.51):**
|
|
298
298
|
> - Ground truth: 377 confirmed_malicious via auto-labeler (OSSF malicious-packages, GitHub Advisory Database, npm registry takedown correlation)
|
|
@@ -340,7 +340,7 @@ npm test
|
|
|
340
340
|
|
|
341
341
|
### Testing
|
|
342
342
|
|
|
343
|
-
- **
|
|
343
|
+
- **3230 tests** across 66 modular test files
|
|
344
344
|
- **56 fuzz tests** - Malformed inputs, ReDoS, unicode, binary
|
|
345
345
|
- **Datadog 17K benchmark** - 14,587 confirmed malware samples (in-scope)
|
|
346
346
|
- **Ground truth validation** - 67 real-world attacks (93.75% TPR@3, 85.9% TPR@20)
|
|
@@ -362,7 +362,7 @@ npm test
|
|
|
362
362
|
- [Evaluation Methodology](docs/EVALUATION_METHODOLOGY.md) - Experimental protocol, holdout scores
|
|
363
363
|
- [Threat Model](docs/threat-model.md) - What MUAD'DIB detects and doesn't detect
|
|
364
364
|
- [Adversarial Evaluation](ADVERSARIAL.md) - Red team samples and ADR results
|
|
365
|
-
- [Security Policy](SECURITY.md) - Detection rules reference (
|
|
365
|
+
- [Security Policy](SECURITY.md) - Detection rules reference (207 rules)
|
|
366
366
|
- [Security Audit](docs/SECURITY_AUDIT.md) - Bypass validation report
|
|
367
367
|
- [FP Analysis](docs/EVALUATION.md) - Historical false positive analysis
|
|
368
368
|
|
package/package.json
CHANGED
package/src/monitor/classify.js
CHANGED
|
@@ -57,7 +57,13 @@ const HIGH_CONFIDENCE_MALICE_TYPES = new Set([
|
|
|
57
57
|
'npm_token_steal', // exec("npm config get _authToken") (CanisterWorm findNpmTokens)
|
|
58
58
|
'root_filesystem_wipe', // rm -rf / (CanisterWorm kamikaze.sh wiper T1485)
|
|
59
59
|
'proc_mem_scan', // /proc/mem scanning (TeamPCP Trivy credential stealer)
|
|
60
|
-
'trusted_new_unknown_dependency'
|
|
60
|
+
'trusted_new_unknown_dependency', // TRUSTED package added unknown/new (<7d) dependency (account takeover)
|
|
61
|
+
// v2.10.89: Security review findings — always malicious regardless of lifecycle
|
|
62
|
+
'curl_env_exfil', // curl/wget + env/base64 in lifecycle (exfiltration)
|
|
63
|
+
'function_constructor_require', // new Function.constructor("require") (RCE evasion)
|
|
64
|
+
'newsletter_auto_follow', // Baileys WhatsApp newsletter hijack
|
|
65
|
+
// v2.10.93: Security review 2026-04-10→17 findings
|
|
66
|
+
'self_destruct_eval' // dynamic exec + unlink __filename (csec anti-forensics)
|
|
61
67
|
]);
|
|
62
68
|
|
|
63
69
|
// Lifecycle compound types that indicate real malicious intent beyond a simple postinstall
|
|
@@ -881,6 +881,11 @@ const PLAYBOOKS = {
|
|
|
881
881
|
lifecycle_env_exfil:
|
|
882
882
|
'CRITIQUE: Lifecycle hook + exfiltration env via curl/wget a l\'installation. ' +
|
|
883
883
|
'Machine compromise si deja installe. Rotation immediate de TOUS les secrets.',
|
|
884
|
+
|
|
885
|
+
self_destruct_eval:
|
|
886
|
+
'CRITIQUE: Execution dynamique de code (eval/new Function/Module._compile) + auto-suppression du fichier execute (unlinkSync/renameSync sur __filename). ' +
|
|
887
|
+
'Pattern anti-forensique professionnel: le malware execute son payload obfusque puis efface ses traces. Campagne csec-crypto-toolkit (avril 2026) exfiltre .env, GITHUB_TOKEN, NPM_TOKEN, AWS/SSH keys vers csec-supply-chain-attack.vercel.app, puis unlinkSync(__filename). ' +
|
|
888
|
+
'Machine compromise si deja installe. Rotation immediate de TOUS les secrets (.env, tokens CI/CD, cles SSH). Verifier les .env des repertoires parents jusqu\'a 6 niveaux. Supprimer le package.',
|
|
884
889
|
};
|
|
885
890
|
|
|
886
891
|
function getPlaybook(threatType) {
|
package/src/rules/index.js
CHANGED
|
@@ -2264,6 +2264,18 @@ const RULES = {
|
|
|
2264
2264
|
],
|
|
2265
2265
|
mitre: 'T1496'
|
|
2266
2266
|
},
|
|
2267
|
+
self_destruct_eval: {
|
|
2268
|
+
id: 'MUADDIB-AST-089',
|
|
2269
|
+
name: 'Self-Destructing Dynamic Execution',
|
|
2270
|
+
severity: 'CRITICAL',
|
|
2271
|
+
confidence: 'high',
|
|
2272
|
+
description: 'Execution dynamique de code (eval/new Function/Module._compile) combinee a la suppression ou renommage du fichier en cours d\'execution (unlinkSync/rmSync/renameSync sur __filename, module.filename, ou require.main.filename). Anti-forensics: le malware execute son payload obfusque puis efface ses traces. Aucun package legitime ne detruit son propre source apres execution de code dynamique. Campagne csec-crypto-toolkit (avril 2026): XOR(OrDeR_7077)+base64+new Function, exfiltre .env/.ssh/.npmrc vers csec-supply-chain-attack.vercel.app, puis unlinkSync(__filename).',
|
|
2273
|
+
references: [
|
|
2274
|
+
'https://attack.mitre.org/techniques/T1070.004/',
|
|
2275
|
+
'https://attack.mitre.org/techniques/T1140/'
|
|
2276
|
+
],
|
|
2277
|
+
mitre: 'T1070.004'
|
|
2278
|
+
},
|
|
2267
2279
|
version_99_preinstall: {
|
|
2268
2280
|
id: 'MUADDIB-PKG-019',
|
|
2269
2281
|
name: 'Dependency Confusion Version Indicator',
|
|
@@ -144,7 +144,12 @@ const GIT_HOOKS = [
|
|
|
144
144
|
|
|
145
145
|
// Suspicious C2/exfiltration domains (HIGH severity)
|
|
146
146
|
const SUSPICIOUS_DOMAINS_HIGH = [
|
|
147
|
+
// OAST (Out-of-band Application Security Testing) callback domains.
|
|
148
|
+
// Legitimate only for authorized pentesting — in published npm packages,
|
|
149
|
+
// these are always reconnaissance/exfiltration callbacks (dependency confusion, SSRF confirmation).
|
|
147
150
|
'oastify.com', 'oast.fun', 'oast.me', 'oast.live',
|
|
151
|
+
'oast.online', 'oast.pro',
|
|
152
|
+
'interact.sh', 'projectdiscovery.io',
|
|
148
153
|
'burpcollaborator.net', 'webhook.site', 'pipedream.net',
|
|
149
154
|
'requestbin.com', 'hookbin.com', 'canarytokens.com',
|
|
150
155
|
// GlassWorm C2 IPs (mars 2026)
|
|
@@ -167,7 +172,11 @@ const SUSPICIOUS_DOMAINS_HIGH = [
|
|
|
167
172
|
'minhdong.site', // Facebook credential proxy (fca-mmtat)
|
|
168
173
|
'ltidi.storage.googleapis.com', // KuCoin dependency confusion payload
|
|
169
174
|
'jsonkeeper.com', // Robert King campaign C2 dead drop
|
|
170
|
-
'npoint.io'
|
|
175
|
+
'npoint.io', // Robert King campaign C2 dead drop
|
|
176
|
+
// v2.10.93: Security review 2026-04-10→17 findings
|
|
177
|
+
'csec-supply-chain-attack.vercel.app', // csec-crypto-toolkit credential stealer C2 (XOR+Function+unlink)
|
|
178
|
+
'files.giftedtech.co.ke', // silva-baileys V3 newsletter JID remote loader
|
|
179
|
+
'phish.sh' // JET/SkipTheDishes depconf webhook exfil
|
|
171
180
|
];
|
|
172
181
|
|
|
173
182
|
// Suspicious tunnel/proxy domains (MEDIUM severity)
|
|
@@ -23,6 +23,26 @@ function handlePostWalk(ctx) {
|
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
// v2.10.93: csec-style credential stealer — dynamic exec + self-deletion of __filename.
|
|
27
|
+
// csec-crypto-toolkit (avril 2026): XOR-obfuscated setup.js that exfiltrates
|
|
28
|
+
// .env/.ssh/.npmrc to csec-supply-chain-attack.vercel.app, then unlinks itself
|
|
29
|
+
// and replaces package.json with package.md to erase traces.
|
|
30
|
+
// Threat model: the combination of (a) dynamic code execution from a variable
|
|
31
|
+
// (eval/new Function/Module._compile) with (b) self-deletion of the currently
|
|
32
|
+
// executing file is a professional anti-forensics signature. Legitimate packages
|
|
33
|
+
// do not destroy their own source after executing obfuscated code.
|
|
34
|
+
// Suppressed in dist/build to avoid bundler self-cleanup FPs (rare but possible).
|
|
35
|
+
if (ctx.hasDynamicExec && ctx.hasSelfDelete) {
|
|
36
|
+
const isDistFile = /^(dist|build|out|output)[/\\]/i.test(ctx.relFile) ||
|
|
37
|
+
/\.(bundle|min)\.js$/i.test(ctx.relFile);
|
|
38
|
+
ctx.threats.push({
|
|
39
|
+
type: 'self_destruct_eval',
|
|
40
|
+
severity: isDistFile ? 'HIGH' : 'CRITICAL',
|
|
41
|
+
message: 'Anti-forensics: dynamic code execution (eval/new Function/Module._compile) + self-deletion of __filename — malware staging with trace removal (csec pattern).',
|
|
42
|
+
file: ctx.relFile
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
26
46
|
// SANDWORM_MODE R7: env harvesting = Object.entries/keys/values(process.env) + sensitive pattern in file
|
|
27
47
|
if (ctx.hasEnvEnumeration && ctx.hasEnvHarvestPattern && ctx.hasNetworkCallInFile) {
|
|
28
48
|
ctx.threats.push({
|
package/src/scanner/ast.js
CHANGED
|
@@ -140,6 +140,10 @@ function analyzeFile(content, filePath, basePath) {
|
|
|
140
140
|
hasTempFileExec: false,
|
|
141
141
|
hasFileDelete: false,
|
|
142
142
|
hasDevShmInContent: /\/dev\/shm\b/.test(content),
|
|
143
|
+
// v2.10.93: csec-style self-deletion — unlink/rename of __filename targets the
|
|
144
|
+
// executing file itself. Distinct from hasFileDelete (any file). Combined with
|
|
145
|
+
// hasDynamicExec in a compound to flag anti-forensics obfuscated stealers.
|
|
146
|
+
hasSelfDelete: /\b(?:unlinkSync|unlink|rmSync|renameSync|rm)\s*\(\s*(?:__filename|module\.filename|require\.main\.filename)\b/.test(content),
|
|
143
147
|
// SANDWORM_MODE P2: env harvesting co-occurrence
|
|
144
148
|
hasEnvEnumeration: false, // Object.entries/keys/values(process.env)
|
|
145
149
|
hasEnvHarvestPattern: /\b(KEY|SECRET|TOKEN|PASSWORD|CREDENTIAL|NPM|AWS|SSH|WEBHOOK)\b/.test(content),
|
package/src/scanner/package.js
CHANGED
|
@@ -324,11 +324,41 @@ async function scanPackageJson(targetPath) {
|
|
|
324
324
|
/\/\/192\.168\.\d{1,3}\.\d{1,3}[:/]/,
|
|
325
325
|
/\/\/172\.(1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}[:/]/
|
|
326
326
|
].some(p => p.test(urlLower));
|
|
327
|
+
|
|
328
|
+
// v2.10.93: External raw tarball URL as dep — ltidi chain attack pattern.
|
|
329
|
+
// GitHub/GitLab/Bitbucket release tarballs are legitimate; everything else
|
|
330
|
+
// pointing to .tgz/.tar.gz/.zip is payload delivery via third-party storage
|
|
331
|
+
// (GCS, S3, CDNs). The stub package bypasses all lifecycle/obfuscation scanners
|
|
332
|
+
// because the malicious code lives in the external tarball fetched at install.
|
|
333
|
+
const isTarballUrl = /\.(tgz|tar\.gz|tar\.bz2|zip)(\?|#|$)/.test(urlLower);
|
|
334
|
+
const isLegitTarballHost = [
|
|
335
|
+
/\/\/github\.com\//,
|
|
336
|
+
/\/\/codeload\.github\.com\//,
|
|
337
|
+
/\/\/objects\.githubusercontent\.com\//,
|
|
338
|
+
/\/\/gitlab\.com\//,
|
|
339
|
+
/\/\/bitbucket\.org\//,
|
|
340
|
+
/\/\/registry\.npmjs\.org\//,
|
|
341
|
+
/\/\/registry\.yarnpkg\.com\//
|
|
342
|
+
].some(p => p.test(urlLower));
|
|
343
|
+
const isExternalTarball = isTarballUrl && !isLegitTarballHost;
|
|
344
|
+
|
|
345
|
+
let severity;
|
|
346
|
+
let note;
|
|
347
|
+
if (isSuspicious) {
|
|
348
|
+
severity = 'CRITICAL';
|
|
349
|
+
note = ' (tunnel/private/localhost)';
|
|
350
|
+
} else if (isExternalTarball) {
|
|
351
|
+
severity = 'CRITICAL';
|
|
352
|
+
note = ' (external raw tarball on third-party host — chain attack pattern, npm registry audit bypass)';
|
|
353
|
+
} else {
|
|
354
|
+
severity = 'HIGH';
|
|
355
|
+
note = ' (unusual, verify source)';
|
|
356
|
+
}
|
|
357
|
+
|
|
327
358
|
threats.push({
|
|
328
359
|
type: 'dependency_url_suspicious',
|
|
329
|
-
severity
|
|
330
|
-
message: `Dependency "${depName}" uses HTTP URL: ${depVersion}
|
|
331
|
-
(isSuspicious ? ' (tunnel/private/localhost)' : ' (unusual, verify source)'),
|
|
360
|
+
severity,
|
|
361
|
+
message: `Dependency "${depName}" uses HTTP URL: ${depVersion}${note}`,
|
|
332
362
|
file: 'package.json'
|
|
333
363
|
});
|
|
334
364
|
}
|
package/src/scoring.js
CHANGED
|
@@ -253,6 +253,7 @@ const DIST_EXEMPT_TYPES = new Set([
|
|
|
253
253
|
'npm_publish_worm', // exec("npm publish") (worm propagation)
|
|
254
254
|
'curl_env_exfil', // curl/wget env exfil in lifecycle (always malicious)
|
|
255
255
|
'function_constructor_require', // new Function.constructor("require") (always malicious)
|
|
256
|
+
'self_destruct_eval', // dynamic exec + unlink __filename (csec anti-forensics)
|
|
256
257
|
// Dangerous shell commands in dist/ are real threats, never bundler output
|
|
257
258
|
'dangerous_exec',
|
|
258
259
|
// Compound scoring rules — co-occurrence signals, never FP
|
|
@@ -966,7 +967,11 @@ function calculateRiskScore(deduped, intentResult) {
|
|
|
966
967
|
);
|
|
967
968
|
const _hasHC = deduped.some(t => HIGH_CONFIDENCE_MALICE_TYPES.has(t.type));
|
|
968
969
|
const _hasCompound = deduped.some(t => t.compound === true);
|
|
969
|
-
|
|
970
|
+
// v2.10.89: staged_payload + suspicious_domain(HIGH) = confirmed C2 eval, bypass MT-1 cap
|
|
971
|
+
// json-spacer, reactvora: eval(data.content) from jsonkeeper.com is always malicious
|
|
972
|
+
const _hasStagedC2 = deduped.some(t => t.type === 'staged_payload') &&
|
|
973
|
+
deduped.some(t => t.type === 'suspicious_domain' && t.severity === 'HIGH');
|
|
974
|
+
if (!_hasLifecycle && !_hasHC && !_hasCompound && !_hasStagedC2) {
|
|
970
975
|
riskScore = Math.min(riskScore, 35);
|
|
971
976
|
}
|
|
972
977
|
|