@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
package/dist/server.js
CHANGED
|
@@ -137,6 +137,7 @@ __export(config_exports, {
|
|
|
137
137
|
getRetryConfig: () => getRetryConfig,
|
|
138
138
|
getTimeoutConfig: () => getTimeoutConfig,
|
|
139
139
|
isBrainEnabled: () => isBrainEnabled,
|
|
140
|
+
isFormalObserveMode: () => isFormalObserveMode,
|
|
140
141
|
isHeuristicEnabled: () => isHeuristicEnabled,
|
|
141
142
|
isLLMAvailable: () => isLLMAvailable,
|
|
142
143
|
isLogicRulesEnabled: () => isLogicRulesEnabled,
|
|
@@ -252,6 +253,7 @@ function getCustomRules() {
|
|
|
252
253
|
}
|
|
253
254
|
function isLLMAvailable() {
|
|
254
255
|
if (process.env.AGENT_MOSS_DISABLE_LLM === "1") return false;
|
|
256
|
+
if (!layerEnabledOrDefault("L3_llm_analysis")) return false;
|
|
255
257
|
return llmConfig.apiKey.length > 0;
|
|
256
258
|
}
|
|
257
259
|
async function probeLLMConnection(timeoutMs = 8e3) {
|
|
@@ -316,13 +318,26 @@ function getProviderHeaders(provider) {
|
|
|
316
318
|
return PROVIDER_HEADERS[provider] || {};
|
|
317
319
|
}
|
|
318
320
|
function isHeuristicEnabled() {
|
|
319
|
-
|
|
321
|
+
if (process.env.AGENT_MOSS_DISABLE_HEURISTIC === "1") return false;
|
|
322
|
+
return layerEnabledOrDefault("L1_heuristic");
|
|
323
|
+
}
|
|
324
|
+
function layerEnabledOrDefault(key) {
|
|
325
|
+
try {
|
|
326
|
+
const { isLayerEnabled: isLayerEnabled2 } = __require("../infra/runtime-config");
|
|
327
|
+
return isLayerEnabled2(key);
|
|
328
|
+
} catch {
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
320
331
|
}
|
|
321
332
|
function isBrainEnabled() {
|
|
322
333
|
return process.env.AGENT_MOSS_BRAIN_ENABLED === "1";
|
|
323
334
|
}
|
|
324
335
|
function isLogicRulesEnabled() {
|
|
325
|
-
|
|
336
|
+
if (process.env.AGENT_MOSS_DISABLE_LOGIC_RULES === "1") return false;
|
|
337
|
+
return layerEnabledOrDefault("L2_logic_rules");
|
|
338
|
+
}
|
|
339
|
+
function isFormalObserveMode() {
|
|
340
|
+
return process.env.AGENT_MOSS_FORMAL_OBSERVE === "1";
|
|
326
341
|
}
|
|
327
342
|
var timeoutConfig, retryConfig, llmConfig, __configModuleDir, USER_RULES_PATHS, customRules, PROVIDER_HEADERS;
|
|
328
343
|
var init_config = __esm({
|
|
@@ -580,8 +595,12 @@ var init_inline_analyzer = __esm({
|
|
|
580
595
|
|
|
581
596
|
// src/infra/rules-loader.ts
|
|
582
597
|
import { readFileSync as readFileSync2, existsSync } from "fs";
|
|
583
|
-
import { resolve, dirname } from "path";
|
|
598
|
+
import { resolve, dirname, join as join2 } from "path";
|
|
584
599
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
600
|
+
import { homedir as homedir2 } from "os";
|
|
601
|
+
function dataRootDir() {
|
|
602
|
+
return process.env.AGENT_MOSS_DATA_DIR || join2(homedir2(), ".config", "agentmoss");
|
|
603
|
+
}
|
|
585
604
|
function findRulesFile(name) {
|
|
586
605
|
for (const dir of RULES_DIR_CANDS) {
|
|
587
606
|
const p = resolve(dir, name);
|
|
@@ -607,6 +626,24 @@ function loadL2() {
|
|
|
607
626
|
function loadL3() {
|
|
608
627
|
return loadJson("l3_rules.json");
|
|
609
628
|
}
|
|
629
|
+
function loadMC() {
|
|
630
|
+
const mc = loadJson("mc_rules.json");
|
|
631
|
+
const rules = mc.rules || [];
|
|
632
|
+
try {
|
|
633
|
+
const rt = (init_runtime_config(), __toCommonJS(runtime_config_exports));
|
|
634
|
+
if (rules.length > 0 && typeof rt.hasRuntimeConfig === "function" && rt.hasRuntimeConfig()) {
|
|
635
|
+
const rtRules = rt.loadRuntimeConfig()?.L2_2?.ltl_rules?.rules || [];
|
|
636
|
+
const enabledById = {};
|
|
637
|
+
for (const r of rtRules) if (r?.id) enabledById[r.id] = r.enabled !== false;
|
|
638
|
+
for (const r of rules) if (r?.id && r.id in enabledById) r.enabled = enabledById[r.id];
|
|
639
|
+
}
|
|
640
|
+
} catch {
|
|
641
|
+
}
|
|
642
|
+
return mc;
|
|
643
|
+
}
|
|
644
|
+
function loadToolRegistry() {
|
|
645
|
+
return loadJson("tool_registry.json");
|
|
646
|
+
}
|
|
610
647
|
var __filename2, __dirname2, RULES_DIR_CANDS;
|
|
611
648
|
var init_rules_loader = __esm({
|
|
612
649
|
"src/infra/rules-loader.ts"() {
|
|
@@ -616,6 +653,10 @@ var init_rules_loader = __esm({
|
|
|
616
653
|
__dirname2 = dirname(__filename2);
|
|
617
654
|
RULES_DIR_CANDS = (() => {
|
|
618
655
|
const cands = [
|
|
656
|
+
// 用户数据目录覆盖优先(与 Python rules_loader._load_json 的
|
|
657
|
+
// <data_root>/rules/<name> 优先逻辑对齐)。用户安装 pip/npm 包后把自定义
|
|
658
|
+
// mc_rules.json 放到 <data_root>/rules/ 即可覆盖内置默认,无需改源码。
|
|
659
|
+
resolve(dataRootDir(), "rules"),
|
|
619
660
|
// dist: __dirname=dist/ → dist/rules;__dirname=dist/engine/ → dist/engine/../rules=dist/rules
|
|
620
661
|
resolve(__dirname2, "../rules"),
|
|
621
662
|
resolve(__dirname2, "rules"),
|
|
@@ -639,7 +680,8 @@ function _loadSensitivePathScope(scope) {
|
|
|
639
680
|
path: sp.path,
|
|
640
681
|
riskLevel: sp.risk_level,
|
|
641
682
|
desc: sp.desc,
|
|
642
|
-
credential: sp.deny_mode === "deny_both" ? true : void 0
|
|
683
|
+
credential: sp.deny_mode === "deny_both" ? true : void 0,
|
|
684
|
+
dirDelOnly: sp.dir_del_only === true ? true : void 0
|
|
643
685
|
}));
|
|
644
686
|
}
|
|
645
687
|
function isWriteOperation(actionType, actionDetail, writeKeywords = WRITE_KEYWORDS) {
|
|
@@ -737,7 +779,7 @@ function runLogicCheck(input) {
|
|
|
737
779
|
riskType: "intent_deviation",
|
|
738
780
|
confidence: 85,
|
|
739
781
|
source: "logic_rule",
|
|
740
|
-
violatedLayers: ["
|
|
782
|
+
violatedLayers: ["L2.1"]
|
|
741
783
|
};
|
|
742
784
|
}
|
|
743
785
|
}
|
|
@@ -761,7 +803,7 @@ function runLogicCheck(input) {
|
|
|
761
803
|
riskType: "privilege_escalation",
|
|
762
804
|
confidence: 85,
|
|
763
805
|
source: "logic_rule",
|
|
764
|
-
violatedLayers: ["
|
|
806
|
+
violatedLayers: ["L2.1"]
|
|
765
807
|
};
|
|
766
808
|
}
|
|
767
809
|
}
|
|
@@ -813,7 +855,7 @@ function checkComboChain(actionHistory, actionDetailRaw) {
|
|
|
813
855
|
riskType: "combo_chain",
|
|
814
856
|
confidence: 85,
|
|
815
857
|
source: "logic_rule",
|
|
816
|
-
violatedLayers: ["
|
|
858
|
+
violatedLayers: ["L2.1"]
|
|
817
859
|
};
|
|
818
860
|
}
|
|
819
861
|
}
|
|
@@ -938,7 +980,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
|
|
|
938
980
|
riskType: "read_before_write",
|
|
939
981
|
confidence: 85,
|
|
940
982
|
source: "logic_rule",
|
|
941
|
-
violatedLayers: ["
|
|
983
|
+
violatedLayers: ["L2.1"]
|
|
942
984
|
};
|
|
943
985
|
}
|
|
944
986
|
return null;
|
|
@@ -958,7 +1000,7 @@ function checkPasswordConsent(actionType, actionDetail, actionHistory) {
|
|
|
958
1000
|
riskType: "consent_missing",
|
|
959
1001
|
confidence: 85,
|
|
960
1002
|
source: "logic_rule",
|
|
961
|
-
violatedLayers: ["
|
|
1003
|
+
violatedLayers: ["L2.1"]
|
|
962
1004
|
};
|
|
963
1005
|
}
|
|
964
1006
|
function checkUserDeletionConsent(actionType, actionDetail, actionHistory) {
|
|
@@ -976,7 +1018,7 @@ function checkUserDeletionConsent(actionType, actionDetail, actionHistory) {
|
|
|
976
1018
|
riskType: "consent_missing",
|
|
977
1019
|
confidence: 85,
|
|
978
1020
|
source: "logic_rule",
|
|
979
|
-
violatedLayers: ["
|
|
1021
|
+
violatedLayers: ["L2.1"]
|
|
980
1022
|
};
|
|
981
1023
|
}
|
|
982
1024
|
function containsPrivilegeEscalation(actionDetail) {
|
|
@@ -1036,7 +1078,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
|
|
|
1036
1078
|
riskType: "file_access",
|
|
1037
1079
|
confidence: 95,
|
|
1038
1080
|
source: "logic_rule",
|
|
1039
|
-
violatedLayers: ["
|
|
1081
|
+
violatedLayers: ["L2.1"],
|
|
1040
1082
|
isCredential: true
|
|
1041
1083
|
};
|
|
1042
1084
|
}
|
|
@@ -1052,7 +1094,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
|
|
|
1052
1094
|
riskType: "file_access",
|
|
1053
1095
|
confidence: 95,
|
|
1054
1096
|
source: "logic_rule",
|
|
1055
|
-
violatedLayers: ["
|
|
1097
|
+
violatedLayers: ["L2.1"]
|
|
1056
1098
|
};
|
|
1057
1099
|
}
|
|
1058
1100
|
}
|
|
@@ -1066,7 +1108,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
|
|
|
1066
1108
|
riskType: "file_access",
|
|
1067
1109
|
confidence: 70,
|
|
1068
1110
|
source: "logic_rule",
|
|
1069
|
-
violatedLayers: ["
|
|
1111
|
+
violatedLayers: ["L2.1"]
|
|
1070
1112
|
};
|
|
1071
1113
|
}
|
|
1072
1114
|
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;
|
|
@@ -1078,7 +1120,7 @@ var init_logic_rules = __esm({
|
|
|
1078
1120
|
init_rules_loader();
|
|
1079
1121
|
ROOT_SENSITIVE_PATHS = _loadSensitivePathScope("root");
|
|
1080
1122
|
HOME_SENSITIVE_PATHS = _loadSensitivePathScope("home");
|
|
1081
|
-
CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path6, riskLevel, desc }) => ({ path: path6, riskLevel, desc, credential: true }));
|
|
1123
|
+
CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path6, riskLevel, desc, dirDelOnly }) => ({ path: path6, riskLevel, desc, credential: true, dirDelOnly }));
|
|
1082
1124
|
NON_WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
1083
1125
|
"ask_user_question",
|
|
1084
1126
|
"glob",
|
|
@@ -1353,6 +1395,7 @@ var init_skill_loader = __esm({
|
|
|
1353
1395
|
// src/infra/runtime-config.ts
|
|
1354
1396
|
var runtime_config_exports = {};
|
|
1355
1397
|
__export(runtime_config_exports, {
|
|
1398
|
+
DEFAULT_LAYERS: () => DEFAULT_LAYERS,
|
|
1356
1399
|
DENY_MODE_LABELS: () => DENY_MODE_LABELS,
|
|
1357
1400
|
addAgentRule: () => addAgentRule,
|
|
1358
1401
|
addCustomRule: () => addCustomRule,
|
|
@@ -1404,7 +1447,7 @@ __export(runtime_config_exports, {
|
|
|
1404
1447
|
updateSkillEnabled: () => updateSkillEnabled
|
|
1405
1448
|
});
|
|
1406
1449
|
import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync4, mkdirSync, unlinkSync, readdirSync as readdirSync3 } from "fs";
|
|
1407
|
-
import { join as
|
|
1450
|
+
import { join as join3 } from "path";
|
|
1408
1451
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
1409
1452
|
import { dirname as dirname3 } from "path";
|
|
1410
1453
|
function runtimeDir() {
|
|
@@ -1428,6 +1471,7 @@ function emptyRuntimeConfig() {
|
|
|
1428
1471
|
L1_rules: {},
|
|
1429
1472
|
L2_rules: {},
|
|
1430
1473
|
L3_skills: {},
|
|
1474
|
+
L2_2: {},
|
|
1431
1475
|
agent_rules: {},
|
|
1432
1476
|
scenario_rules: {}
|
|
1433
1477
|
};
|
|
@@ -1455,6 +1499,7 @@ function loadRuntimeConfig() {
|
|
|
1455
1499
|
L1_rules: merged.L1_rules || {},
|
|
1456
1500
|
L2_rules: merged.L2_rules || {},
|
|
1457
1501
|
L3_skills: merged.L3_skills || {},
|
|
1502
|
+
L2_2: merged.L2_2 || {},
|
|
1458
1503
|
agent_rules: merged.agent_rules || {},
|
|
1459
1504
|
scenario_rules: merged.scenario_rules || {}
|
|
1460
1505
|
};
|
|
@@ -1507,6 +1552,7 @@ function getEnabledL2SensitivePaths(runtime) {
|
|
|
1507
1552
|
const out = { path: r.path || "", risk_level: r.risk_level || "", desc: r.desc || "", deny_mode: r.deny_mode || "deny_write" };
|
|
1508
1553
|
if (r.credential) out.credential = true;
|
|
1509
1554
|
if (r.read_only) out.read_only = true;
|
|
1555
|
+
if (r.dir_del_only) out.dir_del_only = true;
|
|
1510
1556
|
return out;
|
|
1511
1557
|
});
|
|
1512
1558
|
}
|
|
@@ -1615,6 +1661,7 @@ function _l2SensitivePathsToRules() {
|
|
|
1615
1661
|
builtin: true
|
|
1616
1662
|
};
|
|
1617
1663
|
_syncDenyModeFields(rule);
|
|
1664
|
+
if (sp.dirDelOnly) rule.dir_del_only = true;
|
|
1618
1665
|
return rule;
|
|
1619
1666
|
});
|
|
1620
1667
|
}
|
|
@@ -1702,7 +1749,7 @@ function _scenarioRulesToDefaults() {
|
|
|
1702
1749
|
return out;
|
|
1703
1750
|
}
|
|
1704
1751
|
for (const scenarioId of entries) {
|
|
1705
|
-
const rulesFile =
|
|
1752
|
+
const rulesFile = join3(scenDir, scenarioId, "rules.json");
|
|
1706
1753
|
if (!existsSync4(rulesFile)) continue;
|
|
1707
1754
|
let data;
|
|
1708
1755
|
try {
|
|
@@ -1751,7 +1798,7 @@ function _agentRulesToDefaults() {
|
|
|
1751
1798
|
return out;
|
|
1752
1799
|
}
|
|
1753
1800
|
for (const agentId of entries) {
|
|
1754
|
-
const rulesFile =
|
|
1801
|
+
const rulesFile = join3(agentsDir, agentId, "rules.json");
|
|
1755
1802
|
if (!existsSync4(rulesFile)) continue;
|
|
1756
1803
|
let data;
|
|
1757
1804
|
try {
|
|
@@ -1792,7 +1839,7 @@ function _agentRulesToDefaults() {
|
|
|
1792
1839
|
function generateSourceDefaults() {
|
|
1793
1840
|
return {
|
|
1794
1841
|
version: 1,
|
|
1795
|
-
layers: {
|
|
1842
|
+
layers: { ...DEFAULT_LAYERS },
|
|
1796
1843
|
L1_rules: {
|
|
1797
1844
|
dangerous_commands: {
|
|
1798
1845
|
category_enabled: true,
|
|
@@ -1831,10 +1878,45 @@ function generateSourceDefaults() {
|
|
|
1831
1878
|
user_custom: { category_enabled: true, category_desc: "\u7528\u6237\u81EA\u5B9A\u4E49 L2 \u903B\u8F91\u89C4\u5219", rules: [] }
|
|
1832
1879
|
},
|
|
1833
1880
|
L3_skills: _l3SkillsToRules(),
|
|
1881
|
+
L2_2: _ltlRulesToRuntime(),
|
|
1834
1882
|
agent_rules: _agentRulesToDefaults(),
|
|
1835
1883
|
scenario_rules: _scenarioRulesToDefaults()
|
|
1836
1884
|
};
|
|
1837
1885
|
}
|
|
1886
|
+
function _ltlRulesToRuntime() {
|
|
1887
|
+
const cands = [
|
|
1888
|
+
join3(__dirname4, "..", "rules"),
|
|
1889
|
+
join3(__dirname4, "..", "..", "..", "agent_moss", "rules"),
|
|
1890
|
+
join3(process.cwd(), "agent_moss", "rules")
|
|
1891
|
+
];
|
|
1892
|
+
let mc = {};
|
|
1893
|
+
for (const dir of cands) {
|
|
1894
|
+
const p = join3(dir, "mc_rules.json");
|
|
1895
|
+
if (existsSync4(p)) {
|
|
1896
|
+
try {
|
|
1897
|
+
mc = JSON.parse(readFileSync4(p, "utf-8")) || {};
|
|
1898
|
+
} catch {
|
|
1899
|
+
mc = {};
|
|
1900
|
+
}
|
|
1901
|
+
break;
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
const rules = mc.rules || [];
|
|
1905
|
+
return {
|
|
1906
|
+
ltl_rules: {
|
|
1907
|
+
category_enabled: true,
|
|
1908
|
+
category_desc: "LTL \u653B\u51FB\u6A21\u5F0F\u89C4\u5219\uFF08\u65F6\u5E8F\u653B\u51FB\u68C0\u6D4B\uFF0C\u5148 A \u540E B\uFF09",
|
|
1909
|
+
rules: rules.map((r) => ({
|
|
1910
|
+
id: r.id,
|
|
1911
|
+
reason: r.reason || "",
|
|
1912
|
+
risk_level: r.risk_level || "high",
|
|
1913
|
+
action: r.action || "block",
|
|
1914
|
+
enabled: true,
|
|
1915
|
+
builtin: true
|
|
1916
|
+
}))
|
|
1917
|
+
}
|
|
1918
|
+
};
|
|
1919
|
+
}
|
|
1838
1920
|
function normalizePatternForFingerprint(pattern) {
|
|
1839
1921
|
return pattern.replace(/\\\//g, "/");
|
|
1840
1922
|
}
|
|
@@ -1984,6 +2066,11 @@ function mergeConfig(userCfg, srcDefaults) {
|
|
|
1984
2066
|
);
|
|
1985
2067
|
_mergeRuleCategories(cfg, srcDefaults, "L1_rules");
|
|
1986
2068
|
_mergeRuleCategories(cfg, srcDefaults, "L2_rules");
|
|
2069
|
+
if (!cfg.L2_2 && cfg.L2_4) {
|
|
2070
|
+
cfg.L2_2 = cfg.L2_4;
|
|
2071
|
+
delete cfg.L2_4;
|
|
2072
|
+
}
|
|
2073
|
+
_mergeRuleCategories(cfg, srcDefaults, "L2_2");
|
|
1987
2074
|
_mergeSkillCategories(cfg, srcDefaults, "L3_skills");
|
|
1988
2075
|
_mergeAgentRules(cfg, srcDefaults);
|
|
1989
2076
|
_mergeAgentRules(cfg, srcDefaults, "scenario_rules");
|
|
@@ -2176,12 +2263,12 @@ function deleteCustomRule(layer, category, ruleId) {
|
|
|
2176
2263
|
function addCustomSkill(skillId, category, keywords, content) {
|
|
2177
2264
|
_validateSkillId(skillId);
|
|
2178
2265
|
const runtime = loadRuntimeConfig();
|
|
2179
|
-
const skillsDir =
|
|
2266
|
+
const skillsDir = join3(RUNTIME_CONFIG_DIR, "agent_moss_skills");
|
|
2180
2267
|
try {
|
|
2181
2268
|
mkdirSync(skillsDir, { recursive: true });
|
|
2182
2269
|
} catch {
|
|
2183
2270
|
}
|
|
2184
|
-
writeFileSync(
|
|
2271
|
+
writeFileSync(join3(skillsDir, `${skillId}.md`), content, "utf-8");
|
|
2185
2272
|
const l3 = runtime.L3_skills || (runtime.L3_skills = {});
|
|
2186
2273
|
const cat = l3[category] || (l3[category] = { category_enabled: true, category_desc: "", skills: [] });
|
|
2187
2274
|
(cat.skills || (cat.skills = [])).push({ id: skillId, keywords, enabled: true, builtin: false });
|
|
@@ -2196,7 +2283,7 @@ function deleteCustomSkill(skillId, category) {
|
|
|
2196
2283
|
if (idx >= 0) {
|
|
2197
2284
|
if (skills[idx].builtin) return null;
|
|
2198
2285
|
skills.splice(idx, 1);
|
|
2199
|
-
const skillPath =
|
|
2286
|
+
const skillPath = join3(RUNTIME_CONFIG_DIR, "agent_moss_skills", `${skillId}.md`);
|
|
2200
2287
|
try {
|
|
2201
2288
|
if (existsSync4(skillPath)) unlinkSync(skillPath);
|
|
2202
2289
|
} catch {
|
|
@@ -2390,7 +2477,7 @@ function updateScenarioCategoryEnabled(scenarioId, category, enabled) {
|
|
|
2390
2477
|
saveRuntimeConfig(runtime);
|
|
2391
2478
|
return runtime;
|
|
2392
2479
|
}
|
|
2393
|
-
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;
|
|
2480
|
+
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;
|
|
2394
2481
|
var init_runtime_config = __esm({
|
|
2395
2482
|
"src/infra/runtime-config.ts"() {
|
|
2396
2483
|
"use strict";
|
|
@@ -2402,21 +2489,28 @@ var init_runtime_config = __esm({
|
|
|
2402
2489
|
__dirname4 = dirname3(fileURLToPath5(import.meta.url));
|
|
2403
2490
|
ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
|
|
2404
2491
|
RUNTIME_CONFIG_DIR = runtimeDir();
|
|
2405
|
-
RUNTIME_CONFIG_PATH =
|
|
2492
|
+
RUNTIME_CONFIG_PATH = join3(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
|
|
2406
2493
|
LAYER_DISABLE_ENV = {
|
|
2407
2494
|
L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
|
|
2408
2495
|
L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
|
|
2409
2496
|
L3_llm_analysis: "AGENT_MOSS_DISABLE_LLM"
|
|
2410
2497
|
};
|
|
2498
|
+
DEFAULT_LAYERS = {
|
|
2499
|
+
L1_heuristic: true,
|
|
2500
|
+
L2_logic_rules: true,
|
|
2501
|
+
L3_llm_analysis: true,
|
|
2502
|
+
agent_rules_enabled: true,
|
|
2503
|
+
scenario_rules_enabled: true
|
|
2504
|
+
};
|
|
2411
2505
|
DENY_MODE_LABELS = {
|
|
2412
2506
|
deny_write: "\u4EC5\u62E6\u622A\u5199\u5165",
|
|
2413
2507
|
deny_read: "\u4EC5\u62E6\u622A\u8BFB\u53D6",
|
|
2414
2508
|
deny_both: "\u8BFB\u5199\u5747\u62E6\u622A"
|
|
2415
2509
|
};
|
|
2416
|
-
_AGENTS_DIR_PROD =
|
|
2417
|
-
_AGENTS_DIR_DEV =
|
|
2418
|
-
_SCENARIOS_DIR_PROD =
|
|
2419
|
-
_SCENARIOS_DIR_DEV =
|
|
2510
|
+
_AGENTS_DIR_PROD = join3(__dirname4, "agents");
|
|
2511
|
+
_AGENTS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "agents");
|
|
2512
|
+
_SCENARIOS_DIR_PROD = join3(__dirname4, "scenarios");
|
|
2513
|
+
_SCENARIOS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
|
|
2420
2514
|
SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
|
|
2421
2515
|
}
|
|
2422
2516
|
});
|
|
@@ -2554,11 +2648,21 @@ function getScenarioExtensions(scenarioId) {
|
|
|
2554
2648
|
);
|
|
2555
2649
|
return ext;
|
|
2556
2650
|
}
|
|
2651
|
+
function getScenarioExtensionsMulti(scenarioSpec) {
|
|
2652
|
+
if (!scenarioSpec) return null;
|
|
2653
|
+
const ids = scenarioSpec.split(",").map((s) => s.trim()).filter(Boolean);
|
|
2654
|
+
if (ids.length === 0) return null;
|
|
2655
|
+
const exts = ids.map((id) => getScenarioExtensions(id));
|
|
2656
|
+
const nonNull = exts.filter((e) => e !== null);
|
|
2657
|
+
if (nonNull.length === 0) return null;
|
|
2658
|
+
return mergeAgentExtensions(...nonNull);
|
|
2659
|
+
}
|
|
2557
2660
|
var __filename5, __dirname6, SCENARIOS_DIR_PROD, SCENARIOS_DIR_DEV_FALLBACK, cache2;
|
|
2558
2661
|
var init_scenarios = __esm({
|
|
2559
2662
|
"src/scenarios/index.ts"() {
|
|
2560
2663
|
"use strict";
|
|
2561
2664
|
init_esm_shims();
|
|
2665
|
+
init_agents();
|
|
2562
2666
|
__filename5 = fileURLToPath7(import.meta.url);
|
|
2563
2667
|
__dirname6 = dirname5(__filename5);
|
|
2564
2668
|
SCENARIOS_DIR_PROD = resolve4(__dirname6, "scenarios");
|
|
@@ -2586,7 +2690,7 @@ function runHeuristicCheck(input) {
|
|
|
2586
2690
|
riskType: "sensitive_action",
|
|
2587
2691
|
confidence: 90,
|
|
2588
2692
|
source: "heuristic",
|
|
2589
|
-
violatedLayers: ["
|
|
2693
|
+
violatedLayers: ["L1.1"]
|
|
2590
2694
|
};
|
|
2591
2695
|
}
|
|
2592
2696
|
}
|
|
@@ -2596,8 +2700,10 @@ function runHeuristicCheck(input) {
|
|
|
2596
2700
|
const scanDetail = normalizePathSegments(stripLiteralTextSegments(input.aNext.actionDetail));
|
|
2597
2701
|
const scanCommand = `${input.aNext.actionType}: ${scanDetail}`;
|
|
2598
2702
|
let topRiskType = "";
|
|
2703
|
+
const exemptKeygen = isSshKeygenGenerate(input.aNext.actionDetail);
|
|
2599
2704
|
for (const rule of DANGEROUS_PATTERNS) {
|
|
2600
2705
|
if (rule.pattern.test(scanCommand)) {
|
|
2706
|
+
if (exemptKeygen && rule.riskType === "file_access") continue;
|
|
2601
2707
|
matched.push(rule.description);
|
|
2602
2708
|
if (severityRank(rule.severity) > severityRank(highestSeverity)) {
|
|
2603
2709
|
highestSeverity = rule.severity;
|
|
@@ -2607,8 +2713,8 @@ function runHeuristicCheck(input) {
|
|
|
2607
2713
|
}
|
|
2608
2714
|
highestRiskType = topRiskType || highestRiskType;
|
|
2609
2715
|
const agentExt = mergeAgentExtensions(
|
|
2610
|
-
getAgentExtensions(input.agentId),
|
|
2611
|
-
|
|
2716
|
+
isLayerEnabled("agent_rules_enabled") ? getAgentExtensions(input.agentId) : null,
|
|
2717
|
+
isLayerEnabled("scenario_rules_enabled") ? getScenarioExtensionsMulti(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "") : null
|
|
2612
2718
|
);
|
|
2613
2719
|
if (agentExt?.extra_command_patterns) {
|
|
2614
2720
|
for (const rule of agentExt.extra_command_patterns) {
|
|
@@ -2639,10 +2745,26 @@ function runHeuristicCheck(input) {
|
|
|
2639
2745
|
const detailL = input.aNext.actionDetail.toLowerCase();
|
|
2640
2746
|
const isExcludedType = SENSITIVE_EXCLUDED_PREFIXES.some((p) => actionTypeL.startsWith(p));
|
|
2641
2747
|
const dangerousResult = checkDangerousPatterns(actionTypeL, detailL);
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2748
|
+
const pathResult = isExcludedType ? null : checkSensitivePath(detailL, input.cwd || "/", agentExt);
|
|
2749
|
+
const homeResult = checkHomeDeletion(actionTypeL, detailL);
|
|
2750
|
+
const mergeResults = [dangerousResult, pathResult, homeResult].filter(Boolean);
|
|
2751
|
+
if (mergeResults.length > 0) {
|
|
2752
|
+
let best = mergeResults[0];
|
|
2753
|
+
for (const r of mergeResults) {
|
|
2754
|
+
if (severityRank(r.riskLevel) > severityRank(best.riskLevel)) best = r;
|
|
2755
|
+
}
|
|
2756
|
+
if (severityRank(highestSeverity) > severityRank(best.riskLevel)) {
|
|
2757
|
+
return {
|
|
2758
|
+
allowed: false,
|
|
2759
|
+
reason: `\u68C0\u6D4B\u5230\u9AD8\u5371\u64CD\u4F5C: ${matched.join(", ")}`,
|
|
2760
|
+
riskLevel: highestSeverity,
|
|
2761
|
+
riskType: highestRiskType,
|
|
2762
|
+
confidence: 95,
|
|
2763
|
+
source: "heuristic",
|
|
2764
|
+
violatedLayers: ["L1.1"]
|
|
2765
|
+
};
|
|
2766
|
+
}
|
|
2767
|
+
return best;
|
|
2646
2768
|
}
|
|
2647
2769
|
if (matched.length > 0) {
|
|
2648
2770
|
return {
|
|
@@ -2652,8 +2774,7 @@ function runHeuristicCheck(input) {
|
|
|
2652
2774
|
riskType: highestRiskType,
|
|
2653
2775
|
confidence: 95,
|
|
2654
2776
|
source: "heuristic",
|
|
2655
|
-
violatedLayers: ["
|
|
2656
|
-
// 对齐 py coordinator.py:165(L1.1 = 命中高危命令模式/注入关键词)
|
|
2777
|
+
violatedLayers: ["L1.1"]
|
|
2657
2778
|
};
|
|
2658
2779
|
}
|
|
2659
2780
|
const lateralResult = checkLateralMovement(detailL);
|
|
@@ -2781,6 +2902,7 @@ function isSearchActionType(actionType) {
|
|
|
2781
2902
|
function checkSensitivePath(command, cwd, agentExt) {
|
|
2782
2903
|
const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
|
|
2783
2904
|
const isWriteOpFlag = isWriteOperation("", command);
|
|
2905
|
+
const keygenGenerate = isSshKeygenGenerate(command);
|
|
2784
2906
|
const pathScanText = normalizePathSegments(stripLiteralTextSegments(command));
|
|
2785
2907
|
const pathMatches = (text, path6) => {
|
|
2786
2908
|
const parts = path6.split(/(\(\?!.*?\))/);
|
|
@@ -2793,6 +2915,7 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2793
2915
|
};
|
|
2794
2916
|
for (const sp of ROOT_SENSITIVE_PATHS) {
|
|
2795
2917
|
if (pathMatches(pathScanText, sp.path.toLowerCase())) {
|
|
2918
|
+
if (keygenGenerate) continue;
|
|
2796
2919
|
if (sp.path === "/dev/random") {
|
|
2797
2920
|
if (isWriteOpFlag) {
|
|
2798
2921
|
continue;
|
|
@@ -2804,7 +2927,7 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2804
2927
|
riskType: "file_access",
|
|
2805
2928
|
confidence: 90,
|
|
2806
2929
|
source: "heuristic",
|
|
2807
|
-
violatedLayers: ["
|
|
2930
|
+
violatedLayers: ["L1.1"]
|
|
2808
2931
|
};
|
|
2809
2932
|
}
|
|
2810
2933
|
if (!isWriteOpFlag && !sp.credential) {
|
|
@@ -2817,16 +2940,51 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2817
2940
|
riskType: "file_access",
|
|
2818
2941
|
confidence: 90,
|
|
2819
2942
|
source: "heuristic",
|
|
2820
|
-
violatedLayers: ["
|
|
2943
|
+
violatedLayers: ["L1.1"],
|
|
2821
2944
|
isCredential: !!sp.credential
|
|
2822
2945
|
};
|
|
2823
2946
|
}
|
|
2824
2947
|
}
|
|
2825
2948
|
for (const sp of HOME_SENSITIVE_PATHS) {
|
|
2826
2949
|
const expanded = `${expandedCwd}/${sp.path}`;
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2950
|
+
let pPath = sp.path;
|
|
2951
|
+
if (!sp.path.endsWith(".pub")) pPath = `${sp.path}(?!\\.pub)`;
|
|
2952
|
+
let dirPrefixMatch = null;
|
|
2953
|
+
if (sp.dirDelOnly) {
|
|
2954
|
+
const dirRe = (t) => new RegExp(`(?:^|[\\s/\\\\'"])${t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:/|[\\s'"\\\\]|$)`, "i");
|
|
2955
|
+
dirPrefixMatch = dirRe(sp.path).test(pathScanText) || dirRe(expanded.toLowerCase()).test(pathScanText);
|
|
2956
|
+
}
|
|
2957
|
+
const pathReMatch = sp.dirDelOnly ? !!dirPrefixMatch : pathMatches(pathScanText, pPath);
|
|
2958
|
+
const expandedReMatch = sp.dirDelOnly ? false : pathMatches(pathScanText, expanded + (sp.path.endsWith(".pub") ? "" : "(?!\\.pub)"));
|
|
2959
|
+
if (pathReMatch || expandedReMatch) {
|
|
2960
|
+
if (keygenGenerate) continue;
|
|
2961
|
+
if (sp.dirDelOnly) {
|
|
2962
|
+
if (isDeleteActionForDir(command, sp.path)) {
|
|
2963
|
+
return {
|
|
2964
|
+
allowed: false,
|
|
2965
|
+
reason: `\u5220\u9664\u654F\u611F\u76EE\u5F55: ${sp.desc} (${sp.path})`,
|
|
2966
|
+
riskLevel: sp.riskLevel,
|
|
2967
|
+
riskType: "file_access",
|
|
2968
|
+
confidence: 90,
|
|
2969
|
+
source: "heuristic",
|
|
2970
|
+
violatedLayers: ["L1.1"],
|
|
2971
|
+
isCredential: !!sp.credential
|
|
2972
|
+
};
|
|
2973
|
+
}
|
|
2974
|
+
if (isArchiveReadOfDir(command, sp.path)) {
|
|
2975
|
+
return {
|
|
2976
|
+
allowed: false,
|
|
2977
|
+
reason: `\u5F52\u6863/\u590D\u5236\u8BFB\u53D6\u654F\u611F\u76EE\u5F55: ${sp.desc} (${sp.path})`,
|
|
2978
|
+
riskLevel: sp.riskLevel,
|
|
2979
|
+
riskType: "file_access",
|
|
2980
|
+
confidence: 90,
|
|
2981
|
+
source: "heuristic",
|
|
2982
|
+
violatedLayers: ["L1.1"],
|
|
2983
|
+
isCredential: !!sp.credential
|
|
2984
|
+
};
|
|
2985
|
+
}
|
|
2986
|
+
continue;
|
|
2987
|
+
}
|
|
2830
2988
|
if (!isWriteOpFlag && !sp.credential) {
|
|
2831
2989
|
continue;
|
|
2832
2990
|
}
|
|
@@ -2837,7 +2995,7 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2837
2995
|
riskType: "file_access",
|
|
2838
2996
|
confidence: 90,
|
|
2839
2997
|
source: "heuristic",
|
|
2840
|
-
violatedLayers: ["
|
|
2998
|
+
violatedLayers: ["L1.1"],
|
|
2841
2999
|
isCredential: !!sp.credential
|
|
2842
3000
|
};
|
|
2843
3001
|
}
|
|
@@ -2860,7 +3018,7 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2860
3018
|
riskType: "file_access",
|
|
2861
3019
|
confidence: 90,
|
|
2862
3020
|
source: "heuristic",
|
|
2863
|
-
violatedLayers: ["
|
|
3021
|
+
violatedLayers: ["L1.1"],
|
|
2864
3022
|
isCredential: !!sp.credential
|
|
2865
3023
|
};
|
|
2866
3024
|
}
|
|
@@ -2877,7 +3035,7 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2877
3035
|
riskType: "file_access",
|
|
2878
3036
|
confidence: 90,
|
|
2879
3037
|
source: "heuristic",
|
|
2880
|
-
violatedLayers: ["
|
|
3038
|
+
violatedLayers: ["L1.1"],
|
|
2881
3039
|
isCredential: true
|
|
2882
3040
|
};
|
|
2883
3041
|
}
|
|
@@ -2890,18 +3048,142 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2890
3048
|
riskType: "file_access",
|
|
2891
3049
|
confidence: 90,
|
|
2892
3050
|
source: "heuristic",
|
|
2893
|
-
violatedLayers: ["
|
|
3051
|
+
violatedLayers: ["L1.1"]
|
|
2894
3052
|
};
|
|
2895
3053
|
}
|
|
2896
3054
|
return null;
|
|
2897
3055
|
}
|
|
3056
|
+
function isHomeValue(path6) {
|
|
3057
|
+
let p = path6.trim().toLowerCase();
|
|
3058
|
+
if (p.startsWith("$(") && p.endsWith(")")) {
|
|
3059
|
+
const inner = p.slice(2, -1).trim();
|
|
3060
|
+
return HOME_TARGET_RE.test(" " + inner);
|
|
3061
|
+
}
|
|
3062
|
+
if (/^\/home\/[^/\s]+\/?$/.test(p)) return true;
|
|
3063
|
+
return HOME_TARGET_RE.test(" " + p);
|
|
3064
|
+
}
|
|
3065
|
+
function isDeleteCommand(seg) {
|
|
3066
|
+
const s = seg.trimStart();
|
|
3067
|
+
if (/^(?:rm|rmdir|unlink)\b/i.test(s)) return true;
|
|
3068
|
+
return /删除|移除/.test(s);
|
|
3069
|
+
}
|
|
3070
|
+
function homeEscapeTarget(seg) {
|
|
3071
|
+
const low = seg.toLowerCase();
|
|
3072
|
+
const re = /(?:~|(?:\$home|\$\{home\})|\/home\/[a-z0-9_.-]+)/g;
|
|
3073
|
+
let m;
|
|
3074
|
+
while ((m = re.exec(low)) !== null) {
|
|
3075
|
+
const endIdx = m.index + m[0].length;
|
|
3076
|
+
const tail = low.slice(endIdx);
|
|
3077
|
+
if (!tail) continue;
|
|
3078
|
+
let token = tail.trimStart().split(/\s+/)[0] || "";
|
|
3079
|
+
token = token.split("&&")[0].split("||")[0].split(";")[0].replace(/\/+$/, "");
|
|
3080
|
+
token = token.replace(/^['"]|['"]$/g, "");
|
|
3081
|
+
const segments = token.split("/").filter((s) => s && s !== ".");
|
|
3082
|
+
if (!segments.some((s) => s === "..")) continue;
|
|
3083
|
+
let depth = 0;
|
|
3084
|
+
let minDepth = 0;
|
|
3085
|
+
for (const s of segments) {
|
|
3086
|
+
depth += s === ".." ? -1 : 1;
|
|
3087
|
+
minDepth = Math.min(minDepth, depth);
|
|
3088
|
+
}
|
|
3089
|
+
if (minDepth < 0) return true;
|
|
3090
|
+
if (depth <= 0) return true;
|
|
3091
|
+
}
|
|
3092
|
+
return false;
|
|
3093
|
+
}
|
|
3094
|
+
function checkHomeDeletion(actionType, actionDetail) {
|
|
3095
|
+
if (actionType !== "bash" && actionType !== "executebash" && actionType !== "terminal-executebash") return null;
|
|
3096
|
+
const segs = actionDetail.split(/&&|\|\||;|\n/).map((s) => s.trim()).filter(Boolean);
|
|
3097
|
+
for (const seg of segs) {
|
|
3098
|
+
if (!isDeleteCommand(seg)) continue;
|
|
3099
|
+
if (HOME_TARGET_RE.test(seg) || homeEscapeTarget(seg)) {
|
|
3100
|
+
return {
|
|
3101
|
+
allowed: false,
|
|
3102
|
+
reason: "\u68C0\u6D4B\u5230\u5220\u9664\u4E3B\u76EE\u5F55 (~/$HOME) \u64CD\u4F5C",
|
|
3103
|
+
riskLevel: "high",
|
|
3104
|
+
riskType: "script_execution",
|
|
3105
|
+
confidence: 90,
|
|
3106
|
+
source: "heuristic",
|
|
3107
|
+
violatedLayers: ["L1.1"]
|
|
3108
|
+
};
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
const assigned = {};
|
|
3112
|
+
for (const m of actionDetail.matchAll(HOME_ASSIGN_RE)) assigned[m[1]] = m[2];
|
|
3113
|
+
for (const [varName, path6] of Object.entries(assigned)) {
|
|
3114
|
+
if (!isHomeValue(path6)) continue;
|
|
3115
|
+
const varRefRe = new RegExp(`(?:${escapeRegExp(varName)}|\\$\\{${escapeRegExp(varName)}\\})`);
|
|
3116
|
+
for (const seg of segs) {
|
|
3117
|
+
if (!isDeleteCommand(seg)) continue;
|
|
3118
|
+
if (varRefRe.test(seg)) {
|
|
3119
|
+
return {
|
|
3120
|
+
allowed: false,
|
|
3121
|
+
reason: `\u68C0\u6D4B\u5230\u901A\u8FC7\u53D8\u91CF ${varName}\uFF08=${path6}\uFF0C\u4E3B\u76EE\u5F55\uFF09\u6267\u884C\u5220\u9664`,
|
|
3122
|
+
riskLevel: "high",
|
|
3123
|
+
riskType: "script_execution",
|
|
3124
|
+
confidence: 90,
|
|
3125
|
+
source: "heuristic",
|
|
3126
|
+
violatedLayers: ["L1.1"]
|
|
3127
|
+
};
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
}
|
|
3131
|
+
return null;
|
|
3132
|
+
}
|
|
3133
|
+
function escapeRegExp(s) {
|
|
3134
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3135
|
+
}
|
|
3136
|
+
function isDeleteActionForDir(command, dirPath) {
|
|
3137
|
+
const low = command.toLowerCase();
|
|
3138
|
+
const dpath = dirPath.toLowerCase();
|
|
3139
|
+
if (new RegExp(`\\b(?:rm|unlink|rmdir)\\b[^&|;]*?${escapeRegExp(dpath)}`, "i").test(low)) {
|
|
3140
|
+
return true;
|
|
3141
|
+
}
|
|
3142
|
+
if (new RegExp(`(?:\u5220\u9664|\u79FB\u9664)[^\\n;|&]*?${escapeRegExp(dpath)}`).test(low)) {
|
|
3143
|
+
return true;
|
|
3144
|
+
}
|
|
3145
|
+
for (const seg of low.split(/&&|\|\||;|\n/)) {
|
|
3146
|
+
const fw = (seg.trim().split(/\s+/)[0] || "").toLowerCase();
|
|
3147
|
+
if ((fw === "rm" || fw === "unlink" || fw === "rmdir") && low.includes(dpath)) {
|
|
3148
|
+
return true;
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
return false;
|
|
3152
|
+
}
|
|
3153
|
+
function isArchiveReadOfDir(command, dirPath) {
|
|
3154
|
+
const low = command.toLowerCase();
|
|
3155
|
+
const dpath = dirPath.toLowerCase();
|
|
3156
|
+
for (const seg of low.split(/&&|\|\||;|\n/)) {
|
|
3157
|
+
const s = seg.trim();
|
|
3158
|
+
if (!s || !s.includes(dpath)) continue;
|
|
3159
|
+
const parts = s.split(/\s+/);
|
|
3160
|
+
const fw = parts[0] || "";
|
|
3161
|
+
if (fw === "zip") return true;
|
|
3162
|
+
if (fw === "tar") {
|
|
3163
|
+
if (/(?:^|\s)-[a-zA-Z]*x|--extract/.test(s)) continue;
|
|
3164
|
+
return true;
|
|
3165
|
+
}
|
|
3166
|
+
if (fw === "cp" || fw === "rsync") {
|
|
3167
|
+
const segPaths = s.match(/[/~][^\s;|&><'"]*/g) || [];
|
|
3168
|
+
if (segPaths.length && segPaths[0].includes(dpath)) return true;
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
return false;
|
|
3172
|
+
}
|
|
3173
|
+
function isSshKeygenGenerate(command) {
|
|
3174
|
+
const low = command.toLowerCase();
|
|
3175
|
+
if (!low.includes("ssh-keygen")) return false;
|
|
3176
|
+
if (!/(?:^|\s)-t\s+\S+/.test(low)) return false;
|
|
3177
|
+
if (/(?:^|\s)-\S*[ylpB]\S*/.test(low)) return false;
|
|
3178
|
+
return true;
|
|
3179
|
+
}
|
|
2898
3180
|
function checkDangerousPatterns(actionType, actionDetail) {
|
|
2899
3181
|
if (actionType === "file_write" || actionType === "file_edit") return null;
|
|
2900
3182
|
const scan = stripHeredocContent(actionDetail);
|
|
2901
3183
|
const TEMP_DIRS = ["/tmp", "/var/tmp", "/run", "/dev/shm"];
|
|
2902
3184
|
const isTempPath = TEMP_DIRS.some(
|
|
2903
3185
|
(td) => new RegExp(`(?:^|[\\s/])${td.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[\\s/]|$)`).test(actionDetail)
|
|
2904
|
-
);
|
|
3186
|
+
) || /(?:^|[\s;]|&&\s*|\|\|\s*)var\/tmp(?:[/\s;]|$)/.test(actionDetail);
|
|
2905
3187
|
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;
|
|
2906
3188
|
if (!isTempPath && wildcardDeleteRe.test(scan)) {
|
|
2907
3189
|
return {
|
|
@@ -2911,7 +3193,7 @@ function checkDangerousPatterns(actionType, actionDetail) {
|
|
|
2911
3193
|
riskType: "script_execution",
|
|
2912
3194
|
confidence: 90,
|
|
2913
3195
|
source: "heuristic",
|
|
2914
|
-
violatedLayers: ["
|
|
3196
|
+
violatedLayers: ["L1.1"]
|
|
2915
3197
|
};
|
|
2916
3198
|
}
|
|
2917
3199
|
const findWriteMatch = actionDetail.match(
|
|
@@ -2926,10 +3208,10 @@ function checkDangerousPatterns(actionType, actionDetail) {
|
|
|
2926
3208
|
riskType: "script_execution",
|
|
2927
3209
|
confidence: 90,
|
|
2928
3210
|
source: "heuristic",
|
|
2929
|
-
violatedLayers: ["
|
|
3211
|
+
violatedLayers: ["L1.1"]
|
|
2930
3212
|
};
|
|
2931
3213
|
}
|
|
2932
|
-
if (/>\s
|
|
3214
|
+
if (/>\s*['"]?\/etc\//.test(actionDetail) || />\s*['"]?\/boot\//.test(actionDetail) || />\s*['"]?\/proc\//.test(actionDetail)) {
|
|
2933
3215
|
return {
|
|
2934
3216
|
allowed: false,
|
|
2935
3217
|
reason: "\u68C0\u6D4B\u5230\u91CD\u5B9A\u5411\u8986\u76D6\u5199\u5165\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55",
|
|
@@ -2937,7 +3219,7 @@ function checkDangerousPatterns(actionType, actionDetail) {
|
|
|
2937
3219
|
riskType: "file_access",
|
|
2938
3220
|
confidence: 95,
|
|
2939
3221
|
source: "heuristic",
|
|
2940
|
-
violatedLayers: ["
|
|
3222
|
+
violatedLayers: ["L1.1"]
|
|
2941
3223
|
};
|
|
2942
3224
|
}
|
|
2943
3225
|
if (/>\s*\/dev\/(sd|hd|nvme|vd|loop|mem|kmsg|port|zero|random|urandom|full|core)/i.test(actionDetail)) {
|
|
@@ -2948,7 +3230,7 @@ function checkDangerousPatterns(actionType, actionDetail) {
|
|
|
2948
3230
|
riskType: "file_access",
|
|
2949
3231
|
confidence: 90,
|
|
2950
3232
|
source: "heuristic",
|
|
2951
|
-
violatedLayers: ["
|
|
3233
|
+
violatedLayers: ["L1.1"]
|
|
2952
3234
|
};
|
|
2953
3235
|
}
|
|
2954
3236
|
return null;
|
|
@@ -3039,13 +3321,13 @@ function checkLateralMovement(actionDetail) {
|
|
|
3039
3321
|
riskType: "lateral_movement",
|
|
3040
3322
|
confidence: 80,
|
|
3041
3323
|
source: "heuristic",
|
|
3042
|
-
violatedLayers: ["
|
|
3324
|
+
violatedLayers: ["L1.1"]
|
|
3043
3325
|
};
|
|
3044
3326
|
}
|
|
3045
3327
|
}
|
|
3046
3328
|
return null;
|
|
3047
3329
|
}
|
|
3048
|
-
var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES;
|
|
3330
|
+
var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES, HOME_TARGET_RE, HOME_ASSIGN_RE;
|
|
3049
3331
|
var init_heuristic = __esm({
|
|
3050
3332
|
"src/engine/heuristic.ts"() {
|
|
3051
3333
|
"use strict";
|
|
@@ -3053,6 +3335,7 @@ var init_heuristic = __esm({
|
|
|
3053
3335
|
init_config();
|
|
3054
3336
|
init_agents();
|
|
3055
3337
|
init_scenarios();
|
|
3338
|
+
init_runtime_config();
|
|
3056
3339
|
init_rules_loader();
|
|
3057
3340
|
init_logic_rules();
|
|
3058
3341
|
INJECTION_KEYWORDS = (() => {
|
|
@@ -3074,6 +3357,8 @@ var init_heuristic = __esm({
|
|
|
3074
3357
|
})();
|
|
3075
3358
|
SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
|
|
3076
3359
|
SEARCH_ACTION_PREFIXES = ["search-", "web-", "browser-", "query-", "websearch", "web_search"];
|
|
3360
|
+
HOME_TARGET_RE = /(?:^|[\s="'($])(?:~|(?:\$home|\$\{home\}))(?:\/(?:\.\*|\*|\.)?)?(?=[\s"';}|&)]|$)/i;
|
|
3361
|
+
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;
|
|
3077
3362
|
}
|
|
3078
3363
|
});
|
|
3079
3364
|
|
|
@@ -3335,7 +3620,7 @@ var require_bindings = __commonJS({
|
|
|
3335
3620
|
var fs7 = __require("fs");
|
|
3336
3621
|
var path6 = __require("path");
|
|
3337
3622
|
var fileURLToPath10 = require_file_uri_to_path();
|
|
3338
|
-
var
|
|
3623
|
+
var join11 = path6.join;
|
|
3339
3624
|
var dirname11 = path6.dirname;
|
|
3340
3625
|
var exists = fs7.accessSync && function(path7) {
|
|
3341
3626
|
try {
|
|
@@ -3395,7 +3680,7 @@ var require_bindings = __commonJS({
|
|
|
3395
3680
|
var requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
|
|
3396
3681
|
var tries = [], i = 0, l = opts.try.length, n, b, err;
|
|
3397
3682
|
for (; i < l; i++) {
|
|
3398
|
-
n =
|
|
3683
|
+
n = join11.apply(
|
|
3399
3684
|
null,
|
|
3400
3685
|
opts.try[i].map(function(p) {
|
|
3401
3686
|
return opts[p] || p;
|
|
@@ -3456,7 +3741,7 @@ var require_bindings = __commonJS({
|
|
|
3456
3741
|
if (dir === ".") {
|
|
3457
3742
|
dir = process.cwd();
|
|
3458
3743
|
}
|
|
3459
|
-
if (exists(
|
|
3744
|
+
if (exists(join11(dir, "package.json")) || exists(join11(dir, "node_modules"))) {
|
|
3460
3745
|
return dir;
|
|
3461
3746
|
}
|
|
3462
3747
|
if (prev === dir) {
|
|
@@ -3465,7 +3750,7 @@ var require_bindings = __commonJS({
|
|
|
3465
3750
|
);
|
|
3466
3751
|
}
|
|
3467
3752
|
prev = dir;
|
|
3468
|
-
dir =
|
|
3753
|
+
dir = join11(dir, "..");
|
|
3469
3754
|
}
|
|
3470
3755
|
};
|
|
3471
3756
|
}
|
|
@@ -4079,7 +4364,7 @@ var AnalyzeRequestSchema = z.object({
|
|
|
4079
4364
|
session_history: z.array(z.object({
|
|
4080
4365
|
session_id: z.string(),
|
|
4081
4366
|
action_history: z.array(ActionItemSchema).default([])
|
|
4082
|
-
})).default([]).describe("Multi-session historical actions (optional): for L2.
|
|
4367
|
+
})).default([]).describe("Multi-session historical actions (optional): for L2.2 formal engine cross-session combo-chain / sensitive-data-flow verification. Provided by caller (incl. future agentgate)."),
|
|
4083
4368
|
a_next: ActionItemSchema.describe("Next action to be executed"),
|
|
4084
4369
|
reason: z.string().default("").describe("Reason for executing this action"),
|
|
4085
4370
|
os_type: z.string().default("").describe("OS type: 'linux', 'windows', or empty for auto-detect"),
|
|
@@ -4096,7 +4381,9 @@ var AnalyzeResponseSchema = z.object({
|
|
|
4096
4381
|
policy: z.string().default(""),
|
|
4097
4382
|
violated_policy: z.string().default(""),
|
|
4098
4383
|
confidence: z.number().int().min(0).max(100).default(100),
|
|
4099
|
-
analysis_duration_ms: z.number().default(0)
|
|
4384
|
+
analysis_duration_ms: z.number().default(0),
|
|
4385
|
+
// 组合攻击链反例路径:每步 [session_id, seq, stage, timestamp],含真实发生时间
|
|
4386
|
+
violation_chain: z.array(z.array(z.union([z.string(), z.number()]).nullable())).default([])
|
|
4100
4387
|
});
|
|
4101
4388
|
var HealthResponseSchema = z.object({
|
|
4102
4389
|
status: z.string().default("healthy"),
|
|
@@ -4171,7 +4458,7 @@ function loadTemplates() {
|
|
|
4171
4458
|
// src/engine/script-analyzer.ts
|
|
4172
4459
|
init_esm_shims();
|
|
4173
4460
|
import { readFileSync as readFileSync8 } from "fs";
|
|
4174
|
-
import { resolve as resolve6, join as
|
|
4461
|
+
import { resolve as resolve6, join as join4 } from "path";
|
|
4175
4462
|
var SUSPICIOUS_PATTERNS = [
|
|
4176
4463
|
// Network exfiltration (curl/wget POST in scripts is almost never legitimate)
|
|
4177
4464
|
{ name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
|
|
@@ -4304,7 +4591,7 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
|
|
|
4304
4591
|
}
|
|
4305
4592
|
function readScriptContent(scriptPath, maxLines = 500) {
|
|
4306
4593
|
try {
|
|
4307
|
-
const expandedPath = scriptPath.startsWith("~") ?
|
|
4594
|
+
const expandedPath = scriptPath.startsWith("~") ? join4(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve6(scriptPath);
|
|
4308
4595
|
const raw = readFileSync8(expandedPath, "utf-8");
|
|
4309
4596
|
const lines = raw.split("\n").slice(0, maxLines);
|
|
4310
4597
|
return { content: lines.join("\n"), lineCount: lines.length, error: "" };
|
|
@@ -4359,9 +4646,10 @@ ${result.scriptContent}
|
|
|
4359
4646
|
init_esm_shims();
|
|
4360
4647
|
init_heuristic();
|
|
4361
4648
|
init_logic_rules();
|
|
4649
|
+
init_inline_analyzer();
|
|
4362
4650
|
import { statSync as statSync2, existsSync as existsSync9 } from "fs";
|
|
4363
|
-
import { resolve as resolve8, join as
|
|
4364
|
-
import { homedir as
|
|
4651
|
+
import { resolve as resolve8, join as join6, isAbsolute as isAbsolute2 } from "path";
|
|
4652
|
+
import { homedir as homedir4 } from "os";
|
|
4365
4653
|
|
|
4366
4654
|
// src/engine/script-engine/tracker.ts
|
|
4367
4655
|
init_esm_shims();
|
|
@@ -5973,6 +6261,13 @@ function extractScriptPath2(command) {
|
|
|
5973
6261
|
return null;
|
|
5974
6262
|
}
|
|
5975
6263
|
function extractScriptPathWithArgs(command) {
|
|
6264
|
+
const inline = command.match(/(?:^|\s|&&|\|\|)\s*(?:bash|\/bin\/bash|sh|zsh)\s+-c\s*(['"])([\s\S]*?)\1/i);
|
|
6265
|
+
if (inline) {
|
|
6266
|
+
const inner = inline[2];
|
|
6267
|
+
const realPath = extractScriptPath2(inner);
|
|
6268
|
+
if (realPath) return { path: realPath, args: [] };
|
|
6269
|
+
return { path: null, args: [] };
|
|
6270
|
+
}
|
|
5976
6271
|
const scriptPath = extractScriptPath2(command);
|
|
5977
6272
|
if (!scriptPath) return { path: null, args: [] };
|
|
5978
6273
|
const idx = command.indexOf(scriptPath);
|
|
@@ -5986,11 +6281,59 @@ function extractScriptPathWithArgs(command) {
|
|
|
5986
6281
|
}
|
|
5987
6282
|
return { path: scriptPath, args };
|
|
5988
6283
|
}
|
|
6284
|
+
function looksLikeScriptPath(tok) {
|
|
6285
|
+
const t = tok.trim().replace(/^['"]|['"]$/g, "");
|
|
6286
|
+
if (!t) return false;
|
|
6287
|
+
const low = t.toLowerCase();
|
|
6288
|
+
if (["bash", "sh", "zsh", "dash", "python", "python3", "perl", "ruby", "node", "php", "lua"].includes(low)) return false;
|
|
6289
|
+
if (/\.(sh|bash|py|pl|rb|js|php|lua)$/.test(low)) return true;
|
|
6290
|
+
if (t.includes("/") && !t.startsWith("-")) return true;
|
|
6291
|
+
return false;
|
|
6292
|
+
}
|
|
5989
6293
|
function extractInlineScriptPath(actionDetail) {
|
|
6294
|
+
const code = extractInlineCode(actionDetail);
|
|
6295
|
+
if (code) {
|
|
6296
|
+
const candidates = [];
|
|
6297
|
+
const osRe = /os\.(?:system|popen)\s*\(\s*(['"])(.*?)\1\s*\)/gi;
|
|
6298
|
+
let m;
|
|
6299
|
+
while ((m = osRe.exec(code)) !== null) candidates.push(m[2]);
|
|
6300
|
+
const subListRe = /subprocess\.(?:run|call|Popen|check_output|check_call)\s*\(\s*\[(.*?)\]/gi;
|
|
6301
|
+
while ((m = subListRe.exec(code)) !== null) candidates.push(m[1]);
|
|
6302
|
+
const subShellRe = /subprocess\.(?:run|call|Popen|check_output|check_call)\s*\(\s*(['"])(.*?)\1[^)]*?shell\s*=\s*true/gi;
|
|
6303
|
+
while ((m = subShellRe.exec(code)) !== null) candidates.push(m[2]);
|
|
6304
|
+
for (const cand of candidates) {
|
|
6305
|
+
const tokens = cand.includes(",") ? cand.split(/\s*,\s*/).map((t) => t.trim().replace(/^['"]|['"]$/g, "")) : cand.split(/\s+/);
|
|
6306
|
+
for (const tok of tokens) {
|
|
6307
|
+
if (looksLikeScriptPath(tok)) {
|
|
6308
|
+
try {
|
|
6309
|
+
const resolved = resolvePath2(tok.replace(/^['"]|['"]$/g, ""));
|
|
6310
|
+
if (existsSync9(resolved)) {
|
|
6311
|
+
const stat = statSync2(resolved);
|
|
6312
|
+
if (stat.isFile()) return resolved;
|
|
6313
|
+
}
|
|
6314
|
+
} catch {
|
|
6315
|
+
}
|
|
6316
|
+
}
|
|
6317
|
+
}
|
|
6318
|
+
}
|
|
6319
|
+
const openRe = /open\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
6320
|
+
while ((m = openRe.exec(code)) !== null) {
|
|
6321
|
+
const p = m[1];
|
|
6322
|
+
try {
|
|
6323
|
+
const resolved = resolvePath2(p);
|
|
6324
|
+
if (existsSync9(resolved)) {
|
|
6325
|
+
const stat = statSync2(resolved);
|
|
6326
|
+
if (stat.isFile()) return resolved;
|
|
6327
|
+
}
|
|
6328
|
+
} catch {
|
|
6329
|
+
}
|
|
6330
|
+
}
|
|
6331
|
+
return null;
|
|
6332
|
+
}
|
|
5990
6333
|
const re = /open\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
5991
|
-
let
|
|
5992
|
-
while ((
|
|
5993
|
-
const p =
|
|
6334
|
+
let m2;
|
|
6335
|
+
while ((m2 = re.exec(actionDetail)) !== null) {
|
|
6336
|
+
const p = m2[1];
|
|
5994
6337
|
let resolved;
|
|
5995
6338
|
try {
|
|
5996
6339
|
resolved = resolvePath2(p);
|
|
@@ -6006,7 +6349,7 @@ function extractInlineScriptPath(actionDetail) {
|
|
|
6006
6349
|
function resolvePath2(p) {
|
|
6007
6350
|
let expanded = p;
|
|
6008
6351
|
if (expanded.startsWith("~/") || expanded === "~") {
|
|
6009
|
-
expanded =
|
|
6352
|
+
expanded = join6(homedir4(), expanded.slice(1));
|
|
6010
6353
|
}
|
|
6011
6354
|
return resolve8(expanded);
|
|
6012
6355
|
}
|
|
@@ -6087,7 +6430,7 @@ function analyzeScriptContent2(input) {
|
|
|
6087
6430
|
isInline = true;
|
|
6088
6431
|
}
|
|
6089
6432
|
if (resolvedPath && !isAbsolute2(resolvedPath) && !resolvedPath.startsWith("~/")) {
|
|
6090
|
-
resolvedPath =
|
|
6433
|
+
resolvedPath = join6(input.cwd || process.cwd(), resolvedPath);
|
|
6091
6434
|
}
|
|
6092
6435
|
const l1Adapter = (content) => {
|
|
6093
6436
|
const virtualInput = {
|
|
@@ -6375,7 +6718,7 @@ async function analyzeWithLLM(prompt, config) {
|
|
|
6375
6718
|
riskType: riskType || "",
|
|
6376
6719
|
confidence: typeof parsed.confidence === "number" ? Math.min(100, Math.max(0, parsed.confidence)) : 70,
|
|
6377
6720
|
source: "llm",
|
|
6378
|
-
violatedLayers: ["
|
|
6721
|
+
violatedLayers: ["L3.1"]
|
|
6379
6722
|
};
|
|
6380
6723
|
} catch (err) {
|
|
6381
6724
|
if (abortController.signal.aborted) {
|
|
@@ -6398,6 +6741,7 @@ async function analyzeWithLLM(prompt, config) {
|
|
|
6398
6741
|
|
|
6399
6742
|
// src/engine/coordinator.ts
|
|
6400
6743
|
init_config();
|
|
6744
|
+
init_runtime_config();
|
|
6401
6745
|
init_agents();
|
|
6402
6746
|
init_scenarios();
|
|
6403
6747
|
|
|
@@ -6407,9 +6751,9 @@ init_esm_shims();
|
|
|
6407
6751
|
// src/brain/rule-store.ts
|
|
6408
6752
|
init_esm_shims();
|
|
6409
6753
|
import { existsSync as existsSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
6410
|
-
import { join as
|
|
6411
|
-
import { homedir as
|
|
6412
|
-
var DEFAULT_RULES_PATH =
|
|
6754
|
+
import { join as join7 } from "path";
|
|
6755
|
+
import { homedir as homedir5 } from "os";
|
|
6756
|
+
var DEFAULT_RULES_PATH = join7(homedir5(), ".agentmoss", "brain", "rules.json");
|
|
6413
6757
|
var ENV_RULES_PATH = "AGENT_MOSS_BRAIN_RULES_PATH";
|
|
6414
6758
|
var STATUS_DRAFT = "draft";
|
|
6415
6759
|
var STATUS_APPROVED = "approved";
|
|
@@ -6971,6 +7315,15 @@ function decodeBase64Literals(text) {
|
|
|
6971
7315
|
}
|
|
6972
7316
|
return out;
|
|
6973
7317
|
}
|
|
7318
|
+
var QUOTED_STR_RE = /('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`)/g;
|
|
7319
|
+
var OUTPUT_CMDS = /* @__PURE__ */ new Set(["echo", "printf", "print"]);
|
|
7320
|
+
function stripStringLiterals(text) {
|
|
7321
|
+
const m = text.match(/\s*([A-Za-z_][\w-]*|\/?[\w./-]+)\b/);
|
|
7322
|
+
if (!m) return text;
|
|
7323
|
+
const cmd = m[1].split("/").pop().toLowerCase();
|
|
7324
|
+
if (!OUTPUT_CMDS.has(cmd)) return text;
|
|
7325
|
+
return text.replace(QUOTED_STR_RE, "");
|
|
7326
|
+
}
|
|
6974
7327
|
function decodeUrlAndNumeric(text) {
|
|
6975
7328
|
const out = [];
|
|
6976
7329
|
let s = text;
|
|
@@ -7004,7 +7357,10 @@ function decodeUrlAndNumeric(text) {
|
|
|
7004
7357
|
return out;
|
|
7005
7358
|
}
|
|
7006
7359
|
function allCandidates(detail, max = 8) {
|
|
7007
|
-
const
|
|
7360
|
+
const shell = stripStringLiterals(detail);
|
|
7361
|
+
const cands = [shell];
|
|
7362
|
+
cands.push(...decodeBase64Literals(shell));
|
|
7363
|
+
cands.push(...decodeUrlAndNumeric(shell));
|
|
7008
7364
|
cands.push(...decodeBase64Literals(detail));
|
|
7009
7365
|
cands.push(...decodeUrlAndNumeric(detail));
|
|
7010
7366
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -7029,6 +7385,128 @@ function matchStageAny(detail, stages) {
|
|
|
7029
7385
|
return "";
|
|
7030
7386
|
}
|
|
7031
7387
|
|
|
7388
|
+
// src/engine/formal-engine/ltl.ts
|
|
7389
|
+
init_esm_shims();
|
|
7390
|
+
init_rules_loader();
|
|
7391
|
+
|
|
7392
|
+
// src/engine/formal-engine/ir.ts
|
|
7393
|
+
init_esm_shims();
|
|
7394
|
+
function allNodes(mg) {
|
|
7395
|
+
const out = [];
|
|
7396
|
+
for (const s of mg.sessions) out.push(...s.nodes);
|
|
7397
|
+
return out;
|
|
7398
|
+
}
|
|
7399
|
+
function allEdges(mg) {
|
|
7400
|
+
const out = [];
|
|
7401
|
+
for (const s of mg.sessions) out.push(...s.edges);
|
|
7402
|
+
out.push(...mg.crossEdges);
|
|
7403
|
+
return out;
|
|
7404
|
+
}
|
|
7405
|
+
function emptyVerificationResult() {
|
|
7406
|
+
return { verified: true, violations: [] };
|
|
7407
|
+
}
|
|
7408
|
+
|
|
7409
|
+
// src/engine/formal-engine/ltl.ts
|
|
7410
|
+
var AP_RE = null;
|
|
7411
|
+
function compileAps(apPatterns) {
|
|
7412
|
+
if (AP_RE) return;
|
|
7413
|
+
AP_RE = {};
|
|
7414
|
+
for (const [name, pats] of Object.entries(apPatterns || {})) {
|
|
7415
|
+
AP_RE[name] = pats.map((p) => new RegExp(p, "i"));
|
|
7416
|
+
}
|
|
7417
|
+
}
|
|
7418
|
+
function labelEvent(actionDetail) {
|
|
7419
|
+
if (!AP_RE) compileAps(loadMC().ap_patterns || {});
|
|
7420
|
+
const candidates = allCandidates(actionDetail);
|
|
7421
|
+
const hits = /* @__PURE__ */ new Set();
|
|
7422
|
+
for (const [name, pats] of Object.entries(AP_RE || {})) {
|
|
7423
|
+
for (const c of candidates) {
|
|
7424
|
+
const m = pats.some((p) => p.test(c));
|
|
7425
|
+
if (m) {
|
|
7426
|
+
hits.add(name);
|
|
7427
|
+
break;
|
|
7428
|
+
}
|
|
7429
|
+
}
|
|
7430
|
+
}
|
|
7431
|
+
return hits;
|
|
7432
|
+
}
|
|
7433
|
+
function evaluateLTL(formula, trace, pos) {
|
|
7434
|
+
const op = formula[0];
|
|
7435
|
+
switch (op) {
|
|
7436
|
+
case "ap":
|
|
7437
|
+
return trace[pos].has(formula[1]);
|
|
7438
|
+
case "!":
|
|
7439
|
+
return !evaluateLTL(formula[1], trace, pos);
|
|
7440
|
+
case "&":
|
|
7441
|
+
return evaluateLTL(formula[1], trace, pos) && evaluateLTL(formula[2], trace, pos);
|
|
7442
|
+
case "|":
|
|
7443
|
+
return evaluateLTL(formula[1], trace, pos) || evaluateLTL(formula[2], trace, pos);
|
|
7444
|
+
case "->":
|
|
7445
|
+
return !evaluateLTL(formula[1], trace, pos) || evaluateLTL(formula[2], trace, pos);
|
|
7446
|
+
case "X":
|
|
7447
|
+
return pos + 1 < trace.length && evaluateLTL(formula[1], trace, pos + 1);
|
|
7448
|
+
case "G": {
|
|
7449
|
+
for (let j = pos; j < trace.length; j++) {
|
|
7450
|
+
if (!evaluateLTL(formula[1], trace, j)) return false;
|
|
7451
|
+
}
|
|
7452
|
+
return true;
|
|
7453
|
+
}
|
|
7454
|
+
case "F": {
|
|
7455
|
+
for (let j = pos; j < trace.length; j++) {
|
|
7456
|
+
if (evaluateLTL(formula[1], trace, j)) return true;
|
|
7457
|
+
}
|
|
7458
|
+
return false;
|
|
7459
|
+
}
|
|
7460
|
+
case "U": {
|
|
7461
|
+
for (let j = pos; j < trace.length; j++) {
|
|
7462
|
+
if (evaluateLTL(formula[2], trace, j)) {
|
|
7463
|
+
for (let k = pos; k < j; k++) {
|
|
7464
|
+
if (!evaluateLTL(formula[1], trace, k)) return false;
|
|
7465
|
+
}
|
|
7466
|
+
return true;
|
|
7467
|
+
}
|
|
7468
|
+
}
|
|
7469
|
+
return false;
|
|
7470
|
+
}
|
|
7471
|
+
default:
|
|
7472
|
+
return false;
|
|
7473
|
+
}
|
|
7474
|
+
}
|
|
7475
|
+
function ruleViolates(rule, traceWith, witnessMode) {
|
|
7476
|
+
const formula = rule?.formula;
|
|
7477
|
+
if (!Array.isArray(formula) || formula.length === 0) return false;
|
|
7478
|
+
const satWith = evaluateLTL(formula, traceWith, 0);
|
|
7479
|
+
if (!satWith) return false;
|
|
7480
|
+
if (witnessMode && traceWith.length > 1) {
|
|
7481
|
+
const traceWithout = traceWith.slice(0, -1);
|
|
7482
|
+
if (evaluateLTL(formula, traceWithout, 0)) return false;
|
|
7483
|
+
}
|
|
7484
|
+
return true;
|
|
7485
|
+
}
|
|
7486
|
+
function checkMCRules(graph) {
|
|
7487
|
+
const violations = [];
|
|
7488
|
+
const nodes = allNodes(graph);
|
|
7489
|
+
if (nodes.length === 0) return violations;
|
|
7490
|
+
const mc = loadMC();
|
|
7491
|
+
const rules = mc.rules || [];
|
|
7492
|
+
if (rules.length === 0) return violations;
|
|
7493
|
+
const trace = nodes.map((n) => labelEvent(n.actionDetail));
|
|
7494
|
+
const witnessMode = graph.witnessIdx !== void 0;
|
|
7495
|
+
for (const rule of rules) {
|
|
7496
|
+
if (rule?.type !== "ltl" || !(rule.enabled !== false)) continue;
|
|
7497
|
+
if (ruleViolates(rule, trace, witnessMode)) {
|
|
7498
|
+
violations.push({
|
|
7499
|
+
propertyId: String(rule.id || "mc-ltl"),
|
|
7500
|
+
reason: String(rule.reason || "L2.2 \u6A21\u578B\u68C0\u6D4B\u89C4\u5219\u547D\u4E2D"),
|
|
7501
|
+
riskLevel: String(rule.risk_level || "high"),
|
|
7502
|
+
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) }))
|
|
7503
|
+
});
|
|
7504
|
+
if (witnessMode) return violations;
|
|
7505
|
+
}
|
|
7506
|
+
}
|
|
7507
|
+
return violations;
|
|
7508
|
+
}
|
|
7509
|
+
|
|
7032
7510
|
// src/engine/formal-engine/builder.ts
|
|
7033
7511
|
var COMBO_STAGES2 = null;
|
|
7034
7512
|
function loadStages() {
|
|
@@ -7057,6 +7535,26 @@ function extractResources(actionDetail) {
|
|
|
7057
7535
|
function markStage(actionDetail) {
|
|
7058
7536
|
return matchStageAny(actionDetail, loadStages());
|
|
7059
7537
|
}
|
|
7538
|
+
function markIntegrity(actionDetail) {
|
|
7539
|
+
const aps = labelEvent(actionDetail);
|
|
7540
|
+
if (aps.has("download_file") || aps.has("prompt_injection")) {
|
|
7541
|
+
return { integrity: "low", isLowSource: true };
|
|
7542
|
+
}
|
|
7543
|
+
return { integrity: "medium", isLowSource: false };
|
|
7544
|
+
}
|
|
7545
|
+
function initNode(sessionId, seq, actionType, actionDetail) {
|
|
7546
|
+
const { integrity, isLowSource } = markIntegrity(actionDetail);
|
|
7547
|
+
return {
|
|
7548
|
+
sessionId,
|
|
7549
|
+
seq,
|
|
7550
|
+
actionType,
|
|
7551
|
+
actionDetail,
|
|
7552
|
+
stage: markStage(actionDetail),
|
|
7553
|
+
resources: extractResources(actionDetail),
|
|
7554
|
+
integrity,
|
|
7555
|
+
isLowSource
|
|
7556
|
+
};
|
|
7557
|
+
}
|
|
7060
7558
|
function buildSessionGraph(actionHistory, aNext, sessionId = "current") {
|
|
7061
7559
|
const nodes = [];
|
|
7062
7560
|
const edges = [];
|
|
@@ -7070,17 +7568,9 @@ function buildSessionGraph(actionHistory, aNext, sessionId = "current") {
|
|
|
7070
7568
|
details.push({ actionType: String(aNext.actionType || ""), actionDetail: String(aNext.actionDetail || "") });
|
|
7071
7569
|
}
|
|
7072
7570
|
details.forEach((d, seq) => {
|
|
7073
|
-
const
|
|
7074
|
-
const node = {
|
|
7075
|
-
sessionId,
|
|
7076
|
-
seq,
|
|
7077
|
-
actionType: d.actionType,
|
|
7078
|
-
actionDetail: d.actionDetail,
|
|
7079
|
-
stage,
|
|
7080
|
-
resources: extractResources(d.actionDetail)
|
|
7081
|
-
};
|
|
7571
|
+
const node = initNode(sessionId, seq, d.actionType, d.actionDetail);
|
|
7082
7572
|
nodes.push(node);
|
|
7083
|
-
if (stage) stageFlags.add(stage);
|
|
7573
|
+
if (node.stage) stageFlags.add(node.stage);
|
|
7084
7574
|
});
|
|
7085
7575
|
for (let i = 0; i < nodes.length - 1; i++) {
|
|
7086
7576
|
edges.push({ src: i, dst: i + 1, kind: "next" });
|
|
@@ -7100,15 +7590,12 @@ function buildMultiSessionGraph(sessions, aNext) {
|
|
|
7100
7590
|
if (aNext) {
|
|
7101
7591
|
const last = mg.sessions[mg.sessions.length - 1];
|
|
7102
7592
|
if (last.sessionId === "current") {
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
stage,
|
|
7110
|
-
resources: extractResources(String(aNext.actionDetail || ""))
|
|
7111
|
-
});
|
|
7593
|
+
last.nodes.push(initNode(
|
|
7594
|
+
last.sessionId,
|
|
7595
|
+
last.nodes.length,
|
|
7596
|
+
String(aNext.actionType || ""),
|
|
7597
|
+
String(aNext.actionDetail || "")
|
|
7598
|
+
));
|
|
7112
7599
|
if (last.nodes.length > 1) {
|
|
7113
7600
|
last.edges.push({ src: last.nodes.length - 2, dst: last.nodes.length - 1, kind: "next" });
|
|
7114
7601
|
}
|
|
@@ -7122,11 +7609,25 @@ function buildMultiSessionGraph(sessions, aNext) {
|
|
|
7122
7609
|
function addCrossDataEdges(mg) {
|
|
7123
7610
|
const nodes = allNodesFlat(mg);
|
|
7124
7611
|
if (nodes.length < 2) return;
|
|
7125
|
-
|
|
7126
|
-
|
|
7127
|
-
|
|
7128
|
-
const
|
|
7129
|
-
if (
|
|
7612
|
+
const resIndex = /* @__PURE__ */ new Map();
|
|
7613
|
+
nodes.forEach((n, i) => {
|
|
7614
|
+
for (const r of n.resources) {
|
|
7615
|
+
const list = resIndex.get(r);
|
|
7616
|
+
if (list) list.push(i);
|
|
7617
|
+
else resIndex.set(r, [i]);
|
|
7618
|
+
}
|
|
7619
|
+
});
|
|
7620
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7621
|
+
for (const idxs of resIndex.values()) {
|
|
7622
|
+
if (idxs.length < 2) continue;
|
|
7623
|
+
for (let a = 0; a < idxs.length; a++) {
|
|
7624
|
+
const i = idxs[a];
|
|
7625
|
+
for (let b = a + 1; b < idxs.length; b++) {
|
|
7626
|
+
const j = idxs[b];
|
|
7627
|
+
if (nodes[i].sessionId === nodes[j].sessionId) continue;
|
|
7628
|
+
const key = `${i}:${j}`;
|
|
7629
|
+
if (seen.has(key)) continue;
|
|
7630
|
+
seen.add(key);
|
|
7130
7631
|
mg.crossEdges.push({ src: i, dst: j, kind: "data" });
|
|
7131
7632
|
}
|
|
7132
7633
|
}
|
|
@@ -7137,28 +7638,6 @@ function allNodesFlat(mg) {
|
|
|
7137
7638
|
for (const s of mg.sessions) out.push(...s.nodes);
|
|
7138
7639
|
return out;
|
|
7139
7640
|
}
|
|
7140
|
-
function intersect(a, b) {
|
|
7141
|
-
const out = /* @__PURE__ */ new Set();
|
|
7142
|
-
for (const v of a) if (b.has(v)) out.add(v);
|
|
7143
|
-
return out;
|
|
7144
|
-
}
|
|
7145
|
-
|
|
7146
|
-
// src/engine/formal-engine/ir.ts
|
|
7147
|
-
init_esm_shims();
|
|
7148
|
-
function allNodes(mg) {
|
|
7149
|
-
const out = [];
|
|
7150
|
-
for (const s of mg.sessions) out.push(...s.nodes);
|
|
7151
|
-
return out;
|
|
7152
|
-
}
|
|
7153
|
-
function allEdges(mg) {
|
|
7154
|
-
const out = [];
|
|
7155
|
-
for (const s of mg.sessions) out.push(...s.edges);
|
|
7156
|
-
out.push(...mg.crossEdges);
|
|
7157
|
-
return out;
|
|
7158
|
-
}
|
|
7159
|
-
function emptyVerificationResult() {
|
|
7160
|
-
return { verified: true, violations: [] };
|
|
7161
|
-
}
|
|
7162
7641
|
|
|
7163
7642
|
// src/engine/formal-engine/properties.ts
|
|
7164
7643
|
init_esm_shims();
|
|
@@ -7181,27 +7660,50 @@ function checkCrossSessionChain(graph) {
|
|
|
7181
7660
|
const violations = [];
|
|
7182
7661
|
const nodes = allNodes(graph);
|
|
7183
7662
|
if (nodes.length === 0) return violations;
|
|
7663
|
+
const witness = graph.witnessIdx;
|
|
7184
7664
|
for (const chain of loadCrossSessionChains()) {
|
|
7185
7665
|
const stages = chain.stages;
|
|
7186
7666
|
if (!stages.length) continue;
|
|
7187
|
-
|
|
7188
|
-
|
|
7189
|
-
|
|
7190
|
-
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7667
|
+
if (witness !== void 0) {
|
|
7668
|
+
if (nodes[witness].stage !== stages[stages.length - 1]) continue;
|
|
7669
|
+
let matched = 0;
|
|
7670
|
+
const path6 = [];
|
|
7671
|
+
for (let k = 0; k < witness; k++) {
|
|
7672
|
+
const n = nodes[k];
|
|
7673
|
+
if (matched < stages.length - 1 && n.stage === stages[matched]) {
|
|
7674
|
+
matched += 1;
|
|
7675
|
+
path6.push({ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) });
|
|
7676
|
+
}
|
|
7196
7677
|
}
|
|
7197
|
-
|
|
7198
|
-
|
|
7678
|
+
if (matched < stages.length - 1) continue;
|
|
7679
|
+
path6.push({ sessionId: nodes[witness].sessionId, seq: nodes[witness].seq, detail: nodes[witness].actionDetail.slice(0, 60) });
|
|
7199
7680
|
violations.push({
|
|
7200
7681
|
propertyId: "cross-" + chain.id,
|
|
7201
7682
|
reason: chain.reason,
|
|
7202
7683
|
riskLevel: chain.riskLevel,
|
|
7203
7684
|
path: path6
|
|
7204
7685
|
});
|
|
7686
|
+
return violations;
|
|
7687
|
+
} else {
|
|
7688
|
+
let matched = 0;
|
|
7689
|
+
let stageIdx = 0;
|
|
7690
|
+
const path6 = [];
|
|
7691
|
+
for (const n of nodes) {
|
|
7692
|
+
if (stageIdx < stages.length && n.stage === stages[stageIdx]) {
|
|
7693
|
+
matched += 1;
|
|
7694
|
+
path6.push({ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) });
|
|
7695
|
+
stageIdx += 1;
|
|
7696
|
+
if (matched >= stages.length) break;
|
|
7697
|
+
}
|
|
7698
|
+
}
|
|
7699
|
+
if (matched >= stages.length) {
|
|
7700
|
+
violations.push({
|
|
7701
|
+
propertyId: "cross-" + chain.id,
|
|
7702
|
+
reason: chain.reason,
|
|
7703
|
+
riskLevel: chain.riskLevel,
|
|
7704
|
+
path: path6
|
|
7705
|
+
});
|
|
7706
|
+
}
|
|
7205
7707
|
}
|
|
7206
7708
|
}
|
|
7207
7709
|
return violations;
|
|
@@ -7236,14 +7738,18 @@ function checkSensitiveDataFlow(graph) {
|
|
|
7236
7738
|
}
|
|
7237
7739
|
}
|
|
7238
7740
|
}
|
|
7239
|
-
|
|
7741
|
+
const witness = graph.witnessIdx;
|
|
7742
|
+
const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
|
|
7743
|
+
for (const i of candidates) {
|
|
7744
|
+
const n = nodes[i];
|
|
7240
7745
|
if (tainted.has(i) && EXFIL_RE.test(n.actionDetail)) {
|
|
7241
7746
|
violations.push({
|
|
7242
|
-
propertyId: "sensitive-data-
|
|
7747
|
+
propertyId: "sensitive-data-flow",
|
|
7243
7748
|
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`,
|
|
7244
7749
|
riskLevel: "critical",
|
|
7245
7750
|
path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
|
|
7246
7751
|
});
|
|
7752
|
+
if (witness !== void 0) return violations;
|
|
7247
7753
|
}
|
|
7248
7754
|
}
|
|
7249
7755
|
return violations;
|
|
@@ -7274,12 +7780,38 @@ function checkNeverExfilAfterPrivesc(graph) {
|
|
|
7274
7780
|
const violations = [];
|
|
7275
7781
|
const nodes = allNodes(graph);
|
|
7276
7782
|
if (nodes.length < 2) return violations;
|
|
7783
|
+
const witness = graph.witnessIdx;
|
|
7784
|
+
if (witness !== void 0) {
|
|
7785
|
+
const evIdx = [];
|
|
7786
|
+
nodes.forEach((n, i) => {
|
|
7787
|
+
if ((i === witness || n.sessionId === "ebpf-evidence") && isExfilNode(n)) evIdx.push(i);
|
|
7788
|
+
});
|
|
7789
|
+
if (evIdx.length === 0) return violations;
|
|
7790
|
+
const firstEv = Math.min(...evIdx);
|
|
7791
|
+
let pre = null;
|
|
7792
|
+
for (let i = 0; i < firstEv; i++) {
|
|
7793
|
+
if (isPrivescNode(nodes[i])) pre = nodes[i];
|
|
7794
|
+
}
|
|
7795
|
+
if (pre) {
|
|
7796
|
+
const post = nodes[firstEv];
|
|
7797
|
+
return [{
|
|
7798
|
+
propertyId: "no-exfil-after-privesc",
|
|
7799
|
+
reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
|
|
7800
|
+
riskLevel: "critical",
|
|
7801
|
+
path: [
|
|
7802
|
+
{ sessionId: pre.sessionId, seq: pre.seq, detail: pre.actionDetail.slice(0, 60) },
|
|
7803
|
+
{ sessionId: post.sessionId, seq: post.seq, detail: post.actionDetail.slice(0, 60) }
|
|
7804
|
+
]
|
|
7805
|
+
}];
|
|
7806
|
+
}
|
|
7807
|
+
return violations;
|
|
7808
|
+
}
|
|
7277
7809
|
for (let i = 0; i < nodes.length; i++) {
|
|
7278
7810
|
if (!isPrivescNode(nodes[i])) continue;
|
|
7279
7811
|
for (let j = i + 1; j < nodes.length; j++) {
|
|
7280
7812
|
if (isExfilNode(nodes[j])) {
|
|
7281
7813
|
violations.push({
|
|
7282
|
-
propertyId: "no-
|
|
7814
|
+
propertyId: "no-exfil-after-privesc",
|
|
7283
7815
|
reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
|
|
7284
7816
|
riskLevel: "critical",
|
|
7285
7817
|
path: [
|
|
@@ -7297,12 +7829,38 @@ function checkNeverExfilAfterCred(graph) {
|
|
|
7297
7829
|
const violations = [];
|
|
7298
7830
|
const nodes = allNodes(graph);
|
|
7299
7831
|
if (nodes.length < 2) return violations;
|
|
7832
|
+
const witness = graph.witnessIdx;
|
|
7833
|
+
if (witness !== void 0) {
|
|
7834
|
+
const evIdx = [];
|
|
7835
|
+
nodes.forEach((n, i) => {
|
|
7836
|
+
if ((i === witness || n.sessionId === "ebpf-evidence") && isExfilNode(n)) evIdx.push(i);
|
|
7837
|
+
});
|
|
7838
|
+
if (evIdx.length === 0) return violations;
|
|
7839
|
+
const firstEv = Math.min(...evIdx);
|
|
7840
|
+
let pre = null;
|
|
7841
|
+
for (let i = 0; i < firstEv; i++) {
|
|
7842
|
+
if (isCredNode(nodes[i])) pre = nodes[i];
|
|
7843
|
+
}
|
|
7844
|
+
if (pre) {
|
|
7845
|
+
const post = nodes[firstEv];
|
|
7846
|
+
return [{
|
|
7847
|
+
propertyId: "no-exfil-after-credential",
|
|
7848
|
+
reason: "\u8BFB\u53D6\u51ED\u636E\u540E\u51FA\u73B0\u5916\u4F20\uFF0C\u8FDD\u53CD \u25A1(CRED\u2192\xAC\u25C7EXFIL)",
|
|
7849
|
+
riskLevel: "critical",
|
|
7850
|
+
path: [
|
|
7851
|
+
{ sessionId: pre.sessionId, seq: pre.seq, detail: pre.actionDetail.slice(0, 60) },
|
|
7852
|
+
{ sessionId: post.sessionId, seq: post.seq, detail: post.actionDetail.slice(0, 60) }
|
|
7853
|
+
]
|
|
7854
|
+
}];
|
|
7855
|
+
}
|
|
7856
|
+
return violations;
|
|
7857
|
+
}
|
|
7300
7858
|
for (let i = 0; i < nodes.length; i++) {
|
|
7301
7859
|
if (!isCredNode(nodes[i])) continue;
|
|
7302
7860
|
for (let j = i + 1; j < nodes.length; j++) {
|
|
7303
7861
|
if (isExfilNode(nodes[j])) {
|
|
7304
7862
|
violations.push({
|
|
7305
|
-
propertyId: "no-
|
|
7863
|
+
propertyId: "no-exfil-after-credential",
|
|
7306
7864
|
reason: "\u8BFB\u53D6\u51ED\u636E\u540E\u51FA\u73B0\u5916\u4F20\uFF0C\u8FDD\u53CD \u25A1(CRED\u2192\xAC\u25C7EXFIL)",
|
|
7307
7865
|
riskLevel: "critical",
|
|
7308
7866
|
path: [
|
|
@@ -7316,11 +7874,172 @@ function checkNeverExfilAfterCred(graph) {
|
|
|
7316
7874
|
}
|
|
7317
7875
|
return violations;
|
|
7318
7876
|
}
|
|
7877
|
+
var TOOL_REGISTRY = null;
|
|
7878
|
+
function getToolRegistry() {
|
|
7879
|
+
if (TOOL_REGISTRY) return TOOL_REGISTRY;
|
|
7880
|
+
TOOL_REGISTRY = loadToolRegistry().tools || {};
|
|
7881
|
+
return TOOL_REGISTRY;
|
|
7882
|
+
}
|
|
7883
|
+
function lookupToolClass(actionType) {
|
|
7884
|
+
const entry = getToolRegistry()[actionType] || {};
|
|
7885
|
+
return entry.actionClass;
|
|
7886
|
+
}
|
|
7887
|
+
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;
|
|
7888
|
+
var STRONG_COMPUTE_TYPES = /* @__PURE__ */ new Set(["python", "node", "exec", "terminal", "cmd"]);
|
|
7889
|
+
function isComputeNode(n) {
|
|
7890
|
+
if (STRONG_COMPUTE_TYPES.has(n.actionType.toLowerCase())) return true;
|
|
7891
|
+
const cls = lookupToolClass(n.actionType);
|
|
7892
|
+
if (cls === "read" || cls === "write" || cls === "network_egress" || cls === "message_egress") return false;
|
|
7893
|
+
return COMPUTE_RE.test(n.actionDetail);
|
|
7894
|
+
}
|
|
7895
|
+
function checkLowIntegrityCommandExec(graph) {
|
|
7896
|
+
const violations = [];
|
|
7897
|
+
const nodes = allNodes(graph);
|
|
7898
|
+
if (nodes.length === 0) return violations;
|
|
7899
|
+
const witness = graph.witnessIdx;
|
|
7900
|
+
const lowResources = /* @__PURE__ */ new Set();
|
|
7901
|
+
const lowAt = /* @__PURE__ */ new Map();
|
|
7902
|
+
nodes.forEach((n, i) => {
|
|
7903
|
+
if (n.isLowSource) {
|
|
7904
|
+
lowAt.set(i, n.actionDetail.slice(0, 60));
|
|
7905
|
+
for (const r of n.resources) lowResources.add(r);
|
|
7906
|
+
}
|
|
7907
|
+
});
|
|
7908
|
+
let changed = true;
|
|
7909
|
+
while (changed) {
|
|
7910
|
+
changed = false;
|
|
7911
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
7912
|
+
if (lowAt.has(i)) continue;
|
|
7913
|
+
let shared = "";
|
|
7914
|
+
for (const r of nodes[i].resources) {
|
|
7915
|
+
if (lowResources.has(r)) {
|
|
7916
|
+
shared = r;
|
|
7917
|
+
break;
|
|
7918
|
+
}
|
|
7919
|
+
}
|
|
7920
|
+
if (shared) {
|
|
7921
|
+
lowAt.set(i, `\u7EE7\u627F\u81EA ${shared}`);
|
|
7922
|
+
for (const r of nodes[i].resources) lowResources.add(r);
|
|
7923
|
+
changed = true;
|
|
7924
|
+
}
|
|
7925
|
+
}
|
|
7926
|
+
}
|
|
7927
|
+
if (lowAt.size === 0) return violations;
|
|
7928
|
+
const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
|
|
7929
|
+
for (const i of candidates) {
|
|
7930
|
+
const n = nodes[i];
|
|
7931
|
+
if (lowAt.has(i) && isComputeNode(n)) {
|
|
7932
|
+
violations.push({
|
|
7933
|
+
propertyId: "low-integrity-command-exec",
|
|
7934
|
+
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`,
|
|
7935
|
+
riskLevel: "critical",
|
|
7936
|
+
path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
|
|
7937
|
+
});
|
|
7938
|
+
if (witness !== void 0) return violations;
|
|
7939
|
+
}
|
|
7940
|
+
}
|
|
7941
|
+
return violations;
|
|
7942
|
+
}
|
|
7943
|
+
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;
|
|
7944
|
+
function checkCrossSessionReachability(graph) {
|
|
7945
|
+
const violations = [];
|
|
7946
|
+
const nodes = allNodes(graph);
|
|
7947
|
+
if (nodes.length === 0) return violations;
|
|
7948
|
+
const witness = graph.witnessIdx;
|
|
7949
|
+
const sources = /* @__PURE__ */ new Set();
|
|
7950
|
+
nodes.forEach((n, i) => {
|
|
7951
|
+
if (n.isLowSource || DL_OR_LOW_RE.test(n.actionDetail)) sources.add(i);
|
|
7952
|
+
});
|
|
7953
|
+
if (sources.size === 0) return violations;
|
|
7954
|
+
const reached = new Set(sources);
|
|
7955
|
+
let changed = true;
|
|
7956
|
+
while (changed) {
|
|
7957
|
+
changed = false;
|
|
7958
|
+
for (const e of allEdges(graph)) {
|
|
7959
|
+
if (reached.has(e.src) && !reached.has(e.dst)) {
|
|
7960
|
+
const srcRes = nodes[e.src].resources;
|
|
7961
|
+
const dstRes = nodes[e.dst].resources;
|
|
7962
|
+
for (const r of srcRes) {
|
|
7963
|
+
if (dstRes.has(r)) {
|
|
7964
|
+
reached.add(e.dst);
|
|
7965
|
+
changed = true;
|
|
7966
|
+
break;
|
|
7967
|
+
}
|
|
7968
|
+
}
|
|
7969
|
+
}
|
|
7970
|
+
}
|
|
7971
|
+
}
|
|
7972
|
+
const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
|
|
7973
|
+
for (const i of candidates) {
|
|
7974
|
+
const n = nodes[i];
|
|
7975
|
+
if (sources.has(i)) continue;
|
|
7976
|
+
if (reached.has(i)) {
|
|
7977
|
+
const srcIdx = Math.min(...sources);
|
|
7978
|
+
const srcDesc = nodes[srcIdx].actionDetail.slice(0, 40);
|
|
7979
|
+
violations.push({
|
|
7980
|
+
propertyId: "low-integrity-reachability",
|
|
7981
|
+
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`,
|
|
7982
|
+
riskLevel: "high",
|
|
7983
|
+
path: nodes.slice(-3).map((m) => ({ sessionId: m.sessionId, seq: m.seq, detail: m.actionDetail.slice(0, 60) }))
|
|
7984
|
+
});
|
|
7985
|
+
if (witness !== void 0) return violations;
|
|
7986
|
+
}
|
|
7987
|
+
}
|
|
7988
|
+
return violations;
|
|
7989
|
+
}
|
|
7990
|
+
function checkSensitiveDataAggregation(graph) {
|
|
7991
|
+
const violations = [];
|
|
7992
|
+
const nodes = allNodes(graph);
|
|
7993
|
+
if (nodes.length < 2) return violations;
|
|
7994
|
+
const witness = graph.witnessIdx;
|
|
7995
|
+
const resIndex = /* @__PURE__ */ new Map();
|
|
7996
|
+
nodes.forEach((n, i) => {
|
|
7997
|
+
for (const r of n.resources) {
|
|
7998
|
+
const list = resIndex.get(r);
|
|
7999
|
+
if (list) list.push(i);
|
|
8000
|
+
else resIndex.set(r, [i]);
|
|
8001
|
+
}
|
|
8002
|
+
});
|
|
8003
|
+
const sourcesFor = (exfilIdx) => {
|
|
8004
|
+
const srcs = /* @__PURE__ */ new Set();
|
|
8005
|
+
const n = nodes[exfilIdx];
|
|
8006
|
+
for (const r of n.resources) {
|
|
8007
|
+
for (const j of resIndex.get(r) || []) {
|
|
8008
|
+
if (j === exfilIdx) continue;
|
|
8009
|
+
srcs.add(`${nodes[j].sessionId}#${nodes[j].seq}`);
|
|
8010
|
+
}
|
|
8011
|
+
}
|
|
8012
|
+
return srcs;
|
|
8013
|
+
};
|
|
8014
|
+
const candidates = witness !== void 0 ? [witness] : nodes.map((_, i) => i);
|
|
8015
|
+
for (const i of candidates) {
|
|
8016
|
+
const n = nodes[i];
|
|
8017
|
+
if (!isExfilNode(n)) continue;
|
|
8018
|
+
const srcs = sourcesFor(i);
|
|
8019
|
+
if (srcs.size >= 2) {
|
|
8020
|
+
violations.push({
|
|
8021
|
+
propertyId: "sensitive-data-aggregation",
|
|
8022
|
+
reason: `\u654F\u611F\u6570\u636E\u805A\u5408\u5916\u4F20\uFF1Aegress \u805A\u5408 ${srcs.size} \u4E2A\u4E8B\u4EF6\u6E90\u6570\u636E\uFF08Rule 12\uFF09`,
|
|
8023
|
+
riskLevel: "critical",
|
|
8024
|
+
path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
|
|
8025
|
+
});
|
|
8026
|
+
if (witness !== void 0) return violations;
|
|
8027
|
+
}
|
|
8028
|
+
}
|
|
8029
|
+
return violations;
|
|
8030
|
+
}
|
|
7319
8031
|
var ALL_PROPERTIES = [
|
|
7320
|
-
{ id: "cross-session-chain", check: checkCrossSessionChain },
|
|
7321
|
-
{ id: "sensitive-data-flow", check: checkSensitiveDataFlow },
|
|
7322
|
-
{ id: "
|
|
7323
|
-
{ id: "no-
|
|
8032
|
+
{ id: "cross-session-chain", check: checkCrossSessionChain, shortName: "P1" },
|
|
8033
|
+
{ id: "sensitive-data-flow", check: checkSensitiveDataFlow, shortName: "P2" },
|
|
8034
|
+
{ id: "low-integrity-reachability", check: checkCrossSessionReachability, shortName: "P3" },
|
|
8035
|
+
{ id: "no-exfil-after-privesc", check: checkNeverExfilAfterPrivesc, shortName: "P4" },
|
|
8036
|
+
{ id: "no-exfil-after-credential", check: checkNeverExfilAfterCred, shortName: "P5" },
|
|
8037
|
+
// L2.2 模型检测(通用 LTL 求值器,mc_rules.json 单一事实源;witness 语义见 ltl.ts)
|
|
8038
|
+
{ id: "mc-ltl-rules", check: checkMCRules, shortName: "P6" },
|
|
8039
|
+
// P7 低完整性控制命令执行(integrity 维度,站点 L2.1 最有区分度的检测之一)
|
|
8040
|
+
{ id: "low-integrity-command-exec", check: checkLowIntegrityCommandExec, shortName: "P7" },
|
|
8041
|
+
// P8 敏感数据聚合外传(egress 聚合 ≥2 事件源)
|
|
8042
|
+
{ id: "sensitive-data-aggregation", check: checkSensitiveDataAggregation, shortName: "P8" }
|
|
7324
8043
|
];
|
|
7325
8044
|
|
|
7326
8045
|
// src/engine/formal-engine/engine.ts
|
|
@@ -7352,6 +8071,9 @@ var FormalEngine = class {
|
|
|
7352
8071
|
});
|
|
7353
8072
|
}
|
|
7354
8073
|
const graph = buildMultiSessionGraph(all, aNext);
|
|
8074
|
+
if (aNext) {
|
|
8075
|
+
graph.witnessIdx = allNodes(graph).length - 1;
|
|
8076
|
+
}
|
|
7355
8077
|
const result = emptyVerificationResult();
|
|
7356
8078
|
for (const prop of this.properties) {
|
|
7357
8079
|
let violations;
|
|
@@ -7383,7 +8105,38 @@ init_rules_loader();
|
|
|
7383
8105
|
var DESTROY_RE = /\b(?:pkill|kill)\b|systemctl\s+stop|service\s+stop|crontab\s+-r|rm\s+-[rf]?\s+/i;
|
|
7384
8106
|
var NEVER_ACTIVATED_RE = /\b(?:not\s+found|no\s+such\s+file|command\s+not\s+found|refused|failed\s+to\s+(?:start|bind|listen)|address\s+already\s+in\s+use)\b/i;
|
|
7385
8107
|
var CRED_FILE_RE2 = /(known_hosts|\.ssh|id_rsa|\/etc\/shadow|\/etc\/passwd|credentials)/i;
|
|
7386
|
-
var EXFIL_RE2 = /https
|
|
8108
|
+
var EXFIL_RE2 = /https?:\/\/(?!127\.\d+\.\d+\.\d+|0\.0\.0\.0|localhost|\[?::1\]?)|scp\s+.*@|rsync\s+.*@/i;
|
|
8109
|
+
var PORT_RE = /(?:-p\s+(\d{1,5})|-p(\d{1,5})|:(\d{1,5})\b)/i;
|
|
8110
|
+
var BARE_PORT_RE = /(?<=^|\s)(\d{1,5})(?=\s|$)/g;
|
|
8111
|
+
var SOCK_RE = /([\w./-]*(?:\/tmp\/|\/var\/run\/|\.sock\b)[\w./-]*)/;
|
|
8112
|
+
var UNIT_RE = /systemctl\s+(?:start|enable|restart)\s+([\w.-]+)/i;
|
|
8113
|
+
var PORT_STAGE_RE = /LISTEN|PERSIST|SCAN/;
|
|
8114
|
+
function extractInstances(actionDetail, stage) {
|
|
8115
|
+
const out = [];
|
|
8116
|
+
if (PORT_STAGE_RE.test(stage)) {
|
|
8117
|
+
const d = actionDetail;
|
|
8118
|
+
let m;
|
|
8119
|
+
const re = new RegExp(PORT_RE.source, "gi");
|
|
8120
|
+
while ((m = re.exec(d)) !== null) {
|
|
8121
|
+
const p = m[1] || m[2] || m[3];
|
|
8122
|
+
if (p && Number(p) > 0 && Number(p) <= 65535) out.push(`port:${p}`);
|
|
8123
|
+
}
|
|
8124
|
+
if (/SCAN/.test(stage)) {
|
|
8125
|
+
const bareRe = new RegExp(BARE_PORT_RE.source, "g");
|
|
8126
|
+
while ((m = bareRe.exec(d)) !== null) {
|
|
8127
|
+
if (m[1] && Number(m[1]) > 0 && Number(m[1]) <= 65535) out.push(`port:${m[1]}`);
|
|
8128
|
+
}
|
|
8129
|
+
}
|
|
8130
|
+
m = SOCK_RE.exec(d);
|
|
8131
|
+
if (m && m[1]) out.push(`sock:${m[1]}`);
|
|
8132
|
+
m = UNIT_RE.exec(d);
|
|
8133
|
+
if (m && m[1]) out.push(`unit:${m[1]}`);
|
|
8134
|
+
}
|
|
8135
|
+
return out;
|
|
8136
|
+
}
|
|
8137
|
+
function buckKey(chainId, inst) {
|
|
8138
|
+
return inst ? `${chainId}@@${inst}` : chainId;
|
|
8139
|
+
}
|
|
7387
8140
|
var STAGES_PATTERNS = null;
|
|
7388
8141
|
var CHAINS = null;
|
|
7389
8142
|
function loadRules() {
|
|
@@ -7426,7 +8179,11 @@ function lifecycleFor(detail, stage, isError) {
|
|
|
7426
8179
|
return "";
|
|
7427
8180
|
}
|
|
7428
8181
|
var MAX_NODES = 1e5;
|
|
7429
|
-
var StatefulChainTracker = class {
|
|
8182
|
+
var StatefulChainTracker = class _StatefulChainTracker {
|
|
8183
|
+
/** 本地接收时刻(实时路径无 called_at 时的兜底时间戳,ISO)。 */
|
|
8184
|
+
static now() {
|
|
8185
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
8186
|
+
}
|
|
7430
8187
|
agentId;
|
|
7431
8188
|
nodes_ = [];
|
|
7432
8189
|
ptr = {};
|
|
@@ -7448,10 +8205,14 @@ var StatefulChainTracker = class {
|
|
|
7448
8205
|
for (const chain of chains) {
|
|
7449
8206
|
const stages = chain.stages || [];
|
|
7450
8207
|
if (stages.length === 0) continue;
|
|
7451
|
-
const
|
|
8208
|
+
const cid = String(chain.id || "");
|
|
8209
|
+
let matched = this.ptr[cid] || 0;
|
|
8210
|
+
for (const [k, v] of Object.entries(this.ptr)) {
|
|
8211
|
+
if (k.startsWith(`${cid}@@`) && v > matched && v < stages.length) matched = v;
|
|
8212
|
+
}
|
|
7452
8213
|
if (matched > 0 && matched < stages.length) {
|
|
7453
8214
|
const pf = {
|
|
7454
|
-
chainId:
|
|
8215
|
+
chainId: cid,
|
|
7455
8216
|
stages,
|
|
7456
8217
|
matched,
|
|
7457
8218
|
riskLevel: String(chain.riskLevel || chain.risk_level || "high"),
|
|
@@ -7527,8 +8288,10 @@ var StatefulChainTracker = class {
|
|
|
7527
8288
|
actionDetail: ad,
|
|
7528
8289
|
stage,
|
|
7529
8290
|
resources: extractResources2(ad),
|
|
8291
|
+
instances: extractInstances(ad, stage),
|
|
7530
8292
|
isError,
|
|
7531
|
-
lifecycle: lifecycleFor(ad, stage, isError)
|
|
8293
|
+
lifecycle: lifecycleFor(ad, stage, isError),
|
|
8294
|
+
timestamp: typeof action.calledAt === "string" ? action.calledAt : _StatefulChainTracker.now()
|
|
7532
8295
|
};
|
|
7533
8296
|
this.nodes_.push(node);
|
|
7534
8297
|
this.seqNext += 1;
|
|
@@ -7536,11 +8299,19 @@ var StatefulChainTracker = class {
|
|
|
7536
8299
|
}
|
|
7537
8300
|
recordLifecycle(node) {
|
|
7538
8301
|
if (node.lifecycle === "dead") {
|
|
7539
|
-
|
|
8302
|
+
const clearedInsts = [];
|
|
8303
|
+
for (const inst of node.instances) {
|
|
8304
|
+
const key = `inst:${inst}`;
|
|
8305
|
+
if (key in this.aliveResources) {
|
|
8306
|
+
delete this.aliveResources[key];
|
|
8307
|
+
clearedInsts.push(inst);
|
|
8308
|
+
}
|
|
8309
|
+
}
|
|
8310
|
+
let pathHit = false;
|
|
7540
8311
|
for (const r of node.resources) {
|
|
7541
8312
|
if (r in this.aliveResources) {
|
|
7542
8313
|
delete this.aliveResources[r];
|
|
7543
|
-
|
|
8314
|
+
pathHit = true;
|
|
7544
8315
|
}
|
|
7545
8316
|
}
|
|
7546
8317
|
const detail = node.actionDetail;
|
|
@@ -7548,16 +8319,30 @@ var StatefulChainTracker = class {
|
|
|
7548
8319
|
const base = r.split("/").pop() || "";
|
|
7549
8320
|
if (base && detail.includes(base)) {
|
|
7550
8321
|
delete this.aliveResources[r];
|
|
7551
|
-
|
|
8322
|
+
pathHit = true;
|
|
7552
8323
|
}
|
|
7553
8324
|
}
|
|
7554
|
-
if (
|
|
8325
|
+
if (clearedInsts.length > 0 && !pathHit) {
|
|
8326
|
+
for (const inst of clearedInsts) this.resetChainsForInstance(inst);
|
|
8327
|
+
} else {
|
|
8328
|
+
this.resetChainsOnDestroy();
|
|
8329
|
+
}
|
|
7555
8330
|
return;
|
|
7556
8331
|
}
|
|
7557
8332
|
if (node.lifecycle === "alive") {
|
|
7558
8333
|
for (const r of node.resources) {
|
|
7559
8334
|
this.aliveResources[r] = node.seq;
|
|
7560
8335
|
}
|
|
8336
|
+
for (const inst of node.instances) {
|
|
8337
|
+
this.aliveResources[`inst:${inst}`] = node.seq;
|
|
8338
|
+
}
|
|
8339
|
+
}
|
|
8340
|
+
}
|
|
8341
|
+
/** 只重置指定实例在全部链上的进度(不影响其他端口/资源)。 */
|
|
8342
|
+
resetChainsForInstance(inst) {
|
|
8343
|
+
const key = buckKey("", inst).slice(2);
|
|
8344
|
+
for (const k of Object.keys(this.ptr)) {
|
|
8345
|
+
if (k.endsWith(`@@${inst}`)) this.ptr[k] = 0;
|
|
7561
8346
|
}
|
|
7562
8347
|
}
|
|
7563
8348
|
resetChainsOnDestroy() {
|
|
@@ -7565,6 +8350,29 @@ var StatefulChainTracker = class {
|
|
|
7565
8350
|
this.ptr[cid] = 0;
|
|
7566
8351
|
}
|
|
7567
8352
|
}
|
|
8353
|
+
/**
|
|
8354
|
+
* 当前节点应推进的链桶集合。
|
|
8355
|
+
* - 有实例节点(LISTEN/PERSIST 带端口/socket/unit)→ 只推自己的实例桶;
|
|
8356
|
+
* 同时为所有链的该实例建立桶(若首次出现)。
|
|
8357
|
+
* - 无实例节点(EXEC/SCAN 等中间段)→ 推全局桶 + 所有当前活跃实例桶
|
|
8358
|
+
* (信息不足时保守推进一切可能,避免漏判)。
|
|
8359
|
+
*/
|
|
8360
|
+
targetsForNode(node, chainId) {
|
|
8361
|
+
const insts = node.instances;
|
|
8362
|
+
if (insts.length > 0) {
|
|
8363
|
+
const active = insts.filter((i) => this.aliveResources[`inst:${i}`] !== void 0);
|
|
8364
|
+
if (active.length > 0) return active.map((i) => buckKey(chainId, i));
|
|
8365
|
+
return [chainId];
|
|
8366
|
+
}
|
|
8367
|
+
const targets = [chainId];
|
|
8368
|
+
for (const k of Object.keys(this.aliveResources)) {
|
|
8369
|
+
if (k.startsWith("inst:")) {
|
|
8370
|
+
const inst = k.slice("inst:".length);
|
|
8371
|
+
targets.push(`${chainId}@@${inst}`);
|
|
8372
|
+
}
|
|
8373
|
+
}
|
|
8374
|
+
return targets;
|
|
8375
|
+
}
|
|
7568
8376
|
advanceChains(node) {
|
|
7569
8377
|
if (node.lifecycle === "dead") return null;
|
|
7570
8378
|
if (node.lifecycle === "never_activated") {
|
|
@@ -7578,23 +8386,32 @@ var StatefulChainTracker = class {
|
|
|
7578
8386
|
const stages = chain.stages || [];
|
|
7579
8387
|
if (stages.length === 0) continue;
|
|
7580
8388
|
const cid = String(chain.id || "");
|
|
7581
|
-
const
|
|
7582
|
-
|
|
7583
|
-
this.ptr[
|
|
7584
|
-
if (cur
|
|
7585
|
-
|
|
7586
|
-
|
|
7587
|
-
|
|
7588
|
-
|
|
7589
|
-
|
|
7590
|
-
|
|
7591
|
-
|
|
8389
|
+
const targets = this.targetsForNode(node, cid);
|
|
8390
|
+
for (const key of targets) {
|
|
8391
|
+
const cur = this.ptr[key] || 0;
|
|
8392
|
+
if (cur < stages.length && stages[cur] === stage) {
|
|
8393
|
+
this.ptr[key] = cur + 1;
|
|
8394
|
+
if (cur + 1 >= stages.length) {
|
|
8395
|
+
return {
|
|
8396
|
+
chainId: cid,
|
|
8397
|
+
stages,
|
|
8398
|
+
riskLevel: String(chain.riskLevel || chain.risk_level || "high"),
|
|
8399
|
+
reason: String(chain.reason || "\u68C0\u6D4B\u5230\u7EC4\u5408\u653B\u51FB\u94FE"),
|
|
8400
|
+
path: this.chainPath(stages)
|
|
8401
|
+
};
|
|
8402
|
+
}
|
|
7592
8403
|
}
|
|
7593
8404
|
}
|
|
7594
8405
|
}
|
|
7595
8406
|
return null;
|
|
7596
8407
|
}
|
|
7597
8408
|
invalidateChainOnFailure(node) {
|
|
8409
|
+
if (node.instances.length > 0) {
|
|
8410
|
+
for (const inst of node.instances) {
|
|
8411
|
+
this.resetChainsForInstance(inst);
|
|
8412
|
+
}
|
|
8413
|
+
return;
|
|
8414
|
+
}
|
|
7598
8415
|
const failed = new Set(node.resources);
|
|
7599
8416
|
if (failed.size === 0) return;
|
|
7600
8417
|
const { chains } = loadRules();
|
|
@@ -7610,7 +8427,7 @@ var StatefulChainTracker = class {
|
|
|
7610
8427
|
let want = stages[0];
|
|
7611
8428
|
for (const n of this.nodes_) {
|
|
7612
8429
|
if (n.stage === want) {
|
|
7613
|
-
path6.push({ sessionId: n.sessionId, seq: n.seq, stage: n.stage });
|
|
8430
|
+
path6.push({ sessionId: n.sessionId, seq: n.seq, stage: n.stage, timestamp: n.timestamp });
|
|
7614
8431
|
const idx = stages.indexOf(n.stage);
|
|
7615
8432
|
if (idx + 1 < stages.length) {
|
|
7616
8433
|
want = stages[idx + 1];
|
|
@@ -7646,7 +8463,7 @@ var StatefulChainTracker = class {
|
|
|
7646
8463
|
if (!this.tainted.has(node.seq)) return [];
|
|
7647
8464
|
if (EXFIL_RE2.test(node.actionDetail)) {
|
|
7648
8465
|
return [{
|
|
7649
|
-
property_id: "sensitive-data-
|
|
8466
|
+
property_id: "sensitive-data-flow",
|
|
7650
8467
|
reason: `\u654F\u611F\u6570\u636E\u4F20\u64AD\u5916\u4F20\uFF1A${this.taintSource(node)} \u2192 ${node.sessionId}#${node.seq} \u7ECF\u5171\u4EAB\u8F7D\u4F53\u5916\u53D1`,
|
|
7651
8468
|
risk_level: "critical",
|
|
7652
8469
|
path: [[node.sessionId, node.seq, node.actionDetail.slice(0, 60)]]
|
|
@@ -7688,8 +8505,12 @@ var StatefulChainTracker = class {
|
|
|
7688
8505
|
actionDetail: String(raw.actionDetail || ""),
|
|
7689
8506
|
stage: String(raw.stage || ""),
|
|
7690
8507
|
resources: [...raw.resources || []],
|
|
8508
|
+
instances: [...raw.instances || []],
|
|
8509
|
+
// 旧快照无此字段 → 空
|
|
7691
8510
|
isError: Boolean(raw.isError),
|
|
7692
|
-
lifecycle: String(raw.lifecycle || "")
|
|
8511
|
+
lifecycle: String(raw.lifecycle || ""),
|
|
8512
|
+
timestamp: typeof raw.timestamp === "string" ? raw.timestamp : null
|
|
8513
|
+
// 旧快照无此字段 → null
|
|
7693
8514
|
});
|
|
7694
8515
|
}
|
|
7695
8516
|
this.seqNext = Number(snapshot.seq_next || this.nodes_.length);
|
|
@@ -7892,6 +8713,7 @@ function getBrainStore() {
|
|
|
7892
8713
|
}
|
|
7893
8714
|
var formalEngine = new FormalEngine();
|
|
7894
8715
|
async function runAnalysis(input) {
|
|
8716
|
+
const observedFormalLayers = [];
|
|
7895
8717
|
const heuristicResult = isHeuristicEnabled() ? runHeuristicCheck(input) : null;
|
|
7896
8718
|
const isInlineScriptCmd = isInlineScriptCommand(input.aNext.actionDetail.toLowerCase());
|
|
7897
8719
|
const isLiteralTextCmd = isLiteralTextCommand(input.aNext.actionDetail.toLowerCase());
|
|
@@ -7917,7 +8739,7 @@ async function runAnalysis(input) {
|
|
|
7917
8739
|
riskType: node.l1RiskType,
|
|
7918
8740
|
confidence: 90,
|
|
7919
8741
|
source: "heuristic_script_scan",
|
|
7920
|
-
violatedLayers: ["
|
|
8742
|
+
violatedLayers: ["L1.2"]
|
|
7921
8743
|
};
|
|
7922
8744
|
}
|
|
7923
8745
|
}
|
|
@@ -7946,8 +8768,8 @@ async function runAnalysis(input) {
|
|
|
7946
8768
|
const rawDetail = extractRawCommand(input.aNext.actionDetail);
|
|
7947
8769
|
const lowerDetail = rawDetail.toLowerCase();
|
|
7948
8770
|
const agentExt = mergeAgentExtensions(
|
|
7949
|
-
getAgentExtensions(input.agentId),
|
|
7950
|
-
|
|
8771
|
+
isLayerEnabled("agent_rules_enabled") ? getAgentExtensions(input.agentId) : null,
|
|
8772
|
+
isLayerEnabled("scenario_rules_enabled") ? getScenarioExtensionsMulti(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "") : null
|
|
7951
8773
|
);
|
|
7952
8774
|
if (FULLY_SAFE_TOOLS.has(actionType)) {
|
|
7953
8775
|
return {
|
|
@@ -8035,7 +8857,7 @@ async function runAnalysis(input) {
|
|
|
8035
8857
|
riskType: inlineJudgment.riskType,
|
|
8036
8858
|
confidence: 90,
|
|
8037
8859
|
source: "logic_rule",
|
|
8038
|
-
violatedLayers: ["1
|
|
8860
|
+
violatedLayers: ["L2.1"]
|
|
8039
8861
|
};
|
|
8040
8862
|
}
|
|
8041
8863
|
return {
|
|
@@ -8064,15 +8886,22 @@ async function runAnalysis(input) {
|
|
|
8064
8886
|
const formalResult = formalEngine.verify(formalSessions, input.aNext);
|
|
8065
8887
|
if (!formalResult.verified) {
|
|
8066
8888
|
const v = formalResult.violations[0];
|
|
8067
|
-
|
|
8068
|
-
|
|
8069
|
-
|
|
8070
|
-
|
|
8071
|
-
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
|
|
8075
|
-
|
|
8889
|
+
if (isFormalObserveMode()) {
|
|
8890
|
+
console.warn(
|
|
8891
|
+
`[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)}`
|
|
8892
|
+
);
|
|
8893
|
+
observedFormalLayers.push("L2.2");
|
|
8894
|
+
} else {
|
|
8895
|
+
return {
|
|
8896
|
+
allowed: false,
|
|
8897
|
+
reason: v ? v.reason : "\u5F62\u5F0F\u5316\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF08\u884C\u4E3A\u6027\u8D28\u8FDD\u53CD\uFF09",
|
|
8898
|
+
riskLevel: v ? v.riskLevel : "high",
|
|
8899
|
+
riskType: "formal_verification",
|
|
8900
|
+
confidence: 90,
|
|
8901
|
+
source: "formal_engine",
|
|
8902
|
+
violatedLayers: ["L2.2"]
|
|
8903
|
+
};
|
|
8904
|
+
}
|
|
8076
8905
|
}
|
|
8077
8906
|
}
|
|
8078
8907
|
if (scriptChain && scriptChain.nodes.length > 0 && isLogicRulesEnabled()) {
|
|
@@ -8087,7 +8916,7 @@ async function runAnalysis(input) {
|
|
|
8087
8916
|
riskType: node.l2RiskType,
|
|
8088
8917
|
confidence: 85,
|
|
8089
8918
|
source: "logic_rule_script_scan",
|
|
8090
|
-
violatedLayers: ["
|
|
8919
|
+
violatedLayers: ["L2.3"]
|
|
8091
8920
|
};
|
|
8092
8921
|
}
|
|
8093
8922
|
}
|
|
@@ -8119,7 +8948,7 @@ async function runAnalysis(input) {
|
|
|
8119
8948
|
riskType: "",
|
|
8120
8949
|
confidence: 85,
|
|
8121
8950
|
source: "heuristic_and_logic_only",
|
|
8122
|
-
violatedLayers: []
|
|
8951
|
+
violatedLayers: observedFormalLayers.length > 0 ? [...observedFormalLayers] : []
|
|
8123
8952
|
};
|
|
8124
8953
|
}
|
|
8125
8954
|
let skipL3Hints = "\u65E0\uFF08\u5F53\u524D\u65E0\u7981\u7528\u89C4\u5219\u547D\u4E2D\uFF09";
|
|
@@ -8164,7 +8993,7 @@ async function runAnalysis(input) {
|
|
|
8164
8993
|
riskType: "",
|
|
8165
8994
|
confidence: 50,
|
|
8166
8995
|
source: "llm_failure_fallback",
|
|
8167
|
-
violatedLayers: []
|
|
8996
|
+
violatedLayers: observedFormalLayers.length > 0 ? [...observedFormalLayers] : []
|
|
8168
8997
|
};
|
|
8169
8998
|
}
|
|
8170
8999
|
return {
|
|
@@ -8174,7 +9003,7 @@ async function runAnalysis(input) {
|
|
|
8174
9003
|
riskType: "",
|
|
8175
9004
|
confidence: 60,
|
|
8176
9005
|
source: "llm_failure_fallback",
|
|
8177
|
-
violatedLayers: ["
|
|
9006
|
+
violatedLayers: ["L3.1"]
|
|
8178
9007
|
};
|
|
8179
9008
|
}
|
|
8180
9009
|
if (!llmResult.allowed && llmResult.confidence && llmResult.confidence >= 90 && _brainExtractor) {
|
|
@@ -8191,6 +9020,9 @@ async function runAnalysis(input) {
|
|
|
8191
9020
|
} catch {
|
|
8192
9021
|
}
|
|
8193
9022
|
}
|
|
9023
|
+
if (observedFormalLayers.length > 0 && llmResult.allowed) {
|
|
9024
|
+
llmResult.violatedLayers = Array.from(/* @__PURE__ */ new Set([...observedFormalLayers, ...llmResult.violatedLayers || []]));
|
|
9025
|
+
}
|
|
8194
9026
|
return llmResult;
|
|
8195
9027
|
}
|
|
8196
9028
|
|
|
@@ -8540,7 +9372,7 @@ async function analyzeRoute(c) {
|
|
|
8540
9372
|
const judgment = incMatchedToDeny(inc);
|
|
8541
9373
|
if (judgment) {
|
|
8542
9374
|
const layers = [...result.violatedLayers];
|
|
8543
|
-
if (!layers.includes("2
|
|
9375
|
+
if (!layers.includes("L2.2-stateful")) layers.push("L2.2-stateful");
|
|
8544
9376
|
console.error(
|
|
8545
9377
|
`[AgentMoss] stateful \u589E\u91CF\u62E6\u622A: agent=${agentId || "-"} property=${judgment.property_id} risk=${judgment.risk_level}`
|
|
8546
9378
|
);
|
|
@@ -8554,7 +9386,8 @@ async function analyzeRoute(c) {
|
|
|
8554
9386
|
policy: "",
|
|
8555
9387
|
violated_policy: "",
|
|
8556
9388
|
confidence: 90,
|
|
8557
|
-
analysis_duration_ms: Math.round((performance.now() - t0) * 10) / 10
|
|
9389
|
+
analysis_duration_ms: Math.round((performance.now() - t0) * 10) / 10,
|
|
9390
|
+
violation_chain: judgment.violation_chain
|
|
8558
9391
|
})
|
|
8559
9392
|
);
|
|
8560
9393
|
}
|
|
@@ -8593,17 +9426,20 @@ function getChainStateStoreFor(_agentId) {
|
|
|
8593
9426
|
}
|
|
8594
9427
|
function incMatchedToDeny(inc) {
|
|
8595
9428
|
if (inc.matched) {
|
|
9429
|
+
const chain = (inc.matched.path || []).map((p) => [p.sessionId, p.seq, p.stage, p.timestamp]);
|
|
8596
9430
|
return {
|
|
8597
9431
|
property_id: "cross-" + inc.matched.chainId,
|
|
8598
9432
|
reason: inc.matched.reason,
|
|
8599
|
-
risk_level: inc.matched.riskLevel
|
|
9433
|
+
risk_level: inc.matched.riskLevel,
|
|
9434
|
+
violation_chain: chain
|
|
8600
9435
|
};
|
|
8601
9436
|
}
|
|
8602
9437
|
if (inc.exfil && inc.exfil.length > 0) {
|
|
8603
9438
|
return {
|
|
8604
|
-
property_id: "sensitive-data-
|
|
9439
|
+
property_id: "sensitive-data-flow",
|
|
8605
9440
|
reason: inc.exfil[0].reason,
|
|
8606
|
-
risk_level: inc.exfil[0].risk_level
|
|
9441
|
+
risk_level: inc.exfil[0].risk_level,
|
|
9442
|
+
violation_chain: []
|
|
8607
9443
|
};
|
|
8608
9444
|
}
|
|
8609
9445
|
return null;
|
|
@@ -8682,12 +9518,13 @@ init_esm_shims();
|
|
|
8682
9518
|
init_esm_shims();
|
|
8683
9519
|
init_runtime_config();
|
|
8684
9520
|
init_token_stats();
|
|
9521
|
+
init_rules_loader();
|
|
8685
9522
|
import { Hono } from "hono";
|
|
8686
9523
|
import { readFileSync as readFileSync15, existsSync as existsSync14 } from "fs";
|
|
8687
|
-
import { join as
|
|
9524
|
+
import { join as join10, dirname as dirname10 } from "path";
|
|
8688
9525
|
import { fileURLToPath as fileURLToPath9 } from "url";
|
|
8689
9526
|
var __dirname8 = dirname10(fileURLToPath9(import.meta.url));
|
|
8690
|
-
var STATIC_DIR =
|
|
9527
|
+
var STATIC_DIR = join10(__dirname8, "static");
|
|
8691
9528
|
var CONSOLE_TOKEN_ENV = "AGENT_MOSS_CONSOLE_TOKEN";
|
|
8692
9529
|
function getConsoleToken() {
|
|
8693
9530
|
return process.env[CONSOLE_TOKEN_ENV] || "";
|
|
@@ -8711,7 +9548,7 @@ function createConsoleRouter() {
|
|
|
8711
9548
|
const console2 = new Hono();
|
|
8712
9549
|
console2.use("/api/*", authMiddleware());
|
|
8713
9550
|
const serveIndex = (c) => {
|
|
8714
|
-
const indexFile =
|
|
9551
|
+
const indexFile = join10(STATIC_DIR, "index.html");
|
|
8715
9552
|
if (existsSync14(indexFile)) {
|
|
8716
9553
|
return c.html(readFileSync15(indexFile, "utf-8"));
|
|
8717
9554
|
}
|
|
@@ -8728,24 +9565,89 @@ function createConsoleRouter() {
|
|
|
8728
9565
|
const rc = updateLayerEnabled(body.layer_key, body.enabled);
|
|
8729
9566
|
return c.json({ layers: rc.layers, env_overrides: getEnvOverrides() });
|
|
8730
9567
|
});
|
|
9568
|
+
function getL22View() {
|
|
9569
|
+
const ltlHuman = {
|
|
9570
|
+
"mc-ltl-001": "\u5148\u8BFB\u53D6\u4E86\u51ED\u636E/\u5BC6\u94A5\uFF0C\u4E4B\u540E\u51FA\u73B0\u7F51\u7EDC\u5916\u4F20",
|
|
9571
|
+
"mc-ltl-002": "\u5148\u4E0B\u8F7D/\u83B7\u53D6\u4E86\u6587\u4EF6\uFF0C\u4E4B\u540E\u6267\u884C\u4E86\u6587\u4EF6",
|
|
9572
|
+
"mc-ltl-003": "\u5148\u7981\u7528\u4E86\u5B89\u5168\u9632\u62A4\uFF0C\u4E4B\u540E\u51FA\u73B0\u7834\u574F\u6027\u64CD\u4F5C\uFF08\u6267\u884C/\u5220\u9664/\u63D0\u6743\uFF09",
|
|
9573
|
+
"mc-ltl-004": "\u5148\u51FA\u73B0\u63D0\u793A\u8BCD\u6CE8\u5165\uFF0C\u4E4B\u540E\u51FA\u73B0\u654F\u611F\u64CD\u4F5C\uFF08\u5916\u4F20/\u5199\u6587\u4EF6/\u6267\u884C\uFF09",
|
|
9574
|
+
"mc-ltl-005": "\u5148\u8BFB\u53D6\u51ED\u636E\uFF0C\u4E4B\u540E\u51FA\u73B0\u6A2A\u5411\u79FB\u52A8\uFF08ssh/scp \u5230\u8FDC\u7A0B\uFF09",
|
|
9575
|
+
"mc-ltl-006": "\u5148\u63A5\u89E6\u654F\u611F\u6570\u636E\uFF0C\u4E4B\u540E\u6E05\u9664\u65E5\u5FD7",
|
|
9576
|
+
"mc-ltl-007": "\u5148\u63D0\u6743\uFF0C\u4E4B\u540E\u5B89\u88C5\u6301\u4E45\u5316\u540E\u95E8",
|
|
9577
|
+
"mc-ltl-008": "\u5148\u4E0B\u8F7D\u6587\u4EF6\uFF0C\u4E4B\u540E\u5B89\u88C5\u6301\u4E45\u5316\u540E\u95E8",
|
|
9578
|
+
"mc-ltl-009": "\u5148\u63A5\u89E6\u654F\u611F\u6570\u636E \u2192 \u6253\u5305\u538B\u7F29 \u2192 \u7F51\u7EDC\u5916\u4F20",
|
|
9579
|
+
"mc-ltl-010": "\u5B89\u88C5\u4E86\u6301\u4E45\u5316\u673A\u5236\uFF08crontab/systemd/.bashrc \u7B49\uFF09\uFF0C\u9700\u5BA1\u6279",
|
|
9580
|
+
"mc-ctl-001": "\u4EFB\u4F55\u60C5\u51B5\u4E0B\u4E0D\u5141\u8BB8\u6E05\u9664\u65E5\u5FD7\uFF08\u5B89\u5168\u5E95\u7EBF\uFF09"
|
|
9581
|
+
};
|
|
9582
|
+
const mc = loadMC();
|
|
9583
|
+
const rules = mc.rules || [];
|
|
9584
|
+
return {
|
|
9585
|
+
enabled_via: "L2_logic_rules",
|
|
9586
|
+
description: "\u65F6\u5E8F\u4E0E\u6570\u636E\u6D41\u653B\u51FB\u68C0\u6D4B\uFF08L2.2 \u6A21\u578B\u68C0\u6D4B\uFF09",
|
|
9587
|
+
ltl_rules: rules.map((r) => ({
|
|
9588
|
+
id: r.id,
|
|
9589
|
+
type: r.type,
|
|
9590
|
+
formula: Array.isArray(r.formula) ? "<\u516C\u5F0F>" : r.formula,
|
|
9591
|
+
reason: r.reason,
|
|
9592
|
+
risk_level: r.risk_level,
|
|
9593
|
+
action: r.action,
|
|
9594
|
+
enabled: r.enabled !== false,
|
|
9595
|
+
human: ltlHuman[r.id] || r.reason || ""
|
|
9596
|
+
}))
|
|
9597
|
+
};
|
|
9598
|
+
}
|
|
9599
|
+
function withL22View(rc) {
|
|
9600
|
+
if (rc && rc.L2_2 && typeof rc.L2_2 === "object" && !Array.isArray(rc.L2_2)) {
|
|
9601
|
+
const cat = rc.L2_2.ltl_rules;
|
|
9602
|
+
const rls = cat && cat.rules || [];
|
|
9603
|
+
const ltlHumanLocal = {
|
|
9604
|
+
"mc-ltl-001": "\u5148\u8BFB\u53D6\u4E86\u51ED\u636E/\u5BC6\u94A5\uFF0C\u4E4B\u540E\u51FA\u73B0\u7F51\u7EDC\u5916\u4F20",
|
|
9605
|
+
"mc-ltl-002": "\u5148\u4E0B\u8F7D/\u83B7\u53D6\u4E86\u6587\u4EF6\uFF0C\u4E4B\u540E\u6267\u884C\u4E86\u6587\u4EF6",
|
|
9606
|
+
"mc-ltl-003": "\u5148\u7981\u7528\u4E86\u5B89\u5168\u9632\u62A4\uFF0C\u4E4B\u540E\u51FA\u73B0\u7834\u574F\u6027\u64CD\u4F5C\uFF08\u6267\u884C/\u5220\u9664/\u63D0\u6743\uFF09",
|
|
9607
|
+
"mc-ltl-004": "\u5148\u51FA\u73B0\u63D0\u793A\u8BCD\u6CE8\u5165\uFF0C\u4E4B\u540E\u51FA\u73B0\u654F\u611F\u64CD\u4F5C\uFF08\u5916\u4F20/\u5199\u6587\u4EF6/\u6267\u884C\uFF09",
|
|
9608
|
+
"mc-ltl-005": "\u5148\u8BFB\u53D6\u51ED\u636E\uFF0C\u4E4B\u540E\u51FA\u73B0\u6A2A\u5411\u79FB\u52A8\uFF08ssh/scp \u5230\u8FDC\u7A0B\uFF09",
|
|
9609
|
+
"mc-ltl-006": "\u5148\u63A5\u89E6\u654F\u611F\u6570\u636E\uFF0C\u4E4B\u540E\u6E05\u9664\u65E5\u5FD7",
|
|
9610
|
+
"mc-ltl-007": "\u5148\u63D0\u6743\uFF0C\u4E4B\u540E\u5B89\u88C5\u6301\u4E45\u5316\u540E\u95E8",
|
|
9611
|
+
"mc-ltl-008": "\u5148\u4E0B\u8F7D\u6587\u4EF6\uFF0C\u4E4B\u540E\u5B89\u88C5\u6301\u4E45\u5316\u540E\u95E8",
|
|
9612
|
+
"mc-ltl-009": "\u5148\u63A5\u89E6\u654F\u611F\u6570\u636E \u2192 \u6253\u5305\u538B\u7F29 \u2192 \u7F51\u7EDC\u5916\u4F20",
|
|
9613
|
+
"mc-ltl-010": "\u5B89\u88C5\u4E86\u6301\u4E45\u5316\u673A\u5236\uFF08crontab/systemd/.bashrc \u7B49\uFF09\uFF0C\u9700\u5BA1\u6279",
|
|
9614
|
+
"mc-ctl-001": "\u4EFB\u4F55\u60C5\u51B5\u4E0B\u4E0D\u5141\u8BB8\u6E05\u9664\u65E5\u5FD7\uFF08\u5B89\u5168\u5E95\u7EBF\uFF09"
|
|
9615
|
+
};
|
|
9616
|
+
rc.L2_2 = {
|
|
9617
|
+
enabled_via: "L2_logic_rules",
|
|
9618
|
+
description: "\u65F6\u5E8F\u4E0E\u6570\u636E\u6D41\u653B\u51FB\u68C0\u6D4B\uFF08L2.2 \u6A21\u578B\u68C0\u6D4B\uFF09",
|
|
9619
|
+
ltl_rules: rls.map((r) => ({
|
|
9620
|
+
id: r.id,
|
|
9621
|
+
type: "ltl",
|
|
9622
|
+
formula: "<\u516C\u5F0F>",
|
|
9623
|
+
reason: r.reason || "",
|
|
9624
|
+
risk_level: r.risk_level || "high",
|
|
9625
|
+
action: r.action || "block",
|
|
9626
|
+
enabled: r.enabled !== false,
|
|
9627
|
+
human: ltlHumanLocal[r.id] || r.reason || ""
|
|
9628
|
+
}))
|
|
9629
|
+
};
|
|
9630
|
+
}
|
|
9631
|
+
return rc;
|
|
9632
|
+
}
|
|
8731
9633
|
console2.get("/api/rules", (c) => {
|
|
8732
9634
|
const rc = loadRuntimeConfig();
|
|
8733
|
-
return c.json({ L1_rules: rc.L1_rules, L2_rules: rc.L2_rules });
|
|
9635
|
+
return c.json({ L1_rules: rc.L1_rules, L2_rules: rc.L2_rules, L2_2: getL22View() });
|
|
8734
9636
|
});
|
|
8735
9637
|
console2.put("/api/rules/enabled", async (c) => {
|
|
8736
9638
|
const b = await c.req.json();
|
|
8737
9639
|
const rc = updateRuleEnabled(b.layer, b.category, b.rule_id, b.enabled);
|
|
8738
|
-
return c.json(rc);
|
|
9640
|
+
return c.json(withL22View(rc));
|
|
8739
9641
|
});
|
|
8740
9642
|
console2.put("/api/rules/deny_mode", async (c) => {
|
|
8741
9643
|
const b = await c.req.json();
|
|
8742
9644
|
const rc = updateRuleDenyMode(b.layer, b.category, b.rule_id, b.deny_mode);
|
|
8743
|
-
return c.json(rc);
|
|
9645
|
+
return c.json(withL22View(rc));
|
|
8744
9646
|
});
|
|
8745
9647
|
console2.put("/api/rules/skip_l3", async (c) => {
|
|
8746
9648
|
const b = await c.req.json();
|
|
8747
9649
|
const rc = updateRuleSkipL3(b.layer, b.category, b.rule_id, b.skip_l3_on_disabled);
|
|
8748
|
-
return c.json(rc);
|
|
9650
|
+
return c.json(withL22View(rc));
|
|
8749
9651
|
});
|
|
8750
9652
|
console2.put("/api/categories/enabled", async (c) => {
|
|
8751
9653
|
const b = await c.req.json();
|
|
@@ -8804,9 +9706,9 @@ function createConsoleRouter() {
|
|
|
8804
9706
|
if (!/^[A-Za-z0-9_-]+$/.test(skillId)) {
|
|
8805
9707
|
return c.json({ error: "invalid skill_id" }, 400);
|
|
8806
9708
|
}
|
|
8807
|
-
const skillPath =
|
|
9709
|
+
const skillPath = join10(dirname10(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
|
|
8808
9710
|
if (!existsSync14(skillPath)) {
|
|
8809
|
-
const builtinPath =
|
|
9711
|
+
const builtinPath = join10(__dirname8, "..", "..", "agent_moss", "skills", `${skillId}.md`);
|
|
8810
9712
|
if (existsSync14(builtinPath)) return c.text(readFileSync15(builtinPath, "utf-8"));
|
|
8811
9713
|
return c.json({ error: "skill not found" }, 404);
|
|
8812
9714
|
}
|
|
@@ -8895,7 +9797,7 @@ function createConsoleRouter() {
|
|
|
8895
9797
|
});
|
|
8896
9798
|
console2.get("/api/config", (c) => {
|
|
8897
9799
|
const rc = loadRuntimeConfig();
|
|
8898
|
-
return c.json(rc);
|
|
9800
|
+
return c.json({ ...rc, L2_2: getL22View() });
|
|
8899
9801
|
});
|
|
8900
9802
|
console2.get("/api/env-overrides", (c) => {
|
|
8901
9803
|
return c.json({ overrides: getEnvOverrides() });
|
|
@@ -8972,4 +9874,17 @@ export {
|
|
|
8972
9874
|
findFreePort,
|
|
8973
9875
|
startServer
|
|
8974
9876
|
};
|
|
9877
|
+
//! 顺序即 fail-closed 短路优先级:verify 逐个执行,首个返回违规的性质成为
|
|
9878
|
+
//! first_violation,后续不再评估。调整顺序影响判定归属,须与 Python
|
|
9879
|
+
//! properties.py ALL_PROPERTIES 保持一致并双端测试确认。
|
|
9880
|
+
//!
|
|
9881
|
+
//! shortName(P1~P8 连续短名)与 id(语义化英文)对照(与 Python 一致):
|
|
9882
|
+
//! P1 cross-session-chain 跨会话组合链
|
|
9883
|
+
//! P2 sensitive-data-flow 敏感数据传播
|
|
9884
|
+
//! P3 low-integrity-reachability 低完整性数据流可达
|
|
9885
|
+
//! P4 no-exfil-after-privesc 提权后禁外传
|
|
9886
|
+
//! P5 no-exfil-after-credential 凭据后禁外传
|
|
9887
|
+
//! P6 mc-ltl-rules LTL 模型检测
|
|
9888
|
+
//! P7 low-integrity-command-exec 低完整性命令执行
|
|
9889
|
+
//! P8 sensitive-data-aggregation 敏感数据聚合外传
|
|
8975
9890
|
//# sourceMappingURL=server.js.map
|