@kenhkl/agent-moss 0.14.0 → 0.15.0

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/dist/server.js CHANGED
@@ -632,10 +632,15 @@ function loadMC() {
632
632
  try {
633
633
  const rt = (init_runtime_config(), __toCommonJS(runtime_config_exports));
634
634
  if (rules.length > 0 && typeof rt.hasRuntimeConfig === "function" && rt.hasRuntimeConfig()) {
635
- const rtRules = rt.loadRuntimeConfig()?.L2_2?.ltl_rules?.rules || [];
635
+ const ltl = rt.loadRuntimeConfig()?.L2_2?.ltl_rules || {};
636
+ const rtRules = ltl.rules || [];
636
637
  const enabledById = {};
637
638
  for (const r of rtRules) if (r?.id) enabledById[r.id] = r.enabled !== false;
638
- for (const r of rules) if (r?.id && r.id in enabledById) r.enabled = enabledById[r.id];
639
+ const catEnabled = ltl.category_enabled !== false;
640
+ for (const r of rules) {
641
+ if (r?.id && r.id in enabledById) r.enabled = enabledById[r.id];
642
+ if (!catEnabled) r.enabled = false;
643
+ }
639
644
  }
640
645
  } catch {
641
646
  }
@@ -1433,6 +1438,7 @@ __export(runtime_config_exports, {
1433
1438
  isLayerEnabled: () => isLayerEnabled,
1434
1439
  loadRuntimeConfig: () => loadRuntimeConfig,
1435
1440
  mergeConfig: () => mergeConfig,
1441
+ pickDeletableRule: () => pickDeletableRule,
1436
1442
  saveRuntimeConfig: () => saveRuntimeConfig,
1437
1443
  updateAgentCategoryEnabled: () => updateAgentCategoryEnabled,
1438
1444
  updateAgentRuleEnabled: () => updateAgentRuleEnabled,
@@ -1598,8 +1604,16 @@ function getDisabledL2RulesWithSkipL3() {
1598
1604
  function _slugify(text) {
1599
1605
  return text.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "") || "x";
1600
1606
  }
1607
+ function _fnv1a32(text) {
1608
+ let h = 2166136261;
1609
+ for (const b of Buffer.from(text, "utf-8")) {
1610
+ h ^= b;
1611
+ h = h * 16777619 >>> 0;
1612
+ }
1613
+ return h.toString(16).padStart(8, "0");
1614
+ }
1601
1615
  function _generatePatternId(pattern) {
1602
- return `pat_${_slugify(pattern)}`;
1616
+ return `pat_${_slugify(pattern)}_${_fnv1a32(pattern)}`;
1603
1617
  }
1604
1618
  function _deriveDenyMode(rule) {
1605
1619
  if (rule.deny_mode) return rule.deny_mode;
@@ -1885,10 +1899,13 @@ function generateSourceDefaults() {
1885
1899
  }
1886
1900
  function _ltlRulesToRuntime() {
1887
1901
  const cands = [
1888
- join3(__dirname4, "..", "rules"),
1889
- join3(__dirname4, "..", "..", "..", "agent_moss", "rules"),
1890
- join3(process.cwd(), "agent_moss", "rules")
1891
- ];
1902
+ getRuntimeConfigDir() && join3(getRuntimeConfigDir(), "rules"),
1903
+ join3(__dirname4, "../rules"),
1904
+ join3(__dirname4, "rules"),
1905
+ join3(__dirname4, "../../../agent_moss/rules"),
1906
+ join3(__dirname4, "../../../../agent_moss/rules"),
1907
+ join3(process.cwd(), "agent_moss/rules")
1908
+ ].filter(Boolean);
1892
1909
  let mc = {};
1893
1910
  for (const dir of cands) {
1894
1911
  const p = join3(dir, "mc_rules.json");
@@ -2248,16 +2265,20 @@ function addCustomRule(layer, category, rule) {
2248
2265
  saveRuntimeConfig(runtime);
2249
2266
  return runtime;
2250
2267
  }
2251
- function deleteCustomRule(layer, category, ruleId) {
2252
- const runtime = loadRuntimeConfig();
2253
- const rules = runtime[layer]?.[category]?.rules || [];
2254
- const idx = rules.findIndex((r) => r.id === ruleId);
2268
+ function pickDeletableRule(rules, ruleId) {
2269
+ const idx = rules.findIndex((r) => r.id === ruleId && (!r.builtin || r.source_removed));
2255
2270
  if (idx >= 0) {
2256
- const r = rules[idx];
2257
- if (r.builtin && !r.source_removed) return null;
2258
2271
  rules.splice(idx, 1);
2259
- saveRuntimeConfig(runtime);
2272
+ return rules;
2260
2273
  }
2274
+ if (rules.some((r) => r.id === ruleId)) return null;
2275
+ return rules;
2276
+ }
2277
+ function deleteCustomRule(layer, category, ruleId) {
2278
+ const runtime = loadRuntimeConfig();
2279
+ const rules = runtime[layer]?.[category]?.rules || [];
2280
+ if (pickDeletableRule(rules, ruleId) === null) return null;
2281
+ saveRuntimeConfig(runtime);
2261
2282
  return runtime;
2262
2283
  }
2263
2284
  function addCustomSkill(skillId, category, keywords, content) {
@@ -7373,8 +7394,36 @@ function allCandidates(detail, max = 8) {
7373
7394
  }
7374
7395
  return out;
7375
7396
  }
7397
+ var STAGE_PRIORITY = ["EXFIL", "PRIVESC", "PERSIST", "SCAN", "LISTEN"];
7398
+ var PURPOSE_EXEC_RE = /subprocess\.\w+\s*\(|os\.system\s*\(|shell\s*=\s*True|\beval\s*\(|\bexec\s*\(|os\.popen\s*\(|\/exec\b/i;
7399
+ var LISTEN_LEAD_RE = /http\.server|flask\s+run|nohup\b|nc\s+-l|socat\s+TCP-LISTEN/i;
7400
+ var LISTEN_NOISE_RE = /shell\s*=\s*True|os\.system|subprocess\.(?:run|call|Popen)|\beval\s*\(|\bexec\s*\(/i;
7376
7401
  function matchStageAny(detail, stages) {
7377
- for (const cand of allCandidates(detail)) {
7402
+ const cands = allCandidates(detail);
7403
+ if (cands.length === 0) return "";
7404
+ const shell = cands[0];
7405
+ const hits = [];
7406
+ for (const [name, pats] of Object.entries(stages)) {
7407
+ for (const p of pats) {
7408
+ p.lastIndex = 0;
7409
+ if (p.test(shell)) {
7410
+ hits.push(name);
7411
+ break;
7412
+ }
7413
+ }
7414
+ }
7415
+ if (hits.length > 0) {
7416
+ if (hits.length === 1) return hits[0];
7417
+ if (hits.includes("EXEC") && PURPOSE_EXEC_RE.test(shell)) return "EXEC";
7418
+ if (hits.includes("LISTEN") && LISTEN_LEAD_RE.test(shell) && !LISTEN_NOISE_RE.test(shell)) {
7419
+ return "LISTEN";
7420
+ }
7421
+ for (const s of STAGE_PRIORITY) {
7422
+ if (hits.includes(s)) return s;
7423
+ }
7424
+ if (hits.includes("EXEC")) return "EXEC";
7425
+ }
7426
+ for (const cand of cands.slice(1)) {
7378
7427
  for (const [name, pats] of Object.entries(stages)) {
7379
7428
  for (const p of pats) {
7380
7429
  p.lastIndex = 0;
@@ -7655,7 +7704,7 @@ function loadCrossSessionChains() {
7655
7704
  return CROSS_SESSION_CHAINS;
7656
7705
  }
7657
7706
  var CRED_FILE_RE = /(known_hosts|\.ssh|id_rsa|\/etc\/shadow|\/etc\/passwd|credentials)/i;
7658
- var EXFIL_RE = /https?:\/\/|scp\s+.*@|rsync\s+.*@/i;
7707
+ var EXFIL_RE = /https?:\/\/(?!127\.\d+\.\d+\.\d+|0\.0\.0\.0|localhost|\[?::1\]?)[^\s]+|\bscp\s+.*@|\brsync\s+.*@/i;
7659
7708
  function checkCrossSessionChain(graph) {
7660
7709
  const violations = [];
7661
7710
  const nodes = allNodes(graph);
@@ -7756,9 +7805,67 @@ function checkSensitiveDataFlow(graph) {
7756
7805
  }
7757
7806
  var EBPF_NET_RE = /connect|sendto|network_connect/i;
7758
7807
  var REMOTE_RE = /to\s+\d+\.\d+\.\d+\.\d+|remote_addr/i;
7759
- var PRIVESC_RE = /\bsudo\b|\bsu\b|chmod\s+u\s*\+\s*s|pkexec|setuid/i;
7760
7808
  var EXFIL_ACTION_RE = /\bcurl\b|\bwget\b|\bscp\b|\brsync\b|\brclone\b|\bftp\b|connect\s+.*\d+\.\d+\.\d+\.\d+|open\s+.*tcp|base64\s+.*\-o/i;
7761
7809
  var CRED_STRICT_RE = /(known_hosts|\.ssh|id_rsa|\.pem|\b\.key\b|\/etc\/shadow|\/etc\/passwd|\.aws\/credentials|credentials\.json|netrc|password|secrets?\.ya?ml|credentials?\s*(file|path)?\s*(=|\:)|\bconfig.*(password|token))/i;
7810
+ var INLINE_SHELL_RE = /(?:os\.system|os\.popen)\s*\(\s*['"]([^'"]+)['"]|subprocess\.[a-z_]+\(\s*\[([^\]]+)\]|subprocess\.[a-z_]+\(\s*['"]([^'"]+)['"]|(?:bash|sh|zsh)\s+-c\s*['"]([^'"]+)['"]/gi;
7811
+ function extractInlineShell(s) {
7812
+ const out = [];
7813
+ let m;
7814
+ INLINE_SHELL_RE.lastIndex = 0;
7815
+ while ((m = INLINE_SHELL_RE.exec(s)) !== null) {
7816
+ let g = m[1] ?? m[2] ?? m[3] ?? m[4] ?? "";
7817
+ if (/^["']/.test(g) && (/"\s*,/.test(g) || /'\s*,/.test(g))) {
7818
+ g = g.replace(/['"\[\],]+/g, " ");
7819
+ }
7820
+ g = g.trim();
7821
+ if (g) out.push(g);
7822
+ }
7823
+ return out;
7824
+ }
7825
+ var PKG_MGR = /* @__PURE__ */ new Set(["apt", "apt-get", "aptitude", "yum", "dnf", "pacman", "zypper", "apk", "brew"]);
7826
+ var SHELLS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "fish", "ksh", "csh", "tcsh", "nologin"]);
7827
+ function containsPrivesc(shell) {
7828
+ if (!shell) return false;
7829
+ for (const seg of shell.split(/\&\&|\|\||;|\n/)) {
7830
+ const s = seg.trim();
7831
+ if (!s) continue;
7832
+ const toks = s.split(/\s+/);
7833
+ const head = (toks[0] || "").toLowerCase();
7834
+ const rest = toks.slice(1).join(" ");
7835
+ if (head === "chmod" && /(?:u\+s|g\+s|[2-7][0-7]{3})/.test(rest)) return true;
7836
+ if (head === "setcap" && /cap_/.test(rest)) return true;
7837
+ if (head === "pkexec" || head === "doas") return true;
7838
+ if (head === "sudo") {
7839
+ if (/^-u\s+/.test(rest)) {
7840
+ if (/^-u\s+root\b/.test(rest)) return true;
7841
+ continue;
7842
+ }
7843
+ const m = /^(\S+)(.*)$/.exec(rest);
7844
+ if (m) {
7845
+ const nxt = m[1].toLowerCase();
7846
+ const tail = m[2];
7847
+ if (PKG_MGR.has(nxt)) continue;
7848
+ if (SHELLS.has(nxt)) return true;
7849
+ if (nxt === "-i" || nxt === "-s") return true;
7850
+ if (nxt.startsWith("su")) return true;
7851
+ if (nxt.startsWith("/") && /bin\/(?:bash|sh|zsh|dash|fish)$/.test(nxt)) return true;
7852
+ }
7853
+ if (/\/(?:usr\/(?:local\/)?bin|bin)\/(?:bash|sh|zsh|dash|fish)\b/.test(rest)) return true;
7854
+ if (/\bchmod\s+(?:u\+s|g\+s|[2-7][0-7]{3})\b/.test(rest)) return true;
7855
+ if (/\bsetcap\s+cap_\w+/.test(rest)) return true;
7856
+ if (/\bvisudo\b/.test(rest)) return true;
7857
+ if (/\bpasswd\s+root\b/.test(rest)) return true;
7858
+ if (/\buseradd\b/.test(rest)) return true;
7859
+ }
7860
+ if (head === "su") {
7861
+ if (!rest) return true;
7862
+ if (/^-l?\s*$/.test(rest)) return true;
7863
+ if (/^root\b/.test(rest) || /^-l?\s+root\b/.test(rest)) return true;
7864
+ }
7865
+ }
7866
+ if (/\bset(?:uid|euid|gid|egid|reuid|regid|resuid|resgid)\s*\(/i.test(shell)) return true;
7867
+ return false;
7868
+ }
7762
7869
  function isExfilNode(n) {
7763
7870
  if (n.stage === "EXFIL") return true;
7764
7871
  if (EXFIL_RE.test(n.actionDetail) && EXFIL_ACTION_RE.test(n.actionDetail)) return true;
@@ -7773,8 +7880,12 @@ function isCredNode(n) {
7773
7880
  return CRED_STRICT_RE.test(n.actionDetail);
7774
7881
  }
7775
7882
  function isPrivescNode(n) {
7776
- if (n.stage === "PRIVESC") return true;
7777
- return PRIVESC_RE.test(n.actionDetail);
7883
+ const shell = stripStringLiterals(n.actionDetail);
7884
+ if (containsPrivesc(shell)) return true;
7885
+ for (const cmd of extractInlineShell(shell)) {
7886
+ if (containsPrivesc(cmd)) return true;
7887
+ }
7888
+ return false;
7778
7889
  }
7779
7890
  function checkNeverExfilAfterPrivesc(graph) {
7780
7891
  const violations = [];
@@ -9581,9 +9692,16 @@ function createConsoleRouter() {
9581
9692
  };
9582
9693
  const mc = loadMC();
9583
9694
  const rules = mc.rules || [];
9695
+ let catEnabled = true;
9696
+ try {
9697
+ const rt = loadRuntimeConfig();
9698
+ catEnabled = rt.L2_2?.ltl_rules?.category_enabled !== false;
9699
+ } catch {
9700
+ }
9584
9701
  return {
9585
9702
  enabled_via: "L2_logic_rules",
9586
9703
  description: "\u65F6\u5E8F\u4E0E\u6570\u636E\u6D41\u653B\u51FB\u68C0\u6D4B\uFF08L2.2 \u6A21\u578B\u68C0\u6D4B\uFF09",
9704
+ category_enabled: catEnabled,
9587
9705
  ltl_rules: rules.map((r) => ({
9588
9706
  id: r.id,
9589
9707
  type: r.type,
@@ -9597,9 +9715,9 @@ function createConsoleRouter() {
9597
9715
  };
9598
9716
  }
9599
9717
  function withL22View(rc) {
9600
- if (rc && rc.L2_2 && typeof rc.L2_2 === "object" && !Array.isArray(rc.L2_2)) {
9601
- const cat = rc.L2_2.ltl_rules;
9602
- const rls = cat && cat.rules || [];
9718
+ const l22 = rc && rc.L2_2;
9719
+ if (l22 && typeof l22 === "object" && !Array.isArray(l22) && l22.ltl_rules && typeof l22.ltl_rules === "object" && !Array.isArray(l22.ltl_rules)) {
9720
+ const rls = l22.ltl_rules.rules || [];
9603
9721
  const ltlHumanLocal = {
9604
9722
  "mc-ltl-001": "\u5148\u8BFB\u53D6\u4E86\u51ED\u636E/\u5BC6\u94A5\uFF0C\u4E4B\u540E\u51FA\u73B0\u7F51\u7EDC\u5916\u4F20",
9605
9723
  "mc-ltl-002": "\u5148\u4E0B\u8F7D/\u83B7\u53D6\u4E86\u6587\u4EF6\uFF0C\u4E4B\u540E\u6267\u884C\u4E86\u6587\u4EF6",
@@ -9616,6 +9734,7 @@ function createConsoleRouter() {
9616
9734
  rc.L2_2 = {
9617
9735
  enabled_via: "L2_logic_rules",
9618
9736
  description: "\u65F6\u5E8F\u4E0E\u6570\u636E\u6D41\u653B\u51FB\u68C0\u6D4B\uFF08L2.2 \u6A21\u578B\u68C0\u6D4B\uFF09",
9737
+ category_enabled: l22.ltl_rules.category_enabled !== false,
9619
9738
  ltl_rules: rls.map((r) => ({
9620
9739
  id: r.id,
9621
9740
  type: "ltl",
@@ -9652,7 +9771,7 @@ function createConsoleRouter() {
9652
9771
  console2.put("/api/categories/enabled", async (c) => {
9653
9772
  const b = await c.req.json();
9654
9773
  const rc = updateCategoryEnabled(b.layer, b.category, b.enabled);
9655
- return c.json(rc);
9774
+ return c.json(withL22View(rc));
9656
9775
  });
9657
9776
  console2.post("/api/rules", async (c) => {
9658
9777
  const b = await c.req.json();
@@ -9668,13 +9787,13 @@ function createConsoleRouter() {
9668
9787
  credential: b.credential,
9669
9788
  deny_mode: b.deny_mode
9670
9789
  });
9671
- return c.json(rc);
9790
+ return c.json(withL22View(rc));
9672
9791
  });
9673
9792
  console2.delete("/api/rules", async (c) => {
9674
9793
  const b = await c.req.json();
9675
9794
  deleteCustomRule(b.layer, b.category, b.rule_id);
9676
9795
  const rc = loadRuntimeConfig();
9677
- return c.json(rc);
9796
+ return c.json(withL22View(rc));
9678
9797
  });
9679
9798
  console2.get("/api/skills", (c) => {
9680
9799
  const rc = loadRuntimeConfig();
@@ -9683,23 +9802,23 @@ function createConsoleRouter() {
9683
9802
  console2.put("/api/skills/enabled", async (c) => {
9684
9803
  const b = await c.req.json();
9685
9804
  const rc = updateSkillEnabled(b.skill_id, b.enabled);
9686
- return c.json(rc);
9805
+ return c.json(withL22View(rc));
9687
9806
  });
9688
9807
  console2.put("/api/skill-categories/enabled", async (c) => {
9689
9808
  const b = await c.req.json();
9690
9809
  const rc = updateSkillCategoryEnabled(b.category, b.enabled);
9691
- return c.json(rc);
9810
+ return c.json(withL22View(rc));
9692
9811
  });
9693
9812
  console2.post("/api/skills", async (c) => {
9694
9813
  const b = await c.req.json();
9695
9814
  const rc = addCustomSkill(b.skill_id, b.category || "user_custom", b.keywords || [], b.content);
9696
- return c.json(rc);
9815
+ return c.json(withL22View(rc));
9697
9816
  });
9698
9817
  console2.delete("/api/skills", async (c) => {
9699
9818
  const b = await c.req.json();
9700
9819
  deleteCustomSkill(b.skill_id, b.category);
9701
9820
  const rc = loadRuntimeConfig();
9702
- return c.json(rc);
9821
+ return c.json(withL22View(rc));
9703
9822
  });
9704
9823
  console2.get("/api/skills/:skill_id/content", async (c) => {
9705
9824
  const skillId = c.req.param("skill_id");
@@ -9805,7 +9924,7 @@ function createConsoleRouter() {
9805
9924
  console2.post("/api/reset", (c) => {
9806
9925
  const defaults = generateSourceDefaults();
9807
9926
  saveRuntimeConfig(defaults);
9808
- return c.json(defaults);
9927
+ return c.json(withL22View(defaults));
9809
9928
  });
9810
9929
  console2.get("/api/token-stats", (c) => {
9811
9930
  const days = parseInt(c.req.query("days") || "7", 10);