@kenhkl/agent-moss 0.12.7 → 0.12.9
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__/__init__.cpython-310.pyc +0 -0
- package/dist/agents/__pycache__/__init__.cpython-311.pyc +0 -0
- package/dist/agents/__pycache__/base.cpython-310.pyc +0 -0
- package/dist/agents/__pycache__/base.cpython-311.pyc +0 -0
- package/dist/agents/__pycache__/base.cpython-312.pyc +0 -0
- package/dist/agents/base.py +41 -4
- package/dist/cli.js +3382 -449
- package/dist/cli.js.map +1 -1
- package/dist/engine/coordinator.d.ts +13 -5
- package/dist/engine/coordinator.js +2387 -318
- package/dist/engine/coordinator.js.map +1 -1
- package/dist/rules/l2_rules.json +136 -1
- package/dist/scenarios/__init__.py +119 -0
- package/dist/scenarios/__pycache__/__init__.cpython-310.pyc +0 -0
- package/dist/scenarios/__pycache__/__init__.cpython-311.pyc +0 -0
- package/dist/scenarios/__pycache__/__init__.cpython-312.pyc +0 -0
- package/dist/scenarios/bank/rules.json +23 -0
- package/dist/server.js +4045 -1202
- package/dist/server.js.map +1 -1
- package/dist/static/index.html +119 -1
- package/package.json +2 -1
|
@@ -760,6 +760,53 @@ function runLogicCheck(input) {
|
|
|
760
760
|
if (pwdResult) return pwdResult;
|
|
761
761
|
const userDelResult = checkUserDeletionConsent(actionType, actionDetail, actionHistory);
|
|
762
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
|
+
}
|
|
763
810
|
return null;
|
|
764
811
|
}
|
|
765
812
|
function extractFilePaths(text) {
|
|
@@ -859,17 +906,17 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
|
|
|
859
906
|
}
|
|
860
907
|
}
|
|
861
908
|
const unreadPaths = [];
|
|
862
|
-
for (const
|
|
909
|
+
for (const path5 of uniqueTargetPaths) {
|
|
863
910
|
const hasRead = [...historyReadPaths].some(
|
|
864
|
-
(rp) =>
|
|
911
|
+
(rp) => path5 === rp || path5.startsWith(rp) || rp.startsWith(path5)
|
|
865
912
|
);
|
|
866
913
|
if (!hasRead) {
|
|
867
|
-
let isCreate = !fs2.existsSync(
|
|
914
|
+
let isCreate = !fs2.existsSync(path5);
|
|
868
915
|
if (!isCreate) {
|
|
869
916
|
isCreate = CREATE_KEYWORDS.some((kw) => reason.toLowerCase().includes(kw) || actionDetail.includes(kw));
|
|
870
917
|
}
|
|
871
918
|
if (!isCreate) {
|
|
872
|
-
unreadPaths.push(
|
|
919
|
+
unreadPaths.push(path5);
|
|
873
920
|
}
|
|
874
921
|
}
|
|
875
922
|
}
|
|
@@ -1012,7 +1059,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
|
|
|
1012
1059
|
violatedLayers: ["2"]
|
|
1013
1060
|
};
|
|
1014
1061
|
}
|
|
1015
|
-
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;
|
|
1016
1063
|
var init_logic_rules = __esm({
|
|
1017
1064
|
"src/engine/logic-rules.ts"() {
|
|
1018
1065
|
"use strict";
|
|
@@ -1021,7 +1068,7 @@ var init_logic_rules = __esm({
|
|
|
1021
1068
|
init_rules_loader();
|
|
1022
1069
|
ROOT_SENSITIVE_PATHS = _loadSensitivePathScope("root");
|
|
1023
1070
|
HOME_SENSITIVE_PATHS = _loadSensitivePathScope("home");
|
|
1024
|
-
CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path:
|
|
1071
|
+
CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path5, riskLevel, desc }) => ({ path: path5, riskLevel, desc, credential: true }));
|
|
1025
1072
|
NON_WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
1026
1073
|
"ask_user_question",
|
|
1027
1074
|
"glob",
|
|
@@ -1165,6 +1212,24 @@ var init_logic_rules = __esm({
|
|
|
1165
1212
|
reason: p.reason
|
|
1166
1213
|
}));
|
|
1167
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
|
+
})();
|
|
1168
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;
|
|
1169
1234
|
DIR_TRAVERSAL_KW = [
|
|
1170
1235
|
"os.listdir",
|
|
@@ -1282,9 +1347,11 @@ __export(runtime_config_exports, {
|
|
|
1282
1347
|
addAgentRule: () => addAgentRule,
|
|
1283
1348
|
addCustomRule: () => addCustomRule,
|
|
1284
1349
|
addCustomSkill: () => addCustomSkill,
|
|
1350
|
+
addScenarioRule: () => addScenarioRule,
|
|
1285
1351
|
deleteAgentRule: () => deleteAgentRule,
|
|
1286
1352
|
deleteCustomRule: () => deleteCustomRule,
|
|
1287
1353
|
deleteCustomSkill: () => deleteCustomSkill,
|
|
1354
|
+
deleteScenarioRule: () => deleteScenarioRule,
|
|
1288
1355
|
filterEnabledRules: () => filterEnabledRules,
|
|
1289
1356
|
generateSourceDefaults: () => generateSourceDefaults,
|
|
1290
1357
|
getAgentRules: () => getAgentRules,
|
|
@@ -1303,9 +1370,11 @@ __export(runtime_config_exports, {
|
|
|
1303
1370
|
getEnabledL2SensitivePathsOrDefault: () => getEnabledL2SensitivePathsOrDefault,
|
|
1304
1371
|
getEnabledL2UserDeletionPatterns: () => getEnabledL2UserDeletionPatterns,
|
|
1305
1372
|
getEnabledL2UserDeletionPatternsOrDefault: () => getEnabledL2UserDeletionPatternsOrDefault,
|
|
1373
|
+
getEnabledScenarioExtensions: () => getEnabledScenarioExtensions,
|
|
1306
1374
|
getEnvOverrides: () => getEnvOverrides,
|
|
1307
1375
|
getProfileOrDetect: () => getProfileOrDetect,
|
|
1308
1376
|
getRuntimeConfigPath: () => getRuntimeConfigPath,
|
|
1377
|
+
getScenarioRules: () => getScenarioRules,
|
|
1309
1378
|
hasRuntimeConfig: () => hasRuntimeConfig,
|
|
1310
1379
|
isL2CategoryEnabled: () => isL2CategoryEnabled,
|
|
1311
1380
|
isLayerEnabled: () => isLayerEnabled,
|
|
@@ -1319,6 +1388,8 @@ __export(runtime_config_exports, {
|
|
|
1319
1388
|
updateRuleDenyMode: () => updateRuleDenyMode,
|
|
1320
1389
|
updateRuleEnabled: () => updateRuleEnabled,
|
|
1321
1390
|
updateRuleSkipL3: () => updateRuleSkipL3,
|
|
1391
|
+
updateScenarioCategoryEnabled: () => updateScenarioCategoryEnabled,
|
|
1392
|
+
updateScenarioRuleEnabled: () => updateScenarioRuleEnabled,
|
|
1322
1393
|
updateSkillCategoryEnabled: () => updateSkillCategoryEnabled,
|
|
1323
1394
|
updateSkillEnabled: () => updateSkillEnabled
|
|
1324
1395
|
});
|
|
@@ -1347,7 +1418,8 @@ function emptyRuntimeConfig() {
|
|
|
1347
1418
|
L1_rules: {},
|
|
1348
1419
|
L2_rules: {},
|
|
1349
1420
|
L3_skills: {},
|
|
1350
|
-
agent_rules: {}
|
|
1421
|
+
agent_rules: {},
|
|
1422
|
+
scenario_rules: {}
|
|
1351
1423
|
};
|
|
1352
1424
|
}
|
|
1353
1425
|
function loadRuntimeConfig() {
|
|
@@ -1373,7 +1445,8 @@ function loadRuntimeConfig() {
|
|
|
1373
1445
|
L1_rules: merged.L1_rules || {},
|
|
1374
1446
|
L2_rules: merged.L2_rules || {},
|
|
1375
1447
|
L3_skills: merged.L3_skills || {},
|
|
1376
|
-
agent_rules: merged.agent_rules || {}
|
|
1448
|
+
agent_rules: merged.agent_rules || {},
|
|
1449
|
+
scenario_rules: merged.scenario_rules || {}
|
|
1377
1450
|
};
|
|
1378
1451
|
} catch {
|
|
1379
1452
|
return emptyRuntimeConfig();
|
|
@@ -1608,6 +1681,55 @@ function _l3SkillsToRules() {
|
|
|
1608
1681
|
out.user_custom = { category_enabled: true, category_desc: "", skills: [] };
|
|
1609
1682
|
return out;
|
|
1610
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
|
+
}
|
|
1611
1733
|
function _agentRulesToDefaults() {
|
|
1612
1734
|
const out = {};
|
|
1613
1735
|
const agentsDir = existsSync4(_AGENTS_DIR_PROD) ? _AGENTS_DIR_PROD : existsSync4(_AGENTS_DIR_DEV) ? _AGENTS_DIR_DEV : _AGENTS_DIR_PROD;
|
|
@@ -1699,7 +1821,8 @@ function generateSourceDefaults() {
|
|
|
1699
1821
|
user_custom: { category_enabled: true, category_desc: "\u7528\u6237\u81EA\u5B9A\u4E49 L2 \u903B\u8F91\u89C4\u5219", rules: [] }
|
|
1700
1822
|
},
|
|
1701
1823
|
L3_skills: _l3SkillsToRules(),
|
|
1702
|
-
agent_rules: _agentRulesToDefaults()
|
|
1824
|
+
agent_rules: _agentRulesToDefaults(),
|
|
1825
|
+
scenario_rules: _scenarioRulesToDefaults()
|
|
1703
1826
|
};
|
|
1704
1827
|
}
|
|
1705
1828
|
function normalizePatternForFingerprint(pattern) {
|
|
@@ -1792,9 +1915,9 @@ function _mergeSkillCategories(userCfg, srcDefaults, layerKey) {
|
|
|
1792
1915
|
}
|
|
1793
1916
|
}
|
|
1794
1917
|
}
|
|
1795
|
-
function _mergeAgentRules(userCfg, srcDefaults) {
|
|
1796
|
-
const userAgent = userCfg
|
|
1797
|
-
const srcAgent = srcDefaults
|
|
1918
|
+
function _mergeAgentRules(userCfg, srcDefaults, partition = "agent_rules") {
|
|
1919
|
+
const userAgent = userCfg[partition] || (userCfg[partition] = {});
|
|
1920
|
+
const srcAgent = srcDefaults[partition] || {};
|
|
1798
1921
|
for (const [agentId, srcCats] of Object.entries(srcAgent)) {
|
|
1799
1922
|
if (!(agentId in userAgent)) {
|
|
1800
1923
|
userAgent[agentId] = JSON.parse(JSON.stringify(srcCats));
|
|
@@ -1853,6 +1976,7 @@ function mergeConfig(userCfg, srcDefaults) {
|
|
|
1853
1976
|
_mergeRuleCategories(cfg, srcDefaults, "L2_rules");
|
|
1854
1977
|
_mergeSkillCategories(cfg, srcDefaults, "L3_skills");
|
|
1855
1978
|
_mergeAgentRules(cfg, srcDefaults);
|
|
1979
|
+
_mergeAgentRules(cfg, srcDefaults, "scenario_rules");
|
|
1856
1980
|
return cfg;
|
|
1857
1981
|
}
|
|
1858
1982
|
function _migrateCategoriesAcrossLayers(cfg, srcLayer, dstLayer, catNames) {
|
|
@@ -2178,7 +2302,85 @@ function updateAgentCategoryEnabled(agentId, category, enabled) {
|
|
|
2178
2302
|
saveRuntimeConfig(runtime);
|
|
2179
2303
|
return runtime;
|
|
2180
2304
|
}
|
|
2181
|
-
|
|
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;
|
|
2182
2384
|
var init_runtime_config = __esm({
|
|
2183
2385
|
"src/infra/runtime-config.ts"() {
|
|
2184
2386
|
"use strict";
|
|
@@ -2203,6 +2405,8 @@ var init_runtime_config = __esm({
|
|
|
2203
2405
|
};
|
|
2204
2406
|
_AGENTS_DIR_PROD = join2(__dirname4, "agents");
|
|
2205
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");
|
|
2206
2410
|
SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
|
|
2207
2411
|
}
|
|
2208
2412
|
});
|
|
@@ -2254,6 +2458,21 @@ function getAgentExtensions(agentId) {
|
|
|
2254
2458
|
);
|
|
2255
2459
|
return ext;
|
|
2256
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
|
+
}
|
|
2257
2476
|
var __filename4, __dirname5, AGENTS_DIR_PROD, AGENTS_DIR_DEV_FALLBACK, cache;
|
|
2258
2477
|
var init_agents = __esm({
|
|
2259
2478
|
"src/agents/index.ts"() {
|
|
@@ -2267,6 +2486,77 @@ var init_agents = __esm({
|
|
|
2267
2486
|
}
|
|
2268
2487
|
});
|
|
2269
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
|
+
|
|
2270
2560
|
// src/engine/heuristic.ts
|
|
2271
2561
|
function runHeuristicCheck(input) {
|
|
2272
2562
|
const command = `${input.aNext.actionType}: ${input.aNext.actionDetail}`;
|
|
@@ -2306,7 +2596,10 @@ function runHeuristicCheck(input) {
|
|
|
2306
2596
|
}
|
|
2307
2597
|
}
|
|
2308
2598
|
highestRiskType = topRiskType || highestRiskType;
|
|
2309
|
-
const agentExt =
|
|
2599
|
+
const agentExt = mergeAgentExtensions(
|
|
2600
|
+
getAgentExtensions(input.agentId),
|
|
2601
|
+
getScenarioExtensions(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "")
|
|
2602
|
+
);
|
|
2310
2603
|
if (agentExt?.extra_command_patterns) {
|
|
2311
2604
|
for (const rule of agentExt.extra_command_patterns) {
|
|
2312
2605
|
try {
|
|
@@ -2479,13 +2772,13 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2479
2772
|
const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
|
|
2480
2773
|
const isWriteOpFlag = isWriteOperation("", command);
|
|
2481
2774
|
const pathScanText = normalizePathSegments(stripLiteralTextSegments(command));
|
|
2482
|
-
const pathMatches = (text,
|
|
2483
|
-
const parts =
|
|
2775
|
+
const pathMatches = (text, path5) => {
|
|
2776
|
+
const parts = path5.split(/(\(\?!.*?\))/);
|
|
2484
2777
|
const escaped = parts.map((part) => {
|
|
2485
2778
|
if (part.startsWith("(?!")) return part;
|
|
2486
2779
|
return part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2487
2780
|
}).join("");
|
|
2488
|
-
const re =
|
|
2781
|
+
const re = path5.startsWith(".") || path5.startsWith("/") ? new RegExp(`(?:^|[\\s/\\\\'"])${escaped}\\b`, "i") : new RegExp(`\\b${escaped}\\b`, "i");
|
|
2489
2782
|
return re.test(text);
|
|
2490
2783
|
};
|
|
2491
2784
|
for (const sp of ROOT_SENSITIVE_PATHS) {
|
|
@@ -2749,6 +3042,7 @@ var init_heuristic = __esm({
|
|
|
2749
3042
|
init_esm_shims();
|
|
2750
3043
|
init_config();
|
|
2751
3044
|
init_agents();
|
|
3045
|
+
init_scenarios();
|
|
2752
3046
|
init_rules_loader();
|
|
2753
3047
|
init_logic_rules();
|
|
2754
3048
|
INJECTION_KEYWORDS = (() => {
|
|
@@ -2783,16 +3077,16 @@ __export(token_stats_exports, {
|
|
|
2783
3077
|
recordTokenUsage: () => recordTokenUsage,
|
|
2784
3078
|
resetTokenStats: () => resetTokenStats
|
|
2785
3079
|
});
|
|
2786
|
-
import { readFileSync as
|
|
3080
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
2787
3081
|
function getStatsPath() {
|
|
2788
3082
|
const envPath = process.env[ENV_STATS_PATH];
|
|
2789
3083
|
if (envPath) return envPath;
|
|
2790
3084
|
return getTokenStatsPath();
|
|
2791
3085
|
}
|
|
2792
3086
|
function loadAllRecords(statsPath) {
|
|
2793
|
-
if (!
|
|
3087
|
+
if (!existsSync10(statsPath)) return [];
|
|
2794
3088
|
try {
|
|
2795
|
-
const data = JSON.parse(
|
|
3089
|
+
const data = JSON.parse(readFileSync11(statsPath, "utf-8"));
|
|
2796
3090
|
if (Array.isArray(data)) return data;
|
|
2797
3091
|
if (data && Array.isArray(data.records)) return data.records;
|
|
2798
3092
|
return [];
|
|
@@ -2803,7 +3097,7 @@ function loadAllRecords(statsPath) {
|
|
|
2803
3097
|
function saveRecords(statsPath, records) {
|
|
2804
3098
|
try {
|
|
2805
3099
|
const dir = statsPath.substring(0, statsPath.lastIndexOf("/"));
|
|
2806
|
-
if (dir && !
|
|
3100
|
+
if (dir && !existsSync10(dir)) mkdirSync2(dir, { recursive: true });
|
|
2807
3101
|
writeFileSync2(statsPath, JSON.stringify(records, null, 2), "utf-8");
|
|
2808
3102
|
} catch (e) {
|
|
2809
3103
|
}
|
|
@@ -2930,7 +3224,7 @@ function getTokenTrend(days = 0, startDate, endDate) {
|
|
|
2930
3224
|
}
|
|
2931
3225
|
function resetTokenStats() {
|
|
2932
3226
|
const statsPath = getStatsPath();
|
|
2933
|
-
if (
|
|
3227
|
+
if (existsSync10(statsPath)) {
|
|
2934
3228
|
try {
|
|
2935
3229
|
unlinkSync2(statsPath);
|
|
2936
3230
|
} catch {
|
|
@@ -2961,28 +3255,28 @@ init_skill_loader();
|
|
|
2961
3255
|
|
|
2962
3256
|
// src/engine/template-loader.ts
|
|
2963
3257
|
init_esm_shims();
|
|
2964
|
-
import { readFileSync as
|
|
2965
|
-
import { resolve as
|
|
2966
|
-
import { fileURLToPath as
|
|
2967
|
-
var
|
|
2968
|
-
var
|
|
2969
|
-
var TEMPLATES_DIR =
|
|
2970
|
-
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");
|
|
2971
3265
|
var TEMPLATE_FILES = ["prompt1_template.txt", "prompt2_template.txt", "security_judge_template.txt"];
|
|
2972
3266
|
var cachedTemplates = null;
|
|
2973
3267
|
function loadTemplates() {
|
|
2974
3268
|
if (cachedTemplates) return cachedTemplates;
|
|
2975
3269
|
cachedTemplates = {};
|
|
2976
|
-
const dir =
|
|
2977
|
-
if (!
|
|
3270
|
+
const dir = existsSync7(TEMPLATES_DIR) ? TEMPLATES_DIR : TEMPLATES_DIR_FALLBACK;
|
|
3271
|
+
if (!existsSync7(dir)) {
|
|
2978
3272
|
console.warn("[AgentMoss] Templates directory not found:", TEMPLATES_DIR, "and fallback:", TEMPLATES_DIR_FALLBACK);
|
|
2979
3273
|
return cachedTemplates;
|
|
2980
3274
|
}
|
|
2981
3275
|
for (const file of TEMPLATE_FILES) {
|
|
2982
|
-
const filePath =
|
|
2983
|
-
if (
|
|
3276
|
+
const filePath = resolve5(dir, file);
|
|
3277
|
+
if (existsSync7(filePath)) {
|
|
2984
3278
|
const key = file.replace(".txt", "").replace("_template", "");
|
|
2985
|
-
cachedTemplates[key] =
|
|
3279
|
+
cachedTemplates[key] = readFileSync7(filePath, "utf-8");
|
|
2986
3280
|
}
|
|
2987
3281
|
}
|
|
2988
3282
|
console.log(`[AgentMoss] Loaded ${Object.keys(cachedTemplates).length} templates from ${dir}`);
|
|
@@ -2991,8 +3285,8 @@ function loadTemplates() {
|
|
|
2991
3285
|
|
|
2992
3286
|
// src/engine/script-analyzer.ts
|
|
2993
3287
|
init_esm_shims();
|
|
2994
|
-
import { readFileSync as
|
|
2995
|
-
import { resolve as
|
|
3288
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
3289
|
+
import { resolve as resolve6, join as join3 } from "path";
|
|
2996
3290
|
var SUSPICIOUS_PATTERNS = [
|
|
2997
3291
|
// Network exfiltration (curl/wget POST in scripts is almost never legitimate)
|
|
2998
3292
|
{ name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
|
|
@@ -3125,8 +3419,8 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
|
|
|
3125
3419
|
}
|
|
3126
3420
|
function readScriptContent(scriptPath, maxLines = 500) {
|
|
3127
3421
|
try {
|
|
3128
|
-
const expandedPath = scriptPath.startsWith("~") ? join3(process.env.HOME || "/home/user", scriptPath.slice(2)) :
|
|
3129
|
-
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");
|
|
3130
3424
|
const lines = raw.split("\n").slice(0, maxLines);
|
|
3131
3425
|
return { content: lines.join("\n"), lineCount: lines.length, error: "" };
|
|
3132
3426
|
} catch (e) {
|
|
@@ -3180,133 +3474,1524 @@ ${result.scriptContent}
|
|
|
3180
3474
|
init_esm_shims();
|
|
3181
3475
|
init_heuristic();
|
|
3182
3476
|
init_logic_rules();
|
|
3183
|
-
import {
|
|
3184
|
-
import { resolve as
|
|
3185
|
-
import { homedir as
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
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;
|
|
3199
3647
|
}
|
|
3200
|
-
function
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
depth,
|
|
3208
|
-
childPaths: [],
|
|
3209
|
-
error: "",
|
|
3210
|
-
l1Hit: false,
|
|
3211
|
-
l1RiskLevel: "",
|
|
3212
|
-
l1RiskType: "",
|
|
3213
|
-
l1Reason: "",
|
|
3214
|
-
l1MatchedPatterns: [],
|
|
3215
|
-
l2Hit: false,
|
|
3216
|
-
l2ViolatedRule: "",
|
|
3217
|
-
l2RiskLevel: "",
|
|
3218
|
-
l2RiskType: "",
|
|
3219
|
-
l2Reason: "",
|
|
3220
|
-
matchedKeywords: [],
|
|
3221
|
-
isSuspicious: false,
|
|
3222
|
-
keywordRiskLevel: "",
|
|
3223
|
-
keywordRiskReason: ""
|
|
3224
|
-
};
|
|
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;
|
|
3225
3655
|
}
|
|
3226
|
-
function
|
|
3227
|
-
|
|
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;
|
|
3228
3663
|
}
|
|
3229
|
-
function
|
|
3230
|
-
let
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
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;
|
|
3235
3673
|
}
|
|
3236
|
-
function
|
|
3237
|
-
|
|
3238
|
-
const
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
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;
|
|
3251
3690
|
}
|
|
3691
|
+
if (vals.some((v) => v === true)) return true;
|
|
3692
|
+
if (vals.every((v) => v === false)) return false;
|
|
3693
|
+
return null;
|
|
3252
3694
|
}
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
}
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
result = stripOutputText(result);
|
|
3273
|
-
result = expandSimpleVariables(result);
|
|
3274
|
-
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;
|
|
3275
3714
|
}
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
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;
|
|
3723
|
+
}
|
|
3724
|
+
return out;
|
|
3725
|
+
}
|
|
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;
|
|
3731
|
+
}
|
|
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;
|
|
3737
|
+
}
|
|
3738
|
+
return null;
|
|
3739
|
+
}
|
|
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;
|
|
3751
|
+
}
|
|
3752
|
+
const rv = evalExpr(right, env);
|
|
3753
|
+
if (rv !== null) return rv.includes(lv);
|
|
3754
|
+
return null;
|
|
3755
|
+
}
|
|
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;
|
|
3767
|
+
}
|
|
3768
|
+
return null;
|
|
3769
|
+
}
|
|
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;
|
|
3782
|
+
}
|
|
3783
|
+
return true;
|
|
3784
|
+
});
|
|
3785
|
+
}
|
|
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;
|
|
3798
|
+
}
|
|
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);
|
|
3814
|
+
}
|
|
3815
|
+
return;
|
|
3816
|
+
}
|
|
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);
|
|
3821
|
+
}
|
|
3822
|
+
return;
|
|
3823
|
+
}
|
|
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);
|
|
3828
|
+
return;
|
|
3829
|
+
}
|
|
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
|
+
}
|
|
3836
|
+
return;
|
|
3837
|
+
}
|
|
3838
|
+
if (name === "FunctionDefinition" || name === "ClassDefinition") {
|
|
3839
|
+
markDefTree(stmt, lines, lineStarts);
|
|
3840
|
+
return;
|
|
3841
|
+
}
|
|
3842
|
+
if (name === "ExpressionStatement") {
|
|
3843
|
+
invalidateEnvOnCalls(stmt, env);
|
|
3844
|
+
}
|
|
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;
|
|
3860
|
+
}
|
|
3861
|
+
return out;
|
|
3862
|
+
}
|
|
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;
|
|
3874
|
+
}
|
|
3875
|
+
}
|
|
3876
|
+
c = c.nextSibling;
|
|
3877
|
+
}
|
|
3878
|
+
processIfSections(stmt, sections, 0, env, lineStarts, lines, branches);
|
|
3879
|
+
}
|
|
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;
|
|
3887
|
+
}
|
|
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;
|
|
3898
|
+
}
|
|
3899
|
+
if (cond === false) {
|
|
3900
|
+
processIfSections(_stmt, sections, idx + 1, env, lineStarts, lines, branches);
|
|
3901
|
+
return;
|
|
3902
|
+
}
|
|
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);
|
|
3908
|
+
}
|
|
3909
|
+
}
|
|
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]);
|
|
3922
|
+
}
|
|
3923
|
+
return { path: path5, args, interpreter: interp };
|
|
3924
|
+
}
|
|
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);
|
|
3935
|
+
}
|
|
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
|
+
};
|
|
4074
|
+
|
|
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
|
+
}
|
|
4109
|
+
}
|
|
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");
|
|
4514
|
+
}
|
|
4515
|
+
|
|
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;
|
|
4547
|
+
}
|
|
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
|
+
}
|
|
4574
|
+
}
|
|
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;
|
|
4596
|
+
} else {
|
|
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
|
+
}
|
|
4660
|
+
}
|
|
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
|
+
};
|
|
4766
|
+
}
|
|
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;
|
|
4784
|
+
}
|
|
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) {
|
|
4795
|
+
try {
|
|
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: "" };
|
|
4805
|
+
} catch (e) {
|
|
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}` };
|
|
4808
|
+
}
|
|
4809
|
+
}
|
|
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
|
|
3310
4995
|
];
|
|
3311
4996
|
var KEYWORD_COMBINATION_RISKS2 = [
|
|
3312
4997
|
{
|
|
@@ -3402,6 +5087,20 @@ function extractScriptPath2(command) {
|
|
|
3402
5087
|
}
|
|
3403
5088
|
return null;
|
|
3404
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
|
+
}
|
|
3405
5104
|
function extractInlineScriptPath(actionDetail) {
|
|
3406
5105
|
const re = /open\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
3407
5106
|
let m;
|
|
@@ -3409,9 +5108,9 @@ function extractInlineScriptPath(actionDetail) {
|
|
|
3409
5108
|
const p = m[1];
|
|
3410
5109
|
let resolved;
|
|
3411
5110
|
try {
|
|
3412
|
-
resolved =
|
|
3413
|
-
if (
|
|
3414
|
-
const stat =
|
|
5111
|
+
resolved = resolvePath2(p);
|
|
5112
|
+
if (existsSync9(resolved)) {
|
|
5113
|
+
const stat = statSync2(resolved);
|
|
3415
5114
|
if (stat.isFile()) return resolved;
|
|
3416
5115
|
}
|
|
3417
5116
|
} catch {
|
|
@@ -3419,136 +5118,12 @@ function extractInlineScriptPath(actionDetail) {
|
|
|
3419
5118
|
}
|
|
3420
5119
|
return null;
|
|
3421
5120
|
}
|
|
3422
|
-
function
|
|
5121
|
+
function resolvePath2(p) {
|
|
3423
5122
|
let expanded = p;
|
|
3424
5123
|
if (expanded.startsWith("~/") || expanded === "~") {
|
|
3425
|
-
expanded =
|
|
3426
|
-
}
|
|
3427
|
-
return resolve6(expanded);
|
|
3428
|
-
}
|
|
3429
|
-
function readScriptContent2(scriptPath, maxFileSize = 2 * 1024 * 1024) {
|
|
3430
|
-
try {
|
|
3431
|
-
const path3 = resolvePath(scriptPath);
|
|
3432
|
-
if (!existsSync7(path3)) return { content: "", lineCount: 0, fileSize: 0, error: `\u811A\u672C\u4E0D\u5B58\u5728: ${scriptPath}` };
|
|
3433
|
-
const stat = statSync(path3);
|
|
3434
|
-
if (!stat.isFile()) return { content: "", lineCount: 0, fileSize: 0, error: `\u4E0D\u662F\u6587\u4EF6: ${scriptPath}` };
|
|
3435
|
-
if (stat.size > maxFileSize) {
|
|
3436
|
-
return { content: "", lineCount: 0, fileSize: stat.size, error: `\u811A\u672C\u6587\u4EF6\u8FC7\u5927 (${stat.size} bytes)\uFF0C\u8DF3\u8FC7\u8BFB\u53D6` };
|
|
3437
|
-
}
|
|
3438
|
-
const content = readFileSync8(path3, "utf-8");
|
|
3439
|
-
const lineCount = content ? content.split("\n").length : 0;
|
|
3440
|
-
return { content, lineCount, fileSize: stat.size, error: "" };
|
|
3441
|
-
} catch (e) {
|
|
3442
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
3443
|
-
return { content: "", lineCount: 0, fileSize: 0, error: `\u8BFB\u53D6\u811A\u672C\u5931\u8D25: ${msg}` };
|
|
3444
|
-
}
|
|
3445
|
-
}
|
|
3446
|
-
function extractChildScriptPaths(content, baseDir) {
|
|
3447
|
-
const paths = [];
|
|
3448
|
-
for (const pattern of CHILD_SCRIPT_PATTERNS) {
|
|
3449
|
-
pattern.lastIndex = 0;
|
|
3450
|
-
let m;
|
|
3451
|
-
while ((m = pattern.exec(content)) !== null) {
|
|
3452
|
-
const pathStr = m[1];
|
|
3453
|
-
if (pathStr.startsWith("-") || pathStr.startsWith("$")) continue;
|
|
3454
|
-
if (pathStr.includes("/dev/") || pathStr.startsWith("|") || pathStr.startsWith(";")) continue;
|
|
3455
|
-
const full = isAbsolute(pathStr) ? pathStr : join4(baseDir, pathStr);
|
|
3456
|
-
const resolved = resolvePath(full);
|
|
3457
|
-
if (existsSync7(resolved)) {
|
|
3458
|
-
try {
|
|
3459
|
-
if (statSync(resolved).isFile() && !paths.includes(resolved)) paths.push(resolved);
|
|
3460
|
-
} catch {
|
|
3461
|
-
}
|
|
3462
|
-
}
|
|
3463
|
-
}
|
|
3464
|
-
}
|
|
3465
|
-
return paths;
|
|
3466
|
-
}
|
|
3467
|
-
function resolveScriptChain(entryPath, input, maxDepth = 3, maxFileSize = 2 * 1024 * 1024, maxTotalSize = 6 * 1024 * 1024) {
|
|
3468
|
-
const visited = /* @__PURE__ */ new Set();
|
|
3469
|
-
const result = emptyChain();
|
|
3470
|
-
result.entryPath = entryPath;
|
|
3471
|
-
const entry = resolvePath(entryPath);
|
|
3472
|
-
const baseDir = existsSync7(entry) ? dirname6(entry) : input.cwd || process.cwd();
|
|
3473
|
-
resolveRecursive({
|
|
3474
|
-
path: entry,
|
|
3475
|
-
depth: 0,
|
|
3476
|
-
maxDepth,
|
|
3477
|
-
maxFileSize,
|
|
3478
|
-
maxTotalSize,
|
|
3479
|
-
visited,
|
|
3480
|
-
result,
|
|
3481
|
-
baseDir,
|
|
3482
|
-
input
|
|
3483
|
-
});
|
|
3484
|
-
result.hasCritical = result.nodes.some((n) => n.l1RiskLevel === "critical" || n.l2RiskLevel === "critical" || n.keywordRiskLevel === "critical");
|
|
3485
|
-
result.hasHigh = result.nodes.some((n) => ["high", "critical"].includes(n.l1RiskLevel) || ["high", "critical"].includes(n.l2RiskLevel) || ["high", "critical"].includes(n.keywordRiskLevel));
|
|
3486
|
-
result.firstHitNode = result.nodes.find((n) => n.l1Hit || n.l2Hit || n.isSuspicious) || null;
|
|
3487
|
-
return result;
|
|
3488
|
-
}
|
|
3489
|
-
function resolveRecursive(ctx) {
|
|
3490
|
-
const { path: path3, depth, maxDepth, maxFileSize, maxTotalSize, visited, result, baseDir, input } = ctx;
|
|
3491
|
-
if (visited.has(path3)) return;
|
|
3492
|
-
if (depth > maxDepth) {
|
|
3493
|
-
result.maxDepthReached = true;
|
|
3494
|
-
return;
|
|
3495
|
-
}
|
|
3496
|
-
if (result.totalSize >= maxTotalSize) {
|
|
3497
|
-
result.maxSizeReached = true;
|
|
3498
|
-
return;
|
|
3499
|
-
}
|
|
3500
|
-
visited.add(path3);
|
|
3501
|
-
const { content, lineCount, fileSize, error } = readScriptContent2(path3, maxFileSize);
|
|
3502
|
-
if (error) {
|
|
3503
|
-
const node2 = newNode(path3, depth);
|
|
3504
|
-
node2.error = error;
|
|
3505
|
-
node2.fileSize = fileSize;
|
|
3506
|
-
node2.lineCount = lineCount;
|
|
3507
|
-
result.nodes.push(node2);
|
|
3508
|
-
return;
|
|
3509
|
-
}
|
|
3510
|
-
result.totalSize += fileSize;
|
|
3511
|
-
result.totalLines += lineCount;
|
|
3512
|
-
const preprocessed = preprocessScriptContent(content);
|
|
3513
|
-
const node = newNode(path3, depth);
|
|
3514
|
-
node.rawContent = content;
|
|
3515
|
-
node.preprocessedContent = preprocessed;
|
|
3516
|
-
node.lineCount = lineCount;
|
|
3517
|
-
node.fileSize = fileSize;
|
|
3518
|
-
result.nodes.push(node);
|
|
3519
|
-
const virtualInput = {
|
|
3520
|
-
...input,
|
|
3521
|
-
aNext: { actionType: "bash", actionDetail: preprocessed }
|
|
3522
|
-
};
|
|
3523
|
-
const l1 = runHeuristicCheck(virtualInput);
|
|
3524
|
-
if (l1 && !l1.allowed) {
|
|
3525
|
-
node.l1Hit = true;
|
|
3526
|
-
node.l1RiskLevel = l1.riskLevel;
|
|
3527
|
-
node.l1RiskType = l1.riskType;
|
|
3528
|
-
node.l1Reason = l1.reason;
|
|
3529
|
-
node.l1MatchedPatterns = [];
|
|
3530
|
-
if (l1.riskLevel === "critical") {
|
|
3531
|
-
result.hasCritical = true;
|
|
3532
|
-
result.firstHitNode = node;
|
|
3533
|
-
return;
|
|
3534
|
-
}
|
|
3535
|
-
}
|
|
3536
|
-
const childPaths = extractChildScriptPaths(preprocessed, baseDir);
|
|
3537
|
-
node.childPaths = childPaths;
|
|
3538
|
-
for (const childPath of childPaths) {
|
|
3539
|
-
resolveRecursive({
|
|
3540
|
-
path: resolvePath(childPath),
|
|
3541
|
-
depth: depth + 1,
|
|
3542
|
-
maxDepth,
|
|
3543
|
-
maxFileSize,
|
|
3544
|
-
maxTotalSize,
|
|
3545
|
-
visited,
|
|
3546
|
-
result,
|
|
3547
|
-
baseDir: dirname6(resolvePath(childPath)),
|
|
3548
|
-
input
|
|
3549
|
-
});
|
|
3550
|
-
if (result.hasCritical) return;
|
|
5124
|
+
expanded = join5(homedir3(), expanded.slice(1));
|
|
3551
5125
|
}
|
|
5126
|
+
return resolve8(expanded);
|
|
3552
5127
|
}
|
|
3553
5128
|
function scanScriptChainWithL1(chain, input) {
|
|
3554
5129
|
for (const node of chain.nodes) {
|
|
@@ -3617,14 +5192,31 @@ function scanScriptChainKeywords(chain) {
|
|
|
3617
5192
|
function analyzeScriptContent2(input) {
|
|
3618
5193
|
const actionType = input.aNext.actionType.toLowerCase();
|
|
3619
5194
|
if (actionType !== "bash") return emptyChain();
|
|
3620
|
-
const scriptPath =
|
|
5195
|
+
const { path: scriptPath, args: scriptArgs } = extractScriptPathWithArgs(input.aNext.actionDetail);
|
|
3621
5196
|
let resolvedPath = scriptPath;
|
|
5197
|
+
let isInline = false;
|
|
3622
5198
|
if (!resolvedPath) {
|
|
3623
5199
|
const inline = extractInlineScriptPath(input.aNext.actionDetail);
|
|
3624
5200
|
if (!inline) return emptyChain();
|
|
3625
5201
|
resolvedPath = inline;
|
|
5202
|
+
isInline = true;
|
|
3626
5203
|
}
|
|
3627
|
-
|
|
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());
|
|
3628
5220
|
if (chain.nodes.length === 0) return chain;
|
|
3629
5221
|
scanScriptChainWithL1(chain, input);
|
|
3630
5222
|
scanScriptChainWithL2(chain, input);
|
|
@@ -3692,18 +5284,6 @@ ${contentPreview}
|
|
|
3692
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";
|
|
3693
5285
|
return header + parts.join("\n---\n\n");
|
|
3694
5286
|
}
|
|
3695
|
-
var CHILD_SCRIPT_PATTERNS = [
|
|
3696
|
-
/(?:^|\s|&&|\|\|)\s*bash\s+(\S+)/i,
|
|
3697
|
-
/(?:^|\s|&&|\|\|)\s*sh\s+(\S+)/i,
|
|
3698
|
-
/(?:^|\s|&&|\|\|)\s*source\s+(\S+)/i,
|
|
3699
|
-
/(?:^|\s|&&|\|\|)\s*\.\s+(\S+)/,
|
|
3700
|
-
/(?:^|\s|&&|\|\|)\s*python3?\s+(\S+\.py)/i,
|
|
3701
|
-
/(?:^|\s|&&|\|\|)\s*node\s+(\S+\.js)/i,
|
|
3702
|
-
/(?:^|\s|&&|\|\|)\s*ruby\s+(\S+\.rb)/i,
|
|
3703
|
-
/(?:^|\s|&&|\|\|)\s*perl\s+(\S+\.pl)/i,
|
|
3704
|
-
/(?:^|\s|&&|\|\|)\s*php\s+(\S+\.php)/i,
|
|
3705
|
-
/(?:^|\s|&&|\|\|)\s*lua\s+(\S+\.lua)/i
|
|
3706
|
-
];
|
|
3707
5287
|
|
|
3708
5288
|
// src/engine/llm-analyzer.ts
|
|
3709
5289
|
import { appendFileSync } from "fs";
|
|
@@ -3919,7 +5499,7 @@ async function analyzeWithLLM(prompt, config) {
|
|
|
3919
5499
|
}
|
|
3920
5500
|
if (attempt < maxRetries) {
|
|
3921
5501
|
console.error(`[AgentMoss] LLM call failed (attempt ${attempt + 1}/${maxRetries + 1}), retrying in ${retryCfg.retryInterval}s:`, err.message);
|
|
3922
|
-
await new Promise((
|
|
5502
|
+
await new Promise((resolve9) => setTimeout(resolve9, retryCfg.retryInterval * 1e3));
|
|
3923
5503
|
} else {
|
|
3924
5504
|
clearTimeout(totalTimeoutTimer);
|
|
3925
5505
|
console.error("[AgentMoss] LLM call failed after all retries:", err.message);
|
|
@@ -3934,16 +5514,17 @@ async function analyzeWithLLM(prompt, config) {
|
|
|
3934
5514
|
// src/engine/coordinator.ts
|
|
3935
5515
|
init_config();
|
|
3936
5516
|
init_agents();
|
|
5517
|
+
init_scenarios();
|
|
3937
5518
|
|
|
3938
5519
|
// src/brain/index.ts
|
|
3939
5520
|
init_esm_shims();
|
|
3940
5521
|
|
|
3941
5522
|
// src/brain/rule-store.ts
|
|
3942
5523
|
init_esm_shims();
|
|
3943
|
-
import { existsSync as
|
|
3944
|
-
import { join as
|
|
3945
|
-
import { homedir as
|
|
3946
|
-
var DEFAULT_RULES_PATH =
|
|
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");
|
|
3947
5528
|
var ENV_RULES_PATH = "AGENT_MOSS_BRAIN_RULES_PATH";
|
|
3948
5529
|
var STATUS_DRAFT = "draft";
|
|
3949
5530
|
var STATUS_APPROVED = "approved";
|
|
@@ -3967,16 +5548,16 @@ function uuid() {
|
|
|
3967
5548
|
var BrainRuleStore = class {
|
|
3968
5549
|
path;
|
|
3969
5550
|
data;
|
|
3970
|
-
constructor(
|
|
3971
|
-
this.path =
|
|
5551
|
+
constructor(path5) {
|
|
5552
|
+
this.path = path5 || getRulesPath();
|
|
3972
5553
|
this.data = this.load();
|
|
3973
5554
|
}
|
|
3974
5555
|
load() {
|
|
3975
|
-
if (!
|
|
5556
|
+
if (!existsSync11(this.path)) {
|
|
3976
5557
|
return { version: 1, rules: [], pattern_candidates: [] };
|
|
3977
5558
|
}
|
|
3978
5559
|
try {
|
|
3979
|
-
const raw = JSON.parse(
|
|
5560
|
+
const raw = JSON.parse(readFileSync12(this.path, "utf-8"));
|
|
3980
5561
|
return {
|
|
3981
5562
|
version: raw.version || 1,
|
|
3982
5563
|
rules: raw.rules || [],
|
|
@@ -4274,10 +5855,10 @@ var PatternExtractor = class {
|
|
|
4274
5855
|
const pathRe = new RegExp(pathPrefix + pathBody + pathSuffix, "g");
|
|
4275
5856
|
let mm;
|
|
4276
5857
|
while ((mm = pathRe.exec(src)) !== null) {
|
|
4277
|
-
const
|
|
4278
|
-
if (
|
|
4279
|
-
if (["/dev/null", "/dev/zero", "/dev/random", "/dev/urandom"].includes(
|
|
4280
|
-
const gen = this.generalizePath(
|
|
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);
|
|
4281
5862
|
if (seen.has(gen)) continue;
|
|
4282
5863
|
seen.add(gen);
|
|
4283
5864
|
patterns.push({ pattern: gen, layer: 1, scope: "openat", ruleType: "deny_path" });
|
|
@@ -4296,8 +5877,8 @@ var PatternExtractor = class {
|
|
|
4296
5877
|
}
|
|
4297
5878
|
return false;
|
|
4298
5879
|
}
|
|
4299
|
-
generalizePath(
|
|
4300
|
-
const p =
|
|
5880
|
+
generalizePath(path5) {
|
|
5881
|
+
const p = path5.toLowerCase();
|
|
4301
5882
|
if (/\/etc\/(shadow|passwd|sudoers|security|ssh)/.test(p)) {
|
|
4302
5883
|
return "(?:^|[^a-z0-9_-])etc/(?:shadow|passwd|sudoers|security/|ssh/)";
|
|
4303
5884
|
}
|
|
@@ -4460,6 +6041,468 @@ function checkLayer2Context(pattern, actionHistory, actionDetail) {
|
|
|
4460
6041
|
return true;
|
|
4461
6042
|
}
|
|
4462
6043
|
|
|
6044
|
+
// src/engine/formal-engine/index.ts
|
|
6045
|
+
init_esm_shims();
|
|
6046
|
+
|
|
6047
|
+
// src/engine/formal-engine/engine.ts
|
|
6048
|
+
init_esm_shims();
|
|
6049
|
+
|
|
6050
|
+
// src/engine/formal-engine/builder.ts
|
|
6051
|
+
init_esm_shims();
|
|
6052
|
+
init_rules_loader();
|
|
6053
|
+
|
|
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;
|
|
6061
|
+
}
|
|
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;
|
|
6066
|
+
}
|
|
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);
|
|
6083
|
+
}
|
|
6084
|
+
} catch {
|
|
6085
|
+
}
|
|
6086
|
+
}
|
|
6087
|
+
return out;
|
|
6088
|
+
}
|
|
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;
|
|
6101
|
+
}
|
|
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);
|
|
6110
|
+
}
|
|
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;
|
|
6120
|
+
}
|
|
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;
|
|
6132
|
+
}
|
|
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);
|
|
6199
|
+
});
|
|
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"));
|
|
6232
|
+
}
|
|
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
|
|
6319
|
+
});
|
|
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
|
+
}
|
|
6351
|
+
}
|
|
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;
|
|
6473
|
+
try {
|
|
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: []
|
|
6482
|
+
});
|
|
6483
|
+
return result;
|
|
6484
|
+
}
|
|
6485
|
+
if (violations.length > 0) {
|
|
6486
|
+
result.verified = false;
|
|
6487
|
+
result.violations.push(...violations);
|
|
6488
|
+
break;
|
|
6489
|
+
}
|
|
6490
|
+
}
|
|
6491
|
+
return result;
|
|
6492
|
+
}
|
|
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";
|
|
6505
|
+
|
|
4463
6506
|
// src/engine/coordinator.ts
|
|
4464
6507
|
var FULLY_SAFE_TOOLS = /* @__PURE__ */ new Set([
|
|
4465
6508
|
"ask_user_question",
|
|
@@ -4562,6 +6605,7 @@ function _resetBrainForTest() {
|
|
|
4562
6605
|
_brainExtractor = null;
|
|
4563
6606
|
_brainInitialized = false;
|
|
4564
6607
|
}
|
|
6608
|
+
var formalEngine = new FormalEngine();
|
|
4565
6609
|
async function runAnalysis(input) {
|
|
4566
6610
|
const heuristicResult = isHeuristicEnabled() ? runHeuristicCheck(input) : null;
|
|
4567
6611
|
const isInlineScriptCmd = isInlineScriptCommand(input.aNext.actionDetail.toLowerCase());
|
|
@@ -4616,7 +6660,10 @@ async function runAnalysis(input) {
|
|
|
4616
6660
|
const actionType = input.aNext.actionType.toLowerCase();
|
|
4617
6661
|
const rawDetail = extractRawCommand(input.aNext.actionDetail);
|
|
4618
6662
|
const lowerDetail = rawDetail.toLowerCase();
|
|
4619
|
-
const agentExt =
|
|
6663
|
+
const agentExt = mergeAgentExtensions(
|
|
6664
|
+
getAgentExtensions(input.agentId),
|
|
6665
|
+
getScenarioExtensions(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "")
|
|
6666
|
+
);
|
|
4620
6667
|
if (FULLY_SAFE_TOOLS.has(actionType)) {
|
|
4621
6668
|
return {
|
|
4622
6669
|
allowed: true,
|
|
@@ -4721,6 +6768,28 @@ async function runAnalysis(input) {
|
|
|
4721
6768
|
return effectiveResult;
|
|
4722
6769
|
}
|
|
4723
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
|
+
}
|
|
4724
6793
|
if (scriptChain && scriptChain.nodes.length > 0 && isLogicRulesEnabled()) {
|
|
4725
6794
|
for (const node of scriptChain.nodes) {
|
|
4726
6795
|
if (node.error || !node.preprocessedContent) continue;
|