@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
|
@@ -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
|
|
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
|
-
|
|
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(
|
|
1885
|
-
join3(__dirname4, "
|
|
1886
|
-
join3(
|
|
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
|
|
2248
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
6896
|
-
|
|
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 = [];
|