@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/cli.js +152 -32
- package/dist/cli.js.map +1 -1
- package/dist/engine/coordinator.js +130 -19
- package/dist/engine/coordinator.js.map +1 -1
- package/dist/rules/l2_rules.json +242 -242
- package/dist/server.js +149 -30
- package/dist/server.js.map +1 -1
- package/dist/static/index.html +18 -4
- package/package.json +1 -1
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
|
|
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
|
-
|
|
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(
|
|
1889
|
-
join3(__dirname4, "
|
|
1890
|
-
join3(
|
|
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
|
|
2252
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
7777
|
-
|
|
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
|
-
|
|
9601
|
-
|
|
9602
|
-
const rls =
|
|
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);
|