muaddib-scanner 2.11.169 → 2.11.170
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/package.json
CHANGED
package/src/monitor/classify.js
CHANGED
|
@@ -164,6 +164,24 @@ function hasIOCMatch(result) {
|
|
|
164
164
|
return result.threats.some(t => IOC_MATCH_TYPES.has(t.type));
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
// Stealth-exec + obfuscation compound: a detached/silent background process
|
|
168
|
+
// (silent_stealth_process AST-092 / detached_process AST-012) shipped ALONGSIDE
|
|
169
|
+
// obfuscator.io-mangled code (js_obfuscation_pattern ENTROPY-003, the `_0x*` hex
|
|
170
|
+
// signature). The AND is quasi-never legitimate: legit process managers (pm2,
|
|
171
|
+
// Platformatic Watt, the mongodb driver) detach but do NOT obfuscate, and legit
|
|
172
|
+
// obfuscated bundles do NOT silently detach a process. It is the ATO / CI-supply-
|
|
173
|
+
// chain fingerprint (@asyncapi/specs Miasma 2026-07-14; @umudik/lotaru) whose raw
|
|
174
|
+
// score reputation attenuation crushes to LOW on an established package.
|
|
175
|
+
// Measured on the 128,429-scan production ledger (scripts/mine-fpr): exactly two
|
|
176
|
+
// packages match — BOTH confirmed malicious — so wiring this as a reputation-
|
|
177
|
+
// independent webhook bypass adds ZERO benign false positives (ΔFPR = 0).
|
|
178
|
+
function hasStealthObfuscationCompound(result) {
|
|
179
|
+
if (!result || !result.threats) return false;
|
|
180
|
+
const types = new Set(result.threats.map(t => t.type));
|
|
181
|
+
const stealthExec = types.has('silent_stealth_process') || types.has('detached_process');
|
|
182
|
+
return stealthExec && types.has('js_obfuscation_pattern');
|
|
183
|
+
}
|
|
184
|
+
|
|
167
185
|
function hasTyposquat(result) {
|
|
168
186
|
if (!result || !result.threats) return false;
|
|
169
187
|
return result.threats.some(t => t.type === 'typosquat_detected' || t.type === 'pypi_typosquat_detected');
|
|
@@ -496,6 +514,7 @@ module.exports = {
|
|
|
496
514
|
hasHighOrCritical,
|
|
497
515
|
hasHighConfidenceThreat,
|
|
498
516
|
hasIOCMatch,
|
|
517
|
+
hasStealthObfuscationCompound,
|
|
499
518
|
hasTyposquat,
|
|
500
519
|
hasLifecycleWithIntent,
|
|
501
520
|
isSuspectClassification,
|
package/src/monitor/queue.js
CHANGED
|
@@ -67,6 +67,7 @@ const {
|
|
|
67
67
|
const {
|
|
68
68
|
isSuspectClassification,
|
|
69
69
|
hasHighConfidenceThreat,
|
|
70
|
+
hasStealthObfuscationCompound,
|
|
70
71
|
isSandboxEnabled,
|
|
71
72
|
isCanaryEnabled,
|
|
72
73
|
recordError,
|
|
@@ -1491,8 +1492,15 @@ async function scanPackage(name, version, ecosystem, tarballUrl, registryMeta, s
|
|
|
1491
1492
|
// Adjusts score for webhook decision without mutating persisted alert data.
|
|
1492
1493
|
// High-confidence malice types BYPASS reputation — supply-chain compromise protection.
|
|
1493
1494
|
// Reuses npmRegistryMeta fetched earlier (ML Phase 2a) — no duplicate HTTP call.
|
|
1495
|
+
// Malice bypass: don't let reputation attenuation re-crush a confirmed-malice
|
|
1496
|
+
// detection (the scoring.js Track R floor already lifted it). The stealth+obf
|
|
1497
|
+
// compound is included here for the SAME reason as _hasConfirmedMalice — its
|
|
1498
|
+
// component types aren't individually high-confidence, so hasHighConfidenceThreat
|
|
1499
|
+
// alone misses the asyncapi/specs Miasma ATO and would re-attenuate 20 → 2 for the
|
|
1500
|
+
// webhook/ranking decision.
|
|
1501
|
+
const _maliceBypass = hasHighConfidenceThreat(result) || hasStealthObfuscationCompound(result);
|
|
1494
1502
|
let adjustedResult = result;
|
|
1495
|
-
if (ecosystem === 'npm' && !
|
|
1503
|
+
if (ecosystem === 'npm' && !_maliceBypass) {
|
|
1496
1504
|
try {
|
|
1497
1505
|
const reputationFactor = computeReputationFactor(npmRegistryMeta);
|
|
1498
1506
|
if (reputationFactor !== 1.0) {
|
|
@@ -1507,8 +1515,8 @@ async function scanPackage(name, version, ecosystem, tarballUrl, registryMeta, s
|
|
|
1507
1515
|
} catch (err) {
|
|
1508
1516
|
console.error(`[MONITOR] Reputation error for ${name}: ${err.message}`);
|
|
1509
1517
|
}
|
|
1510
|
-
} else if (ecosystem === 'npm' &&
|
|
1511
|
-
console.log(`[MONITOR] REPUTATION BYPASS: ${name} has
|
|
1518
|
+
} else if (ecosystem === 'npm' && _maliceBypass) {
|
|
1519
|
+
console.log(`[MONITOR] REPUTATION BYPASS: ${name} has confirmed-malice signal — using raw score`);
|
|
1512
1520
|
}
|
|
1513
1521
|
|
|
1514
1522
|
// Record daily alert with post-reputation score for top suspects ranking.
|
package/src/monitor/webhook.js
CHANGED
|
@@ -55,6 +55,7 @@ const {
|
|
|
55
55
|
const {
|
|
56
56
|
HIGH_CONFIDENCE_MALICE_TYPES,
|
|
57
57
|
hasIOCMatch,
|
|
58
|
+
hasStealthObfuscationCompound,
|
|
58
59
|
hasHighOrCritical,
|
|
59
60
|
formatErrorBreakdown
|
|
60
61
|
} = require('./classify.js');
|
|
@@ -128,6 +129,17 @@ function shouldSendWebhook(result, sandboxResult, mlResult) {
|
|
|
128
129
|
if (mlResult && mlResult.prediction !== 'clean' && mlResult.probability >= 0.90
|
|
129
130
|
&& hasHighOrCritical(result)) return true;
|
|
130
131
|
|
|
132
|
+
// 1c. Stealth-exec + obfuscation compound — reputation-independent bypass.
|
|
133
|
+
// A detached/silent background process shipped with obfuscator.io-mangled code
|
|
134
|
+
// is the account-takeover / CI-supply-chain fingerprint. On an established
|
|
135
|
+
// package the reputationFactor (0.1x) crushes the raw score below `threshold`
|
|
136
|
+
// and silences the webhook: @asyncapi/specs@6.11.2 (Miasma, 2026-07-14) scored
|
|
137
|
+
// globalRiskScore 49 → riskScore 4/LOW → suppressed, despite a CRITICAL
|
|
138
|
+
// silent_stealth_process finding. This compound is quasi-never benign (measured
|
|
139
|
+
// ΔFPR=0 on the 128K-scan production ledger), so it fires regardless of
|
|
140
|
+
// reputation attenuation — same rationale as the IOC bypass above.
|
|
141
|
+
if (hasStealthObfuscationCompound(result)) return true;
|
|
142
|
+
|
|
131
143
|
// 2. Real sandbox detection (> 30) — always send
|
|
132
144
|
if (sandboxScore > 30) return true;
|
|
133
145
|
|
package/src/scoring.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
const { getRule } = require('./rules/index.js');
|
|
21
|
-
const { HIGH_CONFIDENCE_MALICE_TYPES } = require('./monitor/classify.js');
|
|
21
|
+
const { HIGH_CONFIDENCE_MALICE_TYPES, hasStealthObfuscationCompound } = require('./monitor/classify.js');
|
|
22
22
|
// v2.10.73 P1: bundle detection helpers — extended bundle path regex + veto check
|
|
23
23
|
const { BUNDLE_PATH_RE, hasBundleVetoSignal } = require('./shared/bundle-detect.js');
|
|
24
24
|
|
|
@@ -1982,7 +1982,15 @@ function _hasConfirmedMalice(threats) {
|
|
|
1982
1982
|
const hasCompound = threats.some(t => t.compound === true);
|
|
1983
1983
|
const hasStagedC2 = threats.some(t => t.type === 'staged_payload') &&
|
|
1984
1984
|
threats.some(t => t.type === 'suspicious_domain' && t.severity === 'HIGH');
|
|
1985
|
-
|
|
1985
|
+
// Stealth-exec + obfuscation compound (asyncapi/specs Miasma ATO 2026-07-14): a
|
|
1986
|
+
// detached/silent background process (silent_stealth_process / detached_process) shipped
|
|
1987
|
+
// with obfuscator.io-mangled code (js_obfuscation_pattern). Quasi-never benign, BUT the
|
|
1988
|
+
// component types are individually NOT in HIGH_CONFIDENCE_MALICE_TYPES (they fire on
|
|
1989
|
+
// pm2 / mongodb / anti-piracy bundles) so only the COMBINATION is confirmed-malice.
|
|
1990
|
+
// Measured ΔFPR=0 on the 130K-scan production ledger (2 matches, both malicious). Shared
|
|
1991
|
+
// predicate with the monitor webhook + reputation bypass (classify.hasStealthObfuscationCompound).
|
|
1992
|
+
const hasStealthObf = hasStealthObfuscationCompound({ threats });
|
|
1993
|
+
return hasHC || hasCompound || hasStagedC2 || hasStealthObf;
|
|
1986
1994
|
}
|
|
1987
1995
|
|
|
1988
1996
|
// P3 (TeamPCP / Mini Shai-Hulud hardening): broader malice predicate used to
|