@kenhkl/agent-moss 0.14.0 → 0.15.1

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/cli.js CHANGED
@@ -633,10 +633,15 @@ function loadMC() {
633
633
  try {
634
634
  const rt = (init_runtime_config(), __toCommonJS(runtime_config_exports));
635
635
  if (rules.length > 0 && typeof rt.hasRuntimeConfig === "function" && rt.hasRuntimeConfig()) {
636
- const rtRules = rt.loadRuntimeConfig()?.L2_2?.ltl_rules?.rules || [];
636
+ const ltl = rt.loadRuntimeConfig()?.L2_2?.ltl_rules || {};
637
+ const rtRules = ltl.rules || [];
637
638
  const enabledById = {};
638
639
  for (const r of rtRules) if (r?.id) enabledById[r.id] = r.enabled !== false;
639
- for (const r of rules) if (r?.id && r.id in enabledById) r.enabled = enabledById[r.id];
640
+ const catEnabled = ltl.category_enabled !== false;
641
+ for (const r of rules) {
642
+ if (r?.id && r.id in enabledById) r.enabled = enabledById[r.id];
643
+ if (!catEnabled) r.enabled = false;
644
+ }
640
645
  }
641
646
  } catch {
642
647
  }
@@ -1434,6 +1439,7 @@ __export(runtime_config_exports, {
1434
1439
  isLayerEnabled: () => isLayerEnabled,
1435
1440
  loadRuntimeConfig: () => loadRuntimeConfig,
1436
1441
  mergeConfig: () => mergeConfig,
1442
+ pickDeletableRule: () => pickDeletableRule,
1437
1443
  saveRuntimeConfig: () => saveRuntimeConfig,
1438
1444
  updateAgentCategoryEnabled: () => updateAgentCategoryEnabled,
1439
1445
  updateAgentRuleEnabled: () => updateAgentRuleEnabled,
@@ -1599,8 +1605,16 @@ function getDisabledL2RulesWithSkipL3() {
1599
1605
  function _slugify(text) {
1600
1606
  return text.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "") || "x";
1601
1607
  }
1608
+ function _fnv1a32(text) {
1609
+ let h = 2166136261;
1610
+ for (const b of Buffer.from(text, "utf-8")) {
1611
+ h ^= b;
1612
+ h = h * 16777619 >>> 0;
1613
+ }
1614
+ return h.toString(16).padStart(8, "0");
1615
+ }
1602
1616
  function _generatePatternId(pattern) {
1603
- return `pat_${_slugify(pattern)}`;
1617
+ return `pat_${_slugify(pattern)}_${_fnv1a32(pattern)}`;
1604
1618
  }
1605
1619
  function _deriveDenyMode(rule) {
1606
1620
  if (rule.deny_mode) return rule.deny_mode;
@@ -1886,10 +1900,13 @@ function generateSourceDefaults() {
1886
1900
  }
1887
1901
  function _ltlRulesToRuntime() {
1888
1902
  const cands = [
1889
- join3(__dirname4, "..", "rules"),
1890
- join3(__dirname4, "..", "..", "..", "agent_moss", "rules"),
1891
- join3(process.cwd(), "agent_moss", "rules")
1892
- ];
1903
+ getRuntimeConfigDir() && join3(getRuntimeConfigDir(), "rules"),
1904
+ join3(__dirname4, "../rules"),
1905
+ join3(__dirname4, "rules"),
1906
+ join3(__dirname4, "../../../agent_moss/rules"),
1907
+ join3(__dirname4, "../../../../agent_moss/rules"),
1908
+ join3(process.cwd(), "agent_moss/rules")
1909
+ ].filter(Boolean);
1893
1910
  let mc = {};
1894
1911
  for (const dir of cands) {
1895
1912
  const p = join3(dir, "mc_rules.json");
@@ -2249,16 +2266,20 @@ function addCustomRule(layer, category, rule) {
2249
2266
  saveRuntimeConfig(runtime);
2250
2267
  return runtime;
2251
2268
  }
2252
- function deleteCustomRule(layer, category, ruleId) {
2253
- const runtime = loadRuntimeConfig();
2254
- const rules = runtime[layer]?.[category]?.rules || [];
2255
- const idx = rules.findIndex((r) => r.id === ruleId);
2269
+ function pickDeletableRule(rules, ruleId) {
2270
+ const idx = rules.findIndex((r) => r.id === ruleId && (!r.builtin || r.source_removed));
2256
2271
  if (idx >= 0) {
2257
- const r = rules[idx];
2258
- if (r.builtin && !r.source_removed) return null;
2259
2272
  rules.splice(idx, 1);
2260
- saveRuntimeConfig(runtime);
2273
+ return rules;
2261
2274
  }
2275
+ if (rules.some((r) => r.id === ruleId)) return null;
2276
+ return rules;
2277
+ }
2278
+ function deleteCustomRule(layer, category, ruleId) {
2279
+ const runtime = loadRuntimeConfig();
2280
+ const rules = runtime[layer]?.[category]?.rules || [];
2281
+ if (pickDeletableRule(rules, ruleId) === null) return null;
2282
+ saveRuntimeConfig(runtime);
2262
2283
  return runtime;
2263
2284
  }
2264
2285
  function addCustomSkill(skillId, category, keywords, content) {
@@ -6552,7 +6573,31 @@ function allCandidates(detail, max = 8) {
6552
6573
  return out;
6553
6574
  }
6554
6575
  function matchStageAny(detail, stages) {
6555
- for (const cand of allCandidates(detail)) {
6576
+ const cands = allCandidates(detail);
6577
+ if (cands.length === 0) return "";
6578
+ const shell = cands[0];
6579
+ const hits = [];
6580
+ for (const [name, pats] of Object.entries(stages)) {
6581
+ for (const p of pats) {
6582
+ p.lastIndex = 0;
6583
+ if (p.test(shell)) {
6584
+ hits.push(name);
6585
+ break;
6586
+ }
6587
+ }
6588
+ }
6589
+ if (hits.length > 0) {
6590
+ if (hits.length === 1) return hits[0];
6591
+ if (hits.includes("EXEC") && PURPOSE_EXEC_RE.test(shell)) return "EXEC";
6592
+ if (hits.includes("LISTEN") && LISTEN_LEAD_RE.test(shell) && !LISTEN_NOISE_RE.test(shell)) {
6593
+ return "LISTEN";
6594
+ }
6595
+ for (const s of STAGE_PRIORITY) {
6596
+ if (hits.includes(s)) return s;
6597
+ }
6598
+ if (hits.includes("EXEC")) return "EXEC";
6599
+ }
6600
+ for (const cand of cands.slice(1)) {
6556
6601
  for (const [name, pats] of Object.entries(stages)) {
6557
6602
  for (const p of pats) {
6558
6603
  p.lastIndex = 0;
@@ -6562,7 +6607,7 @@ function matchStageAny(detail, stages) {
6562
6607
  }
6563
6608
  return "";
6564
6609
  }
6565
- var B64_TOKEN_RE, QUOTED_STR_RE, OUTPUT_CMDS;
6610
+ var B64_TOKEN_RE, QUOTED_STR_RE, OUTPUT_CMDS, STAGE_PRIORITY, PURPOSE_EXEC_RE, LISTEN_LEAD_RE, LISTEN_NOISE_RE;
6566
6611
  var init_decode = __esm({
6567
6612
  "src/engine/formal-engine/decode.ts"() {
6568
6613
  "use strict";
@@ -6570,6 +6615,10 @@ var init_decode = __esm({
6570
6615
  B64_TOKEN_RE = /[A-Za-z0-9+/]+={0,2}/g;
6571
6616
  QUOTED_STR_RE = /('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`)/g;
6572
6617
  OUTPUT_CMDS = /* @__PURE__ */ new Set(["echo", "printf", "print"]);
6618
+ STAGE_PRIORITY = ["EXFIL", "PRIVESC", "PERSIST", "SCAN", "LISTEN"];
6619
+ PURPOSE_EXEC_RE = /subprocess\.\w+\s*\(|os\.system\s*\(|shell\s*=\s*True|\beval\s*\(|\bexec\s*\(|os\.popen\s*\(|\/exec\b/i;
6620
+ LISTEN_LEAD_RE = /http\.server|flask\s+run|nohup\b|nc\s+-l|socat\s+TCP-LISTEN/i;
6621
+ LISTEN_NOISE_RE = /shell\s*=\s*True|os\.system|subprocess\.(?:run|call|Popen)|\beval\s*\(|\bexec\s*\(/i;
6573
6622
  }
6574
6623
  });
6575
6624
 
@@ -6958,6 +7007,62 @@ function checkSensitiveDataFlow(graph) {
6958
7007
  }
6959
7008
  return violations;
6960
7009
  }
7010
+ function extractInlineShell(s) {
7011
+ const out = [];
7012
+ let m;
7013
+ INLINE_SHELL_RE.lastIndex = 0;
7014
+ while ((m = INLINE_SHELL_RE.exec(s)) !== null) {
7015
+ let g = m[1] ?? m[2] ?? m[3] ?? m[4] ?? "";
7016
+ if (/^["']/.test(g) && (/"\s*,/.test(g) || /'\s*,/.test(g))) {
7017
+ g = g.replace(/['"\[\],]+/g, " ");
7018
+ }
7019
+ g = g.trim();
7020
+ if (g) out.push(g);
7021
+ }
7022
+ return out;
7023
+ }
7024
+ function containsPrivesc(shell) {
7025
+ if (!shell) return false;
7026
+ for (const seg of shell.split(/\&\&|\|\||;|\n/)) {
7027
+ const s = seg.trim();
7028
+ if (!s) continue;
7029
+ const toks = s.split(/\s+/);
7030
+ const head = (toks[0] || "").toLowerCase();
7031
+ const rest = toks.slice(1).join(" ");
7032
+ if (head === "chmod" && /(?:u\+s|g\+s|[2-7][0-7]{3})/.test(rest)) return true;
7033
+ if (head === "setcap" && /cap_/.test(rest)) return true;
7034
+ if (head === "pkexec" || head === "doas") return true;
7035
+ if (head === "sudo") {
7036
+ if (/^-u\s+/.test(rest)) {
7037
+ if (/^-u\s+root\b/.test(rest)) return true;
7038
+ continue;
7039
+ }
7040
+ const m = /^(\S+)(.*)$/.exec(rest);
7041
+ if (m) {
7042
+ const nxt = m[1].toLowerCase();
7043
+ const tail = m[2];
7044
+ if (PKG_MGR.has(nxt)) continue;
7045
+ if (SHELLS.has(nxt)) return true;
7046
+ if (nxt === "-i" || nxt === "-s") return true;
7047
+ if (nxt.startsWith("su")) return true;
7048
+ if (nxt.startsWith("/") && /bin\/(?:bash|sh|zsh|dash|fish)$/.test(nxt)) return true;
7049
+ }
7050
+ if (/\/(?:usr\/(?:local\/)?bin|bin)\/(?:bash|sh|zsh|dash|fish)\b/.test(rest)) return true;
7051
+ if (/\bchmod\s+(?:u\+s|g\+s|[2-7][0-7]{3})\b/.test(rest)) return true;
7052
+ if (/\bsetcap\s+cap_\w+/.test(rest)) return true;
7053
+ if (/\bvisudo\b/.test(rest)) return true;
7054
+ if (/\bpasswd\s+root\b/.test(rest)) return true;
7055
+ if (/\buseradd\b/.test(rest)) return true;
7056
+ }
7057
+ if (head === "su") {
7058
+ if (!rest) return true;
7059
+ if (/^-l?\s*$/.test(rest)) return true;
7060
+ if (/^root\b/.test(rest) || /^-l?\s+root\b/.test(rest)) return true;
7061
+ }
7062
+ }
7063
+ if (/\bset(?:uid|euid|gid|egid|reuid|regid|resuid|resgid)\s*\(/i.test(shell)) return true;
7064
+ return false;
7065
+ }
6961
7066
  function isExfilNode(n) {
6962
7067
  if (n.stage === "EXFIL") return true;
6963
7068
  if (EXFIL_RE.test(n.actionDetail) && EXFIL_ACTION_RE.test(n.actionDetail)) return true;
@@ -6972,8 +7077,12 @@ function isCredNode(n) {
6972
7077
  return CRED_STRICT_RE.test(n.actionDetail);
6973
7078
  }
6974
7079
  function isPrivescNode(n) {
6975
- if (n.stage === "PRIVESC") return true;
6976
- return PRIVESC_RE.test(n.actionDetail);
7080
+ const shell = stripStringLiterals(n.actionDetail);
7081
+ if (containsPrivesc(shell)) return true;
7082
+ for (const cmd of extractInlineShell(shell)) {
7083
+ if (containsPrivesc(cmd)) return true;
7084
+ }
7085
+ return false;
6977
7086
  }
6978
7087
  function checkNeverExfilAfterPrivesc(graph) {
6979
7088
  const violations = [];
@@ -7223,7 +7332,7 @@ function checkSensitiveDataAggregation(graph) {
7223
7332
  }
7224
7333
  return violations;
7225
7334
  }
7226
- var CROSS_SESSION_CHAINS, CRED_FILE_RE, EXFIL_RE, EBPF_NET_RE, REMOTE_RE, PRIVESC_RE, EXFIL_ACTION_RE, CRED_STRICT_RE, TOOL_REGISTRY, COMPUTE_RE, STRONG_COMPUTE_TYPES, DL_OR_LOW_RE, ALL_PROPERTIES;
7335
+ var CROSS_SESSION_CHAINS, CRED_FILE_RE, EXFIL_RE, EBPF_NET_RE, REMOTE_RE, EXFIL_ACTION_RE, CRED_STRICT_RE, INLINE_SHELL_RE, PKG_MGR, SHELLS, TOOL_REGISTRY, COMPUTE_RE, STRONG_COMPUTE_TYPES, DL_OR_LOW_RE, ALL_PROPERTIES;
7227
7336
  var init_properties = __esm({
7228
7337
  "src/engine/formal-engine/properties.ts"() {
7229
7338
  "use strict";
@@ -7231,14 +7340,17 @@ var init_properties = __esm({
7231
7340
  init_rules_loader();
7232
7341
  init_ir();
7233
7342
  init_ltl();
7343
+ init_decode();
7234
7344
  CROSS_SESSION_CHAINS = null;
7235
7345
  CRED_FILE_RE = /(known_hosts|\.ssh|id_rsa|\/etc\/shadow|\/etc\/passwd|credentials)/i;
7236
- EXFIL_RE = /https?:\/\/|scp\s+.*@|rsync\s+.*@/i;
7346
+ EXFIL_RE = /https?:\/\/(?!127\.\d+\.\d+\.\d+|0\.0\.0\.0|localhost|\[?::1\]?)[^\s]+|\bscp\s+.*@|\brsync\s+.*@/i;
7237
7347
  EBPF_NET_RE = /connect|sendto|network_connect/i;
7238
7348
  REMOTE_RE = /to\s+\d+\.\d+\.\d+\.\d+|remote_addr/i;
7239
- PRIVESC_RE = /\bsudo\b|\bsu\b|chmod\s+u\s*\+\s*s|pkexec|setuid/i;
7240
7349
  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;
7241
7350
  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;
7351
+ INLINE_SHELL_RE = /(?:os\.system|os\.popen)\s*\(\s*['"]([^'"]+)['"]|subprocess\.[a-z_]+\(\s*\[([^\]]+)\]|subprocess\.[a-z_]+\(\s*['"]([^'"]+)['"]|(?:bash|sh|zsh)\s+-c\s*['"]([^'"]+)['"]/gi;
7352
+ PKG_MGR = /* @__PURE__ */ new Set(["apt", "apt-get", "aptitude", "yum", "dnf", "pacman", "zypper", "apk", "brew"]);
7353
+ SHELLS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "fish", "ksh", "csh", "tcsh", "nologin"]);
7242
7354
  TOOL_REGISTRY = null;
7243
7355
  COMPUTE_RE = /subprocess|os\.system|shell\s*=\s*True|\beval\(|\bexec\(|bash\s+[^\s]|sh\s+-c|python3?\s+|node\s+|\btimeout\s+|nohup\s+|\b\.\/[\w./-]+/i;
7244
7356
  STRONG_COMPUTE_TYPES = /* @__PURE__ */ new Set(["python", "node", "exec", "terminal", "cmd"]);
@@ -9753,9 +9865,16 @@ function createConsoleRouter() {
9753
9865
  };
9754
9866
  const mc = loadMC();
9755
9867
  const rules = mc.rules || [];
9868
+ let catEnabled = true;
9869
+ try {
9870
+ const rt = loadRuntimeConfig();
9871
+ catEnabled = rt.L2_2?.ltl_rules?.category_enabled !== false;
9872
+ } catch {
9873
+ }
9756
9874
  return {
9757
9875
  enabled_via: "L2_logic_rules",
9758
9876
  description: "\u65F6\u5E8F\u4E0E\u6570\u636E\u6D41\u653B\u51FB\u68C0\u6D4B\uFF08L2.2 \u6A21\u578B\u68C0\u6D4B\uFF09",
9877
+ category_enabled: catEnabled,
9759
9878
  ltl_rules: rules.map((r) => ({
9760
9879
  id: r.id,
9761
9880
  type: r.type,
@@ -9769,9 +9888,9 @@ function createConsoleRouter() {
9769
9888
  };
9770
9889
  }
9771
9890
  function withL22View(rc) {
9772
- if (rc && rc.L2_2 && typeof rc.L2_2 === "object" && !Array.isArray(rc.L2_2)) {
9773
- const cat = rc.L2_2.ltl_rules;
9774
- const rls = cat && cat.rules || [];
9891
+ const l22 = rc && rc.L2_2;
9892
+ if (l22 && typeof l22 === "object" && !Array.isArray(l22) && l22.ltl_rules && typeof l22.ltl_rules === "object" && !Array.isArray(l22.ltl_rules)) {
9893
+ const rls = l22.ltl_rules.rules || [];
9775
9894
  const ltlHumanLocal = {
9776
9895
  "mc-ltl-001": "\u5148\u8BFB\u53D6\u4E86\u51ED\u636E/\u5BC6\u94A5\uFF0C\u4E4B\u540E\u51FA\u73B0\u7F51\u7EDC\u5916\u4F20",
9777
9896
  "mc-ltl-002": "\u5148\u4E0B\u8F7D/\u83B7\u53D6\u4E86\u6587\u4EF6\uFF0C\u4E4B\u540E\u6267\u884C\u4E86\u6587\u4EF6",
@@ -9788,6 +9907,7 @@ function createConsoleRouter() {
9788
9907
  rc.L2_2 = {
9789
9908
  enabled_via: "L2_logic_rules",
9790
9909
  description: "\u65F6\u5E8F\u4E0E\u6570\u636E\u6D41\u653B\u51FB\u68C0\u6D4B\uFF08L2.2 \u6A21\u578B\u68C0\u6D4B\uFF09",
9910
+ category_enabled: l22.ltl_rules.category_enabled !== false,
9791
9911
  ltl_rules: rls.map((r) => ({
9792
9912
  id: r.id,
9793
9913
  type: "ltl",
@@ -9824,7 +9944,7 @@ function createConsoleRouter() {
9824
9944
  console2.put("/api/categories/enabled", async (c) => {
9825
9945
  const b = await c.req.json();
9826
9946
  const rc = updateCategoryEnabled(b.layer, b.category, b.enabled);
9827
- return c.json(rc);
9947
+ return c.json(withL22View(rc));
9828
9948
  });
9829
9949
  console2.post("/api/rules", async (c) => {
9830
9950
  const b = await c.req.json();
@@ -9840,13 +9960,13 @@ function createConsoleRouter() {
9840
9960
  credential: b.credential,
9841
9961
  deny_mode: b.deny_mode
9842
9962
  });
9843
- return c.json(rc);
9963
+ return c.json(withL22View(rc));
9844
9964
  });
9845
9965
  console2.delete("/api/rules", async (c) => {
9846
9966
  const b = await c.req.json();
9847
9967
  deleteCustomRule(b.layer, b.category, b.rule_id);
9848
9968
  const rc = loadRuntimeConfig();
9849
- return c.json(rc);
9969
+ return c.json(withL22View(rc));
9850
9970
  });
9851
9971
  console2.get("/api/skills", (c) => {
9852
9972
  const rc = loadRuntimeConfig();
@@ -9855,23 +9975,23 @@ function createConsoleRouter() {
9855
9975
  console2.put("/api/skills/enabled", async (c) => {
9856
9976
  const b = await c.req.json();
9857
9977
  const rc = updateSkillEnabled(b.skill_id, b.enabled);
9858
- return c.json(rc);
9978
+ return c.json(withL22View(rc));
9859
9979
  });
9860
9980
  console2.put("/api/skill-categories/enabled", async (c) => {
9861
9981
  const b = await c.req.json();
9862
9982
  const rc = updateSkillCategoryEnabled(b.category, b.enabled);
9863
- return c.json(rc);
9983
+ return c.json(withL22View(rc));
9864
9984
  });
9865
9985
  console2.post("/api/skills", async (c) => {
9866
9986
  const b = await c.req.json();
9867
9987
  const rc = addCustomSkill(b.skill_id, b.category || "user_custom", b.keywords || [], b.content);
9868
- return c.json(rc);
9988
+ return c.json(withL22View(rc));
9869
9989
  });
9870
9990
  console2.delete("/api/skills", async (c) => {
9871
9991
  const b = await c.req.json();
9872
9992
  deleteCustomSkill(b.skill_id, b.category);
9873
9993
  const rc = loadRuntimeConfig();
9874
- return c.json(rc);
9994
+ return c.json(withL22View(rc));
9875
9995
  });
9876
9996
  console2.get("/api/skills/:skill_id/content", async (c) => {
9877
9997
  const skillId = c.req.param("skill_id");
@@ -9977,7 +10097,7 @@ function createConsoleRouter() {
9977
10097
  console2.post("/api/reset", (c) => {
9978
10098
  const defaults = generateSourceDefaults();
9979
10099
  saveRuntimeConfig(defaults);
9980
- return c.json(defaults);
10100
+ return c.json(withL22View(defaults));
9981
10101
  });
9982
10102
  console2.get("/api/token-stats", (c) => {
9983
10103
  const days = parseInt(c.req.query("days") || "7", 10);