muaddib-scanner 2.11.22 → 2.11.23

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 CHANGED
@@ -275,7 +275,7 @@ With pre-commit framework:
275
275
  ```yaml
276
276
  repos:
277
277
  - repo: https://github.com/DNSZLSK/muad-dib
278
- rev: v2.11.22
278
+ rev: v2.11.23
279
279
  hooks:
280
280
  - id: muaddib-scan
281
281
  ```
@@ -296,7 +296,7 @@ repos:
296
296
  | **FPR** (Benign random, v2.10.95 measure) | **7.0%** (14/200) | 200 random npm packages, stratified sampling |
297
297
  | **ADR** (Adversarial + Holdout) | **96.3%** (103/107) | 67 adversarial + 40 holdout (107 available on disk), global threshold=20 |
298
298
 
299
- **3586 tests** across 93 files. **234 rules** (229 RULES + 5 PARANOID).
299
+ **3594 tests** across 93 files. **234 rules** (229 RULES + 5 PARANOID).
300
300
 
301
301
  > **ML retrain methodology (v2.10.51):**
302
302
  > - Ground truth: 377 confirmed_malicious via auto-labeler (OSSF malicious-packages, GitHub Advisory Database, npm registry takedown correlation)
@@ -344,7 +344,7 @@ npm test
344
344
 
345
345
  ### Testing
346
346
 
347
- - **3586 tests** across 93 modular test files
347
+ - **3594 tests** across 93 modular test files
348
348
  - **56 fuzz tests** - Malformed inputs, ReDoS, unicode, binary
349
349
  - **Datadog 17K benchmark** - 14,587 confirmed malware samples (in-scope)
350
350
  - **Ground truth validation** - 67 real-world attacks (93.85% TPR@3, 86.2% TPR@20 — v2.10.95 measure)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muaddib-scanner",
3
- "version": "2.11.22",
3
+ "version": "2.11.23",
4
4
  "description": "Supply-chain threat detection & response for npm & PyPI/Python",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -703,6 +703,95 @@ function mcpServerEnvAccess(result, meta) {
703
703
  return true;
704
704
  }
705
705
 
