@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.
@@ -628,10 +628,15 @@ function loadMC() {
628
628
  try {
629
629
  const rt = (init_runtime_config(), __toCommonJS(runtime_config_exports));
630
630
  if (rules.length > 0 && typeof rt.hasRuntimeConfig === "function" && rt.hasRuntimeConfig()) {
631
- const rtRules = rt.loadRuntimeConfig()?.L2_2?.ltl_rules?.rules || [];
631
+ const ltl = rt.loadRuntimeConfig()?.L2_2?.ltl_rules || {};
632
+ const rtRules = ltl.rules || [];
632
633
  const enabledById = {};
633
634
  for (const r of rtRules) if (r?.id) enabledById[r.id] = r.enabled !== false;
634
- for (const r of rules) if (r?.id && r.id in enabledById) r.enabled = enabledById[r.id];
635
+ const catEnabled = ltl.category_enabled !== false;
636
+ for (const r of rules) {
637
+ if (r?.id && r.id in enabledById) r.enabled = enabledById[r.id];
638
+ if (!catEnabled) r.enabled = false;
639
+ }
635
640
  }
636
641
  } catch {
637
642
  }
@@ -1429,6 +1434,7 @@ __export(runtime_config_exports, {
1429
1434
  isLayerEnabled: () => isLayerEnabled,
1430
1435
  loadRuntimeConfig: () => loadRuntimeConfig,
1431
1436
  mergeConfig: () => mergeConfig,
1437
+ pickDeletableRule: () => pickDeletableRule,
1432
1438
  saveRuntimeConfig: () => saveRuntimeConfig,
1433
1439
  updateAgentCategoryEnabled: () => updateAgentCategoryEnabled,
1434
1440
  updateAgentRuleEnabled: () => updateAgentRuleEnabled,
@@ -1594,8 +1600,16 @@ function getDisabledL2RulesWithSkipL3() {
1594
1600
  function _slugify(text) {
1595
1601
  return text.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "") || "x";
1596
1602
  }
1603
+ function _fnv1a32(text) {
1604
+ let h = 2166136261;
1605
+ for (const b of Buffer.from(text, "utf-8")) {
1606
+ h ^= b;
1607
+ h = h * 16777619 >>> 0;
1608
+ }
1609
+ return h.toString(16).padStart(8, "0");
1610
+ }
1597
1611
  function _generatePatternId(pattern) {
1598
- return `pat_${_slugify(pattern)}`;
1612
+ return `pat_${_slugify(pattern)}_${_fnv1a32(pattern)}`;
1599
1613
  }
1600
1614
  function _deriveDenyMode(rule) {
1601
1615
  if (rule.deny_mode) return rule.deny_mode;
@@ -1881,10 +1895,13 @@ function generateSourceDefaults() {
1881
1895
  }
1882
1896
  function _ltlRulesToRuntime() {
1883
1897
  const cands = [
1884
- join3(__dirname4, "..", "rules"),
1885
- join3(__dirname4, "..", "..", "..", "agent_moss", "rules"),
1886
- join3(process.cwd(), "agent_moss", "rules")
1887
- ];
1898
+ getRuntimeConfigDir() && join3(getRuntimeConfigDir(), "rules"),
1899
+ join3(__dirname4, "../rules"),
1900
+ join3(__dirname4, "rules"),
1901
+ join3(__dirname4, "../../../agent_moss/rules"),
1902
+ join3(__dirname4, "../../../../agent_moss/rules"),
1903
+ join3(process.cwd(), "agent_moss/rules")
1904
+ ].filter(Boolean);
1888
1905
  let mc = {};
1889
1906
  for (const dir of cands) {
1890
1907
  const p = join3(dir, "mc_rules.json");
@@ -2244,16 +2261,20 @@ function addCustomRule(layer, category, rule) {
2244
2261
  saveRuntimeConfig(runtime);
2245
2262
  return runtime;
2246
2263
  }
2247
- function deleteCustomRule(layer, category, ruleId) {
2248
- const runtime = loadRuntimeConfig();
2249
- const rules = runtime[layer]?.[category]?.rules || [];
2250
- const idx = rules.findIndex((r) => r.id === ruleId);
2264
+ function pickDeletableRule(rules, ruleId) {
2265
+ const idx = rules.findIndex((r) => r.id === ruleId && (!r.builtin || r.source_removed));
2251
2266
  if (idx >= 0) {
2252
- const r = rules[idx];
2253
- if (r.builtin && !r.source_removed) return null;
2254
2267
  rules.splice(idx, 1);
2255
- saveRuntimeConfig(runtime);
2268
+ return rules;
2256
2269
  }
2270
+ if (rules.some((r) => r.id === ruleId)) return null;
2271
+ return rules;
2272
+ }
2273
+ function deleteCustomRule(layer, category, ruleId) {
2274
+ const runtime = loadRuntimeConfig();
2275
+ const rules = runtime[layer]?.[category]?.rules || [];
2276
+ if (pickDeletableRule(rules, ruleId) === null) return null;
2277
+ saveRuntimeConfig(runtime);
2257
2278
  return runtime;
2258
2279
  }
2259
2280
  function addCustomSkill(skillId, category, keywords, content) {
@@ -6492,8 +6513,36 @@ function allCandidates(detail, max = 8) {
6492
6513
  }
6493
6514
  return out;
6494
6515
  }
6516
+ var STAGE_PRIORITY = ["EXFIL", "PRIVESC", "PERSIST", "SCAN", "LISTEN"];
6517
+ var PURPOSE_EXEC_RE = /subprocess\.\w+\s*\(|os\.system\s*\(|shell\s*=\s*True|\beval\s*\(|\bexec\s*\(|os\.popen\s*\(|\/exec\b/i;
6518
+ var LISTEN_LEAD_RE = /http\.server|flask\s+run|nohup\b|nc\s+-l|socat\s+TCP-LISTEN/i;
6519
+ var LISTEN_NOISE_RE = /shell\s*=\s*True|os\.system|subprocess\.(?:run|call|Popen)|\beval\s*\(|\bexec\s*\(/i;
6495
6520
  function matchStageAny(detail, stages) {
6496
- for (const cand of allCandidates(detail)) {
6521
+ const cands = allCandidates(detail);
6522
+ if (cands.length === 0) return "";
6523
+ const shell = cands[0];
6524
+ const hits = [];
6525
+ for (const [name, pats] of Object.entries(stages)) {
6526
+ for (const p of pats) {
6527
+ p.lastIndex = 0;
6528
+ if (p.test(shell)) {
6529
+ hits.push(name);
6530
+ break;
6531
+ }
6532
+ }
6533
+ }
6534
+ if (hits.length > 0) {
6535
+ if (hits.length === 1) return hits[0];
6536
+ if (hits.includes("EXEC") && PURPOSE_EXEC_RE.test(shell)) return "EXEC";
6537
+ if (hits.includes("LISTEN") && LISTEN_LEAD_RE.test(shell) && !LISTEN_NOISE_RE.test(shell)) {
6538
+ return "LISTEN";
6539
+ }
6540
+ for (const s of STAGE_PRIORITY) {
6541
+ if (hits.includes(s)) return s;
6542
+ }
6543
+ if (hits.includes("EXEC")) return "EXEC";
6544
+ }
6545
+ for (const cand of cands.slice(1)) {
6497
6546
  for (const [name, pats] of Object.entries(stages)) {
6498
6547
  for (const p of pats) {
6499
6548
  p.lastIndex = 0;
@@ -6774,7 +6823,7 @@ function loadCrossSessionChains() {
6774
6823
  return CROSS_SESSION_CHAINS;
6775
6824
  }
6776
6825
  var CRED_FILE_RE = /(known_hosts|\.ssh|id_rsa|\/etc\/shadow|\/etc\/passwd|credentials)/i;
6777
- var EXFIL_RE = /https?:\/\/|scp\s+.*@|rsync\s+.*@/i;
6826
+ var EXFIL_RE = /https?:\/\/(?!127\.\d+\.\d+\.\d+|0\.0\.0\.0|localhost|\[?::1\]?)[^\s]+|\bscp\s+.*@|\brsync\s+.*@/i;
6778
6827
  function checkCrossSessionChain(graph) {
6779
6828
  const violations = [];
6780
6829
  const nodes = allNodes(graph);
@@ -6875,9 +6924,67 @@ function checkSensitiveDataFlow(graph) {
6875
6924
  }
6876
6925
  var EBPF_NET_RE = /connect|sendto|network_connect/i;
6877
6926
  var REMOTE_RE = /to\s+\d+\.\d+\.\d+\.\d+|remote_addr/i;
6878
- var PRIVESC_RE = /\bsudo\b|\bsu\b|chmod\s+u\s*\+\s*s|pkexec|setuid/i;
6879
6927
  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;
6880
6928
  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;
6929
+ 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;
6930
+ function extractInlineShell(s) {
6931
+ const out = [];
6932
+ let m;
6933
+ INLINE_SHELL_RE.lastIndex = 0;
6934
+ while ((m = INLINE_SHELL_RE.exec(s)) !== null) {
6935
+ let g = m[1] ?? m[2] ?? m[3] ?? m[4] ?? "";
6936
+ if (/^["']/.test(g) && (/"\s*,/.test(g) || /'\s*,/.test(g))) {
6937
+ g = g.replace(/['"\[\],]+/g, " ");
6938
+ }
6939
+ g = g.trim();
6940
+ if (g) out.push(g);
6941
+ }
6942
+ return out;
6943
+ }
6944
+ var PKG_MGR = /* @__PURE__ */ new Set(["apt", "apt-get", "aptitude", "yum", "dnf", "pacman", "zypper", "apk", "brew"]);
6945
+ var SHELLS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "fish", "ksh", "csh", "tcsh", "nologin"]);
6946
+ function containsPrivesc(shell) {
6947
+ if (!shell) return false;
6948
+ for (const seg of shell.split(/\&\&|\|\||;|\n/)) {
6949
+ const s = seg.trim();
6950
+ if (!s) continue;
6951
+ const toks = s.split(/\s+/);
6952
+ const head = (toks[0] || "").toLowerCase();
6953
+ const rest = toks.slice(1).join(" ");
6954
+ if (head === "chmod" && /(?:u\+s|g\+s|[2-7][0-7]{3})/.test(rest)) return true;
6955
+ if (head === "setcap" && /cap_/.test(rest)) return true;
6956
+ if (head === "pkexec" || head === "doas") return true;
6957
+ if (head === "sudo") {
6958
+ if (/^-u\s+/.test(rest)) {
6959
+ if (/^-u\s+root\b/.test(rest)) return true;
6960
+ continue;
6961
+ }
6962
+ const m = /^(\S+)(.*)$/.exec(rest);
6963
+ if (m) {
6964
+ const nxt = m[1].toLowerCase();
6965
+ const tail = m[2];
6966
+ if (PKG_MGR.has(nxt)) continue;
6967
+ if (SHELLS.has(nxt)) return true;
6968
+ if (nxt === "-i" || nxt === "-s") return true;
6969
+ if (nxt.startsWith("su")) return true;
6970
+ if (nxt.startsWith("/") && /bin\/(?:bash|sh|zsh|dash|fish)$/.test(nxt)) return true;
6971
+ }
6972
+ if (/\/(?:usr\/(?:local\/)?bin|bin)\/(?:bash|sh|zsh|dash|fish)\b/.test(rest)) return true;
6973
+ if (/\bchmod\s+(?:u\+s|g\+s|[2-7][0-7]{3})\b/.test(rest)) return true;
6974
+ if (/\bsetcap\s+cap_\w+/.test(rest)) return true;
6975
+ if (/\bvisudo\b/.test(rest)) return true;
6976
+ if (/\bpasswd\s+root\b/.test(rest)) return true;
6977
+ if (/\buseradd\b/.test(rest)) return true;
6978
+ }
6979
+ if (head === "su") {
6980
+ if (!rest) return true;
6981
+ if (/^-l?\s*$/.test(rest)) return true;
6982
+ if (/^root\b/.test(rest) || /^-l?\s+root\b/.test(rest)) return true;
6983
+ }
6984
+ }
6985
+ if (/\bset(?:uid|euid|gid|egid|reuid|regid|resuid|resgid)\s*\(/i.test(shell)) return true;
6986
+ return false;
6987
+ }
6881
6988
  function isExfilNode(n) {
6882
6989
  if (n.stage === "EXFIL") return true;
6883
6990
  if (EXFIL_RE.test(n.actionDetail) && EXFIL_ACTION_RE.test(n.actionDetail)) return true;
@@ -6892,8 +6999,12 @@ function isCredNode(n) {
6892
6999
  return CRED_STRICT_RE.test(n.actionDetail);
6893
7000
  }
6894
7001
  function isPrivescNode(n) {
6895
- if (n.stage === "PRIVESC") return true;
6896
- return PRIVESC_RE.test(n.actionDetail);
7002
+ const shell = stripStringLiterals(n.actionDetail);
7003
+ if (containsPrivesc(shell)) return true;
7004
+ for (const cmd of extractInlineShell(shell)) {
7005
+ if (containsPrivesc(cmd)) return true;
7006
+ }
7007
+ return false;
6897
7008
  }
6898
7009
  function checkNeverExfilAfterPrivesc(graph) {
6899
7010
  const violations = [];