muaddib-scanner 2.11.23 → 2.11.24

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.23
278
+ rev: v2.11.24
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
- **3594 tests** across 93 files. **234 rules** (229 RULES + 5 PARANOID).
299
+ **3602 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
- - **3594 tests** across 93 modular test files
347
+ - **3602 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.23",
3
+ "version": "2.11.24",
4
4
  "description": "Supply-chain threat detection & response for npm & PyPI/Python",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -792,6 +792,148 @@ function vendorCliSdk(result, meta) {
792
792
  return true;
793
793
  }
794
794
 
795
+ // ============================================================================
796
+ // Feature 11 — ai_agent_bot (v2.11.24, audit week3 cluster, 54 FP)
797
+ // ============================================================================
798
+ //
799
+ // Targets the third cluster from the audit 2026-05-week3 (54 entries,
800
+ // 18.9 % of FP): packages that ARE themselves multi-provider AI agents,
801
+ // orchestrators, chatbots, or IM⇄AI bridges. Examples: gm-skill (AI coding
802
+ // harness), codexmate (multi-provider orchestrator), lazyclaw (terminal
803
+ // multi-LLM CLI), linco-connect (WeChat→Claude bridge), natureco-cli
804
+ // (WhatsApp+Telegram bot), multis (Telegram chatbot), @aitne-sh/aitne
805
+ // (personal AI daemon), @jhizzard/termdeck (browser term mux with AI),
806
+ // triflux (Claude Code router), opuscode (Claude config wizard).
807
+ //
808
+ // These packages legitimately fire `dangerous_call_eval` (LLM tool-use
809
+ // execute_code feature), `remote_code_load` (bun x pkg@latest fetching),
810
+ // `detached_credential_exfil` (local session token storage), and lots
811
+ // of `env_access` + `suspicious_dataflow`. F11 cannot blacklist these —
812
+ // they ARE the core capabilities. Instead the conjunction requires:
813
+ //
814
+ // - Positive AI agent identity (name/desc/keywords/deps signal)
815
+ // - Evidence the package operates on agent runtime data (touches paths
816
+ // like ~/.claude/, ~/.codex/, ~/.cursor/, etc.)
817
+ // - Absence of SANDWORM_MODE signatures: no preinstall, no
818
+ // mcp_config_injection (F9 priority), no third-party suspicious_domain,
819
+ // no credential file harvest, no binary dropper (F2 priority).
820
+ //
821
+ // Cap 35 (aligned with F10 — broader conjunction than F9).
822
+
823
+ // Agent runtime directory regex — matches references in threat messages to
824
+ // AI tool runtime paths. Both '~/.X/' and 'os.homedir() + "/.X"' patterns
825
+ // surface as substrings here.
826
+ const AGENT_RUNTIME_PATHS_RE = /[~/\\\\]\.(?:claude|codex|cursor|windsurf|continue|openclaude|openclaudia|hermes|aiflow|tdpilot|aitne|kimi|opuscode|freddie|gm-?log|gm-?skill|termdeck|relaydesk|natureco|grok|gemini|copilot|cline|aider|cody|tabnine|cursor-ai|cursorrules|claude-?desktop|claude-?code|llm[- ]?cache)\b/i;
827
+
828
+ // AI agent name regex — package name signals identity.
829
+ const AGENT_NAME_RE = /(?:^|[/_-])(?:agent|bot|chat|chatbot|claw|codex|coder|swarm|harness|brain|orchestr|orchestrator|claude|llm|hermes|aider|kimi|cline|cody|aitne|opuscode|relaydesk|termdeck|gm-skill|gm-hermes|gm-qwen|gm-thebird|gm-plugkit|relipa|triflux|protocol-proxy|codexmate|lazy?claw|natureco)(?:[_-]|$)/i;
830
+
831
+ // Keywords that signal AI agent purpose (case-insensitive).
832
+ const AGENT_KEYWORDS_SET = new Set([
833
+ 'agent', 'ai', 'llm', 'chatbot', 'bot', 'claude', 'codex',
834
+ 'cursor', 'copilot', 'ollama', 'openai', 'anthropic', 'gemini',
835
+ 'multi-llm', 'multi-provider', 'orchestrator', 'coding-agent',
836
+ 'ai-agent', 'llm-agent', 'mcp-agent'
837
+ ]);
838
+
839
+ // Description regex — matches agent purpose phrases.
840
+ const AGENT_DESC_RE = /\b(?:ai|llm|claude|codex|gemini|openai|anthropic|ollama)[ -]?(?:agent|bot|chatbot|orchestrator|harness|cli|assistant|coding[ -]?agent|gateway|relay|router|harness|workspace)\b|\bmulti[ -]?provider\b|\bcoding[ -]?agent\b|\bagent[ -]?(?:bridge|router|orchestrator)\b|telegram[ -]?(?:bot|bridge)|whatsapp[ -]?(?:bot|bridge)|wechat[ -]?(?:bot|bridge)/i;
841
+
842
+ // Dependency names that signal AI agent / bot framework usage.
843
+ const AGENT_DEPS = new Set([
844
+ '@anthropic-ai/sdk', '@anthropic-ai/claude-code', '@openai/agents', 'openai',
845
+ '@google/genai', '@google/generative-ai', 'ai', 'ollama', 'groq-sdk',
846
+ 'telegraf', 'node-telegram-bot-api', '@whiskeysockets/baileys',
847
+ 'whatsapp-web.js', 'discord.js', 'eventsource', 'node-pty',
848
+ '@anthropic-ai/bedrock-sdk', '@openai/realtime-api-beta'
849
+ ]);
850
+
851
+ function _f11HasAgentIdentity(meta) {
852
+ if (!meta) return false;
853
+ const name = String(meta.name || '');
854
+ if (AGENT_NAME_RE.test(name)) return true;
855
+ const r = (meta.registryMeta || {});
856
+ const desc = r.description || meta.description || '';
857
+ if (AGENT_DESC_RE.test(desc)) return true;
858
+ if (Array.isArray(r.keywords)) {
859
+ for (const k of r.keywords) {
860
+ if (AGENT_KEYWORDS_SET.has(String(k).toLowerCase())) return true;
861
+ }
862
+ }
863
+ const deps = r.dependencies || meta.dependencies;
864
+ if (deps && typeof deps === 'object') {
865
+ for (const d of Object.keys(deps)) {
866
+ if (AGENT_DEPS.has(d)) return true;
867
+ }
868
+ }
869
+ return false;
870
+ }
871
+
872
+ function _f11HasAgentPathReference(threats) {
873
+ for (const t of threats) {
874
+ const msg = String(t.message || '');
875
+ if (AGENT_RUNTIME_PATHS_RE.test(msg)) return true;
876
+ // Also accept the threat's file field — sometimes the path leaks via the
877
+ // file location rather than the message body.
878
+ const file = String(t.file || '');
879
+ if (AGENT_RUNTIME_PATHS_RE.test(file)) return true;
880
+ }
881
+ return false;
882
+ }
883
+
884
+ /**
885
+ * Feature 11 — TRUE iff the package self-identifies as an AI agent / bot /
886
+ * multi-LLM orchestrator AND demonstrably operates on AI tool runtime
887
+ * data (~/.claude/, ~/.codex/, ~/.cursor/, etc.) AND lacks the
888
+ * SANDWORM_MODE / vendor-impersonation signatures.
889
+ *
890
+ * Conjunction of 7 conditions:
891
+ *
892
+ * C1 AI agent identity (name|desc|keywords|deps signal)
893
+ * C2 no install lifecycle hook
894
+ * C3 no `mcp_config_injection` (F9 priority)
895
+ * C4 no `suspicious_domain` threat (third-party exfil discriminator)
896
+ * C5 no credential file path in any threat message (reuse F9 regex)
897
+ * C6 >=1 threat references an agent runtime path (positive operating signal)
898
+ * C7 no `binary_dropper` / `download_exec_binary` (F2 priority)
899
+ *
900
+ * Cap 35. Same cap as F10 (broader conjunction than F9). Reuses
901
+ * `F9_CREDENTIAL_FILE_RE` from v2.11.22.
902
+ *
903
+ * Discriminator vs malware:
904
+ * - SANDWORM droppers use preinstall/postinstall (C2 blocks).
905
+ * - MCP-impersonating malware emits mcp_config_injection (C3 → F9).
906
+ * - Exfilers have suspicious_domain (C4 blocks).
907
+ * - Binary droppers (C7 → F2 territory).
908
+ * - Credential file harvesters (C5 blocks).
909
+ *
910
+ * Covers up to 54 FP (18.9% of audit week3). Effective estimated coverage
911
+ * 30-40 (55-75%): the rest lack agent runtime path references or fire on
912
+ * suspicious_domain due to Chinese model rerouting (yingclaw pattern).
913
+ */
914
+ function aiAgentBot(result, meta) {
915
+ // C1 — identity
916
+ if (!_f11HasAgentIdentity(meta)) return false;
917
+ const threats = (result && result.threats) || [];
918
+ if (threats.length === 0) return false;
919
+ // C2 — no install lifecycle hook
920
+ if (hasLifecycleScripts(meta)) return false;
921
+ // C3, C4, C7 — fast threat-type checks
922
+ for (const t of threats) {
923
+ if (t.type === 'mcp_config_injection') return false; // C3
924
+ if (t.type === 'suspicious_domain') return false; // C4
925
+ if (t.type === 'binary_dropper') return false; // C7
926
+ if (t.type === 'download_exec_binary') return false; // C7
927
+ }
928
+ // C5 — no credential file path in any message
929
+ for (const t of threats) {
930
+ if (F9_CREDENTIAL_FILE_RE.test(String(t.message || ''))) return false;
931
+ }
932
+ // C6 — at least one threat references an agent runtime path
933
+ if (!_f11HasAgentPathReference(threats)) return false;
934
+ return true;
935
+ }
936
+
795
937
  /**
796
938
  * Feature 8 — TRUE iff the package declares at least one install
797
939
  * lifecycle script AND the scan shows no network egress capability
@@ -946,6 +1088,8 @@ function extractFeatures(result, meta) {
946
1088
  features.mcp_server_env_access = mcpServerEnvAccess(result, meta) ? 1 : 0;
947
1089
  // --- v2.11.23 Feature 10 (audit week3 cluster — up to 96 FP) ---
948
1090
  features.vendor_cli_sdk = vendorCliSdk(result, meta) ? 1 : 0;
1091
+ // --- v2.11.24 Feature 11 (audit week3 cluster — up to 54 FP) ---
1092
+ features.ai_agent_bot = aiAgentBot(result, meta) ? 1 : 0;
949
1093
 
950
1094
  return features;
951
1095
  }
@@ -1026,5 +1170,6 @@ module.exports = {
1026
1170
  placeholderAntiDepConfusion,
1027
1171
  installScriptNoNetworkEgress,
1028
1172
  mcpServerEnvAccess,
1029
- vendorCliSdk
1173
+ vendorCliSdk,
1174
+ aiAgentBot
1030
1175
  };
package/src/scoring.js CHANGED
@@ -1485,6 +1485,7 @@ const {
1485
1485
  placeholderAntiDepConfusion,
1486
1486
  mcpServerEnvAccess,
1487
1487
  vendorCliSdk,
1488
+ aiAgentBot,
1488
1489
  } = require('./ml/feature-extractor.js');
1489
1490
 
1490
1491
  /**
@@ -1543,6 +1544,10 @@ function applyContextualFPCaps(result, pkgMeta) {
1543
1544
  if (vendorCliSdk(result, meta)) {
1544
1545
  applied.push({ feature: 'vendor_cli_sdk', cap: 35 });
1545
1546
  }
1547
+ // F11: legit AI agent / bot / multi-LLM orchestrator → MAX 35
1548
+ if (aiAgentBot(result, meta)) {
1549
+ applied.push({ feature: 'ai_agent_bot', cap: 35 });
1550
+ }
1546
1551
  // F5: typosquat on scoped package → suppress typosquat points
1547
1552
  if (typosquatScopedPackage(result, meta)) {
1548
1553
  applied.push({ feature: 'typosquat_scoped_package', cap: -1 });