706
+ // ============================================================================
707
+ // Feature 10 — vendor_cli_sdk (v2.11.23, audit week3 cluster, 96 FP)
708
+ // ============================================================================
709
+ //
710
+ // Targets the largest residual FP cluster from the audit 2026-05-week3
711
+ // (96 entries, 33.6% of FP): legitimate vendor / community CLIs and SDKs
712
+ // that fire `credential_regex_harvest` + `env_access` on their OWN
713
+ // in-package credential handling (Stripe checkout, OAuth-PKCE, bearer
714
+ // tokens to vendor APIs, .env template scaffolding). Examples observed:
715
+ // @nocobase/cli-v1, @posterly/cli, @super-hands/cli, codeapp-js-cli
716
+ // (Microsoft Power Apps), nodebb-plugin-flawless-donations (Stripe),
717
+ // @aiyiran/myclaw (Chinese OpenClaw wrapper), usegrain (scaffolder),
718
+ // @tapestry-mud/cli, db-model-router, etc.
719
+ //
720
+ // Discriminator vs vendor-impersonating malware: SANDWORM_MODE droppers
721
+ // (a) typically have no `bin` entry (they install via lifecycle hook,
722
+ // not user-invoked CLI), (b) emit `mcp_config_injection` (F9 catches
723
+ // those), (c) cite credential file paths (.npmrc / .ssh / .aws), (d)
724
+ // emit third-party exfil threats. F10's conjunction requires NONE of
725
+ // these and additionally requires a vendor identity hint (homepage or
726
+ // scoped name).
727
+
728
+ function _f10HasBinEntry(meta) {
729
+ const bin = meta && meta.registryMeta && meta.registryMeta.bin;
730
+ if (!bin) return false;
731
+ if (typeof bin === 'string' && bin.trim().length > 0) return true;
732
+ if (typeof bin === 'object' && Object.keys(bin).length > 0) return true;
733
+ return false;
734
+ }
735
+
736
+ function _f10HasVendorIdentity(meta) {
737
+ if (!meta) return false;
738
+ if (getHomepageHost(meta)) return true;
739
+ const name = meta.name && String(meta.name);
740
+ if (name && name.startsWith('@') && name.includes('/')) return true;
741
+ return false;
742
+ }
743
+
744
+ /**
745
+ * Feature 10 — TRUE iff the package looks structurally like a legitimate
746
+ * vendor / community CLI / SDK whose credential-handling threats are
747
+ * intrinsic to its functionality, not an exfil vector.
748
+ *
749
+ * Conjunction of 7 conditions (see file header for SANDWORM_MODE
750
+ * discriminator rationale):
751
+ *
752
+ * C1 has `bin` entry — CLI signal
753
+ * C2 credential_regex_harvest OR env_access fires
754
+ * C3 no `mcp_config_injection` — F9 catches MCP installers
755
+ * C4 no install lifecycle hook — legit CLIs are opt-in
756
+ * C5 no third-party exfil threat (15 types)
757
+ * C6 no credential file path (.npmrc/.ssh/.aws) in any threat message
758
+ * C7 vendor identity present (homepage host OR scoped @vendor/name)
759
+ *
760
+ * Cap value 35 (CRITICAL → MEDIUM-HIGH boundary). Reuses the F9 constants
761
+ * F9_EXFIL_TYPES and F9_CREDENTIAL_FILE_RE for C5/C6.
762
+ *
763
+ * Covers up to 96 FP (33.6% of audit week3 FP corpus). Estimated effective
764
+ * coverage 60-75 after the conjunction filters (some week3 entries lack
765
+ * a bin field, e.g. design-system asset packages — those fall under F1
766
+ * `bundle_without_install_scripts` instead).
767
+ */
768
+ function vendorCliSdk(result, meta) {
769
+ // C1 — has bin entry
770
+ if (!_f10HasBinEntry(meta)) return false;
771
+ const threats = (result && result.threats) || [];
772
+ if (threats.length === 0) return false;
773
+ // C2 — at least one credential-noise threat (the FP source)
774
+ const hasCredentialNoise = threats.some(t =>
775
+ t.type === 'credential_regex_harvest' ||
776
+ t.type === 'env_access' ||
777
+ t.type === 'env_charcode_reconstruction' ||
778
+ t.type === 'credential_tampering'
779
+ );
780
+ if (!hasCredentialNoise) return false;
781
+ // C3 — no mcp_config_injection (F9 territory)
782
+ if (threats.some(t => t.type === 'mcp_config_injection')) return false;
783
+ // C4 — no install lifecycle hook
784
+ if (hasLifecycleScripts(meta)) return false;
785
+ // C5 + C6 — scan threats for exfil signal and credential-file mentions
786
+ for (const t of threats) {
787
+ if (F9_EXFIL_TYPES.has(t.type)) return false; // C5
788
+ if (F9_CREDENTIAL_FILE_RE.test(String(t.message || ''))) return false; // C6
789
+ }
790
+ // C7 — vendor identity
791
+ if (!_f10HasVendorIdentity(meta)) return false;
792
+ return true;
793
+ }
794
+
706
795
  /**
707
796
  * Feature 8 — TRUE iff the package declares at least one install
708
797
  * lifecycle script AND the scan shows no network egress capability
@@ -855,6 +944,8 @@ function extractFeatures(result, meta) {
855
944
 
856
945
  // --- v2.11.22 Feature 9 (audit week3 cluster — 25 FP) ---
857
946
  features.mcp_server_env_access = mcpServerEnvAccess(result, meta) ? 1 : 0;
947
+ // --- v2.11.23 Feature 10 (audit week3 cluster — up to 96 FP) ---
948
+ features.vendor_cli_sdk = vendorCliSdk(result, meta) ? 1 : 0;
858
949
 
859
950
  return features;
860
951
  }
@@ -934,5 +1025,6 @@ module.exports = {
934
1025
  obfuscationWithoutVector,
935
1026
  placeholderAntiDepConfusion,
936
1027
  installScriptNoNetworkEgress,
937
- mcpServerEnvAccess
1028
+ mcpServerEnvAccess,
1029
+ vendorCliSdk
938
1030
  };
package/src/scoring.js CHANGED
@@ -1484,6 +1484,7 @@ const {
1484
1484
  obfuscationWithoutVector,
1485
1485
  placeholderAntiDepConfusion,
1486
1486
  mcpServerEnvAccess,
1487
+ vendorCliSdk,
1487
1488
  } = require('./ml/feature-extractor.js');
1488
1489
 
1489
1490
  /**
@@ -1538,6 +1539,10 @@ function applyContextualFPCaps(result, pkgMeta) {
1538
1539
  if (obfuscationWithoutVector(result)) {
1539
1540
  applied.push({ feature: 'obfuscation_without_vector', cap: 35 });
1540
1541
  }
1542
+ // F10: legit vendor CLI/SDK with intrinsic credential handling → MAX 35
1543
+ if (vendorCliSdk(result, meta)) {
1544
+ applied.push({ feature: 'vendor_cli_sdk', cap: 35 });
1545
+ }
1541
1546
  // F5: typosquat on scoped package → suppress typosquat points
1542
1547
  if (typosquatScopedPackage(result, meta)) {
1543
1548
  applied.push({ feature: 'typosquat_scoped_package', cap: -1 });