@kenhkl/agent-moss 0.12.6 → 0.12.8
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/agents/__pycache__/base.cpython-312.pyc +0 -0
- package/dist/agents/base.py +41 -4
- package/dist/cli.js +4475 -916
- package/dist/cli.js.map +1 -1
- package/dist/engine/coordinator.d.ts +16 -6
- package/dist/engine/coordinator.js +3378 -708
- package/dist/engine/coordinator.js.map +1 -1
- package/dist/rules/l1_rules.json +21 -0
- package/dist/rules/l2_rules.json +136 -1
- package/dist/scenarios/__init__.py +119 -0
- package/dist/scenarios/__pycache__/__init__.cpython-312.pyc +0 -0
- package/dist/scenarios/bank/rules.json +23 -0
- package/dist/server.js +4180 -742
- package/dist/server.js.map +1 -1
- package/dist/static/index.html +119 -1
- package/package.json +2 -1
- package/dist/skills/brain_script_execution_php_r_system.md +0 -17
- package/dist/skills/brain_script_execution_python_c_import.md +0 -17
|
@@ -126,6 +126,7 @@ __export(config_exports, {
|
|
|
126
126
|
getProviderHeaders: () => getProviderHeaders,
|
|
127
127
|
getRetryConfig: () => getRetryConfig,
|
|
128
128
|
getTimeoutConfig: () => getTimeoutConfig,
|
|
129
|
+
isBrainEnabled: () => isBrainEnabled,
|
|
129
130
|
isHeuristicEnabled: () => isHeuristicEnabled,
|
|
130
131
|
isLLMAvailable: () => isLLMAvailable,
|
|
131
132
|
isLogicRulesEnabled: () => isLogicRulesEnabled,
|
|
@@ -245,11 +246,11 @@ function isLLMAvailable() {
|
|
|
245
246
|
}
|
|
246
247
|
async function probeLLMConnection(timeoutMs = 8e3) {
|
|
247
248
|
if (!isLLMAvailable()) return false;
|
|
248
|
-
const
|
|
249
|
+
const OpenAI3 = (await import("openai")).default;
|
|
249
250
|
const config = getLLMConfig();
|
|
250
251
|
const provider = detectProvider(config.baseUrl, config.provider);
|
|
251
252
|
const extraHeaders = getProviderHeaders(provider);
|
|
252
|
-
const client = new
|
|
253
|
+
const client = new OpenAI3({
|
|
253
254
|
baseURL: config.baseUrl,
|
|
254
255
|
apiKey: config.apiKey,
|
|
255
256
|
timeout: timeoutMs,
|
|
@@ -307,6 +308,9 @@ function getProviderHeaders(provider) {
|
|
|
307
308
|
function isHeuristicEnabled() {
|
|
308
309
|
return process.env.AGENT_MOSS_DISABLE_HEURISTIC !== "1";
|
|
309
310
|
}
|
|
311
|
+
function isBrainEnabled() {
|
|
312
|
+
return process.env.AGENT_MOSS_BRAIN_ENABLED === "1";
|
|
313
|
+
}
|
|
310
314
|
function isLogicRulesEnabled() {
|
|
311
315
|
return process.env.AGENT_MOSS_DISABLE_LOGIC_RULES !== "1";
|
|
312
316
|
}
|
|
@@ -756,6 +760,53 @@ function runLogicCheck(input) {
|
|
|
756
760
|
if (pwdResult) return pwdResult;
|
|
757
761
|
const userDelResult = checkUserDeletionConsent(actionType, actionDetail, actionHistory);
|
|
758
762
|
if (userDelResult) return userDelResult;
|
|
763
|
+
const comboResult = checkComboChain(actionHistory, aNext.actionDetail);
|
|
764
|
+
if (comboResult) return comboResult;
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
767
|
+
function checkComboChain(actionHistory, actionDetailRaw) {
|
|
768
|
+
INLINE_SCRIPT_RE.lastIndex = 0;
|
|
769
|
+
if (INLINE_SCRIPT_RE.test(actionDetailRaw)) return null;
|
|
770
|
+
const details = [];
|
|
771
|
+
for (const h of actionHistory) {
|
|
772
|
+
if (h.actionDetail) details.push(h.actionDetail.toLowerCase());
|
|
773
|
+
}
|
|
774
|
+
details.push(actionDetailRaw.toLowerCase());
|
|
775
|
+
const seq = details.map((d) => {
|
|
776
|
+
const hits = /* @__PURE__ */ new Set();
|
|
777
|
+
for (const [stage, pats] of Object.entries(COMBO_STAGES)) {
|
|
778
|
+
if (pats.some((p) => {
|
|
779
|
+
p.lastIndex = 0;
|
|
780
|
+
return p.test(d);
|
|
781
|
+
})) hits.add(stage);
|
|
782
|
+
}
|
|
783
|
+
return hits;
|
|
784
|
+
});
|
|
785
|
+
for (const chain of COMBO_CHAINS) {
|
|
786
|
+
const chainStages = chain.stages;
|
|
787
|
+
if (!chainStages.length || chain.min_stages <= 0) continue;
|
|
788
|
+
let matched = 0;
|
|
789
|
+
let stageIdx = 0;
|
|
790
|
+
for (const hits of seq) {
|
|
791
|
+
if (stageIdx < chainStages.length && hits.has(chainStages[stageIdx])) {
|
|
792
|
+
matched += 1;
|
|
793
|
+
stageIdx += 1;
|
|
794
|
+
if (matched >= chain.min_stages) break;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
if (matched >= chain.min_stages) {
|
|
798
|
+
const riskLevel = chain.risk_level === "critical" || chain.risk_level === "high" || chain.risk_level === "medium" || chain.risk_level === "low" ? chain.risk_level : "high";
|
|
799
|
+
return {
|
|
800
|
+
allowed: false,
|
|
801
|
+
reason: chain.reason,
|
|
802
|
+
riskLevel,
|
|
803
|
+
riskType: "combo_chain",
|
|
804
|
+
confidence: 85,
|
|
805
|
+
source: "logic_rule",
|
|
806
|
+
violatedLayers: ["2"]
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
}
|
|
759
810
|
return null;
|
|
760
811
|
}
|
|
761
812
|
function extractFilePaths(text) {
|
|
@@ -855,17 +906,17 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
|
|
|
855
906
|
}
|
|
856
907
|
}
|
|
857
908
|
const unreadPaths = [];
|
|
858
|
-
for (const
|
|
909
|
+
for (const path5 of uniqueTargetPaths) {
|
|
859
910
|
const hasRead = [...historyReadPaths].some(
|
|
860
|
-
(rp) =>
|
|
911
|
+
(rp) => path5 === rp || path5.startsWith(rp) || rp.startsWith(path5)
|
|
861
912
|
);
|
|
862
913
|
if (!hasRead) {
|
|
863
|
-
let isCreate = !fs2.existsSync(
|
|
914
|
+
let isCreate = !fs2.existsSync(path5);
|
|
864
915
|
if (!isCreate) {
|
|
865
916
|
isCreate = CREATE_KEYWORDS.some((kw) => reason.toLowerCase().includes(kw) || actionDetail.includes(kw));
|
|
866
917
|
}
|
|
867
918
|
if (!isCreate) {
|
|
868
|
-
unreadPaths.push(
|
|
919
|
+
unreadPaths.push(path5);
|
|
869
920
|
}
|
|
870
921
|
}
|
|
871
922
|
}
|
|
@@ -1008,7 +1059,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
|
|
|
1008
1059
|
violatedLayers: ["2"]
|
|
1009
1060
|
};
|
|
1010
1061
|
}
|
|
1011
|
-
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, INLINE_SCRIPT_RE, DIR_TRAVERSAL_KW;
|
|
1062
|
+
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;
|
|
1012
1063
|
var init_logic_rules = __esm({
|
|
1013
1064
|
"src/engine/logic-rules.ts"() {
|
|
1014
1065
|
"use strict";
|
|
@@ -1017,7 +1068,7 @@ var init_logic_rules = __esm({
|
|
|
1017
1068
|
init_rules_loader();
|
|
1018
1069
|
ROOT_SENSITIVE_PATHS = _loadSensitivePathScope("root");
|
|
1019
1070
|
HOME_SENSITIVE_PATHS = _loadSensitivePathScope("home");
|
|
1020
|
-
CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path:
|
|
1071
|
+
CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path5, riskLevel, desc }) => ({ path: path5, riskLevel, desc, credential: true }));
|
|
1021
1072
|
NON_WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
1022
1073
|
"ask_user_question",
|
|
1023
1074
|
"glob",
|
|
@@ -1161,6 +1212,24 @@ var init_logic_rules = __esm({
|
|
|
1161
1212
|
reason: p.reason
|
|
1162
1213
|
}));
|
|
1163
1214
|
})();
|
|
1215
|
+
COMBO_STAGES = (() => {
|
|
1216
|
+
const combo = loadL2().combo_chains || {};
|
|
1217
|
+
const stages = {};
|
|
1218
|
+
for (const [name, pats] of Object.entries(combo.stages || {})) {
|
|
1219
|
+
stages[name] = pats.map((p) => new RegExp(p, "i"));
|
|
1220
|
+
}
|
|
1221
|
+
return stages;
|
|
1222
|
+
})();
|
|
1223
|
+
COMBO_CHAINS = (() => {
|
|
1224
|
+
const combo = loadL2().combo_chains || {};
|
|
1225
|
+
return (combo.chains || []).map((c) => ({
|
|
1226
|
+
id: String(c.id || "combo_chain"),
|
|
1227
|
+
stages: c.stages || [],
|
|
1228
|
+
min_stages: Number(c.min_stages) || (c.stages || []).length,
|
|
1229
|
+
risk_level: String(c.risk_level || "high"),
|
|
1230
|
+
reason: String(c.reason || "\u68C0\u6D4B\u5230\u7EC4\u5408\u653B\u51FB\u94FE")
|
|
1231
|
+
}));
|
|
1232
|
+
})();
|
|
1164
1233
|
INLINE_SCRIPT_RE = /(?:^|\s)(?:python|python3|python3\.\d+|perl|perl5|ruby|jruby|mruby|node|php|lua)\s+(?:-[a-zA-Z]*[cer][a-zA-Z]*\s+)/i;
|
|
1165
1234
|
DIR_TRAVERSAL_KW = [
|
|
1166
1235
|
"os.listdir",
|
|
@@ -1278,9 +1347,11 @@ __export(runtime_config_exports, {
|
|
|
1278
1347
|
addAgentRule: () => addAgentRule,
|
|
1279
1348
|
addCustomRule: () => addCustomRule,
|
|
1280
1349
|
addCustomSkill: () => addCustomSkill,
|
|
1350
|
+
addScenarioRule: () => addScenarioRule,
|
|
1281
1351
|
deleteAgentRule: () => deleteAgentRule,
|
|
1282
1352
|
deleteCustomRule: () => deleteCustomRule,
|
|
1283
1353
|
deleteCustomSkill: () => deleteCustomSkill,
|
|
1354
|
+
deleteScenarioRule: () => deleteScenarioRule,
|
|
1284
1355
|
filterEnabledRules: () => filterEnabledRules,
|
|
1285
1356
|
generateSourceDefaults: () => generateSourceDefaults,
|
|
1286
1357
|
getAgentRules: () => getAgentRules,
|
|
@@ -1299,9 +1370,11 @@ __export(runtime_config_exports, {
|
|
|
1299
1370
|
getEnabledL2SensitivePathsOrDefault: () => getEnabledL2SensitivePathsOrDefault,
|
|
1300
1371
|
getEnabledL2UserDeletionPatterns: () => getEnabledL2UserDeletionPatterns,
|
|
1301
1372
|
getEnabledL2UserDeletionPatternsOrDefault: () => getEnabledL2UserDeletionPatternsOrDefault,
|
|
1373
|
+
getEnabledScenarioExtensions: () => getEnabledScenarioExtensions,
|
|
1302
1374
|
getEnvOverrides: () => getEnvOverrides,
|
|
1303
1375
|
getProfileOrDetect: () => getProfileOrDetect,
|
|
1304
1376
|
getRuntimeConfigPath: () => getRuntimeConfigPath,
|
|
1377
|
+
getScenarioRules: () => getScenarioRules,
|
|
1305
1378
|
hasRuntimeConfig: () => hasRuntimeConfig,
|
|
1306
1379
|
isL2CategoryEnabled: () => isL2CategoryEnabled,
|
|
1307
1380
|
isLayerEnabled: () => isLayerEnabled,
|
|
@@ -1315,6 +1388,8 @@ __export(runtime_config_exports, {
|
|
|
1315
1388
|
updateRuleDenyMode: () => updateRuleDenyMode,
|
|
1316
1389
|
updateRuleEnabled: () => updateRuleEnabled,
|
|
1317
1390
|
updateRuleSkipL3: () => updateRuleSkipL3,
|
|
1391
|
+
updateScenarioCategoryEnabled: () => updateScenarioCategoryEnabled,
|
|
1392
|
+
updateScenarioRuleEnabled: () => updateScenarioRuleEnabled,
|
|
1318
1393
|
updateSkillCategoryEnabled: () => updateSkillCategoryEnabled,
|
|
1319
1394
|
updateSkillEnabled: () => updateSkillEnabled
|
|
1320
1395
|
});
|
|
@@ -1343,7 +1418,8 @@ function emptyRuntimeConfig() {
|
|
|
1343
1418
|
L1_rules: {},
|
|
1344
1419
|
L2_rules: {},
|
|
1345
1420
|
L3_skills: {},
|
|
1346
|
-
agent_rules: {}
|
|
1421
|
+
agent_rules: {},
|
|
1422
|
+
scenario_rules: {}
|
|
1347
1423
|
};
|
|
1348
1424
|
}
|
|
1349
1425
|
function loadRuntimeConfig() {
|
|
@@ -1369,7 +1445,8 @@ function loadRuntimeConfig() {
|
|
|
1369
1445
|
L1_rules: merged.L1_rules || {},
|
|
1370
1446
|
L2_rules: merged.L2_rules || {},
|
|
1371
1447
|
L3_skills: merged.L3_skills || {},
|
|
1372
|
-
agent_rules: merged.agent_rules || {}
|
|
1448
|
+
agent_rules: merged.agent_rules || {},
|
|
1449
|
+
scenario_rules: merged.scenario_rules || {}
|
|
1373
1450
|
};
|
|
1374
1451
|
} catch {
|
|
1375
1452
|
return emptyRuntimeConfig();
|
|
@@ -1604,6 +1681,55 @@ function _l3SkillsToRules() {
|
|
|
1604
1681
|
out.user_custom = { category_enabled: true, category_desc: "", skills: [] };
|
|
1605
1682
|
return out;
|
|
1606
1683
|
}
|
|
1684
|
+
function _scenarioRulesToDefaults() {
|
|
1685
|
+
const out = {};
|
|
1686
|
+
const scenDir = existsSync4(_SCENARIOS_DIR_PROD) ? _SCENARIOS_DIR_PROD : existsSync4(_SCENARIOS_DIR_DEV) ? _SCENARIOS_DIR_DEV : _SCENARIOS_DIR_PROD;
|
|
1687
|
+
if (!existsSync4(scenDir)) return out;
|
|
1688
|
+
let entries;
|
|
1689
|
+
try {
|
|
1690
|
+
entries = readdirSync3(scenDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
1691
|
+
} catch {
|
|
1692
|
+
return out;
|
|
1693
|
+
}
|
|
1694
|
+
for (const scenarioId of entries) {
|
|
1695
|
+
const rulesFile = join2(scenDir, scenarioId, "rules.json");
|
|
1696
|
+
if (!existsSync4(rulesFile)) continue;
|
|
1697
|
+
let data;
|
|
1698
|
+
try {
|
|
1699
|
+
data = JSON.parse(readFileSync4(rulesFile, "utf-8"));
|
|
1700
|
+
} catch {
|
|
1701
|
+
continue;
|
|
1702
|
+
}
|
|
1703
|
+
out[scenarioId] = {
|
|
1704
|
+
builtin_safe_action_types: {
|
|
1705
|
+
category_enabled: true,
|
|
1706
|
+
category_desc: "\u573A\u666F\u5B8C\u5168\u5B89\u5168\u64CD\u4F5C\u767D\u540D\u5355\uFF08\u573A\u666F\u901A\u5E38\u4E0D\u4F7F\u7528\uFF09",
|
|
1707
|
+
rules: []
|
|
1708
|
+
},
|
|
1709
|
+
builtin_safe_skills: {
|
|
1710
|
+
category_enabled: true,
|
|
1711
|
+
category_desc: "\u573A\u666F\u4FE1\u4EFB\u7684\u5185\u7F6E Skill\uFF08\u573A\u666F\u901A\u5E38\u4E0D\u4F7F\u7528\uFF09",
|
|
1712
|
+
rules: []
|
|
1713
|
+
},
|
|
1714
|
+
extra_command_patterns: {
|
|
1715
|
+
category_enabled: true,
|
|
1716
|
+
category_desc: "\u573A\u666F\u4E13\u5C5E\u5371\u9669\u547D\u4EE4\u6B63\u5219\uFF08L1 \u672B\u5C3E\u8FFD\u52A0\uFF09",
|
|
1717
|
+
rules: (data.extra_command_patterns || []).map((p) => ({ ...p, enabled: true, builtin: true }))
|
|
1718
|
+
},
|
|
1719
|
+
extra_injection_keywords: {
|
|
1720
|
+
category_enabled: true,
|
|
1721
|
+
category_desc: "\u573A\u666F\u4E13\u5C5E\u6CE8\u5165\u5173\u952E\u8BCD\uFF08L1 \u672B\u5C3E\u8FFD\u52A0\uFF09",
|
|
1722
|
+
rules: (data.extra_injection_keywords || []).map((k) => ({ ...k, enabled: true, builtin: true }))
|
|
1723
|
+
},
|
|
1724
|
+
extra_sensitive_paths: {
|
|
1725
|
+
category_enabled: true,
|
|
1726
|
+
category_desc: "\u573A\u666F\u4E13\u5C5E\u654F\u611F\u8DEF\u5F84\uFF08L1 \u672B\u5C3E\u8FFD\u52A0\uFF09",
|
|
1727
|
+
rules: (data.extra_sensitive_paths || []).map((sp) => ({ ...sp, enabled: true, builtin: true }))
|
|
1728
|
+
}
|
|
1729
|
+
};
|
|
1730
|
+
}
|
|
1731
|
+
return out;
|
|
1732
|
+
}
|
|
1607
1733
|
function _agentRulesToDefaults() {
|
|
1608
1734
|
const out = {};
|
|
1609
1735
|
const agentsDir = existsSync4(_AGENTS_DIR_PROD) ? _AGENTS_DIR_PROD : existsSync4(_AGENTS_DIR_DEV) ? _AGENTS_DIR_DEV : _AGENTS_DIR_PROD;
|
|
@@ -1695,7 +1821,8 @@ function generateSourceDefaults() {
|
|
|
1695
1821
|
user_custom: { category_enabled: true, category_desc: "\u7528\u6237\u81EA\u5B9A\u4E49 L2 \u903B\u8F91\u89C4\u5219", rules: [] }
|
|
1696
1822
|
},
|
|
1697
1823
|
L3_skills: _l3SkillsToRules(),
|
|
1698
|
-
agent_rules: _agentRulesToDefaults()
|
|
1824
|
+
agent_rules: _agentRulesToDefaults(),
|
|
1825
|
+
scenario_rules: _scenarioRulesToDefaults()
|
|
1699
1826
|
};
|
|
1700
1827
|
}
|
|
1701
1828
|
function normalizePatternForFingerprint(pattern) {
|
|
@@ -1788,9 +1915,9 @@ function _mergeSkillCategories(userCfg, srcDefaults, layerKey) {
|
|
|
1788
1915
|
}
|
|
1789
1916
|
}
|
|
1790
1917
|
}
|
|
1791
|
-
function _mergeAgentRules(userCfg, srcDefaults) {
|
|
1792
|
-
const userAgent = userCfg
|
|
1793
|
-
const srcAgent = srcDefaults
|
|
1918
|
+
function _mergeAgentRules(userCfg, srcDefaults, partition = "agent_rules") {
|
|
1919
|
+
const userAgent = userCfg[partition] || (userCfg[partition] = {});
|
|
1920
|
+
const srcAgent = srcDefaults[partition] || {};
|
|
1794
1921
|
for (const [agentId, srcCats] of Object.entries(srcAgent)) {
|
|
1795
1922
|
if (!(agentId in userAgent)) {
|
|
1796
1923
|
userAgent[agentId] = JSON.parse(JSON.stringify(srcCats));
|
|
@@ -1849,6 +1976,7 @@ function mergeConfig(userCfg, srcDefaults) {
|
|
|
1849
1976
|
_mergeRuleCategories(cfg, srcDefaults, "L2_rules");
|
|
1850
1977
|
_mergeSkillCategories(cfg, srcDefaults, "L3_skills");
|
|
1851
1978
|
_mergeAgentRules(cfg, srcDefaults);
|
|
1979
|
+
_mergeAgentRules(cfg, srcDefaults, "scenario_rules");
|
|
1852
1980
|
return cfg;
|
|
1853
1981
|
}
|
|
1854
1982
|
function _migrateCategoriesAcrossLayers(cfg, srcLayer, dstLayer, catNames) {
|
|
@@ -2174,7 +2302,85 @@ function updateAgentCategoryEnabled(agentId, category, enabled) {
|
|
|
2174
2302
|
saveRuntimeConfig(runtime);
|
|
2175
2303
|
return runtime;
|
|
2176
2304
|
}
|
|
2177
|
-
|
|
2305
|
+
function getScenarioRules(runtime) {
|
|
2306
|
+
return runtime.scenario_rules || {};
|
|
2307
|
+
}
|
|
2308
|
+
function getEnabledScenarioExtensions(runtime, scenarioId) {
|
|
2309
|
+
const scenData = runtime.scenario_rules?.[scenarioId];
|
|
2310
|
+
if (!scenData) return null;
|
|
2311
|
+
const cmdPatterns = _agentCategoryEnabledRules(scenData.extra_command_patterns).filter((r) => r.pattern).map((r) => ({
|
|
2312
|
+
pattern: r.pattern,
|
|
2313
|
+
risk_level: r.risk_level || "",
|
|
2314
|
+
risk_type: r.risk_type || "",
|
|
2315
|
+
reason: r.reason || ""
|
|
2316
|
+
}));
|
|
2317
|
+
const injKeywords = _agentCategoryEnabledRules(scenData.extra_injection_keywords).filter((r) => r.keyword).map((r) => ({
|
|
2318
|
+
keyword: r.keyword,
|
|
2319
|
+
risk_level: r.risk_level || "",
|
|
2320
|
+
lang: r.lang || "en"
|
|
2321
|
+
}));
|
|
2322
|
+
const sensitivePaths = _agentCategoryEnabledRules(scenData.extra_sensitive_paths).filter((r) => r.path).map((r) => ({
|
|
2323
|
+
path: r.path,
|
|
2324
|
+
risk_level: r.risk_level || "",
|
|
2325
|
+
desc: r.desc || "",
|
|
2326
|
+
credential: r.credential,
|
|
2327
|
+
read_only: r.read_only,
|
|
2328
|
+
deny_mode: r.deny_mode
|
|
2329
|
+
}));
|
|
2330
|
+
return {
|
|
2331
|
+
agent_id: scenarioId,
|
|
2332
|
+
description: "",
|
|
2333
|
+
extra_command_patterns: cmdPatterns,
|
|
2334
|
+
extra_injection_keywords: injKeywords,
|
|
2335
|
+
extra_sensitive_paths: sensitivePaths,
|
|
2336
|
+
builtin_safe_skills: [],
|
|
2337
|
+
builtin_safe_action_types: [],
|
|
2338
|
+
extra_skills: []
|
|
2339
|
+
};
|
|
2340
|
+
}
|
|
2341
|
+
function updateScenarioRuleEnabled(scenarioId, category, ruleFp, enabled) {
|
|
2342
|
+
const runtime = loadRuntimeConfig();
|
|
2343
|
+
const cat = runtime.scenario_rules?.[scenarioId]?.[category];
|
|
2344
|
+
if (cat) {
|
|
2345
|
+
const targetFp = _ruleFingerprint(ruleFp);
|
|
2346
|
+
for (const r of cat.rules || []) {
|
|
2347
|
+
if (_ruleFingerprint(r) === targetFp) {
|
|
2348
|
+
r.enabled = enabled;
|
|
2349
|
+
break;
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
saveRuntimeConfig(runtime);
|
|
2354
|
+
return runtime;
|
|
2355
|
+
}
|
|
2356
|
+
function addScenarioRule(scenarioId, category, rule) {
|
|
2357
|
+
const runtime = loadRuntimeConfig();
|
|
2358
|
+
if (!runtime.scenario_rules) runtime.scenario_rules = {};
|
|
2359
|
+
if (!runtime.scenario_rules[scenarioId]) runtime.scenario_rules[scenarioId] = {};
|
|
2360
|
+
const cats = runtime.scenario_rules[scenarioId];
|
|
2361
|
+
if (!cats[category]) cats[category] = { category_enabled: true, category_desc: "", rules: [] };
|
|
2362
|
+
(cats[category].rules || (cats[category].rules = [])).push({ ...rule, enabled: true, builtin: false });
|
|
2363
|
+
saveRuntimeConfig(runtime);
|
|
2364
|
+
return runtime;
|
|
2365
|
+
}
|
|
2366
|
+
function deleteScenarioRule(scenarioId, category, ruleFp) {
|
|
2367
|
+
const runtime = loadRuntimeConfig();
|
|
2368
|
+
const cat = runtime.scenario_rules?.[scenarioId]?.[category];
|
|
2369
|
+
if (cat) {
|
|
2370
|
+
const targetFp = _ruleFingerprint(ruleFp);
|
|
2371
|
+
cat.rules = (cat.rules || []).filter((r) => _ruleFingerprint(r) !== targetFp);
|
|
2372
|
+
}
|
|
2373
|
+
saveRuntimeConfig(runtime);
|
|
2374
|
+
return runtime;
|
|
2375
|
+
}
|
|
2376
|
+
function updateScenarioCategoryEnabled(scenarioId, category, enabled) {
|
|
2377
|
+
const runtime = loadRuntimeConfig();
|
|
2378
|
+
const cat = runtime.scenario_rules?.[scenarioId]?.[category];
|
|
2379
|
+
if (cat) cat.category_enabled = enabled;
|
|
2380
|
+
saveRuntimeConfig(runtime);
|
|
2381
|
+
return runtime;
|
|
2382
|
+
}
|
|
2383
|
+
var __dirname4, ENV_RUNTIME_CONFIG_PATH, RUNTIME_CONFIG_DIR, RUNTIME_CONFIG_PATH, LAYER_DISABLE_ENV, DENY_MODE_LABELS, _AGENTS_DIR_PROD, _AGENTS_DIR_DEV, _SCENARIOS_DIR_PROD, _SCENARIOS_DIR_DEV, SKILL_ID_RE;
|
|
2178
2384
|
var init_runtime_config = __esm({
|
|
2179
2385
|
"src/infra/runtime-config.ts"() {
|
|
2180
2386
|
"use strict";
|
|
@@ -2199,6 +2405,8 @@ var init_runtime_config = __esm({
|
|
|
2199
2405
|
};
|
|
2200
2406
|
_AGENTS_DIR_PROD = join2(__dirname4, "agents");
|
|
2201
2407
|
_AGENTS_DIR_DEV = join2(__dirname4, "..", "..", "..", "agent_moss", "agents");
|
|
2408
|
+
_SCENARIOS_DIR_PROD = join2(__dirname4, "scenarios");
|
|
2409
|
+
_SCENARIOS_DIR_DEV = join2(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
|
|
2202
2410
|
SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
|
|
2203
2411
|
}
|
|
2204
2412
|
});
|
|
@@ -2250,6 +2458,21 @@ function getAgentExtensions(agentId) {
|
|
|
2250
2458
|
);
|
|
2251
2459
|
return ext;
|
|
2252
2460
|
}
|
|
2461
|
+
function mergeAgentExtensions(...exts) {
|
|
2462
|
+
const nonNull = exts.filter((e) => !!e);
|
|
2463
|
+
if (nonNull.length === 0) return null;
|
|
2464
|
+
const base = nonNull[0];
|
|
2465
|
+
return {
|
|
2466
|
+
agent_id: base.agent_id,
|
|
2467
|
+
description: base.description,
|
|
2468
|
+
extra_command_patterns: nonNull.flatMap((e) => e.extra_command_patterns || []),
|
|
2469
|
+
extra_injection_keywords: nonNull.flatMap((e) => e.extra_injection_keywords || []),
|
|
2470
|
+
extra_sensitive_paths: nonNull.flatMap((e) => e.extra_sensitive_paths || []),
|
|
2471
|
+
builtin_safe_skills: nonNull.flatMap((e) => e.builtin_safe_skills || []),
|
|
2472
|
+
builtin_safe_action_types: nonNull.flatMap((e) => e.builtin_safe_action_types || []),
|
|
2473
|
+
extra_skills: nonNull.flatMap((e) => e.extra_skills || [])
|
|
2474
|
+
};
|
|
2475
|
+
}
|
|
2253
2476
|
var __filename4, __dirname5, AGENTS_DIR_PROD, AGENTS_DIR_DEV_FALLBACK, cache;
|
|
2254
2477
|
var init_agents = __esm({
|
|
2255
2478
|
"src/agents/index.ts"() {
|
|
@@ -2263,6 +2486,77 @@ var init_agents = __esm({
|
|
|
2263
2486
|
}
|
|
2264
2487
|
});
|
|
2265
2488
|
|
|
2489
|
+
// src/scenarios/index.ts
|
|
2490
|
+
import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
|
|
2491
|
+
import { resolve as resolve4, dirname as dirname5 } from "path";
|
|
2492
|
+
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
2493
|
+
function resolveScenariosDir() {
|
|
2494
|
+
if (existsSync6(SCENARIOS_DIR_PROD)) return SCENARIOS_DIR_PROD;
|
|
2495
|
+
if (existsSync6(SCENARIOS_DIR_DEV_FALLBACK)) return SCENARIOS_DIR_DEV_FALLBACK;
|
|
2496
|
+
return SCENARIOS_DIR_PROD;
|
|
2497
|
+
}
|
|
2498
|
+
function getScenarioExtensions(scenarioId) {
|
|
2499
|
+
if (!scenarioId) return null;
|
|
2500
|
+
if (cache2.has(scenarioId)) return cache2.get(scenarioId) ?? null;
|
|
2501
|
+
let ext = null;
|
|
2502
|
+
try {
|
|
2503
|
+
const { hasRuntimeConfig: hasRuntimeConfig2, loadRuntimeConfig: loadRuntimeConfig2, getEnabledScenarioExtensions: getEnabledScenarioExtensions2 } = (init_runtime_config(), __toCommonJS(runtime_config_exports));
|
|
2504
|
+
if (hasRuntimeConfig2()) {
|
|
2505
|
+
ext = getEnabledScenarioExtensions2(loadRuntimeConfig2(), scenarioId);
|
|
2506
|
+
}
|
|
2507
|
+
} catch {
|
|
2508
|
+
}
|
|
2509
|
+
if (ext === null) {
|
|
2510
|
+
const scenariosDir = resolveScenariosDir();
|
|
2511
|
+
const rulesFile = resolve4(scenariosDir, scenarioId, "rules.json");
|
|
2512
|
+
if (!existsSync6(rulesFile)) {
|
|
2513
|
+
cache2.set(scenarioId, null);
|
|
2514
|
+
return null;
|
|
2515
|
+
}
|
|
2516
|
+
try {
|
|
2517
|
+
const raw = readFileSync6(rulesFile, "utf-8");
|
|
2518
|
+
const data = JSON.parse(raw);
|
|
2519
|
+
const id = data.scenario_id || data.agent_id || "";
|
|
2520
|
+
if (!id) {
|
|
2521
|
+
console.warn(`[AgentMoss scenarios] rules.json \u7F3A\u5C11 scenario_id \u5B57\u6BB5: ${rulesFile}`);
|
|
2522
|
+
cache2.set(scenarioId, null);
|
|
2523
|
+
return null;
|
|
2524
|
+
}
|
|
2525
|
+
ext = {
|
|
2526
|
+
agent_id: id,
|
|
2527
|
+
description: data.description || "",
|
|
2528
|
+
extra_command_patterns: data.extra_command_patterns || [],
|
|
2529
|
+
extra_injection_keywords: data.extra_injection_keywords || [],
|
|
2530
|
+
extra_sensitive_paths: data.extra_sensitive_paths || [],
|
|
2531
|
+
builtin_safe_skills: data.builtin_safe_skills || [],
|
|
2532
|
+
builtin_safe_action_types: data.builtin_safe_action_types || [],
|
|
2533
|
+
extra_skills: data.extra_skills || []
|
|
2534
|
+
};
|
|
2535
|
+
} catch (e) {
|
|
2536
|
+
console.error(`[AgentMoss scenarios] \u52A0\u8F7D\u573A\u666F ${scenarioId} rules.json \u5931\u8D25:`, e.message);
|
|
2537
|
+
cache2.set(scenarioId, null);
|
|
2538
|
+
return null;
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
cache2.set(scenarioId, ext);
|
|
2542
|
+
console.log(
|
|
2543
|
+
`[AgentMoss scenarios] \u52A0\u8F7D\u573A\u666F ${scenarioId} \u5B9A\u5236\u89C4\u5219: ${ext.extra_command_patterns?.length ?? 0} cmd patterns, ${ext.extra_injection_keywords?.length ?? 0} injection keywords, ${ext.extra_sensitive_paths?.length ?? 0} sensitive paths`
|
|
2544
|
+
);
|
|
2545
|
+
return ext;
|
|
2546
|
+
}
|
|
2547
|
+
var __filename5, __dirname6, SCENARIOS_DIR_PROD, SCENARIOS_DIR_DEV_FALLBACK, cache2;
|
|
2548
|
+
var init_scenarios = __esm({
|
|
2549
|
+
"src/scenarios/index.ts"() {
|
|
2550
|
+
"use strict";
|
|
2551
|
+
init_esm_shims();
|
|
2552
|
+
__filename5 = fileURLToPath7(import.meta.url);
|
|
2553
|
+
__dirname6 = dirname5(__filename5);
|
|
2554
|
+
SCENARIOS_DIR_PROD = resolve4(__dirname6, "scenarios");
|
|
2555
|
+
SCENARIOS_DIR_DEV_FALLBACK = resolve4(__dirname6, "../../../agent_moss/scenarios");
|
|
2556
|
+
cache2 = /* @__PURE__ */ new Map();
|
|
2557
|
+
}
|
|
2558
|
+
});
|
|
2559
|
+
|
|
2266
2560
|
// src/engine/heuristic.ts
|
|
2267
2561
|
function runHeuristicCheck(input) {
|
|
2268
2562
|
const command = `${input.aNext.actionType}: ${input.aNext.actionDetail}`;
|
|
@@ -2302,7 +2596,10 @@ function runHeuristicCheck(input) {
|
|
|
2302
2596
|
}
|
|
2303
2597
|
}
|
|
2304
2598
|
highestRiskType = topRiskType || highestRiskType;
|
|
2305
|
-
const agentExt =
|
|
2599
|
+
const agentExt = mergeAgentExtensions(
|
|
2600
|
+
getAgentExtensions(input.agentId),
|
|
2601
|
+
getScenarioExtensions(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "")
|
|
2602
|
+
);
|
|
2306
2603
|
if (agentExt?.extra_command_patterns) {
|
|
2307
2604
|
for (const rule of agentExt.extra_command_patterns) {
|
|
2308
2605
|
try {
|
|
@@ -2475,13 +2772,13 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2475
2772
|
const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
|
|
2476
2773
|
const isWriteOpFlag = isWriteOperation("", command);
|
|
2477
2774
|
const pathScanText = normalizePathSegments(stripLiteralTextSegments(command));
|
|
2478
|
-
const pathMatches = (text,
|
|
2479
|
-
const parts =
|
|
2775
|
+
const pathMatches = (text, path5) => {
|
|
2776
|
+
const parts = path5.split(/(\(\?!.*?\))/);
|
|
2480
2777
|
const escaped = parts.map((part) => {
|
|
2481
2778
|
if (part.startsWith("(?!")) return part;
|
|
2482
2779
|
return part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2483
2780
|
}).join("");
|
|
2484
|
-
const re =
|
|
2781
|
+
const re = path5.startsWith(".") || path5.startsWith("/") ? new RegExp(`(?:^|[\\s/\\\\'"])${escaped}\\b`, "i") : new RegExp(`\\b${escaped}\\b`, "i");
|
|
2485
2782
|
return re.test(text);
|
|
2486
2783
|
};
|
|
2487
2784
|
for (const sp of ROOT_SENSITIVE_PATHS) {
|
|
@@ -2745,6 +3042,7 @@ var init_heuristic = __esm({
|
|
|
2745
3042
|
init_esm_shims();
|
|
2746
3043
|
init_config();
|
|
2747
3044
|
init_agents();
|
|
3045
|
+
init_scenarios();
|
|
2748
3046
|
init_rules_loader();
|
|
2749
3047
|
init_logic_rules();
|
|
2750
3048
|
INJECTION_KEYWORDS = (() => {
|
|
@@ -2779,16 +3077,16 @@ __export(token_stats_exports, {
|
|
|
2779
3077
|
recordTokenUsage: () => recordTokenUsage,
|
|
2780
3078
|
resetTokenStats: () => resetTokenStats
|
|
2781
3079
|
});
|
|
2782
|
-
import { readFileSync as
|
|
3080
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
2783
3081
|
function getStatsPath() {
|
|
2784
3082
|
const envPath = process.env[ENV_STATS_PATH];
|
|
2785
3083
|
if (envPath) return envPath;
|
|
2786
3084
|
return getTokenStatsPath();
|
|
2787
3085
|
}
|
|
2788
3086
|
function loadAllRecords(statsPath) {
|
|
2789
|
-
if (!
|
|
3087
|
+
if (!existsSync10(statsPath)) return [];
|
|
2790
3088
|
try {
|
|
2791
|
-
const data = JSON.parse(
|
|
3089
|
+
const data = JSON.parse(readFileSync11(statsPath, "utf-8"));
|
|
2792
3090
|
if (Array.isArray(data)) return data;
|
|
2793
3091
|
if (data && Array.isArray(data.records)) return data.records;
|
|
2794
3092
|
return [];
|
|
@@ -2799,7 +3097,7 @@ function loadAllRecords(statsPath) {
|
|
|
2799
3097
|
function saveRecords(statsPath, records) {
|
|
2800
3098
|
try {
|
|
2801
3099
|
const dir = statsPath.substring(0, statsPath.lastIndexOf("/"));
|
|
2802
|
-
if (dir && !
|
|
3100
|
+
if (dir && !existsSync10(dir)) mkdirSync2(dir, { recursive: true });
|
|
2803
3101
|
writeFileSync2(statsPath, JSON.stringify(records, null, 2), "utf-8");
|
|
2804
3102
|
} catch (e) {
|
|
2805
3103
|
}
|
|
@@ -2926,7 +3224,7 @@ function getTokenTrend(days = 0, startDate, endDate) {
|
|
|
2926
3224
|
}
|
|
2927
3225
|
function resetTokenStats() {
|
|
2928
3226
|
const statsPath = getStatsPath();
|
|
2929
|
-
if (
|
|
3227
|
+
if (existsSync10(statsPath)) {
|
|
2930
3228
|
try {
|
|
2931
3229
|
unlinkSync2(statsPath);
|
|
2932
3230
|
} catch {
|
|
@@ -2957,28 +3255,28 @@ init_skill_loader();
|
|
|
2957
3255
|
|
|
2958
3256
|
// src/engine/template-loader.ts
|
|
2959
3257
|
init_esm_shims();
|
|
2960
|
-
import { readFileSync as
|
|
2961
|
-
import { resolve as
|
|
2962
|
-
import { fileURLToPath as
|
|
2963
|
-
var
|
|
2964
|
-
var
|
|
2965
|
-
var TEMPLATES_DIR =
|
|
2966
|
-
var TEMPLATES_DIR_FALLBACK =
|
|
3258
|
+
import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
|
|
3259
|
+
import { resolve as resolve5, dirname as dirname6 } from "path";
|
|
3260
|
+
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
3261
|
+
var __filename6 = fileURLToPath8(import.meta.url);
|
|
3262
|
+
var __dirname7 = dirname6(__filename6);
|
|
3263
|
+
var TEMPLATES_DIR = resolve5(__dirname7, "../templates");
|
|
3264
|
+
var TEMPLATES_DIR_FALLBACK = resolve5(__dirname7, "../../../agent_moss/templates");
|
|
2967
3265
|
var TEMPLATE_FILES = ["prompt1_template.txt", "prompt2_template.txt", "security_judge_template.txt"];
|
|
2968
3266
|
var cachedTemplates = null;
|
|
2969
3267
|
function loadTemplates() {
|
|
2970
3268
|
if (cachedTemplates) return cachedTemplates;
|
|
2971
3269
|
cachedTemplates = {};
|
|
2972
|
-
const dir =
|
|
2973
|
-
if (!
|
|
3270
|
+
const dir = existsSync7(TEMPLATES_DIR) ? TEMPLATES_DIR : TEMPLATES_DIR_FALLBACK;
|
|
3271
|
+
if (!existsSync7(dir)) {
|
|
2974
3272
|
console.warn("[AgentMoss] Templates directory not found:", TEMPLATES_DIR, "and fallback:", TEMPLATES_DIR_FALLBACK);
|
|
2975
3273
|
return cachedTemplates;
|
|
2976
3274
|
}
|
|
2977
3275
|
for (const file of TEMPLATE_FILES) {
|
|
2978
|
-
const filePath =
|
|
2979
|
-
if (
|
|
3276
|
+
const filePath = resolve5(dir, file);
|
|
3277
|
+
if (existsSync7(filePath)) {
|
|
2980
3278
|
const key = file.replace(".txt", "").replace("_template", "");
|
|
2981
|
-
cachedTemplates[key] =
|
|
3279
|
+
cachedTemplates[key] = readFileSync7(filePath, "utf-8");
|
|
2982
3280
|
}
|
|
2983
3281
|
}
|
|
2984
3282
|
console.log(`[AgentMoss] Loaded ${Object.keys(cachedTemplates).length} templates from ${dir}`);
|
|
@@ -2987,8 +3285,8 @@ function loadTemplates() {
|
|
|
2987
3285
|
|
|
2988
3286
|
// src/engine/script-analyzer.ts
|
|
2989
3287
|
init_esm_shims();
|
|
2990
|
-
import { readFileSync as
|
|
2991
|
-
import { resolve as
|
|
3288
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
3289
|
+
import { resolve as resolve6, join as join3 } from "path";
|
|
2992
3290
|
var SUSPICIOUS_PATTERNS = [
|
|
2993
3291
|
// Network exfiltration (curl/wget POST in scripts is almost never legitimate)
|
|
2994
3292
|
{ name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
|
|
@@ -3121,8 +3419,8 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
|
|
|
3121
3419
|
}
|
|
3122
3420
|
function readScriptContent(scriptPath, maxLines = 500) {
|
|
3123
3421
|
try {
|
|
3124
|
-
const expandedPath = scriptPath.startsWith("~") ? join3(process.env.HOME || "/home/user", scriptPath.slice(2)) :
|
|
3125
|
-
const raw =
|
|
3422
|
+
const expandedPath = scriptPath.startsWith("~") ? join3(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve6(scriptPath);
|
|
3423
|
+
const raw = readFileSync8(expandedPath, "utf-8");
|
|
3126
3424
|
const lines = raw.split("\n").slice(0, maxLines);
|
|
3127
3425
|
return { content: lines.join("\n"), lineCount: lines.length, error: "" };
|
|
3128
3426
|
} catch (e) {
|
|
@@ -3176,760 +3474,3036 @@ ${result.scriptContent}
|
|
|
3176
3474
|
init_esm_shims();
|
|
3177
3475
|
init_heuristic();
|
|
3178
3476
|
init_logic_rules();
|
|
3179
|
-
import {
|
|
3180
|
-
import { resolve as
|
|
3181
|
-
import { homedir as
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3477
|
+
import { statSync as statSync2, existsSync as existsSync9 } from "fs";
|
|
3478
|
+
import { resolve as resolve8, join as join5, isAbsolute as isAbsolute2 } from "path";
|
|
3479
|
+
import { homedir as homedir3 } from "os";
|
|
3480
|
+
|
|
3481
|
+
// src/engine/script-engine/tracker.ts
|
|
3482
|
+
init_esm_shims();
|
|
3483
|
+
import * as fs3 from "fs";
|
|
3484
|
+
import * as path3 from "path";
|
|
3485
|
+
import * as os from "os";
|
|
3486
|
+
|
|
3487
|
+
// src/engine/script-engine/backends/python-backend.ts
|
|
3488
|
+
init_esm_shims();
|
|
3489
|
+
import { parser } from "@lezer/python";
|
|
3490
|
+
var MAX_LINES = 500;
|
|
3491
|
+
var PURE_BUILTINS = /* @__PURE__ */ new Set([
|
|
3492
|
+
"len",
|
|
3493
|
+
"print",
|
|
3494
|
+
"str",
|
|
3495
|
+
"int",
|
|
3496
|
+
"float",
|
|
3497
|
+
"bool",
|
|
3498
|
+
"list",
|
|
3499
|
+
"dict",
|
|
3500
|
+
"set",
|
|
3501
|
+
"tuple",
|
|
3502
|
+
"range",
|
|
3503
|
+
"sorted",
|
|
3504
|
+
"enumerate",
|
|
3505
|
+
"zip",
|
|
3506
|
+
"map",
|
|
3507
|
+
"filter",
|
|
3508
|
+
"sum",
|
|
3509
|
+
"min",
|
|
3510
|
+
"max",
|
|
3511
|
+
"abs",
|
|
3512
|
+
"round",
|
|
3513
|
+
"isinstance",
|
|
3514
|
+
"type",
|
|
3515
|
+
"open",
|
|
3516
|
+
"input",
|
|
3517
|
+
"repr",
|
|
3518
|
+
"ord",
|
|
3519
|
+
"chr",
|
|
3520
|
+
"hex",
|
|
3521
|
+
"oct",
|
|
3522
|
+
"bin",
|
|
3523
|
+
"format",
|
|
3524
|
+
"id",
|
|
3525
|
+
"hash",
|
|
3526
|
+
"any",
|
|
3527
|
+
"all",
|
|
3528
|
+
"next",
|
|
3529
|
+
"iter",
|
|
3530
|
+
"reversed",
|
|
3531
|
+
"pow",
|
|
3532
|
+
"divmod"
|
|
3533
|
+
]);
|
|
3534
|
+
var CHILD_CALL_RE = /(?:^|\s|&&|\|\|)\s*((?:\/bin\/)?bash|sh|zsh|(?:python|python3|node|ruby|perl|php|lua))\s+(\S+(?:\.(?:sh|shrc|py|js|rb|pl|php|lua))?)([^\n;|&]*?)$/gim;
|
|
3535
|
+
var SUBPROCESS_FUNCS = /* @__PURE__ */ new Set(["run", "call", "Popen", "check_call", "check_output"]);
|
|
3536
|
+
var OS_FUNCS = /* @__PURE__ */ new Set(["system", "popen", "popen2", "popen3", "popen4"]);
|
|
3537
|
+
function buildLineStarts(content) {
|
|
3538
|
+
const starts = [0];
|
|
3539
|
+
for (let i = 0; i < content.length; i++) {
|
|
3540
|
+
if (content[i] === "\n") starts.push(i + 1);
|
|
3541
|
+
}
|
|
3542
|
+
return starts;
|
|
3543
|
+
}
|
|
3544
|
+
function lineAt(lineStarts, pos) {
|
|
3545
|
+
let lo = 0;
|
|
3546
|
+
let hi = lineStarts.length - 1;
|
|
3547
|
+
while (lo < hi) {
|
|
3548
|
+
const mid = lo + hi + 1 >> 1;
|
|
3549
|
+
if (lineStarts[mid] <= pos) lo = mid;
|
|
3550
|
+
else hi = mid - 1;
|
|
3551
|
+
}
|
|
3552
|
+
return lo + 1;
|
|
3553
|
+
}
|
|
3554
|
+
function markNodeLines(node, lines, lineStarts) {
|
|
3555
|
+
const start = lineAt(lineStarts, node.from);
|
|
3556
|
+
const end = node.to > node.from ? lineAt(lineStarts, node.to - 1) : start;
|
|
3557
|
+
for (let i = start; i <= end; i++) lines.add(i);
|
|
3558
|
+
}
|
|
3559
|
+
function reconstruct(sourceLines, lines) {
|
|
3560
|
+
const out = [];
|
|
3561
|
+
for (const i of [...lines].sort((a, b) => a - b)) {
|
|
3562
|
+
if (i >= 1 && i <= sourceLines.length) out.push(sourceLines[i - 1]);
|
|
3563
|
+
}
|
|
3564
|
+
return out.join("\n");
|
|
3565
|
+
}
|
|
3566
|
+
function makeEnv(argv) {
|
|
3567
|
+
return { argv, vars: {} };
|
|
3568
|
+
}
|
|
3569
|
+
var SOURCE = "";
|
|
3570
|
+
function srcSlice(node) {
|
|
3571
|
+
return SOURCE.slice(node.from, node.to);
|
|
3572
|
+
}
|
|
3573
|
+
function stringValue(tokenText) {
|
|
3574
|
+
let s = tokenText;
|
|
3575
|
+
if (s.length >= 2) {
|
|
3576
|
+
const q = s[0];
|
|
3577
|
+
if ((q === '"' || q === "'") && s.endsWith(q)) s = s.slice(1, -1);
|
|
3578
|
+
}
|
|
3579
|
+
s = s.replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\r/g, "\r").replace(/\\\\/g, "\\").replace(/\\"/g, '"').replace(/\\'/g, "'");
|
|
3580
|
+
return s;
|
|
3581
|
+
}
|
|
3582
|
+
function isSysArgv(node) {
|
|
3583
|
+
if (!node || node.name !== "MemberExpression") return false;
|
|
3584
|
+
const first = node.firstChild;
|
|
3585
|
+
if (!first || first.name !== "VariableName" || srcSlice(first) !== "sys") return false;
|
|
3586
|
+
const prop = node.getChild("PropertyName");
|
|
3587
|
+
return prop !== null && srcSlice(prop) === "argv";
|
|
3588
|
+
}
|
|
3589
|
+
function evalExpr(node, env) {
|
|
3590
|
+
if (!node) return null;
|
|
3591
|
+
const name = node.name;
|
|
3592
|
+
if (name === "String") return stringValue(srcSlice(node));
|
|
3593
|
+
if (name === "Number") return srcSlice(node);
|
|
3594
|
+
if (name === "VariableName") {
|
|
3595
|
+
const id = srcSlice(node);
|
|
3596
|
+
return id in env.vars ? env.vars[id] : null;
|
|
3597
|
+
}
|
|
3598
|
+
if (name === "MemberExpression") {
|
|
3599
|
+
const bracket = node.getChild("[");
|
|
3600
|
+
if (bracket) {
|
|
3601
|
+
const inner = node.firstChild;
|
|
3602
|
+
if (inner && isSysArgv(inner)) {
|
|
3603
|
+
const idxNode = bracket.nextSibling;
|
|
3604
|
+
if (idxNode && idxNode.name === "Number") {
|
|
3605
|
+
const idx = parseInt(srcSlice(idxNode), 10);
|
|
3606
|
+
if (idx >= 1 && idx <= env.argv.length) return env.argv[idx - 1];
|
|
3607
|
+
return null;
|
|
3608
|
+
}
|
|
3609
|
+
}
|
|
3610
|
+
return null;
|
|
3611
|
+
}
|
|
3612
|
+
if (isSysArgv(node)) return env.argv[0] ?? null;
|
|
3613
|
+
return null;
|
|
3614
|
+
}
|
|
3615
|
+
if (name === "CallExpression") {
|
|
3616
|
+
const func = node.firstChild;
|
|
3617
|
+
if (func && func.name === "VariableName" && srcSlice(func) === "len") {
|
|
3618
|
+
const argList = node.getChild("ArgList");
|
|
3619
|
+
const inner = argList ? firstNamedChild(argList) : null;
|
|
3620
|
+
if (inner && isSysArgv(inner)) return String(env.argv.length + 1);
|
|
3621
|
+
}
|
|
3622
|
+
return null;
|
|
3623
|
+
}
|
|
3624
|
+
if (name === "ConditionalExpression") {
|
|
3625
|
+
const ifKw = node.getChild("if");
|
|
3626
|
+
const elseKw = node.getChild("else");
|
|
3627
|
+
if (!ifKw || !elseKw) return null;
|
|
3628
|
+
const thenNode = node.firstChild;
|
|
3629
|
+
const condNode = ifKw.nextSibling;
|
|
3630
|
+
const orelseNode = elseKw.nextSibling;
|
|
3631
|
+
if (!thenNode || !condNode || !orelseNode) return null;
|
|
3632
|
+
const cond = evalCondition(condNode, env);
|
|
3633
|
+
if (cond === true) return evalExpr(thenNode, env);
|
|
3634
|
+
if (cond === false) return evalExpr(orelseNode, env);
|
|
3635
|
+
return null;
|
|
3636
|
+
}
|
|
3637
|
+
if (name === "BinaryExpression") {
|
|
3638
|
+
const op = findOp(node);
|
|
3639
|
+
if (op === "+") {
|
|
3640
|
+
const left = evalExpr(node.firstChild, env);
|
|
3641
|
+
const right = evalExpr(lastNamedChild(node), env);
|
|
3642
|
+
if (left !== null && right !== null) return left + right;
|
|
3643
|
+
}
|
|
3644
|
+
return null;
|
|
3645
|
+
}
|
|
3646
|
+
return null;
|
|
3195
3647
|
}
|
|
3196
|
-
function
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
depth,
|
|
3204
|
-
childPaths: [],
|
|
3205
|
-
error: "",
|
|
3206
|
-
l1Hit: false,
|
|
3207
|
-
l1RiskLevel: "",
|
|
3208
|
-
l1RiskType: "",
|
|
3209
|
-
l1Reason: "",
|
|
3210
|
-
l1MatchedPatterns: [],
|
|
3211
|
-
l2Hit: false,
|
|
3212
|
-
l2ViolatedRule: "",
|
|
3213
|
-
l2RiskLevel: "",
|
|
3214
|
-
l2RiskType: "",
|
|
3215
|
-
l2Reason: "",
|
|
3216
|
-
matchedKeywords: [],
|
|
3217
|
-
isSuspicious: false,
|
|
3218
|
-
keywordRiskLevel: "",
|
|
3219
|
-
keywordRiskReason: ""
|
|
3220
|
-
};
|
|
3648
|
+
function firstNamedChild(node) {
|
|
3649
|
+
let c = node.firstChild;
|
|
3650
|
+
while (c) {
|
|
3651
|
+
if (c.name !== "(" && c.name !== ")" && c.name !== "," && c.name !== ":") return c;
|
|
3652
|
+
c = c.nextSibling;
|
|
3653
|
+
}
|
|
3654
|
+
return null;
|
|
3221
3655
|
}
|
|
3222
|
-
function
|
|
3223
|
-
|
|
3656
|
+
function lastNamedChild(node) {
|
|
3657
|
+
let c = node.lastChild;
|
|
3658
|
+
while (c) {
|
|
3659
|
+
if (c.name !== "(" && c.name !== ")" && c.name !== "," && c.name !== ":") return c;
|
|
3660
|
+
c = c.prevSibling;
|
|
3661
|
+
}
|
|
3662
|
+
return null;
|
|
3224
3663
|
}
|
|
3225
|
-
function
|
|
3226
|
-
let
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3664
|
+
function findOp(node) {
|
|
3665
|
+
let c = node.firstChild;
|
|
3666
|
+
while (c) {
|
|
3667
|
+
if (c.name === "CompareOp" || c.name === "and" || c.name === "or" || c.name === "UpdateOp") {
|
|
3668
|
+
return srcSlice(c).trim();
|
|
3669
|
+
}
|
|
3670
|
+
c = c.nextSibling;
|
|
3671
|
+
}
|
|
3672
|
+
return null;
|
|
3231
3673
|
}
|
|
3232
|
-
function
|
|
3233
|
-
|
|
3234
|
-
const
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3674
|
+
function evalCondition(node, env) {
|
|
3675
|
+
if (!node) return null;
|
|
3676
|
+
const name = node.name;
|
|
3677
|
+
if (name === "Boolean") {
|
|
3678
|
+
const t = srcSlice(node).trim();
|
|
3679
|
+
return t === "True" ? true : t === "False" ? false : null;
|
|
3680
|
+
}
|
|
3681
|
+
if (name === "BinaryExpression") {
|
|
3682
|
+
const op = findOp(node);
|
|
3683
|
+
if (op === "and" || op === "or") {
|
|
3684
|
+
const parts2 = namedChildren(node);
|
|
3685
|
+
const vals = parts2.map((p) => evalCondition(p, env));
|
|
3686
|
+
if (op === "and") {
|
|
3687
|
+
if (vals.some((v) => v === false)) return false;
|
|
3688
|
+
if (vals.every((v) => v === true)) return true;
|
|
3689
|
+
return null;
|
|
3247
3690
|
}
|
|
3691
|
+
if (vals.some((v) => v === true)) return true;
|
|
3692
|
+
if (vals.every((v) => v === false)) return false;
|
|
3693
|
+
return null;
|
|
3248
3694
|
}
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
}
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
result = stripOutputText(result);
|
|
3269
|
-
result = expandSimpleVariables(result);
|
|
3270
|
-
return result;
|
|
3695
|
+
const parts = namedChildren(node);
|
|
3696
|
+
if (parts.length !== 3) return null;
|
|
3697
|
+
const [left, opNode, right] = parts;
|
|
3698
|
+
const opText = srcSlice(opNode).trim();
|
|
3699
|
+
return evalCompare(left, opText, right, env);
|
|
3700
|
+
}
|
|
3701
|
+
if (name === "UnaryExpression") {
|
|
3702
|
+
const kw = node.getChild("not");
|
|
3703
|
+
if (kw) {
|
|
3704
|
+
const operand = kw.nextSibling;
|
|
3705
|
+
const v = evalCondition(operand, env);
|
|
3706
|
+
return v === null ? null : !v;
|
|
3707
|
+
}
|
|
3708
|
+
return null;
|
|
3709
|
+
}
|
|
3710
|
+
if (name === "CallExpression") {
|
|
3711
|
+
return null;
|
|
3712
|
+
}
|
|
3713
|
+
return null;
|
|
3271
3714
|
}
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
{ name: "/dev/tcp", pattern: /\/dev\/tcp\// },
|
|
3281
|
-
{ name: "/dev/udp", pattern: /\/dev\/udp\// },
|
|
3282
|
-
{ name: "$(base64", pattern: /\$\(\s*base64/ },
|
|
3283
|
-
{ name: "`base64", pattern: /`\s*base64/ },
|
|
3284
|
-
{ name: "base64 decode pipe", pattern: /base64\s+(-d|--decode)\s*\|/ },
|
|
3285
|
-
{ name: "/etc/shadow", pattern: /\/etc\/shadow/ },
|
|
3286
|
-
{ name: "id_rsa", pattern: /\bid_rsa\b/ },
|
|
3287
|
-
{ name: "id_ed25519", pattern: /\bid_ed25519\b/ },
|
|
3288
|
-
{ name: "crontab install", pattern: /crontab\s+-/ },
|
|
3289
|
-
{ name: "authorized_keys", pattern: /authorized_keys/ },
|
|
3290
|
-
{ name: "ssh no host key check", pattern: /ssh\s+.*StrictHostKeyChecking\s*=\s*no/ }
|
|
3291
|
-
];
|
|
3292
|
-
var SCRIPT_EXEC_PATTERNS2 = [
|
|
3293
|
-
/(?:^|\s|&&|\|\|)\s*bash\s+(\S+)/i,
|
|
3294
|
-
/(?:^|\s|&&|\|\|)\s*\/bin\/bash\s+(\S+)/i,
|
|
3295
|
-
/(?:^|\s|&&|\|\|)\s*sh\s+(\S+)/i,
|
|
3296
|
-
/(?:^|\s|&&|\|\|)\s*zsh\s+(\S+)/i,
|
|
3297
|
-
/\.\/(\S+)/,
|
|
3298
|
-
/(?:^|\s|&&|\|\|)\s*source\s+(\S+)/i,
|
|
3299
|
-
/(?:^|\s|&&|\|\|)\s*\.\s+(\S+)/,
|
|
3300
|
-
/(?:^|\s|&&|\|\|)\s*python3?\s+(\S+\.py)/i,
|
|
3301
|
-
/(?:^|\s|&&|\|\|)\s*node\s+(\S+\.js)/i,
|
|
3302
|
-
/(?:^|\s|&&|\|\|)\s*ruby\s+(\S+\.rb)/i,
|
|
3303
|
-
/(?:^|\s|&&|\|\|)\s*perl\s+(\S+\.pl)/i,
|
|
3304
|
-
/(?:^|\s|&&|\|\|)\s*php\s+(\S+\.php)/i,
|
|
3305
|
-
/(?:^|\s|&&|\|\|)\s*lua\s+(\S+\.lua)/i
|
|
3306
|
-
];
|
|
3307
|
-
var KEYWORD_COMBINATION_RISKS2 = [
|
|
3308
|
-
{
|
|
3309
|
-
group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
|
|
3310
|
-
group2: /* @__PURE__ */ new Set(["$(base64", "`base64", "base64 decode pipe"]),
|
|
3311
|
-
level: "critical",
|
|
3312
|
-
reason: "\u68C0\u6D4B\u5230\u7F51\u7EDC\u6570\u636E\u53D1\u9001 + Base64\u7F16\u7801\u7EC4\u5408\uFF0C\u6784\u6210 Critical \u7EA7\u522B\u6570\u636E\u5916\u4F20\u98CE\u9669\u3002\u653B\u51FB\u8005\u53EF\u80FD\u5C06\u654F\u611F\u6570\u636E\u7F16\u7801\u540E\u5916\u4F20\u3002"
|
|
3313
|
-
},
|
|
3314
|
-
{
|
|
3315
|
-
group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
|
|
3316
|
-
group2: /* @__PURE__ */ new Set(["/etc/shadow", "id_rsa", "id_ed25519"]),
|
|
3317
|
-
level: "critical",
|
|
3318
|
-
reason: "\u68C0\u6D4B\u5230\u7F51\u7EDC\u6570\u636E\u53D1\u9001 + \u654F\u611F\u6587\u4EF6\u8BBF\u95EE\u7EC4\u5408\uFF0C\u6784\u6210 Critical \u7EA7\u522B\u51ED\u8BC1\u5916\u4F20\u98CE\u9669\u3002"
|
|
3319
|
-
},
|
|
3320
|
-
{
|
|
3321
|
-
group1: /* @__PURE__ */ new Set(["nc -e", "ncat -e", "/dev/tcp", "/dev/udp"]),
|
|
3322
|
-
group2: /* @__PURE__ */ new Set(),
|
|
3323
|
-
level: "high",
|
|
3324
|
-
reason: "\u68C0\u6D4B\u5230\u53CD\u5F39 Shell \u7279\u5F81\uFF0C\u653B\u51FB\u8005\u53EF\u80FD\u5EFA\u7ACB\u8FDC\u7A0B\u63A7\u5236\u901A\u9053\u3002"
|
|
3325
|
-
},
|
|
3326
|
-
{
|
|
3327
|
-
group1: /* @__PURE__ */ new Set(["crontab install", "authorized_keys", "ssh no host key check"]),
|
|
3328
|
-
group2: /* @__PURE__ */ new Set(),
|
|
3329
|
-
level: "high",
|
|
3330
|
-
reason: "\u68C0\u6D4B\u5230\u6301\u4E45\u5316\u540E\u95E8\u7279\u5F81\uFF0C\u653B\u51FB\u8005\u53EF\u80FD\u5728\u7CFB\u7EDF\u4E2D\u690D\u5165\u540E\u95E8\u3002"
|
|
3331
|
-
}
|
|
3332
|
-
];
|
|
3333
|
-
var SINGLE_KEYWORD_RISKS2 = {
|
|
3334
|
-
"curl POST": { level: "high", reason: "\u68C0\u6D4B\u5230 curl POST \u8BF7\u6C42\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
|
|
3335
|
-
"curl --data": { level: "high", reason: "\u68C0\u6D4B\u5230 curl --data \u53C2\u6570\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
|
|
3336
|
-
"curl -d": { level: "high", reason: "\u68C0\u6D4B\u5230 curl -d \u53C2\u6570\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
|
|
3337
|
-
"wget --post-data": { level: "high", reason: "\u68C0\u6D4B\u5230 wget POST \u8BF7\u6C42\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
|
|
3338
|
-
"wget --post-file": { level: "high", reason: "\u68C0\u6D4B\u5230 wget \u6587\u4EF6\u4E0A\u4F20\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6587\u4EF6\u3002" },
|
|
3339
|
-
"nc -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 nc -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
|
|
3340
|
-
"ncat -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 ncat -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
|
|
3341
|
-
"/dev/tcp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/tcp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
|
|
3342
|
-
"/dev/udp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/udp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
|
|
3343
|
-
"$(base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
|
|
3344
|
-
"`base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
|
|
3345
|
-
"base64 decode pipe": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u89E3\u7801\u540E\u7BA1\u9053\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
|
|
3346
|
-
"/etc/shadow": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6\u3002" },
|
|
3347
|
-
"id_rsa": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
|
|
3348
|
-
"id_ed25519": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
|
|
3349
|
-
"crontab install": { level: "high", reason: "\u68C0\u6D4B\u5230\u4FEE\u6539\u5B9A\u65F6\u4EFB\u52A1\uFF0C\u53EF\u80FD\u662F\u6301\u4E45\u5316\u540E\u95E8\u3002" },
|
|
3350
|
-
"authorized_keys": { level: "high", reason: "\u68C0\u6D4B\u5230\u4FEE\u6539 SSH \u6388\u6743\u5BC6\u94A5\uFF0C\u53EF\u80FD\u662F\u6301\u4E45\u5316\u540E\u95E8\u3002" },
|
|
3351
|
-
"ssh no host key check": { level: "high", reason: "\u68C0\u6D4B\u5230 SSH \u8DF3\u8FC7\u4E3B\u673A\u5BC6\u94A5\u9A8C\u8BC1\uFF0C\u53EF\u80FD\u662F\u6A2A\u5411\u79FB\u52A8\u3002" }
|
|
3352
|
-
};
|
|
3353
|
-
var LEVEL_ORDER2 = { low: 1, medium: 2, high: 3, critical: 4 };
|
|
3354
|
-
function scanScriptForKeywords2(content) {
|
|
3355
|
-
const matched = [];
|
|
3356
|
-
for (const { name, pattern } of SUSPICIOUS_PATTERNS2) {
|
|
3357
|
-
if (pattern.test(content)) matched.push(name);
|
|
3715
|
+
function namedChildren(node) {
|
|
3716
|
+
const out = [];
|
|
3717
|
+
let c = node.firstChild;
|
|
3718
|
+
while (c) {
|
|
3719
|
+
if (c.name !== "(" && c.name !== ")" && c.name !== "," && c.name !== ":" && c.name !== "[" && c.name !== "]") {
|
|
3720
|
+
out.push(c);
|
|
3721
|
+
}
|
|
3722
|
+
c = c.nextSibling;
|
|
3358
3723
|
}
|
|
3359
|
-
return
|
|
3724
|
+
return out;
|
|
3360
3725
|
}
|
|
3361
|
-
function
|
|
3362
|
-
if (
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
const hitGroup2 = rule.group2.size === 0 ? true : [...rule.group2].some((k) => keywordSet.has(k));
|
|
3367
|
-
if (hitGroup1 && hitGroup2) return { level: rule.level, reason: rule.reason };
|
|
3726
|
+
function evalCompare(left, opText, right, env) {
|
|
3727
|
+
if (opText === "==" && left.name === "VariableName" && srcSlice(left) === "__name__") {
|
|
3728
|
+
const rv = evalExpr(right, env);
|
|
3729
|
+
if (rv !== null) return rv === "__main__";
|
|
3730
|
+
return null;
|
|
3368
3731
|
}
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
for (const p of envPatterns) {
|
|
3375
|
-
if (contentLower.includes(p.toLowerCase())) {
|
|
3376
|
-
return {
|
|
3377
|
-
level: "critical",
|
|
3378
|
-
reason: "\u68C0\u6D4B\u5230\u7F51\u7EDC\u6570\u636E\u53D1\u9001 + \u7CFB\u7EDF\u73AF\u5883\u53D8\u91CF\u6536\u96C6\u7EC4\u5408\uFF0C\u6784\u6210 Critical \u7EA7\u522B\u6570\u636E\u5916\u4F20\u98CE\u9669\u3002\u811A\u672C\u53EF\u80FD\u5C06\u7528\u6237\u73AF\u5883\u53D8\u91CF\uFF08\u53EF\u80FD\u5305\u542B API Key\u3001\u5BC6\u94A5\u7B49\u654F\u611F\u4FE1\u606F\uFF09\u53D1\u9001\u5230\u8FDC\u7A0B\u670D\u52A1\u5668\u3002"
|
|
3379
|
-
};
|
|
3380
|
-
}
|
|
3732
|
+
if (opText === "==" || opText === "!=") {
|
|
3733
|
+
const lv = evalExpr(left, env);
|
|
3734
|
+
const rv = evalExpr(right, env);
|
|
3735
|
+
if (lv !== null && rv !== null) {
|
|
3736
|
+
return opText === "==" ? lv === rv : lv !== rv;
|
|
3381
3737
|
}
|
|
3738
|
+
return null;
|
|
3382
3739
|
}
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
const
|
|
3387
|
-
if (
|
|
3388
|
-
|
|
3389
|
-
|
|
3740
|
+
if (opText === "in") {
|
|
3741
|
+
const lv = evalExpr(left, env);
|
|
3742
|
+
if (lv === null) return null;
|
|
3743
|
+
const rightName = right.name;
|
|
3744
|
+
if (rightName === "TupleExpression" || rightName === "ArrayExpression" || rightName === "SetExpression" || rightName === "ParenthesizedExpression") {
|
|
3745
|
+
const container = rightName === "ParenthesizedExpression" && right.firstChild && right.firstChild.name === "TupleExpression" ? right.firstChild : right;
|
|
3746
|
+
for (const elt of namedChildren(container)) {
|
|
3747
|
+
const ev = evalExpr(elt, env);
|
|
3748
|
+
if (ev !== null && ev === lv) return true;
|
|
3749
|
+
}
|
|
3750
|
+
return false;
|
|
3390
3751
|
}
|
|
3752
|
+
const rv = evalExpr(right, env);
|
|
3753
|
+
if (rv !== null) return rv.includes(lv);
|
|
3754
|
+
return null;
|
|
3391
3755
|
}
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
const
|
|
3397
|
-
|
|
3756
|
+
if (opText === ">" || opText === ">=" || opText === "<" || opText === "<=") {
|
|
3757
|
+
const lv = evalExpr(left, env);
|
|
3758
|
+
const rv = evalExpr(right, env);
|
|
3759
|
+
if (lv === null || rv === null) return null;
|
|
3760
|
+
const ln = parseInt(lv, 10);
|
|
3761
|
+
const rn = parseInt(rv, 10);
|
|
3762
|
+
if (Number.isNaN(ln) || Number.isNaN(rn)) return null;
|
|
3763
|
+
if (opText === ">") return ln > rn;
|
|
3764
|
+
if (opText === ">=") return ln >= rn;
|
|
3765
|
+
if (opText === "<") return ln < rn;
|
|
3766
|
+
return ln <= rn;
|
|
3398
3767
|
}
|
|
3399
3768
|
return null;
|
|
3400
3769
|
}
|
|
3401
|
-
function
|
|
3402
|
-
const
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
} catch {
|
|
3770
|
+
function processStmts(stmts, env, lineStarts, lines, branches) {
|
|
3771
|
+
for (const s of stmts) processStmt(s, env, lineStarts, lines, branches);
|
|
3772
|
+
}
|
|
3773
|
+
function invalidateEnvOnCalls(node, env) {
|
|
3774
|
+
const cursor = node.cursor();
|
|
3775
|
+
cursor.iterate((n) => {
|
|
3776
|
+
const nd = n.node;
|
|
3777
|
+
if (nd.name !== "CallExpression") return true;
|
|
3778
|
+
const func = nd.firstChild;
|
|
3779
|
+
if (func && func.name === "VariableName" && !PURE_BUILTINS.has(srcSlice(func))) {
|
|
3780
|
+
for (const k of Object.keys(env.vars)) env.vars[k] = null;
|
|
3781
|
+
return false;
|
|
3414
3782
|
}
|
|
3415
|
-
|
|
3416
|
-
|
|
3783
|
+
return true;
|
|
3784
|
+
});
|
|
3417
3785
|
}
|
|
3418
|
-
function
|
|
3419
|
-
|
|
3420
|
-
if (
|
|
3421
|
-
|
|
3786
|
+
function processStmt(stmt, env, lineStarts, lines, branches) {
|
|
3787
|
+
const name = stmt.name;
|
|
3788
|
+
if (name === "AssignStatement") {
|
|
3789
|
+
const target = stmt.firstChild;
|
|
3790
|
+
if (target && target.name === "VariableName") {
|
|
3791
|
+
const valNode = target.nextSibling ? target.nextSibling.nextSibling : null;
|
|
3792
|
+
invalidateEnvOnCalls(stmt, env);
|
|
3793
|
+
const val = evalExpr(valNode, env);
|
|
3794
|
+
env.vars[srcSlice(target)] = val;
|
|
3795
|
+
}
|
|
3796
|
+
markNodeLines(stmt, lines, lineStarts);
|
|
3797
|
+
return;
|
|
3422
3798
|
}
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3799
|
+
if (name === "UpdateStatement") {
|
|
3800
|
+
const target = stmt.firstChild;
|
|
3801
|
+
if (target && target.name === "VariableName") env.vars[srcSlice(target)] = null;
|
|
3802
|
+
invalidateEnvOnCalls(stmt, env);
|
|
3803
|
+
markNodeLines(stmt, lines, lineStarts);
|
|
3804
|
+
return;
|
|
3805
|
+
}
|
|
3806
|
+
if (name === "IfStatement") {
|
|
3807
|
+
processIf(stmt, env, lineStarts, lines, branches);
|
|
3808
|
+
return;
|
|
3809
|
+
}
|
|
3810
|
+
if (name === "ForStatement" || name === "WhileStatement") {
|
|
3811
|
+
markNodeLines(stmt, lines, lineStarts);
|
|
3812
|
+
for (const body of stmt.getChildren("Body")) {
|
|
3813
|
+
processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
|
|
3433
3814
|
}
|
|
3434
|
-
|
|
3435
|
-
const lineCount = content ? content.split("\n").length : 0;
|
|
3436
|
-
return { content, lineCount, fileSize: stat.size, error: "" };
|
|
3437
|
-
} catch (e) {
|
|
3438
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
3439
|
-
return { content: "", lineCount: 0, fileSize: 0, error: `\u8BFB\u53D6\u811A\u672C\u5931\u8D25: ${msg}` };
|
|
3815
|
+
return;
|
|
3440
3816
|
}
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
pattern.lastIndex = 0;
|
|
3446
|
-
let m;
|
|
3447
|
-
while ((m = pattern.exec(content)) !== null) {
|
|
3448
|
-
const pathStr = m[1];
|
|
3449
|
-
if (pathStr.startsWith("-") || pathStr.startsWith("$")) continue;
|
|
3450
|
-
if (pathStr.includes("/dev/") || pathStr.startsWith("|") || pathStr.startsWith(";")) continue;
|
|
3451
|
-
const full = isAbsolute(pathStr) ? pathStr : join4(baseDir, pathStr);
|
|
3452
|
-
const resolved = resolvePath(full);
|
|
3453
|
-
if (existsSync7(resolved)) {
|
|
3454
|
-
try {
|
|
3455
|
-
if (statSync(resolved).isFile() && !paths.includes(resolved)) paths.push(resolved);
|
|
3456
|
-
} catch {
|
|
3457
|
-
}
|
|
3458
|
-
}
|
|
3817
|
+
if (name === "TryStatement") {
|
|
3818
|
+
markNodeLines(stmt, lines, lineStarts);
|
|
3819
|
+
for (const body of stmt.getChildren("Body")) {
|
|
3820
|
+
processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
|
|
3459
3821
|
}
|
|
3822
|
+
return;
|
|
3460
3823
|
}
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
const result = emptyChain();
|
|
3466
|
-
result.entryPath = entryPath;
|
|
3467
|
-
const entry = resolvePath(entryPath);
|
|
3468
|
-
const baseDir = existsSync7(entry) ? dirname6(entry) : input.cwd || process.cwd();
|
|
3469
|
-
resolveRecursive({
|
|
3470
|
-
path: entry,
|
|
3471
|
-
depth: 0,
|
|
3472
|
-
maxDepth,
|
|
3473
|
-
maxFileSize,
|
|
3474
|
-
maxTotalSize,
|
|
3475
|
-
visited,
|
|
3476
|
-
result,
|
|
3477
|
-
baseDir,
|
|
3478
|
-
input
|
|
3479
|
-
});
|
|
3480
|
-
result.hasCritical = result.nodes.some((n) => n.l1RiskLevel === "critical" || n.l2RiskLevel === "critical" || n.keywordRiskLevel === "critical");
|
|
3481
|
-
result.hasHigh = result.nodes.some((n) => ["high", "critical"].includes(n.l1RiskLevel) || ["high", "critical"].includes(n.l2RiskLevel) || ["high", "critical"].includes(n.keywordRiskLevel));
|
|
3482
|
-
result.firstHitNode = result.nodes.find((n) => n.l1Hit || n.l2Hit || n.isSuspicious) || null;
|
|
3483
|
-
return result;
|
|
3484
|
-
}
|
|
3485
|
-
function resolveRecursive(ctx) {
|
|
3486
|
-
const { path: path3, depth, maxDepth, maxFileSize, maxTotalSize, visited, result, baseDir, input } = ctx;
|
|
3487
|
-
if (visited.has(path3)) return;
|
|
3488
|
-
if (depth > maxDepth) {
|
|
3489
|
-
result.maxDepthReached = true;
|
|
3824
|
+
if (name === "WithStatement") {
|
|
3825
|
+
markNodeLines(stmt, lines, lineStarts);
|
|
3826
|
+
const body = stmt.getChild("Body");
|
|
3827
|
+
if (body) processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
|
|
3490
3828
|
return;
|
|
3491
3829
|
}
|
|
3492
|
-
if (
|
|
3493
|
-
|
|
3830
|
+
if (name === "MatchStatement") {
|
|
3831
|
+
markNodeLines(stmt, lines, lineStarts);
|
|
3832
|
+
for (const clause of stmt.getChildren("MatchClause")) {
|
|
3833
|
+
const body = clause.getChild("Body");
|
|
3834
|
+
if (body) processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
|
|
3835
|
+
}
|
|
3494
3836
|
return;
|
|
3495
3837
|
}
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
if (error) {
|
|
3499
|
-
const node2 = newNode(path3, depth);
|
|
3500
|
-
node2.error = error;
|
|
3501
|
-
node2.fileSize = fileSize;
|
|
3502
|
-
node2.lineCount = lineCount;
|
|
3503
|
-
result.nodes.push(node2);
|
|
3838
|
+
if (name === "FunctionDefinition" || name === "ClassDefinition") {
|
|
3839
|
+
markDefTree(stmt, lines, lineStarts);
|
|
3504
3840
|
return;
|
|
3505
3841
|
}
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
const preprocessed = preprocessScriptContent(content);
|
|
3509
|
-
const node = newNode(path3, depth);
|
|
3510
|
-
node.rawContent = content;
|
|
3511
|
-
node.preprocessedContent = preprocessed;
|
|
3512
|
-
node.lineCount = lineCount;
|
|
3513
|
-
node.fileSize = fileSize;
|
|
3514
|
-
result.nodes.push(node);
|
|
3515
|
-
const virtualInput = {
|
|
3516
|
-
...input,
|
|
3517
|
-
aNext: { actionType: "bash", actionDetail: preprocessed }
|
|
3518
|
-
};
|
|
3519
|
-
const l1 = runHeuristicCheck(virtualInput);
|
|
3520
|
-
if (l1 && !l1.allowed) {
|
|
3521
|
-
node.l1Hit = true;
|
|
3522
|
-
node.l1RiskLevel = l1.riskLevel;
|
|
3523
|
-
node.l1RiskType = l1.riskType;
|
|
3524
|
-
node.l1Reason = l1.reason;
|
|
3525
|
-
node.l1MatchedPatterns = [];
|
|
3526
|
-
if (l1.riskLevel === "critical") {
|
|
3527
|
-
result.hasCritical = true;
|
|
3528
|
-
result.firstHitNode = node;
|
|
3529
|
-
return;
|
|
3530
|
-
}
|
|
3842
|
+
if (name === "ExpressionStatement") {
|
|
3843
|
+
invalidateEnvOnCalls(stmt, env);
|
|
3531
3844
|
}
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3845
|
+
markNodeLines(stmt, lines, lineStarts);
|
|
3846
|
+
}
|
|
3847
|
+
function markDefTree(stmt, lines, lineStarts) {
|
|
3848
|
+
const cursor = stmt.cursor();
|
|
3849
|
+
cursor.iterate((n) => {
|
|
3850
|
+
markNodeLines(n.node, lines, lineStarts);
|
|
3851
|
+
return true;
|
|
3852
|
+
});
|
|
3853
|
+
}
|
|
3854
|
+
function bodyStmts(body) {
|
|
3855
|
+
const out = [];
|
|
3856
|
+
let c = body.firstChild;
|
|
3857
|
+
while (c) {
|
|
3858
|
+
if (c.name !== ":" && c.name !== ";") out.push(c);
|
|
3859
|
+
c = c.nextSibling;
|
|
3547
3860
|
}
|
|
3861
|
+
return out;
|
|
3548
3862
|
}
|
|
3549
|
-
function
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
}
|
|
3556
|
-
|
|
3557
|
-
if (
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
node.l1RiskType = l1.riskType;
|
|
3561
|
-
node.l1Reason = l1.reason;
|
|
3562
|
-
if (l1.riskLevel === "critical") {
|
|
3563
|
-
chain.hasCritical = true;
|
|
3564
|
-
chain.firstHitNode = node;
|
|
3565
|
-
break;
|
|
3863
|
+
function processIf(stmt, env, lineStarts, lines, branches) {
|
|
3864
|
+
const sections = [];
|
|
3865
|
+
let c = stmt.firstChild;
|
|
3866
|
+
while (c) {
|
|
3867
|
+
if (c.name === "if" || c.name === "elif") {
|
|
3868
|
+
sections.push({ cond: c.nextSibling ?? null, body: null, isElse: false });
|
|
3869
|
+
} else if (c.name === "else") {
|
|
3870
|
+
sections.push({ cond: null, body: null, isElse: true });
|
|
3871
|
+
} else if (c.name === "Body") {
|
|
3872
|
+
if (sections.length > 0 && sections[sections.length - 1].body === null) {
|
|
3873
|
+
sections[sections.length - 1].body = c;
|
|
3566
3874
|
}
|
|
3567
3875
|
}
|
|
3876
|
+
c = c.nextSibling;
|
|
3568
3877
|
}
|
|
3569
|
-
|
|
3878
|
+
processIfSections(stmt, sections, 0, env, lineStarts, lines, branches);
|
|
3570
3879
|
}
|
|
3571
|
-
function
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
};
|
|
3579
|
-
const l2 = runLogicCheck(virtualInput);
|
|
3580
|
-
if (l2 && !l2.allowed) {
|
|
3581
|
-
node.l2Hit = true;
|
|
3582
|
-
node.l2ViolatedRule = `script_logic:${node.scriptPath}`;
|
|
3583
|
-
node.l2RiskLevel = l2.riskLevel;
|
|
3584
|
-
node.l2RiskType = l2.riskType;
|
|
3585
|
-
node.l2Reason = `\u811A\u672C ${node.scriptPath} (\u6DF1\u5EA6 ${node.depth}) \u4E2D: ${l2.reason}`;
|
|
3586
|
-
if (l2.riskLevel === "critical") {
|
|
3587
|
-
chain.hasCritical = true;
|
|
3588
|
-
chain.firstHitNode = node;
|
|
3589
|
-
break;
|
|
3590
|
-
}
|
|
3591
|
-
}
|
|
3880
|
+
function processIfSections(_stmt, sections, idx, env, lineStarts, lines, branches) {
|
|
3881
|
+
if (idx >= sections.length) return;
|
|
3882
|
+
const s = sections[idx];
|
|
3883
|
+
if (s.cond) markNodeLines(s.cond, lines, lineStarts);
|
|
3884
|
+
if (s.isElse) {
|
|
3885
|
+
if (s.body) processStmts(bodyStmts(s.body), env, lineStarts, lines, branches);
|
|
3886
|
+
return;
|
|
3592
3887
|
}
|
|
3593
|
-
|
|
3594
|
-
|
|
3888
|
+
const cond = s.cond ? evalCondition(s.cond, env) : null;
|
|
3889
|
+
branches.push({
|
|
3890
|
+
kind: "if",
|
|
3891
|
+
label: s.cond ? srcSlice(s.cond) : "",
|
|
3892
|
+
body: "",
|
|
3893
|
+
reachable: cond !== false
|
|
3894
|
+
});
|
|
3895
|
+
if (cond === true) {
|
|
3896
|
+
if (s.body) processStmts(bodyStmts(s.body), env, lineStarts, lines, branches);
|
|
3897
|
+
return;
|
|
3595
3898
|
}
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
for (const node of chain.nodes) {
|
|
3600
|
-
if (node.error || !node.preprocessedContent) continue;
|
|
3601
|
-
if (node.l1Hit || node.l2Hit) continue;
|
|
3602
|
-
const matched = scanScriptForKeywords2(node.preprocessedContent);
|
|
3603
|
-
node.matchedKeywords = matched;
|
|
3604
|
-
node.isSuspicious = matched.length > 0;
|
|
3605
|
-
if (matched.length > 0) {
|
|
3606
|
-
const risk = assessKeywordCombinationRisk2(matched, node.preprocessedContent);
|
|
3607
|
-
node.keywordRiskLevel = risk.level;
|
|
3608
|
-
node.keywordRiskReason = risk.reason;
|
|
3609
|
-
}
|
|
3899
|
+
if (cond === false) {
|
|
3900
|
+
processIfSections(_stmt, sections, idx + 1, env, lineStarts, lines, branches);
|
|
3901
|
+
return;
|
|
3610
3902
|
}
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
const scriptPath = extractScriptPath2(input.aNext.actionDetail);
|
|
3617
|
-
let resolvedPath = scriptPath;
|
|
3618
|
-
if (!resolvedPath) {
|
|
3619
|
-
const inline = extractInlineScriptPath(input.aNext.actionDetail);
|
|
3620
|
-
if (!inline) return emptyChain();
|
|
3621
|
-
resolvedPath = inline;
|
|
3903
|
+
if (s.body) processStmts(bodyStmts(s.body), env, lineStarts, lines, branches);
|
|
3904
|
+
for (let j = idx + 1; j < sections.length; j++) {
|
|
3905
|
+
const sj = sections[j];
|
|
3906
|
+
if (sj.cond) markNodeLines(sj.cond, lines, lineStarts);
|
|
3907
|
+
if (sj.body) processStmts(bodyStmts(sj.body), env, lineStarts, lines, branches);
|
|
3622
3908
|
}
|
|
3623
|
-
const chain = resolveScriptChain(resolvedPath, input);
|
|
3624
|
-
if (chain.nodes.length === 0) return chain;
|
|
3625
|
-
scanScriptChainWithL1(chain, input);
|
|
3626
|
-
scanScriptChainWithL2(chain, input);
|
|
3627
|
-
scanScriptChainKeywords(chain);
|
|
3628
|
-
return chain;
|
|
3629
3909
|
}
|
|
3630
|
-
function
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
const
|
|
3641
|
-
|
|
3642
|
-
const pre = node.preprocessedContent || "";
|
|
3643
|
-
let preview = pre.slice(0, 600);
|
|
3644
|
-
if (pre.length > 600) preview += `
|
|
3645
|
-
... (\u622A\u65AD\uFF0C\u5B8C\u6574\u5185\u5BB9 ${pre.length} \u5B57\u7B26)`;
|
|
3646
|
-
parts2.push(
|
|
3647
|
-
` \u811A\u672C\u8DEF\u5F84: ${node.scriptPath}\uFF08\u9012\u5F52\u6DF1\u5EA6 ${node.depth}\uFF0C${node.lineCount} \u884C\uFF09
|
|
3648
|
-
\u5185\u5BB9\uFF08\u9884\u5904\u7406\u540E\uFF09:
|
|
3649
|
-
\`\`\`
|
|
3650
|
-
${preview}
|
|
3651
|
-
\`\`\``
|
|
3652
|
-
);
|
|
3910
|
+
function parseShellString(s) {
|
|
3911
|
+
CHILD_CALL_RE.lastIndex = 0;
|
|
3912
|
+
let m;
|
|
3913
|
+
while ((m = CHILD_CALL_RE.exec(s)) !== null) {
|
|
3914
|
+
const interp = m[1];
|
|
3915
|
+
const path5 = m[2].trim();
|
|
3916
|
+
let rest = m[3].trim();
|
|
3917
|
+
if (path5.startsWith("-") || path5.startsWith("$") || path5.includes("/dev/")) continue;
|
|
3918
|
+
rest = rest.split(/[;|&<>]/)[0].trim();
|
|
3919
|
+
const args = [];
|
|
3920
|
+
for (const tm of rest.matchAll(/(["'])(.*?)\1|(\S+)/g)) {
|
|
3921
|
+
args.push(tm[1] ? tm[2] : tm[3]);
|
|
3653
3922
|
}
|
|
3654
|
-
return
|
|
3923
|
+
return { path: path5, args, interpreter: interp };
|
|
3655
3924
|
}
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3925
|
+
return null;
|
|
3926
|
+
}
|
|
3927
|
+
function extractCallsFromTree(tree, reachableLines) {
|
|
3928
|
+
const calls = [];
|
|
3929
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3930
|
+
const add = (c) => {
|
|
3931
|
+
const key = `${c.path}\0${c.args.join("\0")}\0${c.interpreter}`;
|
|
3932
|
+
if (!seen.has(key)) {
|
|
3933
|
+
seen.add(key);
|
|
3934
|
+
calls.push(c);
|
|
3666
3935
|
}
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
if (
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3936
|
+
};
|
|
3937
|
+
const lineReachable = (node) => {
|
|
3938
|
+
if (reachableLines === null) return true;
|
|
3939
|
+
return reachableLines.has(lineAt(buildLineStarts(SOURCE), node.from));
|
|
3940
|
+
};
|
|
3941
|
+
const cursor = tree.cursor();
|
|
3942
|
+
cursor.iterate((n) => {
|
|
3943
|
+
const node = n.node;
|
|
3944
|
+
if (node.name !== "CallExpression") return true;
|
|
3945
|
+
if (!lineReachable(node)) return true;
|
|
3946
|
+
const func = node.firstChild;
|
|
3947
|
+
if (!func || func.name !== "MemberExpression") return true;
|
|
3948
|
+
const modName = func.firstChild;
|
|
3949
|
+
if (!modName || modName.name !== "VariableName") return true;
|
|
3950
|
+
const funcNameNode = func.getChild("PropertyName");
|
|
3951
|
+
if (!funcNameNode) return true;
|
|
3952
|
+
const mod = srcSlice(modName);
|
|
3953
|
+
const funcName = srcSlice(funcNameNode);
|
|
3954
|
+
const argList = node.getChild("ArgList");
|
|
3955
|
+
const firstArg = argList ? firstNamedChild(argList) : null;
|
|
3956
|
+
if (mod === "subprocess" && SUBPROCESS_FUNCS.has(funcName)) {
|
|
3957
|
+
if (firstArg && (firstArg.name === "ArrayExpression" || firstArg.name === "TupleExpression")) {
|
|
3958
|
+
const elts = [];
|
|
3959
|
+
let allStr = true;
|
|
3960
|
+
for (const elt of namedChildren(firstArg)) {
|
|
3961
|
+
if (elt.name === "String") elts.push(stringValue(srcSlice(elt)));
|
|
3962
|
+
else {
|
|
3963
|
+
allStr = false;
|
|
3964
|
+
break;
|
|
3965
|
+
}
|
|
3966
|
+
}
|
|
3967
|
+
if (allStr && elts.length >= 2) {
|
|
3968
|
+
const interp = elts[0];
|
|
3969
|
+
const path5 = elts[1];
|
|
3970
|
+
if (!path5.startsWith("-") && !path5.startsWith("$") && !path5.includes("/dev/")) {
|
|
3971
|
+
add({ path: path5, args: elts.slice(2), interpreter: interp });
|
|
3972
|
+
return true;
|
|
3973
|
+
}
|
|
3974
|
+
}
|
|
3975
|
+
}
|
|
3976
|
+
if (firstArg && firstArg.name === "String") {
|
|
3977
|
+
const sc = parseShellString(stringValue(srcSlice(firstArg)));
|
|
3978
|
+
if (sc) add(sc);
|
|
3979
|
+
}
|
|
3980
|
+
} else if (mod === "os" && OS_FUNCS.has(funcName)) {
|
|
3981
|
+
if (firstArg && firstArg.name === "String") {
|
|
3982
|
+
const sc = parseShellString(stringValue(srcSlice(firstArg)));
|
|
3983
|
+
if (sc) add(sc);
|
|
3984
|
+
}
|
|
3985
|
+
}
|
|
3986
|
+
return true;
|
|
3987
|
+
});
|
|
3988
|
+
const cursor2 = tree.cursor();
|
|
3989
|
+
cursor2.iterate((n) => {
|
|
3990
|
+
const node = n.node;
|
|
3991
|
+
if (node.name !== "ImportStatement") return true;
|
|
3992
|
+
const txt = srcSlice(node);
|
|
3993
|
+
if (!/^from\s+\./m.test(txt)) return true;
|
|
3994
|
+
const m = txt.match(/\bimport\s+(.+)$/m);
|
|
3995
|
+
if (m) {
|
|
3996
|
+
for (const part of m[1].split(",")) {
|
|
3997
|
+
const name = part.trim().replace(/\s+as\s+\w+$/, "");
|
|
3998
|
+
if (name && name !== "*") add({ path: name.replace(/\./g, "/") + ".py", args: [], interpreter: "python3" });
|
|
3999
|
+
}
|
|
4000
|
+
}
|
|
4001
|
+
return true;
|
|
4002
|
+
});
|
|
4003
|
+
return calls;
|
|
4004
|
+
}
|
|
4005
|
+
function extractCallsRegex(content) {
|
|
4006
|
+
const calls = [];
|
|
4007
|
+
CHILD_CALL_RE.lastIndex = 0;
|
|
4008
|
+
let m;
|
|
4009
|
+
while ((m = CHILD_CALL_RE.exec(content)) !== null) {
|
|
4010
|
+
const interp = m[1];
|
|
4011
|
+
const path5 = m[2].trim();
|
|
4012
|
+
let rest = m[3].trim();
|
|
4013
|
+
if (path5.startsWith("-") || path5.startsWith("$") || path5.includes("/dev/")) continue;
|
|
4014
|
+
rest = rest.split(/[;|&<>]/)[0].trim();
|
|
4015
|
+
const args = [];
|
|
4016
|
+
for (const tm of rest.matchAll(/(["'])(.*?)\1|(\S+)/g)) {
|
|
4017
|
+
args.push(tm[1] ? tm[2] : tm[3]);
|
|
4018
|
+
}
|
|
4019
|
+
calls.push({ path: path5, args, interpreter: interp });
|
|
4020
|
+
}
|
|
4021
|
+
return calls;
|
|
4022
|
+
}
|
|
4023
|
+
var PythonBackend = class {
|
|
4024
|
+
language = "python";
|
|
4025
|
+
parse(content, entryArgs) {
|
|
4026
|
+
const c = content || "";
|
|
4027
|
+
SOURCE = c;
|
|
4028
|
+
const sourceLines = c.split("\n");
|
|
4029
|
+
const lineStarts = buildLineStarts(c);
|
|
4030
|
+
const env = makeEnv(entryArgs || []);
|
|
4031
|
+
const lines = /* @__PURE__ */ new Set();
|
|
4032
|
+
const branches = [];
|
|
4033
|
+
if (sourceLines.length > MAX_LINES) {
|
|
4034
|
+
return {
|
|
4035
|
+
reachableContent: c,
|
|
4036
|
+
controlFlowResolved: false,
|
|
4037
|
+
calls: extractCallsRegex(c),
|
|
4038
|
+
branches: [],
|
|
4039
|
+
unresolvedSnippets: [],
|
|
4040
|
+
language: this.language
|
|
4041
|
+
};
|
|
4042
|
+
}
|
|
4043
|
+
let tree;
|
|
4044
|
+
try {
|
|
4045
|
+
tree = parser.parse(c).topNode;
|
|
4046
|
+
} catch {
|
|
4047
|
+
return {
|
|
4048
|
+
reachableContent: c,
|
|
4049
|
+
controlFlowResolved: false,
|
|
4050
|
+
calls: extractCallsRegex(c),
|
|
4051
|
+
branches: [],
|
|
4052
|
+
unresolvedSnippets: [],
|
|
4053
|
+
language: this.language
|
|
4054
|
+
};
|
|
4055
|
+
}
|
|
4056
|
+
const stmts = bodyStmts(tree);
|
|
4057
|
+
processStmts(stmts, env, lineStarts, lines, branches);
|
|
4058
|
+
const reachable = reconstruct(sourceLines, lines);
|
|
4059
|
+
let calls = extractCallsFromTree(tree, lines);
|
|
4060
|
+
if (calls.length === 0) calls = extractCallsRegex(c);
|
|
4061
|
+
const srcNonblank = sourceLines.filter((l) => l.trim().length > 0);
|
|
4062
|
+
const rcNonblank = reachable.split("\n").filter((l) => l.trim().length > 0);
|
|
4063
|
+
const resolved = rcNonblank.length !== srcNonblank.length || rcNonblank.some((l, i) => l !== srcNonblank[i]);
|
|
4064
|
+
return {
|
|
4065
|
+
reachableContent: reachable,
|
|
4066
|
+
controlFlowResolved: resolved,
|
|
4067
|
+
calls,
|
|
4068
|
+
branches,
|
|
4069
|
+
unresolvedSnippets: [],
|
|
4070
|
+
language: this.language
|
|
4071
|
+
};
|
|
4072
|
+
}
|
|
4073
|
+
};
|
|
3677
4074
|
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
4075
|
+
// src/engine/script-engine/backends/index.ts
|
|
4076
|
+
init_esm_shims();
|
|
4077
|
+
|
|
4078
|
+
// src/engine/script-engine/backends/shell-backend.ts
|
|
4079
|
+
init_esm_shims();
|
|
4080
|
+
|
|
4081
|
+
// src/engine/script-engine/symbolic.ts
|
|
4082
|
+
init_esm_shims();
|
|
4083
|
+
function reescape(s) {
|
|
4084
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4085
|
+
}
|
|
4086
|
+
var RE_DOTALL = "s";
|
|
4087
|
+
var MAX_LINES2 = 500;
|
|
4088
|
+
var MAX_BRANCHES = 50;
|
|
4089
|
+
var MAX_VARS = 200;
|
|
4090
|
+
function svalConc(v) {
|
|
4091
|
+
return { kind: "concrete", value: v, children: [], op: "" };
|
|
4092
|
+
}
|
|
4093
|
+
function svalSym(name) {
|
|
4094
|
+
return { kind: "symbol", value: name, children: [], op: "" };
|
|
4095
|
+
}
|
|
4096
|
+
function svalExpr(children) {
|
|
4097
|
+
return { kind: "expr", value: null, children, op: "+" };
|
|
4098
|
+
}
|
|
4099
|
+
function fold(val) {
|
|
4100
|
+
if (val.kind !== "expr") return val;
|
|
4101
|
+
const parts = [];
|
|
4102
|
+
for (const c of val.children) {
|
|
4103
|
+
const cc = fold(c);
|
|
4104
|
+
if (cc.kind === "concrete") {
|
|
4105
|
+
parts.push(cc.value || "");
|
|
4106
|
+
} else {
|
|
4107
|
+
return cc;
|
|
4108
|
+
}
|
|
3684
4109
|
}
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
4110
|
+
return svalConc(parts.join(""));
|
|
4111
|
+
}
|
|
4112
|
+
var ASGN_RE = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
|
|
4113
|
+
var CASE_START_RE = /^\s*case\s+/;
|
|
4114
|
+
var IF_START_RE = /^\s*if\s+/;
|
|
4115
|
+
var NOT_MODELED_RE = /\$\(|`[^`]*`/;
|
|
4116
|
+
var CASE_OPEN_RE = /\bcase\b/;
|
|
4117
|
+
var CASE_CLOSE_RE = /\besac\b/;
|
|
4118
|
+
var IF_OPEN_RE = /\bif\b/;
|
|
4119
|
+
var IF_CLOSE_RE = /\bfi\b/;
|
|
4120
|
+
function countMatches(re, s) {
|
|
4121
|
+
const m = s.match(re);
|
|
4122
|
+
return m ? m.length : 0;
|
|
4123
|
+
}
|
|
4124
|
+
function findBlockEnd(lines, startLine, openRe, closeRe) {
|
|
4125
|
+
let depth = 0;
|
|
4126
|
+
for (let idx = startLine; idx < lines.length; idx++) {
|
|
4127
|
+
depth += countMatches(openRe, lines[idx]) - countMatches(closeRe, lines[idx]);
|
|
4128
|
+
if (depth <= 0 && idx > startLine) return idx;
|
|
4129
|
+
}
|
|
4130
|
+
return -1;
|
|
4131
|
+
}
|
|
4132
|
+
function findCaseEnd(lines, start) {
|
|
4133
|
+
return findBlockEnd(lines, start, CASE_OPEN_RE, CASE_CLOSE_RE);
|
|
4134
|
+
}
|
|
4135
|
+
function findIfEnd(lines, start) {
|
|
4136
|
+
return findBlockEnd(lines, start, IF_OPEN_RE, IF_CLOSE_RE);
|
|
4137
|
+
}
|
|
4138
|
+
var LOOP_DO_RE = /\bdo\b/g;
|
|
4139
|
+
var LOOP_DONE_RE = /\bdone\b/g;
|
|
4140
|
+
function findLoopEnd(lines, start) {
|
|
4141
|
+
let depth = 0;
|
|
4142
|
+
for (let idx = start; idx < lines.length; idx++) {
|
|
4143
|
+
depth += countMatches(LOOP_DO_RE, lines[idx]) - countMatches(LOOP_DONE_RE, lines[idx]);
|
|
4144
|
+
if (depth <= 0 && idx > start) return idx;
|
|
4145
|
+
}
|
|
4146
|
+
return -1;
|
|
4147
|
+
}
|
|
4148
|
+
var FN_DEF_RE = /^\s*(?:function\s+)?[A-Za-z_][A-Za-z0-9_]*\s*\(\s*\)\s*\{/;
|
|
4149
|
+
var FN_DEF_KW_RE = /^\s*function\s+[A-Za-z_][A-Za-z0-9_]*\s*\{?/;
|
|
4150
|
+
function findFnEnd(lines, start) {
|
|
4151
|
+
let depth = 0;
|
|
4152
|
+
for (let idx = start; idx < lines.length; idx++) {
|
|
4153
|
+
depth += (lines[idx].match(/\{/g) || []).length - (lines[idx].match(/\}/g) || []).length;
|
|
4154
|
+
if (depth <= 0 && idx > start) return idx;
|
|
4155
|
+
}
|
|
4156
|
+
return -1;
|
|
4157
|
+
}
|
|
4158
|
+
function stripComments(content) {
|
|
4159
|
+
return content.split("\n").filter((l) => !l.trimStart().startsWith("#")).join("\n");
|
|
4160
|
+
}
|
|
4161
|
+
function parseCaseStmt(block) {
|
|
4162
|
+
const header = block.match(/\s*case\s+(\S[^;]*?)\s*\bin\b[\s\S]*/);
|
|
4163
|
+
if (!header) return null;
|
|
4164
|
+
const sel = header[1].trim();
|
|
4165
|
+
const lines = block.split("\n");
|
|
4166
|
+
let headerLine = 0;
|
|
4167
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4168
|
+
if (/\bin\b/.test(lines[i]) && /\bcase\b/.test(lines[i])) {
|
|
4169
|
+
headerLine = i;
|
|
4170
|
+
break;
|
|
4171
|
+
}
|
|
4172
|
+
}
|
|
4173
|
+
const bodyStartLine = headerLine + 1;
|
|
4174
|
+
const endLine = findCaseEnd(lines, 0);
|
|
4175
|
+
if (endLine === -1) return null;
|
|
4176
|
+
const branches = splitCaseBranchesDepthAware(lines.slice(bodyStartLine, endLine));
|
|
4177
|
+
if (!branches.length) return null;
|
|
4178
|
+
return { kind: "case", line: block, modelable: true, selector: sel, branches };
|
|
4179
|
+
}
|
|
4180
|
+
function splitCaseBranchesDepthAware(bodyLines) {
|
|
4181
|
+
let depth = 0;
|
|
4182
|
+
let curPat = null;
|
|
4183
|
+
let curBody = [];
|
|
4184
|
+
const branches = [];
|
|
4185
|
+
const flush = () => {
|
|
4186
|
+
if (curPat !== null) {
|
|
4187
|
+
branches.push({ pat: curPat, body: curBody.join("\n") });
|
|
4188
|
+
}
|
|
4189
|
+
curPat = null;
|
|
4190
|
+
curBody = [];
|
|
4191
|
+
};
|
|
4192
|
+
for (const ln of bodyLines) {
|
|
4193
|
+
const stripped = ln.trim();
|
|
4194
|
+
if (depth === 0) {
|
|
4195
|
+
const bm = stripped.match(/^([^()]+?)\s*\)\s*(.*)$/);
|
|
4196
|
+
if (bm) {
|
|
4197
|
+
if (curPat !== null) flush();
|
|
4198
|
+
curPat = bm[1].trim().replace(/^["']|["']$/g, "");
|
|
4199
|
+
curBody = bm[2].trim() ? [bm[2].trim()] : [];
|
|
4200
|
+
continue;
|
|
4201
|
+
}
|
|
4202
|
+
}
|
|
4203
|
+
if (/\bcase\b/.test(stripped)) depth += 1;
|
|
4204
|
+
else if (/\besac\b/.test(stripped)) depth = Math.max(0, depth - 1);
|
|
4205
|
+
else if (curPat !== null) {
|
|
4206
|
+
curBody.push(ln);
|
|
4207
|
+
if (depth === 0 && stripped.endsWith(";;")) flush();
|
|
4208
|
+
}
|
|
4209
|
+
}
|
|
4210
|
+
if (curPat !== null && curBody.length) flush();
|
|
4211
|
+
return branches;
|
|
4212
|
+
}
|
|
4213
|
+
function parseIfStmt(block) {
|
|
4214
|
+
const m = block.match(/\s*if\s+([^\n;]+?)\s*;\s*then/);
|
|
4215
|
+
if (!m) return null;
|
|
4216
|
+
const cond = m[1].trim();
|
|
4217
|
+
const lines = block.split("\n");
|
|
4218
|
+
const fiLine = findIfEnd(lines, 0);
|
|
4219
|
+
if (fiLine === -1) return null;
|
|
4220
|
+
const body = lines.slice(1, fiLine).join("\n");
|
|
4221
|
+
const elseM = /\belse\b/.exec(body);
|
|
4222
|
+
let thenBody;
|
|
4223
|
+
let elseBody;
|
|
4224
|
+
if (elseM) {
|
|
4225
|
+
thenBody = body.slice(0, elseM.index);
|
|
4226
|
+
elseBody = body.slice(elseM.index + 4);
|
|
4227
|
+
} else {
|
|
4228
|
+
thenBody = body;
|
|
4229
|
+
elseBody = null;
|
|
4230
|
+
}
|
|
4231
|
+
return { kind: "if", line: block, modelable: true, cond, thenBody, elseBody };
|
|
4232
|
+
}
|
|
4233
|
+
function tokenize(content) {
|
|
4234
|
+
const lines = stripComments(content).split("\n");
|
|
4235
|
+
const stmts = [];
|
|
4236
|
+
let i = 0;
|
|
4237
|
+
while (i < lines.length) {
|
|
4238
|
+
const ln = lines[i];
|
|
4239
|
+
const stripped = ln.trim();
|
|
4240
|
+
if (!stripped) {
|
|
4241
|
+
i++;
|
|
4242
|
+
continue;
|
|
4243
|
+
}
|
|
4244
|
+
if (/\b(?:while|for|until)\b/.test(stripped) && !CASE_START_RE.test(stripped) && !IF_START_RE.test(stripped)) {
|
|
4245
|
+
const endLine = findLoopEnd(lines, i);
|
|
4246
|
+
if (endLine !== -1) {
|
|
4247
|
+
for (let k = i; k <= endLine; k++) stmts.push({ kind: "plain", line: lines[k], modelable: false });
|
|
4248
|
+
i = endLine + 1;
|
|
4249
|
+
continue;
|
|
4250
|
+
}
|
|
4251
|
+
stmts.push({ kind: "plain", line: ln, modelable: false });
|
|
4252
|
+
i++;
|
|
4253
|
+
continue;
|
|
4254
|
+
}
|
|
4255
|
+
if (FN_DEF_RE.test(stripped) || FN_DEF_KW_RE.test(stripped)) {
|
|
4256
|
+
const endLine = findFnEnd(lines, i);
|
|
4257
|
+
if (endLine !== -1) {
|
|
4258
|
+
for (let k = i; k <= endLine; k++) stmts.push({ kind: "plain", line: lines[k], modelable: false });
|
|
4259
|
+
i = endLine + 1;
|
|
4260
|
+
continue;
|
|
4261
|
+
}
|
|
4262
|
+
stmts.push({ kind: "plain", line: ln, modelable: false });
|
|
4263
|
+
i++;
|
|
4264
|
+
continue;
|
|
4265
|
+
}
|
|
4266
|
+
if (CASE_START_RE.test(stripped)) {
|
|
4267
|
+
const endLine = findCaseEnd(lines, i);
|
|
4268
|
+
if (endLine === -1) {
|
|
4269
|
+
stmts.push({ kind: "plain", line: ln, modelable: false });
|
|
4270
|
+
i++;
|
|
4271
|
+
continue;
|
|
4272
|
+
}
|
|
4273
|
+
const parsed = parseCaseStmt(lines.slice(i, endLine + 1).join("\n"));
|
|
4274
|
+
stmts.push(parsed || { kind: "plain", line: ln, modelable: false });
|
|
4275
|
+
i = endLine + 1;
|
|
4276
|
+
continue;
|
|
4277
|
+
}
|
|
4278
|
+
if (IF_START_RE.test(stripped)) {
|
|
4279
|
+
const endLine = findIfEnd(lines, i);
|
|
4280
|
+
if (endLine === -1) {
|
|
4281
|
+
stmts.push({ kind: "plain", line: ln, modelable: false });
|
|
4282
|
+
i++;
|
|
4283
|
+
continue;
|
|
4284
|
+
}
|
|
4285
|
+
const parsed = parseIfStmt(lines.slice(i, endLine + 1).join("\n"));
|
|
4286
|
+
stmts.push(parsed || { kind: "plain", line: ln, modelable: false });
|
|
4287
|
+
i = endLine + 1;
|
|
4288
|
+
continue;
|
|
4289
|
+
}
|
|
4290
|
+
if (/\bshift\b|\bset\s+--(?:\s|$)|[\s;|&]read\b|^\s*read\b/.test(stripped)) {
|
|
4291
|
+
stmts.push({ kind: "plain", line: ln, modelable: false });
|
|
4292
|
+
i++;
|
|
4293
|
+
continue;
|
|
4294
|
+
}
|
|
4295
|
+
const am = stripped.match(ASGN_RE);
|
|
4296
|
+
if (am && !NOT_MODELED_RE.test(stripped)) {
|
|
4297
|
+
stmts.push({ kind: "assign", line: stripped, modelable: true, name: am[1], rhs: am[2].trim() });
|
|
4298
|
+
i++;
|
|
4299
|
+
continue;
|
|
4300
|
+
}
|
|
4301
|
+
stmts.push(NOT_MODELED_RE.test(stripped) ? { kind: "plain", line: ln, modelable: false } : { kind: "plain", line: ln, modelable: true });
|
|
4302
|
+
i++;
|
|
4303
|
+
}
|
|
4304
|
+
return stmts;
|
|
4305
|
+
}
|
|
4306
|
+
var EXPR_SPLIT_RE = /(\$\{?\d+\}?|\$\{?[A-Za-z_][A-Za-z0-9_]*\}?)/;
|
|
4307
|
+
function evalExpr2(text, env) {
|
|
4308
|
+
const isSingleQuoted = text.length >= 2 && text[0] === "'" && text[text.length - 1] === "'";
|
|
4309
|
+
if (text.length >= 2 && (text[0] === '"' || text[0] === "'") && text[text.length - 1] === text[0]) {
|
|
4310
|
+
text = text.slice(1, -1);
|
|
4311
|
+
}
|
|
4312
|
+
if (isSingleQuoted) return svalConc(text);
|
|
4313
|
+
const parts = text.split(EXPR_SPLIT_RE);
|
|
4314
|
+
const result = [];
|
|
4315
|
+
for (const p of parts) {
|
|
4316
|
+
if (!p) continue;
|
|
4317
|
+
const brace = p.match(/^\$\{([^}]+)\}$/);
|
|
4318
|
+
if (brace) {
|
|
4319
|
+
const inner = brace[1];
|
|
4320
|
+
if (/^\d+$/.test(inner)) {
|
|
4321
|
+
const v = env.get(`$${inner}`);
|
|
4322
|
+
result.push(v || svalSym(`$${inner}`));
|
|
4323
|
+
} else {
|
|
4324
|
+
const v = env.get(inner);
|
|
4325
|
+
result.push(v || svalSym(inner));
|
|
4326
|
+
}
|
|
4327
|
+
continue;
|
|
4328
|
+
}
|
|
4329
|
+
const bare = p.match(/^\$([A-Za-z_][A-Za-z0-9_]*|\d+)$/);
|
|
4330
|
+
if (bare) {
|
|
4331
|
+
const varName = bare[1];
|
|
4332
|
+
const key = /^\d+$/.test(varName) ? `$${varName}` : varName;
|
|
4333
|
+
const v = env.get(key);
|
|
4334
|
+
result.push(v || svalSym(p));
|
|
4335
|
+
continue;
|
|
4336
|
+
}
|
|
4337
|
+
result.push(svalConc(p));
|
|
4338
|
+
}
|
|
4339
|
+
if (result.length === 1) return fold(result[0]);
|
|
4340
|
+
return fold(svalExpr(result));
|
|
4341
|
+
}
|
|
4342
|
+
function initEnv(entryArgs) {
|
|
4343
|
+
const env = /* @__PURE__ */ new Map();
|
|
4344
|
+
if (entryArgs && entryArgs.length) {
|
|
4345
|
+
entryArgs.forEach((a, idx) => env.set(`$${idx + 1}`, svalConc(a)));
|
|
4346
|
+
env.set("$@", svalConc(entryArgs.join(" ")));
|
|
4347
|
+
env.set("$*", svalConc(entryArgs.join(" ")));
|
|
4348
|
+
}
|
|
4349
|
+
return env;
|
|
4350
|
+
}
|
|
4351
|
+
var HAS_GLOB = /[*?[]/;
|
|
4352
|
+
function globToRegex(pattern) {
|
|
4353
|
+
let out = "^";
|
|
4354
|
+
let i = 0;
|
|
4355
|
+
while (i < pattern.length) {
|
|
4356
|
+
const c = pattern[i];
|
|
4357
|
+
if (c === "*") out += ".*";
|
|
4358
|
+
else if (c === "?") out += ".";
|
|
4359
|
+
else if (c === "[") {
|
|
4360
|
+
let j = i + 1;
|
|
4361
|
+
let neg = false;
|
|
4362
|
+
if (j < pattern.length && (pattern[j] === "!" || pattern[j] === "^")) {
|
|
4363
|
+
neg = true;
|
|
4364
|
+
j++;
|
|
4365
|
+
}
|
|
4366
|
+
let cls = "";
|
|
4367
|
+
while (j < pattern.length && pattern[j] !== "]") {
|
|
4368
|
+
cls += pattern[j];
|
|
4369
|
+
j++;
|
|
4370
|
+
}
|
|
4371
|
+
if (j >= pattern.length) {
|
|
4372
|
+
out += reescape(pattern.slice(i));
|
|
4373
|
+
break;
|
|
4374
|
+
}
|
|
4375
|
+
out += "[" + (neg ? "^" : "") + reescape(cls) + "]";
|
|
4376
|
+
i = j;
|
|
4377
|
+
} else {
|
|
4378
|
+
out += reescape(c);
|
|
4379
|
+
}
|
|
4380
|
+
i++;
|
|
4381
|
+
}
|
|
4382
|
+
out += "$";
|
|
4383
|
+
return new RegExp(out, RE_DOTALL);
|
|
4384
|
+
}
|
|
4385
|
+
function matchGlob(pattern, value) {
|
|
4386
|
+
if (!HAS_GLOB.test(pattern)) return pattern === value;
|
|
4387
|
+
return globToRegex(pattern).test(value);
|
|
4388
|
+
}
|
|
4389
|
+
function evalNumericCompare(op, left, right) {
|
|
4390
|
+
const li = parseInt(left, 10);
|
|
4391
|
+
const ri = parseInt(right, 10);
|
|
4392
|
+
if (isNaN(li) || isNaN(ri)) return null;
|
|
4393
|
+
switch (op) {
|
|
4394
|
+
case "-gt":
|
|
4395
|
+
return li > ri;
|
|
4396
|
+
case "-lt":
|
|
4397
|
+
return li < ri;
|
|
4398
|
+
case "-eq":
|
|
4399
|
+
return li === ri;
|
|
4400
|
+
case "-ge":
|
|
4401
|
+
return li >= ri;
|
|
4402
|
+
case "-le":
|
|
4403
|
+
return li <= ri;
|
|
4404
|
+
default:
|
|
4405
|
+
return null;
|
|
4406
|
+
}
|
|
4407
|
+
}
|
|
4408
|
+
function resolveVarSide(tok, env) {
|
|
4409
|
+
if (!tok.startsWith("$")) return tok;
|
|
4410
|
+
const ev = evalExpr2(tok, env);
|
|
4411
|
+
return ev.kind === "concrete" ? ev.value : null;
|
|
4412
|
+
}
|
|
4413
|
+
function evalCondition2(cond, env) {
|
|
4414
|
+
let inner = cond.trim().replace(/^\[\[?|\]\]?$/g, "").trim();
|
|
4415
|
+
if (/^-[A-Za-z]\s/.test(inner) || inner.startsWith("-d") || inner.startsWith("-f")) return null;
|
|
4416
|
+
const innerQ = inner.replace(/"/g, "").replace(/'/g, "");
|
|
4417
|
+
for (const op2 of ["-gt", "-lt", "-eq", "-ge", "-le"]) {
|
|
4418
|
+
const re = new RegExp(`\\s${op2}\\s`);
|
|
4419
|
+
const opm = re.exec(innerQ);
|
|
4420
|
+
if (!opm) continue;
|
|
4421
|
+
const left2 = innerQ.slice(0, opm.index).trim();
|
|
4422
|
+
const right2 = innerQ.slice(opm.index + opm[0].length).trim();
|
|
4423
|
+
const lv2 = resolveVarSide(left2, env);
|
|
4424
|
+
const rv2 = resolveVarSide(right2, env);
|
|
4425
|
+
if (lv2 === null || rv2 === null) return null;
|
|
4426
|
+
return evalNumericCompare(op2, lv2, rv2);
|
|
4427
|
+
}
|
|
4428
|
+
const sm = innerQ.match(/(.*?)\s*(==|=|!=|=~)\s*(.*)/);
|
|
4429
|
+
if (!sm) return null;
|
|
4430
|
+
const op = sm[2];
|
|
4431
|
+
let left = sm[1].trim();
|
|
4432
|
+
let right = sm[3].trim();
|
|
4433
|
+
if (op === "=~") {
|
|
4434
|
+
if (right.startsWith("/") && right.endsWith("/")) right = right.slice(1, -1);
|
|
4435
|
+
const lv2 = resolveVarSide(left, env);
|
|
4436
|
+
if (lv2 === null) return null;
|
|
4437
|
+
try {
|
|
4438
|
+
return new RegExp(right).test(lv2);
|
|
4439
|
+
} catch {
|
|
4440
|
+
return null;
|
|
4441
|
+
}
|
|
4442
|
+
}
|
|
4443
|
+
const lv = resolveVarSide(left, env);
|
|
4444
|
+
const rv = resolveVarSide(right, env);
|
|
4445
|
+
if (lv === null || rv === null) return null;
|
|
4446
|
+
if (op === "!=") return lv !== rv;
|
|
4447
|
+
return lv === rv;
|
|
4448
|
+
}
|
|
4449
|
+
function symbolicProcess(content, entryArgs) {
|
|
4450
|
+
if (!content) return null;
|
|
4451
|
+
const stmts = tokenize(content);
|
|
4452
|
+
if (!stmts.some((s) => s.kind === "case" || s.kind === "if" || s.kind === "assign")) return null;
|
|
4453
|
+
if (stmts.some((s) => !s.modelable)) return null;
|
|
4454
|
+
const env = initEnv(entryArgs);
|
|
4455
|
+
let linesBudget = 0;
|
|
4456
|
+
let branchesBudget = 0;
|
|
4457
|
+
let modelable = false;
|
|
4458
|
+
const out = [];
|
|
4459
|
+
for (const s of stmts) {
|
|
4460
|
+
linesBudget++;
|
|
4461
|
+
if (linesBudget > MAX_LINES2) return null;
|
|
4462
|
+
if (s.kind === "assign") {
|
|
4463
|
+
modelable = true;
|
|
4464
|
+
if (env.size >= MAX_VARS) return null;
|
|
4465
|
+
const v = evalExpr2(s.rhs, env);
|
|
4466
|
+
env.set(s.name, fold(v));
|
|
4467
|
+
out.push(s.line);
|
|
4468
|
+
continue;
|
|
4469
|
+
}
|
|
4470
|
+
if (s.kind === "case") {
|
|
4471
|
+
modelable = true;
|
|
4472
|
+
branchesBudget += s.branches.length;
|
|
4473
|
+
if (branchesBudget > MAX_BRANCHES) return null;
|
|
4474
|
+
const selVal = fold(evalExpr2(s.selector, env));
|
|
4475
|
+
if (selVal.kind !== "concrete") return null;
|
|
4476
|
+
const concrete = selVal.value || "";
|
|
4477
|
+
let selected = null;
|
|
4478
|
+
let fallback = "";
|
|
4479
|
+
for (const b of s.branches) {
|
|
4480
|
+
const patStripped = b.pat.trim();
|
|
4481
|
+
if (patStripped === "*") {
|
|
4482
|
+
fallback = b.body;
|
|
4483
|
+
continue;
|
|
4484
|
+
}
|
|
4485
|
+
if (matchGlob(patStripped, concrete)) {
|
|
4486
|
+
selected = b.body;
|
|
4487
|
+
break;
|
|
4488
|
+
}
|
|
4489
|
+
}
|
|
4490
|
+
const final = selected !== null ? selected : fallback || null;
|
|
4491
|
+
if (final === null) return null;
|
|
4492
|
+
out.push(`case ${s.selector} in
|
|
4493
|
+
${final}
|
|
4494
|
+
esac`);
|
|
4495
|
+
continue;
|
|
4496
|
+
}
|
|
4497
|
+
if (s.kind === "if") {
|
|
4498
|
+
modelable = true;
|
|
4499
|
+
branchesBudget++;
|
|
4500
|
+
if (branchesBudget > MAX_BRANCHES) return null;
|
|
4501
|
+
const truth = evalCondition2(s.cond, env);
|
|
4502
|
+
if (truth === null) return null;
|
|
4503
|
+
const chosen = truth ? s.thenBody : s.elseBody;
|
|
4504
|
+
if (chosen === null) return null;
|
|
4505
|
+
out.push(`if ${s.cond}; then
|
|
4506
|
+
${chosen}
|
|
4507
|
+
fi`);
|
|
4508
|
+
continue;
|
|
4509
|
+
}
|
|
4510
|
+
out.push(s.line);
|
|
4511
|
+
}
|
|
4512
|
+
if (!modelable) return null;
|
|
4513
|
+
return out.join("\n");
|
|
3690
4514
|
}
|
|
3691
|
-
var CHILD_SCRIPT_PATTERNS = [
|
|
3692
|
-
/(?:^|\s|&&|\|\|)\s*bash\s+(\S+)/i,
|
|
3693
|
-
/(?:^|\s|&&|\|\|)\s*sh\s+(\S+)/i,
|
|
3694
|
-
/(?:^|\s|&&|\|\|)\s*source\s+(\S+)/i,
|
|
3695
|
-
/(?:^|\s|&&|\|\|)\s*\.\s+(\S+)/,
|
|
3696
|
-
/(?:^|\s|&&|\|\|)\s*python3?\s+(\S+\.py)/i,
|
|
3697
|
-
/(?:^|\s|&&|\|\|)\s*node\s+(\S+\.js)/i,
|
|
3698
|
-
/(?:^|\s|&&|\|\|)\s*ruby\s+(\S+\.rb)/i,
|
|
3699
|
-
/(?:^|\s|&&|\|\|)\s*perl\s+(\S+\.pl)/i,
|
|
3700
|
-
/(?:^|\s|&&|\|\|)\s*php\s+(\S+\.php)/i,
|
|
3701
|
-
/(?:^|\s|&&|\|\|)\s*lua\s+(\S+\.lua)/i
|
|
3702
|
-
];
|
|
3703
4515
|
|
|
3704
|
-
// src/engine/
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
return
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
4516
|
+
// src/engine/script-engine/backends/shell-backend.ts
|
|
4517
|
+
function bindPositionalArgs(content, args) {
|
|
4518
|
+
if (!args.length) return content;
|
|
4519
|
+
const allArgs = args.join(" ");
|
|
4520
|
+
content = content.replace(/\$\{(\d+|@|\*)(?::?[-]?([^}]*))?\}/g, (full, kind, defv) => {
|
|
4521
|
+
if (kind === "@" || kind === "*") return allArgs;
|
|
4522
|
+
if (!/^\d+$/.test(kind)) return full;
|
|
4523
|
+
const idx = parseInt(kind, 10);
|
|
4524
|
+
if (1 <= idx && idx <= args.length) return args[idx - 1];
|
|
4525
|
+
return defv !== void 0 ? defv : "";
|
|
4526
|
+
});
|
|
4527
|
+
content = content.replace(/\$\{(\d+)\}/g, (full, kind) => {
|
|
4528
|
+
const idx = parseInt(kind, 10);
|
|
4529
|
+
if (1 <= idx && idx <= args.length) return args[idx - 1];
|
|
4530
|
+
return "";
|
|
4531
|
+
});
|
|
4532
|
+
content = content.replace(/\$(\d+)/g, (full, kind) => {
|
|
4533
|
+
const idx = parseInt(kind, 10);
|
|
4534
|
+
if (1 <= idx && idx <= args.length) return args[idx - 1];
|
|
4535
|
+
return "";
|
|
4536
|
+
});
|
|
4537
|
+
content = content.replace(/\$[@*]/g, allArgs);
|
|
4538
|
+
return content;
|
|
4539
|
+
}
|
|
4540
|
+
function findSelectorValue(selector, args) {
|
|
4541
|
+
if (!args || !args.length) return null;
|
|
4542
|
+
const m = selector.match(/^\$\{?(\d+)\}?$/);
|
|
4543
|
+
if (m) {
|
|
4544
|
+
const idx = parseInt(m[1], 10);
|
|
4545
|
+
if (1 <= idx && idx <= args.length) return args[idx - 1];
|
|
4546
|
+
return null;
|
|
3721
4547
|
}
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
const
|
|
3726
|
-
const
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
4548
|
+
return null;
|
|
4549
|
+
}
|
|
4550
|
+
function splitCaseBody(body) {
|
|
4551
|
+
const lines = body.split("\n");
|
|
4552
|
+
const branches = [];
|
|
4553
|
+
let cur = null;
|
|
4554
|
+
for (const ln of lines) {
|
|
4555
|
+
const stripped = ln.trim();
|
|
4556
|
+
if (/^[\w:-]+\s*\(\)\s*\{/.test(stripped)) continue;
|
|
4557
|
+
const m = stripped.match(/^([^()]+?)\s*\)\s*(.*)$/);
|
|
4558
|
+
if (m) {
|
|
4559
|
+
const pat = m[1].trim().replace(/^["']|["']$/g, "");
|
|
4560
|
+
const inline = m[2].trim();
|
|
4561
|
+
if (pat) {
|
|
4562
|
+
if (cur) branches.push(cur);
|
|
4563
|
+
cur = { label: pat, body: inline };
|
|
4564
|
+
}
|
|
4565
|
+
continue;
|
|
4566
|
+
}
|
|
4567
|
+
if (cur) {
|
|
4568
|
+
cur.body += cur.body ? "\n" + ln : ln;
|
|
4569
|
+
if (stripped.endsWith(";;")) {
|
|
4570
|
+
branches.push(cur);
|
|
4571
|
+
cur = null;
|
|
4572
|
+
}
|
|
4573
|
+
}
|
|
3730
4574
|
}
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
4575
|
+
if (cur && cur.body.trim()) {
|
|
4576
|
+
if (!branches.includes(cur)) branches.push(cur);
|
|
4577
|
+
}
|
|
4578
|
+
return branches;
|
|
4579
|
+
}
|
|
4580
|
+
function parseCase(content, args) {
|
|
4581
|
+
const m = content.match(/\bcase\s+(\$\{?\d+\}?|[A-Za-z_]\w*|\$[A-Za-z_]\w*)\s+in/i);
|
|
4582
|
+
if (!m) return null;
|
|
4583
|
+
const selector = m[1].trim();
|
|
4584
|
+
const val = findSelectorValue(selector, args);
|
|
4585
|
+
if (val === null) return null;
|
|
4586
|
+
const lines = content.split("\n");
|
|
4587
|
+
const endMatch = findCaseEndInLines(lines, 0);
|
|
4588
|
+
if (endMatch === -1) return null;
|
|
4589
|
+
const bodyLines = lines.slice(1, endMatch);
|
|
4590
|
+
const body = bodyLines.join("\n");
|
|
4591
|
+
const hasNested = /\bcase\s+/.test(body) || /\bif\s+/.test(body);
|
|
4592
|
+
let final;
|
|
4593
|
+
if (hasNested) {
|
|
4594
|
+
final = splitNestedCaseBranches(bodyLines, val);
|
|
4595
|
+
if (final === null) return null;
|
|
3737
4596
|
} else {
|
|
3738
|
-
|
|
4597
|
+
const branches = splitCaseBody(body);
|
|
4598
|
+
let selected = null;
|
|
4599
|
+
let fallback = "";
|
|
4600
|
+
for (const b of branches) {
|
|
4601
|
+
const values = b.label.split("|").map((v) => v.trim()).filter(Boolean);
|
|
4602
|
+
if (values.includes("*")) fallback = b.body;
|
|
4603
|
+
else if (values.includes(val)) selected = b.body;
|
|
4604
|
+
}
|
|
4605
|
+
final = selected !== null ? selected : fallback || null;
|
|
4606
|
+
if (final === null) return null;
|
|
4607
|
+
}
|
|
4608
|
+
const prefix = content.slice(0, m.index ?? 0);
|
|
4609
|
+
const suffix = lines.slice(endMatch + 1).join("\n");
|
|
4610
|
+
return `${prefix}
|
|
4611
|
+
${final}
|
|
4612
|
+
${suffix}`;
|
|
4613
|
+
}
|
|
4614
|
+
function findCaseEndInLines(lines, headerLine) {
|
|
4615
|
+
let depth = 0;
|
|
4616
|
+
for (let idx = headerLine; idx < lines.length; idx++) {
|
|
4617
|
+
const ln = lines[idx];
|
|
4618
|
+
const opens = (ln.match(/\bcase\b/g) || []).length;
|
|
4619
|
+
const closes = (ln.match(/\besac\b/g) || []).length;
|
|
4620
|
+
depth += opens - closes;
|
|
4621
|
+
if (depth <= 0 && idx > headerLine) return idx;
|
|
4622
|
+
}
|
|
4623
|
+
return -1;
|
|
4624
|
+
}
|
|
4625
|
+
function splitNestedCaseBranches(bodyLines, selectorVal) {
|
|
4626
|
+
let depth = 0;
|
|
4627
|
+
let curPat = null;
|
|
4628
|
+
let curBody = [];
|
|
4629
|
+
let selected = null;
|
|
4630
|
+
let fallback = null;
|
|
4631
|
+
const flush = () => {
|
|
4632
|
+
if (curPat === null) return;
|
|
4633
|
+
const values = curPat.split("|").map((v) => v.trim()).filter(Boolean);
|
|
4634
|
+
const body = curBody.join("\n");
|
|
4635
|
+
if (values.includes("*")) {
|
|
4636
|
+
if (fallback === null) fallback = body;
|
|
4637
|
+
} else if (values.includes(selectorVal)) {
|
|
4638
|
+
selected = body;
|
|
4639
|
+
}
|
|
4640
|
+
curPat = null;
|
|
4641
|
+
curBody = [];
|
|
4642
|
+
};
|
|
4643
|
+
for (const ln of bodyLines) {
|
|
4644
|
+
const stripped = ln.trim();
|
|
4645
|
+
if (depth === 0) {
|
|
4646
|
+
const bm = stripped.match(/^([^()]+?)\s*\)\s*(.*)$/);
|
|
4647
|
+
if (bm) {
|
|
4648
|
+
if (curPat !== null) flush();
|
|
4649
|
+
curPat = bm[1].trim().replace(/^["']|["']$/g, "");
|
|
4650
|
+
curBody = bm[2].trim() ? [bm[2].trim()] : [];
|
|
4651
|
+
continue;
|
|
4652
|
+
}
|
|
4653
|
+
}
|
|
4654
|
+
if (/\bcase\b/.test(stripped)) depth += 1;
|
|
4655
|
+
else if (/\besac\b/.test(stripped)) depth = Math.max(0, depth - 1);
|
|
4656
|
+
else if (curPat !== null) {
|
|
4657
|
+
curBody.push(ln);
|
|
4658
|
+
if (depth === 0 && stripped.endsWith(";;")) flush();
|
|
4659
|
+
}
|
|
3739
4660
|
}
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
4661
|
+
if (curPat !== null && curBody.length) flush();
|
|
4662
|
+
return selected !== null ? selected : fallback;
|
|
4663
|
+
}
|
|
4664
|
+
function evalIfCondition(cond, args) {
|
|
4665
|
+
if (!args || !args.length) return null;
|
|
4666
|
+
let inner = cond.replace(/^\[\[?|\]\]?$/g, "").replace(/"/g, "").replace(/'/g, "").trim();
|
|
4667
|
+
const m = inner.match(/(.*?)\s*(==|=)\s*(.*)/);
|
|
4668
|
+
if (!m) return null;
|
|
4669
|
+
let left = m[1].trim();
|
|
4670
|
+
let right = m[3].trim();
|
|
4671
|
+
const lm = left.match(/^\$(\d+)$/);
|
|
4672
|
+
if (lm) {
|
|
4673
|
+
const idx = parseInt(lm[1], 10);
|
|
4674
|
+
if (1 <= idx && idx <= args.length) left = args[idx - 1];
|
|
4675
|
+
else return null;
|
|
4676
|
+
} else if (left.startsWith("$")) return null;
|
|
4677
|
+
const rm = right.match(/^\$(\d+)$/);
|
|
4678
|
+
if (rm) {
|
|
4679
|
+
const idx = parseInt(rm[1], 10);
|
|
4680
|
+
if (1 <= idx && idx <= args.length) right = args[idx - 1];
|
|
4681
|
+
else return null;
|
|
4682
|
+
} else if (right.startsWith("$")) return null;
|
|
4683
|
+
return left === right;
|
|
4684
|
+
}
|
|
4685
|
+
function parseIf(content, args) {
|
|
4686
|
+
const m = content.match(/\bif\s+([^\n;]+?)\s*;\s*then/i);
|
|
4687
|
+
if (!m) return null;
|
|
4688
|
+
const truth = evalIfCondition(m[1].trim(), args);
|
|
4689
|
+
if (truth === null) return null;
|
|
4690
|
+
const start = m.index ?? 0;
|
|
4691
|
+
const thenBodyStart = start + m[0].length;
|
|
4692
|
+
const fiPos = content.indexOf("fi", thenBodyStart);
|
|
4693
|
+
if (fiPos === -1) return null;
|
|
4694
|
+
const segment = content.slice(thenBodyStart, fiPos);
|
|
4695
|
+
const elseM = segment.match(/\belse\b/);
|
|
4696
|
+
let chosen;
|
|
4697
|
+
if (truth) chosen = elseM ? segment.slice(0, elseM.index) : segment;
|
|
4698
|
+
else {
|
|
4699
|
+
if (!elseM) return null;
|
|
4700
|
+
chosen = segment.slice((elseM.index ?? 0) + 4);
|
|
4701
|
+
}
|
|
4702
|
+
return `${content.slice(0, start)}
|
|
4703
|
+
${chosen}
|
|
4704
|
+
${content.slice(fiPos + 2)}`;
|
|
4705
|
+
}
|
|
4706
|
+
function markLoopsReachable(content) {
|
|
4707
|
+
return content;
|
|
4708
|
+
}
|
|
4709
|
+
var CHILD_CALL_RE2 = /(?:^|\s|&&|\|\|)\s*((?:\/bin\/)?bash|sh|zsh|(?:python|python3|node|ruby|perl|php|lua))\s+(\S+(?:\.(?:sh|shrc|py|js|rb|pl|php|lua))?)([^\n;|&]*?)$/gmi;
|
|
4710
|
+
function extractCallsFromContent(content) {
|
|
4711
|
+
const calls = [];
|
|
4712
|
+
let m;
|
|
4713
|
+
CHILD_CALL_RE2.lastIndex = 0;
|
|
4714
|
+
while ((m = CHILD_CALL_RE2.exec(content)) !== null) {
|
|
4715
|
+
const interp = m[1];
|
|
4716
|
+
const path5 = m[2].trim();
|
|
4717
|
+
let rest = m[3].trim();
|
|
4718
|
+
if (path5.startsWith("-") || path5.startsWith("$") || path5.includes("/dev/")) continue;
|
|
4719
|
+
rest = rest.split(/[;|&<>]/)[0].trim();
|
|
4720
|
+
const args = [];
|
|
4721
|
+
for (const tm of rest.matchAll(/(["'])(.*?)\1|(\S+)/g)) {
|
|
4722
|
+
args.push(tm[1] ? tm[2] : tm[3]);
|
|
4723
|
+
}
|
|
4724
|
+
calls.push({ path: path5, args, interpreter: interp });
|
|
4725
|
+
}
|
|
4726
|
+
return calls;
|
|
4727
|
+
}
|
|
4728
|
+
var ShellBackend = class {
|
|
4729
|
+
language = "shell";
|
|
4730
|
+
parse(content, entryArgs) {
|
|
4731
|
+
const c = content || "";
|
|
4732
|
+
const orig = c;
|
|
4733
|
+
let candidate = c;
|
|
4734
|
+
let resolved = false;
|
|
4735
|
+
const p1Safe = !/(?:\bshift\b|\bset\s+--(?:\s|$)|[\s;|&]read\b|^\s*read\b|\(\)\s*\{|function\s+\w+)/.test(c);
|
|
4736
|
+
if (p1Safe) {
|
|
4737
|
+
const caseOut = parseCase(candidate, entryArgs);
|
|
4738
|
+
if (caseOut !== null && caseOut !== candidate) {
|
|
4739
|
+
resolved = true;
|
|
4740
|
+
candidate = caseOut;
|
|
4741
|
+
}
|
|
4742
|
+
const ifOut = parseIf(candidate, entryArgs);
|
|
4743
|
+
if (ifOut !== null && ifOut !== candidate) {
|
|
4744
|
+
resolved = true;
|
|
4745
|
+
candidate = ifOut;
|
|
4746
|
+
}
|
|
4747
|
+
}
|
|
4748
|
+
let bound = p1Safe ? bindPositionalArgs(candidate, entryArgs || []) : candidate;
|
|
4749
|
+
if (!resolved) {
|
|
4750
|
+
const symOut = symbolicProcess(c, entryArgs);
|
|
4751
|
+
if (symOut !== null && symOut !== c) {
|
|
4752
|
+
resolved = true;
|
|
4753
|
+
bound = symOut;
|
|
4754
|
+
}
|
|
4755
|
+
}
|
|
4756
|
+
const loopMarked = markLoopsReachable(bound);
|
|
4757
|
+
const calls = extractCallsFromContent(loopMarked);
|
|
4758
|
+
return {
|
|
4759
|
+
reachableContent: loopMarked,
|
|
4760
|
+
controlFlowResolved: resolved,
|
|
4761
|
+
calls,
|
|
4762
|
+
branches: [],
|
|
4763
|
+
unresolvedSnippets: [],
|
|
4764
|
+
language: this.language
|
|
4765
|
+
};
|
|
3746
4766
|
}
|
|
3747
|
-
|
|
3748
|
-
|
|
4767
|
+
};
|
|
4768
|
+
|
|
4769
|
+
// src/engine/script-engine/tracker.ts
|
|
4770
|
+
var DEFAULT_MAX_DEPTH = 14;
|
|
4771
|
+
var DEFAULT_MAX_NODES = 64;
|
|
4772
|
+
var DEFAULT_MAX_FILE_SIZE = 2 * 1024 * 1024;
|
|
4773
|
+
var DEFAULT_MAX_TOTAL_SIZE = 8 * 1024 * 1024;
|
|
4774
|
+
var BACKENDS = {
|
|
4775
|
+
".py": new PythonBackend()
|
|
4776
|
+
};
|
|
4777
|
+
var DEFAULT_BACKEND = new ShellBackend();
|
|
4778
|
+
function pickBackend(pathStr) {
|
|
4779
|
+
const lower = pathStr.toLowerCase();
|
|
4780
|
+
for (const ext of Object.keys(BACKENDS)) {
|
|
4781
|
+
if (lower.endsWith(ext)) return BACKENDS[ext];
|
|
4782
|
+
}
|
|
4783
|
+
return DEFAULT_BACKEND;
|
|
3749
4784
|
}
|
|
3750
|
-
function
|
|
3751
|
-
|
|
4785
|
+
function resolvePath(p, baseDir) {
|
|
4786
|
+
let expanded = p;
|
|
4787
|
+
if (expanded.startsWith("~/") || expanded === "~") {
|
|
4788
|
+
expanded = path3.join(os.homedir(), expanded.slice(1));
|
|
4789
|
+
} else if (!path3.isAbsolute(expanded) && baseDir) {
|
|
4790
|
+
expanded = path3.join(baseDir, expanded);
|
|
4791
|
+
}
|
|
4792
|
+
return path3.resolve(expanded);
|
|
4793
|
+
}
|
|
4794
|
+
function readScript(pathStr, maxFileSize) {
|
|
3752
4795
|
try {
|
|
3753
|
-
const
|
|
3754
|
-
|
|
3755
|
-
const
|
|
3756
|
-
${
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
4796
|
+
const p = resolvePath(pathStr);
|
|
4797
|
+
if (!fs3.existsSync(p)) return { content: "", lineCount: 0, fileSize: 0, error: `\u811A\u672C\u4E0D\u5B58\u5728: ${pathStr}` };
|
|
4798
|
+
const stat = fs3.statSync(p);
|
|
4799
|
+
if (!stat.isFile()) return { content: "", lineCount: 0, fileSize: 0, error: `\u4E0D\u662F\u6587\u4EF6: ${pathStr}` };
|
|
4800
|
+
if (stat.size > maxFileSize) {
|
|
4801
|
+
return { content: "", lineCount: 0, fileSize: stat.size, error: `\u811A\u672C\u6587\u4EF6\u8FC7\u5927 (${stat.size} bytes)\uFF0C\u8DF3\u8FC7\u8BFB\u53D6` };
|
|
4802
|
+
}
|
|
4803
|
+
const content = fs3.readFileSync(p, "utf-8");
|
|
4804
|
+
return { content, lineCount: content ? content.split("\n").length : 0, fileSize: stat.size, error: "" };
|
|
3760
4805
|
} catch (e) {
|
|
3761
|
-
|
|
4806
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
4807
|
+
return { content: "", lineCount: 0, fileSize: 0, error: `\u8BFB\u53D6\u811A\u672C\u5931\u8D25: ${msg}` };
|
|
3762
4808
|
}
|
|
3763
4809
|
}
|
|
3764
|
-
function
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
4810
|
+
function emptyNode(pathStr, depth) {
|
|
4811
|
+
return {
|
|
4812
|
+
scriptPath: pathStr,
|
|
4813
|
+
rawContent: "",
|
|
4814
|
+
preprocessedContent: "",
|
|
4815
|
+
lineCount: 0,
|
|
4816
|
+
fileSize: 0,
|
|
4817
|
+
depth,
|
|
4818
|
+
childPaths: [],
|
|
4819
|
+
error: "",
|
|
4820
|
+
l1Hit: false,
|
|
4821
|
+
l1RiskLevel: "",
|
|
4822
|
+
l1RiskType: "",
|
|
4823
|
+
l1Reason: "",
|
|
4824
|
+
l1MatchedPatterns: [],
|
|
4825
|
+
l2Hit: false,
|
|
4826
|
+
l2ViolatedRule: "",
|
|
4827
|
+
l2RiskLevel: "",
|
|
4828
|
+
l2RiskType: "",
|
|
4829
|
+
l2Reason: "",
|
|
4830
|
+
matchedKeywords: [],
|
|
4831
|
+
isSuspicious: false,
|
|
4832
|
+
keywordRiskLevel: "",
|
|
4833
|
+
keywordRiskReason: ""
|
|
4834
|
+
};
|
|
4835
|
+
}
|
|
4836
|
+
var RecursiveTracker = class {
|
|
4837
|
+
constructor(commandScanner, maxDepth = DEFAULT_MAX_DEPTH, maxNodes = DEFAULT_MAX_NODES, maxFileSize = DEFAULT_MAX_FILE_SIZE, maxTotalSize = DEFAULT_MAX_TOTAL_SIZE) {
|
|
4838
|
+
this.maxDepth = maxDepth;
|
|
4839
|
+
this.maxNodes = maxNodes;
|
|
4840
|
+
this.maxFileSize = maxFileSize;
|
|
4841
|
+
this.maxTotalSize = maxTotalSize;
|
|
4842
|
+
this._scanner = commandScanner;
|
|
4843
|
+
this.result = {
|
|
4844
|
+
nodes: [],
|
|
4845
|
+
entryPath: "",
|
|
4846
|
+
maxDepthReached: false,
|
|
4847
|
+
maxSizeReached: false,
|
|
4848
|
+
maxNodesReached: false,
|
|
4849
|
+
totalSize: 0,
|
|
4850
|
+
totalLines: 0,
|
|
4851
|
+
totalNodes: 0,
|
|
4852
|
+
hasCritical: false,
|
|
4853
|
+
hasHigh: false,
|
|
4854
|
+
firstHitNode: null,
|
|
4855
|
+
error: ""
|
|
4856
|
+
};
|
|
4857
|
+
}
|
|
4858
|
+
maxDepth;
|
|
4859
|
+
maxNodes;
|
|
4860
|
+
maxFileSize;
|
|
4861
|
+
maxTotalSize;
|
|
4862
|
+
result;
|
|
4863
|
+
visiting = /* @__PURE__ */ new Set();
|
|
4864
|
+
visitedPaths = /* @__PURE__ */ new Set();
|
|
4865
|
+
_scanner;
|
|
4866
|
+
track(entryPath, entryArgs, cwd = "") {
|
|
4867
|
+
const baseDir = cwd || process.cwd();
|
|
4868
|
+
const resolved = resolvePath(entryPath);
|
|
4869
|
+
this.result.entryPath = resolved;
|
|
4870
|
+
this.visitScript(resolved, 0, path3.dirname(resolved), entryArgs);
|
|
4871
|
+
const nodes = this.result.nodes;
|
|
4872
|
+
this.result.hasCritical = nodes.some((n) => n.l1RiskLevel === "critical" || n.l2RiskLevel === "critical" || n.keywordRiskLevel === "critical") || this.result.hasCritical;
|
|
4873
|
+
this.result.hasHigh = nodes.some((n) => ["high", "critical"].includes(n.l1RiskLevel) || ["high", "critical"].includes(n.l2RiskLevel) || ["high", "critical"].includes(n.keywordRiskLevel));
|
|
4874
|
+
this.result.firstHitNode = nodes.find((n) => n.l1Hit || n.l2Hit || n.isSuspicious) || null;
|
|
4875
|
+
return this.result;
|
|
4876
|
+
}
|
|
4877
|
+
visitScript(pathStr, depth, baseDir, args) {
|
|
4878
|
+
pathStr = resolvePath(pathStr);
|
|
4879
|
+
if (this.visiting.has(pathStr)) return;
|
|
4880
|
+
if (this.visitedPaths.has(pathStr)) return;
|
|
4881
|
+
if (depth > this.maxDepth) {
|
|
4882
|
+
this.result.maxDepthReached = true;
|
|
4883
|
+
return;
|
|
4884
|
+
}
|
|
4885
|
+
if (this.result.nodes.length >= this.maxNodes) {
|
|
4886
|
+
this.result.maxNodesReached = true;
|
|
4887
|
+
return;
|
|
4888
|
+
}
|
|
4889
|
+
if (this.result.totalSize >= this.maxTotalSize) {
|
|
4890
|
+
this.result.maxSizeReached = true;
|
|
4891
|
+
return;
|
|
4892
|
+
}
|
|
4893
|
+
this.visiting.add(pathStr);
|
|
4894
|
+
this.visitedPaths.add(pathStr);
|
|
4895
|
+
const { content, lineCount, fileSize, error } = readScript(pathStr, this.maxFileSize);
|
|
4896
|
+
if (error) {
|
|
4897
|
+
const node2 = emptyNode(pathStr, depth);
|
|
4898
|
+
node2.error = error;
|
|
4899
|
+
node2.fileSize = fileSize;
|
|
4900
|
+
node2.lineCount = lineCount;
|
|
4901
|
+
this.result.nodes.push(node2);
|
|
4902
|
+
this.visiting.delete(pathStr);
|
|
4903
|
+
return;
|
|
4904
|
+
}
|
|
4905
|
+
this.result.totalSize += fileSize;
|
|
4906
|
+
this.result.totalLines += lineCount;
|
|
4907
|
+
const ir = pickBackend(pathStr).parse(content, args);
|
|
4908
|
+
const node = emptyNode(pathStr, depth);
|
|
4909
|
+
node.rawContent = content;
|
|
4910
|
+
node.preprocessedContent = ir.reachableContent;
|
|
4911
|
+
node.lineCount = lineCount;
|
|
4912
|
+
node.fileSize = fileSize;
|
|
4913
|
+
this.result.nodes.push(node);
|
|
4914
|
+
this.result.totalNodes = this.result.nodes.length;
|
|
4915
|
+
if (this._scanner && ir.reachableContent) {
|
|
4916
|
+
const l1 = this._scanner(ir.reachableContent);
|
|
4917
|
+
if (l1.hit) {
|
|
4918
|
+
node.l1Hit = true;
|
|
4919
|
+
node.l1RiskLevel = l1.riskLevel;
|
|
4920
|
+
node.l1RiskType = l1.riskType;
|
|
4921
|
+
node.l1Reason = l1.reason;
|
|
4922
|
+
node.l1MatchedPatterns = l1.matchedPatterns || [];
|
|
4923
|
+
if (l1.riskLevel === "critical") {
|
|
4924
|
+
this.result.hasCritical = true;
|
|
4925
|
+
this.result.firstHitNode = node;
|
|
4926
|
+
this.visiting.delete(pathStr);
|
|
4927
|
+
return;
|
|
4928
|
+
}
|
|
4929
|
+
}
|
|
4930
|
+
}
|
|
4931
|
+
const parentDir = path3.dirname(pathStr);
|
|
4932
|
+
const childPaths = [];
|
|
4933
|
+
for (const call of ir.calls) {
|
|
4934
|
+
const child = resolvePath(call.path, parentDir);
|
|
4935
|
+
childPaths.push(child);
|
|
4936
|
+
this.visitScript(child, depth + 1, path3.dirname(child), call.args);
|
|
4937
|
+
if (this.result.hasCritical) break;
|
|
4938
|
+
}
|
|
4939
|
+
node.childPaths = childPaths;
|
|
4940
|
+
this.visiting.delete(pathStr);
|
|
4941
|
+
}
|
|
4942
|
+
};
|
|
4943
|
+
|
|
4944
|
+
// src/engine/script-content-analyzer.ts
|
|
4945
|
+
function emptyChain() {
|
|
4946
|
+
return {
|
|
4947
|
+
nodes: [],
|
|
4948
|
+
entryPath: "",
|
|
4949
|
+
maxDepthReached: false,
|
|
4950
|
+
maxSizeReached: false,
|
|
4951
|
+
maxNodesReached: false,
|
|
4952
|
+
totalSize: 0,
|
|
4953
|
+
totalLines: 0,
|
|
4954
|
+
totalNodes: 0,
|
|
4955
|
+
hasCritical: false,
|
|
4956
|
+
hasHigh: false,
|
|
4957
|
+
firstHitNode: null,
|
|
4958
|
+
error: ""
|
|
4959
|
+
};
|
|
4960
|
+
}
|
|
4961
|
+
var SUSPICIOUS_PATTERNS2 = [
|
|
4962
|
+
{ name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
|
|
4963
|
+
{ name: "curl --data", pattern: /curl\s+(.*\s)?--data/i },
|
|
4964
|
+
{ name: "curl -d", pattern: /curl\s+(.*\s)?-d\s/i },
|
|
4965
|
+
{ name: "wget --post-data", pattern: /wget\s+(.*\s)?--post-data/i },
|
|
4966
|
+
{ name: "wget --post-file", pattern: /wget\s+(.*\s)?--post-file/i },
|
|
4967
|
+
{ name: "nc -e", pattern: /\bnc\s+.*-e\b/ },
|
|
4968
|
+
{ name: "ncat -e", pattern: /\bncat\s+.*-e\b/ },
|
|
4969
|
+
{ name: "/dev/tcp", pattern: /\/dev\/tcp\// },
|
|
4970
|
+
{ name: "/dev/udp", pattern: /\/dev\/udp\// },
|
|
4971
|
+
{ name: "$(base64", pattern: /\$\(\s*base64/ },
|
|
4972
|
+
{ name: "`base64", pattern: /`\s*base64/ },
|
|
4973
|
+
{ name: "base64 decode pipe", pattern: /base64\s+(-d|--decode)\s*\|/ },
|
|
4974
|
+
{ name: "/etc/shadow", pattern: /\/etc\/shadow/ },
|
|
4975
|
+
{ name: "id_rsa", pattern: /\bid_rsa\b/ },
|
|
4976
|
+
{ name: "id_ed25519", pattern: /\bid_ed25519\b/ },
|
|
4977
|
+
{ name: "crontab install", pattern: /crontab\s+-/ },
|
|
4978
|
+
{ name: "authorized_keys", pattern: /authorized_keys/ },
|
|
4979
|
+
{ name: "ssh no host key check", pattern: /ssh\s+.*StrictHostKeyChecking\s*=\s*no/ }
|
|
4980
|
+
];
|
|
4981
|
+
var SCRIPT_EXEC_PATTERNS2 = [
|
|
4982
|
+
/(?:^|\s|&&|\|\|)\s*bash\s+(\S+)/i,
|
|
4983
|
+
/(?:^|\s|&&|\|\|)\s*\/bin\/bash\s+(\S+)/i,
|
|
4984
|
+
/(?:^|\s|&&|\|\|)\s*sh\s+(\S+)/i,
|
|
4985
|
+
/(?:^|\s|&&|\|\|)\s*zsh\s+(\S+)/i,
|
|
4986
|
+
/\.\/(\S+)/,
|
|
4987
|
+
/(?:^|\s|&&|\|\|)\s*source\s+(\S+)/i,
|
|
4988
|
+
/(?:^|\s|&&|\|\|)\s*\.\s+(\S+)/,
|
|
4989
|
+
/(?:^|\s|&&|\|\|)\s*python3?\s+(\S+\.py)/i,
|
|
4990
|
+
/(?:^|\s|&&|\|\|)\s*node\s+(\S+\.js)/i,
|
|
4991
|
+
/(?:^|\s|&&|\|\|)\s*ruby\s+(\S+\.rb)/i,
|
|
4992
|
+
/(?:^|\s|&&|\|\|)\s*perl\s+(\S+\.pl)/i,
|
|
4993
|
+
/(?:^|\s|&&|\|\|)\s*php\s+(\S+\.php)/i,
|
|
4994
|
+
/(?:^|\s|&&|\|\|)\s*lua\s+(\S+\.lua)/i
|
|
4995
|
+
];
|
|
4996
|
+
var KEYWORD_COMBINATION_RISKS2 = [
|
|
4997
|
+
{
|
|
4998
|
+
group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
|
|
4999
|
+
group2: /* @__PURE__ */ new Set(["$(base64", "`base64", "base64 decode pipe"]),
|
|
5000
|
+
level: "critical",
|
|
5001
|
+
reason: "\u68C0\u6D4B\u5230\u7F51\u7EDC\u6570\u636E\u53D1\u9001 + Base64\u7F16\u7801\u7EC4\u5408\uFF0C\u6784\u6210 Critical \u7EA7\u522B\u6570\u636E\u5916\u4F20\u98CE\u9669\u3002\u653B\u51FB\u8005\u53EF\u80FD\u5C06\u654F\u611F\u6570\u636E\u7F16\u7801\u540E\u5916\u4F20\u3002"
|
|
5002
|
+
},
|
|
5003
|
+
{
|
|
5004
|
+
group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
|
|
5005
|
+
group2: /* @__PURE__ */ new Set(["/etc/shadow", "id_rsa", "id_ed25519"]),
|
|
5006
|
+
level: "critical",
|
|
5007
|
+
reason: "\u68C0\u6D4B\u5230\u7F51\u7EDC\u6570\u636E\u53D1\u9001 + \u654F\u611F\u6587\u4EF6\u8BBF\u95EE\u7EC4\u5408\uFF0C\u6784\u6210 Critical \u7EA7\u522B\u51ED\u8BC1\u5916\u4F20\u98CE\u9669\u3002"
|
|
5008
|
+
},
|
|
5009
|
+
{
|
|
5010
|
+
group1: /* @__PURE__ */ new Set(["nc -e", "ncat -e", "/dev/tcp", "/dev/udp"]),
|
|
5011
|
+
group2: /* @__PURE__ */ new Set(),
|
|
5012
|
+
level: "high",
|
|
5013
|
+
reason: "\u68C0\u6D4B\u5230\u53CD\u5F39 Shell \u7279\u5F81\uFF0C\u653B\u51FB\u8005\u53EF\u80FD\u5EFA\u7ACB\u8FDC\u7A0B\u63A7\u5236\u901A\u9053\u3002"
|
|
5014
|
+
},
|
|
5015
|
+
{
|
|
5016
|
+
group1: /* @__PURE__ */ new Set(["crontab install", "authorized_keys", "ssh no host key check"]),
|
|
5017
|
+
group2: /* @__PURE__ */ new Set(),
|
|
5018
|
+
level: "high",
|
|
5019
|
+
reason: "\u68C0\u6D4B\u5230\u6301\u4E45\u5316\u540E\u95E8\u7279\u5F81\uFF0C\u653B\u51FB\u8005\u53EF\u80FD\u5728\u7CFB\u7EDF\u4E2D\u690D\u5165\u540E\u95E8\u3002"
|
|
5020
|
+
}
|
|
5021
|
+
];
|
|
5022
|
+
var SINGLE_KEYWORD_RISKS2 = {
|
|
5023
|
+
"curl POST": { level: "high", reason: "\u68C0\u6D4B\u5230 curl POST \u8BF7\u6C42\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
|
|
5024
|
+
"curl --data": { level: "high", reason: "\u68C0\u6D4B\u5230 curl --data \u53C2\u6570\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
|
|
5025
|
+
"curl -d": { level: "high", reason: "\u68C0\u6D4B\u5230 curl -d \u53C2\u6570\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
|
|
5026
|
+
"wget --post-data": { level: "high", reason: "\u68C0\u6D4B\u5230 wget POST \u8BF7\u6C42\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
|
|
5027
|
+
"wget --post-file": { level: "high", reason: "\u68C0\u6D4B\u5230 wget \u6587\u4EF6\u4E0A\u4F20\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6587\u4EF6\u3002" },
|
|
5028
|
+
"nc -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 nc -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
|
|
5029
|
+
"ncat -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 ncat -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
|
|
5030
|
+
"/dev/tcp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/tcp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
|
|
5031
|
+
"/dev/udp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/udp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
|
|
5032
|
+
"$(base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
|
|
5033
|
+
"`base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
|
|
5034
|
+
"base64 decode pipe": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u89E3\u7801\u540E\u7BA1\u9053\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
|
|
5035
|
+
"/etc/shadow": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6\u3002" },
|
|
5036
|
+
"id_rsa": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
|
|
5037
|
+
"id_ed25519": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
|
|
5038
|
+
"crontab install": { level: "high", reason: "\u68C0\u6D4B\u5230\u4FEE\u6539\u5B9A\u65F6\u4EFB\u52A1\uFF0C\u53EF\u80FD\u662F\u6301\u4E45\u5316\u540E\u95E8\u3002" },
|
|
5039
|
+
"authorized_keys": { level: "high", reason: "\u68C0\u6D4B\u5230\u4FEE\u6539 SSH \u6388\u6743\u5BC6\u94A5\uFF0C\u53EF\u80FD\u662F\u6301\u4E45\u5316\u540E\u95E8\u3002" },
|
|
5040
|
+
"ssh no host key check": { level: "high", reason: "\u68C0\u6D4B\u5230 SSH \u8DF3\u8FC7\u4E3B\u673A\u5BC6\u94A5\u9A8C\u8BC1\uFF0C\u53EF\u80FD\u662F\u6A2A\u5411\u79FB\u52A8\u3002" }
|
|
5041
|
+
};
|
|
5042
|
+
var LEVEL_ORDER2 = { low: 1, medium: 2, high: 3, critical: 4 };
|
|
5043
|
+
function scanScriptForKeywords2(content) {
|
|
5044
|
+
const matched = [];
|
|
5045
|
+
for (const { name, pattern } of SUSPICIOUS_PATTERNS2) {
|
|
5046
|
+
if (pattern.test(content)) matched.push(name);
|
|
5047
|
+
}
|
|
5048
|
+
return matched;
|
|
5049
|
+
}
|
|
5050
|
+
function assessKeywordCombinationRisk2(matchedKeywords, scriptContent) {
|
|
5051
|
+
if (matchedKeywords.length === 0) return { level: "", reason: "" };
|
|
5052
|
+
const keywordSet = new Set(matchedKeywords);
|
|
5053
|
+
for (const rule of KEYWORD_COMBINATION_RISKS2) {
|
|
5054
|
+
const hitGroup1 = [...rule.group1].some((k) => keywordSet.has(k));
|
|
5055
|
+
const hitGroup2 = rule.group2.size === 0 ? true : [...rule.group2].some((k) => keywordSet.has(k));
|
|
5056
|
+
if (hitGroup1 && hitGroup2) return { level: rule.level, reason: rule.reason };
|
|
5057
|
+
}
|
|
5058
|
+
const networkPostKeywords = /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]);
|
|
5059
|
+
const hasNetworkPost = [...networkPostKeywords].some((k) => keywordSet.has(k));
|
|
5060
|
+
if (hasNetworkPost) {
|
|
5061
|
+
const envPatterns = ["$env", "$(env)", "$USER", "$(hostname)", "$(pwd)", "$HOME"];
|
|
5062
|
+
const contentLower = scriptContent.toLowerCase();
|
|
5063
|
+
for (const p of envPatterns) {
|
|
5064
|
+
if (contentLower.includes(p.toLowerCase())) {
|
|
5065
|
+
return {
|
|
5066
|
+
level: "critical",
|
|
5067
|
+
reason: "\u68C0\u6D4B\u5230\u7F51\u7EDC\u6570\u636E\u53D1\u9001 + \u7CFB\u7EDF\u73AF\u5883\u53D8\u91CF\u6536\u96C6\u7EC4\u5408\uFF0C\u6784\u6210 Critical \u7EA7\u522B\u6570\u636E\u5916\u4F20\u98CE\u9669\u3002\u811A\u672C\u53EF\u80FD\u5C06\u7528\u6237\u73AF\u5883\u53D8\u91CF\uFF08\u53EF\u80FD\u5305\u542B API Key\u3001\u5BC6\u94A5\u7B49\u654F\u611F\u4FE1\u606F\uFF09\u53D1\u9001\u5230\u8FDC\u7A0B\u670D\u52A1\u5668\u3002"
|
|
5068
|
+
};
|
|
5069
|
+
}
|
|
5070
|
+
}
|
|
5071
|
+
}
|
|
5072
|
+
let bestLevel = "";
|
|
5073
|
+
let bestReason = "";
|
|
5074
|
+
for (const kw of matchedKeywords) {
|
|
5075
|
+
const risk = SINGLE_KEYWORD_RISKS2[kw];
|
|
5076
|
+
if (risk && (LEVEL_ORDER2[risk.level] || 0) > (LEVEL_ORDER2[bestLevel] || 0)) {
|
|
5077
|
+
bestLevel = risk.level;
|
|
5078
|
+
bestReason = risk.reason;
|
|
5079
|
+
}
|
|
5080
|
+
}
|
|
5081
|
+
return { level: bestLevel, reason: bestReason };
|
|
5082
|
+
}
|
|
5083
|
+
function extractScriptPath2(command) {
|
|
5084
|
+
for (const pattern of SCRIPT_EXEC_PATTERNS2) {
|
|
5085
|
+
const m = command.match(pattern);
|
|
5086
|
+
if (m) return m[1];
|
|
5087
|
+
}
|
|
5088
|
+
return null;
|
|
5089
|
+
}
|
|
5090
|
+
function extractScriptPathWithArgs(command) {
|
|
5091
|
+
const scriptPath = extractScriptPath2(command);
|
|
5092
|
+
if (!scriptPath) return { path: null, args: [] };
|
|
5093
|
+
const idx = command.indexOf(scriptPath);
|
|
5094
|
+
if (idx < 0) return { path: scriptPath, args: [] };
|
|
5095
|
+
let rest = command.slice(idx + scriptPath.length);
|
|
5096
|
+
rest = rest.split(/[;|&<>]/)[0].trim();
|
|
5097
|
+
const tokens = [...rest.matchAll(/(["'])(.*?)\1|(\S+)/g)];
|
|
5098
|
+
const args = [];
|
|
5099
|
+
for (const m of tokens) {
|
|
5100
|
+
args.push(m[1] ? m[2] : m[3]);
|
|
5101
|
+
}
|
|
5102
|
+
return { path: scriptPath, args };
|
|
5103
|
+
}
|
|
5104
|
+
function extractInlineScriptPath(actionDetail) {
|
|
5105
|
+
const re = /open\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
5106
|
+
let m;
|
|
5107
|
+
while ((m = re.exec(actionDetail)) !== null) {
|
|
5108
|
+
const p = m[1];
|
|
5109
|
+
let resolved;
|
|
5110
|
+
try {
|
|
5111
|
+
resolved = resolvePath2(p);
|
|
5112
|
+
if (existsSync9(resolved)) {
|
|
5113
|
+
const stat = statSync2(resolved);
|
|
5114
|
+
if (stat.isFile()) return resolved;
|
|
5115
|
+
}
|
|
5116
|
+
} catch {
|
|
5117
|
+
}
|
|
5118
|
+
}
|
|
5119
|
+
return null;
|
|
5120
|
+
}
|
|
5121
|
+
function resolvePath2(p) {
|
|
5122
|
+
let expanded = p;
|
|
5123
|
+
if (expanded.startsWith("~/") || expanded === "~") {
|
|
5124
|
+
expanded = join5(homedir3(), expanded.slice(1));
|
|
5125
|
+
}
|
|
5126
|
+
return resolve8(expanded);
|
|
5127
|
+
}
|
|
5128
|
+
function scanScriptChainWithL1(chain, input) {
|
|
5129
|
+
for (const node of chain.nodes) {
|
|
5130
|
+
if (node.error || !node.preprocessedContent || node.l1Hit) continue;
|
|
5131
|
+
const virtualInput = {
|
|
5132
|
+
...input,
|
|
5133
|
+
aNext: { actionType: "bash", actionDetail: node.preprocessedContent }
|
|
5134
|
+
};
|
|
5135
|
+
const l1 = runHeuristicCheck(virtualInput);
|
|
5136
|
+
if (l1 && !l1.allowed) {
|
|
5137
|
+
node.l1Hit = true;
|
|
5138
|
+
node.l1RiskLevel = l1.riskLevel;
|
|
5139
|
+
node.l1RiskType = l1.riskType;
|
|
5140
|
+
node.l1Reason = l1.reason;
|
|
5141
|
+
if (l1.riskLevel === "critical") {
|
|
5142
|
+
chain.hasCritical = true;
|
|
5143
|
+
chain.firstHitNode = node;
|
|
5144
|
+
break;
|
|
5145
|
+
}
|
|
5146
|
+
}
|
|
5147
|
+
}
|
|
5148
|
+
return chain;
|
|
5149
|
+
}
|
|
5150
|
+
function scanScriptChainWithL2(chain, input) {
|
|
5151
|
+
for (const node of chain.nodes) {
|
|
5152
|
+
if (node.error || !node.preprocessedContent) continue;
|
|
5153
|
+
if (node.l1RiskLevel === "critical") break;
|
|
5154
|
+
const virtualInput = {
|
|
5155
|
+
...input,
|
|
5156
|
+
aNext: { actionType: "bash", actionDetail: node.preprocessedContent }
|
|
5157
|
+
};
|
|
5158
|
+
const l2 = runLogicCheck(virtualInput);
|
|
5159
|
+
if (l2 && !l2.allowed) {
|
|
5160
|
+
node.l2Hit = true;
|
|
5161
|
+
node.l2ViolatedRule = `script_logic:${node.scriptPath}`;
|
|
5162
|
+
node.l2RiskLevel = l2.riskLevel;
|
|
5163
|
+
node.l2RiskType = l2.riskType;
|
|
5164
|
+
node.l2Reason = `\u811A\u672C ${node.scriptPath} (\u6DF1\u5EA6 ${node.depth}) \u4E2D: ${l2.reason}`;
|
|
5165
|
+
if (l2.riskLevel === "critical") {
|
|
5166
|
+
chain.hasCritical = true;
|
|
5167
|
+
chain.firstHitNode = node;
|
|
5168
|
+
break;
|
|
5169
|
+
}
|
|
5170
|
+
}
|
|
5171
|
+
}
|
|
5172
|
+
if (!chain.hasHigh) {
|
|
5173
|
+
chain.hasHigh = chain.nodes.some((n) => ["high", "critical"].includes(n.l2RiskLevel));
|
|
5174
|
+
}
|
|
5175
|
+
return chain;
|
|
5176
|
+
}
|
|
5177
|
+
function scanScriptChainKeywords(chain) {
|
|
5178
|
+
for (const node of chain.nodes) {
|
|
5179
|
+
if (node.error || !node.preprocessedContent) continue;
|
|
5180
|
+
if (node.l1Hit || node.l2Hit) continue;
|
|
5181
|
+
const matched = scanScriptForKeywords2(node.preprocessedContent);
|
|
5182
|
+
node.matchedKeywords = matched;
|
|
5183
|
+
node.isSuspicious = matched.length > 0;
|
|
5184
|
+
if (matched.length > 0) {
|
|
5185
|
+
const risk = assessKeywordCombinationRisk2(matched, node.preprocessedContent);
|
|
5186
|
+
node.keywordRiskLevel = risk.level;
|
|
5187
|
+
node.keywordRiskReason = risk.reason;
|
|
5188
|
+
}
|
|
5189
|
+
}
|
|
5190
|
+
return chain;
|
|
5191
|
+
}
|
|
5192
|
+
function analyzeScriptContent2(input) {
|
|
5193
|
+
const actionType = input.aNext.actionType.toLowerCase();
|
|
5194
|
+
if (actionType !== "bash") return emptyChain();
|
|
5195
|
+
const { path: scriptPath, args: scriptArgs } = extractScriptPathWithArgs(input.aNext.actionDetail);
|
|
5196
|
+
let resolvedPath = scriptPath;
|
|
5197
|
+
let isInline = false;
|
|
5198
|
+
if (!resolvedPath) {
|
|
5199
|
+
const inline = extractInlineScriptPath(input.aNext.actionDetail);
|
|
5200
|
+
if (!inline) return emptyChain();
|
|
5201
|
+
resolvedPath = inline;
|
|
5202
|
+
isInline = true;
|
|
5203
|
+
}
|
|
5204
|
+
if (resolvedPath && !isAbsolute2(resolvedPath) && !resolvedPath.startsWith("~/")) {
|
|
5205
|
+
resolvedPath = join5(input.cwd || process.cwd(), resolvedPath);
|
|
5206
|
+
}
|
|
5207
|
+
const l1Adapter = (content) => {
|
|
5208
|
+
const virtualInput = {
|
|
5209
|
+
...input,
|
|
5210
|
+
aNext: { actionType: "bash", actionDetail: content }
|
|
5211
|
+
};
|
|
5212
|
+
const j = runHeuristicCheck(virtualInput);
|
|
5213
|
+
if (j && !j.allowed) {
|
|
5214
|
+
return { hit: true, riskLevel: j.riskLevel, riskType: j.riskType, reason: j.reason, matchedPatterns: [] };
|
|
5215
|
+
}
|
|
5216
|
+
return { hit: false, riskLevel: "", riskType: "", reason: "", matchedPatterns: [] };
|
|
5217
|
+
};
|
|
5218
|
+
const tracker = new RecursiveTracker(l1Adapter);
|
|
5219
|
+
const chain = tracker.track(resolvedPath, isInline ? null : scriptArgs, input.cwd || process.cwd());
|
|
5220
|
+
if (chain.nodes.length === 0) return chain;
|
|
5221
|
+
scanScriptChainWithL1(chain, input);
|
|
5222
|
+
scanScriptChainWithL2(chain, input);
|
|
5223
|
+
scanScriptChainKeywords(chain);
|
|
5224
|
+
return chain;
|
|
5225
|
+
}
|
|
5226
|
+
function formatScriptChainForPrompt(chain) {
|
|
5227
|
+
if (chain.nodes.length === 0) return "";
|
|
5228
|
+
const errorNodes = chain.nodes.filter((n) => n.error);
|
|
5229
|
+
if (errorNodes.length > 0 && chain.nodes.length === 1) {
|
|
5230
|
+
return `\u26A0\uFE0F \u811A\u672C\u5185\u5BB9\u5206\u6790\u5931\u8D25: ${errorNodes[0].error}`;
|
|
5231
|
+
}
|
|
5232
|
+
const suspiciousNodes = chain.nodes.filter((n) => n.isSuspicious && !n.l1Hit && !n.l2Hit);
|
|
5233
|
+
if (suspiciousNodes.length === 0) {
|
|
5234
|
+
const safeNodes = chain.nodes.filter((n) => !n.error);
|
|
5235
|
+
if (safeNodes.length === 0) return "";
|
|
5236
|
+
const parts2 = ["\u2713 \u811A\u672C\u5185\u5BB9\u5DF2\u5206\u6790\uFF0C\u672A\u53D1\u73B0\u5371\u9669/\u53EF\u7591\u64CD\u4F5C\uFF08L1/L2/\u5173\u952E\u8BCD\u626B\u63CF\u5747\u672A\u547D\u4E2D\uFF09\uFF1A"];
|
|
5237
|
+
for (const node of safeNodes) {
|
|
5238
|
+
const pre = node.preprocessedContent || "";
|
|
5239
|
+
let preview = pre.slice(0, 600);
|
|
5240
|
+
if (pre.length > 600) preview += `
|
|
5241
|
+
... (\u622A\u65AD\uFF0C\u5B8C\u6574\u5185\u5BB9 ${pre.length} \u5B57\u7B26)`;
|
|
5242
|
+
parts2.push(
|
|
5243
|
+
` \u811A\u672C\u8DEF\u5F84: ${node.scriptPath}\uFF08\u9012\u5F52\u6DF1\u5EA6 ${node.depth}\uFF0C${node.lineCount} \u884C\uFF09
|
|
5244
|
+
\u5185\u5BB9\uFF08\u9884\u5904\u7406\u540E\uFF09:
|
|
5245
|
+
\`\`\`
|
|
5246
|
+
${preview}
|
|
5247
|
+
\`\`\``
|
|
5248
|
+
);
|
|
5249
|
+
}
|
|
5250
|
+
return parts2.join("\n");
|
|
5251
|
+
}
|
|
5252
|
+
const parts = [];
|
|
5253
|
+
for (const node of suspiciousNodes) {
|
|
5254
|
+
const keywordsStr = node.matchedKeywords.join(", ");
|
|
5255
|
+
let riskHint = "";
|
|
5256
|
+
if (node.keywordRiskLevel && node.keywordRiskReason) {
|
|
5257
|
+
riskHint = `
|
|
5258
|
+
|
|
5259
|
+
\u{1F534} **\u98CE\u9669\u8BC4\u4F30**: ${node.keywordRiskLevel.toUpperCase()} \u7EA7\u522B
|
|
5260
|
+
**\u98CE\u9669\u539F\u56E0**: ${node.keywordRiskReason}
|
|
5261
|
+
**\u5EFA\u8BAE**: \u6B64\u811A\u672C\u5E94\u88AB\u62D2\u7EDD\uFF08Deny\uFF09\uFF0C\u9664\u975E\u6709\u660E\u786E\u7684\u4E1A\u52A1\u9700\u6C42\u4E14\u5DF2\u786E\u8BA4\u76EE\u6807\u670D\u52A1\u5668\u53EF\u4FE1\u3002`;
|
|
5262
|
+
}
|
|
5263
|
+
const pre = node.preprocessedContent;
|
|
5264
|
+
let contentPreview = pre.slice(0, 2e3);
|
|
5265
|
+
if (pre.length > 2e3) contentPreview += `
|
|
5266
|
+
... (\u622A\u65AD\uFF0C\u5B8C\u6574\u5185\u5BB9 ${pre.length} \u5B57\u7B26)`;
|
|
5267
|
+
parts.push(
|
|
5268
|
+
`\u26A0\uFE0F \u811A\u672C\u5185\u5BB9\u5305\u542B\u53EF\u7591\u5173\u952E\u8BCD:
|
|
5269
|
+
\u811A\u672C\u8DEF\u5F84: ${node.scriptPath}
|
|
5270
|
+
\u9012\u5F52\u6DF1\u5EA6: ${node.depth}
|
|
5271
|
+
\u53EF\u7591\u5173\u952E\u8BCD: ${keywordsStr}
|
|
5272
|
+
\u811A\u672C\u884C\u6570: ${node.lineCount}${riskHint}
|
|
5273
|
+
|
|
5274
|
+
### \u811A\u672C\u5185\u5BB9\uFF08\u9884\u5904\u7406\u540E\uFF09###
|
|
5275
|
+
\`\`\`
|
|
5276
|
+
${contentPreview}
|
|
5277
|
+
\`\`\`
|
|
5278
|
+
`
|
|
5279
|
+
);
|
|
5280
|
+
}
|
|
5281
|
+
let header = `\u26A0\uFE0F \u68C0\u6D4B\u5230\u811A\u672C\u6267\u884C\u94FE\uFF08\u5171 ${chain.nodes.length} \u4E2A\u811A\u672C\uFF09\uFF0C\u5176\u4E2D ${suspiciousNodes.length} \u4E2A\u5305\u542B\u53EF\u7591\u5173\u952E\u8BCD:
|
|
5282
|
+
`;
|
|
5283
|
+
if (chain.maxDepthReached) header += "\u26A0\uFE0F \u6CE8\u610F\uFF1A\u9012\u5F52\u8FFD\u8E2A\u89E6\u8FBE\u6DF1\u5EA6\u4E0A\u9650\uFF0C\u53EF\u80FD\u5B58\u5728\u66F4\u6DF1\u5C42\u7684\u811A\u672C\u8C03\u7528\u672A\u88AB\u626B\u63CF\u3002\n";
|
|
5284
|
+
if (chain.maxSizeReached) header += "\u26A0\uFE0F \u6CE8\u610F\uFF1A\u7D2F\u8BA1\u6587\u4EF6\u5927\u5C0F\u89E6\u8FBE\u4E0A\u9650\uFF0C\u90E8\u5206\u811A\u672C\u53EF\u80FD\u672A\u88AB\u5B8C\u6574\u8BFB\u53D6\u3002\n";
|
|
5285
|
+
return header + parts.join("\n---\n\n");
|
|
5286
|
+
}
|
|
5287
|
+
|
|
5288
|
+
// src/engine/llm-analyzer.ts
|
|
5289
|
+
import { appendFileSync } from "fs";
|
|
5290
|
+
import OpenAI from "openai";
|
|
5291
|
+
var llmCache = /* @__PURE__ */ new Map();
|
|
5292
|
+
var LLM_CACHE_MAX = 200;
|
|
5293
|
+
async function runLLMAnalysis(input, previous) {
|
|
5294
|
+
if (!isLLMAvailable()) {
|
|
5295
|
+
console.log("[AgentMoss] LLM not configured, skipping deep analysis");
|
|
5296
|
+
return {
|
|
5297
|
+
allowed: true,
|
|
5298
|
+
reason: "LLM \u672A\u914D\u7F6E\uFF0C\u8DF3\u8FC7\u6DF1\u5C42\u5206\u6790",
|
|
5299
|
+
riskLevel: "low",
|
|
5300
|
+
riskType: "",
|
|
5301
|
+
confidence: 50,
|
|
5302
|
+
source: "llm",
|
|
5303
|
+
violatedLayers: []
|
|
5304
|
+
};
|
|
5305
|
+
}
|
|
5306
|
+
const globalConfig = getLLMConfig();
|
|
5307
|
+
const config = input.llmConfig?.apiKey ? { ...globalConfig, ...input.llmConfig } : globalConfig;
|
|
5308
|
+
const rawDetail = input.aNext.actionDetail.replace(/^terminal-\w+:\s*command=([^,]+).*/, "$1").trim();
|
|
5309
|
+
const cacheKey = `${input.aNext.actionType}:${rawDetail || input.aNext.actionDetail}`;
|
|
5310
|
+
const cached = llmCache.get(cacheKey);
|
|
5311
|
+
if (cached) {
|
|
5312
|
+
console.log("[AgentMoss] LLM cache hit");
|
|
5313
|
+
return cached;
|
|
5314
|
+
}
|
|
5315
|
+
const matchedSkills = matchSkills(input.aNext.actionType, input.aNext.actionDetail, input.reason);
|
|
5316
|
+
const templates = loadTemplates();
|
|
5317
|
+
let scriptAnalysis;
|
|
5318
|
+
let scriptChainText = "";
|
|
5319
|
+
if (previous.scriptChain) {
|
|
5320
|
+
scriptChainText = formatScriptChainForPrompt(previous.scriptChain);
|
|
5321
|
+
} else {
|
|
5322
|
+
scriptAnalysis = analyzeScriptContent(input.aNext.actionType, input.aNext.actionDetail);
|
|
5323
|
+
}
|
|
5324
|
+
const prompt = buildPrompt(input, previous, matchedSkills, templates, scriptAnalysis, previous.skipL3Hints ?? "", scriptChainText);
|
|
5325
|
+
logPrompt(prompt, input.aNext, previous.llmLogPath);
|
|
5326
|
+
const result = await analyzeWithLLM(prompt, config);
|
|
5327
|
+
if (llmCache.size >= LLM_CACHE_MAX) {
|
|
5328
|
+
const firstKey = llmCache.keys().next().value;
|
|
5329
|
+
if (firstKey) llmCache.delete(firstKey);
|
|
5330
|
+
}
|
|
5331
|
+
llmCache.set(cacheKey, result);
|
|
5332
|
+
return result;
|
|
5333
|
+
}
|
|
5334
|
+
function logPrompt(prompt, aNext, logPath) {
|
|
5335
|
+
if (!logPath) return;
|
|
5336
|
+
try {
|
|
5337
|
+
const actionType = aNext.actionType || "unknown";
|
|
5338
|
+
const actionDetail = (aNext.actionDetail || "").slice(0, 100);
|
|
5339
|
+
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] [LLM_PROMPT] action_type=${actionType}, action_detail=${actionDetail}... prompt_length=${prompt.length}
|
|
5340
|
+
${prompt}
|
|
5341
|
+
--- END PROMPT ---
|
|
5342
|
+
`;
|
|
5343
|
+
appendFileSync(logPath, line, "utf-8");
|
|
5344
|
+
} catch (e) {
|
|
5345
|
+
console.warn("[AgentMoss] Failed to write LLM prompt to log:", e);
|
|
5346
|
+
}
|
|
5347
|
+
}
|
|
5348
|
+
function buildPrompt(input, previous, matchedSkills, templates, scriptAnalysis, skipL3Hints = "", scriptChainText = "") {
|
|
5349
|
+
const skillsText = formatSkillsForPrompt(matchedSkills);
|
|
5350
|
+
const historyStr = input.actionHistory.map((h, i) => `${i + 1}. ${h.actionType}: ${h.actionDetail}`).join("\n") || "(\u65E0\u5386\u53F2\u64CD\u4F5C)";
|
|
5351
|
+
const promptHistoryText = formatPromptHistory(input.promptHistory);
|
|
5352
|
+
const scriptAnalysisText = scriptChainText ? scriptChainText : scriptAnalysis ? formatScriptAnalysisForPrompt(scriptAnalysis) : "";
|
|
5353
|
+
const hintsText = formatHints(previous);
|
|
5354
|
+
const securityJudgeTemplate = templates["security_judge"];
|
|
5355
|
+
if (securityJudgeTemplate) {
|
|
5356
|
+
return securityJudgeTemplate.replaceAll("{prompt_session}", input.promptSession || "\uFF08\u672A\u63D0\u4F9B\uFF09").replaceAll("{prompt_history_text}", promptHistoryText).replaceAll("{action_history}", historyStr).replaceAll("{action_type}", input.aNext.actionType).replaceAll("{action_detail}", input.aNext.actionDetail).replaceAll("{reason}", input.reason || "\u672A\u8BF4\u660E").replaceAll("{skills_text}", skillsText).replaceAll("{hints_text}", hintsText).replaceAll("{script_analysis_text}", scriptAnalysisText).replaceAll("{skip_l3_hints}", skipL3Hints).replaceAll("{{", "{").replaceAll("}}", "}");
|
|
5357
|
+
}
|
|
5358
|
+
console.warn("[AgentMoss] security_judge template missing, using sparse fallback prompt");
|
|
5359
|
+
return `## \u5F53\u524D\u64CD\u4F5C\u4E0A\u4E0B\u6587
|
|
5360
|
+
- \u4F1A\u8BDD ID: ${input.sessionId}
|
|
5361
|
+
- \u7528\u6237\u4EFB\u52A1: ${input.promptSession || "\u672A\u6307\u5B9A"}
|
|
5362
|
+
- \u64CD\u4F5C\u7CFB\u7EDF: ${input.osType}
|
|
5363
|
+
- \u5DE5\u4F5C\u76EE\u5F55: ${input.cwd || "/"}
|
|
5364
|
+
- \u64CD\u4F5C\u539F\u56E0: ${input.reason || "\u672A\u8BF4\u660E"}
|
|
5365
|
+
- \u5386\u53F2\u64CD\u4F5C\u5E8F\u5217:
|
|
5366
|
+
${historyStr}
|
|
5367
|
+
|
|
5368
|
+
- \u5373\u5C06\u6267\u884C\u7684\u64CD\u4F5C:
|
|
5369
|
+
\u7C7B\u578B: ${input.aNext.actionType}
|
|
5370
|
+
\u8BE6\u60C5: ${input.aNext.actionDetail}
|
|
5371
|
+
|
|
5372
|
+
## \u53EF\u7528\u5B89\u5168 Skill
|
|
5373
|
+
${skillsText}
|
|
5374
|
+
|
|
5375
|
+
## \u524D\u7F6E\u68C0\u6D4B\u7ED3\u679C
|
|
5376
|
+
${hintsText}
|
|
5377
|
+
${scriptAnalysisText ? `
|
|
5378
|
+
## \u811A\u672C\u5185\u5BB9\u5206\u6790
|
|
5379
|
+
${scriptAnalysisText}` : ""}
|
|
5380
|
+
|
|
5381
|
+
\u8BF7\u6839\u636E\u4EE5\u4E0A\u4FE1\u606F\uFF0C\u5BF9\u5373\u5C06\u6267\u884C\u7684\u64CD\u4F5C\u8FDB\u884C\u5B89\u5168\u5224\u65AD\u3002\u524D\u7F6E\u68C0\u6D4B\u4E3A low/medium \u4E14\u6709\u5408\u7406\u6267\u884C\u7406\u7531\u65F6\u5E94\u5F53\u5141\u8BB8\u3002\u4F60\u5FC5\u987B\u8F93\u51FA\u4E25\u683C\u7684 JSON \u683C\u5F0F\uFF0C\u4E0D\u8981\u5305\u542B\u4EFB\u4F55\u5176\u4ED6\u6587\u5B57\u3001\u6CE8\u91CA\u6216 markdown \u6807\u8BB0\u3002
|
|
5382
|
+
|
|
5383
|
+
\u8F93\u51FA\u683C\u5F0F:
|
|
5384
|
+
{"allowed": true/false, "reason": "\u5224\u65AD\u7406\u7531", "riskLevel": "low|medium|high|critical", "riskType": "\u98CE\u9669\u7C7B\u578B", "confidence": 0-100}`;
|
|
5385
|
+
}
|
|
5386
|
+
function formatPromptHistory(promptHistory, maxTextChars = 500) {
|
|
5387
|
+
if (!promptHistory || promptHistory.length === 0) {
|
|
5388
|
+
return "\uFF08\u672A\u63D0\u4F9B\u5386\u53F2\u610F\u56FE\uFF09";
|
|
5389
|
+
}
|
|
5390
|
+
const lines = [];
|
|
5391
|
+
for (let i = 0; i < promptHistory.length; i++) {
|
|
5392
|
+
const turn = promptHistory[i];
|
|
5393
|
+
const text = (typeof turn === "string" ? turn : turn?.text || "").trim();
|
|
5394
|
+
if (!text) continue;
|
|
5395
|
+
let line = ` \u7B2C${i + 1}\u8F6E\u7528\u6237\u610F\u56FE: ${text.slice(0, maxTextChars)}`;
|
|
5396
|
+
const actions = typeof turn === "string" ? void 0 : turn?.actions;
|
|
5397
|
+
if (actions && actions.length > 0) {
|
|
5398
|
+
const types = actions.map((a) => a?.actionType).filter((t) => typeof t === "string" && t.length > 0);
|
|
5399
|
+
if (types.length > 0) {
|
|
5400
|
+
line += ` \u2192 \u52A8\u4F5C: ${types.join(", ")}`;
|
|
5401
|
+
}
|
|
5402
|
+
}
|
|
5403
|
+
lines.push(line);
|
|
5404
|
+
}
|
|
5405
|
+
return lines.length > 0 ? lines.join("\n") : "\uFF08\u672A\u63D0\u4F9B\u5386\u53F2\u610F\u56FE\uFF09";
|
|
5406
|
+
}
|
|
5407
|
+
function formatHints(previous) {
|
|
5408
|
+
const hints = [];
|
|
5409
|
+
if (previous.heuristic) {
|
|
5410
|
+
hints.push(
|
|
5411
|
+
`\u7279\u5F81\u5339\u914D\u68C0\u6D4B\u53D1\u73B0\u98CE\u9669 [\u98CE\u9669\u7B49\u7EA7: ${previous.heuristic.riskLevel}]:
|
|
5412
|
+
\u7C7B\u578B: ${previous.heuristic.riskType}
|
|
5413
|
+
\u539F\u56E0: ${previous.heuristic.reason}`
|
|
5414
|
+
);
|
|
5415
|
+
}
|
|
5416
|
+
if (previous.logic) {
|
|
5417
|
+
hints.push(
|
|
5418
|
+
`\u903B\u8F91\u89C4\u5219\u68C0\u6D4B\u53D1\u73B0\u98CE\u9669 [\u98CE\u9669\u7B49\u7EA7: ${previous.logic.riskLevel}]:
|
|
5419
|
+
\u7C7B\u578B: ${previous.logic.riskType}
|
|
5420
|
+
\u539F\u56E0: ${previous.logic.reason}`
|
|
5421
|
+
);
|
|
5422
|
+
}
|
|
5423
|
+
return hints.length > 0 ? hints.join("\n\n") : "\u7279\u5F81\u5339\u914D\u68C0\u6D4B\u548C\u903B\u8F91\u89C4\u5219\u68C0\u6D4B\u5747\u672A\u53D1\u73B0\u98CE\u9669\u3002";
|
|
5424
|
+
}
|
|
5425
|
+
async function analyzeWithLLM(prompt, config) {
|
|
5426
|
+
console.log(`[AgentMoss] LLM analyzing with model: ${config.model}, url: ${config.baseUrl}`);
|
|
5427
|
+
const provider = detectProvider(config.baseUrl, config.provider);
|
|
5428
|
+
const extraHeaders = getProviderHeaders(provider);
|
|
5429
|
+
if (Object.keys(extraHeaders).length > 0) {
|
|
5430
|
+
console.log(`[AgentMoss] Provider detected: ${provider}, injecting headers: ${Object.keys(extraHeaders).join(", ")}`);
|
|
5431
|
+
}
|
|
5432
|
+
const timeoutCfg = getTimeoutConfig();
|
|
5433
|
+
const retryCfg = getRetryConfig();
|
|
5434
|
+
const client = new OpenAI({
|
|
5435
|
+
baseURL: config.baseUrl,
|
|
5436
|
+
apiKey: config.apiKey,
|
|
5437
|
+
// 单次 LLM 调用超时用 prompt1Timeout(对齐 py);env 覆盖的 config.timeout 仍向后兼容
|
|
5438
|
+
timeout: (config.timeout || timeoutCfg.prompt1Timeout) * 1e3,
|
|
5439
|
+
defaultHeaders: Object.keys(extraHeaders).length > 0 ? extraHeaders : void 0
|
|
5440
|
+
});
|
|
5441
|
+
const maxRetries = parseInt(process.env.AGENT_MOSS_LLM_RETRIES || String(retryCfg.maxRetries), 10);
|
|
5442
|
+
const totalTimeoutMs = timeoutCfg.totalTimeout * 1e3;
|
|
5443
|
+
const abortController = new AbortController();
|
|
5444
|
+
const totalTimeoutTimer = setTimeout(() => {
|
|
5445
|
+
console.error(`[AgentMoss] totalTimeout (${timeoutCfg.totalTimeout}s) exceeded, aborting all LLM calls`);
|
|
5446
|
+
abortController.abort();
|
|
5447
|
+
}, totalTimeoutMs);
|
|
5448
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
5449
|
+
if (abortController.signal.aborted) {
|
|
5450
|
+
break;
|
|
5451
|
+
}
|
|
5452
|
+
try {
|
|
5453
|
+
const response = await client.chat.completions.create({
|
|
5454
|
+
model: config.model,
|
|
5455
|
+
temperature: config.temperature || 0.1,
|
|
5456
|
+
max_tokens: config.maxTokens || 1024,
|
|
5457
|
+
messages: [
|
|
5458
|
+
{ role: "system", content: "\u53EA\u8F93\u51FA\u7EAF JSON\uFF0C\u4E0D\u8981\u5305\u542B markdown \u6807\u8BB0\u3001\u4EE3\u7801\u5757\u6216\u4EFB\u4F55\u5176\u4ED6\u6587\u5B57\u3002" },
|
|
5459
|
+
{ role: "user", content: prompt }
|
|
5460
|
+
],
|
|
5461
|
+
signal: abortController.signal
|
|
5462
|
+
});
|
|
5463
|
+
const content = response.choices[0]?.message?.content || "{}";
|
|
5464
|
+
console.log(`[AgentMoss] LLM raw response (attempt ${attempt + 1}/${maxRetries + 1}): ${content.substring(0, 300)}`);
|
|
5465
|
+
let jsonStr = content.trim();
|
|
5466
|
+
if (jsonStr.startsWith("```")) {
|
|
5467
|
+
jsonStr = jsonStr.replace(/^```[\w]*\n?/, "").replace(/\n?```$/, "");
|
|
5468
|
+
}
|
|
5469
|
+
const parsed = JSON.parse(jsonStr);
|
|
5470
|
+
try {
|
|
5471
|
+
const usage = response.usage;
|
|
5472
|
+
const { recordTokenUsage: recordTokenUsage2 } = await Promise.resolve().then(() => (init_token_stats(), token_stats_exports));
|
|
5473
|
+
recordTokenUsage2({
|
|
5474
|
+
sessionId: "audit",
|
|
5475
|
+
step: "L3_security_judge",
|
|
5476
|
+
model: response.model || config.model,
|
|
5477
|
+
promptTokens: usage?.prompt_tokens || 0,
|
|
5478
|
+
completionTokens: usage?.completion_tokens || 0,
|
|
5479
|
+
totalTokens: usage?.total_tokens || 0
|
|
5480
|
+
});
|
|
5481
|
+
} catch {
|
|
5482
|
+
}
|
|
5483
|
+
clearTimeout(totalTimeoutTimer);
|
|
5484
|
+
const riskLevel = parsed.riskLevel ?? parsed.risk_level;
|
|
5485
|
+
const riskType = parsed.riskType ?? parsed.risk_type;
|
|
5486
|
+
return {
|
|
5487
|
+
allowed: parsed.allowed !== false,
|
|
5488
|
+
reason: parsed.reason || "LLM \u5206\u6790\u5B8C\u6210",
|
|
5489
|
+
riskLevel: ["low", "medium", "high", "critical"].includes(riskLevel) ? riskLevel : "low",
|
|
5490
|
+
riskType: riskType || "",
|
|
5491
|
+
confidence: typeof parsed.confidence === "number" ? Math.min(100, Math.max(0, parsed.confidence)) : 70,
|
|
5492
|
+
source: "llm",
|
|
5493
|
+
violatedLayers: ["3"]
|
|
5494
|
+
};
|
|
5495
|
+
} catch (err) {
|
|
5496
|
+
if (abortController.signal.aborted) {
|
|
5497
|
+
console.error(`[AgentMoss] LLM analysis aborted by totalTimeout (${timeoutCfg.totalTimeout}s)`);
|
|
5498
|
+
break;
|
|
5499
|
+
}
|
|
5500
|
+
if (attempt < maxRetries) {
|
|
5501
|
+
console.error(`[AgentMoss] LLM call failed (attempt ${attempt + 1}/${maxRetries + 1}), retrying in ${retryCfg.retryInterval}s:`, err.message);
|
|
5502
|
+
await new Promise((resolve9) => setTimeout(resolve9, retryCfg.retryInterval * 1e3));
|
|
5503
|
+
} else {
|
|
5504
|
+
clearTimeout(totalTimeoutTimer);
|
|
5505
|
+
console.error("[AgentMoss] LLM call failed after all retries:", err.message);
|
|
5506
|
+
throw new Error(`LLM \u5206\u6790\u5931\u8D25 (${maxRetries + 1} \u6B21\u5C1D\u8BD5): ${err.message}`);
|
|
5507
|
+
}
|
|
5508
|
+
}
|
|
5509
|
+
}
|
|
5510
|
+
clearTimeout(totalTimeoutTimer);
|
|
5511
|
+
throw new Error(`LLM \u5206\u6790\u8D85\u65F6\uFF08totalTimeout ${timeoutCfg.totalTimeout}s\uFF09`);
|
|
5512
|
+
}
|
|
5513
|
+
|
|
5514
|
+
// src/engine/coordinator.ts
|
|
5515
|
+
init_config();
|
|
5516
|
+
init_agents();
|
|
5517
|
+
init_scenarios();
|
|
5518
|
+
|
|
5519
|
+
// src/brain/index.ts
|
|
5520
|
+
init_esm_shims();
|
|
5521
|
+
|
|
5522
|
+
// src/brain/rule-store.ts
|
|
5523
|
+
init_esm_shims();
|
|
5524
|
+
import { existsSync as existsSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
5525
|
+
import { join as join6 } from "path";
|
|
5526
|
+
import { homedir as homedir4 } from "os";
|
|
5527
|
+
var DEFAULT_RULES_PATH = join6(homedir4(), ".agentmoss", "brain", "rules.json");
|
|
5528
|
+
var ENV_RULES_PATH = "AGENT_MOSS_BRAIN_RULES_PATH";
|
|
5529
|
+
var STATUS_DRAFT = "draft";
|
|
5530
|
+
var STATUS_APPROVED = "approved";
|
|
5531
|
+
var STATUS_ACTIVE = "active";
|
|
5532
|
+
var STATUS_DISPUTED = "disputed";
|
|
5533
|
+
var STATUS_STALE = "stale";
|
|
5534
|
+
var STATUS_ARCHIVED = "archived";
|
|
5535
|
+
var STATUS_REJECTED = "rejected";
|
|
5536
|
+
function getRulesPath() {
|
|
5537
|
+
const envPath = process.env[ENV_RULES_PATH];
|
|
5538
|
+
if (envPath) return envPath;
|
|
5539
|
+
return DEFAULT_RULES_PATH;
|
|
5540
|
+
}
|
|
5541
|
+
function uuid() {
|
|
5542
|
+
try {
|
|
5543
|
+
return crypto.randomUUID();
|
|
5544
|
+
} catch {
|
|
5545
|
+
return `rule_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
5546
|
+
}
|
|
5547
|
+
}
|
|
5548
|
+
var BrainRuleStore = class {
|
|
5549
|
+
path;
|
|
5550
|
+
data;
|
|
5551
|
+
constructor(path5) {
|
|
5552
|
+
this.path = path5 || getRulesPath();
|
|
5553
|
+
this.data = this.load();
|
|
5554
|
+
}
|
|
5555
|
+
load() {
|
|
5556
|
+
if (!existsSync11(this.path)) {
|
|
5557
|
+
return { version: 1, rules: [], pattern_candidates: [] };
|
|
5558
|
+
}
|
|
5559
|
+
try {
|
|
5560
|
+
const raw = JSON.parse(readFileSync12(this.path, "utf-8"));
|
|
5561
|
+
return {
|
|
5562
|
+
version: raw.version || 1,
|
|
5563
|
+
rules: raw.rules || [],
|
|
5564
|
+
pattern_candidates: raw.pattern_candidates || []
|
|
5565
|
+
};
|
|
5566
|
+
} catch {
|
|
5567
|
+
return { version: 1, rules: [], pattern_candidates: [] };
|
|
5568
|
+
}
|
|
5569
|
+
}
|
|
5570
|
+
save() {
|
|
5571
|
+
const dir = this.path.substring(0, this.path.lastIndexOf("/"));
|
|
5572
|
+
if (dir) mkdirSync3(dir, { recursive: true });
|
|
5573
|
+
writeFileSync3(this.path, JSON.stringify(this.data, null, 2), "utf-8");
|
|
5574
|
+
}
|
|
5575
|
+
addRule(rule) {
|
|
5576
|
+
const now = Date.now();
|
|
5577
|
+
const full = {
|
|
5578
|
+
id: rule.id || uuid(),
|
|
5579
|
+
layer: rule.layer,
|
|
5580
|
+
ruleType: rule.ruleType || "deny_pattern",
|
|
5581
|
+
pattern: rule.pattern,
|
|
5582
|
+
scope: rule.scope || "all",
|
|
5583
|
+
source: rule.source || "llm_extracted",
|
|
5584
|
+
sourceEventIds: rule.sourceEventIds || [],
|
|
5585
|
+
confidence: rule.confidence || 0,
|
|
5586
|
+
status: rule.status || STATUS_DRAFT,
|
|
5587
|
+
createdAt: rule.createdAt || now,
|
|
5588
|
+
lastTriggeredAt: rule.lastTriggeredAt || 0,
|
|
5589
|
+
triggerCount: rule.triggerCount || 0,
|
|
5590
|
+
disputedCount: rule.disputedCount || 0,
|
|
5591
|
+
explanation: rule.explanation || "",
|
|
5592
|
+
originNode: rule.originNode || "local"
|
|
5593
|
+
};
|
|
5594
|
+
this.data.rules.push(full);
|
|
5595
|
+
this.save();
|
|
5596
|
+
return full.id;
|
|
5597
|
+
}
|
|
5598
|
+
approveRule(ruleId) {
|
|
5599
|
+
const rule = this.getRule(ruleId);
|
|
5600
|
+
if (!rule) throw new Error(`\u89C4\u5219\u4E0D\u5B58\u5728: ${ruleId}`);
|
|
5601
|
+
if (rule.status !== STATUS_DRAFT) throw new Error(`\u53EA\u6709 draft \u72B6\u6001\u53EF\u5BA1\u6838\uFF0C\u5F53\u524D: ${rule.status}`);
|
|
5602
|
+
rule.status = STATUS_ACTIVE;
|
|
5603
|
+
this.save();
|
|
5604
|
+
}
|
|
5605
|
+
rejectRule(ruleId) {
|
|
5606
|
+
const rule = this.getRule(ruleId);
|
|
5607
|
+
if (!rule) throw new Error(`\u89C4\u5219\u4E0D\u5B58\u5728: ${ruleId}`);
|
|
5608
|
+
if (rule.status !== STATUS_DRAFT) throw new Error(`\u53EA\u6709 draft \u72B6\u6001\u53EF\u62D2\u7EDD\uFF0C\u5F53\u524D: ${rule.status}`);
|
|
5609
|
+
rule.status = STATUS_REJECTED;
|
|
5610
|
+
this.save();
|
|
5611
|
+
}
|
|
5612
|
+
getRule(ruleId) {
|
|
5613
|
+
return this.data.rules.find((r) => r.id === ruleId);
|
|
5614
|
+
}
|
|
5615
|
+
getActiveRules(layer) {
|
|
5616
|
+
return this.data.rules.filter((r) => r.status === STATUS_ACTIVE && (layer === void 0 || r.layer === layer));
|
|
5617
|
+
}
|
|
5618
|
+
getDraftRules() {
|
|
5619
|
+
return this.data.rules.filter((r) => r.status === STATUS_DRAFT).sort((a, b) => b.createdAt - a.createdAt);
|
|
5620
|
+
}
|
|
5621
|
+
recordTrigger(ruleId) {
|
|
5622
|
+
const rule = this.getRule(ruleId);
|
|
5623
|
+
if (!rule) return;
|
|
5624
|
+
rule.triggerCount += 1;
|
|
5625
|
+
rule.lastTriggeredAt = Date.now();
|
|
5626
|
+
this.save();
|
|
5627
|
+
}
|
|
5628
|
+
// --- pattern_candidates ---
|
|
5629
|
+
addPatternCandidate(pattern, layer, scope, sourceEventId, confidence) {
|
|
5630
|
+
const now = Date.now();
|
|
5631
|
+
const existing = this.data.pattern_candidates.find(
|
|
5632
|
+
(c) => c.pattern === pattern && c.layer === layer && c.scope === scope
|
|
5633
|
+
);
|
|
5634
|
+
if (existing) {
|
|
5635
|
+
if (existing.reviewStatus === "rejected") return 0;
|
|
5636
|
+
existing.occurrenceCount += 1;
|
|
5637
|
+
existing.lastSeenAt = now;
|
|
5638
|
+
existing.sourceEventIds.unshift(sourceEventId);
|
|
5639
|
+
this.save();
|
|
5640
|
+
return existing.occurrenceCount;
|
|
5641
|
+
}
|
|
5642
|
+
this.data.pattern_candidates.push({
|
|
5643
|
+
pattern,
|
|
5644
|
+
layer,
|
|
5645
|
+
scope,
|
|
5646
|
+
sourceEventIds: [sourceEventId],
|
|
5647
|
+
confidence,
|
|
5648
|
+
occurrenceCount: 1,
|
|
5649
|
+
firstSeenAt: now,
|
|
5650
|
+
lastSeenAt: now,
|
|
5651
|
+
reviewStatus: "pending",
|
|
5652
|
+
reviewReason: "",
|
|
5653
|
+
reviewedAt: 0
|
|
5654
|
+
});
|
|
5655
|
+
this.save();
|
|
5656
|
+
return 1;
|
|
5657
|
+
}
|
|
5658
|
+
getPatternCandidates(minOccurrences = 3) {
|
|
5659
|
+
return this.data.pattern_candidates.filter((c) => c.occurrenceCount >= minOccurrences && c.reviewStatus !== "rejected").sort((a, b) => b.occurrenceCount - a.occurrenceCount);
|
|
5660
|
+
}
|
|
5661
|
+
getPendingReviewCandidates(minOccurrences = 3) {
|
|
5662
|
+
return this.data.pattern_candidates.filter((c) => c.occurrenceCount >= minOccurrences && c.reviewStatus === "pending").sort((a, b) => b.occurrenceCount - a.occurrenceCount);
|
|
5663
|
+
}
|
|
5664
|
+
isExcludedPattern(pattern, layer, scope) {
|
|
5665
|
+
const c = this.data.pattern_candidates.find(
|
|
5666
|
+
(c2) => c2.pattern === pattern && c2.layer === layer && c2.scope === scope
|
|
5667
|
+
);
|
|
5668
|
+
return !!c && c.reviewStatus === "rejected";
|
|
5669
|
+
}
|
|
5670
|
+
setPatternReviewStatus(pattern, layer, scope, status, reason = "") {
|
|
5671
|
+
const c = this.data.pattern_candidates.find(
|
|
5672
|
+
(c2) => c2.pattern === pattern && c2.layer === layer && c2.scope === scope
|
|
5673
|
+
);
|
|
5674
|
+
if (c) {
|
|
5675
|
+
c.reviewStatus = status;
|
|
5676
|
+
c.reviewReason = reason;
|
|
5677
|
+
c.reviewedAt = Date.now();
|
|
5678
|
+
this.save();
|
|
5679
|
+
}
|
|
5680
|
+
}
|
|
5681
|
+
removePatternCandidate(pattern, layer, scope) {
|
|
5682
|
+
this.data.pattern_candidates = this.data.pattern_candidates.filter(
|
|
5683
|
+
(c) => !(c.pattern === pattern && c.layer === layer && c.scope === scope)
|
|
5684
|
+
);
|
|
5685
|
+
this.save();
|
|
5686
|
+
}
|
|
5687
|
+
getStats() {
|
|
5688
|
+
const count = (status) => this.data.rules.filter((r) => r.status === status).length;
|
|
5689
|
+
return {
|
|
5690
|
+
draft: count(STATUS_DRAFT),
|
|
5691
|
+
approved: count(STATUS_APPROVED),
|
|
5692
|
+
active: count(STATUS_ACTIVE),
|
|
5693
|
+
disputed: count(STATUS_DISPUTED),
|
|
5694
|
+
stale: count(STATUS_STALE),
|
|
5695
|
+
archived: count(STATUS_ARCHIVED),
|
|
5696
|
+
rejected: count(STATUS_REJECTED),
|
|
5697
|
+
total: this.data.rules.length,
|
|
5698
|
+
by_layer: {
|
|
5699
|
+
1: this.data.rules.filter((r) => r.layer === 1 && r.status === STATUS_ACTIVE).length,
|
|
5700
|
+
2: this.data.rules.filter((r) => r.layer === 2 && r.status === STATUS_ACTIVE).length
|
|
5701
|
+
},
|
|
5702
|
+
pattern_candidates: this.data.pattern_candidates.length
|
|
5703
|
+
};
|
|
5704
|
+
}
|
|
5705
|
+
};
|
|
5706
|
+
|
|
5707
|
+
// src/brain/pattern-extractor.ts
|
|
5708
|
+
init_esm_shims();
|
|
5709
|
+
var PatternExtractor = class {
|
|
5710
|
+
ruleStore;
|
|
5711
|
+
minConfidence;
|
|
5712
|
+
minOccurrences;
|
|
5713
|
+
builtinPatterns;
|
|
5714
|
+
reviewFn;
|
|
5715
|
+
constructor(ruleStore, opts) {
|
|
5716
|
+
this.ruleStore = ruleStore;
|
|
5717
|
+
this.minConfidence = opts?.minConfidence ?? 0.9;
|
|
5718
|
+
this.minOccurrences = opts?.minOccurrences ?? 3;
|
|
5719
|
+
this.builtinPatterns = opts?.builtinPatterns ?? [];
|
|
5720
|
+
this.reviewFn = opts?.review ?? null;
|
|
5721
|
+
}
|
|
5722
|
+
async extract(event, llmResult) {
|
|
5723
|
+
const result = { rulesCreated: 0, patternsRecorded: 0, skippedReason: "", deduped: [] };
|
|
5724
|
+
const confidence = llmResult.confidence ?? 0;
|
|
5725
|
+
const reason = llmResult.reason ?? "";
|
|
5726
|
+
const riskType = llmResult.riskType ?? "";
|
|
5727
|
+
const actionDetail = llmResult.actionDetail ?? event.actionDetail ?? "";
|
|
5728
|
+
const eventId = event.eventId ?? "";
|
|
5729
|
+
if (confidence < this.minConfidence) {
|
|
5730
|
+
result.skippedReason = `confidence ${confidence} < ${this.minConfidence}`;
|
|
5731
|
+
return result;
|
|
5732
|
+
}
|
|
5733
|
+
const patterns = this.extractPatterns(actionDetail, reason, riskType);
|
|
5734
|
+
if (patterns.length === 0) {
|
|
5735
|
+
result.skippedReason = "no extractable pattern";
|
|
5736
|
+
return result;
|
|
5737
|
+
}
|
|
5738
|
+
for (const p of patterns) {
|
|
5739
|
+
if (this.isCoveredByBuiltin(actionDetail)) {
|
|
5740
|
+
result.deduped.push(p.pattern);
|
|
5741
|
+
continue;
|
|
5742
|
+
}
|
|
5743
|
+
const count = this.ruleStore.addPatternCandidate(
|
|
5744
|
+
p.pattern,
|
|
5745
|
+
p.layer,
|
|
5746
|
+
p.scope,
|
|
5747
|
+
eventId,
|
|
5748
|
+
confidence
|
|
5749
|
+
);
|
|
5750
|
+
if (count === 0) {
|
|
5751
|
+
result.deduped.push(p.pattern);
|
|
5752
|
+
continue;
|
|
5753
|
+
}
|
|
5754
|
+
result.patternsRecorded += 1;
|
|
5755
|
+
if (count >= this.minOccurrences) {
|
|
5756
|
+
const candidates = this.ruleStore.getPatternCandidates(this.minOccurrences);
|
|
5757
|
+
const matched = candidates.find((c) => c.pattern === p.pattern && c.layer === p.layer);
|
|
5758
|
+
if (!matched) continue;
|
|
5759
|
+
if (this.reviewFn) {
|
|
5760
|
+
let review;
|
|
5761
|
+
try {
|
|
5762
|
+
review = await this.reviewFn({
|
|
5763
|
+
pattern: p.pattern,
|
|
5764
|
+
layer: p.layer,
|
|
5765
|
+
ruleType: p.ruleType,
|
|
5766
|
+
actionDetail,
|
|
5767
|
+
reason
|
|
5768
|
+
});
|
|
5769
|
+
} catch {
|
|
5770
|
+
review = null;
|
|
5771
|
+
}
|
|
5772
|
+
if (review === null) {
|
|
5773
|
+
continue;
|
|
5774
|
+
}
|
|
5775
|
+
if (!review.shouldKeep) {
|
|
5776
|
+
this.ruleStore.setPatternReviewStatus(
|
|
5777
|
+
p.pattern,
|
|
5778
|
+
p.layer,
|
|
5779
|
+
p.scope,
|
|
5780
|
+
"rejected",
|
|
5781
|
+
review.reason || "LLM \u5BA1\u6838\u62D2\u7EDD"
|
|
5782
|
+
);
|
|
5783
|
+
result.deduped.push(p.pattern);
|
|
5784
|
+
continue;
|
|
5785
|
+
}
|
|
5786
|
+
this.ruleStore.setPatternReviewStatus(
|
|
5787
|
+
p.pattern,
|
|
5788
|
+
p.layer,
|
|
5789
|
+
p.scope,
|
|
5790
|
+
"approved",
|
|
5791
|
+
review.reason || "LLM \u5BA1\u6838\u901A\u8FC7"
|
|
5792
|
+
);
|
|
5793
|
+
}
|
|
5794
|
+
this.ruleStore.addRule({
|
|
5795
|
+
layer: p.layer,
|
|
5796
|
+
ruleType: p.ruleType,
|
|
5797
|
+
pattern: p.pattern,
|
|
5798
|
+
scope: p.scope,
|
|
5799
|
+
source: "llm_extracted",
|
|
5800
|
+
sourceEventIds: eventId ? [eventId] : [],
|
|
5801
|
+
confidence: matched.confidence,
|
|
5802
|
+
status: STATUS_DRAFT,
|
|
5803
|
+
explanation: `\u4ECE ${count} \u6B21 LLM Deny \u4E2D\u63D0\u53D6(\u7ECF\u5BA1\u6838): ${reason.slice(0, 200)}`
|
|
5804
|
+
});
|
|
5805
|
+
this.ruleStore.removePatternCandidate(p.pattern, p.layer, p.scope);
|
|
5806
|
+
result.rulesCreated += 1;
|
|
5807
|
+
}
|
|
5808
|
+
}
|
|
5809
|
+
return result;
|
|
5810
|
+
}
|
|
5811
|
+
extractPatterns(actionDetail, reason, riskType) {
|
|
5812
|
+
const patterns = [];
|
|
5813
|
+
const detailLower = actionDetail.toLowerCase();
|
|
5814
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5815
|
+
const sensitivePathRe = /(\/(?:etc|root)(?:\/[^\s;|&"'<>`$]*)?|(?:[^\s;|&"'<>`$]*)?\/\.ssh\/[^\s;|&"'<>`$]*|(?:[^\s;|&"'<>`$]*)?\/\.aws\/[^\s;|&"'<>`$]*|\S+\.(?:pem|key|p12|pfx|id_rsa|authorized_keys)\b)/;
|
|
5816
|
+
const m = detailLower.match(sensitivePathRe);
|
|
5817
|
+
if (m) {
|
|
5818
|
+
const gen = this.generalizePath(m[1]);
|
|
5819
|
+
if (!seen.has(gen)) {
|
|
5820
|
+
seen.add(gen);
|
|
5821
|
+
patterns.push({ pattern: gen, layer: 1, scope: "openat", ruleType: "deny_path" });
|
|
5822
|
+
}
|
|
5823
|
+
}
|
|
5824
|
+
const dangerousCmdRe = /\b(rm\s+-[a-zA-Z]*r[a-zA-Z]*f|rm\s+--recursive|chmod\s+777|curl\s+[^|]*\|\s*(?:ba)?sh|wget\s+[^|]*\|\s*(?:ba)?sh|mkfs\.|dd\s+if=|>\s*\/dev\/sd)\b/;
|
|
5825
|
+
const cmdM = detailLower.match(dangerousCmdRe);
|
|
5826
|
+
if (cmdM) {
|
|
5827
|
+
const gen = this.generalizeCommand(cmdM[1]);
|
|
5828
|
+
const seenKey = `cmd:${gen}`;
|
|
5829
|
+
if (!seen.has(seenKey)) {
|
|
5830
|
+
seen.add(seenKey);
|
|
5831
|
+
patterns.push({ pattern: gen, layer: 1, scope: "execve", ruleType: "deny_command" });
|
|
5832
|
+
}
|
|
5833
|
+
}
|
|
5834
|
+
const reasonLower = (reason || "").toLowerCase();
|
|
5835
|
+
if (reasonLower.includes("inject") || reasonLower.includes("\u6570\u636E\u6CE8\u5165")) {
|
|
5836
|
+
patterns.push({
|
|
5837
|
+
pattern: "BEHAVIOR_CHAIN:external_read\u2192sensitive_write",
|
|
5838
|
+
layer: 2,
|
|
5839
|
+
scope: "all",
|
|
5840
|
+
ruleType: "deny_pattern"
|
|
5841
|
+
});
|
|
5842
|
+
}
|
|
5843
|
+
if (reasonLower.includes("reconnaissance") || reasonLower.includes("\u4FA6\u5BDF") || reasonLower.includes("\u63D0\u6743")) {
|
|
5844
|
+
patterns.push({
|
|
5845
|
+
pattern: "BEHAVIOR_CHAIN:multi_config_read\u2192privilege_action",
|
|
5846
|
+
layer: 2,
|
|
5847
|
+
scope: "all",
|
|
5848
|
+
ruleType: "deny_pattern"
|
|
5849
|
+
});
|
|
5850
|
+
}
|
|
5851
|
+
const pathPrefix = `(?:^|[\\s"'(=])`;
|
|
5852
|
+
const pathBody = "(\\/(?:[a-zA-Z0-9_.-]+\\/)+[a-zA-Z0-9_.-]+)";
|
|
5853
|
+
const pathSuffix = `(?=[\\s"')\uFF0C\u3002\uFF09\uFF1B;]|$)`;
|
|
5854
|
+
for (const src of [detailLower, reasonLower]) {
|
|
5855
|
+
const pathRe = new RegExp(pathPrefix + pathBody + pathSuffix, "g");
|
|
5856
|
+
let mm;
|
|
5857
|
+
while ((mm = pathRe.exec(src)) !== null) {
|
|
5858
|
+
const path5 = mm[1];
|
|
5859
|
+
if (path5.split("/").length < 3) continue;
|
|
5860
|
+
if (["/dev/null", "/dev/zero", "/dev/random", "/dev/urandom"].includes(path5)) continue;
|
|
5861
|
+
const gen = this.generalizePath(path5);
|
|
5862
|
+
if (seen.has(gen)) continue;
|
|
5863
|
+
seen.add(gen);
|
|
5864
|
+
patterns.push({ pattern: gen, layer: 1, scope: "openat", ruleType: "deny_path" });
|
|
5865
|
+
}
|
|
5866
|
+
}
|
|
5867
|
+
return patterns;
|
|
5868
|
+
}
|
|
5869
|
+
isCoveredByBuiltin(actionDetail) {
|
|
5870
|
+
if (this.builtinPatterns.length === 0 || !actionDetail) return false;
|
|
5871
|
+
for (const pat of this.builtinPatterns) {
|
|
5872
|
+
try {
|
|
5873
|
+
if (new RegExp(pat, "i").test(actionDetail)) return true;
|
|
5874
|
+
} catch {
|
|
5875
|
+
continue;
|
|
5876
|
+
}
|
|
5877
|
+
}
|
|
5878
|
+
return false;
|
|
5879
|
+
}
|
|
5880
|
+
generalizePath(path5) {
|
|
5881
|
+
const p = path5.toLowerCase();
|
|
5882
|
+
if (/\/etc\/(shadow|passwd|sudoers|security|ssh)/.test(p)) {
|
|
5883
|
+
return "(?:^|[^a-z0-9_-])etc/(?:shadow|passwd|sudoers|security/|ssh/)";
|
|
5884
|
+
}
|
|
5885
|
+
if (p.includes("/etc/")) {
|
|
5886
|
+
return "(?:^|[^a-z0-9_-])etc/";
|
|
5887
|
+
}
|
|
5888
|
+
if (p.includes("/root/")) {
|
|
5889
|
+
return "(?:^|[^a-z0-9_-])root/\\.(?:ssh|gnupg|aws|config)/";
|
|
5890
|
+
}
|
|
5891
|
+
if (p.includes(".ssh/")) {
|
|
5892
|
+
return "(?:^|[^a-z0-9_-])[\\w./-]*/\\.ssh/";
|
|
5893
|
+
}
|
|
5894
|
+
if (p.includes(".aws/")) {
|
|
5895
|
+
return "(?:^|[^a-z0-9_-])[\\w./-]*/\\.aws/";
|
|
5896
|
+
}
|
|
5897
|
+
if (/\.(pem|key|p12|pfx|id_rsa|authorized_keys)\b/.test(p)) {
|
|
5898
|
+
return "[\\w./-]*\\.(?:pem|key|p12|pfx|id_rsa|authorized_keys)(?:$|[^a-z0-9])";
|
|
5899
|
+
}
|
|
5900
|
+
const escaped = p.replace(/^\//, "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5901
|
+
return `(?:^|[^a-z0-9_-])${escaped}(?:/|\\b)`;
|
|
5902
|
+
}
|
|
5903
|
+
generalizeCommand(cmd) {
|
|
5904
|
+
if (cmd.includes("rm") && cmd.includes("r") && cmd.includes("f")) {
|
|
5905
|
+
return "rm\\s+(?:-[a-zA-Z]*r[a-zA-Z]*f|--recursive)\\s+/";
|
|
5906
|
+
}
|
|
5907
|
+
if (cmd.includes("chmod")) return "chmod\\s+777";
|
|
5908
|
+
if (cmd.includes("curl") || cmd.includes("wget")) {
|
|
5909
|
+
return "(?:curl|wget)\\s+[^|]*\\|\\s*(?:ba)?sh";
|
|
5910
|
+
}
|
|
5911
|
+
if (cmd.includes("mkfs")) return "mkfs\\.";
|
|
5912
|
+
if (cmd.includes("dd")) return "dd\\s+if=";
|
|
5913
|
+
return cmd.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5914
|
+
}
|
|
5915
|
+
};
|
|
5916
|
+
|
|
5917
|
+
// src/brain/reviewer.ts
|
|
5918
|
+
init_esm_shims();
|
|
5919
|
+
init_config();
|
|
5920
|
+
import OpenAI2 from "openai";
|
|
5921
|
+
var REVIEW_PROMPT = `\u4F60\u662F AgentMoss \u5B89\u5168\u89C4\u5219\u5BA1\u6838\u5668\u3002\u5224\u65AD\u4E0B\u5217"\u4ECE LLM \u62D2\u7EDD\u7ED3\u679C\u4E2D\u63D0\u53D6\u7684\u5B89\u5168\u6A21\u5F0F"\u662F\u5426\u503C\u5F97\u6C89\u6DC0\u4E3A\u672C\u5730\u89C4\u5219\u3002
|
|
5922
|
+
|
|
5923
|
+
## \u63D0\u53D6\u7684\u6A21\u5F0F\uFF08\u5019\u9009\uFF09
|
|
5924
|
+
- \u6A21\u5F0F: {pattern}
|
|
5925
|
+
- \u6240\u5C5E\u5C42: Layer {layer}\uFF081=\u7279\u5F81\u5339\u914D\uFF0C2=\u884C\u4E3A\u94FE\uFF09
|
|
5926
|
+
- \u7C7B\u578B: {ruleType}
|
|
5927
|
+
|
|
5928
|
+
## \u539F\u59CB\u88AB\u62D2\u7EDD\u7684\u64CD\u4F5C
|
|
5929
|
+
{actionDetail}
|
|
5930
|
+
|
|
5931
|
+
## LLM \u5F53\u65F6\u7684\u62D2\u7EDD\u7406\u7531
|
|
5932
|
+
{reason}
|
|
5933
|
+
|
|
5934
|
+
## \u5224\u65AD\u51C6\u5219
|
|
5935
|
+
1. **\u662F\u5426\u8FC7\u9AD8\u6CDB\u5316**\uFF1A\u8FD9\u6761\u6A21\u5F0F\u662F\u5426\u4F1A\u8BEF\u62E6\u6B63\u5E38\u7684\u5408\u6CD5\u64CD\u4F5C\uFF1F
|
|
5936
|
+
2. **\u662F\u5426\u7B26\u5408\u654F\u611F\u7279\u5F81**\uFF1A\u6307\u5411\u7684\u8DEF\u5F84/\u547D\u4EE4\u662F\u5426\u786E\u5B9E\u654F\u611F\uFF08\u51ED\u636E/\u7CFB\u7EDF\u6587\u4EF6/\u6076\u610F\u547D\u4EE4/\u5916\u4F20\uFF09\uFF1F
|
|
5937
|
+
3. **\u4E0E\u4E09\u5C42\u89C4\u5219\u91CD\u590D**\uFF1A\u662F\u5426\u5DF2\u88AB\u73B0\u6709 L1/L2 \u89C4\u5219\u8986\u76D6\uFF1F
|
|
5938
|
+
4. **\u662F\u4E0D\u662F\u771F\u5B9E\u653B\u51FB\u6A21\u5F0F**\uFF1A\u662F\u5426\u4EE3\u8868\u4E00\u79CD\u8FDE\u7EED\u7684\u653B\u51FB\u884C\u4E3A\u6A21\u5F0F\uFF1F
|
|
5939
|
+
|
|
5940
|
+
## \u8F93\u51FA JSON\uFF08\u53EA\u8F93\u51FA JSON\uFF0C\u65E0\u5176\u4ED6\u6587\u5B57\uFF09
|
|
5941
|
+
{"should_keep": true/false, "confidence": 0-100, "reason": "\u7B80\u77ED\u7406\u7531\uFF08\u4E2D\u6587\uFF09", "suggested_rule": "\u82E5\u4FDD\u7559\u5219\u8F93\u51FA\u5EFA\u8BAE\u6B63\u5219\uFF0C\u5426\u5219\u7A7A\u4E32", "duplicate_of": "\u82E5\u91CD\u590D\u5219\u8BF4\u660E\u662F\u54EA\u4E2A\uFF0C\u5426\u5219\u7A7A\u4E32"}`;
|
|
5942
|
+
function createBrainReviewer(timeoutMs = 1e4) {
|
|
5943
|
+
return async (opts) => {
|
|
5944
|
+
if (process.env.AGENT_MOSS_BRAIN_REVIEW === "0") return null;
|
|
5945
|
+
if (!isLLMAvailable()) return null;
|
|
5946
|
+
const config = getLLMConfig();
|
|
5947
|
+
const provider = detectProvider(config.baseUrl, config.provider);
|
|
5948
|
+
const extraHeaders = getProviderHeaders(provider);
|
|
5949
|
+
const client = new OpenAI2({
|
|
5950
|
+
baseURL: config.baseUrl,
|
|
5951
|
+
apiKey: config.apiKey,
|
|
5952
|
+
timeout: timeoutMs,
|
|
5953
|
+
maxRetries: 1,
|
|
5954
|
+
defaultHeaders: Object.keys(extraHeaders).length > 0 ? extraHeaders : void 0
|
|
5955
|
+
});
|
|
5956
|
+
const prompt = REVIEW_PROMPT.replace("{pattern}", opts.pattern).replace("{layer}", String(opts.layer)).replace("{ruleType}", opts.ruleType).replace("{actionDetail}", (opts.actionDetail || "").slice(0, 400)).replace("{reason}", (opts.reason || "").slice(0, 400));
|
|
5957
|
+
try {
|
|
5958
|
+
const response = await client.chat.completions.create({
|
|
5959
|
+
model: config.model,
|
|
5960
|
+
temperature: config.temperature || 0.1,
|
|
5961
|
+
max_tokens: 256,
|
|
5962
|
+
messages: [
|
|
5963
|
+
{ role: "system", content: "\u53EA\u8F93\u51FA\u7EAF JSON\uFF0C\u4E0D\u8981\u5305\u542B markdown \u6807\u8BB0\u3001\u4EE3\u7801\u5757\u6216\u4EFB\u4F55\u5176\u4ED6\u6587\u5B57\u3002" },
|
|
5964
|
+
{ role: "user", content: prompt }
|
|
5965
|
+
]
|
|
5966
|
+
});
|
|
5967
|
+
const content = response.choices[0]?.message?.content || "";
|
|
5968
|
+
const parsed = parseReview(content);
|
|
5969
|
+
return { shouldKeep: parsed.shouldKeep, reason: parsed.reason };
|
|
5970
|
+
} catch (e) {
|
|
5971
|
+
console.warn(`[AgentMoss] Brain \u5BA1\u6838 LLM \u8C03\u7528\u5931\u8D25\uFF08\u964D\u7EA7\u4FDD\u6301\u5019\u9009\uFF09: ${e.message}`);
|
|
5972
|
+
return null;
|
|
5973
|
+
}
|
|
5974
|
+
};
|
|
5975
|
+
}
|
|
5976
|
+
function parseReview(content) {
|
|
5977
|
+
let text = content.trim();
|
|
5978
|
+
if (text.startsWith("```")) {
|
|
5979
|
+
text = text.replace(/^```[a-zA-Z]*\n?/, "").replace(/\n?```$/, "");
|
|
5980
|
+
}
|
|
5981
|
+
const match = text.match(/\{[\s\S]*\}/);
|
|
5982
|
+
if (!match) throw new Error(`\u5BA1\u6838\u54CD\u5E94\u65E0 JSON \u5BF9\u8C61: ${text.slice(0, 200)}`);
|
|
5983
|
+
const data = JSON.parse(match[0]);
|
|
5984
|
+
if (!("should_keep" in data) && !("shouldKeep" in data)) {
|
|
5985
|
+
throw new Error(`\u5BA1\u6838\u54CD\u5E94\u7F3A should_keep: ${text.slice(0, 200)}`);
|
|
5986
|
+
}
|
|
5987
|
+
let shouldKeep = data.shouldKeep ?? data.should_keep;
|
|
5988
|
+
if (typeof shouldKeep === "string") {
|
|
5989
|
+
shouldKeep = ["true", "1", "\u662F", "yes"].includes(shouldKeep.trim().toLowerCase());
|
|
5990
|
+
}
|
|
5991
|
+
return {
|
|
5992
|
+
shouldKeep: !!shouldKeep,
|
|
5993
|
+
confidence: Number(data.confidence ?? 0) || 0,
|
|
5994
|
+
reason: String(data.reason ?? data.suggested_rule ?? "") || ""
|
|
5995
|
+
};
|
|
5996
|
+
}
|
|
5997
|
+
|
|
5998
|
+
// src/brain/index.ts
|
|
5999
|
+
function checkBrainRules(store, opts) {
|
|
6000
|
+
if (!store) return null;
|
|
6001
|
+
const rules = store.getActiveRules(opts.layer);
|
|
6002
|
+
if (rules.length === 0) return null;
|
|
6003
|
+
const combined = `${opts.actionType} ${opts.actionDetail}`;
|
|
6004
|
+
for (const rule of rules) {
|
|
6005
|
+
try {
|
|
6006
|
+
if (new RegExp(rule.pattern, "i").test(combined)) {
|
|
6007
|
+
if (opts.layer === 2 && opts.actionHistory) {
|
|
6008
|
+
if (!checkLayer2Context(rule.pattern, opts.actionHistory, opts.actionDetail)) {
|
|
6009
|
+
continue;
|
|
6010
|
+
}
|
|
6011
|
+
}
|
|
6012
|
+
store.recordTrigger(rule.id);
|
|
6013
|
+
return {
|
|
6014
|
+
ruleId: rule.id,
|
|
6015
|
+
pattern: rule.pattern,
|
|
6016
|
+
explanation: rule.explanation,
|
|
6017
|
+
confidence: rule.confidence
|
|
6018
|
+
};
|
|
6019
|
+
}
|
|
6020
|
+
} catch {
|
|
6021
|
+
continue;
|
|
6022
|
+
}
|
|
6023
|
+
}
|
|
6024
|
+
return null;
|
|
6025
|
+
}
|
|
6026
|
+
function checkLayer2Context(pattern, actionHistory, actionDetail) {
|
|
6027
|
+
if (pattern.startsWith("BEHAVIOR_CHAIN:")) {
|
|
6028
|
+
const chainType = pattern.replace("BEHAVIOR_CHAIN:", "");
|
|
6029
|
+
if (chainType === "external_read\u2192sensitive_write") {
|
|
6030
|
+
const hasExternalRead = actionHistory.slice(-5).some((h) => /curl|wget|http/i.test(h.actionDetail ?? ""));
|
|
6031
|
+
const isSensitiveWrite = /\.ssh|\.gnupg|authorized_keys|bashrc|profile/i.test(actionDetail);
|
|
6032
|
+
return hasExternalRead && isSensitiveWrite;
|
|
6033
|
+
}
|
|
6034
|
+
if (chainType === "multi_config_read\u2192privilege_action") {
|
|
6035
|
+
const configReads = actionHistory.slice(-5).filter((h) => /\/etc\/|config|\.conf|\.yaml|\.json/i.test(h.actionDetail ?? "")).length;
|
|
6036
|
+
const isPrivilege = /sudo|su |chmod|chown/i.test(actionDetail);
|
|
6037
|
+
return configReads >= 2 && isPrivilege;
|
|
6038
|
+
}
|
|
6039
|
+
return false;
|
|
3773
6040
|
}
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
- \u4F1A\u8BDD ID: ${input.sessionId}
|
|
3777
|
-
- \u7528\u6237\u4EFB\u52A1: ${input.promptSession || "\u672A\u6307\u5B9A"}
|
|
3778
|
-
- \u64CD\u4F5C\u7CFB\u7EDF: ${input.osType}
|
|
3779
|
-
- \u5DE5\u4F5C\u76EE\u5F55: ${input.cwd || "/"}
|
|
3780
|
-
- \u64CD\u4F5C\u539F\u56E0: ${input.reason || "\u672A\u8BF4\u660E"}
|
|
3781
|
-
- \u5386\u53F2\u64CD\u4F5C\u5E8F\u5217:
|
|
3782
|
-
${historyStr}
|
|
3783
|
-
|
|
3784
|
-
- \u5373\u5C06\u6267\u884C\u7684\u64CD\u4F5C:
|
|
3785
|
-
\u7C7B\u578B: ${input.aNext.actionType}
|
|
3786
|
-
\u8BE6\u60C5: ${input.aNext.actionDetail}
|
|
6041
|
+
return true;
|
|
6042
|
+
}
|
|
3787
6043
|
|
|
3788
|
-
|
|
3789
|
-
|
|
6044
|
+
// src/engine/formal-engine/index.ts
|
|
6045
|
+
init_esm_shims();
|
|
3790
6046
|
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
${scriptAnalysisText ? `
|
|
3794
|
-
## \u811A\u672C\u5185\u5BB9\u5206\u6790
|
|
3795
|
-
${scriptAnalysisText}` : ""}
|
|
6047
|
+
// src/engine/formal-engine/engine.ts
|
|
6048
|
+
init_esm_shims();
|
|
3796
6049
|
|
|
3797
|
-
|
|
6050
|
+
// src/engine/formal-engine/builder.ts
|
|
6051
|
+
init_esm_shims();
|
|
6052
|
+
init_rules_loader();
|
|
3798
6053
|
|
|
3799
|
-
|
|
3800
|
-
|
|
6054
|
+
// src/engine/formal-engine/decode.ts
|
|
6055
|
+
init_esm_shims();
|
|
6056
|
+
var B64_TOKEN_RE = /[A-Za-z0-9+/]+={0,2}/g;
|
|
6057
|
+
function isValidBase64Token(tok) {
|
|
6058
|
+
if (tok.length < 8) return false;
|
|
6059
|
+
if (!(tok.length % 4 === 0 || tok.endsWith("==") || tok.endsWith("="))) return false;
|
|
6060
|
+
return true;
|
|
3801
6061
|
}
|
|
3802
|
-
function
|
|
3803
|
-
|
|
3804
|
-
|
|
6062
|
+
function isPrintable(s) {
|
|
6063
|
+
for (let i = 0; i < s.length; i++) {
|
|
6064
|
+
const c = s.charCodeAt(i);
|
|
6065
|
+
if (c < 32 || c > 126) return false;
|
|
3805
6066
|
}
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
const
|
|
3813
|
-
if (
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
6067
|
+
return true;
|
|
6068
|
+
}
|
|
6069
|
+
function decodeBase64Literals(text) {
|
|
6070
|
+
const out = [];
|
|
6071
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6072
|
+
for (const m of text.matchAll(B64_TOKEN_RE)) {
|
|
6073
|
+
const tok = m[0];
|
|
6074
|
+
if (!isValidBase64Token(tok)) continue;
|
|
6075
|
+
if (!/[A-Z]/.test(tok) || !/[a-z]/.test(tok)) continue;
|
|
6076
|
+
try {
|
|
6077
|
+
const padded = tok + "=".repeat((4 - tok.length % 4) % 4);
|
|
6078
|
+
const bin = Buffer.from(padded, "base64");
|
|
6079
|
+
const s = bin.toString("utf8");
|
|
6080
|
+
if (s && isPrintable(s) && !seen.has(s)) {
|
|
6081
|
+
seen.add(s);
|
|
6082
|
+
out.push(s);
|
|
3817
6083
|
}
|
|
6084
|
+
} catch {
|
|
3818
6085
|
}
|
|
3819
|
-
lines.push(line);
|
|
3820
6086
|
}
|
|
3821
|
-
return
|
|
6087
|
+
return out;
|
|
3822
6088
|
}
|
|
3823
|
-
function
|
|
3824
|
-
const
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
6089
|
+
function decodeUrlAndNumeric(text) {
|
|
6090
|
+
const out = [];
|
|
6091
|
+
let s = text;
|
|
6092
|
+
for (let i = 0; i < 3; i++) {
|
|
6093
|
+
let nxt = s;
|
|
6094
|
+
try {
|
|
6095
|
+
nxt = decodeURIComponent(s);
|
|
6096
|
+
} catch {
|
|
6097
|
+
break;
|
|
6098
|
+
}
|
|
6099
|
+
if (nxt === s) break;
|
|
6100
|
+
s = nxt;
|
|
3831
6101
|
}
|
|
3832
|
-
if (
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
6102
|
+
if (s !== text) out.push(s);
|
|
6103
|
+
if (/\\[0-7]{3}/.test(text)) {
|
|
6104
|
+
let expanded = "";
|
|
6105
|
+
try {
|
|
6106
|
+
expanded = text.replace(/\\[0-7]{3}/g, (m) => String.fromCharCode(parseInt(m.slice(1), 8)));
|
|
6107
|
+
} catch {
|
|
6108
|
+
}
|
|
6109
|
+
if (expanded && expanded !== text) out.push(expanded);
|
|
3838
6110
|
}
|
|
3839
|
-
|
|
6111
|
+
if (/\\x[0-9a-fA-F]{2}/.test(text)) {
|
|
6112
|
+
let expanded = "";
|
|
6113
|
+
try {
|
|
6114
|
+
expanded = text.replace(/\\x[0-9a-fA-F]{2}/g, (m) => String.fromCharCode(parseInt(m.slice(2), 16)));
|
|
6115
|
+
} catch {
|
|
6116
|
+
}
|
|
6117
|
+
if (expanded && expanded !== text) out.push(expanded);
|
|
6118
|
+
}
|
|
6119
|
+
return out;
|
|
3840
6120
|
}
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
6121
|
+
function allCandidates(detail, max = 8) {
|
|
6122
|
+
const cands = [detail];
|
|
6123
|
+
cands.push(...decodeBase64Literals(detail));
|
|
6124
|
+
cands.push(...decodeUrlAndNumeric(detail));
|
|
6125
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6126
|
+
const out = [];
|
|
6127
|
+
for (const c of cands) {
|
|
6128
|
+
if (seen.has(c) || c.length > 4096) continue;
|
|
6129
|
+
seen.add(c);
|
|
6130
|
+
out.push(c);
|
|
6131
|
+
if (out.length >= max) break;
|
|
3847
6132
|
}
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
6133
|
+
return out;
|
|
6134
|
+
}
|
|
6135
|
+
function matchStageAny(detail, stages) {
|
|
6136
|
+
for (const cand of allCandidates(detail)) {
|
|
6137
|
+
for (const [name, pats] of Object.entries(stages)) {
|
|
6138
|
+
for (const p of pats) {
|
|
6139
|
+
p.lastIndex = 0;
|
|
6140
|
+
if (p.test(cand)) return name;
|
|
6141
|
+
}
|
|
6142
|
+
}
|
|
6143
|
+
}
|
|
6144
|
+
return "";
|
|
6145
|
+
}
|
|
6146
|
+
|
|
6147
|
+
// src/engine/formal-engine/builder.ts
|
|
6148
|
+
var COMBO_STAGES2 = null;
|
|
6149
|
+
function loadStages() {
|
|
6150
|
+
if (COMBO_STAGES2) return COMBO_STAGES2;
|
|
6151
|
+
const combo = loadL2().combo_chains || {};
|
|
6152
|
+
const stages = {};
|
|
6153
|
+
for (const [name, pats] of Object.entries(combo.stages || {})) {
|
|
6154
|
+
stages[name] = pats.map((p) => new RegExp(p, "i"));
|
|
6155
|
+
}
|
|
6156
|
+
COMBO_STAGES2 = stages;
|
|
6157
|
+
return stages;
|
|
6158
|
+
}
|
|
6159
|
+
var PATH_RE = /(?:^|\s|[@=])((?:\/|~\/)[\w./~-]+)/g;
|
|
6160
|
+
function extractResources(actionDetail) {
|
|
6161
|
+
const resources = /* @__PURE__ */ new Set();
|
|
6162
|
+
const text = actionDetail.replace(/'[^']*'|"[^"]*"/g, "");
|
|
6163
|
+
PATH_RE.lastIndex = 0;
|
|
6164
|
+
let m;
|
|
6165
|
+
while ((m = PATH_RE.exec(text)) !== null) {
|
|
6166
|
+
const p = m[1];
|
|
6167
|
+
if (p.startsWith("/dev/") || p.includes("://")) continue;
|
|
6168
|
+
resources.add(p);
|
|
6169
|
+
}
|
|
6170
|
+
return resources;
|
|
6171
|
+
}
|
|
6172
|
+
function markStage(actionDetail) {
|
|
6173
|
+
return matchStageAny(actionDetail, loadStages());
|
|
6174
|
+
}
|
|
6175
|
+
function buildSessionGraph(actionHistory, aNext, sessionId = "current") {
|
|
6176
|
+
const nodes = [];
|
|
6177
|
+
const edges = [];
|
|
6178
|
+
const stageFlags = /* @__PURE__ */ new Set();
|
|
6179
|
+
const details = [];
|
|
6180
|
+
for (const a of actionHistory) {
|
|
6181
|
+
const ad = String(a.actionDetail || "");
|
|
6182
|
+
if (ad) details.push({ actionType: String(a.actionType || ""), actionDetail: ad });
|
|
6183
|
+
}
|
|
6184
|
+
if (aNext) {
|
|
6185
|
+
details.push({ actionType: String(aNext.actionType || ""), actionDetail: String(aNext.actionDetail || "") });
|
|
6186
|
+
}
|
|
6187
|
+
details.forEach((d, seq) => {
|
|
6188
|
+
const stage = markStage(d.actionDetail);
|
|
6189
|
+
const node = {
|
|
6190
|
+
sessionId,
|
|
6191
|
+
seq,
|
|
6192
|
+
actionType: d.actionType,
|
|
6193
|
+
actionDetail: d.actionDetail,
|
|
6194
|
+
stage,
|
|
6195
|
+
resources: extractResources(d.actionDetail)
|
|
6196
|
+
};
|
|
6197
|
+
nodes.push(node);
|
|
6198
|
+
if (stage) stageFlags.add(stage);
|
|
3856
6199
|
});
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
6200
|
+
for (let i = 0; i < nodes.length - 1; i++) {
|
|
6201
|
+
edges.push({ src: i, dst: i + 1, kind: "next" });
|
|
6202
|
+
}
|
|
6203
|
+
return { sessionId, nodes, edges, stageFlags };
|
|
6204
|
+
}
|
|
6205
|
+
function buildMultiSessionGraph(sessions, aNext) {
|
|
6206
|
+
const mg = { sessions: [], crossEdges: [] };
|
|
6207
|
+
if (!sessions || sessions.length === 0) {
|
|
6208
|
+
mg.sessions.push(buildSessionGraph([], aNext, "current"));
|
|
6209
|
+
return mg;
|
|
6210
|
+
}
|
|
6211
|
+
for (const s of sessions) {
|
|
6212
|
+
const sid = String(s.sessionId || "s");
|
|
6213
|
+
mg.sessions.push(buildSessionGraph(s.actionHistory || [], void 0, sid));
|
|
6214
|
+
}
|
|
6215
|
+
if (aNext) {
|
|
6216
|
+
const last = mg.sessions[mg.sessions.length - 1];
|
|
6217
|
+
if (last.sessionId === "current") {
|
|
6218
|
+
const stage = markStage(String(aNext.actionDetail || ""));
|
|
6219
|
+
last.nodes.push({
|
|
6220
|
+
sessionId: last.sessionId,
|
|
6221
|
+
seq: last.nodes.length,
|
|
6222
|
+
actionType: String(aNext.actionType || ""),
|
|
6223
|
+
actionDetail: String(aNext.actionDetail || ""),
|
|
6224
|
+
stage,
|
|
6225
|
+
resources: extractResources(String(aNext.actionDetail || ""))
|
|
6226
|
+
});
|
|
6227
|
+
if (last.nodes.length > 1) {
|
|
6228
|
+
last.edges.push({ src: last.nodes.length - 2, dst: last.nodes.length - 1, kind: "next" });
|
|
6229
|
+
}
|
|
6230
|
+
} else {
|
|
6231
|
+
mg.sessions.push(buildSessionGraph([], aNext, "current"));
|
|
3867
6232
|
}
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
6233
|
+
}
|
|
6234
|
+
addCrossDataEdges(mg);
|
|
6235
|
+
return mg;
|
|
6236
|
+
}
|
|
6237
|
+
function addCrossDataEdges(mg) {
|
|
6238
|
+
const nodes = allNodesFlat(mg);
|
|
6239
|
+
if (nodes.length < 2) return;
|
|
6240
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
6241
|
+
for (let j = i + 1; j < nodes.length; j++) {
|
|
6242
|
+
if (nodes[i].sessionId === nodes[j].sessionId) continue;
|
|
6243
|
+
const shared = intersect(nodes[i].resources, nodes[j].resources);
|
|
6244
|
+
if (shared.size > 0) {
|
|
6245
|
+
mg.crossEdges.push({ src: i, dst: j, kind: "data" });
|
|
6246
|
+
}
|
|
6247
|
+
}
|
|
6248
|
+
}
|
|
6249
|
+
}
|
|
6250
|
+
function allNodesFlat(mg) {
|
|
6251
|
+
const out = [];
|
|
6252
|
+
for (const s of mg.sessions) out.push(...s.nodes);
|
|
6253
|
+
return out;
|
|
6254
|
+
}
|
|
6255
|
+
function intersect(a, b) {
|
|
6256
|
+
const out = /* @__PURE__ */ new Set();
|
|
6257
|
+
for (const v of a) if (b.has(v)) out.add(v);
|
|
6258
|
+
return out;
|
|
6259
|
+
}
|
|
6260
|
+
|
|
6261
|
+
// src/engine/formal-engine/ir.ts
|
|
6262
|
+
init_esm_shims();
|
|
6263
|
+
function allNodes(mg) {
|
|
6264
|
+
const out = [];
|
|
6265
|
+
for (const s of mg.sessions) out.push(...s.nodes);
|
|
6266
|
+
return out;
|
|
6267
|
+
}
|
|
6268
|
+
function allEdges(mg) {
|
|
6269
|
+
const out = [];
|
|
6270
|
+
for (const s of mg.sessions) out.push(...s.edges);
|
|
6271
|
+
out.push(...mg.crossEdges);
|
|
6272
|
+
return out;
|
|
6273
|
+
}
|
|
6274
|
+
function emptyVerificationResult() {
|
|
6275
|
+
return { verified: true, violations: [] };
|
|
6276
|
+
}
|
|
6277
|
+
|
|
6278
|
+
// src/engine/formal-engine/properties.ts
|
|
6279
|
+
init_esm_shims();
|
|
6280
|
+
init_rules_loader();
|
|
6281
|
+
var CROSS_SESSION_CHAINS = null;
|
|
6282
|
+
function loadCrossSessionChains() {
|
|
6283
|
+
if (CROSS_SESSION_CHAINS) return CROSS_SESSION_CHAINS;
|
|
6284
|
+
const combo = loadL2().combo_chains || {};
|
|
6285
|
+
CROSS_SESSION_CHAINS = (combo.chains || []).map((c) => ({
|
|
6286
|
+
id: String(c.id || "combo_chain"),
|
|
6287
|
+
stages: c.stages || [],
|
|
6288
|
+
riskLevel: String(c.riskLevel || c.risk_level || "high"),
|
|
6289
|
+
reason: "\u8DE8\u4F1A\u8BDD" + String(c.reason || "\u7EC4\u5408\u653B\u51FB\u94FE")
|
|
6290
|
+
}));
|
|
6291
|
+
return CROSS_SESSION_CHAINS;
|
|
6292
|
+
}
|
|
6293
|
+
var CRED_FILE_RE = /(known_hosts|\.ssh|id_rsa|\/etc\/shadow|\/etc\/passwd|credentials)/i;
|
|
6294
|
+
var EXFIL_RE = /https?:\/\/|scp\s+.*@|rsync\s+.*@/i;
|
|
6295
|
+
function checkCrossSessionChain(graph) {
|
|
6296
|
+
const violations = [];
|
|
6297
|
+
const nodes = allNodes(graph);
|
|
6298
|
+
if (nodes.length === 0) return violations;
|
|
6299
|
+
for (const chain of loadCrossSessionChains()) {
|
|
6300
|
+
const stages = chain.stages;
|
|
6301
|
+
if (!stages.length) continue;
|
|
6302
|
+
let matched = 0;
|
|
6303
|
+
let stageIdx = 0;
|
|
6304
|
+
const path5 = [];
|
|
6305
|
+
for (const n of nodes) {
|
|
6306
|
+
if (stageIdx < stages.length && n.stage === stages[stageIdx]) {
|
|
6307
|
+
matched += 1;
|
|
6308
|
+
path5.push({ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) });
|
|
6309
|
+
stageIdx += 1;
|
|
6310
|
+
if (matched >= stages.length) break;
|
|
6311
|
+
}
|
|
6312
|
+
}
|
|
6313
|
+
if (matched >= stages.length) {
|
|
6314
|
+
violations.push({
|
|
6315
|
+
propertyId: "cross-" + chain.id,
|
|
6316
|
+
reason: chain.reason,
|
|
6317
|
+
riskLevel: chain.riskLevel,
|
|
6318
|
+
path: path5
|
|
3878
6319
|
});
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
6320
|
+
}
|
|
6321
|
+
}
|
|
6322
|
+
return violations;
|
|
6323
|
+
}
|
|
6324
|
+
function checkSensitiveDataFlow(graph) {
|
|
6325
|
+
const violations = [];
|
|
6326
|
+
const nodes = allNodes(graph);
|
|
6327
|
+
if (nodes.length === 0) return violations;
|
|
6328
|
+
const tainted = /* @__PURE__ */ new Map();
|
|
6329
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
6330
|
+
const n = nodes[i];
|
|
6331
|
+
if (n.stage === "CRED" && [...n.resources].some((r) => CRED_FILE_RE.test(r))) {
|
|
6332
|
+
tainted.set(i, `${n.sessionId}#${n.seq} \u8BBF\u95EE ${[...n.resources][0]}`);
|
|
6333
|
+
}
|
|
6334
|
+
}
|
|
6335
|
+
if (tainted.size === 0) return violations;
|
|
6336
|
+
const edges = allEdges(graph);
|
|
6337
|
+
let changed = true;
|
|
6338
|
+
while (changed) {
|
|
6339
|
+
changed = false;
|
|
6340
|
+
for (const e of edges) {
|
|
6341
|
+
if (tainted.has(e.src) && !tainted.has(e.dst)) {
|
|
6342
|
+
const srcRes = nodes[e.src].resources;
|
|
6343
|
+
const dstRes = nodes[e.dst].resources;
|
|
6344
|
+
for (const r of srcRes) {
|
|
6345
|
+
if (dstRes.has(r)) {
|
|
6346
|
+
tainted.set(e.dst, tainted.get(e.src));
|
|
6347
|
+
changed = true;
|
|
6348
|
+
break;
|
|
6349
|
+
}
|
|
6350
|
+
}
|
|
3884
6351
|
}
|
|
3885
|
-
|
|
6352
|
+
}
|
|
6353
|
+
}
|
|
6354
|
+
for (const [i, n] of nodes.entries()) {
|
|
6355
|
+
if (tainted.has(i) && EXFIL_RE.test(n.actionDetail)) {
|
|
6356
|
+
violations.push({
|
|
6357
|
+
propertyId: "sensitive-data-exfil",
|
|
6358
|
+
reason: `\u654F\u611F\u6570\u636E\u4F20\u64AD\u5916\u4F20\uFF1A${tainted.get(i)} \u2192 ${n.sessionId}#${n.seq} \u7ECF\u5171\u4EAB\u8F7D\u4F53\u5916\u53D1`,
|
|
6359
|
+
riskLevel: "critical",
|
|
6360
|
+
path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
|
|
6361
|
+
});
|
|
6362
|
+
}
|
|
6363
|
+
}
|
|
6364
|
+
return violations;
|
|
6365
|
+
}
|
|
6366
|
+
var EBPF_NET_RE = /connect|sendto|network_connect/i;
|
|
6367
|
+
var REMOTE_RE = /to\s+\d+\.\d+\.\d+\.\d+|remote_addr/i;
|
|
6368
|
+
var PRIVESC_RE = /\bsudo\b|\bsu\b|chmod\s+u\s*\+\s*s|pkexec|setuid/i;
|
|
6369
|
+
var EXFIL_ACTION_RE = /\bcurl\b|\bwget\b|\bscp\b|\brsync\b|\brclone\b|\bftp\b|connect\s+.*\d+\.\d+\.\d+\.\d+|open\s+.*tcp|base64\s+.*\-o/i;
|
|
6370
|
+
var CRED_STRICT_RE = /(known_hosts|\.ssh|id_rsa|\.pem|\b\.key\b|\/etc\/shadow|\/etc\/passwd|\.aws\/credentials|credentials\.json|netrc|password|secrets?\.ya?ml|credentials?\s*(file|path)?\s*(=|\:)|\bconfig.*(password|token))/i;
|
|
6371
|
+
function isExfilNode(n) {
|
|
6372
|
+
if (n.stage === "EXFIL") return true;
|
|
6373
|
+
if (EXFIL_RE.test(n.actionDetail) && EXFIL_ACTION_RE.test(n.actionDetail)) return true;
|
|
6374
|
+
if (EBPF_NET_RE.test(n.actionDetail) && REMOTE_RE.test(n.actionDetail)) return true;
|
|
6375
|
+
return false;
|
|
6376
|
+
}
|
|
6377
|
+
function isCredNode(n) {
|
|
6378
|
+
if (n.stage !== "CRED") return false;
|
|
6379
|
+
for (const r of n.resources) {
|
|
6380
|
+
if (CRED_STRICT_RE.test(r)) return true;
|
|
6381
|
+
}
|
|
6382
|
+
return CRED_STRICT_RE.test(n.actionDetail);
|
|
6383
|
+
}
|
|
6384
|
+
function isPrivescNode(n) {
|
|
6385
|
+
if (n.stage === "PRIVESC") return true;
|
|
6386
|
+
return PRIVESC_RE.test(n.actionDetail);
|
|
6387
|
+
}
|
|
6388
|
+
function checkNeverExfilAfterPrivesc(graph) {
|
|
6389
|
+
const violations = [];
|
|
6390
|
+
const nodes = allNodes(graph);
|
|
6391
|
+
if (nodes.length < 2) return violations;
|
|
6392
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
6393
|
+
if (!isPrivescNode(nodes[i])) continue;
|
|
6394
|
+
for (let j = i + 1; j < nodes.length; j++) {
|
|
6395
|
+
if (isExfilNode(nodes[j])) {
|
|
6396
|
+
violations.push({
|
|
6397
|
+
propertyId: "no-privesc-then-exfil",
|
|
6398
|
+
reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
|
|
6399
|
+
riskLevel: "critical",
|
|
6400
|
+
path: [
|
|
6401
|
+
{ sessionId: nodes[i].sessionId, seq: nodes[i].seq, detail: nodes[i].actionDetail.slice(0, 60) },
|
|
6402
|
+
{ sessionId: nodes[j].sessionId, seq: nodes[j].seq, detail: nodes[j].actionDetail.slice(0, 60) }
|
|
6403
|
+
]
|
|
6404
|
+
});
|
|
6405
|
+
return violations;
|
|
6406
|
+
}
|
|
6407
|
+
}
|
|
6408
|
+
}
|
|
6409
|
+
return violations;
|
|
6410
|
+
}
|
|
6411
|
+
function checkNeverExfilAfterCred(graph) {
|
|
6412
|
+
const violations = [];
|
|
6413
|
+
const nodes = allNodes(graph);
|
|
6414
|
+
if (nodes.length < 2) return violations;
|
|
6415
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
6416
|
+
if (!isCredNode(nodes[i])) continue;
|
|
6417
|
+
for (let j = i + 1; j < nodes.length; j++) {
|
|
6418
|
+
if (isExfilNode(nodes[j])) {
|
|
6419
|
+
violations.push({
|
|
6420
|
+
propertyId: "no-cred-then-exfil",
|
|
6421
|
+
reason: "\u8BFB\u53D6\u51ED\u636E\u540E\u51FA\u73B0\u5916\u4F20\uFF0C\u8FDD\u53CD \u25A1(CRED\u2192\xAC\u25C7EXFIL)",
|
|
6422
|
+
riskLevel: "critical",
|
|
6423
|
+
path: [
|
|
6424
|
+
{ sessionId: nodes[i].sessionId, seq: nodes[i].seq, detail: nodes[i].actionDetail.slice(0, 60) },
|
|
6425
|
+
{ sessionId: nodes[j].sessionId, seq: nodes[j].seq, detail: nodes[j].actionDetail.slice(0, 60) }
|
|
6426
|
+
]
|
|
6427
|
+
});
|
|
6428
|
+
return violations;
|
|
6429
|
+
}
|
|
6430
|
+
}
|
|
6431
|
+
}
|
|
6432
|
+
return violations;
|
|
6433
|
+
}
|
|
6434
|
+
var ALL_PROPERTIES = [
|
|
6435
|
+
{ id: "cross-session-chain", check: checkCrossSessionChain },
|
|
6436
|
+
{ id: "sensitive-data-flow", check: checkSensitiveDataFlow },
|
|
6437
|
+
{ id: "no-privesc-then-exfil", check: checkNeverExfilAfterPrivesc },
|
|
6438
|
+
{ id: "no-cred-then-exfil", check: checkNeverExfilAfterCred }
|
|
6439
|
+
];
|
|
6440
|
+
|
|
6441
|
+
// src/engine/formal-engine/engine.ts
|
|
6442
|
+
function ebpfEventToDetail(e) {
|
|
6443
|
+
const parts = [];
|
|
6444
|
+
const syscall = String(e.syscall || e.op || "");
|
|
6445
|
+
const argv = String(e.argv || e.pathname || "");
|
|
6446
|
+
const remote = String(e.remote_addr || "");
|
|
6447
|
+
const port = e.remote_port;
|
|
6448
|
+
if (syscall) parts.push(syscall);
|
|
6449
|
+
if (argv && argv !== "" && argv !== "None") parts.push(argv);
|
|
6450
|
+
if (remote) parts.push("to " + remote + (port ? ":" + String(port) : ""));
|
|
6451
|
+
return parts.join(" ");
|
|
6452
|
+
}
|
|
6453
|
+
var FormalEngine = class {
|
|
6454
|
+
properties;
|
|
6455
|
+
constructor(properties) {
|
|
6456
|
+
this.properties = properties && properties.length > 0 ? properties : ALL_PROPERTIES;
|
|
6457
|
+
}
|
|
6458
|
+
verify(sessions, aNext, ebpfEvents) {
|
|
6459
|
+
const all = sessions ? [...sessions] : [];
|
|
6460
|
+
if (ebpfEvents && ebpfEvents.length > 0) {
|
|
6461
|
+
all.push({
|
|
6462
|
+
sessionId: "ebpf-evidence",
|
|
6463
|
+
actionHistory: ebpfEvents.map((e) => ({
|
|
6464
|
+
actionType: "syscall",
|
|
6465
|
+
actionDetail: ebpfEventToDetail(e)
|
|
6466
|
+
}))
|
|
6467
|
+
});
|
|
6468
|
+
}
|
|
6469
|
+
const graph = buildMultiSessionGraph(all, aNext);
|
|
6470
|
+
const result = emptyVerificationResult();
|
|
6471
|
+
for (const prop of this.properties) {
|
|
6472
|
+
let violations;
|
|
3886
6473
|
try {
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
totalTokens: usage?.total_tokens || 0
|
|
6474
|
+
violations = prop.check(graph);
|
|
6475
|
+
} catch (e) {
|
|
6476
|
+
result.verified = false;
|
|
6477
|
+
result.violations.push({
|
|
6478
|
+
propertyId: prop.id,
|
|
6479
|
+
reason: `\u5F62\u5F0F\u5316\u6027\u8D28\u9A8C\u8BC1\u5F02\u5E38\uFF0C\u4FDD\u5B88\u62E6\u622A: ${e}`,
|
|
6480
|
+
riskLevel: "medium",
|
|
6481
|
+
path: []
|
|
3896
6482
|
});
|
|
3897
|
-
|
|
6483
|
+
return result;
|
|
3898
6484
|
}
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
return {
|
|
3903
|
-
allowed: parsed.allowed !== false,
|
|
3904
|
-
reason: parsed.reason || "LLM \u5206\u6790\u5B8C\u6210",
|
|
3905
|
-
riskLevel: ["low", "medium", "high", "critical"].includes(riskLevel) ? riskLevel : "low",
|
|
3906
|
-
riskType: riskType || "",
|
|
3907
|
-
confidence: typeof parsed.confidence === "number" ? Math.min(100, Math.max(0, parsed.confidence)) : 70,
|
|
3908
|
-
source: "llm",
|
|
3909
|
-
violatedLayers: ["3"]
|
|
3910
|
-
};
|
|
3911
|
-
} catch (err) {
|
|
3912
|
-
if (abortController.signal.aborted) {
|
|
3913
|
-
console.error(`[AgentMoss] LLM analysis aborted by totalTimeout (${timeoutCfg.totalTimeout}s)`);
|
|
6485
|
+
if (violations.length > 0) {
|
|
6486
|
+
result.verified = false;
|
|
6487
|
+
result.violations.push(...violations);
|
|
3914
6488
|
break;
|
|
3915
6489
|
}
|
|
3916
|
-
if (attempt < maxRetries) {
|
|
3917
|
-
console.error(`[AgentMoss] LLM call failed (attempt ${attempt + 1}/${maxRetries + 1}), retrying in ${retryCfg.retryInterval}s:`, err.message);
|
|
3918
|
-
await new Promise((resolve7) => setTimeout(resolve7, retryCfg.retryInterval * 1e3));
|
|
3919
|
-
} else {
|
|
3920
|
-
clearTimeout(totalTimeoutTimer);
|
|
3921
|
-
console.error("[AgentMoss] LLM call failed after all retries:", err.message);
|
|
3922
|
-
throw new Error(`LLM \u5206\u6790\u5931\u8D25 (${maxRetries + 1} \u6B21\u5C1D\u8BD5): ${err.message}`);
|
|
3923
|
-
}
|
|
3924
6490
|
}
|
|
6491
|
+
return result;
|
|
3925
6492
|
}
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
6493
|
+
};
|
|
6494
|
+
|
|
6495
|
+
// src/engine/formal-engine/stateful.ts
|
|
6496
|
+
init_esm_shims();
|
|
6497
|
+
init_rules_loader();
|
|
6498
|
+
|
|
6499
|
+
// src/engine/formal-engine/chain_state.ts
|
|
6500
|
+
init_esm_shims();
|
|
6501
|
+
import * as fs4 from "fs";
|
|
6502
|
+
import * as os2 from "os";
|
|
6503
|
+
import * as path4 from "path";
|
|
6504
|
+
import { createHash } from "crypto";
|
|
3929
6505
|
|
|
3930
6506
|
// src/engine/coordinator.ts
|
|
3931
|
-
init_config();
|
|
3932
|
-
init_agents();
|
|
3933
6507
|
var FULLY_SAFE_TOOLS = /* @__PURE__ */ new Set([
|
|
3934
6508
|
"ask_user_question",
|
|
3935
6509
|
"glob",
|
|
@@ -4014,6 +6588,24 @@ function extractRawCommand(detail) {
|
|
|
4014
6588
|
function matchesAny(cmd, patterns) {
|
|
4015
6589
|
return patterns.some((p) => p.test(cmd));
|
|
4016
6590
|
}
|
|
6591
|
+
var _brainStore = null;
|
|
6592
|
+
var _brainExtractor = null;
|
|
6593
|
+
var _brainInitialized = false;
|
|
6594
|
+
function getBrainStore() {
|
|
6595
|
+
if (!isBrainEnabled()) return null;
|
|
6596
|
+
if (!_brainInitialized) {
|
|
6597
|
+
_brainStore = new BrainRuleStore();
|
|
6598
|
+
_brainExtractor = new PatternExtractor(_brainStore, { review: createBrainReviewer() });
|
|
6599
|
+
_brainInitialized = true;
|
|
6600
|
+
}
|
|
6601
|
+
return _brainStore;
|
|
6602
|
+
}
|
|
6603
|
+
function _resetBrainForTest() {
|
|
6604
|
+
_brainStore = null;
|
|
6605
|
+
_brainExtractor = null;
|
|
6606
|
+
_brainInitialized = false;
|
|
6607
|
+
}
|
|
6608
|
+
var formalEngine = new FormalEngine();
|
|
4017
6609
|
async function runAnalysis(input) {
|
|
4018
6610
|
const heuristicResult = isHeuristicEnabled() ? runHeuristicCheck(input) : null;
|
|
4019
6611
|
const isInlineScriptCmd = isInlineScriptCommand(input.aNext.actionDetail.toLowerCase());
|
|
@@ -4046,10 +6638,32 @@ async function runAnalysis(input) {
|
|
|
4046
6638
|
}
|
|
4047
6639
|
}
|
|
4048
6640
|
}
|
|
6641
|
+
const brainStore = getBrainStore();
|
|
6642
|
+
if (brainStore) {
|
|
6643
|
+
const brainL1 = checkBrainRules(brainStore, {
|
|
6644
|
+
layer: 1,
|
|
6645
|
+
actionType: input.aNext.actionType,
|
|
6646
|
+
actionDetail: input.aNext.actionDetail
|
|
6647
|
+
});
|
|
6648
|
+
if (brainL1) {
|
|
6649
|
+
return {
|
|
6650
|
+
allowed: false,
|
|
6651
|
+
reason: `[Brain L1] ${brainL1.explanation}`,
|
|
6652
|
+
riskLevel: "high",
|
|
6653
|
+
riskType: "brain_rule",
|
|
6654
|
+
confidence: Math.round(brainL1.confidence * 100),
|
|
6655
|
+
source: "brain_rule",
|
|
6656
|
+
violatedLayers: ["brain_L1"]
|
|
6657
|
+
};
|
|
6658
|
+
}
|
|
6659
|
+
}
|
|
4049
6660
|
const actionType = input.aNext.actionType.toLowerCase();
|
|
4050
6661
|
const rawDetail = extractRawCommand(input.aNext.actionDetail);
|
|
4051
6662
|
const lowerDetail = rawDetail.toLowerCase();
|
|
4052
|
-
const agentExt =
|
|
6663
|
+
const agentExt = mergeAgentExtensions(
|
|
6664
|
+
getAgentExtensions(input.agentId),
|
|
6665
|
+
getScenarioExtensions(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "")
|
|
6666
|
+
);
|
|
4053
6667
|
if (FULLY_SAFE_TOOLS.has(actionType)) {
|
|
4054
6668
|
return {
|
|
4055
6669
|
allowed: true,
|
|
@@ -4154,6 +6768,28 @@ async function runAnalysis(input) {
|
|
|
4154
6768
|
return effectiveResult;
|
|
4155
6769
|
}
|
|
4156
6770
|
}
|
|
6771
|
+
if (isLogicRulesEnabled()) {
|
|
6772
|
+
const formalSessions = [];
|
|
6773
|
+
if (input.sessionHistory) {
|
|
6774
|
+
for (const s of input.sessionHistory) {
|
|
6775
|
+
formalSessions.push({ sessionId: s.sessionId || "s", actionHistory: s.actionHistory || [] });
|
|
6776
|
+
}
|
|
6777
|
+
}
|
|
6778
|
+
formalSessions.push({ sessionId: "current", actionHistory: input.actionHistory });
|
|
6779
|
+
const formalResult = formalEngine.verify(formalSessions, input.aNext);
|
|
6780
|
+
if (!formalResult.verified) {
|
|
6781
|
+
const v = formalResult.violations[0];
|
|
6782
|
+
return {
|
|
6783
|
+
allowed: false,
|
|
6784
|
+
reason: v ? v.reason : "\u5F62\u5F0F\u5316\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF08\u884C\u4E3A\u6027\u8D28\u8FDD\u53CD\uFF09",
|
|
6785
|
+
riskLevel: v ? v.riskLevel : "high",
|
|
6786
|
+
riskType: "formal_verification",
|
|
6787
|
+
confidence: 90,
|
|
6788
|
+
source: "formal_engine",
|
|
6789
|
+
violatedLayers: ["2.4"]
|
|
6790
|
+
};
|
|
6791
|
+
}
|
|
6792
|
+
}
|
|
4157
6793
|
if (scriptChain && scriptChain.nodes.length > 0 && isLogicRulesEnabled()) {
|
|
4158
6794
|
for (const node of scriptChain.nodes) {
|
|
4159
6795
|
if (node.error || !node.preprocessedContent) continue;
|
|
@@ -4171,6 +6807,25 @@ async function runAnalysis(input) {
|
|
|
4171
6807
|
}
|
|
4172
6808
|
}
|
|
4173
6809
|
}
|
|
6810
|
+
if (brainStore) {
|
|
6811
|
+
const brainL2 = checkBrainRules(brainStore, {
|
|
6812
|
+
layer: 2,
|
|
6813
|
+
actionType: input.aNext.actionType,
|
|
6814
|
+
actionDetail: input.aNext.actionDetail,
|
|
6815
|
+
actionHistory: input.actionHistory
|
|
6816
|
+
});
|
|
6817
|
+
if (brainL2) {
|
|
6818
|
+
return {
|
|
6819
|
+
allowed: false,
|
|
6820
|
+
reason: `[Brain L2] ${brainL2.explanation}`,
|
|
6821
|
+
riskLevel: "high",
|
|
6822
|
+
riskType: "brain_rule",
|
|
6823
|
+
confidence: Math.round(brainL2.confidence * 100),
|
|
6824
|
+
source: "brain_rule",
|
|
6825
|
+
violatedLayers: ["brain_L2"]
|
|
6826
|
+
};
|
|
6827
|
+
}
|
|
6828
|
+
}
|
|
4174
6829
|
if (skipLLM || !isLLMAvailable()) {
|
|
4175
6830
|
return {
|
|
4176
6831
|
allowed: true,
|
|
@@ -4237,10 +6892,25 @@ async function runAnalysis(input) {
|
|
|
4237
6892
|
violatedLayers: ["3"]
|
|
4238
6893
|
};
|
|
4239
6894
|
}
|
|
6895
|
+
if (!llmResult.allowed && llmResult.confidence && llmResult.confidence >= 90 && _brainExtractor) {
|
|
6896
|
+
try {
|
|
6897
|
+
await _brainExtractor.extract(
|
|
6898
|
+
{ actionDetail: input.aNext.actionDetail, eventId: "" },
|
|
6899
|
+
{
|
|
6900
|
+
confidence: llmResult.confidence / 100,
|
|
6901
|
+
reason: llmResult.reason || "",
|
|
6902
|
+
riskType: llmResult.riskType || "",
|
|
6903
|
+
actionDetail: input.aNext.actionDetail
|
|
6904
|
+
}
|
|
6905
|
+
);
|
|
6906
|
+
} catch {
|
|
6907
|
+
}
|
|
6908
|
+
}
|
|
4240
6909
|
return llmResult;
|
|
4241
6910
|
}
|
|
4242
6911
|
export {
|
|
4243
6912
|
TRUSTED_SKILLS_BASE,
|
|
6913
|
+
_resetBrainForTest,
|
|
4244
6914
|
runAnalysis
|
|
4245
6915
|
};
|
|
4246
6916
|
//# sourceMappingURL=coordinator.js.map
|