@kenhkl/agent-moss 0.12.9 → 0.14.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 +1124 -201
- package/dist/cli.js.map +1 -1
- package/dist/engine/coordinator.d.ts +1 -1
- package/dist/engine/coordinator.js +899 -149
- package/dist/engine/coordinator.js.map +1 -1
- package/dist/rules/l1_rules.json +28 -6
- package/dist/rules/l2_rules.json +88 -60
- package/dist/rules/mc_rules.json +369 -0
- package/dist/rules/tool_registry.json +25 -0
- package/dist/scenarios/__init__.py +29 -0
- package/dist/scenarios/__pycache__/__init__.cpython-312.pyc +0 -0
- package/dist/server.js +1108 -193
- package/dist/server.js.map +1 -1
- package/dist/static/index.html +85 -9
- package/package.json +1 -1
|
@@ -2,6 +2,12 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
4
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
6
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
7
|
+
}) : x)(function(x) {
|
|
8
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
9
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
10
|
+
});
|
|
5
11
|
var __esm = (fn, res) => function __init() {
|
|
6
12
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
7
13
|
};
|
|
@@ -127,6 +133,7 @@ __export(config_exports, {
|
|
|
127
133
|
getRetryConfig: () => getRetryConfig,
|
|
128
134
|
getTimeoutConfig: () => getTimeoutConfig,
|
|
129
135
|
isBrainEnabled: () => isBrainEnabled,
|
|
136
|
+
isFormalObserveMode: () => isFormalObserveMode,
|
|
130
137
|
isHeuristicEnabled: () => isHeuristicEnabled,
|
|
131
138
|
isLLMAvailable: () => isLLMAvailable,
|
|
132
139
|
isLogicRulesEnabled: () => isLogicRulesEnabled,
|
|
@@ -242,6 +249,7 @@ function getCustomRules() {
|
|
|
242
249
|
}
|
|
243
250
|
function isLLMAvailable() {
|
|
244
251
|
if (process.env.AGENT_MOSS_DISABLE_LLM === "1") return false;
|
|
252
|
+
if (!layerEnabledOrDefault("L3_llm_analysis")) return false;
|
|
245
253
|
return llmConfig.apiKey.length > 0;
|
|
246
254
|
}
|
|
247
255
|
async function probeLLMConnection(timeoutMs = 8e3) {
|
|
@@ -306,13 +314,26 @@ function getProviderHeaders(provider) {
|
|
|
306
314
|
return PROVIDER_HEADERS[provider] || {};
|
|
307
315
|
}
|
|
308
316
|
function isHeuristicEnabled() {
|
|
309
|
-
|
|
317
|
+
if (process.env.AGENT_MOSS_DISABLE_HEURISTIC === "1") return false;
|
|
318
|
+
return layerEnabledOrDefault("L1_heuristic");
|
|
319
|
+
}
|
|
320
|
+
function layerEnabledOrDefault(key) {
|
|
321
|
+
try {
|
|
322
|
+
const { isLayerEnabled: isLayerEnabled2 } = __require("../infra/runtime-config");
|
|
323
|
+
return isLayerEnabled2(key);
|
|
324
|
+
} catch {
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
310
327
|
}
|
|
311
328
|
function isBrainEnabled() {
|
|
312
329
|
return process.env.AGENT_MOSS_BRAIN_ENABLED === "1";
|
|
313
330
|
}
|
|
314
331
|
function isLogicRulesEnabled() {
|
|
315
|
-
|
|
332
|
+
if (process.env.AGENT_MOSS_DISABLE_LOGIC_RULES === "1") return false;
|
|
333
|
+
return layerEnabledOrDefault("L2_logic_rules");
|
|
334
|
+
}
|
|
335
|
+
function isFormalObserveMode() {
|
|
336
|
+
return process.env.AGENT_MOSS_FORMAL_OBSERVE === "1";
|
|
316
337
|
}
|
|
317
338
|
var timeoutConfig, retryConfig, llmConfig, __configModuleDir, USER_RULES_PATHS, customRules, PROVIDER_HEADERS;
|
|
318
339
|
var init_config = __esm({
|
|
@@ -570,8 +591,12 @@ var init_inline_analyzer = __esm({
|
|
|
570
591
|
|
|
571
592
|
// src/infra/rules-loader.ts
|
|
572
593
|
import { readFileSync as readFileSync2, existsSync } from "fs";
|
|
573
|
-
import { resolve, dirname } from "path";
|
|
594
|
+
import { resolve, dirname, join as join2 } from "path";
|
|
574
595
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
596
|
+
import { homedir as homedir2 } from "os";
|
|
597
|
+
function dataRootDir() {
|
|
598
|
+
return process.env.AGENT_MOSS_DATA_DIR || join2(homedir2(), ".config", "agentmoss");
|
|
599
|
+
}
|
|
575
600
|
function findRulesFile(name) {
|
|
576
601
|
for (const dir of RULES_DIR_CANDS) {
|
|
577
602
|
const p = resolve(dir, name);
|
|
@@ -597,6 +622,24 @@ function loadL2() {
|
|
|
597
622
|
function loadL3() {
|
|
598
623
|
return loadJson("l3_rules.json");
|
|
599
624
|
}
|
|
625
|
+
function loadMC() {
|
|
626
|
+
const mc = loadJson("mc_rules.json");
|
|
627
|
+
const rules = mc.rules || [];
|
|
628
|
+
try {
|
|
629
|
+
const rt = (init_runtime_config(), __toCommonJS(runtime_config_exports));
|
|
630
|
+
if (rules.length > 0 && typeof rt.hasRuntimeConfig === "function" && rt.hasRuntimeConfig()) {
|
|
631
|
+
const rtRules = rt.loadRuntimeConfig()?.L2_2?.ltl_rules?.rules || [];
|
|
632
|
+
const enabledById = {};
|
|
633
|
+
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
|
+
}
|
|
636
|
+
} catch {
|
|
637
|
+
}
|
|
638
|
+
return mc;
|
|
639
|
+
}
|
|
640
|
+
function loadToolRegistry() {
|
|
641
|
+
return loadJson("tool_registry.json");
|
|
642
|
+
}
|
|
600
643
|
var __filename2, __dirname2, RULES_DIR_CANDS;
|
|
601
644
|
var init_rules_loader = __esm({
|
|
602
645
|
"src/infra/rules-loader.ts"() {
|
|
@@ -606,6 +649,10 @@ var init_rules_loader = __esm({
|
|
|
606
649
|
__dirname2 = dirname(__filename2);
|
|
607
650
|
RULES_DIR_CANDS = (() => {
|
|
608
651
|
const cands = [
|
|
652
|
+
// 用户数据目录覆盖优先(与 Python rules_loader._load_json 的
|
|
653
|
+
// <data_root>/rules/<name> 优先逻辑对齐)。用户安装 pip/npm 包后把自定义
|
|
654
|
+
// mc_rules.json 放到 <data_root>/rules/ 即可覆盖内置默认,无需改源码。
|
|
655
|
+
resolve(dataRootDir(), "rules"),
|
|
609
656
|
// dist: __dirname=dist/ → dist/rules;__dirname=dist/engine/ → dist/engine/../rules=dist/rules
|
|
610
657
|
resolve(__dirname2, "../rules"),
|
|
611
658
|
resolve(__dirname2, "rules"),
|
|
@@ -629,7 +676,8 @@ function _loadSensitivePathScope(scope) {
|
|
|
629
676
|
path: sp.path,
|
|
630
677
|
riskLevel: sp.risk_level,
|
|
631
678
|
desc: sp.desc,
|
|
632
|
-
credential: sp.deny_mode === "deny_both" ? true : void 0
|
|
679
|
+
credential: sp.deny_mode === "deny_both" ? true : void 0,
|
|
680
|
+
dirDelOnly: sp.dir_del_only === true ? true : void 0
|
|
633
681
|
}));
|
|
634
682
|
}
|
|
635
683
|
function isWriteOperation(actionType, actionDetail, writeKeywords = WRITE_KEYWORDS) {
|
|
@@ -727,7 +775,7 @@ function runLogicCheck(input) {
|
|
|
727
775
|
riskType: "intent_deviation",
|
|
728
776
|
confidence: 85,
|
|
729
777
|
source: "logic_rule",
|
|
730
|
-
violatedLayers: ["
|
|
778
|
+
violatedLayers: ["L2.1"]
|
|
731
779
|
};
|
|
732
780
|
}
|
|
733
781
|
}
|
|
@@ -751,7 +799,7 @@ function runLogicCheck(input) {
|
|
|
751
799
|
riskType: "privilege_escalation",
|
|
752
800
|
confidence: 85,
|
|
753
801
|
source: "logic_rule",
|
|
754
|
-
violatedLayers: ["
|
|
802
|
+
violatedLayers: ["L2.1"]
|
|
755
803
|
};
|
|
756
804
|
}
|
|
757
805
|
}
|
|
@@ -803,7 +851,7 @@ function checkComboChain(actionHistory, actionDetailRaw) {
|
|
|
803
851
|
riskType: "combo_chain",
|
|
804
852
|
confidence: 85,
|
|
805
853
|
source: "logic_rule",
|
|
806
|
-
violatedLayers: ["
|
|
854
|
+
violatedLayers: ["L2.1"]
|
|
807
855
|
};
|
|
808
856
|
}
|
|
809
857
|
}
|
|
@@ -928,7 +976,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
|
|
|
928
976
|
riskType: "read_before_write",
|
|
929
977
|
confidence: 85,
|
|
930
978
|
source: "logic_rule",
|
|
931
|
-
violatedLayers: ["
|
|
979
|
+
violatedLayers: ["L2.1"]
|
|
932
980
|
};
|
|
933
981
|
}
|
|
934
982
|
return null;
|
|
@@ -948,7 +996,7 @@ function checkPasswordConsent(actionType, actionDetail, actionHistory) {
|
|
|
948
996
|
riskType: "consent_missing",
|
|
949
997
|
confidence: 85,
|
|
950
998
|
source: "logic_rule",
|
|
951
|
-
violatedLayers: ["
|
|
999
|
+
violatedLayers: ["L2.1"]
|
|
952
1000
|
};
|
|
953
1001
|
}
|
|
954
1002
|
function checkUserDeletionConsent(actionType, actionDetail, actionHistory) {
|
|
@@ -966,7 +1014,7 @@ function checkUserDeletionConsent(actionType, actionDetail, actionHistory) {
|
|
|
966
1014
|
riskType: "consent_missing",
|
|
967
1015
|
confidence: 85,
|
|
968
1016
|
source: "logic_rule",
|
|
969
|
-
violatedLayers: ["
|
|
1017
|
+
violatedLayers: ["L2.1"]
|
|
970
1018
|
};
|
|
971
1019
|
}
|
|
972
1020
|
function containsPrivilegeEscalation(actionDetail) {
|
|
@@ -1026,7 +1074,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
|
|
|
1026
1074
|
riskType: "file_access",
|
|
1027
1075
|
confidence: 95,
|
|
1028
1076
|
source: "logic_rule",
|
|
1029
|
-
violatedLayers: ["
|
|
1077
|
+
violatedLayers: ["L2.1"],
|
|
1030
1078
|
isCredential: true
|
|
1031
1079
|
};
|
|
1032
1080
|
}
|
|
@@ -1042,7 +1090,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
|
|
|
1042
1090
|
riskType: "file_access",
|
|
1043
1091
|
confidence: 95,
|
|
1044
1092
|
source: "logic_rule",
|
|
1045
|
-
violatedLayers: ["
|
|
1093
|
+
violatedLayers: ["L2.1"]
|
|
1046
1094
|
};
|
|
1047
1095
|
}
|
|
1048
1096
|
}
|
|
@@ -1056,7 +1104,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
|
|
|
1056
1104
|
riskType: "file_access",
|
|
1057
1105
|
confidence: 70,
|
|
1058
1106
|
source: "logic_rule",
|
|
1059
|
-
violatedLayers: ["
|
|
1107
|
+
violatedLayers: ["L2.1"]
|
|
1060
1108
|
};
|
|
1061
1109
|
}
|
|
1062
1110
|
var ROOT_SENSITIVE_PATHS, HOME_SENSITIVE_PATHS, CREDENTIAL_PATHS, NON_WRITE_TOOLS, NON_WRITE_TOOL_PREFIXES, WRITE_KEYWORDS, WRITE_COMMAND_RES, READ_ONLY_COMMANDS, DEVNULL_REDIRECT_RE, READ_KEYWORDS, CREATE_KEYWORDS, PASSWORD_MODIFY_PATTERNS, USER_DELETION_PATTERNS, INTENT_DEVIATION_PATTERNS, COMBO_STAGES, COMBO_CHAINS, INLINE_SCRIPT_RE, DIR_TRAVERSAL_KW;
|
|
@@ -1068,7 +1116,7 @@ var init_logic_rules = __esm({
|
|
|
1068
1116
|
init_rules_loader();
|
|
1069
1117
|
ROOT_SENSITIVE_PATHS = _loadSensitivePathScope("root");
|
|
1070
1118
|
HOME_SENSITIVE_PATHS = _loadSensitivePathScope("home");
|
|
1071
|
-
CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path5, riskLevel, desc }) => ({ path: path5, riskLevel, desc, credential: true }));
|
|
1119
|
+
CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path5, riskLevel, desc, dirDelOnly }) => ({ path: path5, riskLevel, desc, credential: true, dirDelOnly }));
|
|
1072
1120
|
NON_WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
1073
1121
|
"ask_user_question",
|
|
1074
1122
|
"glob",
|
|
@@ -1343,6 +1391,7 @@ var init_skill_loader = __esm({
|
|
|
1343
1391
|
// src/infra/runtime-config.ts
|
|
1344
1392
|
var runtime_config_exports = {};
|
|
1345
1393
|
__export(runtime_config_exports, {
|
|
1394
|
+
DEFAULT_LAYERS: () => DEFAULT_LAYERS,
|
|
1346
1395
|
DENY_MODE_LABELS: () => DENY_MODE_LABELS,
|
|
1347
1396
|
addAgentRule: () => addAgentRule,
|
|
1348
1397
|
addCustomRule: () => addCustomRule,
|
|
@@ -1394,7 +1443,7 @@ __export(runtime_config_exports, {
|
|
|
1394
1443
|
updateSkillEnabled: () => updateSkillEnabled
|
|
1395
1444
|
});
|
|
1396
1445
|
import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync4, mkdirSync, unlinkSync, readdirSync as readdirSync3 } from "fs";
|
|
1397
|
-
import { join as
|
|
1446
|
+
import { join as join3 } from "path";
|
|
1398
1447
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
1399
1448
|
import { dirname as dirname3 } from "path";
|
|
1400
1449
|
function runtimeDir() {
|
|
@@ -1418,6 +1467,7 @@ function emptyRuntimeConfig() {
|
|
|
1418
1467
|
L1_rules: {},
|
|
1419
1468
|
L2_rules: {},
|
|
1420
1469
|
L3_skills: {},
|
|
1470
|
+
L2_2: {},
|
|
1421
1471
|
agent_rules: {},
|
|
1422
1472
|
scenario_rules: {}
|
|
1423
1473
|
};
|
|
@@ -1445,6 +1495,7 @@ function loadRuntimeConfig() {
|
|
|
1445
1495
|
L1_rules: merged.L1_rules || {},
|
|
1446
1496
|
L2_rules: merged.L2_rules || {},
|
|
1447
1497
|
L3_skills: merged.L3_skills || {},
|
|
1498
|
+
L2_2: merged.L2_2 || {},
|
|
1448
1499
|
agent_rules: merged.agent_rules || {},
|
|
1449
1500
|
scenario_rules: merged.scenario_rules || {}
|
|
1450
1501
|
};
|
|
@@ -1497,6 +1548,7 @@ function getEnabledL2SensitivePaths(runtime) {
|
|
|
1497
1548
|
const out = { path: r.path || "", risk_level: r.risk_level || "", desc: r.desc || "", deny_mode: r.deny_mode || "deny_write" };
|
|
1498
1549
|
if (r.credential) out.credential = true;
|
|
1499
1550
|
if (r.read_only) out.read_only = true;
|
|
1551
|
+
if (r.dir_del_only) out.dir_del_only = true;
|
|
1500
1552
|
return out;
|
|
1501
1553
|
});
|
|
1502
1554
|
}
|
|
@@ -1605,6 +1657,7 @@ function _l2SensitivePathsToRules() {
|
|
|
1605
1657
|
builtin: true
|
|
1606
1658
|
};
|
|
1607
1659
|
_syncDenyModeFields(rule);
|
|
1660
|
+
if (sp.dirDelOnly) rule.dir_del_only = true;
|
|
1608
1661
|
return rule;
|
|
1609
1662
|
});
|
|
1610
1663
|
}
|
|
@@ -1692,7 +1745,7 @@ function _scenarioRulesToDefaults() {
|
|
|
1692
1745
|
return out;
|
|
1693
1746
|
}
|
|
1694
1747
|
for (const scenarioId of entries) {
|
|
1695
|
-
const rulesFile =
|
|
1748
|
+
const rulesFile = join3(scenDir, scenarioId, "rules.json");
|
|
1696
1749
|
if (!existsSync4(rulesFile)) continue;
|
|
1697
1750
|
let data;
|
|
1698
1751
|
try {
|
|
@@ -1741,7 +1794,7 @@ function _agentRulesToDefaults() {
|
|
|
1741
1794
|
return out;
|
|
1742
1795
|
}
|
|
1743
1796
|
for (const agentId of entries) {
|
|
1744
|
-
const rulesFile =
|
|
1797
|
+
const rulesFile = join3(agentsDir, agentId, "rules.json");
|
|
1745
1798
|
if (!existsSync4(rulesFile)) continue;
|
|
1746
1799
|
let data;
|
|
1747
1800
|
try {
|
|
@@ -1782,7 +1835,7 @@ function _agentRulesToDefaults() {
|
|
|
1782
1835
|
function generateSourceDefaults() {
|
|
1783
1836
|
return {
|
|
1784
1837
|
version: 1,
|
|
1785
|
-
layers: {
|
|
1838
|
+
layers: { ...DEFAULT_LAYERS },
|
|
1786
1839
|
L1_rules: {
|
|
1787
1840
|
dangerous_commands: {
|
|
1788
1841
|
category_enabled: true,
|
|
@@ -1821,10 +1874,45 @@ function generateSourceDefaults() {
|
|
|
1821
1874
|
user_custom: { category_enabled: true, category_desc: "\u7528\u6237\u81EA\u5B9A\u4E49 L2 \u903B\u8F91\u89C4\u5219", rules: [] }
|
|
1822
1875
|
},
|
|
1823
1876
|
L3_skills: _l3SkillsToRules(),
|
|
1877
|
+
L2_2: _ltlRulesToRuntime(),
|
|
1824
1878
|
agent_rules: _agentRulesToDefaults(),
|
|
1825
1879
|
scenario_rules: _scenarioRulesToDefaults()
|
|
1826
1880
|
};
|
|
1827
1881
|
}
|
|
1882
|
+
function _ltlRulesToRuntime() {
|
|
1883
|
+
const cands = [
|
|
1884
|
+
join3(__dirname4, "..", "rules"),
|
|
1885
|
+
join3(__dirname4, "..", "..", "..", "agent_moss", "rules"),
|
|
1886
|
+
join3(process.cwd(), "agent_moss", "rules")
|
|
1887
|
+
];
|
|
1888
|
+
let mc = {};
|
|
1889
|
+
for (const dir of cands) {
|
|
1890
|
+
const p = join3(dir, "mc_rules.json");
|
|
1891
|
+
if (existsSync4(p)) {
|
|
1892
|
+
try {
|
|
1893
|
+
mc = JSON.parse(readFileSync4(p, "utf-8")) || {};
|
|
1894
|
+
} catch {
|
|
1895
|
+
mc = {};
|
|
1896
|
+
}
|
|
1897
|
+
break;
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
const rules = mc.rules || [];
|
|
1901
|
+
return {
|
|
1902
|
+
ltl_rules: {
|
|
1903
|
+
category_enabled: true,
|
|
1904
|
+
category_desc: "LTL \u653B\u51FB\u6A21\u5F0F\u89C4\u5219\uFF08\u65F6\u5E8F\u653B\u51FB\u68C0\u6D4B\uFF0C\u5148 A \u540E B\uFF09",
|
|
1905
|
+
rules: rules.map((r) => ({
|
|
1906
|
+
id: r.id,
|
|
1907
|
+
reason: r.reason || "",
|
|
1908
|
+
risk_level: r.risk_level || "high",
|
|
1909
|
+
action: r.action || "block",
|
|
1910
|
+
enabled: true,
|
|
1911
|
+
builtin: true
|
|
1912
|
+
}))
|
|
1913
|
+
}
|
|
1914
|
+
};
|
|
1915
|
+
}
|
|
1828
1916
|
function normalizePatternForFingerprint(pattern) {
|
|
1829
1917
|
return pattern.replace(/\\\//g, "/");
|
|
1830
1918
|
}
|
|
@@ -1974,6 +2062,11 @@ function mergeConfig(userCfg, srcDefaults) {
|
|
|
1974
2062
|
);
|
|
1975
2063
|
_mergeRuleCategories(cfg, srcDefaults, "L1_rules");
|
|
1976
2064
|
_mergeRuleCategories(cfg, srcDefaults, "L2_rules");
|
|
2065
|
+
if (!cfg.L2_2 && cfg.L2_4) {
|
|
2066
|
+
cfg.L2_2 = cfg.L2_4;
|
|
2067
|
+
delete cfg.L2_4;
|
|
2068
|
+
}
|
|
2069
|
+
_mergeRuleCategories(cfg, srcDefaults, "L2_2");
|
|
1977
2070
|
_mergeSkillCategories(cfg, srcDefaults, "L3_skills");
|
|
1978
2071
|
_mergeAgentRules(cfg, srcDefaults);
|
|
1979
2072
|
_mergeAgentRules(cfg, srcDefaults, "scenario_rules");
|
|
@@ -2166,12 +2259,12 @@ function deleteCustomRule(layer, category, ruleId) {
|
|
|
2166
2259
|
function addCustomSkill(skillId, category, keywords, content) {
|
|
2167
2260
|
_validateSkillId(skillId);
|
|
2168
2261
|
const runtime = loadRuntimeConfig();
|
|
2169
|
-
const skillsDir =
|
|
2262
|
+
const skillsDir = join3(RUNTIME_CONFIG_DIR, "agent_moss_skills");
|
|
2170
2263
|
try {
|
|
2171
2264
|
mkdirSync(skillsDir, { recursive: true });
|
|
2172
2265
|
} catch {
|
|
2173
2266
|
}
|
|
2174
|
-
writeFileSync(
|
|
2267
|
+
writeFileSync(join3(skillsDir, `${skillId}.md`), content, "utf-8");
|
|
2175
2268
|
const l3 = runtime.L3_skills || (runtime.L3_skills = {});
|
|
2176
2269
|
const cat = l3[category] || (l3[category] = { category_enabled: true, category_desc: "", skills: [] });
|
|
2177
2270
|
(cat.skills || (cat.skills = [])).push({ id: skillId, keywords, enabled: true, builtin: false });
|
|
@@ -2186,7 +2279,7 @@ function deleteCustomSkill(skillId, category) {
|
|
|
2186
2279
|
if (idx >= 0) {
|
|
2187
2280
|
if (skills[idx].builtin) return null;
|
|
2188
2281
|
skills.splice(idx, 1);
|
|
2189
|
-
const skillPath =
|
|
2282
|
+
const skillPath = join3(RUNTIME_CONFIG_DIR, "agent_moss_skills", `${skillId}.md`);
|
|
2190
2283
|
try {
|
|
2191
2284
|
if (existsSync4(skillPath)) unlinkSync(skillPath);
|
|
2192
2285
|
} catch {
|
|
@@ -2380,7 +2473,7 @@ function updateScenarioCategoryEnabled(scenarioId, category, enabled) {
|
|
|
2380
2473
|
saveRuntimeConfig(runtime);
|
|
2381
2474
|
return runtime;
|
|
2382
2475
|
}
|
|
2383
|
-
var __dirname4, ENV_RUNTIME_CONFIG_PATH, RUNTIME_CONFIG_DIR, RUNTIME_CONFIG_PATH, LAYER_DISABLE_ENV, DENY_MODE_LABELS, _AGENTS_DIR_PROD, _AGENTS_DIR_DEV, _SCENARIOS_DIR_PROD, _SCENARIOS_DIR_DEV, SKILL_ID_RE;
|
|
2476
|
+
var __dirname4, ENV_RUNTIME_CONFIG_PATH, RUNTIME_CONFIG_DIR, RUNTIME_CONFIG_PATH, LAYER_DISABLE_ENV, DEFAULT_LAYERS, DENY_MODE_LABELS, _AGENTS_DIR_PROD, _AGENTS_DIR_DEV, _SCENARIOS_DIR_PROD, _SCENARIOS_DIR_DEV, SKILL_ID_RE;
|
|
2384
2477
|
var init_runtime_config = __esm({
|
|
2385
2478
|
"src/infra/runtime-config.ts"() {
|
|
2386
2479
|
"use strict";
|
|
@@ -2392,21 +2485,28 @@ var init_runtime_config = __esm({
|
|
|
2392
2485
|
__dirname4 = dirname3(fileURLToPath5(import.meta.url));
|
|
2393
2486
|
ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
|
|
2394
2487
|
RUNTIME_CONFIG_DIR = runtimeDir();
|
|
2395
|
-
RUNTIME_CONFIG_PATH =
|
|
2488
|
+
RUNTIME_CONFIG_PATH = join3(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
|
|
2396
2489
|
LAYER_DISABLE_ENV = {
|
|
2397
2490
|
L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
|
|
2398
2491
|
L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
|
|
2399
2492
|
L3_llm_analysis: "AGENT_MOSS_DISABLE_LLM"
|
|
2400
2493
|
};
|
|
2494
|
+
DEFAULT_LAYERS = {
|
|
2495
|
+
L1_heuristic: true,
|
|
2496
|
+
L2_logic_rules: true,
|
|
2497
|
+
L3_llm_analysis: true,
|
|
2498
|
+
agent_rules_enabled: true,
|
|
2499
|
+
scenario_rules_enabled: true
|
|
2500
|
+
};
|
|
2401
2501
|
DENY_MODE_LABELS = {
|
|
2402
2502
|
deny_write: "\u4EC5\u62E6\u622A\u5199\u5165",
|
|
2403
2503
|
deny_read: "\u4EC5\u62E6\u622A\u8BFB\u53D6",
|
|
2404
2504
|
deny_both: "\u8BFB\u5199\u5747\u62E6\u622A"
|
|
2405
2505
|
};
|
|
2406
|
-
_AGENTS_DIR_PROD =
|
|
2407
|
-
_AGENTS_DIR_DEV =
|
|
2408
|
-
_SCENARIOS_DIR_PROD =
|
|
2409
|
-
_SCENARIOS_DIR_DEV =
|
|
2506
|
+
_AGENTS_DIR_PROD = join3(__dirname4, "agents");
|
|
2507
|
+
_AGENTS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "agents");
|
|
2508
|
+
_SCENARIOS_DIR_PROD = join3(__dirname4, "scenarios");
|
|
2509
|
+
_SCENARIOS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
|
|
2410
2510
|
SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
|
|
2411
2511
|
}
|
|
2412
2512
|
});
|
|
@@ -2544,11 +2644,21 @@ function getScenarioExtensions(scenarioId) {
|
|
|
2544
2644
|
);
|
|
2545
2645
|
return ext;
|
|
2546
2646
|
}
|
|
2647
|
+
function getScenarioExtensionsMulti(scenarioSpec) {
|
|
2648
|
+
if (!scenarioSpec) return null;
|
|
2649
|
+
const ids = scenarioSpec.split(",").map((s) => s.trim()).filter(Boolean);
|
|
2650
|
+
if (ids.length === 0) return null;
|
|
2651
|
+
const exts = ids.map((id) => getScenarioExtensions(id));
|
|
2652
|
+
const nonNull = exts.filter((e) => e !== null);
|
|
2653
|
+
if (nonNull.length === 0) return null;
|
|
2654
|
+
return mergeAgentExtensions(...nonNull);
|
|
2655
|
+
}
|
|
2547
2656
|
var __filename5, __dirname6, SCENARIOS_DIR_PROD, SCENARIOS_DIR_DEV_FALLBACK, cache2;
|
|
2548
2657
|
var init_scenarios = __esm({
|
|
2549
2658
|
"src/scenarios/index.ts"() {
|
|
2550
2659
|
"use strict";
|
|
2551
2660
|
init_esm_shims();
|
|
2661
|
+
init_agents();
|
|
2552
2662
|
__filename5 = fileURLToPath7(import.meta.url);
|
|
2553
2663
|
__dirname6 = dirname5(__filename5);
|
|
2554
2664
|
SCENARIOS_DIR_PROD = resolve4(__dirname6, "scenarios");
|
|
@@ -2576,7 +2686,7 @@ function runHeuristicCheck(input) {
|
|
|
2576
2686
|
riskType: "sensitive_action",
|
|
2577
2687
|
confidence: 90,
|
|
2578
2688
|
source: "heuristic",
|
|
2579
|
-
violatedLayers: ["
|
|
2689
|
+
violatedLayers: ["L1.1"]
|
|
2580
2690
|
};
|
|
2581
2691
|
}
|
|
2582
2692
|
}
|
|
@@ -2586,8 +2696,10 @@ function runHeuristicCheck(input) {
|
|
|
2586
2696
|
const scanDetail = normalizePathSegments(stripLiteralTextSegments(input.aNext.actionDetail));
|
|
2587
2697
|
const scanCommand = `${input.aNext.actionType}: ${scanDetail}`;
|
|
2588
2698
|
let topRiskType = "";
|
|
2699
|
+
const exemptKeygen = isSshKeygenGenerate(input.aNext.actionDetail);
|
|
2589
2700
|
for (const rule of DANGEROUS_PATTERNS) {
|
|
2590
2701
|
if (rule.pattern.test(scanCommand)) {
|
|
2702
|
+
if (exemptKeygen && rule.riskType === "file_access") continue;
|
|
2591
2703
|
matched.push(rule.description);
|
|
2592
2704
|
if (severityRank(rule.severity) > severityRank(highestSeverity)) {
|
|
2593
2705
|
highestSeverity = rule.severity;
|
|
@@ -2597,8 +2709,8 @@ function runHeuristicCheck(input) {
|
|
|
2597
2709
|
}
|
|
2598
2710
|
highestRiskType = topRiskType || highestRiskType;
|
|
2599
2711
|
const agentExt = mergeAgentExtensions(
|
|
2600
|
-
getAgentExtensions(input.agentId),
|
|
2601
|
-
|
|
2712
|
+
isLayerEnabled("agent_rules_enabled") ? getAgentExtensions(input.agentId) : null,
|
|
2713
|
+
isLayerEnabled("scenario_rules_enabled") ? getScenarioExtensionsMulti(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "") : null
|
|
2602
2714
|
);
|
|
2603
2715
|
if (agentExt?.extra_command_patterns) {
|
|
2604
2716
|
for (const rule of agentExt.extra_command_patterns) {
|
|
@@ -2629,10 +2741,26 @@ function runHeuristicCheck(input) {
|
|
|
2629
2741
|
const detailL = input.aNext.actionDetail.toLowerCase();
|
|
2630
2742
|
const isExcludedType = SENSITIVE_EXCLUDED_PREFIXES.some((p) => actionTypeL.startsWith(p));
|
|
2631
2743
|
const dangerousResult = checkDangerousPatterns(actionTypeL, detailL);
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2744
|
+
const pathResult = isExcludedType ? null : checkSensitivePath(detailL, input.cwd || "/", agentExt);
|
|
2745
|
+
const homeResult = checkHomeDeletion(actionTypeL, detailL);
|
|
2746
|
+
const mergeResults = [dangerousResult, pathResult, homeResult].filter(Boolean);
|
|
2747
|
+
if (mergeResults.length > 0) {
|
|
2748
|
+
let best = mergeResults[0];
|
|
2749
|
+
for (const r of mergeResults) {
|
|
2750
|
+
if (severityRank(r.riskLevel) > severityRank(best.riskLevel)) best = r;
|
|
2751
|
+
}
|
|
2752
|
+
if (severityRank(highestSeverity) > severityRank(best.riskLevel)) {
|
|
2753
|
+
return {
|
|
2754
|
+
allowed: false,
|
|
2755
|
+
reason: `\u68C0\u6D4B\u5230\u9AD8\u5371\u64CD\u4F5C: ${matched.join(", ")}`,
|
|
2756
|
+
riskLevel: highestSeverity,
|
|
2757
|
+
riskType: highestRiskType,
|
|
2758
|
+
confidence: 95,
|
|
2759
|
+
source: "heuristic",
|
|
2760
|
+
violatedLayers: ["L1.1"]
|
|
2761
|
+
};
|
|
2762
|
+
}
|
|
2763
|
+
return best;
|
|
2636
2764
|
}
|
|
2637
2765
|
if (matched.length > 0) {
|
|
2638
2766
|
return {
|
|
@@ -2642,8 +2770,7 @@ function runHeuristicCheck(input) {
|
|
|
2642
2770
|
riskType: highestRiskType,
|
|
2643
2771
|
confidence: 95,
|
|
2644
2772
|
source: "heuristic",
|
|
2645
|
-
violatedLayers: ["
|
|
2646
|
-
// 对齐 py coordinator.py:165(L1.1 = 命中高危命令模式/注入关键词)
|
|
2773
|
+
violatedLayers: ["L1.1"]
|
|
2647
2774
|
};
|
|
2648
2775
|
}
|
|
2649
2776
|
const lateralResult = checkLateralMovement(detailL);
|
|
@@ -2771,6 +2898,7 @@ function isSearchActionType(actionType) {
|
|
|
2771
2898
|
function checkSensitivePath(command, cwd, agentExt) {
|
|
2772
2899
|
const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
|
|
2773
2900
|
const isWriteOpFlag = isWriteOperation("", command);
|
|
2901
|
+
const keygenGenerate = isSshKeygenGenerate(command);
|
|
2774
2902
|
const pathScanText = normalizePathSegments(stripLiteralTextSegments(command));
|
|
2775
2903
|
const pathMatches = (text, path5) => {
|
|
2776
2904
|
const parts = path5.split(/(\(\?!.*?\))/);
|
|
@@ -2783,6 +2911,7 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2783
2911
|
};
|
|
2784
2912
|
for (const sp of ROOT_SENSITIVE_PATHS) {
|
|
2785
2913
|
if (pathMatches(pathScanText, sp.path.toLowerCase())) {
|
|
2914
|
+
if (keygenGenerate) continue;
|
|
2786
2915
|
if (sp.path === "/dev/random") {
|
|
2787
2916
|
if (isWriteOpFlag) {
|
|
2788
2917
|
continue;
|
|
@@ -2794,7 +2923,7 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2794
2923
|
riskType: "file_access",
|
|
2795
2924
|
confidence: 90,
|
|
2796
2925
|
source: "heuristic",
|
|
2797
|
-
violatedLayers: ["
|
|
2926
|
+
violatedLayers: ["L1.1"]
|
|
2798
2927
|
};
|
|
2799
2928
|
}
|
|
2800
2929
|
if (!isWriteOpFlag && !sp.credential) {
|
|
@@ -2807,16 +2936,51 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2807
2936
|
riskType: "file_access",
|
|
2808
2937
|
confidence: 90,
|
|
2809
2938
|
source: "heuristic",
|
|
2810
|
-
violatedLayers: ["
|
|
2939
|
+
violatedLayers: ["L1.1"],
|
|
2811
2940
|
isCredential: !!sp.credential
|
|
2812
2941
|
};
|
|
2813
2942
|
}
|
|
2814
2943
|
}
|
|
2815
2944
|
for (const sp of HOME_SENSITIVE_PATHS) {
|
|
2816
2945
|
const expanded = `${expandedCwd}/${sp.path}`;
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2946
|
+
let pPath = sp.path;
|
|
2947
|
+
if (!sp.path.endsWith(".pub")) pPath = `${sp.path}(?!\\.pub)`;
|
|
2948
|
+
let dirPrefixMatch = null;
|
|
2949
|
+
if (sp.dirDelOnly) {
|
|
2950
|
+
const dirRe = (t) => new RegExp(`(?:^|[\\s/\\\\'"])${t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:/|[\\s'"\\\\]|$)`, "i");
|
|
2951
|
+
dirPrefixMatch = dirRe(sp.path).test(pathScanText) || dirRe(expanded.toLowerCase()).test(pathScanText);
|
|
2952
|
+
}
|
|
2953
|
+
const pathReMatch = sp.dirDelOnly ? !!dirPrefixMatch : pathMatches(pathScanText, pPath);
|
|
2954
|
+
const expandedReMatch = sp.dirDelOnly ? false : pathMatches(pathScanText, expanded + (sp.path.endsWith(".pub") ? "" : "(?!\\.pub)"));
|
|
2955
|
+
if (pathReMatch || expandedReMatch) {
|
|
2956
|
+
if (keygenGenerate) continue;
|
|
2957
|
+
if (sp.dirDelOnly) {
|
|
2958
|
+
if (isDeleteActionForDir(command, sp.path)) {
|
|
2959
|
+
return {
|
|
2960
|
+
allowed: false,
|
|
2961
|
+
reason: `\u5220\u9664\u654F\u611F\u76EE\u5F55: ${sp.desc} (${sp.path})`,
|
|
2962
|
+
riskLevel: sp.riskLevel,
|
|
2963
|
+
riskType: "file_access",
|
|
2964
|
+
confidence: 90,
|
|
2965
|
+
source: "heuristic",
|
|
2966
|
+
violatedLayers: ["L1.1"],
|
|
2967
|
+
isCredential: !!sp.credential
|
|
2968
|
+
};
|
|
2969
|
+
}
|
|
2970
|
+
if (isArchiveReadOfDir(command, sp.path)) {
|
|
2971
|
+
return {
|
|
2972
|
+
allowed: false,
|
|
2973
|
+
reason: `\u5F52\u6863/\u590D\u5236\u8BFB\u53D6\u654F\u611F\u76EE\u5F55: ${sp.desc} (${sp.path})`,
|
|
2974
|
+
riskLevel: sp.riskLevel,
|
|
2975
|
+
riskType: "file_access",
|
|
2976
|
+
confidence: 90,
|
|
2977
|
+
source: "heuristic",
|
|
2978
|
+
violatedLayers: ["L1.1"],
|
|
2979
|
+
isCredential: !!sp.credential
|
|
2980
|
+
};
|
|
2981
|
+
}
|
|
2982
|
+
continue;
|
|
2983
|
+
}
|
|
2820
2984
|
if (!isWriteOpFlag && !sp.credential) {
|
|
2821
2985
|
continue;
|
|
2822
2986
|
}
|
|
@@ -2827,7 +2991,7 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2827
2991
|
riskType: "file_access",
|
|
2828
2992
|
confidence: 90,
|
|
2829
2993
|
source: "heuristic",
|
|
2830
|
-
violatedLayers: ["
|
|
2994
|
+
violatedLayers: ["L1.1"],
|
|
2831
2995
|
isCredential: !!sp.credential
|
|
2832
2996
|
};
|
|
2833
2997
|
}
|
|
@@ -2850,7 +3014,7 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2850
3014
|
riskType: "file_access",
|
|
2851
3015
|
confidence: 90,
|
|
2852
3016
|
source: "heuristic",
|
|
2853
|
-
violatedLayers: ["
|
|
3017
|
+
violatedLayers: ["L1.1"],
|
|
2854
3018
|
isCredential: !!sp.credential
|
|
2855
3019
|
};
|
|
2856
3020
|
}
|
|
@@ -2867,7 +3031,7 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2867
3031
|
riskType: "file_access",
|
|
2868
3032
|
confidence: 90,
|
|
2869
3033
|
source: "heuristic",
|
|
2870
|
-
violatedLayers: ["
|
|
3034
|
+
violatedLayers: ["L1.1"],
|
|
2871
3035
|
isCredential: true
|
|
2872
3036
|
};
|
|
2873
3037
|
}
|
|
@@ -2880,18 +3044,142 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2880
3044
|
riskType: "file_access",
|
|
2881
3045
|
confidence: 90,
|
|
2882
3046
|
source: "heuristic",
|
|
2883
|
-
violatedLayers: ["
|
|
3047
|
+
violatedLayers: ["L1.1"]
|
|
2884
3048
|
};
|
|
2885
3049
|
}
|
|
2886
3050
|
return null;
|
|
2887
3051
|
}
|
|
3052
|
+
function isHomeValue(path5) {
|
|
3053
|
+
let p = path5.trim().toLowerCase();
|
|
3054
|
+
if (p.startsWith("$(") && p.endsWith(")")) {
|
|
3055
|
+
const inner = p.slice(2, -1).trim();
|
|
3056
|
+
return HOME_TARGET_RE.test(" " + inner);
|
|
3057
|
+
}
|
|
3058
|
+
if (/^\/home\/[^/\s]+\/?$/.test(p)) return true;
|
|
3059
|
+
return HOME_TARGET_RE.test(" " + p);
|
|
3060
|
+
}
|
|
3061
|
+
function isDeleteCommand(seg) {
|
|
3062
|
+
const s = seg.trimStart();
|
|
3063
|
+
if (/^(?:rm|rmdir|unlink)\b/i.test(s)) return true;
|
|
3064
|
+
return /删除|移除/.test(s);
|
|
3065
|
+
}
|
|
3066
|
+
function homeEscapeTarget(seg) {
|
|
3067
|
+
const low = seg.toLowerCase();
|
|
3068
|
+
const re = /(?:~|(?:\$home|\$\{home\})|\/home\/[a-z0-9_.-]+)/g;
|
|
3069
|
+
let m;
|
|
3070
|
+
while ((m = re.exec(low)) !== null) {
|
|
3071
|
+
const endIdx = m.index + m[0].length;
|
|
3072
|
+
const tail = low.slice(endIdx);
|
|
3073
|
+
if (!tail) continue;
|
|
3074
|
+
let token = tail.trimStart().split(/\s+/)[0] || "";
|
|
3075
|
+
token = token.split("&&")[0].split("||")[0].split(";")[0].replace(/\/+$/, "");
|
|
3076
|
+
token = token.replace(/^['"]|['"]$/g, "");
|
|
3077
|
+
const segments = token.split("/").filter((s) => s && s !== ".");
|
|
3078
|
+
if (!segments.some((s) => s === "..")) continue;
|
|
3079
|
+
let depth = 0;
|
|
3080
|
+
let minDepth = 0;
|
|
3081
|
+
for (const s of segments) {
|
|
3082
|
+
depth += s === ".." ? -1 : 1;
|
|
3083
|
+
minDepth = Math.min(minDepth, depth);
|
|
3084
|
+
}
|
|
3085
|
+
if (minDepth < 0) return true;
|
|
3086
|
+
if (depth <= 0) return true;
|
|
3087
|
+
}
|
|
3088
|
+
return false;
|
|
3089
|
+
}
|
|
3090
|
+
function checkHomeDeletion(actionType, actionDetail) {
|
|
3091
|
+
if (actionType !== "bash" && actionType !== "executebash" && actionType !== "terminal-executebash") return null;
|
|
3092
|
+
const segs = actionDetail.split(/&&|\|\||;|\n/).map((s) => s.trim()).filter(Boolean);
|
|
3093
|
+
for (const seg of segs) {
|
|
3094
|
+
if (!isDeleteCommand(seg)) continue;
|
|
3095
|
+
if (HOME_TARGET_RE.test(seg) || homeEscapeTarget(seg)) {
|
|
3096
|
+
return {
|
|
3097
|
+
allowed: false,
|
|
3098
|
+
reason: "\u68C0\u6D4B\u5230\u5220\u9664\u4E3B\u76EE\u5F55 (~/$HOME) \u64CD\u4F5C",
|
|
3099
|
+
riskLevel: "high",
|
|
3100
|
+
riskType: "script_execution",
|
|
3101
|
+
confidence: 90,
|
|
3102
|
+
source: "heuristic",
|
|
3103
|
+
violatedLayers: ["L1.1"]
|
|
3104
|
+
};
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
const assigned = {};
|
|
3108
|
+
for (const m of actionDetail.matchAll(HOME_ASSIGN_RE)) assigned[m[1]] = m[2];
|
|
3109
|
+
for (const [varName, path5] of Object.entries(assigned)) {
|
|
3110
|
+
if (!isHomeValue(path5)) continue;
|
|
3111
|
+
const varRefRe = new RegExp(`(?:${escapeRegExp(varName)}|\\$\\{${escapeRegExp(varName)}\\})`);
|
|
3112
|
+
for (const seg of segs) {
|
|
3113
|
+
if (!isDeleteCommand(seg)) continue;
|
|
3114
|
+
if (varRefRe.test(seg)) {
|
|
3115
|
+
return {
|
|
3116
|
+
allowed: false,
|
|
3117
|
+
reason: `\u68C0\u6D4B\u5230\u901A\u8FC7\u53D8\u91CF ${varName}\uFF08=${path5}\uFF0C\u4E3B\u76EE\u5F55\uFF09\u6267\u884C\u5220\u9664`,
|
|
3118
|
+
riskLevel: "high",
|
|
3119
|
+
riskType: "script_execution",
|
|
3120
|
+
confidence: 90,
|
|
3121
|
+
source: "heuristic",
|
|
3122
|
+
violatedLayers: ["L1.1"]
|
|
3123
|
+
};
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
return null;
|
|
3128
|
+
}
|
|
3129
|
+
function escapeRegExp(s) {
|
|
3130
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3131
|
+
}
|
|
3132
|
+
function isDeleteActionForDir(command, dirPath) {
|
|
3133
|
+
const low = command.toLowerCase();
|
|
3134
|
+
const dpath = dirPath.toLowerCase();
|
|
3135
|
+
if (new RegExp(`\\b(?:rm|unlink|rmdir)\\b[^&|;]*?${escapeRegExp(dpath)}`, "i").test(low)) {
|
|
3136
|
+
return true;
|
|
3137
|
+
}
|
|
3138
|
+
if (new RegExp(`(?:\u5220\u9664|\u79FB\u9664)[^\\n;|&]*?${escapeRegExp(dpath)}`).test(low)) {
|
|
3139
|
+
return true;
|
|
3140
|
+
}
|
|
3141
|
+
for (const seg of low.split(/&&|\|\||;|\n/)) {
|
|
3142
|
+
const fw = (seg.trim().split(/\s+/)[0] || "").toLowerCase();
|
|
3143
|
+
if ((fw === "rm" || fw === "unlink" || fw === "rmdir") && low.includes(dpath)) {
|
|
3144
|
+
return true;
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
return false;
|
|
3148
|
+
}
|
|
3149
|
+
function isArchiveReadOfDir(command, dirPath) {
|
|
3150
|
+
const low = command.toLowerCase();
|
|
3151
|
+
const dpath = dirPath.toLowerCase();
|
|
3152
|
+
for (const seg of low.split(/&&|\|\||;|\n/)) {
|
|
3153
|
+
const s = seg.trim();
|
|
3154
|
+
if (!s || !s.includes(dpath)) continue;
|
|
3155
|
+
const parts = s.split(/\s+/);
|
|
3156
|
+
const fw = parts[0] || "";
|
|
3157
|
+
if (fw === "zip") return true;
|
|
3158
|
+
if (fw === "tar") {
|
|
3159
|
+
if (/(?:^|\s)-[a-zA-Z]*x|--extract/.test(s)) continue;
|
|
3160
|
+
return true;
|
|
3161
|
+
}
|
|
3162
|
+
if (fw === "cp" || fw === "rsync") {
|
|
3163
|
+
const segPaths = s.match(/[/~][^\s;|&><'"]*/g) || [];
|
|
3164
|
+
if (segPaths.length && segPaths[0].includes(dpath)) return true;
|
|
3165
|
+
}
|
|
3166
|
+
}
|
|
3167
|
+
return false;
|
|
3168
|
+
}
|
|
3169
|
+
function isSshKeygenGenerate(command) {
|
|
3170
|
+
const low = command.toLowerCase();
|
|
3171
|
+
if (!low.includes("ssh-keygen")) return false;
|
|
3172
|
+
if (!/(?:^|\s)-t\s+\S+/.test(low)) return false;
|
|
3173
|
+
if (/(?:^|\s)-\S*[ylpB]\S*/.test(low)) return false;
|
|
3174
|
+
return true;
|
|
3175
|
+
}
|
|
2888
3176
|
function checkDangerousPatterns(actionType, actionDetail) {
|
|
2889
3177
|
if (actionType === "file_write" || actionType === "file_edit") return null;
|
|
2890
3178
|
const scan = stripHeredocContent(actionDetail);
|
|
2891
3179
|
const TEMP_DIRS = ["/tmp", "/var/tmp", "/run", "/dev/shm"];
|
|
2892
3180
|
const isTempPath = TEMP_DIRS.some(
|
|
2893
3181
|
(td) => new RegExp(`(?:^|[\\s/])${td.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[\\s/]|$)`).test(actionDetail)
|
|
2894
|
-
);
|
|
3182
|
+
) || /(?:^|[\s;]|&&\s*|\|\|\s*)var\/tmp(?:[/\s;]|$)/.test(actionDetail);
|
|
2895
3183
|
const wildcardDeleteRe = /(?:\brm\b|\b(r?mdir|del|delete|remove|rmdir|unlink)\b|\b删除\b|\b移除\b)[\s\S]{0,40}?[*?]|[*?][\s\S]{0,40}?\b(?:rm|del|delete|remove|rmdir|unlink)\b|rm\s+-[a-zA-Z]*[rf][a-zA-Z]*\s+\S*[*?]/i;
|
|
2896
3184
|
if (!isTempPath && wildcardDeleteRe.test(scan)) {
|
|
2897
3185
|
return {
|
|
@@ -2901,7 +3189,7 @@ function checkDangerousPatterns(actionType, actionDetail) {
|
|
|
2901
3189
|
riskType: "script_execution",
|
|
2902
3190
|
confidence: 90,
|
|
2903
3191
|
source: "heuristic",
|
|
2904
|
-
violatedLayers: ["
|
|
3192
|
+
violatedLayers: ["L1.1"]
|
|
2905
3193
|
};
|
|
2906
3194
|
}
|
|
2907
3195
|
const findWriteMatch = actionDetail.match(
|
|
@@ -2916,10 +3204,10 @@ function checkDangerousPatterns(actionType, actionDetail) {
|
|
|
2916
3204
|
riskType: "script_execution",
|
|
2917
3205
|
confidence: 90,
|
|
2918
3206
|
source: "heuristic",
|
|
2919
|
-
violatedLayers: ["
|
|
3207
|
+
violatedLayers: ["L1.1"]
|
|
2920
3208
|
};
|
|
2921
3209
|
}
|
|
2922
|
-
if (/>\s
|
|
3210
|
+
if (/>\s*['"]?\/etc\//.test(actionDetail) || />\s*['"]?\/boot\//.test(actionDetail) || />\s*['"]?\/proc\//.test(actionDetail)) {
|
|
2923
3211
|
return {
|
|
2924
3212
|
allowed: false,
|
|
2925
3213
|
reason: "\u68C0\u6D4B\u5230\u91CD\u5B9A\u5411\u8986\u76D6\u5199\u5165\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55",
|
|
@@ -2927,7 +3215,7 @@ function checkDangerousPatterns(actionType, actionDetail) {
|
|
|
2927
3215
|
riskType: "file_access",
|
|
2928
3216
|
confidence: 95,
|
|
2929
3217
|
source: "heuristic",
|
|
2930
|
-
violatedLayers: ["
|
|
3218
|
+
violatedLayers: ["L1.1"]
|
|
2931
3219
|
};
|
|
2932
3220
|
}
|
|
2933
3221
|
if (/>\s*\/dev\/(sd|hd|nvme|vd|loop|mem|kmsg|port|zero|random|urandom|full|core)/i.test(actionDetail)) {
|
|
@@ -2938,7 +3226,7 @@ function checkDangerousPatterns(actionType, actionDetail) {
|
|
|
2938
3226
|
riskType: "file_access",
|
|
2939
3227
|
confidence: 90,
|
|
2940
3228
|
source: "heuristic",
|
|
2941
|
-
violatedLayers: ["
|
|
3229
|
+
violatedLayers: ["L1.1"]
|
|
2942
3230
|
};
|
|
2943
3231
|
}
|
|
2944
3232
|
return null;
|
|
@@ -3029,13 +3317,13 @@ function checkLateralMovement(actionDetail) {
|
|
|
3029
3317
|
riskType: "lateral_movement",
|
|
3030
3318
|
confidence: 80,
|
|
3031
3319
|
source: "heuristic",
|
|
3032
|
-
violatedLayers: ["
|
|
3320
|
+
violatedLayers: ["L1.1"]
|
|
3033
3321
|
};
|
|
3034
3322
|
}
|
|
3035
3323
|
}
|
|
3036
3324
|
return null;
|
|
3037
3325
|
}
|
|
3038
|
-
var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES;
|
|
3326
|
+
var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES, HOME_TARGET_RE, HOME_ASSIGN_RE;
|
|
3039
3327
|
var init_heuristic = __esm({
|
|
3040
3328
|
"src/engine/heuristic.ts"() {
|
|
3041
3329
|
"use strict";
|
|
@@ -3043,6 +3331,7 @@ var init_heuristic = __esm({
|
|
|
3043
3331
|
init_config();
|
|
3044
3332
|
init_agents();
|
|
3045
3333
|
init_scenarios();
|
|
3334
|
+
init_runtime_config();
|
|
3046
3335
|
init_rules_loader();
|
|
3047
3336
|
init_logic_rules();
|
|
3048
3337
|
INJECTION_KEYWORDS = (() => {
|
|
@@ -3064,6 +3353,8 @@ var init_heuristic = __esm({
|
|
|
3064
3353
|
})();
|
|
3065
3354
|
SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
|
|
3066
3355
|
SEARCH_ACTION_PREFIXES = ["search-", "web-", "browser-", "query-", "websearch", "web_search"];
|
|
3356
|
+
HOME_TARGET_RE = /(?:^|[\s="'($])(?:~|(?:\$home|\$\{home\}))(?:\/(?:\.\*|\*|\.)?)?(?=[\s"';}|&)]|$)/i;
|
|
3357
|
+
HOME_ASSIGN_RE = /([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(~[A-Za-z0-9_./-]*|\$HOME[A-Za-z0-9_./-]*|\$\{HOME\}[A-Za-z0-9_./-]*|\/home\/[^\s;|&]+|\$\((?:[^()]|\([^()]*\))*\))(?=\s|;|$|\||&)/gi;
|
|
3067
3358
|
}
|
|
3068
3359
|
});
|
|
3069
3360
|
|
|
@@ -3286,7 +3577,7 @@ function loadTemplates() {
|
|
|
3286
3577
|
// src/engine/script-analyzer.ts
|
|
3287
3578
|
init_esm_shims();
|
|
3288
3579
|
import { readFileSync as readFileSync8 } from "fs";
|
|
3289
|
-
import { resolve as resolve6, join as
|
|
3580
|
+
import { resolve as resolve6, join as join4 } from "path";
|
|
3290
3581
|
var SUSPICIOUS_PATTERNS = [
|
|
3291
3582
|
// Network exfiltration (curl/wget POST in scripts is almost never legitimate)
|
|
3292
3583
|
{ name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
|
|
@@ -3419,7 +3710,7 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
|
|
|
3419
3710
|
}
|
|
3420
3711
|
function readScriptContent(scriptPath, maxLines = 500) {
|
|
3421
3712
|
try {
|
|
3422
|
-
const expandedPath = scriptPath.startsWith("~") ?
|
|
3713
|
+
const expandedPath = scriptPath.startsWith("~") ? join4(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve6(scriptPath);
|
|
3423
3714
|
const raw = readFileSync8(expandedPath, "utf-8");
|
|
3424
3715
|
const lines = raw.split("\n").slice(0, maxLines);
|
|
3425
3716
|
return { content: lines.join("\n"), lineCount: lines.length, error: "" };
|
|
@@ -3474,9 +3765,10 @@ ${result.scriptContent}
|
|
|
3474
3765
|
init_esm_shims();
|
|
3475
3766
|
init_heuristic();
|
|
3476
3767
|
init_logic_rules();
|
|
3768
|
+
init_inline_analyzer();
|
|
3477
3769
|
import { statSync as statSync2, existsSync as existsSync9 } from "fs";
|
|
3478
|
-
import { resolve as resolve8, join as
|
|
3479
|
-
import { homedir as
|
|
3770
|
+
import { resolve as resolve8, join as join6, isAbsolute as isAbsolute2 } from "path";
|
|
3771
|
+
import { homedir as homedir4 } from "os";
|
|
3480
3772
|
|
|
3481
3773
|
// src/engine/script-engine/tracker.ts
|
|
3482
3774
|
init_esm_shims();
|
|
@@ -5088,6 +5380,13 @@ function extractScriptPath2(command) {
|
|
|
5088
5380
|
return null;
|
|
5089
5381
|
}
|
|
5090
5382
|
function extractScriptPathWithArgs(command) {
|
|
5383
|
+
const inline = command.match(/(?:^|\s|&&|\|\|)\s*(?:bash|\/bin\/bash|sh|zsh)\s+-c\s*(['"])([\s\S]*?)\1/i);
|
|
5384
|
+
if (inline) {
|
|
5385
|
+
const inner = inline[2];
|
|
5386
|
+
const realPath = extractScriptPath2(inner);
|
|
5387
|
+
if (realPath) return { path: realPath, args: [] };
|
|
5388
|
+
return { path: null, args: [] };
|
|
5389
|
+
}
|
|
5091
5390
|
const scriptPath = extractScriptPath2(command);
|
|
5092
5391
|
if (!scriptPath) return { path: null, args: [] };
|
|
5093
5392
|
const idx = command.indexOf(scriptPath);
|
|
@@ -5101,11 +5400,59 @@ function extractScriptPathWithArgs(command) {
|
|
|
5101
5400
|
}
|
|
5102
5401
|
return { path: scriptPath, args };
|
|
5103
5402
|
}
|
|
5403
|
+
function looksLikeScriptPath(tok) {
|
|
5404
|
+
const t = tok.trim().replace(/^['"]|['"]$/g, "");
|
|
5405
|
+
if (!t) return false;
|
|
5406
|
+
const low = t.toLowerCase();
|
|
5407
|
+
if (["bash", "sh", "zsh", "dash", "python", "python3", "perl", "ruby", "node", "php", "lua"].includes(low)) return false;
|
|
5408
|
+
if (/\.(sh|bash|py|pl|rb|js|php|lua)$/.test(low)) return true;
|
|
5409
|
+
if (t.includes("/") && !t.startsWith("-")) return true;
|
|
5410
|
+
return false;
|
|
5411
|
+
}
|
|
5104
5412
|
function extractInlineScriptPath(actionDetail) {
|
|
5413
|
+
const code = extractInlineCode(actionDetail);
|
|
5414
|
+
if (code) {
|
|
5415
|
+
const candidates = [];
|
|
5416
|
+
const osRe = /os\.(?:system|popen)\s*\(\s*(['"])(.*?)\1\s*\)/gi;
|
|
5417
|
+
let m;
|
|
5418
|
+
while ((m = osRe.exec(code)) !== null) candidates.push(m[2]);
|
|
5419
|
+
const subListRe = /subprocess\.(?:run|call|Popen|check_output|check_call)\s*\(\s*\[(.*?)\]/gi;
|
|
5420
|
+
while ((m = subListRe.exec(code)) !== null) candidates.push(m[1]);
|
|
5421
|
+
const subShellRe = /subprocess\.(?:run|call|Popen|check_output|check_call)\s*\(\s*(['"])(.*?)\1[^)]*?shell\s*=\s*true/gi;
|
|
5422
|
+
while ((m = subShellRe.exec(code)) !== null) candidates.push(m[2]);
|
|
5423
|
+
for (const cand of candidates) {
|
|
5424
|
+
const tokens = cand.includes(",") ? cand.split(/\s*,\s*/).map((t) => t.trim().replace(/^['"]|['"]$/g, "")) : cand.split(/\s+/);
|
|
5425
|
+
for (const tok of tokens) {
|
|
5426
|
+
if (looksLikeScriptPath(tok)) {
|
|
5427
|
+
try {
|
|
5428
|
+
const resolved = resolvePath2(tok.replace(/^['"]|['"]$/g, ""));
|
|
5429
|
+
if (existsSync9(resolved)) {
|
|
5430
|
+
const stat = statSync2(resolved);
|
|
5431
|
+
if (stat.isFile()) return resolved;
|
|
5432
|
+
}
|
|
5433
|
+
} catch {
|
|
5434
|
+
}
|
|
5435
|
+
}
|
|
5436
|
+
}
|
|
5437
|
+
}
|
|
5438
|
+
const openRe = /open\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
5439
|
+
while ((m = openRe.exec(code)) !== null) {
|
|
5440
|
+
const p = m[1];
|
|
5441
|
+
try {
|
|
5442
|
+
const resolved = resolvePath2(p);
|
|
5443
|
+
if (existsSync9(resolved)) {
|
|
5444
|
+
const stat = statSync2(resolved);
|
|
5445
|
+
if (stat.isFile()) return resolved;
|
|
5446
|
+
}
|
|
5447
|
+
} catch {
|
|
5448
|
+
}
|
|
5449
|
+
}
|
|
5450
|
+
return null;
|
|
5451
|
+
}
|
|
5105
5452
|
const re = /open\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
5106
|
-
let
|
|
5107
|
-
while ((
|
|
5108
|
-
const p =
|
|
5453
|
+
let m2;
|
|
5454
|
+
while ((m2 = re.exec(actionDetail)) !== null) {
|
|
5455
|
+
const p = m2[1];
|
|
5109
5456
|
let resolved;
|
|
5110
5457
|
try {
|
|
5111
5458
|
resolved = resolvePath2(p);
|
|
@@ -5121,7 +5468,7 @@ function extractInlineScriptPath(actionDetail) {
|
|
|
5121
5468
|
function resolvePath2(p) {
|
|
5122
5469
|
let expanded = p;
|
|
5123
5470
|
if (expanded.startsWith("~/") || expanded === "~") {
|
|
5124
|
-
expanded =
|
|
5471
|
+
expanded = join6(homedir4(), expanded.slice(1));
|
|
5125
5472
|
}
|
|
5126
5473
|
return resolve8(expanded);
|
|
5127
5474
|
}
|
|
@@ -5202,7 +5549,7 @@ function analyzeScriptContent2(input) {
|
|
|
5202
5549
|
isInline = true;
|
|
5203
5550
|
}
|
|
5204
5551
|
if (resolvedPath && !isAbsolute2(resolvedPath) && !resolvedPath.startsWith("~/")) {
|
|
5205
|
-
resolvedPath =
|
|
5552
|
+
resolvedPath = join6(input.cwd || process.cwd(), resolvedPath);
|
|
5206
5553
|
}
|
|
5207
5554
|
const l1Adapter = (content) => {
|
|
5208
5555
|
const virtualInput = {
|
|
@@ -5490,7 +5837,7 @@ async function analyzeWithLLM(prompt, config) {
|
|
|
5490
5837
|
riskType: riskType || "",
|
|
5491
5838
|
confidence: typeof parsed.confidence === "number" ? Math.min(100, Math.max(0, parsed.confidence)) : 70,
|
|
5492
5839
|
source: "llm",
|
|
5493
|
-
violatedLayers: ["
|
|
5840
|
+
violatedLayers: ["L3.1"]
|
|
5494
5841
|
};
|
|
5495
5842
|
} catch (err) {
|
|
5496
5843
|
if (abortController.signal.aborted) {
|
|
@@ -5513,6 +5860,7 @@ async function analyzeWithLLM(prompt, config) {
|
|
|
5513
5860
|
|
|
5514
5861
|
// src/engine/coordinator.ts
|
|
5515
5862
|
init_config();
|
|
5863
|
+
init_runtime_config();
|
|
5516
5864
|
init_agents();
|
|
5517
5865
|
init_scenarios();
|
|
5518
5866
|
|
|
@@ -5522,9 +5870,9 @@ init_esm_shims();
|
|
|
5522
5870
|
// src/brain/rule-store.ts
|
|
5523
5871
|
init_esm_shims();
|
|
5524
5872
|
import { existsSync as existsSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
5525
|
-
import { join as
|
|
5526
|
-
import { homedir as
|
|
5527
|
-
var DEFAULT_RULES_PATH =
|
|
5873
|
+
import { join as join7 } from "path";
|
|
5874
|
+
import { homedir as homedir5 } from "os";
|
|
5875
|
+
var DEFAULT_RULES_PATH = join7(homedir5(), ".agentmoss", "brain", "rules.json");
|
|
5528
5876
|
var ENV_RULES_PATH = "AGENT_MOSS_BRAIN_RULES_PATH";
|
|
5529
5877
|
var STATUS_DRAFT = "draft";
|
|
5530
5878
|
var STATUS_APPROVED = "approved";
|
|
@@ -6086,6 +6434,15 @@ function decodeBase64Literals(text) {
|
|
|
6086
6434
|
}
|
|
6087
6435
|
return out;
|
|
6088
6436
|
}
|
|
6437
|
+
var QUOTED_STR_RE = /('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`)/g;
|
|
6438
|
+
var OUTPUT_CMDS = /* @__PURE__ */ new Set(["echo", "printf", "print"]);
|
|
6439
|
+
function stripStringLiterals(text) {
|
|
6440
|
+
const m = text.match(/\s*([A-Za-z_][\w-]*|\/?[\w./-]+)\b/);
|
|
6441
|
+
if (!m) return text;
|
|
6442
|
+
const cmd = m[1].split("/").pop().toLowerCase();
|
|
6443
|
+
if (!OUTPUT_CMDS.has(cmd)) return text;
|
|
6444
|
+
return text.replace(QUOTED_STR_RE, "");
|
|
6445
|
+
}
|
|
6089
6446
|
function decodeUrlAndNumeric(text) {
|
|
6090
6447
|
const out = [];
|
|
6091
6448
|
let s = text;
|
|
@@ -6119,7 +6476,10 @@ function decodeUrlAndNumeric(text) {
|
|
|
6119
6476
|
return out;
|
|
6120
6477
|
}
|
|
6121
6478
|
function allCandidates(detail, max = 8) {
|
|
6122
|
-
const
|
|
6479
|
+
const shell = stripStringLiterals(detail);
|
|
6480
|
+
const cands = [shell];
|
|
6481
|
+
cands.push(...decodeBase64Literals(shell));
|
|
6482
|
+
cands.push(...decodeUrlAndNumeric(shell));
|
|
6123
6483
|
cands.push(...decodeBase64Literals(detail));
|
|
6124
6484
|
cands.push(...decodeUrlAndNumeric(detail));
|
|
6125
6485
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -6144,6 +6504,128 @@ function matchStageAny(detail, stages) {
|
|
|
6144
6504
|
return "";
|
|
6145
6505
|
}
|
|
6146
6506
|
|
|
6507
|
+
// src/engine/formal-engine/ltl.ts
|
|
6508
|
+
init_esm_shims();
|
|
6509
|
+
init_rules_loader();
|
|
6510
|
+
|
|
6511
|
+
// src/engine/formal-engine/ir.ts
|
|
6512
|
+
init_esm_shims();
|
|
6513
|
+
function allNodes(mg) {
|
|
6514
|
+
const out = [];
|
|
6515
|
+
for (const s of mg.sessions) out.push(...s.nodes);
|
|
6516
|
+
return out;
|
|
6517
|
+
}
|
|
6518
|
+
function allEdges(mg) {
|
|
6519
|
+
const out = [];
|
|
6520
|
+
for (const s of mg.sessions) out.push(...s.edges);
|
|
6521
|
+
out.push(...mg.crossEdges);
|
|
6522
|
+
return out;
|
|
6523
|
+
}
|
|
6524
|
+
function emptyVerificationResult() {
|
|
6525
|
+
return { verified: true, violations: [] };
|
|
6526
|
+
}
|
|
6527
|
+
|
|
6528
|
+
// src/engine/formal-engine/ltl.ts
|
|
6529
|
+
var AP_RE = null;
|
|
6530
|
+
function compileAps(apPatterns) {
|
|
6531
|
+
if (AP_RE) return;
|
|
6532
|
+
AP_RE = {};
|
|
6533
|
+
for (const [name, pats] of Object.entries(apPatterns || {})) {
|
|
6534
|
+
AP_RE[name] = pats.map((p) => new RegExp(p, "i"));
|
|
6535
|
+
}
|
|
6536
|
+
}
|
|
6537
|
+
function labelEvent(actionDetail) {
|
|
6538
|
+
if (!AP_RE) compileAps(loadMC().ap_patterns || {});
|
|
6539
|
+
const candidates = allCandidates(actionDetail);
|
|
6540
|
+
const hits = /* @__PURE__ */ new Set();
|
|
6541
|
+
for (const [name, pats] of Object.entries(AP_RE || {})) {
|
|
6542
|
+
for (const c of candidates) {
|
|
6543
|
+
const m = pats.some((p) => p.test(c));
|
|
6544
|
+
if (m) {
|
|
6545
|
+
hits.add(name);
|
|
6546
|
+
break;
|
|
6547
|
+
}
|
|
6548
|
+
}
|
|
6549
|
+
}
|
|
6550
|
+
return hits;
|
|
6551
|
+
}
|
|
6552
|
+
function evaluateLTL(formula, trace, pos) {
|
|
6553
|
+
const op = formula[0];
|
|
6554
|
+
switch (op) {
|
|
6555
|
+
case "ap":
|
|
6556
|
+
return trace[pos].has(formula[1]);
|
|
6557
|
+
case "!":
|
|
6558
|
+
return !evaluateLTL(formula[1], trace, pos);
|
|
6559
|
+
case "&":
|
|
6560
|
+
return evaluateLTL(formula[1], trace, pos) && evaluateLTL(formula[2], trace, pos);
|
|
6561
|
+
case "|":
|
|
6562
|
+
return evaluateLTL(formula[1], trace, pos) || evaluateLTL(formula[2], trace, pos);
|
|
6563
|
+
case "->":
|
|
6564
|
+
return !evaluateLTL(formula[1], trace, pos) || evaluateLTL(formula[2], trace, pos);
|
|
6565
|
+
case "X":
|
|
6566
|
+
return pos + 1 < trace.length && evaluateLTL(formula[1], trace, pos + 1);
|
|
6567
|
+
case "G": {
|
|
6568
|
+
for (let j = pos; j < trace.length; j++) {
|
|
6569
|
+
if (!evaluateLTL(formula[1], trace, j)) return false;
|
|
6570
|
+
}
|
|
6571
|
+
return true;
|
|
6572
|
+
}
|
|
6573
|
+
case "F": {
|
|
6574
|
+
for (let j = pos; j < trace.length; j++) {
|
|
6575
|
+
if (evaluateLTL(formula[1], trace, j)) return true;
|
|
6576
|
+
}
|
|
6577
|
+
return false;
|
|
6578
|
+
}
|
|
6579
|
+
case "U": {
|
|
6580
|
+
for (let j = pos; j < trace.length; j++) {
|
|
6581
|
+
if (evaluateLTL(formula[2], trace, j)) {
|
|
6582
|
+
for (let k = pos; k < j; k++) {
|
|
6583
|
+
if (!evaluateLTL(formula[1], trace, k)) return false;
|
|
6584
|
+
}
|
|
6585
|
+
return true;
|
|
6586
|
+
}
|
|
6587
|
+
}
|
|
6588
|
+
return false;
|
|
6589
|
+
}
|
|
6590
|
+
default:
|
|
6591
|
+
return false;
|
|
6592
|
+
}
|
|
6593
|
+
}
|
|
6594
|
+
function ruleViolates(rule, traceWith, witnessMode) {
|
|
6595
|
+
const formula = rule?.formula;
|
|
6596
|
+
if (!Array.isArray(formula) || formula.length === 0) return false;
|
|
6597
|
+
const satWith = evaluateLTL(formula, traceWith, 0);
|
|
6598
|
+
if (!satWith) return false;
|
|
6599
|
+
if (witnessMode && traceWith.length > 1) {
|
|
6600
|
+
const traceWithout = traceWith.slice(0, -1);
|
|
6601
|
+
if (evaluateLTL(formula, traceWithout, 0)) return false;
|
|
6602
|
+
}
|
|
6603
|
+
return true;
|
|
6604
|
+
}
|
|
6605
|
+
function checkMCRules(graph) {
|
|
6606
|
+
const violations = [];
|
|
6607
|
+
const nodes = allNodes(graph);
|
|
6608
|
+
if (nodes.length === 0) return violations;
|
|
6609
|
+
const mc = loadMC();
|
|
6610
|
+
const rules = mc.rules || [];
|
|
6611
|
+
if (rules.length === 0) return violations;
|
|
6612
|
+
const trace = nodes.map((n) => labelEvent(n.actionDetail));
|
|
6613
|
+
const witnessMode = graph.witnessIdx !== void 0;
|
|
6614
|
+
for (const rule of rules) {
|
|
6615
|
+
if (rule?.type !== "ltl" || !(rule.enabled !== false)) continue;
|
|
6616
|
+
if (ruleViolates(rule, trace, witnessMode)) {
|
|
6617
|
+
violations.push({
|
|
6618
|
+
propertyId: String(rule.id || "mc-ltl"),
|
|
6619
|
+
reason: String(rule.reason || "L2.2 \u6A21\u578B\u68C0\u6D4B\u89C4\u5219\u547D\u4E2D"),
|
|
6620
|
+
riskLevel: String(rule.risk_level || "high"),
|
|
6621
|
+
path: nodes.filter((n) => labelEvent(n.actionDetail).size > 0).slice(-3).map((n) => ({ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }))
|
|
6622
|
+
});
|
|
6623
|
+
if (witnessMode) return violations;
|
|
6624
|
+
}
|
|
6625
|
+
}
|
|
6626
|
+
return violations;
|
|
6627
|
+
}
|
|
6628
|
+
|
|
6147
6629
|
// src/engine/formal-engine/builder.ts
|
|
6148
6630
|
var COMBO_STAGES2 = null;
|
|
6149
6631
|
function loadStages() {
|
|
@@ -6172,6 +6654,26 @@ function extractResources(actionDetail) {
|
|
|
6172
6654
|
function markStage(actionDetail) {
|
|
6173
6655
|
return matchStageAny(actionDetail, loadStages());
|
|
6174
6656
|
}
|
|
6657
|
+
function markIntegrity(actionDetail) {
|
|
6658
|
+
const aps = labelEvent(actionDetail);
|
|
6659
|
+
if (aps.has("download_file") || aps.has("prompt_injection")) {
|
|
6660
|
+
return { integrity: "low", isLowSource: true };
|
|
6661
|
+
}
|
|
6662
|
+
return { integrity: "medium", isLowSource: false };
|
|
6663
|
+
}
|
|
6664
|
+
function initNode(sessionId, seq, actionType, actionDetail) {
|
|
6665
|
+
const { integrity, isLowSource } = markIntegrity(actionDetail);
|
|
6666
|
+
return {
|
|
6667
|
+
sessionId,
|
|
6668
|
+
seq,
|
|
6669
|
+
actionType,
|
|
6670
|
+
actionDetail,
|
|
6671
|
+
stage: markStage(actionDetail),
|
|
6672
|
+
resources: extractResources(actionDetail),
|
|
6673
|
+
integrity,
|
|
6674
|
+
isLowSource
|
|
6675
|
+
};
|
|
6676
|
+
}
|
|
6175
6677
|
function buildSessionGraph(actionHistory, aNext, sessionId = "current") {
|
|
6176
6678
|
const nodes = [];
|
|
6177
6679
|
const edges = [];
|
|
@@ -6185,17 +6687,9 @@ function buildSessionGraph(actionHistory, aNext, sessionId = "current") {
|
|
|
6185
6687
|
details.push({ actionType: String(aNext.actionType || ""), actionDetail: String(aNext.actionDetail || "") });
|
|
6186
6688
|
}
|
|
6187
6689
|
details.forEach((d, seq) => {
|
|
6188
|
-
const
|
|
6189
|
-
const node = {
|
|
6190
|
-
sessionId,
|
|
6191
|
-
seq,
|
|
6192
|
-
actionType: d.actionType,
|
|
6193
|
-
actionDetail: d.actionDetail,
|
|
6194
|
-
stage,
|
|
6195
|
-
resources: extractResources(d.actionDetail)
|
|
6196
|
-
};
|
|
6690
|
+
const node = initNode(sessionId, seq, d.actionType, d.actionDetail);
|
|
6197
6691
|
nodes.push(node);
|
|
6198
|
-
if (stage) stageFlags.add(stage);
|
|
6692
|
+
if (node.stage) stageFlags.add(node.stage);
|
|
6199
6693
|
});
|
|
6200
6694
|
for (let i = 0; i < nodes.length - 1; i++) {
|
|
6201
6695
|
edges.push({ src: i, dst: i + 1, kind: "next" });
|
|
@@ -6215,15 +6709,12 @@ function buildMultiSessionGraph(sessions, aNext) {
|
|
|
6215
6709
|
if (aNext) {
|
|
6216
6710
|
const last = mg.sessions[mg.sessions.length - 1];
|
|
6217
6711
|
if (last.sessionId === "current") {
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
stage,
|
|
6225
|
-
resources: extractResources(String(aNext.actionDetail || ""))
|
|
6226
|
-
});
|
|
6712
|
+
last.nodes.push(initNode(
|
|
6713
|
+
last.sessionId,
|
|
6714
|
+
last.nodes.length,
|
|
6715
|
+
String(aNext.actionType || ""),
|
|
6716
|
+
String(aNext.actionDetail || "")
|
|
6717
|
+
));
|
|
6227
6718
|
if (last.nodes.length > 1) {
|
|
6228
6719
|
last.edges.push({ src: last.nodes.length - 2, dst: last.nodes.length - 1, kind: "next" });
|
|
6229
6720
|
}
|
|
@@ -6237,11 +6728,25 @@ function buildMultiSessionGraph(sessions, aNext) {
|
|
|
6237
6728
|
function addCrossDataEdges(mg) {
|
|
6238
6729
|
const nodes = allNodesFlat(mg);
|
|
6239
6730
|
if (nodes.length < 2) return;
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
const
|
|
6244
|
-
if (
|
|
6731
|
+
const resIndex = /* @__PURE__ */ new Map();
|
|
6732
|
+
nodes.forEach((n, i) => {
|
|
6733
|
+
for (const r of n.resources) {
|
|
6734
|
+
const list = resIndex.get(r);
|
|
6735
|
+
if (list) list.push(i);
|
|
6736
|
+
else resIndex.set(r, [i]);
|
|
6737
|
+
}
|
|
6738
|
+
});
|
|
6739
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6740
|
+
for (const idxs of resIndex.values()) {
|
|
6741
|
+
if (idxs.length < 2) continue;
|
|
6742
|
+
for (let a = 0; a < idxs.length; a++) {
|
|
6743
|
+
const i = idxs[a];
|
|
6744
|
+
for (let b = a + 1; b < idxs.length; b++) {
|
|
6745
|
+
const j = idxs[b];
|
|
6746
|
+
if (nodes[i].sessionId === nodes[j].sessionId) continue;
|
|
6747
|
+
const key = `${i}:${j}`;
|
|
6748
|
+
if (seen.has(key)) continue;
|
|
6749
|
+
seen.add(key);
|
|
6245
6750
|
mg.crossEdges.push({ src: i, dst: j, kind: "data" });
|
|
6246
6751
|
}
|
|
6247
6752
|
}
|
|
@@ -6252,28 +6757,6 @@ function allNodesFlat(mg) {
|
|
|
6252
6757
|
for (const s of mg.sessions) out.push(...s.nodes);
|
|
6253
6758
|
return out;
|
|
6254
6759
|
}
|
|
6255
|
-
function intersect(a, b) {
|
|
6256
|
-
const out = /* @__PURE__ */ new Set();
|
|
6257
|
-
for (const v of a) if (b.has(v)) out.add(v);
|
|
6258
|
-
return out;
|
|
6259
|
-
}
|
|
6260
|
-
|
|
6261
|
-
// src/engine/formal-engine/ir.ts
|
|
6262
|
-
init_esm_shims();
|
|
6263
|
-
function allNodes(mg) {
|
|
6264
|
-
const out = [];
|
|
6265
|
-
for (const s of mg.sessions) out.push(...s.nodes);
|
|
6266
|
-
return out;
|
|
6267
|
-
}
|
|
6268
|
-
function allEdges(mg) {
|
|
6269
|
-
const out = [];
|
|
6270
|
-
for (const s of mg.sessions) out.push(...s.edges);
|
|
6271
|
-
out.push(...mg.crossEdges);
|
|
6272
|
-
return out;
|
|
6273
|
-
}
|
|
6274
|
-
function emptyVerificationResult() {
|
|
6275
|
-
return { verified: true, violations: [] };
|
|
6276
|
-
}
|
|
6277
6760
|
|
|
6278
6761
|
// src/engine/formal-engine/properties.ts
|
|
6279
6762
|
init_esm_shims();
|
|
@@ -6296,27 +6779,50 @@ function checkCrossSessionChain(graph) {
|
|
|
6296
6779
|
const violations = [];
|
|
6297
6780
|
const nodes = allNodes(graph);
|
|
6298
6781
|
if (nodes.length === 0) return violations;
|
|
6782
|
+
const witness = graph.witnessIdx;
|
|
6299
6783
|
for (const chain of loadCrossSessionChains()) {
|
|
6300
6784
|
const stages = chain.stages;
|
|
6301
6785
|
if (!stages.length) continue;
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6786
|
+
if (witness !== void 0) {
|
|
6787
|
+
if (nodes[witness].stage !== stages[stages.length - 1]) continue;
|
|
6788
|
+
let matched = 0;
|
|
6789
|
+
const path5 = [];
|
|
6790
|
+
for (let k = 0; k < witness; k++) {
|
|
6791
|
+
const n = nodes[k];
|
|
6792
|
+
if (matched < stages.length - 1 && n.stage === stages[matched]) {
|
|
6793
|
+
matched += 1;
|
|
6794
|
+
path5.push({ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) });
|
|
6795
|
+
}
|
|
6311
6796
|
}
|
|
6312
|
-
|
|
6313
|
-
|
|
6797
|
+
if (matched < stages.length - 1) continue;
|
|
6798
|
+
path5.push({ sessionId: nodes[witness].sessionId, seq: nodes[witness].seq, detail: nodes[witness].actionDetail.slice(0, 60) });
|
|
6314
6799
|
violations.push({
|
|
6315
6800
|
propertyId: "cross-" + chain.id,
|
|
6316
6801
|
reason: chain.reason,
|
|
6317
6802
|
riskLevel: chain.riskLevel,
|
|
6318
6803
|
path: path5
|
|
6319
6804
|
});
|
|
6805
|
+
return violations;
|
|
6806
|
+
} else {
|
|
6807
|
+
let matched = 0;
|
|
6808
|
+
let stageIdx = 0;
|
|
6809
|
+
const path5 = [];
|
|
6810
|
+
for (const n of nodes) {
|
|
6811
|
+
if (stageIdx < stages.length && n.stage === stages[stageIdx]) {
|
|
6812
|
+
matched += 1;
|
|
6813
|
+
path5.push({ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) });
|
|
6814
|
+
stageIdx += 1;
|
|
6815
|
+
if (matched >= stages.length) break;
|
|
6816
|
+
}
|
|
6817
|
+
}
|
|
6818
|
+
if (matched >= stages.length) {
|
|
6819
|
+
violations.push({
|
|
6820
|
+
propertyId: "cross-" + chain.id,
|
|
6821
|
+
reason: chain.reason,
|
|
6822
|
+
riskLevel: chain.riskLevel,
|
|
6823
|
+
path: path5
|
|
6824
|
+
});
|
|
6825
|
+
}
|
|
6320
6826
|
}
|
|
6321
6827
|
}
|
|
6322
6828
|
return violations;
|
|
@@ -6351,14 +6857,18 @@ function checkSensitiveDataFlow(graph) {
|
|
|
6351
6857
|
}
|
|
6352
6858
|
}
|
|
6353
6859
|
}
|
|
6354
|
-
|
|
6860
|
+
const witness = graph.witnessIdx;
|
|
6861
|
+
const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
|
|
6862
|
+
for (const i of candidates) {
|
|
6863
|
+
const n = nodes[i];
|
|
6355
6864
|
if (tainted.has(i) && EXFIL_RE.test(n.actionDetail)) {
|
|
6356
6865
|
violations.push({
|
|
6357
|
-
propertyId: "sensitive-data-
|
|
6866
|
+
propertyId: "sensitive-data-flow",
|
|
6358
6867
|
reason: `\u654F\u611F\u6570\u636E\u4F20\u64AD\u5916\u4F20\uFF1A${tainted.get(i)} \u2192 ${n.sessionId}#${n.seq} \u7ECF\u5171\u4EAB\u8F7D\u4F53\u5916\u53D1`,
|
|
6359
6868
|
riskLevel: "critical",
|
|
6360
6869
|
path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
|
|
6361
6870
|
});
|
|
6871
|
+
if (witness !== void 0) return violations;
|
|
6362
6872
|
}
|
|
6363
6873
|
}
|
|
6364
6874
|
return violations;
|
|
@@ -6389,12 +6899,38 @@ function checkNeverExfilAfterPrivesc(graph) {
|
|
|
6389
6899
|
const violations = [];
|
|
6390
6900
|
const nodes = allNodes(graph);
|
|
6391
6901
|
if (nodes.length < 2) return violations;
|
|
6902
|
+
const witness = graph.witnessIdx;
|
|
6903
|
+
if (witness !== void 0) {
|
|
6904
|
+
const evIdx = [];
|
|
6905
|
+
nodes.forEach((n, i) => {
|
|
6906
|
+
if ((i === witness || n.sessionId === "ebpf-evidence") && isExfilNode(n)) evIdx.push(i);
|
|
6907
|
+
});
|
|
6908
|
+
if (evIdx.length === 0) return violations;
|
|
6909
|
+
const firstEv = Math.min(...evIdx);
|
|
6910
|
+
let pre = null;
|
|
6911
|
+
for (let i = 0; i < firstEv; i++) {
|
|
6912
|
+
if (isPrivescNode(nodes[i])) pre = nodes[i];
|
|
6913
|
+
}
|
|
6914
|
+
if (pre) {
|
|
6915
|
+
const post = nodes[firstEv];
|
|
6916
|
+
return [{
|
|
6917
|
+
propertyId: "no-exfil-after-privesc",
|
|
6918
|
+
reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
|
|
6919
|
+
riskLevel: "critical",
|
|
6920
|
+
path: [
|
|
6921
|
+
{ sessionId: pre.sessionId, seq: pre.seq, detail: pre.actionDetail.slice(0, 60) },
|
|
6922
|
+
{ sessionId: post.sessionId, seq: post.seq, detail: post.actionDetail.slice(0, 60) }
|
|
6923
|
+
]
|
|
6924
|
+
}];
|
|
6925
|
+
}
|
|
6926
|
+
return violations;
|
|
6927
|
+
}
|
|
6392
6928
|
for (let i = 0; i < nodes.length; i++) {
|
|
6393
6929
|
if (!isPrivescNode(nodes[i])) continue;
|
|
6394
6930
|
for (let j = i + 1; j < nodes.length; j++) {
|
|
6395
6931
|
if (isExfilNode(nodes[j])) {
|
|
6396
6932
|
violations.push({
|
|
6397
|
-
propertyId: "no-
|
|
6933
|
+
propertyId: "no-exfil-after-privesc",
|
|
6398
6934
|
reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
|
|
6399
6935
|
riskLevel: "critical",
|
|
6400
6936
|
path: [
|
|
@@ -6412,12 +6948,38 @@ function checkNeverExfilAfterCred(graph) {
|
|
|
6412
6948
|
const violations = [];
|
|
6413
6949
|
const nodes = allNodes(graph);
|
|
6414
6950
|
if (nodes.length < 2) return violations;
|
|
6951
|
+
const witness = graph.witnessIdx;
|
|
6952
|
+
if (witness !== void 0) {
|
|
6953
|
+
const evIdx = [];
|
|
6954
|
+
nodes.forEach((n, i) => {
|
|
6955
|
+
if ((i === witness || n.sessionId === "ebpf-evidence") && isExfilNode(n)) evIdx.push(i);
|
|
6956
|
+
});
|
|
6957
|
+
if (evIdx.length === 0) return violations;
|
|
6958
|
+
const firstEv = Math.min(...evIdx);
|
|
6959
|
+
let pre = null;
|
|
6960
|
+
for (let i = 0; i < firstEv; i++) {
|
|
6961
|
+
if (isCredNode(nodes[i])) pre = nodes[i];
|
|
6962
|
+
}
|
|
6963
|
+
if (pre) {
|
|
6964
|
+
const post = nodes[firstEv];
|
|
6965
|
+
return [{
|
|
6966
|
+
propertyId: "no-exfil-after-credential",
|
|
6967
|
+
reason: "\u8BFB\u53D6\u51ED\u636E\u540E\u51FA\u73B0\u5916\u4F20\uFF0C\u8FDD\u53CD \u25A1(CRED\u2192\xAC\u25C7EXFIL)",
|
|
6968
|
+
riskLevel: "critical",
|
|
6969
|
+
path: [
|
|
6970
|
+
{ sessionId: pre.sessionId, seq: pre.seq, detail: pre.actionDetail.slice(0, 60) },
|
|
6971
|
+
{ sessionId: post.sessionId, seq: post.seq, detail: post.actionDetail.slice(0, 60) }
|
|
6972
|
+
]
|
|
6973
|
+
}];
|
|
6974
|
+
}
|
|
6975
|
+
return violations;
|
|
6976
|
+
}
|
|
6415
6977
|
for (let i = 0; i < nodes.length; i++) {
|
|
6416
6978
|
if (!isCredNode(nodes[i])) continue;
|
|
6417
6979
|
for (let j = i + 1; j < nodes.length; j++) {
|
|
6418
6980
|
if (isExfilNode(nodes[j])) {
|
|
6419
6981
|
violations.push({
|
|
6420
|
-
propertyId: "no-
|
|
6982
|
+
propertyId: "no-exfil-after-credential",
|
|
6421
6983
|
reason: "\u8BFB\u53D6\u51ED\u636E\u540E\u51FA\u73B0\u5916\u4F20\uFF0C\u8FDD\u53CD \u25A1(CRED\u2192\xAC\u25C7EXFIL)",
|
|
6422
6984
|
riskLevel: "critical",
|
|
6423
6985
|
path: [
|
|
@@ -6431,11 +6993,172 @@ function checkNeverExfilAfterCred(graph) {
|
|
|
6431
6993
|
}
|
|
6432
6994
|
return violations;
|
|
6433
6995
|
}
|
|
6996
|
+
var TOOL_REGISTRY = null;
|
|
6997
|
+
function getToolRegistry() {
|
|
6998
|
+
if (TOOL_REGISTRY) return TOOL_REGISTRY;
|
|
6999
|
+
TOOL_REGISTRY = loadToolRegistry().tools || {};
|
|
7000
|
+
return TOOL_REGISTRY;
|
|
7001
|
+
}
|
|
7002
|
+
function lookupToolClass(actionType) {
|
|
7003
|
+
const entry = getToolRegistry()[actionType] || {};
|
|
7004
|
+
return entry.actionClass;
|
|
7005
|
+
}
|
|
7006
|
+
var 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;
|
|
7007
|
+
var STRONG_COMPUTE_TYPES = /* @__PURE__ */ new Set(["python", "node", "exec", "terminal", "cmd"]);
|
|
7008
|
+
function isComputeNode(n) {
|
|
7009
|
+
if (STRONG_COMPUTE_TYPES.has(n.actionType.toLowerCase())) return true;
|
|
7010
|
+
const cls = lookupToolClass(n.actionType);
|
|
7011
|
+
if (cls === "read" || cls === "write" || cls === "network_egress" || cls === "message_egress") return false;
|
|
7012
|
+
return COMPUTE_RE.test(n.actionDetail);
|
|
7013
|
+
}
|
|
7014
|
+
function checkLowIntegrityCommandExec(graph) {
|
|
7015
|
+
const violations = [];
|
|
7016
|
+
const nodes = allNodes(graph);
|
|
7017
|
+
if (nodes.length === 0) return violations;
|
|
7018
|
+
const witness = graph.witnessIdx;
|
|
7019
|
+
const lowResources = /* @__PURE__ */ new Set();
|
|
7020
|
+
const lowAt = /* @__PURE__ */ new Map();
|
|
7021
|
+
nodes.forEach((n, i) => {
|
|
7022
|
+
if (n.isLowSource) {
|
|
7023
|
+
lowAt.set(i, n.actionDetail.slice(0, 60));
|
|
7024
|
+
for (const r of n.resources) lowResources.add(r);
|
|
7025
|
+
}
|
|
7026
|
+
});
|
|
7027
|
+
let changed = true;
|
|
7028
|
+
while (changed) {
|
|
7029
|
+
changed = false;
|
|
7030
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
7031
|
+
if (lowAt.has(i)) continue;
|
|
7032
|
+
let shared = "";
|
|
7033
|
+
for (const r of nodes[i].resources) {
|
|
7034
|
+
if (lowResources.has(r)) {
|
|
7035
|
+
shared = r;
|
|
7036
|
+
break;
|
|
7037
|
+
}
|
|
7038
|
+
}
|
|
7039
|
+
if (shared) {
|
|
7040
|
+
lowAt.set(i, `\u7EE7\u627F\u81EA ${shared}`);
|
|
7041
|
+
for (const r of nodes[i].resources) lowResources.add(r);
|
|
7042
|
+
changed = true;
|
|
7043
|
+
}
|
|
7044
|
+
}
|
|
7045
|
+
}
|
|
7046
|
+
if (lowAt.size === 0) return violations;
|
|
7047
|
+
const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
|
|
7048
|
+
for (const i of candidates) {
|
|
7049
|
+
const n = nodes[i];
|
|
7050
|
+
if (lowAt.has(i) && isComputeNode(n)) {
|
|
7051
|
+
violations.push({
|
|
7052
|
+
propertyId: "low-integrity-command-exec",
|
|
7053
|
+
reason: `\u4F4E\u5B8C\u6574\u6027\u6570\u636E\u63A7\u5236\u547D\u4EE4\u6267\u884C\uFF1A${lowAt.get(i)} \u2192 ${n.sessionId}#${n.seq} \u6267\u884C\u4E0D\u53EF\u4FE1\u5185\u5BB9`,
|
|
7054
|
+
riskLevel: "critical",
|
|
7055
|
+
path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
|
|
7056
|
+
});
|
|
7057
|
+
if (witness !== void 0) return violations;
|
|
7058
|
+
}
|
|
7059
|
+
}
|
|
7060
|
+
return violations;
|
|
7061
|
+
}
|
|
7062
|
+
var DL_OR_LOW_RE = /curl\s+.*-o\b|wget\s+.*\s+-O\b|curl\s+.*\|\s*(bash|sh|python)|wget\s+.*\|\s*(bash|sh|python)|git\s+clone|docker\s+pull|pip\s+install|npm\s+install|base64\s+-d/i;
|
|
7063
|
+
function checkCrossSessionReachability(graph) {
|
|
7064
|
+
const violations = [];
|
|
7065
|
+
const nodes = allNodes(graph);
|
|
7066
|
+
if (nodes.length === 0) return violations;
|
|
7067
|
+
const witness = graph.witnessIdx;
|
|
7068
|
+
const sources = /* @__PURE__ */ new Set();
|
|
7069
|
+
nodes.forEach((n, i) => {
|
|
7070
|
+
if (n.isLowSource || DL_OR_LOW_RE.test(n.actionDetail)) sources.add(i);
|
|
7071
|
+
});
|
|
7072
|
+
if (sources.size === 0) return violations;
|
|
7073
|
+
const reached = new Set(sources);
|
|
7074
|
+
let changed = true;
|
|
7075
|
+
while (changed) {
|
|
7076
|
+
changed = false;
|
|
7077
|
+
for (const e of allEdges(graph)) {
|
|
7078
|
+
if (reached.has(e.src) && !reached.has(e.dst)) {
|
|
7079
|
+
const srcRes = nodes[e.src].resources;
|
|
7080
|
+
const dstRes = nodes[e.dst].resources;
|
|
7081
|
+
for (const r of srcRes) {
|
|
7082
|
+
if (dstRes.has(r)) {
|
|
7083
|
+
reached.add(e.dst);
|
|
7084
|
+
changed = true;
|
|
7085
|
+
break;
|
|
7086
|
+
}
|
|
7087
|
+
}
|
|
7088
|
+
}
|
|
7089
|
+
}
|
|
7090
|
+
}
|
|
7091
|
+
const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
|
|
7092
|
+
for (const i of candidates) {
|
|
7093
|
+
const n = nodes[i];
|
|
7094
|
+
if (sources.has(i)) continue;
|
|
7095
|
+
if (reached.has(i)) {
|
|
7096
|
+
const srcIdx = Math.min(...sources);
|
|
7097
|
+
const srcDesc = nodes[srcIdx].actionDetail.slice(0, 40);
|
|
7098
|
+
violations.push({
|
|
7099
|
+
propertyId: "low-integrity-reachability",
|
|
7100
|
+
reason: `\u4F4E\u5B8C\u6574\u6027\u6E90\u7ECF\u8DE8 session \u6570\u636E\u94FE\u591A\u8DF3\u5230\u8FBE\u5F53\u524D\u6267\u884C\uFF08\u6E90: ${srcDesc} \u2192 ${n.sessionId}#${n.seq}\uFF09`,
|
|
7101
|
+
riskLevel: "high",
|
|
7102
|
+
path: nodes.slice(-3).map((m) => ({ sessionId: m.sessionId, seq: m.seq, detail: m.actionDetail.slice(0, 60) }))
|
|
7103
|
+
});
|
|
7104
|
+
if (witness !== void 0) return violations;
|
|
7105
|
+
}
|
|
7106
|
+
}
|
|
7107
|
+
return violations;
|
|
7108
|
+
}
|
|
7109
|
+
function checkSensitiveDataAggregation(graph) {
|
|
7110
|
+
const violations = [];
|
|
7111
|
+
const nodes = allNodes(graph);
|
|
7112
|
+
if (nodes.length < 2) return violations;
|
|
7113
|
+
const witness = graph.witnessIdx;
|
|
7114
|
+
const resIndex = /* @__PURE__ */ new Map();
|
|
7115
|
+
nodes.forEach((n, i) => {
|
|
7116
|
+
for (const r of n.resources) {
|
|
7117
|
+
const list = resIndex.get(r);
|
|
7118
|
+
if (list) list.push(i);
|
|
7119
|
+
else resIndex.set(r, [i]);
|
|
7120
|
+
}
|
|
7121
|
+
});
|
|
7122
|
+
const sourcesFor = (exfilIdx) => {
|
|
7123
|
+
const srcs = /* @__PURE__ */ new Set();
|
|
7124
|
+
const n = nodes[exfilIdx];
|
|
7125
|
+
for (const r of n.resources) {
|
|
7126
|
+
for (const j of resIndex.get(r) || []) {
|
|
7127
|
+
if (j === exfilIdx) continue;
|
|
7128
|
+
srcs.add(`${nodes[j].sessionId}#${nodes[j].seq}`);
|
|
7129
|
+
}
|
|
7130
|
+
}
|
|
7131
|
+
return srcs;
|
|
7132
|
+
};
|
|
7133
|
+
const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
|
|
7134
|
+
for (const i of candidates) {
|
|
7135
|
+
const n = nodes[i];
|
|
7136
|
+
if (!isExfilNode(n)) continue;
|
|
7137
|
+
const srcs = sourcesFor(i);
|
|
7138
|
+
if (srcs.size >= 2) {
|
|
7139
|
+
violations.push({
|
|
7140
|
+
propertyId: "sensitive-data-aggregation",
|
|
7141
|
+
reason: `\u654F\u611F\u6570\u636E\u805A\u5408\u5916\u4F20\uFF1Aegress \u805A\u5408 ${srcs.size} \u4E2A\u4E8B\u4EF6\u6E90\u6570\u636E\uFF08Rule 12\uFF09`,
|
|
7142
|
+
riskLevel: "critical",
|
|
7143
|
+
path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
|
|
7144
|
+
});
|
|
7145
|
+
if (witness !== void 0) return violations;
|
|
7146
|
+
}
|
|
7147
|
+
}
|
|
7148
|
+
return violations;
|
|
7149
|
+
}
|
|
6434
7150
|
var ALL_PROPERTIES = [
|
|
6435
|
-
{ id: "cross-session-chain", check: checkCrossSessionChain },
|
|
6436
|
-
{ id: "sensitive-data-flow", check: checkSensitiveDataFlow },
|
|
6437
|
-
{ id: "
|
|
6438
|
-
{ id: "no-
|
|
7151
|
+
{ id: "cross-session-chain", check: checkCrossSessionChain, shortName: "P1" },
|
|
7152
|
+
{ id: "sensitive-data-flow", check: checkSensitiveDataFlow, shortName: "P2" },
|
|
7153
|
+
{ id: "low-integrity-reachability", check: checkCrossSessionReachability, shortName: "P3" },
|
|
7154
|
+
{ id: "no-exfil-after-privesc", check: checkNeverExfilAfterPrivesc, shortName: "P4" },
|
|
7155
|
+
{ id: "no-exfil-after-credential", check: checkNeverExfilAfterCred, shortName: "P5" },
|
|
7156
|
+
// L2.2 模型检测(通用 LTL 求值器,mc_rules.json 单一事实源;witness 语义见 ltl.ts)
|
|
7157
|
+
{ id: "mc-ltl-rules", check: checkMCRules, shortName: "P6" },
|
|
7158
|
+
// P7 低完整性控制命令执行(integrity 维度,站点 L2.1 最有区分度的检测之一)
|
|
7159
|
+
{ id: "low-integrity-command-exec", check: checkLowIntegrityCommandExec, shortName: "P7" },
|
|
7160
|
+
// P8 敏感数据聚合外传(egress 聚合 ≥2 事件源)
|
|
7161
|
+
{ id: "sensitive-data-aggregation", check: checkSensitiveDataAggregation, shortName: "P8" }
|
|
6439
7162
|
];
|
|
6440
7163
|
|
|
6441
7164
|
// src/engine/formal-engine/engine.ts
|
|
@@ -6467,6 +7190,9 @@ var FormalEngine = class {
|
|
|
6467
7190
|
});
|
|
6468
7191
|
}
|
|
6469
7192
|
const graph = buildMultiSessionGraph(all, aNext);
|
|
7193
|
+
if (aNext) {
|
|
7194
|
+
graph.witnessIdx = allNodes(graph).length - 1;
|
|
7195
|
+
}
|
|
6470
7196
|
const result = emptyVerificationResult();
|
|
6471
7197
|
for (const prop of this.properties) {
|
|
6472
7198
|
let violations;
|
|
@@ -6607,6 +7333,7 @@ function _resetBrainForTest() {
|
|
|
6607
7333
|
}
|
|
6608
7334
|
var formalEngine = new FormalEngine();
|
|
6609
7335
|
async function runAnalysis(input) {
|
|
7336
|
+
const observedFormalLayers = [];
|
|
6610
7337
|
const heuristicResult = isHeuristicEnabled() ? runHeuristicCheck(input) : null;
|
|
6611
7338
|
const isInlineScriptCmd = isInlineScriptCommand(input.aNext.actionDetail.toLowerCase());
|
|
6612
7339
|
const isLiteralTextCmd = isLiteralTextCommand(input.aNext.actionDetail.toLowerCase());
|
|
@@ -6632,7 +7359,7 @@ async function runAnalysis(input) {
|
|
|
6632
7359
|
riskType: node.l1RiskType,
|
|
6633
7360
|
confidence: 90,
|
|
6634
7361
|
source: "heuristic_script_scan",
|
|
6635
|
-
violatedLayers: ["
|
|
7362
|
+
violatedLayers: ["L1.2"]
|
|
6636
7363
|
};
|
|
6637
7364
|
}
|
|
6638
7365
|
}
|
|
@@ -6661,8 +7388,8 @@ async function runAnalysis(input) {
|
|
|
6661
7388
|
const rawDetail = extractRawCommand(input.aNext.actionDetail);
|
|
6662
7389
|
const lowerDetail = rawDetail.toLowerCase();
|
|
6663
7390
|
const agentExt = mergeAgentExtensions(
|
|
6664
|
-
getAgentExtensions(input.agentId),
|
|
6665
|
-
|
|
7391
|
+
isLayerEnabled("agent_rules_enabled") ? getAgentExtensions(input.agentId) : null,
|
|
7392
|
+
isLayerEnabled("scenario_rules_enabled") ? getScenarioExtensionsMulti(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "") : null
|
|
6666
7393
|
);
|
|
6667
7394
|
if (FULLY_SAFE_TOOLS.has(actionType)) {
|
|
6668
7395
|
return {
|
|
@@ -6750,7 +7477,7 @@ async function runAnalysis(input) {
|
|
|
6750
7477
|
riskType: inlineJudgment.riskType,
|
|
6751
7478
|
confidence: 90,
|
|
6752
7479
|
source: "logic_rule",
|
|
6753
|
-
violatedLayers: ["1
|
|
7480
|
+
violatedLayers: ["L2.1"]
|
|
6754
7481
|
};
|
|
6755
7482
|
}
|
|
6756
7483
|
return {
|
|
@@ -6779,15 +7506,22 @@ async function runAnalysis(input) {
|
|
|
6779
7506
|
const formalResult = formalEngine.verify(formalSessions, input.aNext);
|
|
6780
7507
|
if (!formalResult.verified) {
|
|
6781
7508
|
const v = formalResult.violations[0];
|
|
6782
|
-
|
|
6783
|
-
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
6789
|
-
|
|
6790
|
-
|
|
7509
|
+
if (isFormalObserveMode()) {
|
|
7510
|
+
console.warn(
|
|
7511
|
+
`[AgentMoss] \u5F62\u5F0F\u5316\u9A8C\u8BC1 observe\uFF08\u53EA\u8BB0\u5F55\u4E0D\u62E6\u622A\uFF09: property=${v?.propertyId ?? "?"} risk=${v?.riskLevel ?? "?"} path_len=${v?.path?.length ?? 0} reason=${(v?.reason ?? "?").slice(0, 200)}`
|
|
7512
|
+
);
|
|
7513
|
+
observedFormalLayers.push("L2.2");
|
|
7514
|
+
} else {
|
|
7515
|
+
return {
|
|
7516
|
+
allowed: false,
|
|
7517
|
+
reason: v ? v.reason : "\u5F62\u5F0F\u5316\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF08\u884C\u4E3A\u6027\u8D28\u8FDD\u53CD\uFF09",
|
|
7518
|
+
riskLevel: v ? v.riskLevel : "high",
|
|
7519
|
+
riskType: "formal_verification",
|
|
7520
|
+
confidence: 90,
|
|
7521
|
+
source: "formal_engine",
|
|
7522
|
+
violatedLayers: ["L2.2"]
|
|
7523
|
+
};
|
|
7524
|
+
}
|
|
6791
7525
|
}
|
|
6792
7526
|
}
|
|
6793
7527
|
if (scriptChain && scriptChain.nodes.length > 0 && isLogicRulesEnabled()) {
|
|
@@ -6802,7 +7536,7 @@ async function runAnalysis(input) {
|
|
|
6802
7536
|
riskType: node.l2RiskType,
|
|
6803
7537
|
confidence: 85,
|
|
6804
7538
|
source: "logic_rule_script_scan",
|
|
6805
|
-
violatedLayers: ["
|
|
7539
|
+
violatedLayers: ["L2.3"]
|
|
6806
7540
|
};
|
|
6807
7541
|
}
|
|
6808
7542
|
}
|
|
@@ -6834,7 +7568,7 @@ async function runAnalysis(input) {
|
|
|
6834
7568
|
riskType: "",
|
|
6835
7569
|
confidence: 85,
|
|
6836
7570
|
source: "heuristic_and_logic_only",
|
|
6837
|
-
violatedLayers: []
|
|
7571
|
+
violatedLayers: observedFormalLayers.length > 0 ? [...observedFormalLayers] : []
|
|
6838
7572
|
};
|
|
6839
7573
|
}
|
|
6840
7574
|
let skipL3Hints = "\u65E0\uFF08\u5F53\u524D\u65E0\u7981\u7528\u89C4\u5219\u547D\u4E2D\uFF09";
|
|
@@ -6879,7 +7613,7 @@ async function runAnalysis(input) {
|
|
|
6879
7613
|
riskType: "",
|
|
6880
7614
|
confidence: 50,
|
|
6881
7615
|
source: "llm_failure_fallback",
|
|
6882
|
-
violatedLayers: []
|
|
7616
|
+
violatedLayers: observedFormalLayers.length > 0 ? [...observedFormalLayers] : []
|
|
6883
7617
|
};
|
|
6884
7618
|
}
|
|
6885
7619
|
return {
|
|
@@ -6889,7 +7623,7 @@ async function runAnalysis(input) {
|
|
|
6889
7623
|
riskType: "",
|
|
6890
7624
|
confidence: 60,
|
|
6891
7625
|
source: "llm_failure_fallback",
|
|
6892
|
-
violatedLayers: ["
|
|
7626
|
+
violatedLayers: ["L3.1"]
|
|
6893
7627
|
};
|
|
6894
7628
|
}
|
|
6895
7629
|
if (!llmResult.allowed && llmResult.confidence && llmResult.confidence >= 90 && _brainExtractor) {
|
|
@@ -6906,6 +7640,9 @@ async function runAnalysis(input) {
|
|
|
6906
7640
|
} catch {
|
|
6907
7641
|
}
|
|
6908
7642
|
}
|
|
7643
|
+
if (observedFormalLayers.length > 0 && llmResult.allowed) {
|
|
7644
|
+
llmResult.violatedLayers = Array.from(/* @__PURE__ */ new Set([...observedFormalLayers, ...llmResult.violatedLayers || []]));
|
|
7645
|
+
}
|
|
6909
7646
|
return llmResult;
|
|
6910
7647
|
}
|
|
6911
7648
|
export {
|
|
@@ -6913,4 +7650,17 @@ export {
|
|
|
6913
7650
|
_resetBrainForTest,
|
|
6914
7651
|
runAnalysis
|
|
6915
7652
|
};
|
|
7653
|
+
//! 顺序即 fail-closed 短路优先级:verify 逐个执行,首个返回违规的性质成为
|
|
7654
|
+
//! first_violation,后续不再评估。调整顺序影响判定归属,须与 Python
|
|
7655
|
+
//! properties.py ALL_PROPERTIES 保持一致并双端测试确认。
|
|
7656
|
+
//!
|
|
7657
|
+
//! shortName(P1~P8 连续短名)与 id(语义化英文)对照(与 Python 一致):
|
|
7658
|
+
//! P1 cross-session-chain 跨会话组合链
|
|
7659
|
+
//! P2 sensitive-data-flow 敏感数据传播
|
|
7660
|
+
//! P3 low-integrity-reachability 低完整性数据流可达
|
|
7661
|
+
//! P4 no-exfil-after-privesc 提权后禁外传
|
|
7662
|
+
//! P5 no-exfil-after-credential 凭据后禁外传
|
|
7663
|
+
//! P6 mc-ltl-rules LTL 模型检测
|
|
7664
|
+
//! P7 low-integrity-command-exec 低完整性命令执行
|
|
7665
|
+
//! P8 sensitive-data-aggregation 敏感数据聚合外传
|
|
6916
7666
|
//# sourceMappingURL=coordinator.js.map
|