@kenhkl/agent-moss 0.12.7 → 0.12.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/__pycache__/base.cpython-312.pyc +0 -0
- package/dist/agents/base.py +41 -4
- package/dist/cli.js +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-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
package/dist/cli.js
CHANGED
|
@@ -771,6 +771,53 @@ function runLogicCheck(input) {
|
|
|
771
771
|
if (pwdResult) return pwdResult;
|
|
772
772
|
const userDelResult = checkUserDeletionConsent(actionType, actionDetail, actionHistory);
|
|
773
773
|
if (userDelResult) return userDelResult;
|
|
774
|
+
const comboResult = checkComboChain(actionHistory, aNext.actionDetail);
|
|
775
|
+
if (comboResult) return comboResult;
|
|
776
|
+
return null;
|
|
777
|
+
}
|
|
778
|
+
function checkComboChain(actionHistory, actionDetailRaw) {
|
|
779
|
+
INLINE_SCRIPT_RE.lastIndex = 0;
|
|
780
|
+
if (INLINE_SCRIPT_RE.test(actionDetailRaw)) return null;
|
|
781
|
+
const details = [];
|
|
782
|
+
for (const h of actionHistory) {
|
|
783
|
+
if (h.actionDetail) details.push(h.actionDetail.toLowerCase());
|
|
784
|
+
}
|
|
785
|
+
details.push(actionDetailRaw.toLowerCase());
|
|
786
|
+
const seq = details.map((d) => {
|
|
787
|
+
const hits = /* @__PURE__ */ new Set();
|
|
788
|
+
for (const [stage, pats] of Object.entries(COMBO_STAGES)) {
|
|
789
|
+
if (pats.some((p) => {
|
|
790
|
+
p.lastIndex = 0;
|
|
791
|
+
return p.test(d);
|
|
792
|
+
})) hits.add(stage);
|
|
793
|
+
}
|
|
794
|
+
return hits;
|
|
795
|
+
});
|
|
796
|
+
for (const chain of COMBO_CHAINS) {
|
|
797
|
+
const chainStages = chain.stages;
|
|
798
|
+
if (!chainStages.length || chain.min_stages <= 0) continue;
|
|
799
|
+
let matched = 0;
|
|
800
|
+
let stageIdx = 0;
|
|
801
|
+
for (const hits of seq) {
|
|
802
|
+
if (stageIdx < chainStages.length && hits.has(chainStages[stageIdx])) {
|
|
803
|
+
matched += 1;
|
|
804
|
+
stageIdx += 1;
|
|
805
|
+
if (matched >= chain.min_stages) break;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
if (matched >= chain.min_stages) {
|
|
809
|
+
const riskLevel = chain.risk_level === "critical" || chain.risk_level === "high" || chain.risk_level === "medium" || chain.risk_level === "low" ? chain.risk_level : "high";
|
|
810
|
+
return {
|
|
811
|
+
allowed: false,
|
|
812
|
+
reason: chain.reason,
|
|
813
|
+
riskLevel,
|
|
814
|
+
riskType: "combo_chain",
|
|
815
|
+
confidence: 85,
|
|
816
|
+
source: "logic_rule",
|
|
817
|
+
violatedLayers: ["2"]
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
}
|
|
774
821
|
return null;
|
|
775
822
|
}
|
|
776
823
|
function extractFilePaths(text) {
|
|
@@ -870,17 +917,17 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
|
|
|
870
917
|
}
|
|
871
918
|
}
|
|
872
919
|
const unreadPaths = [];
|
|
873
|
-
for (const
|
|
920
|
+
for (const path6 of uniqueTargetPaths) {
|
|
874
921
|
const hasRead = [...historyReadPaths].some(
|
|
875
|
-
(rp) =>
|
|
922
|
+
(rp) => path6 === rp || path6.startsWith(rp) || rp.startsWith(path6)
|
|
876
923
|
);
|
|
877
924
|
if (!hasRead) {
|
|
878
|
-
let isCreate = !fs2.existsSync(
|
|
925
|
+
let isCreate = !fs2.existsSync(path6);
|
|
879
926
|
if (!isCreate) {
|
|
880
927
|
isCreate = CREATE_KEYWORDS.some((kw) => reason.toLowerCase().includes(kw) || actionDetail.includes(kw));
|
|
881
928
|
}
|
|
882
929
|
if (!isCreate) {
|
|
883
|
-
unreadPaths.push(
|
|
930
|
+
unreadPaths.push(path6);
|
|
884
931
|
}
|
|
885
932
|
}
|
|
886
933
|
}
|
|
@@ -1023,7 +1070,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
|
|
|
1023
1070
|
violatedLayers: ["2"]
|
|
1024
1071
|
};
|
|
1025
1072
|
}
|
|
1026
|
-
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;
|
|
1073
|
+
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;
|
|
1027
1074
|
var init_logic_rules = __esm({
|
|
1028
1075
|
"src/engine/logic-rules.ts"() {
|
|
1029
1076
|
"use strict";
|
|
@@ -1032,7 +1079,7 @@ var init_logic_rules = __esm({
|
|
|
1032
1079
|
init_rules_loader();
|
|
1033
1080
|
ROOT_SENSITIVE_PATHS = _loadSensitivePathScope("root");
|
|
1034
1081
|
HOME_SENSITIVE_PATHS = _loadSensitivePathScope("home");
|
|
1035
|
-
CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path:
|
|
1082
|
+
CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path6, riskLevel, desc }) => ({ path: path6, riskLevel, desc, credential: true }));
|
|
1036
1083
|
NON_WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
1037
1084
|
"ask_user_question",
|
|
1038
1085
|
"glob",
|
|
@@ -1176,6 +1223,24 @@ var init_logic_rules = __esm({
|
|
|
1176
1223
|
reason: p.reason
|
|
1177
1224
|
}));
|
|
1178
1225
|
})();
|
|
1226
|
+
COMBO_STAGES = (() => {
|
|
1227
|
+
const combo = loadL2().combo_chains || {};
|
|
1228
|
+
const stages = {};
|
|
1229
|
+
for (const [name, pats] of Object.entries(combo.stages || {})) {
|
|
1230
|
+
stages[name] = pats.map((p) => new RegExp(p, "i"));
|
|
1231
|
+
}
|
|
1232
|
+
return stages;
|
|
1233
|
+
})();
|
|
1234
|
+
COMBO_CHAINS = (() => {
|
|
1235
|
+
const combo = loadL2().combo_chains || {};
|
|
1236
|
+
return (combo.chains || []).map((c) => ({
|
|
1237
|
+
id: String(c.id || "combo_chain"),
|
|
1238
|
+
stages: c.stages || [],
|
|
1239
|
+
min_stages: Number(c.min_stages) || (c.stages || []).length,
|
|
1240
|
+
risk_level: String(c.risk_level || "high"),
|
|
1241
|
+
reason: String(c.reason || "\u68C0\u6D4B\u5230\u7EC4\u5408\u653B\u51FB\u94FE")
|
|
1242
|
+
}));
|
|
1243
|
+
})();
|
|
1179
1244
|
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;
|
|
1180
1245
|
DIR_TRAVERSAL_KW = [
|
|
1181
1246
|
"os.listdir",
|
|
@@ -1293,9 +1358,11 @@ __export(runtime_config_exports, {
|
|
|
1293
1358
|
addAgentRule: () => addAgentRule,
|
|
1294
1359
|
addCustomRule: () => addCustomRule,
|
|
1295
1360
|
addCustomSkill: () => addCustomSkill,
|
|
1361
|
+
addScenarioRule: () => addScenarioRule,
|
|
1296
1362
|
deleteAgentRule: () => deleteAgentRule,
|
|
1297
1363
|
deleteCustomRule: () => deleteCustomRule,
|
|
1298
1364
|
deleteCustomSkill: () => deleteCustomSkill,
|
|
1365
|
+
deleteScenarioRule: () => deleteScenarioRule,
|
|
1299
1366
|
filterEnabledRules: () => filterEnabledRules,
|
|
1300
1367
|
generateSourceDefaults: () => generateSourceDefaults,
|
|
1301
1368
|
getAgentRules: () => getAgentRules,
|
|
@@ -1314,9 +1381,11 @@ __export(runtime_config_exports, {
|
|
|
1314
1381
|
getEnabledL2SensitivePathsOrDefault: () => getEnabledL2SensitivePathsOrDefault,
|
|
1315
1382
|
getEnabledL2UserDeletionPatterns: () => getEnabledL2UserDeletionPatterns,
|
|
1316
1383
|
getEnabledL2UserDeletionPatternsOrDefault: () => getEnabledL2UserDeletionPatternsOrDefault,
|
|
1384
|
+
getEnabledScenarioExtensions: () => getEnabledScenarioExtensions,
|
|
1317
1385
|
getEnvOverrides: () => getEnvOverrides,
|
|
1318
1386
|
getProfileOrDetect: () => getProfileOrDetect,
|
|
1319
1387
|
getRuntimeConfigPath: () => getRuntimeConfigPath,
|
|
1388
|
+
getScenarioRules: () => getScenarioRules,
|
|
1320
1389
|
hasRuntimeConfig: () => hasRuntimeConfig,
|
|
1321
1390
|
isL2CategoryEnabled: () => isL2CategoryEnabled,
|
|
1322
1391
|
isLayerEnabled: () => isLayerEnabled,
|
|
@@ -1330,6 +1399,8 @@ __export(runtime_config_exports, {
|
|
|
1330
1399
|
updateRuleDenyMode: () => updateRuleDenyMode,
|
|
1331
1400
|
updateRuleEnabled: () => updateRuleEnabled,
|
|
1332
1401
|
updateRuleSkipL3: () => updateRuleSkipL3,
|
|
1402
|
+
updateScenarioCategoryEnabled: () => updateScenarioCategoryEnabled,
|
|
1403
|
+
updateScenarioRuleEnabled: () => updateScenarioRuleEnabled,
|
|
1333
1404
|
updateSkillCategoryEnabled: () => updateSkillCategoryEnabled,
|
|
1334
1405
|
updateSkillEnabled: () => updateSkillEnabled
|
|
1335
1406
|
});
|
|
@@ -1358,7 +1429,8 @@ function emptyRuntimeConfig() {
|
|
|
1358
1429
|
L1_rules: {},
|
|
1359
1430
|
L2_rules: {},
|
|
1360
1431
|
L3_skills: {},
|
|
1361
|
-
agent_rules: {}
|
|
1432
|
+
agent_rules: {},
|
|
1433
|
+
scenario_rules: {}
|
|
1362
1434
|
};
|
|
1363
1435
|
}
|
|
1364
1436
|
function loadRuntimeConfig() {
|
|
@@ -1384,7 +1456,8 @@ function loadRuntimeConfig() {
|
|
|
1384
1456
|
L1_rules: merged.L1_rules || {},
|
|
1385
1457
|
L2_rules: merged.L2_rules || {},
|
|
1386
1458
|
L3_skills: merged.L3_skills || {},
|
|
1387
|
-
agent_rules: merged.agent_rules || {}
|
|
1459
|
+
agent_rules: merged.agent_rules || {},
|
|
1460
|
+
scenario_rules: merged.scenario_rules || {}
|
|
1388
1461
|
};
|
|
1389
1462
|
} catch {
|
|
1390
1463
|
return emptyRuntimeConfig();
|
|
@@ -1619,6 +1692,55 @@ function _l3SkillsToRules() {
|
|
|
1619
1692
|
out.user_custom = { category_enabled: true, category_desc: "", skills: [] };
|
|
1620
1693
|
return out;
|
|
1621
1694
|
}
|
|
1695
|
+
function _scenarioRulesToDefaults() {
|
|
1696
|
+
const out = {};
|
|
1697
|
+
const scenDir = existsSync4(_SCENARIOS_DIR_PROD) ? _SCENARIOS_DIR_PROD : existsSync4(_SCENARIOS_DIR_DEV) ? _SCENARIOS_DIR_DEV : _SCENARIOS_DIR_PROD;
|
|
1698
|
+
if (!existsSync4(scenDir)) return out;
|
|
1699
|
+
let entries;
|
|
1700
|
+
try {
|
|
1701
|
+
entries = readdirSync3(scenDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
1702
|
+
} catch {
|
|
1703
|
+
return out;
|
|
1704
|
+
}
|
|
1705
|
+
for (const scenarioId of entries) {
|
|
1706
|
+
const rulesFile = join2(scenDir, scenarioId, "rules.json");
|
|
1707
|
+
if (!existsSync4(rulesFile)) continue;
|
|
1708
|
+
let data;
|
|
1709
|
+
try {
|
|
1710
|
+
data = JSON.parse(readFileSync4(rulesFile, "utf-8"));
|
|
1711
|
+
} catch {
|
|
1712
|
+
continue;
|
|
1713
|
+
}
|
|
1714
|
+
out[scenarioId] = {
|
|
1715
|
+
builtin_safe_action_types: {
|
|
1716
|
+
category_enabled: true,
|
|
1717
|
+
category_desc: "\u573A\u666F\u5B8C\u5168\u5B89\u5168\u64CD\u4F5C\u767D\u540D\u5355\uFF08\u573A\u666F\u901A\u5E38\u4E0D\u4F7F\u7528\uFF09",
|
|
1718
|
+
rules: []
|
|
1719
|
+
},
|
|
1720
|
+
builtin_safe_skills: {
|
|
1721
|
+
category_enabled: true,
|
|
1722
|
+
category_desc: "\u573A\u666F\u4FE1\u4EFB\u7684\u5185\u7F6E Skill\uFF08\u573A\u666F\u901A\u5E38\u4E0D\u4F7F\u7528\uFF09",
|
|
1723
|
+
rules: []
|
|
1724
|
+
},
|
|
1725
|
+
extra_command_patterns: {
|
|
1726
|
+
category_enabled: true,
|
|
1727
|
+
category_desc: "\u573A\u666F\u4E13\u5C5E\u5371\u9669\u547D\u4EE4\u6B63\u5219\uFF08L1 \u672B\u5C3E\u8FFD\u52A0\uFF09",
|
|
1728
|
+
rules: (data.extra_command_patterns || []).map((p) => ({ ...p, enabled: true, builtin: true }))
|
|
1729
|
+
},
|
|
1730
|
+
extra_injection_keywords: {
|
|
1731
|
+
category_enabled: true,
|
|
1732
|
+
category_desc: "\u573A\u666F\u4E13\u5C5E\u6CE8\u5165\u5173\u952E\u8BCD\uFF08L1 \u672B\u5C3E\u8FFD\u52A0\uFF09",
|
|
1733
|
+
rules: (data.extra_injection_keywords || []).map((k) => ({ ...k, enabled: true, builtin: true }))
|
|
1734
|
+
},
|
|
1735
|
+
extra_sensitive_paths: {
|
|
1736
|
+
category_enabled: true,
|
|
1737
|
+
category_desc: "\u573A\u666F\u4E13\u5C5E\u654F\u611F\u8DEF\u5F84\uFF08L1 \u672B\u5C3E\u8FFD\u52A0\uFF09",
|
|
1738
|
+
rules: (data.extra_sensitive_paths || []).map((sp) => ({ ...sp, enabled: true, builtin: true }))
|
|
1739
|
+
}
|
|
1740
|
+
};
|
|
1741
|
+
}
|
|
1742
|
+
return out;
|
|
1743
|
+
}
|
|
1622
1744
|
function _agentRulesToDefaults() {
|
|
1623
1745
|
const out = {};
|
|
1624
1746
|
const agentsDir = existsSync4(_AGENTS_DIR_PROD) ? _AGENTS_DIR_PROD : existsSync4(_AGENTS_DIR_DEV) ? _AGENTS_DIR_DEV : _AGENTS_DIR_PROD;
|
|
@@ -1710,7 +1832,8 @@ function generateSourceDefaults() {
|
|
|
1710
1832
|
user_custom: { category_enabled: true, category_desc: "\u7528\u6237\u81EA\u5B9A\u4E49 L2 \u903B\u8F91\u89C4\u5219", rules: [] }
|
|
1711
1833
|
},
|
|
1712
1834
|
L3_skills: _l3SkillsToRules(),
|
|
1713
|
-
agent_rules: _agentRulesToDefaults()
|
|
1835
|
+
agent_rules: _agentRulesToDefaults(),
|
|
1836
|
+
scenario_rules: _scenarioRulesToDefaults()
|
|
1714
1837
|
};
|
|
1715
1838
|
}
|
|
1716
1839
|
function normalizePatternForFingerprint(pattern) {
|
|
@@ -1803,9 +1926,9 @@ function _mergeSkillCategories(userCfg, srcDefaults, layerKey) {
|
|
|
1803
1926
|
}
|
|
1804
1927
|
}
|
|
1805
1928
|
}
|
|
1806
|
-
function _mergeAgentRules(userCfg, srcDefaults) {
|
|
1807
|
-
const userAgent = userCfg
|
|
1808
|
-
const srcAgent = srcDefaults
|
|
1929
|
+
function _mergeAgentRules(userCfg, srcDefaults, partition = "agent_rules") {
|
|
1930
|
+
const userAgent = userCfg[partition] || (userCfg[partition] = {});
|
|
1931
|
+
const srcAgent = srcDefaults[partition] || {};
|
|
1809
1932
|
for (const [agentId, srcCats] of Object.entries(srcAgent)) {
|
|
1810
1933
|
if (!(agentId in userAgent)) {
|
|
1811
1934
|
userAgent[agentId] = JSON.parse(JSON.stringify(srcCats));
|
|
@@ -1864,6 +1987,7 @@ function mergeConfig(userCfg, srcDefaults) {
|
|
|
1864
1987
|
_mergeRuleCategories(cfg, srcDefaults, "L2_rules");
|
|
1865
1988
|
_mergeSkillCategories(cfg, srcDefaults, "L3_skills");
|
|
1866
1989
|
_mergeAgentRules(cfg, srcDefaults);
|
|
1990
|
+
_mergeAgentRules(cfg, srcDefaults, "scenario_rules");
|
|
1867
1991
|
return cfg;
|
|
1868
1992
|
}
|
|
1869
1993
|
function _migrateCategoriesAcrossLayers(cfg, srcLayer, dstLayer, catNames) {
|
|
@@ -2189,7 +2313,85 @@ function updateAgentCategoryEnabled(agentId, category, enabled) {
|
|
|
2189
2313
|
saveRuntimeConfig(runtime);
|
|
2190
2314
|
return runtime;
|
|
2191
2315
|
}
|
|
2192
|
-
|
|
2316
|
+
function getScenarioRules(runtime) {
|
|
2317
|
+
return runtime.scenario_rules || {};
|
|
2318
|
+
}
|
|
2319
|
+
function getEnabledScenarioExtensions(runtime, scenarioId) {
|
|
2320
|
+
const scenData = runtime.scenario_rules?.[scenarioId];
|
|
2321
|
+
if (!scenData) return null;
|
|
2322
|
+
const cmdPatterns = _agentCategoryEnabledRules(scenData.extra_command_patterns).filter((r) => r.pattern).map((r) => ({
|
|
2323
|
+
pattern: r.pattern,
|
|
2324
|
+
risk_level: r.risk_level || "",
|
|
2325
|
+
risk_type: r.risk_type || "",
|
|
2326
|
+
reason: r.reason || ""
|
|
2327
|
+
}));
|
|
2328
|
+
const injKeywords = _agentCategoryEnabledRules(scenData.extra_injection_keywords).filter((r) => r.keyword).map((r) => ({
|
|
2329
|
+
keyword: r.keyword,
|
|
2330
|
+
risk_level: r.risk_level || "",
|
|
2331
|
+
lang: r.lang || "en"
|
|
2332
|
+
}));
|
|
2333
|
+
const sensitivePaths = _agentCategoryEnabledRules(scenData.extra_sensitive_paths).filter((r) => r.path).map((r) => ({
|
|
2334
|
+
path: r.path,
|
|
2335
|
+
risk_level: r.risk_level || "",
|
|
2336
|
+
desc: r.desc || "",
|
|
2337
|
+
credential: r.credential,
|
|
2338
|
+
read_only: r.read_only,
|
|
2339
|
+
deny_mode: r.deny_mode
|
|
2340
|
+
}));
|
|
2341
|
+
return {
|
|
2342
|
+
agent_id: scenarioId,
|
|
2343
|
+
description: "",
|
|
2344
|
+
extra_command_patterns: cmdPatterns,
|
|
2345
|
+
extra_injection_keywords: injKeywords,
|
|
2346
|
+
extra_sensitive_paths: sensitivePaths,
|
|
2347
|
+
builtin_safe_skills: [],
|
|
2348
|
+
builtin_safe_action_types: [],
|
|
2349
|
+
extra_skills: []
|
|
2350
|
+
};
|
|
2351
|
+
}
|
|
2352
|
+
function updateScenarioRuleEnabled(scenarioId, category, ruleFp, enabled) {
|
|
2353
|
+
const runtime = loadRuntimeConfig();
|
|
2354
|
+
const cat = runtime.scenario_rules?.[scenarioId]?.[category];
|
|
2355
|
+
if (cat) {
|
|
2356
|
+
const targetFp = _ruleFingerprint(ruleFp);
|
|
2357
|
+
for (const r of cat.rules || []) {
|
|
2358
|
+
if (_ruleFingerprint(r) === targetFp) {
|
|
2359
|
+
r.enabled = enabled;
|
|
2360
|
+
break;
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
saveRuntimeConfig(runtime);
|
|
2365
|
+
return runtime;
|
|
2366
|
+
}
|
|
2367
|
+
function addScenarioRule(scenarioId, category, rule) {
|
|
2368
|
+
const runtime = loadRuntimeConfig();
|
|
2369
|
+
if (!runtime.scenario_rules) runtime.scenario_rules = {};
|
|
2370
|
+
if (!runtime.scenario_rules[scenarioId]) runtime.scenario_rules[scenarioId] = {};
|
|
2371
|
+
const cats = runtime.scenario_rules[scenarioId];
|
|
2372
|
+
if (!cats[category]) cats[category] = { category_enabled: true, category_desc: "", rules: [] };
|
|
2373
|
+
(cats[category].rules || (cats[category].rules = [])).push({ ...rule, enabled: true, builtin: false });
|
|
2374
|
+
saveRuntimeConfig(runtime);
|
|
2375
|
+
return runtime;
|
|
2376
|
+
}
|
|
2377
|
+
function deleteScenarioRule(scenarioId, category, ruleFp) {
|
|
2378
|
+
const runtime = loadRuntimeConfig();
|
|
2379
|
+
const cat = runtime.scenario_rules?.[scenarioId]?.[category];
|
|
2380
|
+
if (cat) {
|
|
2381
|
+
const targetFp = _ruleFingerprint(ruleFp);
|
|
2382
|
+
cat.rules = (cat.rules || []).filter((r) => _ruleFingerprint(r) !== targetFp);
|
|
2383
|
+
}
|
|
2384
|
+
saveRuntimeConfig(runtime);
|
|
2385
|
+
return runtime;
|
|
2386
|
+
}
|
|
2387
|
+
function updateScenarioCategoryEnabled(scenarioId, category, enabled) {
|
|
2388
|
+
const runtime = loadRuntimeConfig();
|
|
2389
|
+
const cat = runtime.scenario_rules?.[scenarioId]?.[category];
|
|
2390
|
+
if (cat) cat.category_enabled = enabled;
|
|
2391
|
+
saveRuntimeConfig(runtime);
|
|
2392
|
+
return runtime;
|
|
2393
|
+
}
|
|
2394
|
+
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;
|
|
2193
2395
|
var init_runtime_config = __esm({
|
|
2194
2396
|
"src/infra/runtime-config.ts"() {
|
|
2195
2397
|
"use strict";
|
|
@@ -2214,6 +2416,8 @@ var init_runtime_config = __esm({
|
|
|
2214
2416
|
};
|
|
2215
2417
|
_AGENTS_DIR_PROD = join2(__dirname4, "agents");
|
|
2216
2418
|
_AGENTS_DIR_DEV = join2(__dirname4, "..", "..", "..", "agent_moss", "agents");
|
|
2419
|
+
_SCENARIOS_DIR_PROD = join2(__dirname4, "scenarios");
|
|
2420
|
+
_SCENARIOS_DIR_DEV = join2(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
|
|
2217
2421
|
SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
|
|
2218
2422
|
}
|
|
2219
2423
|
});
|
|
@@ -2265,6 +2469,21 @@ function getAgentExtensions(agentId) {
|
|
|
2265
2469
|
);
|
|
2266
2470
|
return ext;
|
|
2267
2471
|
}
|
|
2472
|
+
function mergeAgentExtensions(...exts) {
|
|
2473
|
+
const nonNull = exts.filter((e) => !!e);
|
|
2474
|
+
if (nonNull.length === 0) return null;
|
|
2475
|
+
const base = nonNull[0];
|
|
2476
|
+
return {
|
|
2477
|
+
agent_id: base.agent_id,
|
|
2478
|
+
description: base.description,
|
|
2479
|
+
extra_command_patterns: nonNull.flatMap((e) => e.extra_command_patterns || []),
|
|
2480
|
+
extra_injection_keywords: nonNull.flatMap((e) => e.extra_injection_keywords || []),
|
|
2481
|
+
extra_sensitive_paths: nonNull.flatMap((e) => e.extra_sensitive_paths || []),
|
|
2482
|
+
builtin_safe_skills: nonNull.flatMap((e) => e.builtin_safe_skills || []),
|
|
2483
|
+
builtin_safe_action_types: nonNull.flatMap((e) => e.builtin_safe_action_types || []),
|
|
2484
|
+
extra_skills: nonNull.flatMap((e) => e.extra_skills || [])
|
|
2485
|
+
};
|
|
2486
|
+
}
|
|
2268
2487
|
var __filename4, __dirname5, AGENTS_DIR_PROD, AGENTS_DIR_DEV_FALLBACK, cache;
|
|
2269
2488
|
var init_agents = __esm({
|
|
2270
2489
|
"src/agents/index.ts"() {
|
|
@@ -2278,6 +2497,77 @@ var init_agents = __esm({
|
|
|
2278
2497
|
}
|
|
2279
2498
|
});
|
|
2280
2499
|
|
|
2500
|
+
// src/scenarios/index.ts
|
|
2501
|
+
import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
|
|
2502
|
+
import { resolve as resolve4, dirname as dirname5 } from "path";
|
|
2503
|
+
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
2504
|
+
function resolveScenariosDir() {
|
|
2505
|
+
if (existsSync6(SCENARIOS_DIR_PROD)) return SCENARIOS_DIR_PROD;
|
|
2506
|
+
if (existsSync6(SCENARIOS_DIR_DEV_FALLBACK)) return SCENARIOS_DIR_DEV_FALLBACK;
|
|
2507
|
+
return SCENARIOS_DIR_PROD;
|
|
2508
|
+
}
|
|
2509
|
+
function getScenarioExtensions(scenarioId) {
|
|
2510
|
+
if (!scenarioId) return null;
|
|
2511
|
+
if (cache2.has(scenarioId)) return cache2.get(scenarioId) ?? null;
|
|
2512
|
+
let ext = null;
|
|
2513
|
+
try {
|
|
2514
|
+
const { hasRuntimeConfig: hasRuntimeConfig2, loadRuntimeConfig: loadRuntimeConfig2, getEnabledScenarioExtensions: getEnabledScenarioExtensions2 } = (init_runtime_config(), __toCommonJS(runtime_config_exports));
|
|
2515
|
+
if (hasRuntimeConfig2()) {
|
|
2516
|
+
ext = getEnabledScenarioExtensions2(loadRuntimeConfig2(), scenarioId);
|
|
2517
|
+
}
|
|
2518
|
+
} catch {
|
|
2519
|
+
}
|
|
2520
|
+
if (ext === null) {
|
|
2521
|
+
const scenariosDir = resolveScenariosDir();
|
|
2522
|
+
const rulesFile = resolve4(scenariosDir, scenarioId, "rules.json");
|
|
2523
|
+
if (!existsSync6(rulesFile)) {
|
|
2524
|
+
cache2.set(scenarioId, null);
|
|
2525
|
+
return null;
|
|
2526
|
+
}
|
|
2527
|
+
try {
|
|
2528
|
+
const raw = readFileSync6(rulesFile, "utf-8");
|
|
2529
|
+
const data = JSON.parse(raw);
|
|
2530
|
+
const id = data.scenario_id || data.agent_id || "";
|
|
2531
|
+
if (!id) {
|
|
2532
|
+
console.warn(`[AgentMoss scenarios] rules.json \u7F3A\u5C11 scenario_id \u5B57\u6BB5: ${rulesFile}`);
|
|
2533
|
+
cache2.set(scenarioId, null);
|
|
2534
|
+
return null;
|
|
2535
|
+
}
|
|
2536
|
+
ext = {
|
|
2537
|
+
agent_id: id,
|
|
2538
|
+
description: data.description || "",
|
|
2539
|
+
extra_command_patterns: data.extra_command_patterns || [],
|
|
2540
|
+
extra_injection_keywords: data.extra_injection_keywords || [],
|
|
2541
|
+
extra_sensitive_paths: data.extra_sensitive_paths || [],
|
|
2542
|
+
builtin_safe_skills: data.builtin_safe_skills || [],
|
|
2543
|
+
builtin_safe_action_types: data.builtin_safe_action_types || [],
|
|
2544
|
+
extra_skills: data.extra_skills || []
|
|
2545
|
+
};
|
|
2546
|
+
} catch (e) {
|
|
2547
|
+
console.error(`[AgentMoss scenarios] \u52A0\u8F7D\u573A\u666F ${scenarioId} rules.json \u5931\u8D25:`, e.message);
|
|
2548
|
+
cache2.set(scenarioId, null);
|
|
2549
|
+
return null;
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
cache2.set(scenarioId, ext);
|
|
2553
|
+
console.log(
|
|
2554
|
+
`[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`
|
|
2555
|
+
);
|
|
2556
|
+
return ext;
|
|
2557
|
+
}
|
|
2558
|
+
var __filename5, __dirname6, SCENARIOS_DIR_PROD, SCENARIOS_DIR_DEV_FALLBACK, cache2;
|
|
2559
|
+
var init_scenarios = __esm({
|
|
2560
|
+
"src/scenarios/index.ts"() {
|
|
2561
|
+
"use strict";
|
|
2562
|
+
init_esm_shims();
|
|
2563
|
+
__filename5 = fileURLToPath7(import.meta.url);
|
|
2564
|
+
__dirname6 = dirname5(__filename5);
|
|
2565
|
+
SCENARIOS_DIR_PROD = resolve4(__dirname6, "scenarios");
|
|
2566
|
+
SCENARIOS_DIR_DEV_FALLBACK = resolve4(__dirname6, "../../../agent_moss/scenarios");
|
|
2567
|
+
cache2 = /* @__PURE__ */ new Map();
|
|
2568
|
+
}
|
|
2569
|
+
});
|
|
2570
|
+
|
|
2281
2571
|
// src/engine/heuristic.ts
|
|
2282
2572
|
function runHeuristicCheck(input) {
|
|
2283
2573
|
const command = `${input.aNext.actionType}: ${input.aNext.actionDetail}`;
|
|
@@ -2317,7 +2607,10 @@ function runHeuristicCheck(input) {
|
|
|
2317
2607
|
}
|
|
2318
2608
|
}
|
|
2319
2609
|
highestRiskType = topRiskType || highestRiskType;
|
|
2320
|
-
const agentExt =
|
|
2610
|
+
const agentExt = mergeAgentExtensions(
|
|
2611
|
+
getAgentExtensions(input.agentId),
|
|
2612
|
+
getScenarioExtensions(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "")
|
|
2613
|
+
);
|
|
2321
2614
|
if (agentExt?.extra_command_patterns) {
|
|
2322
2615
|
for (const rule of agentExt.extra_command_patterns) {
|
|
2323
2616
|
try {
|
|
@@ -2490,13 +2783,13 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2490
2783
|
const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
|
|
2491
2784
|
const isWriteOpFlag = isWriteOperation("", command);
|
|
2492
2785
|
const pathScanText = normalizePathSegments(stripLiteralTextSegments(command));
|
|
2493
|
-
const pathMatches = (text,
|
|
2494
|
-
const parts =
|
|
2786
|
+
const pathMatches = (text, path6) => {
|
|
2787
|
+
const parts = path6.split(/(\(\?!.*?\))/);
|
|
2495
2788
|
const escaped = parts.map((part) => {
|
|
2496
2789
|
if (part.startsWith("(?!")) return part;
|
|
2497
2790
|
return part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2498
2791
|
}).join("");
|
|
2499
|
-
const re =
|
|
2792
|
+
const re = path6.startsWith(".") || path6.startsWith("/") ? new RegExp(`(?:^|[\\s/\\\\'"])${escaped}\\b`, "i") : new RegExp(`\\b${escaped}\\b`, "i");
|
|
2500
2793
|
return re.test(text);
|
|
2501
2794
|
};
|
|
2502
2795
|
for (const sp of ROOT_SENSITIVE_PATHS) {
|
|
@@ -2760,6 +3053,7 @@ var init_heuristic = __esm({
|
|
|
2760
3053
|
init_esm_shims();
|
|
2761
3054
|
init_config();
|
|
2762
3055
|
init_agents();
|
|
3056
|
+
init_scenarios();
|
|
2763
3057
|
init_rules_loader();
|
|
2764
3058
|
init_logic_rules();
|
|
2765
3059
|
INJECTION_KEYWORDS = (() => {
|
|
@@ -2785,44 +3079,44 @@ var init_heuristic = __esm({
|
|
|
2785
3079
|
});
|
|
2786
3080
|
|
|
2787
3081
|
// src/engine/template-loader.ts
|
|
2788
|
-
import { readFileSync as
|
|
2789
|
-
import { resolve as
|
|
2790
|
-
import { fileURLToPath as
|
|
3082
|
+
import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
|
|
3083
|
+
import { resolve as resolve5, dirname as dirname6 } from "path";
|
|
3084
|
+
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
2791
3085
|
function loadTemplates() {
|
|
2792
3086
|
if (cachedTemplates) return cachedTemplates;
|
|
2793
3087
|
cachedTemplates = {};
|
|
2794
|
-
const dir =
|
|
2795
|
-
if (!
|
|
3088
|
+
const dir = existsSync7(TEMPLATES_DIR) ? TEMPLATES_DIR : TEMPLATES_DIR_FALLBACK;
|
|
3089
|
+
if (!existsSync7(dir)) {
|
|
2796
3090
|
console.warn("[AgentMoss] Templates directory not found:", TEMPLATES_DIR, "and fallback:", TEMPLATES_DIR_FALLBACK);
|
|
2797
3091
|
return cachedTemplates;
|
|
2798
3092
|
}
|
|
2799
3093
|
for (const file of TEMPLATE_FILES) {
|
|
2800
|
-
const filePath =
|
|
2801
|
-
if (
|
|
3094
|
+
const filePath = resolve5(dir, file);
|
|
3095
|
+
if (existsSync7(filePath)) {
|
|
2802
3096
|
const key = file.replace(".txt", "").replace("_template", "");
|
|
2803
|
-
cachedTemplates[key] =
|
|
3097
|
+
cachedTemplates[key] = readFileSync7(filePath, "utf-8");
|
|
2804
3098
|
}
|
|
2805
3099
|
}
|
|
2806
3100
|
console.log(`[AgentMoss] Loaded ${Object.keys(cachedTemplates).length} templates from ${dir}`);
|
|
2807
3101
|
return cachedTemplates;
|
|
2808
3102
|
}
|
|
2809
|
-
var
|
|
3103
|
+
var __filename6, __dirname7, TEMPLATES_DIR, TEMPLATES_DIR_FALLBACK, TEMPLATE_FILES, cachedTemplates;
|
|
2810
3104
|
var init_template_loader = __esm({
|
|
2811
3105
|
"src/engine/template-loader.ts"() {
|
|
2812
3106
|
"use strict";
|
|
2813
3107
|
init_esm_shims();
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
TEMPLATES_DIR =
|
|
2817
|
-
TEMPLATES_DIR_FALLBACK =
|
|
3108
|
+
__filename6 = fileURLToPath8(import.meta.url);
|
|
3109
|
+
__dirname7 = dirname6(__filename6);
|
|
3110
|
+
TEMPLATES_DIR = resolve5(__dirname7, "../templates");
|
|
3111
|
+
TEMPLATES_DIR_FALLBACK = resolve5(__dirname7, "../../../agent_moss/templates");
|
|
2818
3112
|
TEMPLATE_FILES = ["prompt1_template.txt", "prompt2_template.txt", "security_judge_template.txt"];
|
|
2819
3113
|
cachedTemplates = null;
|
|
2820
3114
|
}
|
|
2821
3115
|
});
|
|
2822
3116
|
|
|
2823
3117
|
// src/engine/script-analyzer.ts
|
|
2824
|
-
import { readFileSync as
|
|
2825
|
-
import { resolve as
|
|
3118
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
3119
|
+
import { resolve as resolve6, join as join3 } from "path";
|
|
2826
3120
|
function emptyResult() {
|
|
2827
3121
|
return { scriptPath: "", scriptContent: "", matchedKeywords: [], lineCount: 0, isSuspicious: false, error: "", riskLevel: "", riskReason: "" };
|
|
2828
3122
|
}
|
|
@@ -2875,8 +3169,8 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
|
|
|
2875
3169
|
}
|
|
2876
3170
|
function readScriptContent(scriptPath, maxLines = 500) {
|
|
2877
3171
|
try {
|
|
2878
|
-
const expandedPath = scriptPath.startsWith("~") ? join3(process.env.HOME || "/home/user", scriptPath.slice(2)) :
|
|
2879
|
-
const raw =
|
|
3172
|
+
const expandedPath = scriptPath.startsWith("~") ? join3(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve6(scriptPath);
|
|
3173
|
+
const raw = readFileSync8(expandedPath, "utf-8");
|
|
2880
3174
|
const lines = raw.split("\n").slice(0, maxLines);
|
|
2881
3175
|
return { content: lines.join("\n"), lineCount: lines.length, error: "" };
|
|
2882
3176
|
} catch (e) {
|
|
@@ -3013,294 +3307,1604 @@ var init_script_analyzer = __esm({
|
|
|
3013
3307
|
}
|
|
3014
3308
|
});
|
|
3015
3309
|
|
|
3016
|
-
// src/engine/script-
|
|
3017
|
-
import {
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
}
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
}
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
const
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3310
|
+
// src/engine/script-engine/backends/python-backend.ts
|
|
3311
|
+
import { parser } from "@lezer/python";
|
|
3312
|
+
function buildLineStarts(content) {
|
|
3313
|
+
const starts = [0];
|
|
3314
|
+
for (let i = 0; i < content.length; i++) {
|
|
3315
|
+
if (content[i] === "\n") starts.push(i + 1);
|
|
3316
|
+
}
|
|
3317
|
+
return starts;
|
|
3318
|
+
}
|
|
3319
|
+
function lineAt(lineStarts, pos) {
|
|
3320
|
+
let lo = 0;
|
|
3321
|
+
let hi = lineStarts.length - 1;
|
|
3322
|
+
while (lo < hi) {
|
|
3323
|
+
const mid = lo + hi + 1 >> 1;
|
|
3324
|
+
if (lineStarts[mid] <= pos) lo = mid;
|
|
3325
|
+
else hi = mid - 1;
|
|
3326
|
+
}
|
|
3327
|
+
return lo + 1;
|
|
3328
|
+
}
|
|
3329
|
+
function markNodeLines(node, lines, lineStarts) {
|
|
3330
|
+
const start = lineAt(lineStarts, node.from);
|
|
3331
|
+
const end = node.to > node.from ? lineAt(lineStarts, node.to - 1) : start;
|
|
3332
|
+
for (let i = start; i <= end; i++) lines.add(i);
|
|
3333
|
+
}
|
|
3334
|
+
function reconstruct(sourceLines, lines) {
|
|
3335
|
+
const out = [];
|
|
3336
|
+
for (const i of [...lines].sort((a, b) => a - b)) {
|
|
3337
|
+
if (i >= 1 && i <= sourceLines.length) out.push(sourceLines[i - 1]);
|
|
3338
|
+
}
|
|
3339
|
+
return out.join("\n");
|
|
3340
|
+
}
|
|
3341
|
+
function makeEnv(argv) {
|
|
3342
|
+
return { argv, vars: {} };
|
|
3343
|
+
}
|
|
3344
|
+
function srcSlice(node) {
|
|
3345
|
+
return SOURCE.slice(node.from, node.to);
|
|
3346
|
+
}
|
|
3347
|
+
function stringValue(tokenText) {
|
|
3348
|
+
let s = tokenText;
|
|
3349
|
+
if (s.length >= 2) {
|
|
3350
|
+
const q = s[0];
|
|
3351
|
+
if ((q === '"' || q === "'") && s.endsWith(q)) s = s.slice(1, -1);
|
|
3352
|
+
}
|
|
3353
|
+
s = s.replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\r/g, "\r").replace(/\\\\/g, "\\").replace(/\\"/g, '"').replace(/\\'/g, "'");
|
|
3354
|
+
return s;
|
|
3355
|
+
}
|
|
3356
|
+
function isSysArgv(node) {
|
|
3357
|
+
if (!node || node.name !== "MemberExpression") return false;
|
|
3358
|
+
const first = node.firstChild;
|
|
3359
|
+
if (!first || first.name !== "VariableName" || srcSlice(first) !== "sys") return false;
|
|
3360
|
+
const prop = node.getChild("PropertyName");
|
|
3361
|
+
return prop !== null && srcSlice(prop) === "argv";
|
|
3362
|
+
}
|
|
3363
|
+
function evalExpr(node, env) {
|
|
3364
|
+
if (!node) return null;
|
|
3365
|
+
const name = node.name;
|
|
3366
|
+
if (name === "String") return stringValue(srcSlice(node));
|
|
3367
|
+
if (name === "Number") return srcSlice(node);
|
|
3368
|
+
if (name === "VariableName") {
|
|
3369
|
+
const id = srcSlice(node);
|
|
3370
|
+
return id in env.vars ? env.vars[id] : null;
|
|
3371
|
+
}
|
|
3372
|
+
if (name === "MemberExpression") {
|
|
3373
|
+
const bracket = node.getChild("[");
|
|
3374
|
+
if (bracket) {
|
|
3375
|
+
const inner = node.firstChild;
|
|
3376
|
+
if (inner && isSysArgv(inner)) {
|
|
3377
|
+
const idxNode = bracket.nextSibling;
|
|
3378
|
+
if (idxNode && idxNode.name === "Number") {
|
|
3379
|
+
const idx = parseInt(srcSlice(idxNode), 10);
|
|
3380
|
+
if (idx >= 1 && idx <= env.argv.length) return env.argv[idx - 1];
|
|
3381
|
+
return null;
|
|
3084
3382
|
}
|
|
3085
3383
|
}
|
|
3384
|
+
return null;
|
|
3086
3385
|
}
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
let expanded = content;
|
|
3090
|
-
for (let round = 0; round < 3; round++) {
|
|
3091
|
-
const sortedVars = Object.keys(varMap).sort((a, b) => b.length - a.length);
|
|
3092
|
-
let newExpanded = expanded;
|
|
3093
|
-
for (const v of sortedVars) {
|
|
3094
|
-
const val = varMap[v];
|
|
3095
|
-
newExpanded = newExpanded.split("${" + v + "}").join(val);
|
|
3096
|
-
newExpanded = newExpanded.split("$" + v).join(val);
|
|
3097
|
-
}
|
|
3098
|
-
if (newExpanded === expanded) break;
|
|
3099
|
-
expanded = newExpanded;
|
|
3100
|
-
collectAssignments(expanded);
|
|
3101
|
-
}
|
|
3102
|
-
return expanded;
|
|
3103
|
-
}
|
|
3104
|
-
function preprocessScriptContent(content) {
|
|
3105
|
-
let result = stripComments(content);
|
|
3106
|
-
result = stripOutputText(result);
|
|
3107
|
-
result = expandSimpleVariables(result);
|
|
3108
|
-
return result;
|
|
3109
|
-
}
|
|
3110
|
-
function scanScriptForKeywords2(content) {
|
|
3111
|
-
const matched = [];
|
|
3112
|
-
for (const { name, pattern } of SUSPICIOUS_PATTERNS2) {
|
|
3113
|
-
if (pattern.test(content)) matched.push(name);
|
|
3114
|
-
}
|
|
3115
|
-
return matched;
|
|
3116
|
-
}
|
|
3117
|
-
function assessKeywordCombinationRisk2(matchedKeywords, scriptContent) {
|
|
3118
|
-
if (matchedKeywords.length === 0) return { level: "", reason: "" };
|
|
3119
|
-
const keywordSet = new Set(matchedKeywords);
|
|
3120
|
-
for (const rule of KEYWORD_COMBINATION_RISKS2) {
|
|
3121
|
-
const hitGroup1 = [...rule.group1].some((k) => keywordSet.has(k));
|
|
3122
|
-
const hitGroup2 = rule.group2.size === 0 ? true : [...rule.group2].some((k) => keywordSet.has(k));
|
|
3123
|
-
if (hitGroup1 && hitGroup2) return { level: rule.level, reason: rule.reason };
|
|
3386
|
+
if (isSysArgv(node)) return env.argv[0] ?? null;
|
|
3387
|
+
return null;
|
|
3124
3388
|
}
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
if (contentLower.includes(p.toLowerCase())) {
|
|
3132
|
-
return {
|
|
3133
|
-
level: "critical",
|
|
3134
|
-
reason: "\u68C0\u6D4B\u5230\u7F51\u7EDC\u6570\u636E\u53D1\u9001 + \u7CFB\u7EDF\u73AF\u5883\u53D8\u91CF\u6536\u96C6\u7EC4\u5408\uFF0C\u6784\u6210 Critical \u7EA7\u522B\u6570\u636E\u5916\u4F20\u98CE\u9669\u3002\u811A\u672C\u53EF\u80FD\u5C06\u7528\u6237\u73AF\u5883\u53D8\u91CF\uFF08\u53EF\u80FD\u5305\u542B API Key\u3001\u5BC6\u94A5\u7B49\u654F\u611F\u4FE1\u606F\uFF09\u53D1\u9001\u5230\u8FDC\u7A0B\u670D\u52A1\u5668\u3002"
|
|
3135
|
-
};
|
|
3136
|
-
}
|
|
3389
|
+
if (name === "CallExpression") {
|
|
3390
|
+
const func = node.firstChild;
|
|
3391
|
+
if (func && func.name === "VariableName" && srcSlice(func) === "len") {
|
|
3392
|
+
const argList = node.getChild("ArgList");
|
|
3393
|
+
const inner = argList ? firstNamedChild(argList) : null;
|
|
3394
|
+
if (inner && isSysArgv(inner)) return String(env.argv.length + 1);
|
|
3137
3395
|
}
|
|
3396
|
+
return null;
|
|
3138
3397
|
}
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3398
|
+
if (name === "ConditionalExpression") {
|
|
3399
|
+
const ifKw = node.getChild("if");
|
|
3400
|
+
const elseKw = node.getChild("else");
|
|
3401
|
+
if (!ifKw || !elseKw) return null;
|
|
3402
|
+
const thenNode = node.firstChild;
|
|
3403
|
+
const condNode = ifKw.nextSibling;
|
|
3404
|
+
const orelseNode = elseKw.nextSibling;
|
|
3405
|
+
if (!thenNode || !condNode || !orelseNode) return null;
|
|
3406
|
+
const cond = evalCondition(condNode, env);
|
|
3407
|
+
if (cond === true) return evalExpr(thenNode, env);
|
|
3408
|
+
if (cond === false) return evalExpr(orelseNode, env);
|
|
3409
|
+
return null;
|
|
3410
|
+
}
|
|
3411
|
+
if (name === "BinaryExpression") {
|
|
3412
|
+
const op = findOp(node);
|
|
3413
|
+
if (op === "+") {
|
|
3414
|
+
const left = evalExpr(node.firstChild, env);
|
|
3415
|
+
const right = evalExpr(lastNamedChild(node), env);
|
|
3416
|
+
if (left !== null && right !== null) return left + right;
|
|
3146
3417
|
}
|
|
3418
|
+
return null;
|
|
3147
3419
|
}
|
|
3148
|
-
return
|
|
3420
|
+
return null;
|
|
3149
3421
|
}
|
|
3150
|
-
function
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
if (
|
|
3422
|
+
function firstNamedChild(node) {
|
|
3423
|
+
let c = node.firstChild;
|
|
3424
|
+
while (c) {
|
|
3425
|
+
if (c.name !== "(" && c.name !== ")" && c.name !== "," && c.name !== ":") return c;
|
|
3426
|
+
c = c.nextSibling;
|
|
3154
3427
|
}
|
|
3155
3428
|
return null;
|
|
3156
3429
|
}
|
|
3157
|
-
function
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
let resolved;
|
|
3163
|
-
try {
|
|
3164
|
-
resolved = resolvePath(p);
|
|
3165
|
-
if (existsSync7(resolved)) {
|
|
3166
|
-
const stat = statSync(resolved);
|
|
3167
|
-
if (stat.isFile()) return resolved;
|
|
3168
|
-
}
|
|
3169
|
-
} catch {
|
|
3170
|
-
}
|
|
3430
|
+
function lastNamedChild(node) {
|
|
3431
|
+
let c = node.lastChild;
|
|
3432
|
+
while (c) {
|
|
3433
|
+
if (c.name !== "(" && c.name !== ")" && c.name !== "," && c.name !== ":") return c;
|
|
3434
|
+
c = c.prevSibling;
|
|
3171
3435
|
}
|
|
3172
3436
|
return null;
|
|
3173
3437
|
}
|
|
3174
|
-
function
|
|
3175
|
-
let
|
|
3176
|
-
|
|
3177
|
-
|
|
3438
|
+
function findOp(node) {
|
|
3439
|
+
let c = node.firstChild;
|
|
3440
|
+
while (c) {
|
|
3441
|
+
if (c.name === "CompareOp" || c.name === "and" || c.name === "or" || c.name === "UpdateOp") {
|
|
3442
|
+
return srcSlice(c).trim();
|
|
3443
|
+
}
|
|
3444
|
+
c = c.nextSibling;
|
|
3178
3445
|
}
|
|
3179
|
-
return
|
|
3446
|
+
return null;
|
|
3180
3447
|
}
|
|
3181
|
-
function
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
const
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3448
|
+
function evalCondition(node, env) {
|
|
3449
|
+
if (!node) return null;
|
|
3450
|
+
const name = node.name;
|
|
3451
|
+
if (name === "Boolean") {
|
|
3452
|
+
const t = srcSlice(node).trim();
|
|
3453
|
+
return t === "True" ? true : t === "False" ? false : null;
|
|
3454
|
+
}
|
|
3455
|
+
if (name === "BinaryExpression") {
|
|
3456
|
+
const op = findOp(node);
|
|
3457
|
+
if (op === "and" || op === "or") {
|
|
3458
|
+
const parts2 = namedChildren(node);
|
|
3459
|
+
const vals = parts2.map((p) => evalCondition(p, env));
|
|
3460
|
+
if (op === "and") {
|
|
3461
|
+
if (vals.some((v) => v === false)) return false;
|
|
3462
|
+
if (vals.every((v) => v === true)) return true;
|
|
3463
|
+
return null;
|
|
3464
|
+
}
|
|
3465
|
+
if (vals.some((v) => v === true)) return true;
|
|
3466
|
+
if (vals.every((v) => v === false)) return false;
|
|
3467
|
+
return null;
|
|
3468
|
+
}
|
|
3469
|
+
const parts = namedChildren(node);
|
|
3470
|
+
if (parts.length !== 3) return null;
|
|
3471
|
+
const [left, opNode, right] = parts;
|
|
3472
|
+
const opText = srcSlice(opNode).trim();
|
|
3473
|
+
return evalCompare(left, opText, right, env);
|
|
3474
|
+
}
|
|
3475
|
+
if (name === "UnaryExpression") {
|
|
3476
|
+
const kw = node.getChild("not");
|
|
3477
|
+
if (kw) {
|
|
3478
|
+
const operand = kw.nextSibling;
|
|
3479
|
+
const v = evalCondition(operand, env);
|
|
3480
|
+
return v === null ? null : !v;
|
|
3481
|
+
}
|
|
3482
|
+
return null;
|
|
3483
|
+
}
|
|
3484
|
+
if (name === "CallExpression") {
|
|
3485
|
+
return null;
|
|
3486
|
+
}
|
|
3487
|
+
return null;
|
|
3488
|
+
}
|
|
3489
|
+
function namedChildren(node) {
|
|
3490
|
+
const out = [];
|
|
3491
|
+
let c = node.firstChild;
|
|
3492
|
+
while (c) {
|
|
3493
|
+
if (c.name !== "(" && c.name !== ")" && c.name !== "," && c.name !== ":" && c.name !== "[" && c.name !== "]") {
|
|
3494
|
+
out.push(c);
|
|
3495
|
+
}
|
|
3496
|
+
c = c.nextSibling;
|
|
3497
|
+
}
|
|
3498
|
+
return out;
|
|
3499
|
+
}
|
|
3500
|
+
function evalCompare(left, opText, right, env) {
|
|
3501
|
+
if (opText === "==" && left.name === "VariableName" && srcSlice(left) === "__name__") {
|
|
3502
|
+
const rv = evalExpr(right, env);
|
|
3503
|
+
if (rv !== null) return rv === "__main__";
|
|
3504
|
+
return null;
|
|
3505
|
+
}
|
|
3506
|
+
if (opText === "==" || opText === "!=") {
|
|
3507
|
+
const lv = evalExpr(left, env);
|
|
3508
|
+
const rv = evalExpr(right, env);
|
|
3509
|
+
if (lv !== null && rv !== null) {
|
|
3510
|
+
return opText === "==" ? lv === rv : lv !== rv;
|
|
3511
|
+
}
|
|
3512
|
+
return null;
|
|
3513
|
+
}
|
|
3514
|
+
if (opText === "in") {
|
|
3515
|
+
const lv = evalExpr(left, env);
|
|
3516
|
+
if (lv === null) return null;
|
|
3517
|
+
const rightName = right.name;
|
|
3518
|
+
if (rightName === "TupleExpression" || rightName === "ArrayExpression" || rightName === "SetExpression" || rightName === "ParenthesizedExpression") {
|
|
3519
|
+
const container = rightName === "ParenthesizedExpression" && right.firstChild && right.firstChild.name === "TupleExpression" ? right.firstChild : right;
|
|
3520
|
+
for (const elt of namedChildren(container)) {
|
|
3521
|
+
const ev = evalExpr(elt, env);
|
|
3522
|
+
if (ev !== null && ev === lv) return true;
|
|
3523
|
+
}
|
|
3524
|
+
return false;
|
|
3525
|
+
}
|
|
3526
|
+
const rv = evalExpr(right, env);
|
|
3527
|
+
if (rv !== null) return rv.includes(lv);
|
|
3528
|
+
return null;
|
|
3529
|
+
}
|
|
3530
|
+
if (opText === ">" || opText === ">=" || opText === "<" || opText === "<=") {
|
|
3531
|
+
const lv = evalExpr(left, env);
|
|
3532
|
+
const rv = evalExpr(right, env);
|
|
3533
|
+
if (lv === null || rv === null) return null;
|
|
3534
|
+
const ln = parseInt(lv, 10);
|
|
3535
|
+
const rn = parseInt(rv, 10);
|
|
3536
|
+
if (Number.isNaN(ln) || Number.isNaN(rn)) return null;
|
|
3537
|
+
if (opText === ">") return ln > rn;
|
|
3538
|
+
if (opText === ">=") return ln >= rn;
|
|
3539
|
+
if (opText === "<") return ln < rn;
|
|
3540
|
+
return ln <= rn;
|
|
3541
|
+
}
|
|
3542
|
+
return null;
|
|
3543
|
+
}
|
|
3544
|
+
function processStmts(stmts, env, lineStarts, lines, branches) {
|
|
3545
|
+
for (const s of stmts) processStmt(s, env, lineStarts, lines, branches);
|
|
3546
|
+
}
|
|
3547
|
+
function invalidateEnvOnCalls(node, env) {
|
|
3548
|
+
const cursor = node.cursor();
|
|
3549
|
+
cursor.iterate((n) => {
|
|
3550
|
+
const nd = n.node;
|
|
3551
|
+
if (nd.name !== "CallExpression") return true;
|
|
3552
|
+
const func = nd.firstChild;
|
|
3553
|
+
if (func && func.name === "VariableName" && !PURE_BUILTINS.has(srcSlice(func))) {
|
|
3554
|
+
for (const k of Object.keys(env.vars)) env.vars[k] = null;
|
|
3555
|
+
return false;
|
|
3556
|
+
}
|
|
3557
|
+
return true;
|
|
3558
|
+
});
|
|
3559
|
+
}
|
|
3560
|
+
function processStmt(stmt, env, lineStarts, lines, branches) {
|
|
3561
|
+
const name = stmt.name;
|
|
3562
|
+
if (name === "AssignStatement") {
|
|
3563
|
+
const target = stmt.firstChild;
|
|
3564
|
+
if (target && target.name === "VariableName") {
|
|
3565
|
+
const valNode = target.nextSibling ? target.nextSibling.nextSibling : null;
|
|
3566
|
+
invalidateEnvOnCalls(stmt, env);
|
|
3567
|
+
const val = evalExpr(valNode, env);
|
|
3568
|
+
env.vars[srcSlice(target)] = val;
|
|
3569
|
+
}
|
|
3570
|
+
markNodeLines(stmt, lines, lineStarts);
|
|
3571
|
+
return;
|
|
3572
|
+
}
|
|
3573
|
+
if (name === "UpdateStatement") {
|
|
3574
|
+
const target = stmt.firstChild;
|
|
3575
|
+
if (target && target.name === "VariableName") env.vars[srcSlice(target)] = null;
|
|
3576
|
+
invalidateEnvOnCalls(stmt, env);
|
|
3577
|
+
markNodeLines(stmt, lines, lineStarts);
|
|
3578
|
+
return;
|
|
3579
|
+
}
|
|
3580
|
+
if (name === "IfStatement") {
|
|
3581
|
+
processIf(stmt, env, lineStarts, lines, branches);
|
|
3582
|
+
return;
|
|
3583
|
+
}
|
|
3584
|
+
if (name === "ForStatement" || name === "WhileStatement") {
|
|
3585
|
+
markNodeLines(stmt, lines, lineStarts);
|
|
3586
|
+
for (const body of stmt.getChildren("Body")) {
|
|
3587
|
+
processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
|
|
3588
|
+
}
|
|
3589
|
+
return;
|
|
3590
|
+
}
|
|
3591
|
+
if (name === "TryStatement") {
|
|
3592
|
+
markNodeLines(stmt, lines, lineStarts);
|
|
3593
|
+
for (const body of stmt.getChildren("Body")) {
|
|
3594
|
+
processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
|
|
3595
|
+
}
|
|
3596
|
+
return;
|
|
3597
|
+
}
|
|
3598
|
+
if (name === "WithStatement") {
|
|
3599
|
+
markNodeLines(stmt, lines, lineStarts);
|
|
3600
|
+
const body = stmt.getChild("Body");
|
|
3601
|
+
if (body) processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
|
|
3602
|
+
return;
|
|
3603
|
+
}
|
|
3604
|
+
if (name === "MatchStatement") {
|
|
3605
|
+
markNodeLines(stmt, lines, lineStarts);
|
|
3606
|
+
for (const clause of stmt.getChildren("MatchClause")) {
|
|
3607
|
+
const body = clause.getChild("Body");
|
|
3608
|
+
if (body) processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
|
|
3189
3609
|
}
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3610
|
+
return;
|
|
3611
|
+
}
|
|
3612
|
+
if (name === "FunctionDefinition" || name === "ClassDefinition") {
|
|
3613
|
+
markDefTree(stmt, lines, lineStarts);
|
|
3614
|
+
return;
|
|
3615
|
+
}
|
|
3616
|
+
if (name === "ExpressionStatement") {
|
|
3617
|
+
invalidateEnvOnCalls(stmt, env);
|
|
3618
|
+
}
|
|
3619
|
+
markNodeLines(stmt, lines, lineStarts);
|
|
3620
|
+
}
|
|
3621
|
+
function markDefTree(stmt, lines, lineStarts) {
|
|
3622
|
+
const cursor = stmt.cursor();
|
|
3623
|
+
cursor.iterate((n) => {
|
|
3624
|
+
markNodeLines(n.node, lines, lineStarts);
|
|
3625
|
+
return true;
|
|
3626
|
+
});
|
|
3627
|
+
}
|
|
3628
|
+
function bodyStmts(body) {
|
|
3629
|
+
const out = [];
|
|
3630
|
+
let c = body.firstChild;
|
|
3631
|
+
while (c) {
|
|
3632
|
+
if (c.name !== ":" && c.name !== ";") out.push(c);
|
|
3633
|
+
c = c.nextSibling;
|
|
3634
|
+
}
|
|
3635
|
+
return out;
|
|
3636
|
+
}
|
|
3637
|
+
function processIf(stmt, env, lineStarts, lines, branches) {
|
|
3638
|
+
const sections = [];
|
|
3639
|
+
let c = stmt.firstChild;
|
|
3640
|
+
while (c) {
|
|
3641
|
+
if (c.name === "if" || c.name === "elif") {
|
|
3642
|
+
sections.push({ cond: c.nextSibling ?? null, body: null, isElse: false });
|
|
3643
|
+
} else if (c.name === "else") {
|
|
3644
|
+
sections.push({ cond: null, body: null, isElse: true });
|
|
3645
|
+
} else if (c.name === "Body") {
|
|
3646
|
+
if (sections.length > 0 && sections[sections.length - 1].body === null) {
|
|
3647
|
+
sections[sections.length - 1].body = c;
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
c = c.nextSibling;
|
|
3651
|
+
}
|
|
3652
|
+
processIfSections(stmt, sections, 0, env, lineStarts, lines, branches);
|
|
3653
|
+
}
|
|
3654
|
+
function processIfSections(_stmt, sections, idx, env, lineStarts, lines, branches) {
|
|
3655
|
+
if (idx >= sections.length) return;
|
|
3656
|
+
const s = sections[idx];
|
|
3657
|
+
if (s.cond) markNodeLines(s.cond, lines, lineStarts);
|
|
3658
|
+
if (s.isElse) {
|
|
3659
|
+
if (s.body) processStmts(bodyStmts(s.body), env, lineStarts, lines, branches);
|
|
3660
|
+
return;
|
|
3661
|
+
}
|
|
3662
|
+
const cond = s.cond ? evalCondition(s.cond, env) : null;
|
|
3663
|
+
branches.push({
|
|
3664
|
+
kind: "if",
|
|
3665
|
+
label: s.cond ? srcSlice(s.cond) : "",
|
|
3666
|
+
body: "",
|
|
3667
|
+
reachable: cond !== false
|
|
3668
|
+
});
|
|
3669
|
+
if (cond === true) {
|
|
3670
|
+
if (s.body) processStmts(bodyStmts(s.body), env, lineStarts, lines, branches);
|
|
3671
|
+
return;
|
|
3672
|
+
}
|
|
3673
|
+
if (cond === false) {
|
|
3674
|
+
processIfSections(_stmt, sections, idx + 1, env, lineStarts, lines, branches);
|
|
3675
|
+
return;
|
|
3676
|
+
}
|
|
3677
|
+
if (s.body) processStmts(bodyStmts(s.body), env, lineStarts, lines, branches);
|
|
3678
|
+
for (let j = idx + 1; j < sections.length; j++) {
|
|
3679
|
+
const sj = sections[j];
|
|
3680
|
+
if (sj.cond) markNodeLines(sj.cond, lines, lineStarts);
|
|
3681
|
+
if (sj.body) processStmts(bodyStmts(sj.body), env, lineStarts, lines, branches);
|
|
3682
|
+
}
|
|
3683
|
+
}
|
|
3684
|
+
function parseShellString(s) {
|
|
3685
|
+
CHILD_CALL_RE.lastIndex = 0;
|
|
3686
|
+
let m;
|
|
3687
|
+
while ((m = CHILD_CALL_RE.exec(s)) !== null) {
|
|
3688
|
+
const interp = m[1];
|
|
3689
|
+
const path6 = m[2].trim();
|
|
3690
|
+
let rest = m[3].trim();
|
|
3691
|
+
if (path6.startsWith("-") || path6.startsWith("$") || path6.includes("/dev/")) continue;
|
|
3692
|
+
rest = rest.split(/[;|&<>]/)[0].trim();
|
|
3693
|
+
const args = [];
|
|
3694
|
+
for (const tm of rest.matchAll(/(["'])(.*?)\1|(\S+)/g)) {
|
|
3695
|
+
args.push(tm[1] ? tm[2] : tm[3]);
|
|
3696
|
+
}
|
|
3697
|
+
return { path: path6, args, interpreter: interp };
|
|
3698
|
+
}
|
|
3699
|
+
return null;
|
|
3700
|
+
}
|
|
3701
|
+
function extractCallsFromTree(tree, reachableLines) {
|
|
3702
|
+
const calls = [];
|
|
3703
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3704
|
+
const add = (c) => {
|
|
3705
|
+
const key = `${c.path}\0${c.args.join("\0")}\0${c.interpreter}`;
|
|
3706
|
+
if (!seen.has(key)) {
|
|
3707
|
+
seen.add(key);
|
|
3708
|
+
calls.push(c);
|
|
3709
|
+
}
|
|
3710
|
+
};
|
|
3711
|
+
const lineReachable = (node) => {
|
|
3712
|
+
if (reachableLines === null) return true;
|
|
3713
|
+
return reachableLines.has(lineAt(buildLineStarts(SOURCE), node.from));
|
|
3714
|
+
};
|
|
3715
|
+
const cursor = tree.cursor();
|
|
3716
|
+
cursor.iterate((n) => {
|
|
3717
|
+
const node = n.node;
|
|
3718
|
+
if (node.name !== "CallExpression") return true;
|
|
3719
|
+
if (!lineReachable(node)) return true;
|
|
3720
|
+
const func = node.firstChild;
|
|
3721
|
+
if (!func || func.name !== "MemberExpression") return true;
|
|
3722
|
+
const modName = func.firstChild;
|
|
3723
|
+
if (!modName || modName.name !== "VariableName") return true;
|
|
3724
|
+
const funcNameNode = func.getChild("PropertyName");
|
|
3725
|
+
if (!funcNameNode) return true;
|
|
3726
|
+
const mod = srcSlice(modName);
|
|
3727
|
+
const funcName = srcSlice(funcNameNode);
|
|
3728
|
+
const argList = node.getChild("ArgList");
|
|
3729
|
+
const firstArg = argList ? firstNamedChild(argList) : null;
|
|
3730
|
+
if (mod === "subprocess" && SUBPROCESS_FUNCS.has(funcName)) {
|
|
3731
|
+
if (firstArg && (firstArg.name === "ArrayExpression" || firstArg.name === "TupleExpression")) {
|
|
3732
|
+
const elts = [];
|
|
3733
|
+
let allStr = true;
|
|
3734
|
+
for (const elt of namedChildren(firstArg)) {
|
|
3735
|
+
if (elt.name === "String") elts.push(stringValue(srcSlice(elt)));
|
|
3736
|
+
else {
|
|
3737
|
+
allStr = false;
|
|
3738
|
+
break;
|
|
3739
|
+
}
|
|
3740
|
+
}
|
|
3741
|
+
if (allStr && elts.length >= 2) {
|
|
3742
|
+
const interp = elts[0];
|
|
3743
|
+
const path6 = elts[1];
|
|
3744
|
+
if (!path6.startsWith("-") && !path6.startsWith("$") && !path6.includes("/dev/")) {
|
|
3745
|
+
add({ path: path6, args: elts.slice(2), interpreter: interp });
|
|
3746
|
+
return true;
|
|
3747
|
+
}
|
|
3748
|
+
}
|
|
3749
|
+
}
|
|
3750
|
+
if (firstArg && firstArg.name === "String") {
|
|
3751
|
+
const sc = parseShellString(stringValue(srcSlice(firstArg)));
|
|
3752
|
+
if (sc) add(sc);
|
|
3753
|
+
}
|
|
3754
|
+
} else if (mod === "os" && OS_FUNCS.has(funcName)) {
|
|
3755
|
+
if (firstArg && firstArg.name === "String") {
|
|
3756
|
+
const sc = parseShellString(stringValue(srcSlice(firstArg)));
|
|
3757
|
+
if (sc) add(sc);
|
|
3758
|
+
}
|
|
3759
|
+
}
|
|
3760
|
+
return true;
|
|
3761
|
+
});
|
|
3762
|
+
const cursor2 = tree.cursor();
|
|
3763
|
+
cursor2.iterate((n) => {
|
|
3764
|
+
const node = n.node;
|
|
3765
|
+
if (node.name !== "ImportStatement") return true;
|
|
3766
|
+
const txt = srcSlice(node);
|
|
3767
|
+
if (!/^from\s+\./m.test(txt)) return true;
|
|
3768
|
+
const m = txt.match(/\bimport\s+(.+)$/m);
|
|
3769
|
+
if (m) {
|
|
3770
|
+
for (const part of m[1].split(",")) {
|
|
3771
|
+
const name = part.trim().replace(/\s+as\s+\w+$/, "");
|
|
3772
|
+
if (name && name !== "*") add({ path: name.replace(/\./g, "/") + ".py", args: [], interpreter: "python3" });
|
|
3773
|
+
}
|
|
3774
|
+
}
|
|
3775
|
+
return true;
|
|
3776
|
+
});
|
|
3777
|
+
return calls;
|
|
3778
|
+
}
|
|
3779
|
+
function extractCallsRegex(content) {
|
|
3780
|
+
const calls = [];
|
|
3781
|
+
CHILD_CALL_RE.lastIndex = 0;
|
|
3782
|
+
let m;
|
|
3783
|
+
while ((m = CHILD_CALL_RE.exec(content)) !== null) {
|
|
3784
|
+
const interp = m[1];
|
|
3785
|
+
const path6 = m[2].trim();
|
|
3786
|
+
let rest = m[3].trim();
|
|
3787
|
+
if (path6.startsWith("-") || path6.startsWith("$") || path6.includes("/dev/")) continue;
|
|
3788
|
+
rest = rest.split(/[;|&<>]/)[0].trim();
|
|
3789
|
+
const args = [];
|
|
3790
|
+
for (const tm of rest.matchAll(/(["'])(.*?)\1|(\S+)/g)) {
|
|
3791
|
+
args.push(tm[1] ? tm[2] : tm[3]);
|
|
3792
|
+
}
|
|
3793
|
+
calls.push({ path: path6, args, interpreter: interp });
|
|
3794
|
+
}
|
|
3795
|
+
return calls;
|
|
3796
|
+
}
|
|
3797
|
+
var MAX_LINES, PURE_BUILTINS, CHILD_CALL_RE, SUBPROCESS_FUNCS, OS_FUNCS, SOURCE, PythonBackend;
|
|
3798
|
+
var init_python_backend = __esm({
|
|
3799
|
+
"src/engine/script-engine/backends/python-backend.ts"() {
|
|
3800
|
+
"use strict";
|
|
3801
|
+
init_esm_shims();
|
|
3802
|
+
MAX_LINES = 500;
|
|
3803
|
+
PURE_BUILTINS = /* @__PURE__ */ new Set([
|
|
3804
|
+
"len",
|
|
3805
|
+
"print",
|
|
3806
|
+
"str",
|
|
3807
|
+
"int",
|
|
3808
|
+
"float",
|
|
3809
|
+
"bool",
|
|
3810
|
+
"list",
|
|
3811
|
+
"dict",
|
|
3812
|
+
"set",
|
|
3813
|
+
"tuple",
|
|
3814
|
+
"range",
|
|
3815
|
+
"sorted",
|
|
3816
|
+
"enumerate",
|
|
3817
|
+
"zip",
|
|
3818
|
+
"map",
|
|
3819
|
+
"filter",
|
|
3820
|
+
"sum",
|
|
3821
|
+
"min",
|
|
3822
|
+
"max",
|
|
3823
|
+
"abs",
|
|
3824
|
+
"round",
|
|
3825
|
+
"isinstance",
|
|
3826
|
+
"type",
|
|
3827
|
+
"open",
|
|
3828
|
+
"input",
|
|
3829
|
+
"repr",
|
|
3830
|
+
"ord",
|
|
3831
|
+
"chr",
|
|
3832
|
+
"hex",
|
|
3833
|
+
"oct",
|
|
3834
|
+
"bin",
|
|
3835
|
+
"format",
|
|
3836
|
+
"id",
|
|
3837
|
+
"hash",
|
|
3838
|
+
"any",
|
|
3839
|
+
"all",
|
|
3840
|
+
"next",
|
|
3841
|
+
"iter",
|
|
3842
|
+
"reversed",
|
|
3843
|
+
"pow",
|
|
3844
|
+
"divmod"
|
|
3845
|
+
]);
|
|
3846
|
+
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;
|
|
3847
|
+
SUBPROCESS_FUNCS = /* @__PURE__ */ new Set(["run", "call", "Popen", "check_call", "check_output"]);
|
|
3848
|
+
OS_FUNCS = /* @__PURE__ */ new Set(["system", "popen", "popen2", "popen3", "popen4"]);
|
|
3849
|
+
SOURCE = "";
|
|
3850
|
+
PythonBackend = class {
|
|
3851
|
+
language = "python";
|
|
3852
|
+
parse(content, entryArgs) {
|
|
3853
|
+
const c = content || "";
|
|
3854
|
+
SOURCE = c;
|
|
3855
|
+
const sourceLines = c.split("\n");
|
|
3856
|
+
const lineStarts = buildLineStarts(c);
|
|
3857
|
+
const env = makeEnv(entryArgs || []);
|
|
3858
|
+
const lines = /* @__PURE__ */ new Set();
|
|
3859
|
+
const branches = [];
|
|
3860
|
+
if (sourceLines.length > MAX_LINES) {
|
|
3861
|
+
return {
|
|
3862
|
+
reachableContent: c,
|
|
3863
|
+
controlFlowResolved: false,
|
|
3864
|
+
calls: extractCallsRegex(c),
|
|
3865
|
+
branches: [],
|
|
3866
|
+
unresolvedSnippets: [],
|
|
3867
|
+
language: this.language
|
|
3868
|
+
};
|
|
3869
|
+
}
|
|
3870
|
+
let tree;
|
|
3871
|
+
try {
|
|
3872
|
+
tree = parser.parse(c).topNode;
|
|
3873
|
+
} catch {
|
|
3874
|
+
return {
|
|
3875
|
+
reachableContent: c,
|
|
3876
|
+
controlFlowResolved: false,
|
|
3877
|
+
calls: extractCallsRegex(c),
|
|
3878
|
+
branches: [],
|
|
3879
|
+
unresolvedSnippets: [],
|
|
3880
|
+
language: this.language
|
|
3881
|
+
};
|
|
3882
|
+
}
|
|
3883
|
+
const stmts = bodyStmts(tree);
|
|
3884
|
+
processStmts(stmts, env, lineStarts, lines, branches);
|
|
3885
|
+
const reachable = reconstruct(sourceLines, lines);
|
|
3886
|
+
let calls = extractCallsFromTree(tree, lines);
|
|
3887
|
+
if (calls.length === 0) calls = extractCallsRegex(c);
|
|
3888
|
+
const srcNonblank = sourceLines.filter((l) => l.trim().length > 0);
|
|
3889
|
+
const rcNonblank = reachable.split("\n").filter((l) => l.trim().length > 0);
|
|
3890
|
+
const resolved = rcNonblank.length !== srcNonblank.length || rcNonblank.some((l, i) => l !== srcNonblank[i]);
|
|
3891
|
+
return {
|
|
3892
|
+
reachableContent: reachable,
|
|
3893
|
+
controlFlowResolved: resolved,
|
|
3894
|
+
calls,
|
|
3895
|
+
branches,
|
|
3896
|
+
unresolvedSnippets: [],
|
|
3897
|
+
language: this.language
|
|
3898
|
+
};
|
|
3899
|
+
}
|
|
3900
|
+
};
|
|
3901
|
+
}
|
|
3902
|
+
});
|
|
3903
|
+
|
|
3904
|
+
// src/engine/script-engine/symbolic.ts
|
|
3905
|
+
function reescape(s) {
|
|
3906
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3907
|
+
}
|
|
3908
|
+
function svalConc(v) {
|
|
3909
|
+
return { kind: "concrete", value: v, children: [], op: "" };
|
|
3910
|
+
}
|
|
3911
|
+
function svalSym(name) {
|
|
3912
|
+
return { kind: "symbol", value: name, children: [], op: "" };
|
|
3913
|
+
}
|
|
3914
|
+
function svalExpr(children) {
|
|
3915
|
+
return { kind: "expr", value: null, children, op: "+" };
|
|
3916
|
+
}
|
|
3917
|
+
function fold(val) {
|
|
3918
|
+
if (val.kind !== "expr") return val;
|
|
3919
|
+
const parts = [];
|
|
3920
|
+
for (const c of val.children) {
|
|
3921
|
+
const cc = fold(c);
|
|
3922
|
+
if (cc.kind === "concrete") {
|
|
3923
|
+
parts.push(cc.value || "");
|
|
3924
|
+
} else {
|
|
3925
|
+
return cc;
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3928
|
+
return svalConc(parts.join(""));
|
|
3929
|
+
}
|
|
3930
|
+
function countMatches(re, s) {
|
|
3931
|
+
const m = s.match(re);
|
|
3932
|
+
return m ? m.length : 0;
|
|
3933
|
+
}
|
|
3934
|
+
function findBlockEnd(lines, startLine, openRe, closeRe) {
|
|
3935
|
+
let depth = 0;
|
|
3936
|
+
for (let idx = startLine; idx < lines.length; idx++) {
|
|
3937
|
+
depth += countMatches(openRe, lines[idx]) - countMatches(closeRe, lines[idx]);
|
|
3938
|
+
if (depth <= 0 && idx > startLine) return idx;
|
|
3939
|
+
}
|
|
3940
|
+
return -1;
|
|
3941
|
+
}
|
|
3942
|
+
function findCaseEnd(lines, start) {
|
|
3943
|
+
return findBlockEnd(lines, start, CASE_OPEN_RE, CASE_CLOSE_RE);
|
|
3944
|
+
}
|
|
3945
|
+
function findIfEnd(lines, start) {
|
|
3946
|
+
return findBlockEnd(lines, start, IF_OPEN_RE, IF_CLOSE_RE);
|
|
3947
|
+
}
|
|
3948
|
+
function findLoopEnd(lines, start) {
|
|
3949
|
+
let depth = 0;
|
|
3950
|
+
for (let idx = start; idx < lines.length; idx++) {
|
|
3951
|
+
depth += countMatches(LOOP_DO_RE, lines[idx]) - countMatches(LOOP_DONE_RE, lines[idx]);
|
|
3952
|
+
if (depth <= 0 && idx > start) return idx;
|
|
3953
|
+
}
|
|
3954
|
+
return -1;
|
|
3955
|
+
}
|
|
3956
|
+
function findFnEnd(lines, start) {
|
|
3957
|
+
let depth = 0;
|
|
3958
|
+
for (let idx = start; idx < lines.length; idx++) {
|
|
3959
|
+
depth += (lines[idx].match(/\{/g) || []).length - (lines[idx].match(/\}/g) || []).length;
|
|
3960
|
+
if (depth <= 0 && idx > start) return idx;
|
|
3961
|
+
}
|
|
3962
|
+
return -1;
|
|
3963
|
+
}
|
|
3964
|
+
function stripComments(content) {
|
|
3965
|
+
return content.split("\n").filter((l) => !l.trimStart().startsWith("#")).join("\n");
|
|
3966
|
+
}
|
|
3967
|
+
function parseCaseStmt(block) {
|
|
3968
|
+
const header = block.match(/\s*case\s+(\S[^;]*?)\s*\bin\b[\s\S]*/);
|
|
3969
|
+
if (!header) return null;
|
|
3970
|
+
const sel = header[1].trim();
|
|
3971
|
+
const lines = block.split("\n");
|
|
3972
|
+
let headerLine = 0;
|
|
3973
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3974
|
+
if (/\bin\b/.test(lines[i]) && /\bcase\b/.test(lines[i])) {
|
|
3975
|
+
headerLine = i;
|
|
3976
|
+
break;
|
|
3977
|
+
}
|
|
3978
|
+
}
|
|
3979
|
+
const bodyStartLine = headerLine + 1;
|
|
3980
|
+
const endLine = findCaseEnd(lines, 0);
|
|
3981
|
+
if (endLine === -1) return null;
|
|
3982
|
+
const branches = splitCaseBranchesDepthAware(lines.slice(bodyStartLine, endLine));
|
|
3983
|
+
if (!branches.length) return null;
|
|
3984
|
+
return { kind: "case", line: block, modelable: true, selector: sel, branches };
|
|
3985
|
+
}
|
|
3986
|
+
function splitCaseBranchesDepthAware(bodyLines) {
|
|
3987
|
+
let depth = 0;
|
|
3988
|
+
let curPat = null;
|
|
3989
|
+
let curBody = [];
|
|
3990
|
+
const branches = [];
|
|
3991
|
+
const flush = () => {
|
|
3992
|
+
if (curPat !== null) {
|
|
3993
|
+
branches.push({ pat: curPat, body: curBody.join("\n") });
|
|
3994
|
+
}
|
|
3995
|
+
curPat = null;
|
|
3996
|
+
curBody = [];
|
|
3997
|
+
};
|
|
3998
|
+
for (const ln of bodyLines) {
|
|
3999
|
+
const stripped = ln.trim();
|
|
4000
|
+
if (depth === 0) {
|
|
4001
|
+
const bm = stripped.match(/^([^()]+?)\s*\)\s*(.*)$/);
|
|
4002
|
+
if (bm) {
|
|
4003
|
+
if (curPat !== null) flush();
|
|
4004
|
+
curPat = bm[1].trim().replace(/^["']|["']$/g, "");
|
|
4005
|
+
curBody = bm[2].trim() ? [bm[2].trim()] : [];
|
|
4006
|
+
continue;
|
|
4007
|
+
}
|
|
4008
|
+
}
|
|
4009
|
+
if (/\bcase\b/.test(stripped)) depth += 1;
|
|
4010
|
+
else if (/\besac\b/.test(stripped)) depth = Math.max(0, depth - 1);
|
|
4011
|
+
else if (curPat !== null) {
|
|
4012
|
+
curBody.push(ln);
|
|
4013
|
+
if (depth === 0 && stripped.endsWith(";;")) flush();
|
|
4014
|
+
}
|
|
4015
|
+
}
|
|
4016
|
+
if (curPat !== null && curBody.length) flush();
|
|
4017
|
+
return branches;
|
|
4018
|
+
}
|
|
4019
|
+
function parseIfStmt(block) {
|
|
4020
|
+
const m = block.match(/\s*if\s+([^\n;]+?)\s*;\s*then/);
|
|
4021
|
+
if (!m) return null;
|
|
4022
|
+
const cond = m[1].trim();
|
|
4023
|
+
const lines = block.split("\n");
|
|
4024
|
+
const fiLine = findIfEnd(lines, 0);
|
|
4025
|
+
if (fiLine === -1) return null;
|
|
4026
|
+
const body = lines.slice(1, fiLine).join("\n");
|
|
4027
|
+
const elseM = /\belse\b/.exec(body);
|
|
4028
|
+
let thenBody;
|
|
4029
|
+
let elseBody;
|
|
4030
|
+
if (elseM) {
|
|
4031
|
+
thenBody = body.slice(0, elseM.index);
|
|
4032
|
+
elseBody = body.slice(elseM.index + 4);
|
|
4033
|
+
} else {
|
|
4034
|
+
thenBody = body;
|
|
4035
|
+
elseBody = null;
|
|
4036
|
+
}
|
|
4037
|
+
return { kind: "if", line: block, modelable: true, cond, thenBody, elseBody };
|
|
4038
|
+
}
|
|
4039
|
+
function tokenize(content) {
|
|
4040
|
+
const lines = stripComments(content).split("\n");
|
|
4041
|
+
const stmts = [];
|
|
4042
|
+
let i = 0;
|
|
4043
|
+
while (i < lines.length) {
|
|
4044
|
+
const ln = lines[i];
|
|
4045
|
+
const stripped = ln.trim();
|
|
4046
|
+
if (!stripped) {
|
|
4047
|
+
i++;
|
|
4048
|
+
continue;
|
|
4049
|
+
}
|
|
4050
|
+
if (/\b(?:while|for|until)\b/.test(stripped) && !CASE_START_RE.test(stripped) && !IF_START_RE.test(stripped)) {
|
|
4051
|
+
const endLine = findLoopEnd(lines, i);
|
|
4052
|
+
if (endLine !== -1) {
|
|
4053
|
+
for (let k = i; k <= endLine; k++) stmts.push({ kind: "plain", line: lines[k], modelable: false });
|
|
4054
|
+
i = endLine + 1;
|
|
4055
|
+
continue;
|
|
4056
|
+
}
|
|
4057
|
+
stmts.push({ kind: "plain", line: ln, modelable: false });
|
|
4058
|
+
i++;
|
|
4059
|
+
continue;
|
|
4060
|
+
}
|
|
4061
|
+
if (FN_DEF_RE.test(stripped) || FN_DEF_KW_RE.test(stripped)) {
|
|
4062
|
+
const endLine = findFnEnd(lines, i);
|
|
4063
|
+
if (endLine !== -1) {
|
|
4064
|
+
for (let k = i; k <= endLine; k++) stmts.push({ kind: "plain", line: lines[k], modelable: false });
|
|
4065
|
+
i = endLine + 1;
|
|
4066
|
+
continue;
|
|
4067
|
+
}
|
|
4068
|
+
stmts.push({ kind: "plain", line: ln, modelable: false });
|
|
4069
|
+
i++;
|
|
4070
|
+
continue;
|
|
4071
|
+
}
|
|
4072
|
+
if (CASE_START_RE.test(stripped)) {
|
|
4073
|
+
const endLine = findCaseEnd(lines, i);
|
|
4074
|
+
if (endLine === -1) {
|
|
4075
|
+
stmts.push({ kind: "plain", line: ln, modelable: false });
|
|
4076
|
+
i++;
|
|
4077
|
+
continue;
|
|
4078
|
+
}
|
|
4079
|
+
const parsed = parseCaseStmt(lines.slice(i, endLine + 1).join("\n"));
|
|
4080
|
+
stmts.push(parsed || { kind: "plain", line: ln, modelable: false });
|
|
4081
|
+
i = endLine + 1;
|
|
4082
|
+
continue;
|
|
4083
|
+
}
|
|
4084
|
+
if (IF_START_RE.test(stripped)) {
|
|
4085
|
+
const endLine = findIfEnd(lines, i);
|
|
4086
|
+
if (endLine === -1) {
|
|
4087
|
+
stmts.push({ kind: "plain", line: ln, modelable: false });
|
|
4088
|
+
i++;
|
|
4089
|
+
continue;
|
|
4090
|
+
}
|
|
4091
|
+
const parsed = parseIfStmt(lines.slice(i, endLine + 1).join("\n"));
|
|
4092
|
+
stmts.push(parsed || { kind: "plain", line: ln, modelable: false });
|
|
4093
|
+
i = endLine + 1;
|
|
4094
|
+
continue;
|
|
4095
|
+
}
|
|
4096
|
+
if (/\bshift\b|\bset\s+--(?:\s|$)|[\s;|&]read\b|^\s*read\b/.test(stripped)) {
|
|
4097
|
+
stmts.push({ kind: "plain", line: ln, modelable: false });
|
|
4098
|
+
i++;
|
|
4099
|
+
continue;
|
|
4100
|
+
}
|
|
4101
|
+
const am = stripped.match(ASGN_RE);
|
|
4102
|
+
if (am && !NOT_MODELED_RE.test(stripped)) {
|
|
4103
|
+
stmts.push({ kind: "assign", line: stripped, modelable: true, name: am[1], rhs: am[2].trim() });
|
|
4104
|
+
i++;
|
|
4105
|
+
continue;
|
|
4106
|
+
}
|
|
4107
|
+
stmts.push(NOT_MODELED_RE.test(stripped) ? { kind: "plain", line: ln, modelable: false } : { kind: "plain", line: ln, modelable: true });
|
|
4108
|
+
i++;
|
|
4109
|
+
}
|
|
4110
|
+
return stmts;
|
|
4111
|
+
}
|
|
4112
|
+
function evalExpr2(text, env) {
|
|
4113
|
+
const isSingleQuoted = text.length >= 2 && text[0] === "'" && text[text.length - 1] === "'";
|
|
4114
|
+
if (text.length >= 2 && (text[0] === '"' || text[0] === "'") && text[text.length - 1] === text[0]) {
|
|
4115
|
+
text = text.slice(1, -1);
|
|
4116
|
+
}
|
|
4117
|
+
if (isSingleQuoted) return svalConc(text);
|
|
4118
|
+
const parts = text.split(EXPR_SPLIT_RE);
|
|
4119
|
+
const result = [];
|
|
4120
|
+
for (const p of parts) {
|
|
4121
|
+
if (!p) continue;
|
|
4122
|
+
const brace = p.match(/^\$\{([^}]+)\}$/);
|
|
4123
|
+
if (brace) {
|
|
4124
|
+
const inner = brace[1];
|
|
4125
|
+
if (/^\d+$/.test(inner)) {
|
|
4126
|
+
const v = env.get(`$${inner}`);
|
|
4127
|
+
result.push(v || svalSym(`$${inner}`));
|
|
4128
|
+
} else {
|
|
4129
|
+
const v = env.get(inner);
|
|
4130
|
+
result.push(v || svalSym(inner));
|
|
4131
|
+
}
|
|
4132
|
+
continue;
|
|
4133
|
+
}
|
|
4134
|
+
const bare = p.match(/^\$([A-Za-z_][A-Za-z0-9_]*|\d+)$/);
|
|
4135
|
+
if (bare) {
|
|
4136
|
+
const varName = bare[1];
|
|
4137
|
+
const key = /^\d+$/.test(varName) ? `$${varName}` : varName;
|
|
4138
|
+
const v = env.get(key);
|
|
4139
|
+
result.push(v || svalSym(p));
|
|
4140
|
+
continue;
|
|
4141
|
+
}
|
|
4142
|
+
result.push(svalConc(p));
|
|
4143
|
+
}
|
|
4144
|
+
if (result.length === 1) return fold(result[0]);
|
|
4145
|
+
return fold(svalExpr(result));
|
|
4146
|
+
}
|
|
4147
|
+
function initEnv(entryArgs) {
|
|
4148
|
+
const env = /* @__PURE__ */ new Map();
|
|
4149
|
+
if (entryArgs && entryArgs.length) {
|
|
4150
|
+
entryArgs.forEach((a, idx) => env.set(`$${idx + 1}`, svalConc(a)));
|
|
4151
|
+
env.set("$@", svalConc(entryArgs.join(" ")));
|
|
4152
|
+
env.set("$*", svalConc(entryArgs.join(" ")));
|
|
4153
|
+
}
|
|
4154
|
+
return env;
|
|
4155
|
+
}
|
|
4156
|
+
function globToRegex(pattern) {
|
|
4157
|
+
let out = "^";
|
|
4158
|
+
let i = 0;
|
|
4159
|
+
while (i < pattern.length) {
|
|
4160
|
+
const c = pattern[i];
|
|
4161
|
+
if (c === "*") out += ".*";
|
|
4162
|
+
else if (c === "?") out += ".";
|
|
4163
|
+
else if (c === "[") {
|
|
4164
|
+
let j = i + 1;
|
|
4165
|
+
let neg = false;
|
|
4166
|
+
if (j < pattern.length && (pattern[j] === "!" || pattern[j] === "^")) {
|
|
4167
|
+
neg = true;
|
|
4168
|
+
j++;
|
|
4169
|
+
}
|
|
4170
|
+
let cls = "";
|
|
4171
|
+
while (j < pattern.length && pattern[j] !== "]") {
|
|
4172
|
+
cls += pattern[j];
|
|
4173
|
+
j++;
|
|
4174
|
+
}
|
|
4175
|
+
if (j >= pattern.length) {
|
|
4176
|
+
out += reescape(pattern.slice(i));
|
|
4177
|
+
break;
|
|
4178
|
+
}
|
|
4179
|
+
out += "[" + (neg ? "^" : "") + reescape(cls) + "]";
|
|
4180
|
+
i = j;
|
|
4181
|
+
} else {
|
|
4182
|
+
out += reescape(c);
|
|
4183
|
+
}
|
|
4184
|
+
i++;
|
|
4185
|
+
}
|
|
4186
|
+
out += "$";
|
|
4187
|
+
return new RegExp(out, RE_DOTALL);
|
|
4188
|
+
}
|
|
4189
|
+
function matchGlob(pattern, value) {
|
|
4190
|
+
if (!HAS_GLOB.test(pattern)) return pattern === value;
|
|
4191
|
+
return globToRegex(pattern).test(value);
|
|
4192
|
+
}
|
|
4193
|
+
function evalNumericCompare(op, left, right) {
|
|
4194
|
+
const li = parseInt(left, 10);
|
|
4195
|
+
const ri = parseInt(right, 10);
|
|
4196
|
+
if (isNaN(li) || isNaN(ri)) return null;
|
|
4197
|
+
switch (op) {
|
|
4198
|
+
case "-gt":
|
|
4199
|
+
return li > ri;
|
|
4200
|
+
case "-lt":
|
|
4201
|
+
return li < ri;
|
|
4202
|
+
case "-eq":
|
|
4203
|
+
return li === ri;
|
|
4204
|
+
case "-ge":
|
|
4205
|
+
return li >= ri;
|
|
4206
|
+
case "-le":
|
|
4207
|
+
return li <= ri;
|
|
4208
|
+
default:
|
|
4209
|
+
return null;
|
|
4210
|
+
}
|
|
4211
|
+
}
|
|
4212
|
+
function resolveVarSide(tok, env) {
|
|
4213
|
+
if (!tok.startsWith("$")) return tok;
|
|
4214
|
+
const ev = evalExpr2(tok, env);
|
|
4215
|
+
return ev.kind === "concrete" ? ev.value : null;
|
|
4216
|
+
}
|
|
4217
|
+
function evalCondition2(cond, env) {
|
|
4218
|
+
let inner = cond.trim().replace(/^\[\[?|\]\]?$/g, "").trim();
|
|
4219
|
+
if (/^-[A-Za-z]\s/.test(inner) || inner.startsWith("-d") || inner.startsWith("-f")) return null;
|
|
4220
|
+
const innerQ = inner.replace(/"/g, "").replace(/'/g, "");
|
|
4221
|
+
for (const op2 of ["-gt", "-lt", "-eq", "-ge", "-le"]) {
|
|
4222
|
+
const re = new RegExp(`\\s${op2}\\s`);
|
|
4223
|
+
const opm = re.exec(innerQ);
|
|
4224
|
+
if (!opm) continue;
|
|
4225
|
+
const left2 = innerQ.slice(0, opm.index).trim();
|
|
4226
|
+
const right2 = innerQ.slice(opm.index + opm[0].length).trim();
|
|
4227
|
+
const lv2 = resolveVarSide(left2, env);
|
|
4228
|
+
const rv2 = resolveVarSide(right2, env);
|
|
4229
|
+
if (lv2 === null || rv2 === null) return null;
|
|
4230
|
+
return evalNumericCompare(op2, lv2, rv2);
|
|
4231
|
+
}
|
|
4232
|
+
const sm = innerQ.match(/(.*?)\s*(==|=|!=|=~)\s*(.*)/);
|
|
4233
|
+
if (!sm) return null;
|
|
4234
|
+
const op = sm[2];
|
|
4235
|
+
let left = sm[1].trim();
|
|
4236
|
+
let right = sm[3].trim();
|
|
4237
|
+
if (op === "=~") {
|
|
4238
|
+
if (right.startsWith("/") && right.endsWith("/")) right = right.slice(1, -1);
|
|
4239
|
+
const lv2 = resolveVarSide(left, env);
|
|
4240
|
+
if (lv2 === null) return null;
|
|
4241
|
+
try {
|
|
4242
|
+
return new RegExp(right).test(lv2);
|
|
4243
|
+
} catch {
|
|
4244
|
+
return null;
|
|
4245
|
+
}
|
|
4246
|
+
}
|
|
4247
|
+
const lv = resolveVarSide(left, env);
|
|
4248
|
+
const rv = resolveVarSide(right, env);
|
|
4249
|
+
if (lv === null || rv === null) return null;
|
|
4250
|
+
if (op === "!=") return lv !== rv;
|
|
4251
|
+
return lv === rv;
|
|
4252
|
+
}
|
|
4253
|
+
function symbolicProcess(content, entryArgs) {
|
|
4254
|
+
if (!content) return null;
|
|
4255
|
+
const stmts = tokenize(content);
|
|
4256
|
+
if (!stmts.some((s) => s.kind === "case" || s.kind === "if" || s.kind === "assign")) return null;
|
|
4257
|
+
if (stmts.some((s) => !s.modelable)) return null;
|
|
4258
|
+
const env = initEnv(entryArgs);
|
|
4259
|
+
let linesBudget = 0;
|
|
4260
|
+
let branchesBudget = 0;
|
|
4261
|
+
let modelable = false;
|
|
4262
|
+
const out = [];
|
|
4263
|
+
for (const s of stmts) {
|
|
4264
|
+
linesBudget++;
|
|
4265
|
+
if (linesBudget > MAX_LINES2) return null;
|
|
4266
|
+
if (s.kind === "assign") {
|
|
4267
|
+
modelable = true;
|
|
4268
|
+
if (env.size >= MAX_VARS) return null;
|
|
4269
|
+
const v = evalExpr2(s.rhs, env);
|
|
4270
|
+
env.set(s.name, fold(v));
|
|
4271
|
+
out.push(s.line);
|
|
4272
|
+
continue;
|
|
4273
|
+
}
|
|
4274
|
+
if (s.kind === "case") {
|
|
4275
|
+
modelable = true;
|
|
4276
|
+
branchesBudget += s.branches.length;
|
|
4277
|
+
if (branchesBudget > MAX_BRANCHES) return null;
|
|
4278
|
+
const selVal = fold(evalExpr2(s.selector, env));
|
|
4279
|
+
if (selVal.kind !== "concrete") return null;
|
|
4280
|
+
const concrete = selVal.value || "";
|
|
4281
|
+
let selected = null;
|
|
4282
|
+
let fallback = "";
|
|
4283
|
+
for (const b of s.branches) {
|
|
4284
|
+
const patStripped = b.pat.trim();
|
|
4285
|
+
if (patStripped === "*") {
|
|
4286
|
+
fallback = b.body;
|
|
4287
|
+
continue;
|
|
4288
|
+
}
|
|
4289
|
+
if (matchGlob(patStripped, concrete)) {
|
|
4290
|
+
selected = b.body;
|
|
4291
|
+
break;
|
|
4292
|
+
}
|
|
4293
|
+
}
|
|
4294
|
+
const final = selected !== null ? selected : fallback || null;
|
|
4295
|
+
if (final === null) return null;
|
|
4296
|
+
out.push(`case ${s.selector} in
|
|
4297
|
+
${final}
|
|
4298
|
+
esac`);
|
|
4299
|
+
continue;
|
|
4300
|
+
}
|
|
4301
|
+
if (s.kind === "if") {
|
|
4302
|
+
modelable = true;
|
|
4303
|
+
branchesBudget++;
|
|
4304
|
+
if (branchesBudget > MAX_BRANCHES) return null;
|
|
4305
|
+
const truth = evalCondition2(s.cond, env);
|
|
4306
|
+
if (truth === null) return null;
|
|
4307
|
+
const chosen = truth ? s.thenBody : s.elseBody;
|
|
4308
|
+
if (chosen === null) return null;
|
|
4309
|
+
out.push(`if ${s.cond}; then
|
|
4310
|
+
${chosen}
|
|
4311
|
+
fi`);
|
|
4312
|
+
continue;
|
|
4313
|
+
}
|
|
4314
|
+
out.push(s.line);
|
|
4315
|
+
}
|
|
4316
|
+
if (!modelable) return null;
|
|
4317
|
+
return out.join("\n");
|
|
4318
|
+
}
|
|
4319
|
+
var RE_DOTALL, MAX_LINES2, MAX_BRANCHES, MAX_VARS, ASGN_RE, CASE_START_RE, IF_START_RE, NOT_MODELED_RE, CASE_OPEN_RE, CASE_CLOSE_RE, IF_OPEN_RE, IF_CLOSE_RE, LOOP_DO_RE, LOOP_DONE_RE, FN_DEF_RE, FN_DEF_KW_RE, EXPR_SPLIT_RE, HAS_GLOB;
|
|
4320
|
+
var init_symbolic = __esm({
|
|
4321
|
+
"src/engine/script-engine/symbolic.ts"() {
|
|
4322
|
+
"use strict";
|
|
4323
|
+
init_esm_shims();
|
|
4324
|
+
RE_DOTALL = "s";
|
|
4325
|
+
MAX_LINES2 = 500;
|
|
4326
|
+
MAX_BRANCHES = 50;
|
|
4327
|
+
MAX_VARS = 200;
|
|
4328
|
+
ASGN_RE = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
|
|
4329
|
+
CASE_START_RE = /^\s*case\s+/;
|
|
4330
|
+
IF_START_RE = /^\s*if\s+/;
|
|
4331
|
+
NOT_MODELED_RE = /\$\(|`[^`]*`/;
|
|
4332
|
+
CASE_OPEN_RE = /\bcase\b/;
|
|
4333
|
+
CASE_CLOSE_RE = /\besac\b/;
|
|
4334
|
+
IF_OPEN_RE = /\bif\b/;
|
|
4335
|
+
IF_CLOSE_RE = /\bfi\b/;
|
|
4336
|
+
LOOP_DO_RE = /\bdo\b/g;
|
|
4337
|
+
LOOP_DONE_RE = /\bdone\b/g;
|
|
4338
|
+
FN_DEF_RE = /^\s*(?:function\s+)?[A-Za-z_][A-Za-z0-9_]*\s*\(\s*\)\s*\{/;
|
|
4339
|
+
FN_DEF_KW_RE = /^\s*function\s+[A-Za-z_][A-Za-z0-9_]*\s*\{?/;
|
|
4340
|
+
EXPR_SPLIT_RE = /(\$\{?\d+\}?|\$\{?[A-Za-z_][A-Za-z0-9_]*\}?)/;
|
|
4341
|
+
HAS_GLOB = /[*?[]/;
|
|
4342
|
+
}
|
|
4343
|
+
});
|
|
4344
|
+
|
|
4345
|
+
// src/engine/script-engine/backends/shell-backend.ts
|
|
4346
|
+
function bindPositionalArgs(content, args) {
|
|
4347
|
+
if (!args.length) return content;
|
|
4348
|
+
const allArgs = args.join(" ");
|
|
4349
|
+
content = content.replace(/\$\{(\d+|@|\*)(?::?[-]?([^}]*))?\}/g, (full, kind, defv) => {
|
|
4350
|
+
if (kind === "@" || kind === "*") return allArgs;
|
|
4351
|
+
if (!/^\d+$/.test(kind)) return full;
|
|
4352
|
+
const idx = parseInt(kind, 10);
|
|
4353
|
+
if (1 <= idx && idx <= args.length) return args[idx - 1];
|
|
4354
|
+
return defv !== void 0 ? defv : "";
|
|
4355
|
+
});
|
|
4356
|
+
content = content.replace(/\$\{(\d+)\}/g, (full, kind) => {
|
|
4357
|
+
const idx = parseInt(kind, 10);
|
|
4358
|
+
if (1 <= idx && idx <= args.length) return args[idx - 1];
|
|
4359
|
+
return "";
|
|
4360
|
+
});
|
|
4361
|
+
content = content.replace(/\$(\d+)/g, (full, kind) => {
|
|
4362
|
+
const idx = parseInt(kind, 10);
|
|
4363
|
+
if (1 <= idx && idx <= args.length) return args[idx - 1];
|
|
4364
|
+
return "";
|
|
4365
|
+
});
|
|
4366
|
+
content = content.replace(/\$[@*]/g, allArgs);
|
|
4367
|
+
return content;
|
|
4368
|
+
}
|
|
4369
|
+
function findSelectorValue(selector, args) {
|
|
4370
|
+
if (!args || !args.length) return null;
|
|
4371
|
+
const m = selector.match(/^\$\{?(\d+)\}?$/);
|
|
4372
|
+
if (m) {
|
|
4373
|
+
const idx = parseInt(m[1], 10);
|
|
4374
|
+
if (1 <= idx && idx <= args.length) return args[idx - 1];
|
|
4375
|
+
return null;
|
|
4376
|
+
}
|
|
4377
|
+
return null;
|
|
4378
|
+
}
|
|
4379
|
+
function splitCaseBody(body) {
|
|
4380
|
+
const lines = body.split("\n");
|
|
4381
|
+
const branches = [];
|
|
4382
|
+
let cur = null;
|
|
4383
|
+
for (const ln of lines) {
|
|
4384
|
+
const stripped = ln.trim();
|
|
4385
|
+
if (/^[\w:-]+\s*\(\)\s*\{/.test(stripped)) continue;
|
|
4386
|
+
const m = stripped.match(/^([^()]+?)\s*\)\s*(.*)$/);
|
|
4387
|
+
if (m) {
|
|
4388
|
+
const pat = m[1].trim().replace(/^["']|["']$/g, "");
|
|
4389
|
+
const inline = m[2].trim();
|
|
4390
|
+
if (pat) {
|
|
4391
|
+
if (cur) branches.push(cur);
|
|
4392
|
+
cur = { label: pat, body: inline };
|
|
4393
|
+
}
|
|
4394
|
+
continue;
|
|
4395
|
+
}
|
|
4396
|
+
if (cur) {
|
|
4397
|
+
cur.body += cur.body ? "\n" + ln : ln;
|
|
4398
|
+
if (stripped.endsWith(";;")) {
|
|
4399
|
+
branches.push(cur);
|
|
4400
|
+
cur = null;
|
|
4401
|
+
}
|
|
4402
|
+
}
|
|
4403
|
+
}
|
|
4404
|
+
if (cur && cur.body.trim()) {
|
|
4405
|
+
if (!branches.includes(cur)) branches.push(cur);
|
|
4406
|
+
}
|
|
4407
|
+
return branches;
|
|
4408
|
+
}
|
|
4409
|
+
function parseCase(content, args) {
|
|
4410
|
+
const m = content.match(/\bcase\s+(\$\{?\d+\}?|[A-Za-z_]\w*|\$[A-Za-z_]\w*)\s+in/i);
|
|
4411
|
+
if (!m) return null;
|
|
4412
|
+
const selector = m[1].trim();
|
|
4413
|
+
const val = findSelectorValue(selector, args);
|
|
4414
|
+
if (val === null) return null;
|
|
4415
|
+
const lines = content.split("\n");
|
|
4416
|
+
const endMatch = findCaseEndInLines(lines, 0);
|
|
4417
|
+
if (endMatch === -1) return null;
|
|
4418
|
+
const bodyLines = lines.slice(1, endMatch);
|
|
4419
|
+
const body = bodyLines.join("\n");
|
|
4420
|
+
const hasNested = /\bcase\s+/.test(body) || /\bif\s+/.test(body);
|
|
4421
|
+
let final;
|
|
4422
|
+
if (hasNested) {
|
|
4423
|
+
final = splitNestedCaseBranches(bodyLines, val);
|
|
4424
|
+
if (final === null) return null;
|
|
4425
|
+
} else {
|
|
4426
|
+
const branches = splitCaseBody(body);
|
|
4427
|
+
let selected = null;
|
|
4428
|
+
let fallback = "";
|
|
4429
|
+
for (const b of branches) {
|
|
4430
|
+
const values = b.label.split("|").map((v) => v.trim()).filter(Boolean);
|
|
4431
|
+
if (values.includes("*")) fallback = b.body;
|
|
4432
|
+
else if (values.includes(val)) selected = b.body;
|
|
4433
|
+
}
|
|
4434
|
+
final = selected !== null ? selected : fallback || null;
|
|
4435
|
+
if (final === null) return null;
|
|
4436
|
+
}
|
|
4437
|
+
const prefix = content.slice(0, m.index ?? 0);
|
|
4438
|
+
const suffix = lines.slice(endMatch + 1).join("\n");
|
|
4439
|
+
return `${prefix}
|
|
4440
|
+
${final}
|
|
4441
|
+
${suffix}`;
|
|
4442
|
+
}
|
|
4443
|
+
function findCaseEndInLines(lines, headerLine) {
|
|
4444
|
+
let depth = 0;
|
|
4445
|
+
for (let idx = headerLine; idx < lines.length; idx++) {
|
|
4446
|
+
const ln = lines[idx];
|
|
4447
|
+
const opens = (ln.match(/\bcase\b/g) || []).length;
|
|
4448
|
+
const closes = (ln.match(/\besac\b/g) || []).length;
|
|
4449
|
+
depth += opens - closes;
|
|
4450
|
+
if (depth <= 0 && idx > headerLine) return idx;
|
|
4451
|
+
}
|
|
4452
|
+
return -1;
|
|
4453
|
+
}
|
|
4454
|
+
function splitNestedCaseBranches(bodyLines, selectorVal) {
|
|
4455
|
+
let depth = 0;
|
|
4456
|
+
let curPat = null;
|
|
4457
|
+
let curBody = [];
|
|
4458
|
+
let selected = null;
|
|
4459
|
+
let fallback = null;
|
|
4460
|
+
const flush = () => {
|
|
4461
|
+
if (curPat === null) return;
|
|
4462
|
+
const values = curPat.split("|").map((v) => v.trim()).filter(Boolean);
|
|
4463
|
+
const body = curBody.join("\n");
|
|
4464
|
+
if (values.includes("*")) {
|
|
4465
|
+
if (fallback === null) fallback = body;
|
|
4466
|
+
} else if (values.includes(selectorVal)) {
|
|
4467
|
+
selected = body;
|
|
4468
|
+
}
|
|
4469
|
+
curPat = null;
|
|
4470
|
+
curBody = [];
|
|
4471
|
+
};
|
|
4472
|
+
for (const ln of bodyLines) {
|
|
4473
|
+
const stripped = ln.trim();
|
|
4474
|
+
if (depth === 0) {
|
|
4475
|
+
const bm = stripped.match(/^([^()]+?)\s*\)\s*(.*)$/);
|
|
4476
|
+
if (bm) {
|
|
4477
|
+
if (curPat !== null) flush();
|
|
4478
|
+
curPat = bm[1].trim().replace(/^["']|["']$/g, "");
|
|
4479
|
+
curBody = bm[2].trim() ? [bm[2].trim()] : [];
|
|
4480
|
+
continue;
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4483
|
+
if (/\bcase\b/.test(stripped)) depth += 1;
|
|
4484
|
+
else if (/\besac\b/.test(stripped)) depth = Math.max(0, depth - 1);
|
|
4485
|
+
else if (curPat !== null) {
|
|
4486
|
+
curBody.push(ln);
|
|
4487
|
+
if (depth === 0 && stripped.endsWith(";;")) flush();
|
|
4488
|
+
}
|
|
4489
|
+
}
|
|
4490
|
+
if (curPat !== null && curBody.length) flush();
|
|
4491
|
+
return selected !== null ? selected : fallback;
|
|
4492
|
+
}
|
|
4493
|
+
function evalIfCondition(cond, args) {
|
|
4494
|
+
if (!args || !args.length) return null;
|
|
4495
|
+
let inner = cond.replace(/^\[\[?|\]\]?$/g, "").replace(/"/g, "").replace(/'/g, "").trim();
|
|
4496
|
+
const m = inner.match(/(.*?)\s*(==|=)\s*(.*)/);
|
|
4497
|
+
if (!m) return null;
|
|
4498
|
+
let left = m[1].trim();
|
|
4499
|
+
let right = m[3].trim();
|
|
4500
|
+
const lm = left.match(/^\$(\d+)$/);
|
|
4501
|
+
if (lm) {
|
|
4502
|
+
const idx = parseInt(lm[1], 10);
|
|
4503
|
+
if (1 <= idx && idx <= args.length) left = args[idx - 1];
|
|
4504
|
+
else return null;
|
|
4505
|
+
} else if (left.startsWith("$")) return null;
|
|
4506
|
+
const rm = right.match(/^\$(\d+)$/);
|
|
4507
|
+
if (rm) {
|
|
4508
|
+
const idx = parseInt(rm[1], 10);
|
|
4509
|
+
if (1 <= idx && idx <= args.length) right = args[idx - 1];
|
|
4510
|
+
else return null;
|
|
4511
|
+
} else if (right.startsWith("$")) return null;
|
|
4512
|
+
return left === right;
|
|
4513
|
+
}
|
|
4514
|
+
function parseIf(content, args) {
|
|
4515
|
+
const m = content.match(/\bif\s+([^\n;]+?)\s*;\s*then/i);
|
|
4516
|
+
if (!m) return null;
|
|
4517
|
+
const truth = evalIfCondition(m[1].trim(), args);
|
|
4518
|
+
if (truth === null) return null;
|
|
4519
|
+
const start = m.index ?? 0;
|
|
4520
|
+
const thenBodyStart = start + m[0].length;
|
|
4521
|
+
const fiPos = content.indexOf("fi", thenBodyStart);
|
|
4522
|
+
if (fiPos === -1) return null;
|
|
4523
|
+
const segment = content.slice(thenBodyStart, fiPos);
|
|
4524
|
+
const elseM = segment.match(/\belse\b/);
|
|
4525
|
+
let chosen;
|
|
4526
|
+
if (truth) chosen = elseM ? segment.slice(0, elseM.index) : segment;
|
|
4527
|
+
else {
|
|
4528
|
+
if (!elseM) return null;
|
|
4529
|
+
chosen = segment.slice((elseM.index ?? 0) + 4);
|
|
4530
|
+
}
|
|
4531
|
+
return `${content.slice(0, start)}
|
|
4532
|
+
${chosen}
|
|
4533
|
+
${content.slice(fiPos + 2)}`;
|
|
4534
|
+
}
|
|
4535
|
+
function markLoopsReachable(content) {
|
|
4536
|
+
return content;
|
|
4537
|
+
}
|
|
4538
|
+
function extractCallsFromContent(content) {
|
|
4539
|
+
const calls = [];
|
|
4540
|
+
let m;
|
|
4541
|
+
CHILD_CALL_RE2.lastIndex = 0;
|
|
4542
|
+
while ((m = CHILD_CALL_RE2.exec(content)) !== null) {
|
|
4543
|
+
const interp = m[1];
|
|
4544
|
+
const path6 = m[2].trim();
|
|
4545
|
+
let rest = m[3].trim();
|
|
4546
|
+
if (path6.startsWith("-") || path6.startsWith("$") || path6.includes("/dev/")) continue;
|
|
4547
|
+
rest = rest.split(/[;|&<>]/)[0].trim();
|
|
4548
|
+
const args = [];
|
|
4549
|
+
for (const tm of rest.matchAll(/(["'])(.*?)\1|(\S+)/g)) {
|
|
4550
|
+
args.push(tm[1] ? tm[2] : tm[3]);
|
|
4551
|
+
}
|
|
4552
|
+
calls.push({ path: path6, args, interpreter: interp });
|
|
4553
|
+
}
|
|
4554
|
+
return calls;
|
|
4555
|
+
}
|
|
4556
|
+
var CHILD_CALL_RE2, ShellBackend;
|
|
4557
|
+
var init_shell_backend = __esm({
|
|
4558
|
+
"src/engine/script-engine/backends/shell-backend.ts"() {
|
|
4559
|
+
"use strict";
|
|
4560
|
+
init_esm_shims();
|
|
4561
|
+
init_symbolic();
|
|
4562
|
+
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;
|
|
4563
|
+
ShellBackend = class {
|
|
4564
|
+
language = "shell";
|
|
4565
|
+
parse(content, entryArgs) {
|
|
4566
|
+
const c = content || "";
|
|
4567
|
+
const orig = c;
|
|
4568
|
+
let candidate = c;
|
|
4569
|
+
let resolved = false;
|
|
4570
|
+
const p1Safe = !/(?:\bshift\b|\bset\s+--(?:\s|$)|[\s;|&]read\b|^\s*read\b|\(\)\s*\{|function\s+\w+)/.test(c);
|
|
4571
|
+
if (p1Safe) {
|
|
4572
|
+
const caseOut = parseCase(candidate, entryArgs);
|
|
4573
|
+
if (caseOut !== null && caseOut !== candidate) {
|
|
4574
|
+
resolved = true;
|
|
4575
|
+
candidate = caseOut;
|
|
4576
|
+
}
|
|
4577
|
+
const ifOut = parseIf(candidate, entryArgs);
|
|
4578
|
+
if (ifOut !== null && ifOut !== candidate) {
|
|
4579
|
+
resolved = true;
|
|
4580
|
+
candidate = ifOut;
|
|
4581
|
+
}
|
|
4582
|
+
}
|
|
4583
|
+
let bound = p1Safe ? bindPositionalArgs(candidate, entryArgs || []) : candidate;
|
|
4584
|
+
if (!resolved) {
|
|
4585
|
+
const symOut = symbolicProcess(c, entryArgs);
|
|
4586
|
+
if (symOut !== null && symOut !== c) {
|
|
4587
|
+
resolved = true;
|
|
4588
|
+
bound = symOut;
|
|
4589
|
+
}
|
|
4590
|
+
}
|
|
4591
|
+
const loopMarked = markLoopsReachable(bound);
|
|
4592
|
+
const calls = extractCallsFromContent(loopMarked);
|
|
4593
|
+
return {
|
|
4594
|
+
reachableContent: loopMarked,
|
|
4595
|
+
controlFlowResolved: resolved,
|
|
4596
|
+
calls,
|
|
4597
|
+
branches: [],
|
|
4598
|
+
unresolvedSnippets: [],
|
|
4599
|
+
language: this.language
|
|
4600
|
+
};
|
|
4601
|
+
}
|
|
4602
|
+
};
|
|
4603
|
+
}
|
|
4604
|
+
});
|
|
4605
|
+
|
|
4606
|
+
// src/engine/script-engine/backends/index.ts
|
|
4607
|
+
var init_backends = __esm({
|
|
4608
|
+
"src/engine/script-engine/backends/index.ts"() {
|
|
4609
|
+
"use strict";
|
|
4610
|
+
init_esm_shims();
|
|
4611
|
+
init_shell_backend();
|
|
4612
|
+
init_shell_backend();
|
|
4613
|
+
init_python_backend();
|
|
4614
|
+
}
|
|
4615
|
+
});
|
|
4616
|
+
|
|
4617
|
+
// src/engine/script-engine/tracker.ts
|
|
4618
|
+
import * as fs3 from "fs";
|
|
4619
|
+
import * as path3 from "path";
|
|
4620
|
+
import * as os from "os";
|
|
4621
|
+
function pickBackend(pathStr) {
|
|
4622
|
+
const lower = pathStr.toLowerCase();
|
|
4623
|
+
for (const ext of Object.keys(BACKENDS)) {
|
|
4624
|
+
if (lower.endsWith(ext)) return BACKENDS[ext];
|
|
4625
|
+
}
|
|
4626
|
+
return DEFAULT_BACKEND;
|
|
4627
|
+
}
|
|
4628
|
+
function resolvePath(p, baseDir) {
|
|
4629
|
+
let expanded = p;
|
|
4630
|
+
if (expanded.startsWith("~/") || expanded === "~") {
|
|
4631
|
+
expanded = path3.join(os.homedir(), expanded.slice(1));
|
|
4632
|
+
} else if (!path3.isAbsolute(expanded) && baseDir) {
|
|
4633
|
+
expanded = path3.join(baseDir, expanded);
|
|
4634
|
+
}
|
|
4635
|
+
return path3.resolve(expanded);
|
|
4636
|
+
}
|
|
4637
|
+
function readScript(pathStr, maxFileSize) {
|
|
4638
|
+
try {
|
|
4639
|
+
const p = resolvePath(pathStr);
|
|
4640
|
+
if (!fs3.existsSync(p)) return { content: "", lineCount: 0, fileSize: 0, error: `\u811A\u672C\u4E0D\u5B58\u5728: ${pathStr}` };
|
|
4641
|
+
const stat = fs3.statSync(p);
|
|
4642
|
+
if (!stat.isFile()) return { content: "", lineCount: 0, fileSize: 0, error: `\u4E0D\u662F\u6587\u4EF6: ${pathStr}` };
|
|
4643
|
+
if (stat.size > maxFileSize) {
|
|
4644
|
+
return { content: "", lineCount: 0, fileSize: stat.size, error: `\u811A\u672C\u6587\u4EF6\u8FC7\u5927 (${stat.size} bytes)\uFF0C\u8DF3\u8FC7\u8BFB\u53D6` };
|
|
4645
|
+
}
|
|
4646
|
+
const content = fs3.readFileSync(p, "utf-8");
|
|
4647
|
+
return { content, lineCount: content ? content.split("\n").length : 0, fileSize: stat.size, error: "" };
|
|
3193
4648
|
} catch (e) {
|
|
3194
4649
|
const msg = e instanceof Error ? e.message : String(e);
|
|
3195
4650
|
return { content: "", lineCount: 0, fileSize: 0, error: `\u8BFB\u53D6\u811A\u672C\u5931\u8D25: ${msg}` };
|
|
3196
4651
|
}
|
|
3197
4652
|
}
|
|
3198
|
-
function
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
4653
|
+
function emptyNode(pathStr, depth) {
|
|
4654
|
+
return {
|
|
4655
|
+
scriptPath: pathStr,
|
|
4656
|
+
rawContent: "",
|
|
4657
|
+
preprocessedContent: "",
|
|
4658
|
+
lineCount: 0,
|
|
4659
|
+
fileSize: 0,
|
|
4660
|
+
depth,
|
|
4661
|
+
childPaths: [],
|
|
4662
|
+
error: "",
|
|
4663
|
+
l1Hit: false,
|
|
4664
|
+
l1RiskLevel: "",
|
|
4665
|
+
l1RiskType: "",
|
|
4666
|
+
l1Reason: "",
|
|
4667
|
+
l1MatchedPatterns: [],
|
|
4668
|
+
l2Hit: false,
|
|
4669
|
+
l2ViolatedRule: "",
|
|
4670
|
+
l2RiskLevel: "",
|
|
4671
|
+
l2RiskType: "",
|
|
4672
|
+
l2Reason: "",
|
|
4673
|
+
matchedKeywords: [],
|
|
4674
|
+
isSuspicious: false,
|
|
4675
|
+
keywordRiskLevel: "",
|
|
4676
|
+
keywordRiskReason: ""
|
|
4677
|
+
};
|
|
4678
|
+
}
|
|
4679
|
+
var DEFAULT_MAX_DEPTH, DEFAULT_MAX_NODES, DEFAULT_MAX_FILE_SIZE, DEFAULT_MAX_TOTAL_SIZE, BACKENDS, DEFAULT_BACKEND, RecursiveTracker;
|
|
4680
|
+
var init_tracker = __esm({
|
|
4681
|
+
"src/engine/script-engine/tracker.ts"() {
|
|
4682
|
+
"use strict";
|
|
4683
|
+
init_esm_shims();
|
|
4684
|
+
init_python_backend();
|
|
4685
|
+
init_backends();
|
|
4686
|
+
DEFAULT_MAX_DEPTH = 14;
|
|
4687
|
+
DEFAULT_MAX_NODES = 64;
|
|
4688
|
+
DEFAULT_MAX_FILE_SIZE = 2 * 1024 * 1024;
|
|
4689
|
+
DEFAULT_MAX_TOTAL_SIZE = 8 * 1024 * 1024;
|
|
4690
|
+
BACKENDS = {
|
|
4691
|
+
".py": new PythonBackend()
|
|
4692
|
+
};
|
|
4693
|
+
DEFAULT_BACKEND = new ShellBackend();
|
|
4694
|
+
RecursiveTracker = class {
|
|
4695
|
+
constructor(commandScanner, maxDepth = DEFAULT_MAX_DEPTH, maxNodes = DEFAULT_MAX_NODES, maxFileSize = DEFAULT_MAX_FILE_SIZE, maxTotalSize = DEFAULT_MAX_TOTAL_SIZE) {
|
|
4696
|
+
this.maxDepth = maxDepth;
|
|
4697
|
+
this.maxNodes = maxNodes;
|
|
4698
|
+
this.maxFileSize = maxFileSize;
|
|
4699
|
+
this.maxTotalSize = maxTotalSize;
|
|
4700
|
+
this._scanner = commandScanner;
|
|
4701
|
+
this.result = {
|
|
4702
|
+
nodes: [],
|
|
4703
|
+
entryPath: "",
|
|
4704
|
+
maxDepthReached: false,
|
|
4705
|
+
maxSizeReached: false,
|
|
4706
|
+
maxNodesReached: false,
|
|
4707
|
+
totalSize: 0,
|
|
4708
|
+
totalLines: 0,
|
|
4709
|
+
totalNodes: 0,
|
|
4710
|
+
hasCritical: false,
|
|
4711
|
+
hasHigh: false,
|
|
4712
|
+
firstHitNode: null,
|
|
4713
|
+
error: ""
|
|
4714
|
+
};
|
|
4715
|
+
}
|
|
4716
|
+
maxDepth;
|
|
4717
|
+
maxNodes;
|
|
4718
|
+
maxFileSize;
|
|
4719
|
+
maxTotalSize;
|
|
4720
|
+
result;
|
|
4721
|
+
visiting = /* @__PURE__ */ new Set();
|
|
4722
|
+
visitedPaths = /* @__PURE__ */ new Set();
|
|
4723
|
+
_scanner;
|
|
4724
|
+
track(entryPath, entryArgs, cwd = "") {
|
|
4725
|
+
const baseDir = cwd || process.cwd();
|
|
4726
|
+
const resolved = resolvePath(entryPath);
|
|
4727
|
+
this.result.entryPath = resolved;
|
|
4728
|
+
this.visitScript(resolved, 0, path3.dirname(resolved), entryArgs);
|
|
4729
|
+
const nodes = this.result.nodes;
|
|
4730
|
+
this.result.hasCritical = nodes.some((n) => n.l1RiskLevel === "critical" || n.l2RiskLevel === "critical" || n.keywordRiskLevel === "critical") || this.result.hasCritical;
|
|
4731
|
+
this.result.hasHigh = nodes.some((n) => ["high", "critical"].includes(n.l1RiskLevel) || ["high", "critical"].includes(n.l2RiskLevel) || ["high", "critical"].includes(n.keywordRiskLevel));
|
|
4732
|
+
this.result.firstHitNode = nodes.find((n) => n.l1Hit || n.l2Hit || n.isSuspicious) || null;
|
|
4733
|
+
return this.result;
|
|
4734
|
+
}
|
|
4735
|
+
visitScript(pathStr, depth, baseDir, args) {
|
|
4736
|
+
pathStr = resolvePath(pathStr);
|
|
4737
|
+
if (this.visiting.has(pathStr)) return;
|
|
4738
|
+
if (this.visitedPaths.has(pathStr)) return;
|
|
4739
|
+
if (depth > this.maxDepth) {
|
|
4740
|
+
this.result.maxDepthReached = true;
|
|
4741
|
+
return;
|
|
4742
|
+
}
|
|
4743
|
+
if (this.result.nodes.length >= this.maxNodes) {
|
|
4744
|
+
this.result.maxNodesReached = true;
|
|
4745
|
+
return;
|
|
4746
|
+
}
|
|
4747
|
+
if (this.result.totalSize >= this.maxTotalSize) {
|
|
4748
|
+
this.result.maxSizeReached = true;
|
|
4749
|
+
return;
|
|
4750
|
+
}
|
|
4751
|
+
this.visiting.add(pathStr);
|
|
4752
|
+
this.visitedPaths.add(pathStr);
|
|
4753
|
+
const { content, lineCount, fileSize, error } = readScript(pathStr, this.maxFileSize);
|
|
4754
|
+
if (error) {
|
|
4755
|
+
const node2 = emptyNode(pathStr, depth);
|
|
4756
|
+
node2.error = error;
|
|
4757
|
+
node2.fileSize = fileSize;
|
|
4758
|
+
node2.lineCount = lineCount;
|
|
4759
|
+
this.result.nodes.push(node2);
|
|
4760
|
+
this.visiting.delete(pathStr);
|
|
4761
|
+
return;
|
|
4762
|
+
}
|
|
4763
|
+
this.result.totalSize += fileSize;
|
|
4764
|
+
this.result.totalLines += lineCount;
|
|
4765
|
+
const ir = pickBackend(pathStr).parse(content, args);
|
|
4766
|
+
const node = emptyNode(pathStr, depth);
|
|
4767
|
+
node.rawContent = content;
|
|
4768
|
+
node.preprocessedContent = ir.reachableContent;
|
|
4769
|
+
node.lineCount = lineCount;
|
|
4770
|
+
node.fileSize = fileSize;
|
|
4771
|
+
this.result.nodes.push(node);
|
|
4772
|
+
this.result.totalNodes = this.result.nodes.length;
|
|
4773
|
+
if (this._scanner && ir.reachableContent) {
|
|
4774
|
+
const l1 = this._scanner(ir.reachableContent);
|
|
4775
|
+
if (l1.hit) {
|
|
4776
|
+
node.l1Hit = true;
|
|
4777
|
+
node.l1RiskLevel = l1.riskLevel;
|
|
4778
|
+
node.l1RiskType = l1.riskType;
|
|
4779
|
+
node.l1Reason = l1.reason;
|
|
4780
|
+
node.l1MatchedPatterns = l1.matchedPatterns || [];
|
|
4781
|
+
if (l1.riskLevel === "critical") {
|
|
4782
|
+
this.result.hasCritical = true;
|
|
4783
|
+
this.result.firstHitNode = node;
|
|
4784
|
+
this.visiting.delete(pathStr);
|
|
4785
|
+
return;
|
|
4786
|
+
}
|
|
4787
|
+
}
|
|
4788
|
+
}
|
|
4789
|
+
const parentDir = path3.dirname(pathStr);
|
|
4790
|
+
const childPaths = [];
|
|
4791
|
+
for (const call of ir.calls) {
|
|
4792
|
+
const child = resolvePath(call.path, parentDir);
|
|
4793
|
+
childPaths.push(child);
|
|
4794
|
+
this.visitScript(child, depth + 1, path3.dirname(child), call.args);
|
|
4795
|
+
if (this.result.hasCritical) break;
|
|
4796
|
+
}
|
|
4797
|
+
node.childPaths = childPaths;
|
|
4798
|
+
this.visiting.delete(pathStr);
|
|
4799
|
+
}
|
|
4800
|
+
};
|
|
4801
|
+
}
|
|
4802
|
+
});
|
|
4803
|
+
|
|
4804
|
+
// src/engine/script-content-analyzer.ts
|
|
4805
|
+
import { statSync as statSync2, existsSync as existsSync9 } from "fs";
|
|
4806
|
+
import { resolve as resolve8, join as join5, isAbsolute as isAbsolute2 } from "path";
|
|
4807
|
+
import { homedir as homedir3 } from "os";
|
|
4808
|
+
function emptyChain() {
|
|
4809
|
+
return {
|
|
4810
|
+
nodes: [],
|
|
4811
|
+
entryPath: "",
|
|
4812
|
+
maxDepthReached: false,
|
|
4813
|
+
maxSizeReached: false,
|
|
4814
|
+
maxNodesReached: false,
|
|
4815
|
+
totalSize: 0,
|
|
4816
|
+
totalLines: 0,
|
|
4817
|
+
totalNodes: 0,
|
|
4818
|
+
hasCritical: false,
|
|
4819
|
+
hasHigh: false,
|
|
4820
|
+
firstHitNode: null,
|
|
4821
|
+
error: ""
|
|
4822
|
+
};
|
|
4823
|
+
}
|
|
4824
|
+
function scanScriptForKeywords2(content) {
|
|
4825
|
+
const matched = [];
|
|
4826
|
+
for (const { name, pattern } of SUSPICIOUS_PATTERNS2) {
|
|
4827
|
+
if (pattern.test(content)) matched.push(name);
|
|
4828
|
+
}
|
|
4829
|
+
return matched;
|
|
4830
|
+
}
|
|
4831
|
+
function assessKeywordCombinationRisk2(matchedKeywords, scriptContent) {
|
|
4832
|
+
if (matchedKeywords.length === 0) return { level: "", reason: "" };
|
|
4833
|
+
const keywordSet = new Set(matchedKeywords);
|
|
4834
|
+
for (const rule of KEYWORD_COMBINATION_RISKS2) {
|
|
4835
|
+
const hitGroup1 = [...rule.group1].some((k) => keywordSet.has(k));
|
|
4836
|
+
const hitGroup2 = rule.group2.size === 0 ? true : [...rule.group2].some((k) => keywordSet.has(k));
|
|
4837
|
+
if (hitGroup1 && hitGroup2) return { level: rule.level, reason: rule.reason };
|
|
4838
|
+
}
|
|
4839
|
+
const networkPostKeywords = /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]);
|
|
4840
|
+
const hasNetworkPost = [...networkPostKeywords].some((k) => keywordSet.has(k));
|
|
4841
|
+
if (hasNetworkPost) {
|
|
4842
|
+
const envPatterns = ["$env", "$(env)", "$USER", "$(hostname)", "$(pwd)", "$HOME"];
|
|
4843
|
+
const contentLower = scriptContent.toLowerCase();
|
|
4844
|
+
for (const p of envPatterns) {
|
|
4845
|
+
if (contentLower.includes(p.toLowerCase())) {
|
|
4846
|
+
return {
|
|
4847
|
+
level: "critical",
|
|
4848
|
+
reason: "\u68C0\u6D4B\u5230\u7F51\u7EDC\u6570\u636E\u53D1\u9001 + \u7CFB\u7EDF\u73AF\u5883\u53D8\u91CF\u6536\u96C6\u7EC4\u5408\uFF0C\u6784\u6210 Critical \u7EA7\u522B\u6570\u636E\u5916\u4F20\u98CE\u9669\u3002\u811A\u672C\u53EF\u80FD\u5C06\u7528\u6237\u73AF\u5883\u53D8\u91CF\uFF08\u53EF\u80FD\u5305\u542B API Key\u3001\u5BC6\u94A5\u7B49\u654F\u611F\u4FE1\u606F\uFF09\u53D1\u9001\u5230\u8FDC\u7A0B\u670D\u52A1\u5668\u3002"
|
|
4849
|
+
};
|
|
3214
4850
|
}
|
|
3215
4851
|
}
|
|
3216
4852
|
}
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
depth: 0,
|
|
3228
|
-
maxDepth,
|
|
3229
|
-
maxFileSize,
|
|
3230
|
-
maxTotalSize,
|
|
3231
|
-
visited,
|
|
3232
|
-
result,
|
|
3233
|
-
baseDir,
|
|
3234
|
-
input
|
|
3235
|
-
});
|
|
3236
|
-
result.hasCritical = result.nodes.some((n) => n.l1RiskLevel === "critical" || n.l2RiskLevel === "critical" || n.keywordRiskLevel === "critical");
|
|
3237
|
-
result.hasHigh = result.nodes.some((n) => ["high", "critical"].includes(n.l1RiskLevel) || ["high", "critical"].includes(n.l2RiskLevel) || ["high", "critical"].includes(n.keywordRiskLevel));
|
|
3238
|
-
result.firstHitNode = result.nodes.find((n) => n.l1Hit || n.l2Hit || n.isSuspicious) || null;
|
|
3239
|
-
return result;
|
|
4853
|
+
let bestLevel = "";
|
|
4854
|
+
let bestReason = "";
|
|
4855
|
+
for (const kw of matchedKeywords) {
|
|
4856
|
+
const risk = SINGLE_KEYWORD_RISKS2[kw];
|
|
4857
|
+
if (risk && (LEVEL_ORDER2[risk.level] || 0) > (LEVEL_ORDER2[bestLevel] || 0)) {
|
|
4858
|
+
bestLevel = risk.level;
|
|
4859
|
+
bestReason = risk.reason;
|
|
4860
|
+
}
|
|
4861
|
+
}
|
|
4862
|
+
return { level: bestLevel, reason: bestReason };
|
|
3240
4863
|
}
|
|
3241
|
-
function
|
|
3242
|
-
const
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
result.maxDepthReached = true;
|
|
3246
|
-
return;
|
|
4864
|
+
function extractScriptPath2(command) {
|
|
4865
|
+
for (const pattern of SCRIPT_EXEC_PATTERNS2) {
|
|
4866
|
+
const m = command.match(pattern);
|
|
4867
|
+
if (m) return m[1];
|
|
3247
4868
|
}
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
4869
|
+
return null;
|
|
4870
|
+
}
|
|
4871
|
+
function extractScriptPathWithArgs(command) {
|
|
4872
|
+
const scriptPath = extractScriptPath2(command);
|
|
4873
|
+
if (!scriptPath) return { path: null, args: [] };
|
|
4874
|
+
const idx = command.indexOf(scriptPath);
|
|
4875
|
+
if (idx < 0) return { path: scriptPath, args: [] };
|
|
4876
|
+
let rest = command.slice(idx + scriptPath.length);
|
|
4877
|
+
rest = rest.split(/[;|&<>]/)[0].trim();
|
|
4878
|
+
const tokens = [...rest.matchAll(/(["'])(.*?)\1|(\S+)/g)];
|
|
4879
|
+
const args = [];
|
|
4880
|
+
for (const m of tokens) {
|
|
4881
|
+
args.push(m[1] ? m[2] : m[3]);
|
|
3251
4882
|
}
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
4883
|
+
return { path: scriptPath, args };
|
|
4884
|
+
}
|
|
4885
|
+
function extractInlineScriptPath(actionDetail) {
|
|
4886
|
+
const re = /open\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
4887
|
+
let m;
|
|
4888
|
+
while ((m = re.exec(actionDetail)) !== null) {
|
|
4889
|
+
const p = m[1];
|
|
4890
|
+
let resolved;
|
|
4891
|
+
try {
|
|
4892
|
+
resolved = resolvePath2(p);
|
|
4893
|
+
if (existsSync9(resolved)) {
|
|
4894
|
+
const stat = statSync2(resolved);
|
|
4895
|
+
if (stat.isFile()) return resolved;
|
|
4896
|
+
}
|
|
4897
|
+
} catch {
|
|
4898
|
+
}
|
|
3261
4899
|
}
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
node.lineCount = lineCount;
|
|
3269
|
-
node.fileSize = fileSize;
|
|
3270
|
-
result.nodes.push(node);
|
|
3271
|
-
const virtualInput = {
|
|
3272
|
-
...input,
|
|
3273
|
-
aNext: { actionType: "bash", actionDetail: preprocessed }
|
|
3274
|
-
};
|
|
3275
|
-
const l1 = runHeuristicCheck(virtualInput);
|
|
3276
|
-
if (l1 && !l1.allowed) {
|
|
3277
|
-
node.l1Hit = true;
|
|
3278
|
-
node.l1RiskLevel = l1.riskLevel;
|
|
3279
|
-
node.l1RiskType = l1.riskType;
|
|
3280
|
-
node.l1Reason = l1.reason;
|
|
3281
|
-
node.l1MatchedPatterns = [];
|
|
3282
|
-
if (l1.riskLevel === "critical") {
|
|
3283
|
-
result.hasCritical = true;
|
|
3284
|
-
result.firstHitNode = node;
|
|
3285
|
-
return;
|
|
3286
|
-
}
|
|
3287
|
-
}
|
|
3288
|
-
const childPaths = extractChildScriptPaths(preprocessed, baseDir);
|
|
3289
|
-
node.childPaths = childPaths;
|
|
3290
|
-
for (const childPath of childPaths) {
|
|
3291
|
-
resolveRecursive({
|
|
3292
|
-
path: resolvePath(childPath),
|
|
3293
|
-
depth: depth + 1,
|
|
3294
|
-
maxDepth,
|
|
3295
|
-
maxFileSize,
|
|
3296
|
-
maxTotalSize,
|
|
3297
|
-
visited,
|
|
3298
|
-
result,
|
|
3299
|
-
baseDir: dirname6(resolvePath(childPath)),
|
|
3300
|
-
input
|
|
3301
|
-
});
|
|
3302
|
-
if (result.hasCritical) return;
|
|
4900
|
+
return null;
|
|
4901
|
+
}
|
|
4902
|
+
function resolvePath2(p) {
|
|
4903
|
+
let expanded = p;
|
|
4904
|
+
if (expanded.startsWith("~/") || expanded === "~") {
|
|
4905
|
+
expanded = join5(homedir3(), expanded.slice(1));
|
|
3303
4906
|
}
|
|
4907
|
+
return resolve8(expanded);
|
|
3304
4908
|
}
|
|
3305
4909
|
function scanScriptChainWithL1(chain, input) {
|
|
3306
4910
|
for (const node of chain.nodes) {
|
|
@@ -3369,14 +4973,31 @@ function scanScriptChainKeywords(chain) {
|
|
|
3369
4973
|
function analyzeScriptContent2(input) {
|
|
3370
4974
|
const actionType = input.aNext.actionType.toLowerCase();
|
|
3371
4975
|
if (actionType !== "bash") return emptyChain();
|
|
3372
|
-
const scriptPath =
|
|
4976
|
+
const { path: scriptPath, args: scriptArgs } = extractScriptPathWithArgs(input.aNext.actionDetail);
|
|
3373
4977
|
let resolvedPath = scriptPath;
|
|
4978
|
+
let isInline = false;
|
|
3374
4979
|
if (!resolvedPath) {
|
|
3375
4980
|
const inline = extractInlineScriptPath(input.aNext.actionDetail);
|
|
3376
4981
|
if (!inline) return emptyChain();
|
|
3377
4982
|
resolvedPath = inline;
|
|
4983
|
+
isInline = true;
|
|
3378
4984
|
}
|
|
3379
|
-
|
|
4985
|
+
if (resolvedPath && !isAbsolute2(resolvedPath) && !resolvedPath.startsWith("~/")) {
|
|
4986
|
+
resolvedPath = join5(input.cwd || process.cwd(), resolvedPath);
|
|
4987
|
+
}
|
|
4988
|
+
const l1Adapter = (content) => {
|
|
4989
|
+
const virtualInput = {
|
|
4990
|
+
...input,
|
|
4991
|
+
aNext: { actionType: "bash", actionDetail: content }
|
|
4992
|
+
};
|
|
4993
|
+
const j = runHeuristicCheck(virtualInput);
|
|
4994
|
+
if (j && !j.allowed) {
|
|
4995
|
+
return { hit: true, riskLevel: j.riskLevel, riskType: j.riskType, reason: j.reason, matchedPatterns: [] };
|
|
4996
|
+
}
|
|
4997
|
+
return { hit: false, riskLevel: "", riskType: "", reason: "", matchedPatterns: [] };
|
|
4998
|
+
};
|
|
4999
|
+
const tracker = new RecursiveTracker(l1Adapter);
|
|
5000
|
+
const chain = tracker.track(resolvedPath, isInline ? null : scriptArgs, input.cwd || process.cwd());
|
|
3380
5001
|
if (chain.nodes.length === 0) return chain;
|
|
3381
5002
|
scanScriptChainWithL1(chain, input);
|
|
3382
5003
|
scanScriptChainWithL2(chain, input);
|
|
@@ -3444,13 +5065,14 @@ ${contentPreview}
|
|
|
3444
5065
|
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";
|
|
3445
5066
|
return header + parts.join("\n---\n\n");
|
|
3446
5067
|
}
|
|
3447
|
-
var SUSPICIOUS_PATTERNS2, SCRIPT_EXEC_PATTERNS2, KEYWORD_COMBINATION_RISKS2, SINGLE_KEYWORD_RISKS2, LEVEL_ORDER2
|
|
5068
|
+
var SUSPICIOUS_PATTERNS2, SCRIPT_EXEC_PATTERNS2, KEYWORD_COMBINATION_RISKS2, SINGLE_KEYWORD_RISKS2, LEVEL_ORDER2;
|
|
3448
5069
|
var init_script_content_analyzer = __esm({
|
|
3449
5070
|
"src/engine/script-content-analyzer.ts"() {
|
|
3450
5071
|
"use strict";
|
|
3451
5072
|
init_esm_shims();
|
|
3452
5073
|
init_heuristic();
|
|
3453
5074
|
init_logic_rules();
|
|
5075
|
+
init_tracker();
|
|
3454
5076
|
SUSPICIOUS_PATTERNS2 = [
|
|
3455
5077
|
{ name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
|
|
3456
5078
|
{ name: "curl --data", pattern: /curl\s+(.*\s)?--data/i },
|
|
@@ -3533,18 +5155,6 @@ var init_script_content_analyzer = __esm({
|
|
|
3533
5155
|
"ssh no host key check": { level: "high", reason: "\u68C0\u6D4B\u5230 SSH \u8DF3\u8FC7\u4E3B\u673A\u5BC6\u94A5\u9A8C\u8BC1\uFF0C\u53EF\u80FD\u662F\u6A2A\u5411\u79FB\u52A8\u3002" }
|
|
3534
5156
|
};
|
|
3535
5157
|
LEVEL_ORDER2 = { low: 1, medium: 2, high: 3, critical: 4 };
|
|
3536
|
-
CHILD_SCRIPT_PATTERNS = [
|
|
3537
|
-
/(?:^|\s|&&|\|\|)\s*bash\s+(\S+)/i,
|
|
3538
|
-
/(?:^|\s|&&|\|\|)\s*sh\s+(\S+)/i,
|
|
3539
|
-
/(?:^|\s|&&|\|\|)\s*source\s+(\S+)/i,
|
|
3540
|
-
/(?:^|\s|&&|\|\|)\s*\.\s+(\S+)/,
|
|
3541
|
-
/(?:^|\s|&&|\|\|)\s*python3?\s+(\S+\.py)/i,
|
|
3542
|
-
/(?:^|\s|&&|\|\|)\s*node\s+(\S+\.js)/i,
|
|
3543
|
-
/(?:^|\s|&&|\|\|)\s*ruby\s+(\S+\.rb)/i,
|
|
3544
|
-
/(?:^|\s|&&|\|\|)\s*perl\s+(\S+\.pl)/i,
|
|
3545
|
-
/(?:^|\s|&&|\|\|)\s*php\s+(\S+\.php)/i,
|
|
3546
|
-
/(?:^|\s|&&|\|\|)\s*lua\s+(\S+\.lua)/i
|
|
3547
|
-
];
|
|
3548
5158
|
}
|
|
3549
5159
|
});
|
|
3550
5160
|
|
|
@@ -3558,16 +5168,16 @@ __export(token_stats_exports, {
|
|
|
3558
5168
|
recordTokenUsage: () => recordTokenUsage,
|
|
3559
5169
|
resetTokenStats: () => resetTokenStats
|
|
3560
5170
|
});
|
|
3561
|
-
import { readFileSync as
|
|
5171
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
3562
5172
|
function getStatsPath() {
|
|
3563
5173
|
const envPath = process.env[ENV_STATS_PATH];
|
|
3564
5174
|
if (envPath) return envPath;
|
|
3565
5175
|
return getTokenStatsPath();
|
|
3566
5176
|
}
|
|
3567
5177
|
function loadAllRecords(statsPath) {
|
|
3568
|
-
if (!
|
|
5178
|
+
if (!existsSync10(statsPath)) return [];
|
|
3569
5179
|
try {
|
|
3570
|
-
const data = JSON.parse(
|
|
5180
|
+
const data = JSON.parse(readFileSync11(statsPath, "utf-8"));
|
|
3571
5181
|
if (Array.isArray(data)) return data;
|
|
3572
5182
|
if (data && Array.isArray(data.records)) return data.records;
|
|
3573
5183
|
return [];
|
|
@@ -3578,7 +5188,7 @@ function loadAllRecords(statsPath) {
|
|
|
3578
5188
|
function saveRecords(statsPath, records) {
|
|
3579
5189
|
try {
|
|
3580
5190
|
const dir = statsPath.substring(0, statsPath.lastIndexOf("/"));
|
|
3581
|
-
if (dir && !
|
|
5191
|
+
if (dir && !existsSync10(dir)) mkdirSync2(dir, { recursive: true });
|
|
3582
5192
|
writeFileSync2(statsPath, JSON.stringify(records, null, 2), "utf-8");
|
|
3583
5193
|
} catch (e) {
|
|
3584
5194
|
}
|
|
@@ -3705,7 +5315,7 @@ function getTokenTrend(days = 0, startDate, endDate) {
|
|
|
3705
5315
|
}
|
|
3706
5316
|
function resetTokenStats() {
|
|
3707
5317
|
const statsPath = getStatsPath();
|
|
3708
|
-
if (
|
|
5318
|
+
if (existsSync10(statsPath)) {
|
|
3709
5319
|
try {
|
|
3710
5320
|
unlinkSync2(statsPath);
|
|
3711
5321
|
} catch {
|
|
@@ -3935,7 +5545,7 @@ async function analyzeWithLLM(prompt, config) {
|
|
|
3935
5545
|
}
|
|
3936
5546
|
if (attempt < maxRetries) {
|
|
3937
5547
|
console.error(`[AgentMoss] LLM call failed (attempt ${attempt + 1}/${maxRetries + 1}), retrying in ${retryCfg.retryInterval}s:`, err.message);
|
|
3938
|
-
await new Promise((
|
|
5548
|
+
await new Promise((resolve9) => setTimeout(resolve9, retryCfg.retryInterval * 1e3));
|
|
3939
5549
|
} else {
|
|
3940
5550
|
clearTimeout(totalTimeoutTimer);
|
|
3941
5551
|
console.error("[AgentMoss] LLM call failed after all retries:", err.message);
|
|
@@ -3962,9 +5572,9 @@ var init_llm_analyzer = __esm({
|
|
|
3962
5572
|
});
|
|
3963
5573
|
|
|
3964
5574
|
// src/brain/rule-store.ts
|
|
3965
|
-
import { existsSync as
|
|
3966
|
-
import { join as
|
|
3967
|
-
import { homedir as
|
|
5575
|
+
import { existsSync as existsSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
5576
|
+
import { join as join6 } from "path";
|
|
5577
|
+
import { homedir as homedir4 } from "os";
|
|
3968
5578
|
function getRulesPath() {
|
|
3969
5579
|
const envPath = process.env[ENV_RULES_PATH];
|
|
3970
5580
|
if (envPath) return envPath;
|
|
@@ -3982,7 +5592,7 @@ var init_rule_store = __esm({
|
|
|
3982
5592
|
"src/brain/rule-store.ts"() {
|
|
3983
5593
|
"use strict";
|
|
3984
5594
|
init_esm_shims();
|
|
3985
|
-
DEFAULT_RULES_PATH =
|
|
5595
|
+
DEFAULT_RULES_PATH = join6(homedir4(), ".agentmoss", "brain", "rules.json");
|
|
3986
5596
|
ENV_RULES_PATH = "AGENT_MOSS_BRAIN_RULES_PATH";
|
|
3987
5597
|
STATUS_DRAFT = "draft";
|
|
3988
5598
|
STATUS_APPROVED = "approved";
|
|
@@ -3994,16 +5604,16 @@ var init_rule_store = __esm({
|
|
|
3994
5604
|
BrainRuleStore = class {
|
|
3995
5605
|
path;
|
|
3996
5606
|
data;
|
|
3997
|
-
constructor(
|
|
3998
|
-
this.path =
|
|
5607
|
+
constructor(path6) {
|
|
5608
|
+
this.path = path6 || getRulesPath();
|
|
3999
5609
|
this.data = this.load();
|
|
4000
5610
|
}
|
|
4001
5611
|
load() {
|
|
4002
|
-
if (!
|
|
5612
|
+
if (!existsSync11(this.path)) {
|
|
4003
5613
|
return { version: 1, rules: [], pattern_candidates: [] };
|
|
4004
5614
|
}
|
|
4005
5615
|
try {
|
|
4006
|
-
const raw = JSON.parse(
|
|
5616
|
+
const raw = JSON.parse(readFileSync12(this.path, "utf-8"));
|
|
4007
5617
|
return {
|
|
4008
5618
|
version: raw.version || 1,
|
|
4009
5619
|
rules: raw.rules || [],
|
|
@@ -4308,10 +5918,10 @@ var init_pattern_extractor = __esm({
|
|
|
4308
5918
|
const pathRe = new RegExp(pathPrefix + pathBody + pathSuffix, "g");
|
|
4309
5919
|
let mm;
|
|
4310
5920
|
while ((mm = pathRe.exec(src)) !== null) {
|
|
4311
|
-
const
|
|
4312
|
-
if (
|
|
4313
|
-
if (["/dev/null", "/dev/zero", "/dev/random", "/dev/urandom"].includes(
|
|
4314
|
-
const gen = this.generalizePath(
|
|
5921
|
+
const path6 = mm[1];
|
|
5922
|
+
if (path6.split("/").length < 3) continue;
|
|
5923
|
+
if (["/dev/null", "/dev/zero", "/dev/random", "/dev/urandom"].includes(path6)) continue;
|
|
5924
|
+
const gen = this.generalizePath(path6);
|
|
4315
5925
|
if (seen.has(gen)) continue;
|
|
4316
5926
|
seen.add(gen);
|
|
4317
5927
|
patterns.push({ pattern: gen, layer: 1, scope: "openat", ruleType: "deny_path" });
|
|
@@ -4330,8 +5940,8 @@ var init_pattern_extractor = __esm({
|
|
|
4330
5940
|
}
|
|
4331
5941
|
return false;
|
|
4332
5942
|
}
|
|
4333
|
-
generalizePath(
|
|
4334
|
-
const p =
|
|
5943
|
+
generalizePath(path6) {
|
|
5944
|
+
const p = path6.toLowerCase();
|
|
4335
5945
|
if (/\/etc\/(shadow|passwd|sudoers|security|ssh)/.test(p)) {
|
|
4336
5946
|
return "(?:^|[^a-z0-9_-])etc/(?:shadow|passwd|sudoers|security/|ssh/)";
|
|
4337
5947
|
}
|
|
@@ -4456,58 +6066,980 @@ var init_reviewer = __esm({
|
|
|
4456
6066
|
}
|
|
4457
6067
|
});
|
|
4458
6068
|
|
|
4459
|
-
// src/brain/index.ts
|
|
4460
|
-
function checkBrainRules(store, opts) {
|
|
4461
|
-
if (!store) return null;
|
|
4462
|
-
const rules = store.getActiveRules(opts.layer);
|
|
4463
|
-
if (rules.length === 0) return null;
|
|
4464
|
-
const combined = `${opts.actionType} ${opts.actionDetail}`;
|
|
4465
|
-
for (const rule of rules) {
|
|
4466
|
-
try {
|
|
4467
|
-
if (new RegExp(rule.pattern, "i").test(combined)) {
|
|
4468
|
-
if (opts.layer === 2 && opts.actionHistory) {
|
|
4469
|
-
if (!checkLayer2Context(rule.pattern, opts.actionHistory, opts.actionDetail)) {
|
|
4470
|
-
continue;
|
|
6069
|
+
// src/brain/index.ts
|
|
6070
|
+
function checkBrainRules(store, opts) {
|
|
6071
|
+
if (!store) return null;
|
|
6072
|
+
const rules = store.getActiveRules(opts.layer);
|
|
6073
|
+
if (rules.length === 0) return null;
|
|
6074
|
+
const combined = `${opts.actionType} ${opts.actionDetail}`;
|
|
6075
|
+
for (const rule of rules) {
|
|
6076
|
+
try {
|
|
6077
|
+
if (new RegExp(rule.pattern, "i").test(combined)) {
|
|
6078
|
+
if (opts.layer === 2 && opts.actionHistory) {
|
|
6079
|
+
if (!checkLayer2Context(rule.pattern, opts.actionHistory, opts.actionDetail)) {
|
|
6080
|
+
continue;
|
|
6081
|
+
}
|
|
6082
|
+
}
|
|
6083
|
+
store.recordTrigger(rule.id);
|
|
6084
|
+
return {
|
|
6085
|
+
ruleId: rule.id,
|
|
6086
|
+
pattern: rule.pattern,
|
|
6087
|
+
explanation: rule.explanation,
|
|
6088
|
+
confidence: rule.confidence
|
|
6089
|
+
};
|
|
6090
|
+
}
|
|
6091
|
+
} catch {
|
|
6092
|
+
continue;
|
|
6093
|
+
}
|
|
6094
|
+
}
|
|
6095
|
+
return null;
|
|
6096
|
+
}
|
|
6097
|
+
function checkLayer2Context(pattern, actionHistory, actionDetail) {
|
|
6098
|
+
if (pattern.startsWith("BEHAVIOR_CHAIN:")) {
|
|
6099
|
+
const chainType = pattern.replace("BEHAVIOR_CHAIN:", "");
|
|
6100
|
+
if (chainType === "external_read\u2192sensitive_write") {
|
|
6101
|
+
const hasExternalRead = actionHistory.slice(-5).some((h) => /curl|wget|http/i.test(h.actionDetail ?? ""));
|
|
6102
|
+
const isSensitiveWrite = /\.ssh|\.gnupg|authorized_keys|bashrc|profile/i.test(actionDetail);
|
|
6103
|
+
return hasExternalRead && isSensitiveWrite;
|
|
6104
|
+
}
|
|
6105
|
+
if (chainType === "multi_config_read\u2192privilege_action") {
|
|
6106
|
+
const configReads = actionHistory.slice(-5).filter((h) => /\/etc\/|config|\.conf|\.yaml|\.json/i.test(h.actionDetail ?? "")).length;
|
|
6107
|
+
const isPrivilege = /sudo|su |chmod|chown/i.test(actionDetail);
|
|
6108
|
+
return configReads >= 2 && isPrivilege;
|
|
6109
|
+
}
|
|
6110
|
+
return false;
|
|
6111
|
+
}
|
|
6112
|
+
return true;
|
|
6113
|
+
}
|
|
6114
|
+
var init_brain = __esm({
|
|
6115
|
+
"src/brain/index.ts"() {
|
|
6116
|
+
"use strict";
|
|
6117
|
+
init_esm_shims();
|
|
6118
|
+
init_rule_store();
|
|
6119
|
+
init_pattern_extractor();
|
|
6120
|
+
init_reviewer();
|
|
6121
|
+
}
|
|
6122
|
+
});
|
|
6123
|
+
|
|
6124
|
+
// src/engine/formal-engine/decode.ts
|
|
6125
|
+
function isValidBase64Token(tok) {
|
|
6126
|
+
if (tok.length < 8) return false;
|
|
6127
|
+
if (!(tok.length % 4 === 0 || tok.endsWith("==") || tok.endsWith("="))) return false;
|
|
6128
|
+
return true;
|
|
6129
|
+
}
|
|
6130
|
+
function isPrintable(s) {
|
|
6131
|
+
for (let i = 0; i < s.length; i++) {
|
|
6132
|
+
const c = s.charCodeAt(i);
|
|
6133
|
+
if (c < 32 || c > 126) return false;
|
|
6134
|
+
}
|
|
6135
|
+
return true;
|
|
6136
|
+
}
|
|
6137
|
+
function decodeBase64Literals(text) {
|
|
6138
|
+
const out = [];
|
|
6139
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6140
|
+
for (const m of text.matchAll(B64_TOKEN_RE)) {
|
|
6141
|
+
const tok = m[0];
|
|
6142
|
+
if (!isValidBase64Token(tok)) continue;
|
|
6143
|
+
if (!/[A-Z]/.test(tok) || !/[a-z]/.test(tok)) continue;
|
|
6144
|
+
try {
|
|
6145
|
+
const padded = tok + "=".repeat((4 - tok.length % 4) % 4);
|
|
6146
|
+
const bin = Buffer.from(padded, "base64");
|
|
6147
|
+
const s = bin.toString("utf8");
|
|
6148
|
+
if (s && isPrintable(s) && !seen.has(s)) {
|
|
6149
|
+
seen.add(s);
|
|
6150
|
+
out.push(s);
|
|
6151
|
+
}
|
|
6152
|
+
} catch {
|
|
6153
|
+
}
|
|
6154
|
+
}
|
|
6155
|
+
return out;
|
|
6156
|
+
}
|
|
6157
|
+
function decodeUrlAndNumeric(text) {
|
|
6158
|
+
const out = [];
|
|
6159
|
+
let s = text;
|
|
6160
|
+
for (let i = 0; i < 3; i++) {
|
|
6161
|
+
let nxt = s;
|
|
6162
|
+
try {
|
|
6163
|
+
nxt = decodeURIComponent(s);
|
|
6164
|
+
} catch {
|
|
6165
|
+
break;
|
|
6166
|
+
}
|
|
6167
|
+
if (nxt === s) break;
|
|
6168
|
+
s = nxt;
|
|
6169
|
+
}
|
|
6170
|
+
if (s !== text) out.push(s);
|
|
6171
|
+
if (/\\[0-7]{3}/.test(text)) {
|
|
6172
|
+
let expanded = "";
|
|
6173
|
+
try {
|
|
6174
|
+
expanded = text.replace(/\\[0-7]{3}/g, (m) => String.fromCharCode(parseInt(m.slice(1), 8)));
|
|
6175
|
+
} catch {
|
|
6176
|
+
}
|
|
6177
|
+
if (expanded && expanded !== text) out.push(expanded);
|
|
6178
|
+
}
|
|
6179
|
+
if (/\\x[0-9a-fA-F]{2}/.test(text)) {
|
|
6180
|
+
let expanded = "";
|
|
6181
|
+
try {
|
|
6182
|
+
expanded = text.replace(/\\x[0-9a-fA-F]{2}/g, (m) => String.fromCharCode(parseInt(m.slice(2), 16)));
|
|
6183
|
+
} catch {
|
|
6184
|
+
}
|
|
6185
|
+
if (expanded && expanded !== text) out.push(expanded);
|
|
6186
|
+
}
|
|
6187
|
+
return out;
|
|
6188
|
+
}
|
|
6189
|
+
function allCandidates(detail, max = 8) {
|
|
6190
|
+
const cands = [detail];
|
|
6191
|
+
cands.push(...decodeBase64Literals(detail));
|
|
6192
|
+
cands.push(...decodeUrlAndNumeric(detail));
|
|
6193
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6194
|
+
const out = [];
|
|
6195
|
+
for (const c of cands) {
|
|
6196
|
+
if (seen.has(c) || c.length > 4096) continue;
|
|
6197
|
+
seen.add(c);
|
|
6198
|
+
out.push(c);
|
|
6199
|
+
if (out.length >= max) break;
|
|
6200
|
+
}
|
|
6201
|
+
return out;
|
|
6202
|
+
}
|
|
6203
|
+
function matchStageAny(detail, stages) {
|
|
6204
|
+
for (const cand of allCandidates(detail)) {
|
|
6205
|
+
for (const [name, pats] of Object.entries(stages)) {
|
|
6206
|
+
for (const p of pats) {
|
|
6207
|
+
p.lastIndex = 0;
|
|
6208
|
+
if (p.test(cand)) return name;
|
|
6209
|
+
}
|
|
6210
|
+
}
|
|
6211
|
+
}
|
|
6212
|
+
return "";
|
|
6213
|
+
}
|
|
6214
|
+
var B64_TOKEN_RE;
|
|
6215
|
+
var init_decode = __esm({
|
|
6216
|
+
"src/engine/formal-engine/decode.ts"() {
|
|
6217
|
+
"use strict";
|
|
6218
|
+
init_esm_shims();
|
|
6219
|
+
B64_TOKEN_RE = /[A-Za-z0-9+/]+={0,2}/g;
|
|
6220
|
+
}
|
|
6221
|
+
});
|
|
6222
|
+
|
|
6223
|
+
// src/engine/formal-engine/builder.ts
|
|
6224
|
+
function loadStages() {
|
|
6225
|
+
if (COMBO_STAGES2) return COMBO_STAGES2;
|
|
6226
|
+
const combo = loadL2().combo_chains || {};
|
|
6227
|
+
const stages = {};
|
|
6228
|
+
for (const [name, pats] of Object.entries(combo.stages || {})) {
|
|
6229
|
+
stages[name] = pats.map((p) => new RegExp(p, "i"));
|
|
6230
|
+
}
|
|
6231
|
+
COMBO_STAGES2 = stages;
|
|
6232
|
+
return stages;
|
|
6233
|
+
}
|
|
6234
|
+
function extractResources(actionDetail) {
|
|
6235
|
+
const resources = /* @__PURE__ */ new Set();
|
|
6236
|
+
const text = actionDetail.replace(/'[^']*'|"[^"]*"/g, "");
|
|
6237
|
+
PATH_RE.lastIndex = 0;
|
|
6238
|
+
let m;
|
|
6239
|
+
while ((m = PATH_RE.exec(text)) !== null) {
|
|
6240
|
+
const p = m[1];
|
|
6241
|
+
if (p.startsWith("/dev/") || p.includes("://")) continue;
|
|
6242
|
+
resources.add(p);
|
|
6243
|
+
}
|
|
6244
|
+
return resources;
|
|
6245
|
+
}
|
|
6246
|
+
function markStage(actionDetail) {
|
|
6247
|
+
return matchStageAny(actionDetail, loadStages());
|
|
6248
|
+
}
|
|
6249
|
+
function buildSessionGraph(actionHistory, aNext, sessionId = "current") {
|
|
6250
|
+
const nodes = [];
|
|
6251
|
+
const edges = [];
|
|
6252
|
+
const stageFlags = /* @__PURE__ */ new Set();
|
|
6253
|
+
const details = [];
|
|
6254
|
+
for (const a of actionHistory) {
|
|
6255
|
+
const ad = String(a.actionDetail || "");
|
|
6256
|
+
if (ad) details.push({ actionType: String(a.actionType || ""), actionDetail: ad });
|
|
6257
|
+
}
|
|
6258
|
+
if (aNext) {
|
|
6259
|
+
details.push({ actionType: String(aNext.actionType || ""), actionDetail: String(aNext.actionDetail || "") });
|
|
6260
|
+
}
|
|
6261
|
+
details.forEach((d, seq) => {
|
|
6262
|
+
const stage = markStage(d.actionDetail);
|
|
6263
|
+
const node = {
|
|
6264
|
+
sessionId,
|
|
6265
|
+
seq,
|
|
6266
|
+
actionType: d.actionType,
|
|
6267
|
+
actionDetail: d.actionDetail,
|
|
6268
|
+
stage,
|
|
6269
|
+
resources: extractResources(d.actionDetail)
|
|
6270
|
+
};
|
|
6271
|
+
nodes.push(node);
|
|
6272
|
+
if (stage) stageFlags.add(stage);
|
|
6273
|
+
});
|
|
6274
|
+
for (let i = 0; i < nodes.length - 1; i++) {
|
|
6275
|
+
edges.push({ src: i, dst: i + 1, kind: "next" });
|
|
6276
|
+
}
|
|
6277
|
+
return { sessionId, nodes, edges, stageFlags };
|
|
6278
|
+
}
|
|
6279
|
+
function buildMultiSessionGraph(sessions, aNext) {
|
|
6280
|
+
const mg = { sessions: [], crossEdges: [] };
|
|
6281
|
+
if (!sessions || sessions.length === 0) {
|
|
6282
|
+
mg.sessions.push(buildSessionGraph([], aNext, "current"));
|
|
6283
|
+
return mg;
|
|
6284
|
+
}
|
|
6285
|
+
for (const s of sessions) {
|
|
6286
|
+
const sid = String(s.sessionId || "s");
|
|
6287
|
+
mg.sessions.push(buildSessionGraph(s.actionHistory || [], void 0, sid));
|
|
6288
|
+
}
|
|
6289
|
+
if (aNext) {
|
|
6290
|
+
const last = mg.sessions[mg.sessions.length - 1];
|
|
6291
|
+
if (last.sessionId === "current") {
|
|
6292
|
+
const stage = markStage(String(aNext.actionDetail || ""));
|
|
6293
|
+
last.nodes.push({
|
|
6294
|
+
sessionId: last.sessionId,
|
|
6295
|
+
seq: last.nodes.length,
|
|
6296
|
+
actionType: String(aNext.actionType || ""),
|
|
6297
|
+
actionDetail: String(aNext.actionDetail || ""),
|
|
6298
|
+
stage,
|
|
6299
|
+
resources: extractResources(String(aNext.actionDetail || ""))
|
|
6300
|
+
});
|
|
6301
|
+
if (last.nodes.length > 1) {
|
|
6302
|
+
last.edges.push({ src: last.nodes.length - 2, dst: last.nodes.length - 1, kind: "next" });
|
|
6303
|
+
}
|
|
6304
|
+
} else {
|
|
6305
|
+
mg.sessions.push(buildSessionGraph([], aNext, "current"));
|
|
6306
|
+
}
|
|
6307
|
+
}
|
|
6308
|
+
addCrossDataEdges(mg);
|
|
6309
|
+
return mg;
|
|
6310
|
+
}
|
|
6311
|
+
function addCrossDataEdges(mg) {
|
|
6312
|
+
const nodes = allNodesFlat(mg);
|
|
6313
|
+
if (nodes.length < 2) return;
|
|
6314
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
6315
|
+
for (let j = i + 1; j < nodes.length; j++) {
|
|
6316
|
+
if (nodes[i].sessionId === nodes[j].sessionId) continue;
|
|
6317
|
+
const shared = intersect(nodes[i].resources, nodes[j].resources);
|
|
6318
|
+
if (shared.size > 0) {
|
|
6319
|
+
mg.crossEdges.push({ src: i, dst: j, kind: "data" });
|
|
6320
|
+
}
|
|
6321
|
+
}
|
|
6322
|
+
}
|
|
6323
|
+
}
|
|
6324
|
+
function allNodesFlat(mg) {
|
|
6325
|
+
const out = [];
|
|
6326
|
+
for (const s of mg.sessions) out.push(...s.nodes);
|
|
6327
|
+
return out;
|
|
6328
|
+
}
|
|
6329
|
+
function intersect(a, b) {
|
|
6330
|
+
const out = /* @__PURE__ */ new Set();
|
|
6331
|
+
for (const v of a) if (b.has(v)) out.add(v);
|
|
6332
|
+
return out;
|
|
6333
|
+
}
|
|
6334
|
+
var COMBO_STAGES2, PATH_RE;
|
|
6335
|
+
var init_builder = __esm({
|
|
6336
|
+
"src/engine/formal-engine/builder.ts"() {
|
|
6337
|
+
"use strict";
|
|
6338
|
+
init_esm_shims();
|
|
6339
|
+
init_rules_loader();
|
|
6340
|
+
init_decode();
|
|
6341
|
+
COMBO_STAGES2 = null;
|
|
6342
|
+
PATH_RE = /(?:^|\s|[@=])((?:\/|~\/)[\w./~-]+)/g;
|
|
6343
|
+
}
|
|
6344
|
+
});
|
|
6345
|
+
|
|
6346
|
+
// src/engine/formal-engine/ir.ts
|
|
6347
|
+
function allNodes(mg) {
|
|
6348
|
+
const out = [];
|
|
6349
|
+
for (const s of mg.sessions) out.push(...s.nodes);
|
|
6350
|
+
return out;
|
|
6351
|
+
}
|
|
6352
|
+
function allEdges(mg) {
|
|
6353
|
+
const out = [];
|
|
6354
|
+
for (const s of mg.sessions) out.push(...s.edges);
|
|
6355
|
+
out.push(...mg.crossEdges);
|
|
6356
|
+
return out;
|
|
6357
|
+
}
|
|
6358
|
+
function emptyVerificationResult() {
|
|
6359
|
+
return { verified: true, violations: [] };
|
|
6360
|
+
}
|
|
6361
|
+
var init_ir = __esm({
|
|
6362
|
+
"src/engine/formal-engine/ir.ts"() {
|
|
6363
|
+
"use strict";
|
|
6364
|
+
init_esm_shims();
|
|
6365
|
+
}
|
|
6366
|
+
});
|
|
6367
|
+
|
|
6368
|
+
// src/engine/formal-engine/properties.ts
|
|
6369
|
+
function loadCrossSessionChains() {
|
|
6370
|
+
if (CROSS_SESSION_CHAINS) return CROSS_SESSION_CHAINS;
|
|
6371
|
+
const combo = loadL2().combo_chains || {};
|
|
6372
|
+
CROSS_SESSION_CHAINS = (combo.chains || []).map((c) => ({
|
|
6373
|
+
id: String(c.id || "combo_chain"),
|
|
6374
|
+
stages: c.stages || [],
|
|
6375
|
+
riskLevel: String(c.riskLevel || c.risk_level || "high"),
|
|
6376
|
+
reason: "\u8DE8\u4F1A\u8BDD" + String(c.reason || "\u7EC4\u5408\u653B\u51FB\u94FE")
|
|
6377
|
+
}));
|
|
6378
|
+
return CROSS_SESSION_CHAINS;
|
|
6379
|
+
}
|
|
6380
|
+
function checkCrossSessionChain(graph) {
|
|
6381
|
+
const violations = [];
|
|
6382
|
+
const nodes = allNodes(graph);
|
|
6383
|
+
if (nodes.length === 0) return violations;
|
|
6384
|
+
for (const chain of loadCrossSessionChains()) {
|
|
6385
|
+
const stages = chain.stages;
|
|
6386
|
+
if (!stages.length) continue;
|
|
6387
|
+
let matched = 0;
|
|
6388
|
+
let stageIdx = 0;
|
|
6389
|
+
const path6 = [];
|
|
6390
|
+
for (const n of nodes) {
|
|
6391
|
+
if (stageIdx < stages.length && n.stage === stages[stageIdx]) {
|
|
6392
|
+
matched += 1;
|
|
6393
|
+
path6.push({ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) });
|
|
6394
|
+
stageIdx += 1;
|
|
6395
|
+
if (matched >= stages.length) break;
|
|
6396
|
+
}
|
|
6397
|
+
}
|
|
6398
|
+
if (matched >= stages.length) {
|
|
6399
|
+
violations.push({
|
|
6400
|
+
propertyId: "cross-" + chain.id,
|
|
6401
|
+
reason: chain.reason,
|
|
6402
|
+
riskLevel: chain.riskLevel,
|
|
6403
|
+
path: path6
|
|
6404
|
+
});
|
|
6405
|
+
}
|
|
6406
|
+
}
|
|
6407
|
+
return violations;
|
|
6408
|
+
}
|
|
6409
|
+
function checkSensitiveDataFlow(graph) {
|
|
6410
|
+
const violations = [];
|
|
6411
|
+
const nodes = allNodes(graph);
|
|
6412
|
+
if (nodes.length === 0) return violations;
|
|
6413
|
+
const tainted = /* @__PURE__ */ new Map();
|
|
6414
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
6415
|
+
const n = nodes[i];
|
|
6416
|
+
if (n.stage === "CRED" && [...n.resources].some((r) => CRED_FILE_RE.test(r))) {
|
|
6417
|
+
tainted.set(i, `${n.sessionId}#${n.seq} \u8BBF\u95EE ${[...n.resources][0]}`);
|
|
6418
|
+
}
|
|
6419
|
+
}
|
|
6420
|
+
if (tainted.size === 0) return violations;
|
|
6421
|
+
const edges = allEdges(graph);
|
|
6422
|
+
let changed = true;
|
|
6423
|
+
while (changed) {
|
|
6424
|
+
changed = false;
|
|
6425
|
+
for (const e of edges) {
|
|
6426
|
+
if (tainted.has(e.src) && !tainted.has(e.dst)) {
|
|
6427
|
+
const srcRes = nodes[e.src].resources;
|
|
6428
|
+
const dstRes = nodes[e.dst].resources;
|
|
6429
|
+
for (const r of srcRes) {
|
|
6430
|
+
if (dstRes.has(r)) {
|
|
6431
|
+
tainted.set(e.dst, tainted.get(e.src));
|
|
6432
|
+
changed = true;
|
|
6433
|
+
break;
|
|
6434
|
+
}
|
|
6435
|
+
}
|
|
6436
|
+
}
|
|
6437
|
+
}
|
|
6438
|
+
}
|
|
6439
|
+
for (const [i, n] of nodes.entries()) {
|
|
6440
|
+
if (tainted.has(i) && EXFIL_RE.test(n.actionDetail)) {
|
|
6441
|
+
violations.push({
|
|
6442
|
+
propertyId: "sensitive-data-exfil",
|
|
6443
|
+
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`,
|
|
6444
|
+
riskLevel: "critical",
|
|
6445
|
+
path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
|
|
6446
|
+
});
|
|
6447
|
+
}
|
|
6448
|
+
}
|
|
6449
|
+
return violations;
|
|
6450
|
+
}
|
|
6451
|
+
function isExfilNode(n) {
|
|
6452
|
+
if (n.stage === "EXFIL") return true;
|
|
6453
|
+
if (EXFIL_RE.test(n.actionDetail) && EXFIL_ACTION_RE.test(n.actionDetail)) return true;
|
|
6454
|
+
if (EBPF_NET_RE.test(n.actionDetail) && REMOTE_RE.test(n.actionDetail)) return true;
|
|
6455
|
+
return false;
|
|
6456
|
+
}
|
|
6457
|
+
function isCredNode(n) {
|
|
6458
|
+
if (n.stage !== "CRED") return false;
|
|
6459
|
+
for (const r of n.resources) {
|
|
6460
|
+
if (CRED_STRICT_RE.test(r)) return true;
|
|
6461
|
+
}
|
|
6462
|
+
return CRED_STRICT_RE.test(n.actionDetail);
|
|
6463
|
+
}
|
|
6464
|
+
function isPrivescNode(n) {
|
|
6465
|
+
if (n.stage === "PRIVESC") return true;
|
|
6466
|
+
return PRIVESC_RE.test(n.actionDetail);
|
|
6467
|
+
}
|
|
6468
|
+
function checkNeverExfilAfterPrivesc(graph) {
|
|
6469
|
+
const violations = [];
|
|
6470
|
+
const nodes = allNodes(graph);
|
|
6471
|
+
if (nodes.length < 2) return violations;
|
|
6472
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
6473
|
+
if (!isPrivescNode(nodes[i])) continue;
|
|
6474
|
+
for (let j = i + 1; j < nodes.length; j++) {
|
|
6475
|
+
if (isExfilNode(nodes[j])) {
|
|
6476
|
+
violations.push({
|
|
6477
|
+
propertyId: "no-privesc-then-exfil",
|
|
6478
|
+
reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
|
|
6479
|
+
riskLevel: "critical",
|
|
6480
|
+
path: [
|
|
6481
|
+
{ sessionId: nodes[i].sessionId, seq: nodes[i].seq, detail: nodes[i].actionDetail.slice(0, 60) },
|
|
6482
|
+
{ sessionId: nodes[j].sessionId, seq: nodes[j].seq, detail: nodes[j].actionDetail.slice(0, 60) }
|
|
6483
|
+
]
|
|
6484
|
+
});
|
|
6485
|
+
return violations;
|
|
6486
|
+
}
|
|
6487
|
+
}
|
|
6488
|
+
}
|
|
6489
|
+
return violations;
|
|
6490
|
+
}
|
|
6491
|
+
function checkNeverExfilAfterCred(graph) {
|
|
6492
|
+
const violations = [];
|
|
6493
|
+
const nodes = allNodes(graph);
|
|
6494
|
+
if (nodes.length < 2) return violations;
|
|
6495
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
6496
|
+
if (!isCredNode(nodes[i])) continue;
|
|
6497
|
+
for (let j = i + 1; j < nodes.length; j++) {
|
|
6498
|
+
if (isExfilNode(nodes[j])) {
|
|
6499
|
+
violations.push({
|
|
6500
|
+
propertyId: "no-cred-then-exfil",
|
|
6501
|
+
reason: "\u8BFB\u53D6\u51ED\u636E\u540E\u51FA\u73B0\u5916\u4F20\uFF0C\u8FDD\u53CD \u25A1(CRED\u2192\xAC\u25C7EXFIL)",
|
|
6502
|
+
riskLevel: "critical",
|
|
6503
|
+
path: [
|
|
6504
|
+
{ sessionId: nodes[i].sessionId, seq: nodes[i].seq, detail: nodes[i].actionDetail.slice(0, 60) },
|
|
6505
|
+
{ sessionId: nodes[j].sessionId, seq: nodes[j].seq, detail: nodes[j].actionDetail.slice(0, 60) }
|
|
6506
|
+
]
|
|
6507
|
+
});
|
|
6508
|
+
return violations;
|
|
6509
|
+
}
|
|
6510
|
+
}
|
|
6511
|
+
}
|
|
6512
|
+
return violations;
|
|
6513
|
+
}
|
|
6514
|
+
var CROSS_SESSION_CHAINS, CRED_FILE_RE, EXFIL_RE, EBPF_NET_RE, REMOTE_RE, PRIVESC_RE, EXFIL_ACTION_RE, CRED_STRICT_RE, ALL_PROPERTIES;
|
|
6515
|
+
var init_properties = __esm({
|
|
6516
|
+
"src/engine/formal-engine/properties.ts"() {
|
|
6517
|
+
"use strict";
|
|
6518
|
+
init_esm_shims();
|
|
6519
|
+
init_rules_loader();
|
|
6520
|
+
init_ir();
|
|
6521
|
+
CROSS_SESSION_CHAINS = null;
|
|
6522
|
+
CRED_FILE_RE = /(known_hosts|\.ssh|id_rsa|\/etc\/shadow|\/etc\/passwd|credentials)/i;
|
|
6523
|
+
EXFIL_RE = /https?:\/\/|scp\s+.*@|rsync\s+.*@/i;
|
|
6524
|
+
EBPF_NET_RE = /connect|sendto|network_connect/i;
|
|
6525
|
+
REMOTE_RE = /to\s+\d+\.\d+\.\d+\.\d+|remote_addr/i;
|
|
6526
|
+
PRIVESC_RE = /\bsudo\b|\bsu\b|chmod\s+u\s*\+\s*s|pkexec|setuid/i;
|
|
6527
|
+
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;
|
|
6528
|
+
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;
|
|
6529
|
+
ALL_PROPERTIES = [
|
|
6530
|
+
{ id: "cross-session-chain", check: checkCrossSessionChain },
|
|
6531
|
+
{ id: "sensitive-data-flow", check: checkSensitiveDataFlow },
|
|
6532
|
+
{ id: "no-privesc-then-exfil", check: checkNeverExfilAfterPrivesc },
|
|
6533
|
+
{ id: "no-cred-then-exfil", check: checkNeverExfilAfterCred }
|
|
6534
|
+
];
|
|
6535
|
+
}
|
|
6536
|
+
});
|
|
6537
|
+
|
|
6538
|
+
// src/engine/formal-engine/engine.ts
|
|
6539
|
+
function ebpfEventToDetail(e) {
|
|
6540
|
+
const parts = [];
|
|
6541
|
+
const syscall = String(e.syscall || e.op || "");
|
|
6542
|
+
const argv = String(e.argv || e.pathname || "");
|
|
6543
|
+
const remote = String(e.remote_addr || "");
|
|
6544
|
+
const port = e.remote_port;
|
|
6545
|
+
if (syscall) parts.push(syscall);
|
|
6546
|
+
if (argv && argv !== "" && argv !== "None") parts.push(argv);
|
|
6547
|
+
if (remote) parts.push("to " + remote + (port ? ":" + String(port) : ""));
|
|
6548
|
+
return parts.join(" ");
|
|
6549
|
+
}
|
|
6550
|
+
var FormalEngine;
|
|
6551
|
+
var init_engine = __esm({
|
|
6552
|
+
"src/engine/formal-engine/engine.ts"() {
|
|
6553
|
+
"use strict";
|
|
6554
|
+
init_esm_shims();
|
|
6555
|
+
init_builder();
|
|
6556
|
+
init_ir();
|
|
6557
|
+
init_properties();
|
|
6558
|
+
FormalEngine = class {
|
|
6559
|
+
properties;
|
|
6560
|
+
constructor(properties) {
|
|
6561
|
+
this.properties = properties && properties.length > 0 ? properties : ALL_PROPERTIES;
|
|
6562
|
+
}
|
|
6563
|
+
verify(sessions, aNext, ebpfEvents) {
|
|
6564
|
+
const all = sessions ? [...sessions] : [];
|
|
6565
|
+
if (ebpfEvents && ebpfEvents.length > 0) {
|
|
6566
|
+
all.push({
|
|
6567
|
+
sessionId: "ebpf-evidence",
|
|
6568
|
+
actionHistory: ebpfEvents.map((e) => ({
|
|
6569
|
+
actionType: "syscall",
|
|
6570
|
+
actionDetail: ebpfEventToDetail(e)
|
|
6571
|
+
}))
|
|
6572
|
+
});
|
|
6573
|
+
}
|
|
6574
|
+
const graph = buildMultiSessionGraph(all, aNext);
|
|
6575
|
+
const result = emptyVerificationResult();
|
|
6576
|
+
for (const prop of this.properties) {
|
|
6577
|
+
let violations;
|
|
6578
|
+
try {
|
|
6579
|
+
violations = prop.check(graph);
|
|
6580
|
+
} catch (e) {
|
|
6581
|
+
result.verified = false;
|
|
6582
|
+
result.violations.push({
|
|
6583
|
+
propertyId: prop.id,
|
|
6584
|
+
reason: `\u5F62\u5F0F\u5316\u6027\u8D28\u9A8C\u8BC1\u5F02\u5E38\uFF0C\u4FDD\u5B88\u62E6\u622A: ${e}`,
|
|
6585
|
+
riskLevel: "medium",
|
|
6586
|
+
path: []
|
|
6587
|
+
});
|
|
6588
|
+
return result;
|
|
6589
|
+
}
|
|
6590
|
+
if (violations.length > 0) {
|
|
6591
|
+
result.verified = false;
|
|
6592
|
+
result.violations.push(...violations);
|
|
6593
|
+
break;
|
|
6594
|
+
}
|
|
6595
|
+
}
|
|
6596
|
+
return result;
|
|
6597
|
+
}
|
|
6598
|
+
};
|
|
6599
|
+
}
|
|
6600
|
+
});
|
|
6601
|
+
|
|
6602
|
+
// src/engine/formal-engine/stateful.ts
|
|
6603
|
+
function loadRules() {
|
|
6604
|
+
if (STAGES_PATTERNS !== null && CHAINS !== null) {
|
|
6605
|
+
return { stages: STAGES_PATTERNS, chains: CHAINS };
|
|
6606
|
+
}
|
|
6607
|
+
const combo = loadL2().combo_chains || {};
|
|
6608
|
+
const stages = {};
|
|
6609
|
+
for (const [name, pats] of Object.entries(combo.stages || {})) {
|
|
6610
|
+
stages[name] = pats.map((p) => new RegExp(p, "i"));
|
|
6611
|
+
}
|
|
6612
|
+
const chains = combo.chains || [];
|
|
6613
|
+
STAGES_PATTERNS = stages;
|
|
6614
|
+
CHAINS = chains;
|
|
6615
|
+
return { stages, chains };
|
|
6616
|
+
}
|
|
6617
|
+
function markStage2(actionDetail) {
|
|
6618
|
+
const { stages } = loadRules();
|
|
6619
|
+
return matchStageAny(actionDetail, stages);
|
|
6620
|
+
}
|
|
6621
|
+
function extractResources2(actionDetail) {
|
|
6622
|
+
const text = actionDetail.replace(/'[^']*'|"[^"]*"/g, "");
|
|
6623
|
+
const out = [];
|
|
6624
|
+
const re = /(?:^|\s|[@=])((?:\/|~\/)[\w./~-]+)/g;
|
|
6625
|
+
let m;
|
|
6626
|
+
while ((m = re.exec(text)) !== null) {
|
|
6627
|
+
const p = m[1];
|
|
6628
|
+
if (p.startsWith("/dev/") || p.includes("://")) continue;
|
|
6629
|
+
if (!out.includes(p)) out.push(p);
|
|
6630
|
+
}
|
|
6631
|
+
return out;
|
|
6632
|
+
}
|
|
6633
|
+
function lifecycleFor(detail, stage, isError) {
|
|
6634
|
+
if (isError) {
|
|
6635
|
+
if (stage === "LISTEN" || stage === "PERSIST" || NEVER_ACTIVATED_RE.test(detail)) return "never_activated";
|
|
6636
|
+
}
|
|
6637
|
+
if (DESTROY_RE.test(detail)) return "dead";
|
|
6638
|
+
if (stage === "LISTEN" || stage === "PERSIST") return "alive";
|
|
6639
|
+
if (detail.includes(">>") || detail.includes(">")) return "alive";
|
|
6640
|
+
return "";
|
|
6641
|
+
}
|
|
6642
|
+
var DESTROY_RE, NEVER_ACTIVATED_RE, CRED_FILE_RE2, EXFIL_RE2, STAGES_PATTERNS, CHAINS, MAX_NODES, StatefulChainTracker;
|
|
6643
|
+
var init_stateful = __esm({
|
|
6644
|
+
"src/engine/formal-engine/stateful.ts"() {
|
|
6645
|
+
"use strict";
|
|
6646
|
+
init_esm_shims();
|
|
6647
|
+
init_rules_loader();
|
|
6648
|
+
init_decode();
|
|
6649
|
+
DESTROY_RE = /\b(?:pkill|kill)\b|systemctl\s+stop|service\s+stop|crontab\s+-r|rm\s+-[rf]?\s+/i;
|
|
6650
|
+
NEVER_ACTIVATED_RE = /\b(?:not\s+found|no\s+such\s+file|command\s+not\s+found|refused|failed\s+to\s+(?:start|bind|listen)|address\s+already\s+in\s+use)\b/i;
|
|
6651
|
+
CRED_FILE_RE2 = /(known_hosts|\.ssh|id_rsa|\/etc\/shadow|\/etc\/passwd|credentials)/i;
|
|
6652
|
+
EXFIL_RE2 = /https?:\/\/|scp\s+.*@|rsync\s+.*@/i;
|
|
6653
|
+
STAGES_PATTERNS = null;
|
|
6654
|
+
CHAINS = null;
|
|
6655
|
+
MAX_NODES = 1e5;
|
|
6656
|
+
StatefulChainTracker = class {
|
|
6657
|
+
agentId;
|
|
6658
|
+
nodes_ = [];
|
|
6659
|
+
ptr = {};
|
|
6660
|
+
tainted = /* @__PURE__ */ new Set();
|
|
6661
|
+
/** taint 反索引:资源路径 → 已染毒(O(1) 命中)。 */
|
|
6662
|
+
taintedResources = /* @__PURE__ */ new Set();
|
|
6663
|
+
aliveResources = {};
|
|
6664
|
+
seqNext = 0;
|
|
6665
|
+
constructor(agentId = "") {
|
|
6666
|
+
this.agentId = agentId;
|
|
6667
|
+
loadRules();
|
|
6668
|
+
}
|
|
6669
|
+
get nodes() {
|
|
6670
|
+
return this.nodes_;
|
|
6671
|
+
}
|
|
6672
|
+
get pendingPrefixes() {
|
|
6673
|
+
const { chains } = loadRules();
|
|
6674
|
+
const out = [];
|
|
6675
|
+
for (const chain of chains) {
|
|
6676
|
+
const stages = chain.stages || [];
|
|
6677
|
+
if (stages.length === 0) continue;
|
|
6678
|
+
const matched = this.ptr[String(chain.id || "")] || 0;
|
|
6679
|
+
if (matched > 0 && matched < stages.length) {
|
|
6680
|
+
const pf = {
|
|
6681
|
+
chainId: String(chain.id || ""),
|
|
6682
|
+
stages,
|
|
6683
|
+
matched,
|
|
6684
|
+
riskLevel: String(chain.riskLevel || chain.risk_level || "high"),
|
|
6685
|
+
get nextStage() {
|
|
6686
|
+
return this.matched < this.stages.length ? this.stages[this.matched] : "";
|
|
6687
|
+
}
|
|
6688
|
+
};
|
|
6689
|
+
out.push(pf);
|
|
4471
6690
|
}
|
|
4472
6691
|
}
|
|
4473
|
-
|
|
6692
|
+
return out;
|
|
6693
|
+
}
|
|
6694
|
+
get aliveResourcesSnapshot() {
|
|
6695
|
+
return { ...this.aliveResources };
|
|
6696
|
+
}
|
|
6697
|
+
/**
|
|
6698
|
+
* 全量/定期灌入历史 session(多 session 各动作追加到时间线)。
|
|
6699
|
+
* 历史节点同样推进链指针 / 生命周期 / taint,否则后续 advance 无法在历史基础上
|
|
6700
|
+
* 补全链。历史阶段推进若补全链,属于历史事实,不阻断当前动作。
|
|
6701
|
+
*/
|
|
6702
|
+
seed(sessions) {
|
|
6703
|
+
let added = 0;
|
|
6704
|
+
for (const s of sessions || []) {
|
|
6705
|
+
const sid = String(s.sessionId || "s");
|
|
6706
|
+
for (const a of s.actionHistory || []) {
|
|
6707
|
+
const node = this.appendNode(a, sid);
|
|
6708
|
+
if (!node) {
|
|
6709
|
+
continue;
|
|
6710
|
+
}
|
|
6711
|
+
added += 1;
|
|
6712
|
+
this.recordLifecycle(node);
|
|
6713
|
+
this.advanceTaint(node);
|
|
6714
|
+
this.advanceChains(node);
|
|
6715
|
+
}
|
|
6716
|
+
}
|
|
6717
|
+
return added;
|
|
6718
|
+
}
|
|
6719
|
+
/** 增量推进:把当前动作追加到时间线 → 链指针推进 → 判定。 */
|
|
6720
|
+
advance(args) {
|
|
6721
|
+
const { aNext, sessionId = "current", isError = false } = args;
|
|
6722
|
+
const node = this.appendNode(aNext, sessionId, isError);
|
|
6723
|
+
if (!node) {
|
|
6724
|
+
return { seq: this.seqNext, node: null, matched: null, pendingPrefixes: this.pendingPrefixes, exfil: [], shouldBlock: false };
|
|
6725
|
+
}
|
|
6726
|
+
const seq = this.seqNext;
|
|
6727
|
+
this.recordLifecycle(node);
|
|
6728
|
+
const matched = this.advanceChains(node);
|
|
6729
|
+
this.advanceTaint(node);
|
|
6730
|
+
const exfil = this.checkExfilAfterTaint(node);
|
|
4474
6731
|
return {
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
6732
|
+
seq,
|
|
6733
|
+
node,
|
|
6734
|
+
matched,
|
|
6735
|
+
pendingPrefixes: this.pendingPrefixes,
|
|
6736
|
+
exfil,
|
|
6737
|
+
get shouldBlock() {
|
|
6738
|
+
return this.matched !== null || this.exfil.length > 0;
|
|
6739
|
+
}
|
|
4479
6740
|
};
|
|
4480
6741
|
}
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
6742
|
+
appendNode(action, sessionId, isError = false) {
|
|
6743
|
+
const ad = String(action.actionDetail || "");
|
|
6744
|
+
const at = String(action.actionType || "");
|
|
6745
|
+
if (!ad) return null;
|
|
6746
|
+
if (this.nodes_.length >= MAX_NODES) {
|
|
6747
|
+
return null;
|
|
6748
|
+
}
|
|
6749
|
+
const stage = markStage2(ad);
|
|
6750
|
+
const node = {
|
|
6751
|
+
seq: this.seqNext,
|
|
6752
|
+
sessionId,
|
|
6753
|
+
actionType: at,
|
|
6754
|
+
actionDetail: ad,
|
|
6755
|
+
stage,
|
|
6756
|
+
resources: extractResources2(ad),
|
|
6757
|
+
isError,
|
|
6758
|
+
lifecycle: lifecycleFor(ad, stage, isError)
|
|
6759
|
+
};
|
|
6760
|
+
this.nodes_.push(node);
|
|
6761
|
+
this.seqNext += 1;
|
|
6762
|
+
return node;
|
|
6763
|
+
}
|
|
6764
|
+
recordLifecycle(node) {
|
|
6765
|
+
if (node.lifecycle === "dead") {
|
|
6766
|
+
let hit = false;
|
|
6767
|
+
for (const r of node.resources) {
|
|
6768
|
+
if (r in this.aliveResources) {
|
|
6769
|
+
delete this.aliveResources[r];
|
|
6770
|
+
hit = true;
|
|
6771
|
+
}
|
|
6772
|
+
}
|
|
6773
|
+
const detail = node.actionDetail;
|
|
6774
|
+
for (const r of Object.keys(this.aliveResources)) {
|
|
6775
|
+
const base = r.split("/").pop() || "";
|
|
6776
|
+
if (base && detail.includes(base)) {
|
|
6777
|
+
delete this.aliveResources[r];
|
|
6778
|
+
hit = true;
|
|
6779
|
+
}
|
|
6780
|
+
}
|
|
6781
|
+
if (hit) this.resetChainsOnDestroy();
|
|
6782
|
+
return;
|
|
6783
|
+
}
|
|
6784
|
+
if (node.lifecycle === "alive") {
|
|
6785
|
+
for (const r of node.resources) {
|
|
6786
|
+
this.aliveResources[r] = node.seq;
|
|
6787
|
+
}
|
|
6788
|
+
}
|
|
6789
|
+
}
|
|
6790
|
+
resetChainsOnDestroy() {
|
|
6791
|
+
for (const cid of Object.keys(this.ptr)) {
|
|
6792
|
+
this.ptr[cid] = 0;
|
|
6793
|
+
}
|
|
6794
|
+
}
|
|
6795
|
+
advanceChains(node) {
|
|
6796
|
+
if (node.lifecycle === "dead") return null;
|
|
6797
|
+
if (node.lifecycle === "never_activated") {
|
|
6798
|
+
this.invalidateChainOnFailure(node);
|
|
6799
|
+
return null;
|
|
6800
|
+
}
|
|
6801
|
+
const stage = node.stage;
|
|
6802
|
+
if (!stage) return null;
|
|
6803
|
+
const { chains } = loadRules();
|
|
6804
|
+
for (const chain of chains) {
|
|
6805
|
+
const stages = chain.stages || [];
|
|
6806
|
+
if (stages.length === 0) continue;
|
|
6807
|
+
const cid = String(chain.id || "");
|
|
6808
|
+
const cur = this.ptr[cid] || 0;
|
|
6809
|
+
if (cur < stages.length && stages[cur] === stage) {
|
|
6810
|
+
this.ptr[cid] = cur + 1;
|
|
6811
|
+
if (cur + 1 >= stages.length) {
|
|
6812
|
+
return {
|
|
6813
|
+
chainId: cid,
|
|
6814
|
+
stages,
|
|
6815
|
+
riskLevel: String(chain.riskLevel || chain.risk_level || "high"),
|
|
6816
|
+
reason: String(chain.reason || "\u68C0\u6D4B\u5230\u7EC4\u5408\u653B\u51FB\u94FE"),
|
|
6817
|
+
path: this.chainPath(stages)
|
|
6818
|
+
};
|
|
6819
|
+
}
|
|
6820
|
+
}
|
|
6821
|
+
}
|
|
6822
|
+
return null;
|
|
6823
|
+
}
|
|
6824
|
+
invalidateChainOnFailure(node) {
|
|
6825
|
+
const failed = new Set(node.resources);
|
|
6826
|
+
if (failed.size === 0) return;
|
|
6827
|
+
const { chains } = loadRules();
|
|
6828
|
+
for (const chain of chains) {
|
|
6829
|
+
const cid = String(chain.id || "");
|
|
6830
|
+
if ((this.ptr[cid] || 0) > 0) {
|
|
6831
|
+
this.ptr[cid] = 0;
|
|
6832
|
+
}
|
|
6833
|
+
}
|
|
6834
|
+
}
|
|
6835
|
+
chainPath(stages) {
|
|
6836
|
+
const path6 = [];
|
|
6837
|
+
let want = stages[0];
|
|
6838
|
+
for (const n of this.nodes_) {
|
|
6839
|
+
if (n.stage === want) {
|
|
6840
|
+
path6.push({ sessionId: n.sessionId, seq: n.seq, stage: n.stage });
|
|
6841
|
+
const idx = stages.indexOf(n.stage);
|
|
6842
|
+
if (idx + 1 < stages.length) {
|
|
6843
|
+
want = stages[idx + 1];
|
|
6844
|
+
} else {
|
|
6845
|
+
break;
|
|
6846
|
+
}
|
|
6847
|
+
}
|
|
6848
|
+
}
|
|
6849
|
+
return path6;
|
|
6850
|
+
}
|
|
6851
|
+
// ---------------- 敏感数据 taint(P2) ----------------
|
|
6852
|
+
advanceTaint(node) {
|
|
6853
|
+
if (node.lifecycle === "dead") return;
|
|
6854
|
+
const idx = node.seq;
|
|
6855
|
+
const taintedRes = /* @__PURE__ */ new Set();
|
|
6856
|
+
for (const r of node.resources) {
|
|
6857
|
+
if (this.resourceTainted(r)) taintedRes.add(r);
|
|
6858
|
+
}
|
|
6859
|
+
if (node.stage === "CRED" && node.resources.some((r) => CRED_FILE_RE2.test(r))) {
|
|
6860
|
+
this.tainted.add(idx);
|
|
6861
|
+
for (const r of node.resources) taintedRes.add(r);
|
|
6862
|
+
}
|
|
6863
|
+
if (taintedRes.size > 0) {
|
|
6864
|
+
this.tainted.add(idx);
|
|
6865
|
+
for (const r of taintedRes) this.taintedResources.add(r);
|
|
6866
|
+
}
|
|
6867
|
+
}
|
|
6868
|
+
resourceTainted(resource) {
|
|
6869
|
+
return this.taintedResources.has(resource);
|
|
6870
|
+
return false;
|
|
6871
|
+
}
|
|
6872
|
+
checkExfilAfterTaint(node) {
|
|
6873
|
+
if (!this.tainted.has(node.seq)) return [];
|
|
6874
|
+
if (EXFIL_RE2.test(node.actionDetail)) {
|
|
6875
|
+
return [{
|
|
6876
|
+
property_id: "sensitive-data-exfil",
|
|
6877
|
+
reason: `\u654F\u611F\u6570\u636E\u4F20\u64AD\u5916\u4F20\uFF1A${this.taintSource(node)} \u2192 ${node.sessionId}#${node.seq} \u7ECF\u5171\u4EAB\u8F7D\u4F53\u5916\u53D1`,
|
|
6878
|
+
risk_level: "critical",
|
|
6879
|
+
path: [[node.sessionId, node.seq, node.actionDetail.slice(0, 60)]]
|
|
6880
|
+
}];
|
|
6881
|
+
}
|
|
6882
|
+
return [];
|
|
6883
|
+
}
|
|
6884
|
+
taintSource(node) {
|
|
6885
|
+
const sorted = [...this.tainted].sort((a, b) => a - b);
|
|
6886
|
+
for (const i of sorted) {
|
|
6887
|
+
if (i === node.seq) continue;
|
|
6888
|
+
const n = this.nodes_[i];
|
|
6889
|
+
if (!n) continue;
|
|
6890
|
+
for (const r of node.resources) {
|
|
6891
|
+
if (n.resources.includes(r)) return `${n.sessionId}#${n.seq} \u8BBF\u95EE ${r}`;
|
|
6892
|
+
}
|
|
6893
|
+
}
|
|
6894
|
+
return "\u672A\u77E5\u51ED\u636E\u6E90";
|
|
6895
|
+
}
|
|
6896
|
+
// ---------------- 序列化/状态 ----------------
|
|
6897
|
+
toSnapshot() {
|
|
6898
|
+
return {
|
|
6899
|
+
agent_id: this.agentId,
|
|
6900
|
+
nodes: this.nodes_.map((n) => ({ ...n, resources: [...n.resources] })),
|
|
6901
|
+
ptrs: { ...this.ptr },
|
|
6902
|
+
tainted: [...this.tainted].sort((a, b) => a - b),
|
|
6903
|
+
alive: { ...this.aliveResources },
|
|
6904
|
+
seq_next: this.seqNext
|
|
6905
|
+
};
|
|
6906
|
+
}
|
|
6907
|
+
restore(snapshot) {
|
|
6908
|
+
if (!snapshot) return;
|
|
6909
|
+
this.agentId = String(snapshot.agent_id || this.agentId);
|
|
6910
|
+
for (const raw of snapshot.nodes || []) {
|
|
6911
|
+
this.nodes_.push({
|
|
6912
|
+
seq: Number(raw.seq || 0),
|
|
6913
|
+
sessionId: String(raw.sessionId || ""),
|
|
6914
|
+
actionType: String(raw.actionType || ""),
|
|
6915
|
+
actionDetail: String(raw.actionDetail || ""),
|
|
6916
|
+
stage: String(raw.stage || ""),
|
|
6917
|
+
resources: [...raw.resources || []],
|
|
6918
|
+
isError: Boolean(raw.isError),
|
|
6919
|
+
lifecycle: String(raw.lifecycle || "")
|
|
6920
|
+
});
|
|
6921
|
+
}
|
|
6922
|
+
this.seqNext = Number(snapshot.seq_next || this.nodes_.length);
|
|
6923
|
+
this.ptr = Object.fromEntries(Object.entries(snapshot.ptrs || {}).map(([k, v]) => [k, Number(v)]));
|
|
6924
|
+
this.tainted = new Set((snapshot.tainted || []).map(Number));
|
|
6925
|
+
this.taintedResources = /* @__PURE__ */ new Set();
|
|
6926
|
+
for (const i of this.tainted) {
|
|
6927
|
+
const n = this.nodes_[i];
|
|
6928
|
+
if (n) for (const r of n.resources) this.taintedResources.add(r);
|
|
6929
|
+
}
|
|
6930
|
+
this.aliveResources = Object.fromEntries(
|
|
6931
|
+
Object.entries(snapshot.alive || {}).map(([k, v]) => [k, Number(v)])
|
|
6932
|
+
);
|
|
6933
|
+
}
|
|
6934
|
+
};
|
|
4484
6935
|
}
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
6936
|
+
});
|
|
6937
|
+
|
|
6938
|
+
// src/engine/formal-engine/chain_state.ts
|
|
6939
|
+
import * as fs4 from "fs";
|
|
6940
|
+
import * as os2 from "os";
|
|
6941
|
+
import * as path4 from "path";
|
|
6942
|
+
import { createHash } from "crypto";
|
|
6943
|
+
function defaultStateDbPath() {
|
|
6944
|
+
const dir = process.env.AGENT_MOSS_STATE_DIR || path4.join(os2.homedir(), ".agentmoss");
|
|
6945
|
+
const tag = createHash("sha1").update("agentmoss-stateful").digest("hex").slice(0, 8);
|
|
6946
|
+
return path4.join(dir, `stateful_chains_${tag}.json`);
|
|
6947
|
+
}
|
|
6948
|
+
var ChainStateStore;
|
|
6949
|
+
var init_chain_state = __esm({
|
|
6950
|
+
"src/engine/formal-engine/chain_state.ts"() {
|
|
6951
|
+
"use strict";
|
|
6952
|
+
init_esm_shims();
|
|
6953
|
+
init_stateful();
|
|
6954
|
+
ChainStateStore = class {
|
|
6955
|
+
filePath;
|
|
6956
|
+
data;
|
|
6957
|
+
_timer = null;
|
|
6958
|
+
_flushDelayMs = 50;
|
|
6959
|
+
constructor(filePath = "") {
|
|
6960
|
+
this.filePath = filePath || defaultStateDbPath();
|
|
6961
|
+
this.data = this._read();
|
|
6962
|
+
}
|
|
6963
|
+
_read() {
|
|
6964
|
+
try {
|
|
6965
|
+
if (fs4.existsSync(this.filePath)) {
|
|
6966
|
+
const raw = fs4.readFileSync(this.filePath, "utf-8");
|
|
6967
|
+
return JSON.parse(raw);
|
|
6968
|
+
}
|
|
6969
|
+
} catch {
|
|
6970
|
+
}
|
|
6971
|
+
return {};
|
|
6972
|
+
}
|
|
6973
|
+
/** 防抖写入:save() 只改内存,后台批量落盘(避免每次 tool call 全量 writeFileSync)。 */
|
|
6974
|
+
_scheduleWrite() {
|
|
6975
|
+
if (this._timer !== null) return;
|
|
6976
|
+
this._timer = setTimeout(() => {
|
|
6977
|
+
this._timer = null;
|
|
6978
|
+
try {
|
|
6979
|
+
fs4.mkdirSync(path4.dirname(this.filePath), { recursive: true });
|
|
6980
|
+
fs4.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2), "utf-8");
|
|
6981
|
+
} catch {
|
|
6982
|
+
}
|
|
6983
|
+
}, this._flushDelayMs);
|
|
6984
|
+
}
|
|
6985
|
+
/** 立即落盘(测试/关停前调用,保证状态不丢)。 */
|
|
6986
|
+
flush() {
|
|
6987
|
+
if (this._timer !== null) {
|
|
6988
|
+
clearTimeout(this._timer);
|
|
6989
|
+
this._timer = null;
|
|
6990
|
+
}
|
|
6991
|
+
try {
|
|
6992
|
+
fs4.mkdirSync(path4.dirname(this.filePath), { recursive: true });
|
|
6993
|
+
fs4.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2), "utf-8");
|
|
6994
|
+
} catch {
|
|
6995
|
+
}
|
|
6996
|
+
}
|
|
6997
|
+
load(agentId) {
|
|
6998
|
+
const entry = this.data[agentId];
|
|
6999
|
+
if (!entry) return null;
|
|
7000
|
+
const t = new StatefulChainTracker(agentId);
|
|
7001
|
+
t.restore(entry.state);
|
|
7002
|
+
return t;
|
|
7003
|
+
}
|
|
7004
|
+
save(tracker) {
|
|
7005
|
+
this.data[tracker.agentId] = {
|
|
7006
|
+
state: tracker.toSnapshot(),
|
|
7007
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
7008
|
+
};
|
|
7009
|
+
this._scheduleWrite();
|
|
7010
|
+
}
|
|
7011
|
+
delete(agentId) {
|
|
7012
|
+
delete this.data[agentId];
|
|
7013
|
+
this._scheduleWrite();
|
|
7014
|
+
}
|
|
7015
|
+
applyAction(agentId, aNext, sessionId, isError = false) {
|
|
7016
|
+
const t = this.load(agentId) || new StatefulChainTracker(agentId);
|
|
7017
|
+
const r = t.advance({ aNext, sessionId, isError });
|
|
7018
|
+
this.save(t);
|
|
7019
|
+
return r;
|
|
7020
|
+
}
|
|
7021
|
+
seedAndApply(agentId, sessions, aNext, sessionId = "current", isError = false) {
|
|
7022
|
+
const t = this.load(agentId) || new StatefulChainTracker(agentId);
|
|
7023
|
+
t.seed(sessions);
|
|
7024
|
+
const r = t.advance({ aNext, sessionId, isError });
|
|
7025
|
+
this.save(t);
|
|
7026
|
+
return r;
|
|
7027
|
+
}
|
|
7028
|
+
};
|
|
4501
7029
|
}
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
7030
|
+
});
|
|
7031
|
+
|
|
7032
|
+
// src/engine/formal-engine/index.ts
|
|
7033
|
+
var init_formal_engine = __esm({
|
|
7034
|
+
"src/engine/formal-engine/index.ts"() {
|
|
4506
7035
|
"use strict";
|
|
4507
7036
|
init_esm_shims();
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
7037
|
+
init_engine();
|
|
7038
|
+
init_ir();
|
|
7039
|
+
init_builder();
|
|
7040
|
+
init_properties();
|
|
7041
|
+
init_stateful();
|
|
7042
|
+
init_chain_state();
|
|
4511
7043
|
}
|
|
4512
7044
|
});
|
|
4513
7045
|
|
|
@@ -4604,7 +7136,10 @@ async function runAnalysis(input) {
|
|
|
4604
7136
|
const actionType = input.aNext.actionType.toLowerCase();
|
|
4605
7137
|
const rawDetail = extractRawCommand(input.aNext.actionDetail);
|
|
4606
7138
|
const lowerDetail = rawDetail.toLowerCase();
|
|
4607
|
-
const agentExt =
|
|
7139
|
+
const agentExt = mergeAgentExtensions(
|
|
7140
|
+
getAgentExtensions(input.agentId),
|
|
7141
|
+
getScenarioExtensions(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "")
|
|
7142
|
+
);
|
|
4608
7143
|
if (FULLY_SAFE_TOOLS.has(actionType)) {
|
|
4609
7144
|
return {
|
|
4610
7145
|
allowed: true,
|
|
@@ -4709,6 +7244,28 @@ async function runAnalysis(input) {
|
|
|
4709
7244
|
return effectiveResult;
|
|
4710
7245
|
}
|
|
4711
7246
|
}
|
|
7247
|
+
if (isLogicRulesEnabled()) {
|
|
7248
|
+
const formalSessions = [];
|
|
7249
|
+
if (input.sessionHistory) {
|
|
7250
|
+
for (const s of input.sessionHistory) {
|
|
7251
|
+
formalSessions.push({ sessionId: s.sessionId || "s", actionHistory: s.actionHistory || [] });
|
|
7252
|
+
}
|
|
7253
|
+
}
|
|
7254
|
+
formalSessions.push({ sessionId: "current", actionHistory: input.actionHistory });
|
|
7255
|
+
const formalResult = formalEngine.verify(formalSessions, input.aNext);
|
|
7256
|
+
if (!formalResult.verified) {
|
|
7257
|
+
const v = formalResult.violations[0];
|
|
7258
|
+
return {
|
|
7259
|
+
allowed: false,
|
|
7260
|
+
reason: v ? v.reason : "\u5F62\u5F0F\u5316\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF08\u884C\u4E3A\u6027\u8D28\u8FDD\u53CD\uFF09",
|
|
7261
|
+
riskLevel: v ? v.riskLevel : "high",
|
|
7262
|
+
riskType: "formal_verification",
|
|
7263
|
+
confidence: 90,
|
|
7264
|
+
source: "formal_engine",
|
|
7265
|
+
violatedLayers: ["2.4"]
|
|
7266
|
+
};
|
|
7267
|
+
}
|
|
7268
|
+
}
|
|
4712
7269
|
if (scriptChain && scriptChain.nodes.length > 0 && isLogicRulesEnabled()) {
|
|
4713
7270
|
for (const node of scriptChain.nodes) {
|
|
4714
7271
|
if (node.error || !node.preprocessedContent) continue;
|
|
@@ -4827,7 +7384,7 @@ async function runAnalysis(input) {
|
|
|
4827
7384
|
}
|
|
4828
7385
|
return llmResult;
|
|
4829
7386
|
}
|
|
4830
|
-
var FULLY_SAFE_TOOLS, TRUSTED_SKILLS_BASE, READONLY_SENSITIVE_TOOLS, FULLY_SAFE_BASH_PATTERNS, READONLY_SENSITIVE_BASH_PATTERNS, _brainStore, _brainExtractor, _brainInitialized;
|
|
7387
|
+
var FULLY_SAFE_TOOLS, TRUSTED_SKILLS_BASE, READONLY_SENSITIVE_TOOLS, FULLY_SAFE_BASH_PATTERNS, READONLY_SENSITIVE_BASH_PATTERNS, _brainStore, _brainExtractor, _brainInitialized, formalEngine;
|
|
4831
7388
|
var init_coordinator = __esm({
|
|
4832
7389
|
"src/engine/coordinator.ts"() {
|
|
4833
7390
|
"use strict";
|
|
@@ -4839,8 +7396,10 @@ var init_coordinator = __esm({
|
|
|
4839
7396
|
init_script_content_analyzer();
|
|
4840
7397
|
init_config();
|
|
4841
7398
|
init_agents();
|
|
7399
|
+
init_scenarios();
|
|
4842
7400
|
init_brain();
|
|
4843
7401
|
init_reviewer();
|
|
7402
|
+
init_formal_engine();
|
|
4844
7403
|
FULLY_SAFE_TOOLS = /* @__PURE__ */ new Set([
|
|
4845
7404
|
"ask_user_question",
|
|
4846
7405
|
"glob",
|
|
@@ -4910,6 +7469,7 @@ var init_coordinator = __esm({
|
|
|
4910
7469
|
_brainStore = null;
|
|
4911
7470
|
_brainExtractor = null;
|
|
4912
7471
|
_brainInitialized = false;
|
|
7472
|
+
formalEngine = new FormalEngine();
|
|
4913
7473
|
}
|
|
4914
7474
|
});
|
|
4915
7475
|
|
|
@@ -4970,20 +7530,20 @@ var require_file_uri_to_path = __commonJS({
|
|
|
4970
7530
|
var rest = decodeURI(uri.substring(7));
|
|
4971
7531
|
var firstSlash = rest.indexOf("/");
|
|
4972
7532
|
var host = rest.substring(0, firstSlash);
|
|
4973
|
-
var
|
|
7533
|
+
var path6 = rest.substring(firstSlash + 1);
|
|
4974
7534
|
if ("localhost" == host) host = "";
|
|
4975
7535
|
if (host) {
|
|
4976
7536
|
host = sep + sep + host;
|
|
4977
7537
|
}
|
|
4978
|
-
|
|
7538
|
+
path6 = path6.replace(/^(.+)\|/, "$1:");
|
|
4979
7539
|
if (sep == "\\") {
|
|
4980
|
-
|
|
7540
|
+
path6 = path6.replace(/\//g, "\\");
|
|
4981
7541
|
}
|
|
4982
|
-
if (/^.+\:/.test(
|
|
7542
|
+
if (/^.+\:/.test(path6)) {
|
|
4983
7543
|
} else {
|
|
4984
|
-
|
|
7544
|
+
path6 = sep + path6;
|
|
4985
7545
|
}
|
|
4986
|
-
return host +
|
|
7546
|
+
return host + path6;
|
|
4987
7547
|
}
|
|
4988
7548
|
}
|
|
4989
7549
|
});
|
|
@@ -4993,19 +7553,19 @@ var require_bindings = __commonJS({
|
|
|
4993
7553
|
"node_modules/bindings/bindings.js"(exports, module) {
|
|
4994
7554
|
"use strict";
|
|
4995
7555
|
init_esm_shims();
|
|
4996
|
-
var
|
|
4997
|
-
var
|
|
4998
|
-
var
|
|
4999
|
-
var
|
|
5000
|
-
var
|
|
5001
|
-
var exists =
|
|
7556
|
+
var fs7 = __require("fs");
|
|
7557
|
+
var path6 = __require("path");
|
|
7558
|
+
var fileURLToPath11 = require_file_uri_to_path();
|
|
7559
|
+
var join10 = path6.join;
|
|
7560
|
+
var dirname11 = path6.dirname;
|
|
7561
|
+
var exists = fs7.accessSync && function(path7) {
|
|
5002
7562
|
try {
|
|
5003
|
-
|
|
7563
|
+
fs7.accessSync(path7);
|
|
5004
7564
|
} catch (e) {
|
|
5005
7565
|
return false;
|
|
5006
7566
|
}
|
|
5007
7567
|
return true;
|
|
5008
|
-
} ||
|
|
7568
|
+
} || fs7.existsSync || path6.existsSync;
|
|
5009
7569
|
var defaults = {
|
|
5010
7570
|
arrow: process.env.NODE_BINDINGS_ARROW || " \u2192 ",
|
|
5011
7571
|
compiled: process.env.NODE_BINDINGS_COMPILED_DIR || "compiled",
|
|
@@ -5050,13 +7610,13 @@ var require_bindings = __commonJS({
|
|
|
5050
7610
|
if (!opts.module_root) {
|
|
5051
7611
|
opts.module_root = exports.getRoot(exports.getFileName());
|
|
5052
7612
|
}
|
|
5053
|
-
if (
|
|
7613
|
+
if (path6.extname(opts.bindings) != ".node") {
|
|
5054
7614
|
opts.bindings += ".node";
|
|
5055
7615
|
}
|
|
5056
7616
|
var requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
|
|
5057
7617
|
var tries = [], i = 0, l = opts.try.length, n, b, err;
|
|
5058
7618
|
for (; i < l; i++) {
|
|
5059
|
-
n =
|
|
7619
|
+
n = join10.apply(
|
|
5060
7620
|
null,
|
|
5061
7621
|
opts.try[i].map(function(p) {
|
|
5062
7622
|
return opts[p] || p;
|
|
@@ -5107,17 +7667,17 @@ var require_bindings = __commonJS({
|
|
|
5107
7667
|
Error.stackTraceLimit = origSTL;
|
|
5108
7668
|
var fileSchema = "file://";
|
|
5109
7669
|
if (fileName.indexOf(fileSchema) === 0) {
|
|
5110
|
-
fileName =
|
|
7670
|
+
fileName = fileURLToPath11(fileName);
|
|
5111
7671
|
}
|
|
5112
7672
|
return fileName;
|
|
5113
7673
|
};
|
|
5114
7674
|
exports.getRoot = function getRoot(file) {
|
|
5115
|
-
var dir =
|
|
7675
|
+
var dir = dirname11(file), prev;
|
|
5116
7676
|
while (true) {
|
|
5117
7677
|
if (dir === ".") {
|
|
5118
7678
|
dir = process.cwd();
|
|
5119
7679
|
}
|
|
5120
|
-
if (exists(
|
|
7680
|
+
if (exists(join10(dir, "package.json")) || exists(join10(dir, "node_modules"))) {
|
|
5121
7681
|
return dir;
|
|
5122
7682
|
}
|
|
5123
7683
|
if (prev === dir) {
|
|
@@ -5126,7 +7686,7 @@ var require_bindings = __commonJS({
|
|
|
5126
7686
|
);
|
|
5127
7687
|
}
|
|
5128
7688
|
prev = dir;
|
|
5129
|
-
dir =
|
|
7689
|
+
dir = join10(dir, "..");
|
|
5130
7690
|
}
|
|
5131
7691
|
};
|
|
5132
7692
|
}
|
|
@@ -5292,11 +7852,11 @@ var require_backup = __commonJS({
|
|
|
5292
7852
|
"node_modules/better-sqlite3/lib/methods/backup.js"(exports, module) {
|
|
5293
7853
|
"use strict";
|
|
5294
7854
|
init_esm_shims();
|
|
5295
|
-
var
|
|
5296
|
-
var
|
|
7855
|
+
var fs7 = __require("fs");
|
|
7856
|
+
var path6 = __require("path");
|
|
5297
7857
|
var { promisify } = __require("util");
|
|
5298
7858
|
var { cppdb } = require_util();
|
|
5299
|
-
var fsAccess = promisify(
|
|
7859
|
+
var fsAccess = promisify(fs7.access);
|
|
5300
7860
|
module.exports = async function backup(filename, options) {
|
|
5301
7861
|
if (options == null) options = {};
|
|
5302
7862
|
if (typeof filename !== "string") throw new TypeError("Expected first argument to be a string");
|
|
@@ -5309,7 +7869,7 @@ var require_backup = __commonJS({
|
|
|
5309
7869
|
if (typeof attachedName !== "string") throw new TypeError('Expected the "attached" option to be a string');
|
|
5310
7870
|
if (!attachedName) throw new TypeError('The "attached" option cannot be an empty string');
|
|
5311
7871
|
if (handler != null && typeof handler !== "function") throw new TypeError('Expected the "progress" option to be a function');
|
|
5312
|
-
await fsAccess(
|
|
7872
|
+
await fsAccess(path6.dirname(filename)).catch(() => {
|
|
5313
7873
|
throw new TypeError("Cannot save backup because the directory does not exist");
|
|
5314
7874
|
});
|
|
5315
7875
|
const isNewFile = await fsAccess(filename).then(() => false, () => true);
|
|
@@ -5318,13 +7878,13 @@ var require_backup = __commonJS({
|
|
|
5318
7878
|
var runBackup = (backup, handler) => {
|
|
5319
7879
|
let rate = 0;
|
|
5320
7880
|
let useDefault = true;
|
|
5321
|
-
return new Promise((
|
|
7881
|
+
return new Promise((resolve9, reject) => {
|
|
5322
7882
|
setImmediate(function step() {
|
|
5323
7883
|
try {
|
|
5324
7884
|
const progress = backup.transfer(rate);
|
|
5325
7885
|
if (!progress.remainingPages) {
|
|
5326
7886
|
backup.close();
|
|
5327
|
-
|
|
7887
|
+
resolve9(progress);
|
|
5328
7888
|
return;
|
|
5329
7889
|
}
|
|
5330
7890
|
if (useDefault) {
|
|
@@ -5620,8 +8180,8 @@ var require_database = __commonJS({
|
|
|
5620
8180
|
"node_modules/better-sqlite3/lib/database.js"(exports, module) {
|
|
5621
8181
|
"use strict";
|
|
5622
8182
|
init_esm_shims();
|
|
5623
|
-
var
|
|
5624
|
-
var
|
|
8183
|
+
var fs7 = __require("fs");
|
|
8184
|
+
var path6 = __require("path");
|
|
5625
8185
|
var util = require_util();
|
|
5626
8186
|
var SqliteError = require_sqlite_error();
|
|
5627
8187
|
var DEFAULT_ADDON;
|
|
@@ -5657,7 +8217,7 @@ var require_database = __commonJS({
|
|
|
5657
8217
|
addon = DEFAULT_ADDON || (DEFAULT_ADDON = require_bindings()("better_sqlite3.node"));
|
|
5658
8218
|
} else if (typeof nativeBinding === "string") {
|
|
5659
8219
|
const requireFunc = typeof __non_webpack_require__ === "function" ? __non_webpack_require__ : __require;
|
|
5660
|
-
addon = requireFunc(
|
|
8220
|
+
addon = requireFunc(path6.resolve(nativeBinding).replace(/(\.node)?$/, ".node"));
|
|
5661
8221
|
} else {
|
|
5662
8222
|
addon = nativeBinding;
|
|
5663
8223
|
}
|
|
@@ -5665,7 +8225,7 @@ var require_database = __commonJS({
|
|
|
5665
8225
|
addon.setErrorConstructor(SqliteError);
|
|
5666
8226
|
addon.isInitialized = true;
|
|
5667
8227
|
}
|
|
5668
|
-
if (!anonymous && !
|
|
8228
|
+
if (!anonymous && !fs7.existsSync(path6.dirname(filename))) {
|
|
5669
8229
|
throw new TypeError("Cannot open database because the directory does not exist");
|
|
5670
8230
|
}
|
|
5671
8231
|
Object.defineProperties(this, {
|
|
@@ -5740,6 +8300,10 @@ var AnalyzeRequestSchema = z.object({
|
|
|
5740
8300
|
prompt_session: z.string().default("").describe("User original prompt (most recent user message; backward compat)"),
|
|
5741
8301
|
prompt_history: z.array(PromptTurnSchema).default([]).describe("Full interleaved prompt history (ordered turns, each with its triggered actions). Optional; empty = fall back to prompt_session only."),
|
|
5742
8302
|
action_history: z.array(ActionItemSchema).default([]).describe("Historical action sequence"),
|
|
8303
|
+
session_history: z.array(z.object({
|
|
8304
|
+
session_id: z.string(),
|
|
8305
|
+
action_history: z.array(ActionItemSchema).default([])
|
|
8306
|
+
})).default([]).describe("Multi-session historical actions (optional): for L2.4 formal engine cross-session combo-chain / sensitive-data-flow verification. Provided by caller (incl. future agentgate)."),
|
|
5743
8307
|
a_next: ActionItemSchema.describe("Next action to be executed"),
|
|
5744
8308
|
reason: z.string().default("").describe("Reason for executing this action"),
|
|
5745
8309
|
os_type: z.string().default("").describe("OS type: 'linux', 'windows', or empty for auto-detect"),
|
|
@@ -5766,13 +8330,33 @@ var HealthResponseSchema = z.object({
|
|
|
5766
8330
|
// 空=未配置(独立部署或旧版),调用方可选择兼容或严格过滤。
|
|
5767
8331
|
instance: z.string().default("")
|
|
5768
8332
|
});
|
|
8333
|
+
var ReviewRequestSchema = z.object({
|
|
8334
|
+
agent_id: z.string().default("").describe("\u76EE\u6807 agent \u6807\u8BC6\uFF08\u51B3\u5B9A\u72B6\u6001\u5E93\u5206\u6876\uFF09"),
|
|
8335
|
+
db_path: z.string().default("").describe("AgentGate \u672C\u5730\u5B58\u50A8\u8DEF\u5F84\uFF08SQLite \u6587\u4EF6 \u6216 tool_calls JSON \u76EE\u5F55\uFF09\uFF1B\u4E3A\u7A7A\u5219\u7528 sessions"),
|
|
8336
|
+
sessions: z.array(z.object({
|
|
8337
|
+
session_id: z.string(),
|
|
8338
|
+
action_history: z.array(ActionItemSchema).default([])
|
|
8339
|
+
})).default([]).describe("\u591A session \u5386\u53F2\u52A8\u4F5C\uFF08\u66FF\u4EE3/\u515C\u5E95 db_path\uFF09"),
|
|
8340
|
+
a_next: ActionItemSchema.optional().describe("\u53EF\u9009\uFF1Aseed \u540E\u8FDE\u540C\u5F53\u524D\u52A8\u4F5C\u4E00\u8D77\u5224\u5B9A"),
|
|
8341
|
+
is_error: z.boolean().default(false).describe("\u5F53\u524D\u52A8\u4F5C\u6267\u884C\u662F\u5426\u5931\u8D25"),
|
|
8342
|
+
output_pending: z.boolean().default(false).describe("\u8FD4\u56DE\u524D\u5E8F\u94FE\u6E05\u5355 pending_prefixes")
|
|
8343
|
+
});
|
|
8344
|
+
var ReviewResponseSchema = z.object({
|
|
8345
|
+
agent_id: z.string().default(""),
|
|
8346
|
+
seeded_nodes: z.number().default(0),
|
|
8347
|
+
verified: z.boolean().default(true),
|
|
8348
|
+
violations: z.array(z.record(z.unknown())).default([]),
|
|
8349
|
+
pending_prefixes: z.array(z.record(z.unknown())).default([]),
|
|
8350
|
+
state_nodes: z.number().default(0),
|
|
8351
|
+
discovered_patterns: z.array(z.record(z.unknown())).default([]).describe("\u6570\u636E\u9A71\u52A8\u53D1\u73B0\uFF1A\u8DE8 session \u9891\u7E41\u5371\u9669\u7EC4\u5408\uFF08\u9884\u7F6E\u94FE\u76F2\u533A\uFF0CPatternMiner\uFF09")
|
|
8352
|
+
});
|
|
5769
8353
|
|
|
5770
8354
|
// src/routes.ts
|
|
5771
8355
|
init_coordinator();
|
|
5772
8356
|
|
|
5773
8357
|
// src/actrail.ts
|
|
5774
8358
|
init_esm_shims();
|
|
5775
|
-
import * as
|
|
8359
|
+
import * as fs5 from "fs";
|
|
5776
8360
|
function escape(value) {
|
|
5777
8361
|
return value.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/=/g, "\\e");
|
|
5778
8362
|
}
|
|
@@ -5793,7 +8377,7 @@ function getStoragePath() {
|
|
|
5793
8377
|
function reportEnforcementEvent(opts) {
|
|
5794
8378
|
if (!isEnabled()) return false;
|
|
5795
8379
|
const storagePath = getStoragePath();
|
|
5796
|
-
if (!
|
|
8380
|
+
if (!fs5.existsSync(storagePath)) return false;
|
|
5797
8381
|
try {
|
|
5798
8382
|
let Database;
|
|
5799
8383
|
try {
|
|
@@ -5871,6 +8455,182 @@ function reportEnforcementEvent(opts) {
|
|
|
5871
8455
|
}
|
|
5872
8456
|
}
|
|
5873
8457
|
|
|
8458
|
+
// src/routes.ts
|
|
8459
|
+
init_formal_engine();
|
|
8460
|
+
|
|
8461
|
+
// src/engine/formal-engine/pattern_miner.ts
|
|
8462
|
+
init_esm_shims();
|
|
8463
|
+
init_builder();
|
|
8464
|
+
var DANGER_STAGES = /* @__PURE__ */ new Set(["PRIVESC", "EXFIL", "LISTEN"]);
|
|
8465
|
+
var SENSITIVE_PAIRS = /* @__PURE__ */ new Set(["CRED|LISTEN", "LISTEN|CRED"]);
|
|
8466
|
+
function projectStages(actionHistory) {
|
|
8467
|
+
const stages = [];
|
|
8468
|
+
let prev = null;
|
|
8469
|
+
for (const act of actionHistory) {
|
|
8470
|
+
const s = markStage(String(act.actionDetail || ""));
|
|
8471
|
+
if (s && s !== prev) {
|
|
8472
|
+
stages.push(s);
|
|
8473
|
+
prev = s;
|
|
8474
|
+
}
|
|
8475
|
+
}
|
|
8476
|
+
return stages;
|
|
8477
|
+
}
|
|
8478
|
+
function isDangerous(pat) {
|
|
8479
|
+
if (pat.some((s) => DANGER_STAGES.has(s))) return true;
|
|
8480
|
+
for (let i = 0; i < pat.length - 1; i++) {
|
|
8481
|
+
if (SENSITIVE_PAIRS.has(pat[i] + "|" + pat[i + 1])) return true;
|
|
8482
|
+
}
|
|
8483
|
+
return false;
|
|
8484
|
+
}
|
|
8485
|
+
function mineDangerousPatterns(sessions, minSup = 2, maxLen = 4) {
|
|
8486
|
+
const seqs = [];
|
|
8487
|
+
for (const s of sessions) {
|
|
8488
|
+
const sid = String(s.sessionId || "");
|
|
8489
|
+
const stages = projectStages(s.actionHistory || []);
|
|
8490
|
+
if (stages.length > 0) seqs.push([sid, stages]);
|
|
8491
|
+
}
|
|
8492
|
+
if (seqs.length === 0) return [];
|
|
8493
|
+
const results = /* @__PURE__ */ new Map();
|
|
8494
|
+
const mine = (prefix, projected) => {
|
|
8495
|
+
if (prefix.length > 0) {
|
|
8496
|
+
results.set(prefix.join("\u2192"), projected.length);
|
|
8497
|
+
}
|
|
8498
|
+
if (prefix.length >= maxLen) return;
|
|
8499
|
+
const itemSids = /* @__PURE__ */ new Map();
|
|
8500
|
+
for (const [sid, suffix] of projected) {
|
|
8501
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8502
|
+
for (const it of suffix) {
|
|
8503
|
+
if (!seen.has(it)) {
|
|
8504
|
+
seen.add(it);
|
|
8505
|
+
if (!itemSids.has(it)) itemSids.set(it, /* @__PURE__ */ new Set());
|
|
8506
|
+
itemSids.get(it).add(sid);
|
|
8507
|
+
}
|
|
8508
|
+
}
|
|
8509
|
+
}
|
|
8510
|
+
for (const [it, sids] of [...itemSids.entries()].sort()) {
|
|
8511
|
+
if (sids.size >= minSup) {
|
|
8512
|
+
const next = [];
|
|
8513
|
+
for (const [sid, suffix] of projected) {
|
|
8514
|
+
const idx = suffix.indexOf(it);
|
|
8515
|
+
if (idx >= 0) next.push([sid, suffix.slice(idx + 1)]);
|
|
8516
|
+
}
|
|
8517
|
+
mine([...prefix, it], next);
|
|
8518
|
+
}
|
|
8519
|
+
}
|
|
8520
|
+
};
|
|
8521
|
+
mine([], seqs);
|
|
8522
|
+
const out = [];
|
|
8523
|
+
for (const [key, support] of results) {
|
|
8524
|
+
const pat = key.split("\u2192");
|
|
8525
|
+
if (pat.length < 2 || !isDangerous(pat)) continue;
|
|
8526
|
+
const risk = pat.some((s) => s === "PRIVESC" || s === "EXFIL") ? "high" : "medium";
|
|
8527
|
+
out.push({
|
|
8528
|
+
pattern: pat,
|
|
8529
|
+
support,
|
|
8530
|
+
risk,
|
|
8531
|
+
reason: `\u8DE8 session \u9891\u7E41\u51FA\u73B0\u7EC4\u5408 ${pat.join("\u2192")}\uFF08${support} \u4E2A session\uFF09\uFF0C\u9884\u7F6E\u94FE\u672A\u76F4\u63A5\u8986\u76D6\uFF0C\u7591\u4F3C\u653B\u51FB\u524D\u5E8F`
|
|
8532
|
+
});
|
|
8533
|
+
}
|
|
8534
|
+
out.sort((a, b) => b.support - a.support || b.pattern.length - a.pattern.length);
|
|
8535
|
+
return out;
|
|
8536
|
+
}
|
|
8537
|
+
|
|
8538
|
+
// src/agent-gate-reader.ts
|
|
8539
|
+
init_esm_shims();
|
|
8540
|
+
import * as fs6 from "fs";
|
|
8541
|
+
import * as os3 from "os";
|
|
8542
|
+
import * as path5 from "path";
|
|
8543
|
+
var DEFAULT_AGENTGATE_SQLITE = path5.join(
|
|
8544
|
+
os3.homedir(),
|
|
8545
|
+
".local",
|
|
8546
|
+
"share",
|
|
8547
|
+
"agent_gate",
|
|
8548
|
+
"agent_gate_ts.db"
|
|
8549
|
+
);
|
|
8550
|
+
function normalizeAction(row) {
|
|
8551
|
+
return {
|
|
8552
|
+
actionType: String(row.action_type || row.tool_name || row.name || ""),
|
|
8553
|
+
actionDetail: String(row.action_detail || row.command || row.input || "")
|
|
8554
|
+
};
|
|
8555
|
+
}
|
|
8556
|
+
function readJsonDir(dirPath, limit = 1e5) {
|
|
8557
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
8558
|
+
try {
|
|
8559
|
+
const files = fs6.readdirSync(dirPath).filter((f) => f.endsWith(".json")).sort();
|
|
8560
|
+
for (const f of files.slice(-200)) {
|
|
8561
|
+
const sid = f.replace(/\.json$/, "");
|
|
8562
|
+
const raw = fs6.readFileSync(path5.join(dirPath, f), "utf-8");
|
|
8563
|
+
const arr = JSON.parse(raw);
|
|
8564
|
+
if (!Array.isArray(arr)) continue;
|
|
8565
|
+
for (const item of arr) {
|
|
8566
|
+
if (!item || typeof item !== "object") continue;
|
|
8567
|
+
const act = normalizeAction(item);
|
|
8568
|
+
if (act.actionDetail) {
|
|
8569
|
+
const list = sessions.get(sid) || [];
|
|
8570
|
+
list.push(act);
|
|
8571
|
+
sessions.set(sid, list);
|
|
8572
|
+
}
|
|
8573
|
+
}
|
|
8574
|
+
let total = 0;
|
|
8575
|
+
for (const v of sessions.values()) total += v.length;
|
|
8576
|
+
if (total > limit) break;
|
|
8577
|
+
}
|
|
8578
|
+
} catch {
|
|
8579
|
+
}
|
|
8580
|
+
return [...sessions.entries()].map(([sessionId, actionHistory]) => ({ sessionId, actionHistory }));
|
|
8581
|
+
}
|
|
8582
|
+
function readSqlite(dbPath, limit = 1e5) {
|
|
8583
|
+
let Database;
|
|
8584
|
+
try {
|
|
8585
|
+
Database = require_lib();
|
|
8586
|
+
} catch {
|
|
8587
|
+
return [];
|
|
8588
|
+
}
|
|
8589
|
+
let db;
|
|
8590
|
+
try {
|
|
8591
|
+
db = new Database(dbPath, { readonly: true });
|
|
8592
|
+
const rows = db.prepare(
|
|
8593
|
+
`SELECT session_id, action_type, tool_name, action_detail, command, called_at
|
|
8594
|
+
FROM tool_calls ORDER BY called_at, id LIMIT ?`
|
|
8595
|
+
).all(limit);
|
|
8596
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
8597
|
+
for (const r of rows) {
|
|
8598
|
+
const sid = String(r.session_id || "");
|
|
8599
|
+
if (!sid) continue;
|
|
8600
|
+
const act = normalizeAction(r);
|
|
8601
|
+
if (!act.actionDetail) continue;
|
|
8602
|
+
const list = sessions.get(sid) || [];
|
|
8603
|
+
list.push(act);
|
|
8604
|
+
sessions.set(sid, list);
|
|
8605
|
+
}
|
|
8606
|
+
return [...sessions.entries()].map(([sessionId, actionHistory]) => ({ sessionId, actionHistory }));
|
|
8607
|
+
} catch {
|
|
8608
|
+
return [];
|
|
8609
|
+
} finally {
|
|
8610
|
+
try {
|
|
8611
|
+
db?.close();
|
|
8612
|
+
} catch {
|
|
8613
|
+
}
|
|
8614
|
+
}
|
|
8615
|
+
}
|
|
8616
|
+
function loadAgentGateSessions(dbPath = "") {
|
|
8617
|
+
if (dbPath) {
|
|
8618
|
+
try {
|
|
8619
|
+
if (fs6.statSync(dbPath).isDirectory()) {
|
|
8620
|
+
return readJsonDir(dbPath);
|
|
8621
|
+
}
|
|
8622
|
+
} catch {
|
|
8623
|
+
}
|
|
8624
|
+
const sql2 = readSqlite(dbPath);
|
|
8625
|
+
if (sql2.length > 0) return sql2;
|
|
8626
|
+
return readJsonDir(dbPath);
|
|
8627
|
+
}
|
|
8628
|
+
const sql = readSqlite(DEFAULT_AGENTGATE_SQLITE);
|
|
8629
|
+
if (sql.length > 0) return sql;
|
|
8630
|
+
const jsonDefault = path5.join(os3.homedir(), ".local", "share", "agent_gate", "tool_calls");
|
|
8631
|
+
return readJsonDir(jsonDefault);
|
|
8632
|
+
}
|
|
8633
|
+
|
|
5874
8634
|
// src/routes.ts
|
|
5875
8635
|
async function healthRoute(c) {
|
|
5876
8636
|
return c.json(HealthResponseSchema.parse({
|
|
@@ -5886,6 +8646,7 @@ async function analyzeRoute(c) {
|
|
|
5886
8646
|
if (!osType) {
|
|
5887
8647
|
osType = process.platform === "win32" ? "windows" : "linux";
|
|
5888
8648
|
}
|
|
8649
|
+
const agentId = req.agent_id || req.metadata?.agent_type || "";
|
|
5889
8650
|
const result = await runAnalysis({
|
|
5890
8651
|
sessionId: req.session_id,
|
|
5891
8652
|
promptSession: req.prompt_session,
|
|
@@ -5893,6 +8654,13 @@ async function analyzeRoute(c) {
|
|
|
5893
8654
|
actionType: h.action_type,
|
|
5894
8655
|
actionDetail: h.action_detail
|
|
5895
8656
|
})),
|
|
8657
|
+
sessionHistory: (req.session_history || []).map((s) => ({
|
|
8658
|
+
sessionId: s.session_id,
|
|
8659
|
+
actionHistory: (s.action_history || []).map((h) => ({
|
|
8660
|
+
actionType: h.action_type,
|
|
8661
|
+
actionDetail: h.action_detail
|
|
8662
|
+
}))
|
|
8663
|
+
})),
|
|
5896
8664
|
aNext: {
|
|
5897
8665
|
actionType: req.a_next.action_type,
|
|
5898
8666
|
actionDetail: req.a_next.action_detail
|
|
@@ -5900,10 +8668,9 @@ async function analyzeRoute(c) {
|
|
|
5900
8668
|
reason: req.reason,
|
|
5901
8669
|
osType,
|
|
5902
8670
|
cwd: req.cwd,
|
|
5903
|
-
// agent_id
|
|
5904
|
-
//
|
|
5905
|
-
|
|
5906
|
-
agentId: req.agent_id || req.metadata?.agent_type || "",
|
|
8671
|
+
// agent_id:AgentGate V1 normalizer 历史上把 agent_type 塞 metadata,
|
|
8672
|
+
// 顶层为空时兜底(OpenDesk 的 agent-moss 需 agent_id 加载专属规则)。
|
|
8673
|
+
agentId,
|
|
5907
8674
|
llmConfig: req.metadata?.llm_config,
|
|
5908
8675
|
llmLogPath: req.metadata?.llm_log_path
|
|
5909
8676
|
});
|
|
@@ -5925,6 +8692,40 @@ async function analyzeRoute(c) {
|
|
|
5925
8692
|
});
|
|
5926
8693
|
} catch {
|
|
5927
8694
|
}
|
|
8695
|
+
const meta = req.metadata || {};
|
|
8696
|
+
const incEnabled = String(meta.stateful_incremental || "").toLowerCase();
|
|
8697
|
+
if (incEnabled === "1" || incEnabled === "true" || incEnabled === "yes" || incEnabled === "on") {
|
|
8698
|
+
try {
|
|
8699
|
+
const store = getChainStateStoreFor(agentId);
|
|
8700
|
+
const inc = store.applyAction(agentId, {
|
|
8701
|
+
actionType: req.a_next.action_type,
|
|
8702
|
+
actionDetail: req.a_next.action_detail
|
|
8703
|
+
}, req.session_id, Boolean(meta.is_error));
|
|
8704
|
+
const judgment = incMatchedToDeny(inc);
|
|
8705
|
+
if (judgment) {
|
|
8706
|
+
const layers = [...result.violatedLayers];
|
|
8707
|
+
if (!layers.includes("2.4-stateful")) layers.push("2.4-stateful");
|
|
8708
|
+
console.error(
|
|
8709
|
+
`[AgentMoss] stateful \u589E\u91CF\u62E6\u622A: agent=${agentId || "-"} property=${judgment.property_id} risk=${judgment.risk_level}`
|
|
8710
|
+
);
|
|
8711
|
+
return c.json(
|
|
8712
|
+
AnalyzeResponseSchema.parse({
|
|
8713
|
+
decision: "Deny",
|
|
8714
|
+
reason: judgment.reason,
|
|
8715
|
+
risk_level: judgment.risk_level,
|
|
8716
|
+
risk_type: "formal_verification",
|
|
8717
|
+
violated_layers: layers,
|
|
8718
|
+
policy: "",
|
|
8719
|
+
violated_policy: "",
|
|
8720
|
+
confidence: 90,
|
|
8721
|
+
analysis_duration_ms: Math.round((performance.now() - t0) * 10) / 10
|
|
8722
|
+
})
|
|
8723
|
+
);
|
|
8724
|
+
}
|
|
8725
|
+
} catch (err) {
|
|
8726
|
+
console.error("[AgentMoss] stateful incremental error (non-fatal):", err);
|
|
8727
|
+
}
|
|
8728
|
+
}
|
|
5928
8729
|
return c.json(
|
|
5929
8730
|
AnalyzeResponseSchema.parse({
|
|
5930
8731
|
decision: result.allowed ? "Allow" : "Deny",
|
|
@@ -5949,6 +8750,94 @@ async function analyzeRoute(c) {
|
|
|
5949
8750
|
return c.json({ error: "Internal error", detail: String(err) }, 500);
|
|
5950
8751
|
}
|
|
5951
8752
|
}
|
|
8753
|
+
var reviewStore = null;
|
|
8754
|
+
function getChainStateStoreFor(_agentId) {
|
|
8755
|
+
if (!reviewStore) reviewStore = new ChainStateStore();
|
|
8756
|
+
return reviewStore;
|
|
8757
|
+
}
|
|
8758
|
+
function incMatchedToDeny(inc) {
|
|
8759
|
+
if (inc.matched) {
|
|
8760
|
+
return {
|
|
8761
|
+
property_id: "cross-" + inc.matched.chainId,
|
|
8762
|
+
reason: inc.matched.reason,
|
|
8763
|
+
risk_level: inc.matched.riskLevel
|
|
8764
|
+
};
|
|
8765
|
+
}
|
|
8766
|
+
if (inc.exfil && inc.exfil.length > 0) {
|
|
8767
|
+
return {
|
|
8768
|
+
property_id: "sensitive-data-exfil",
|
|
8769
|
+
reason: inc.exfil[0].reason,
|
|
8770
|
+
risk_level: inc.exfil[0].risk_level
|
|
8771
|
+
};
|
|
8772
|
+
}
|
|
8773
|
+
return null;
|
|
8774
|
+
}
|
|
8775
|
+
async function reviewRoute(c) {
|
|
8776
|
+
const t0 = performance.now();
|
|
8777
|
+
try {
|
|
8778
|
+
const body = await c.req.json();
|
|
8779
|
+
const req = ReviewRequestSchema.parse(body);
|
|
8780
|
+
const agentId = req.agent_id || "default";
|
|
8781
|
+
const store = getChainStateStoreFor(agentId);
|
|
8782
|
+
let tracker = store.load(agentId);
|
|
8783
|
+
if (!tracker) {
|
|
8784
|
+
tracker = new StatefulChainTracker(agentId);
|
|
8785
|
+
}
|
|
8786
|
+
let sessions = [];
|
|
8787
|
+
if (req.db_path) {
|
|
8788
|
+
sessions = loadAgentGateSessions(req.db_path);
|
|
8789
|
+
if (sessions.length === 0) sessions = (req.sessions || []).map((s) => ({
|
|
8790
|
+
sessionId: s.session_id,
|
|
8791
|
+
actionHistory: s.action_history.map((a) => ({ actionType: a.action_type, actionDetail: a.action_detail }))
|
|
8792
|
+
}));
|
|
8793
|
+
} else {
|
|
8794
|
+
sessions = (req.sessions || []).map((s) => ({
|
|
8795
|
+
sessionId: s.session_id,
|
|
8796
|
+
actionHistory: s.action_history.map((a) => ({ actionType: a.action_type, actionDetail: a.action_detail }))
|
|
8797
|
+
}));
|
|
8798
|
+
}
|
|
8799
|
+
const added = sessions.length > 0 ? tracker.seed(sessions) : 0;
|
|
8800
|
+
store.save(tracker);
|
|
8801
|
+
let verified = true;
|
|
8802
|
+
const violations = [];
|
|
8803
|
+
if (sessions.length > 0 || tracker.nodes.length > 0) {
|
|
8804
|
+
const eng = new FormalEngine();
|
|
8805
|
+
const full = eng.verify(
|
|
8806
|
+
sessions.map((s) => ({ sessionId: s.sessionId, actionHistory: s.actionHistory })),
|
|
8807
|
+
req.a_next ? { actionType: req.a_next.action_type, actionDetail: req.a_next.action_detail } : void 0
|
|
8808
|
+
);
|
|
8809
|
+
verified = full.verified;
|
|
8810
|
+
for (const v of full.violations) {
|
|
8811
|
+
violations.push({ property_id: v.propertyId, reason: v.reason, risk_level: v.riskLevel });
|
|
8812
|
+
}
|
|
8813
|
+
}
|
|
8814
|
+
const pending = tracker.pendingPrefixes.map((p) => ({
|
|
8815
|
+
chain_id: p.chainId,
|
|
8816
|
+
matched: p.matched,
|
|
8817
|
+
next_stage: p.nextStage,
|
|
8818
|
+
risk_level: p.riskLevel
|
|
8819
|
+
}));
|
|
8820
|
+
let discovered = [];
|
|
8821
|
+
if (sessions.length > 0) {
|
|
8822
|
+
try {
|
|
8823
|
+
discovered = mineDangerousPatterns(sessions, 2);
|
|
8824
|
+
} catch {
|
|
8825
|
+
}
|
|
8826
|
+
}
|
|
8827
|
+
return c.json(ReviewResponseSchema.parse({
|
|
8828
|
+
agent_id: agentId,
|
|
8829
|
+
seeded_nodes: added,
|
|
8830
|
+
verified,
|
|
8831
|
+
violations,
|
|
8832
|
+
pending_prefixes: pending,
|
|
8833
|
+
state_nodes: tracker.nodes.length,
|
|
8834
|
+
discovered_patterns: discovered
|
|
8835
|
+
}));
|
|
8836
|
+
} catch (err) {
|
|
8837
|
+
console.error("[AgentMoss] Review error:", err);
|
|
8838
|
+
return c.json({ error: "Internal error", detail: String(err) }, 500);
|
|
8839
|
+
}
|
|
8840
|
+
}
|
|
5952
8841
|
|
|
5953
8842
|
// src/console/index.ts
|
|
5954
8843
|
init_esm_shims();
|
|
@@ -5958,11 +8847,11 @@ init_esm_shims();
|
|
|
5958
8847
|
init_runtime_config();
|
|
5959
8848
|
init_token_stats();
|
|
5960
8849
|
import { Hono } from "hono";
|
|
5961
|
-
import { readFileSync as
|
|
5962
|
-
import { join as
|
|
5963
|
-
import { fileURLToPath as
|
|
5964
|
-
var
|
|
5965
|
-
var STATIC_DIR =
|
|
8850
|
+
import { readFileSync as readFileSync15, existsSync as existsSync14 } from "fs";
|
|
8851
|
+
import { join as join9, dirname as dirname10 } from "path";
|
|
8852
|
+
import { fileURLToPath as fileURLToPath9 } from "url";
|
|
8853
|
+
var __dirname8 = dirname10(fileURLToPath9(import.meta.url));
|
|
8854
|
+
var STATIC_DIR = join9(__dirname8, "static");
|
|
5966
8855
|
var CONSOLE_TOKEN_ENV = "AGENT_MOSS_CONSOLE_TOKEN";
|
|
5967
8856
|
function getConsoleToken() {
|
|
5968
8857
|
return process.env[CONSOLE_TOKEN_ENV] || "";
|
|
@@ -5986,9 +8875,9 @@ function createConsoleRouter() {
|
|
|
5986
8875
|
const console2 = new Hono();
|
|
5987
8876
|
console2.use("/api/*", authMiddleware());
|
|
5988
8877
|
const serveIndex = (c) => {
|
|
5989
|
-
const indexFile =
|
|
5990
|
-
if (
|
|
5991
|
-
return c.html(
|
|
8878
|
+
const indexFile = join9(STATIC_DIR, "index.html");
|
|
8879
|
+
if (existsSync14(indexFile)) {
|
|
8880
|
+
return c.html(readFileSync15(indexFile, "utf-8"));
|
|
5992
8881
|
}
|
|
5993
8882
|
return c.html("<h1>AgentMoss Policy Console</h1><p>Frontend not built. Use /console/api/* endpoints.</p>");
|
|
5994
8883
|
};
|
|
@@ -6079,13 +8968,13 @@ function createConsoleRouter() {
|
|
|
6079
8968
|
if (!/^[A-Za-z0-9_-]+$/.test(skillId)) {
|
|
6080
8969
|
return c.json({ error: "invalid skill_id" }, 400);
|
|
6081
8970
|
}
|
|
6082
|
-
const skillPath =
|
|
6083
|
-
if (!
|
|
6084
|
-
const builtinPath =
|
|
6085
|
-
if (
|
|
8971
|
+
const skillPath = join9(dirname10(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
|
|
8972
|
+
if (!existsSync14(skillPath)) {
|
|
8973
|
+
const builtinPath = join9(__dirname8, "..", "..", "agent_moss", "skills", `${skillId}.md`);
|
|
8974
|
+
if (existsSync14(builtinPath)) return c.text(readFileSync15(builtinPath, "utf-8"));
|
|
6086
8975
|
return c.json({ error: "skill not found" }, 404);
|
|
6087
8976
|
}
|
|
6088
|
-
return c.text(
|
|
8977
|
+
return c.text(readFileSync15(skillPath, "utf-8"));
|
|
6089
8978
|
});
|
|
6090
8979
|
console2.get("/api/agents", (c) => {
|
|
6091
8980
|
const rc = loadRuntimeConfig();
|
|
@@ -6125,6 +9014,49 @@ function createConsoleRouter() {
|
|
|
6125
9014
|
const rc = updateAgentCategoryEnabled(b.agent_id, b.category, b.enabled);
|
|
6126
9015
|
return c.json({ agents: getAgentRules(rc) });
|
|
6127
9016
|
});
|
|
9017
|
+
const enabledScenarioIds = () => (process.env.AGENT_MOSS_SCENARIO || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
9018
|
+
console2.get("/api/scenarios", (c) => {
|
|
9019
|
+
const all = getScenarioRules(loadRuntimeConfig());
|
|
9020
|
+
const enabled = enabledScenarioIds();
|
|
9021
|
+
if (!enabled.length) return c.json({ scenarios: {}, enabled: [] });
|
|
9022
|
+
const shown = {};
|
|
9023
|
+
for (const sid of enabled) if (all[sid]) shown[sid] = all[sid];
|
|
9024
|
+
return c.json({ scenarios: shown, enabled });
|
|
9025
|
+
});
|
|
9026
|
+
console2.put("/api/scenarios/rules/enabled", async (c) => {
|
|
9027
|
+
const b = await c.req.json();
|
|
9028
|
+
const fp = {};
|
|
9029
|
+
if (b.value) fp.value = b.value;
|
|
9030
|
+
if (b.pattern) fp.pattern = b.pattern;
|
|
9031
|
+
if (b.keyword) fp.keyword = b.keyword;
|
|
9032
|
+
if (b.path) fp.path = b.path;
|
|
9033
|
+
const rc = updateScenarioRuleEnabled(b.scenario_id, b.category, fp, b.enabled);
|
|
9034
|
+
return c.json({ scenarios: getScenarioRules(rc), enabled: enabledScenarioIds() });
|
|
9035
|
+
});
|
|
9036
|
+
console2.post("/api/scenarios/rules", async (c) => {
|
|
9037
|
+
const b = await c.req.json();
|
|
9038
|
+
const rule = {};
|
|
9039
|
+
for (const [k, v] of Object.entries(b)) {
|
|
9040
|
+
if (!["scenario_id", "category"].includes(k) && v !== void 0 && v !== null) rule[k] = v;
|
|
9041
|
+
}
|
|
9042
|
+
const rc = addScenarioRule(b.scenario_id, b.category, rule);
|
|
9043
|
+
return c.json({ scenarios: getScenarioRules(rc), enabled: enabledScenarioIds() });
|
|
9044
|
+
});
|
|
9045
|
+
console2.delete("/api/scenarios/rules", async (c) => {
|
|
9046
|
+
const b = await c.req.json();
|
|
9047
|
+
const fp = {};
|
|
9048
|
+
if (b.value) fp.value = b.value;
|
|
9049
|
+
if (b.pattern) fp.pattern = b.pattern;
|
|
9050
|
+
if (b.keyword) fp.keyword = b.keyword;
|
|
9051
|
+
if (b.path) fp.path = b.path;
|
|
9052
|
+
const rc = deleteScenarioRule(b.scenario_id, b.category, fp);
|
|
9053
|
+
return c.json({ scenarios: getScenarioRules(rc), enabled: enabledScenarioIds() });
|
|
9054
|
+
});
|
|
9055
|
+
console2.put("/api/scenarios/categories/enabled", async (c) => {
|
|
9056
|
+
const b = await c.req.json();
|
|
9057
|
+
const rc = updateScenarioCategoryEnabled(b.scenario_id, b.category, b.enabled);
|
|
9058
|
+
return c.json({ scenarios: getScenarioRules(rc), enabled: enabledScenarioIds() });
|
|
9059
|
+
});
|
|
6128
9060
|
console2.get("/api/config", (c) => {
|
|
6129
9061
|
const rc = loadRuntimeConfig();
|
|
6130
9062
|
return c.json(rc);
|
|
@@ -6164,18 +9096,19 @@ function createApp() {
|
|
|
6164
9096
|
app.use("*", logger());
|
|
6165
9097
|
app.get("/api/v1/health", healthRoute);
|
|
6166
9098
|
app.post("/api/v1/analyze", analyzeRoute);
|
|
9099
|
+
app.post("/api/v1/review", reviewRoute);
|
|
6167
9100
|
app.route("/console", createConsoleRouter());
|
|
6168
9101
|
app.get("/console/", (c) => c.redirect("/console"));
|
|
6169
9102
|
return app;
|
|
6170
9103
|
}
|
|
6171
9104
|
async function findFreePort(startPort, host = "127.0.0.1", scan = 100) {
|
|
6172
9105
|
for (let port = startPort; port < startPort + scan; port++) {
|
|
6173
|
-
const free = await new Promise((
|
|
9106
|
+
const free = await new Promise((resolve9) => {
|
|
6174
9107
|
const server = net.createServer();
|
|
6175
9108
|
server.listen(port, host, () => {
|
|
6176
|
-
server.close(() =>
|
|
9109
|
+
server.close(() => resolve9(true));
|
|
6177
9110
|
});
|
|
6178
|
-
server.on("error", () =>
|
|
9111
|
+
server.on("error", () => resolve9(false));
|
|
6179
9112
|
});
|
|
6180
9113
|
if (free) {
|
|
6181
9114
|
if (port !== startPort) {
|
|
@@ -6200,7 +9133,7 @@ async function startServer(host = "127.0.0.1", port = 9090) {
|
|
|
6200
9133
|
}
|
|
6201
9134
|
|
|
6202
9135
|
// src/cli.ts
|
|
6203
|
-
import { fileURLToPath as
|
|
9136
|
+
import { fileURLToPath as fileURLToPath10 } from "url";
|
|
6204
9137
|
import { realpathSync } from "fs";
|
|
6205
9138
|
function printHelp() {
|
|
6206
9139
|
console.log(`AgentMoss security analysis engine (TypeScript v${version})
|
|
@@ -6229,7 +9162,7 @@ async function main() {
|
|
|
6229
9162
|
switch (command) {
|
|
6230
9163
|
case "init": {
|
|
6231
9164
|
const output = args[2] || "input.json";
|
|
6232
|
-
const
|
|
9165
|
+
const fs7 = await import("fs");
|
|
6233
9166
|
const template = {
|
|
6234
9167
|
session_id: "my-session-001",
|
|
6235
9168
|
prompt_session: "Read /var/log/app.log and analyze errors",
|
|
@@ -6239,7 +9172,7 @@ async function main() {
|
|
|
6239
9172
|
os_type: "",
|
|
6240
9173
|
cwd: "/home/user/project"
|
|
6241
9174
|
};
|
|
6242
|
-
|
|
9175
|
+
fs7.writeFileSync(output, JSON.stringify(template, null, 2), "utf-8");
|
|
6243
9176
|
console.log(`Template written to ${output}`);
|
|
6244
9177
|
break;
|
|
6245
9178
|
}
|
|
@@ -6248,11 +9181,11 @@ async function main() {
|
|
|
6248
9181
|
console.error("Usage: agent-moss analyze <input.json>");
|
|
6249
9182
|
process.exit(1);
|
|
6250
9183
|
}
|
|
6251
|
-
const
|
|
6252
|
-
const
|
|
9184
|
+
const fs7 = await import("fs");
|
|
9185
|
+
const path6 = await import("path");
|
|
6253
9186
|
const { runAnalysis: runAnalysis2 } = await Promise.resolve().then(() => (init_coordinator(), coordinator_exports));
|
|
6254
|
-
const filePath =
|
|
6255
|
-
const data = JSON.parse(
|
|
9187
|
+
const filePath = path6.resolve(args[1]);
|
|
9188
|
+
const data = JSON.parse(fs7.readFileSync(filePath, "utf-8"));
|
|
6256
9189
|
const result = await runAnalysis2({
|
|
6257
9190
|
sessionId: data.session_id || "unknown",
|
|
6258
9191
|
promptSession: data.prompt_session || "",
|
|
@@ -6289,9 +9222,9 @@ async function main() {
|
|
|
6289
9222
|
case "install": {
|
|
6290
9223
|
const enable = args.includes("--enable");
|
|
6291
9224
|
const { execSync } = await import("child_process");
|
|
6292
|
-
const
|
|
6293
|
-
const
|
|
6294
|
-
const { fileURLToPath:
|
|
9225
|
+
const fs7 = await import("fs");
|
|
9226
|
+
const path6 = await import("path");
|
|
9227
|
+
const { fileURLToPath: fileURLToPath11 } = await import("url");
|
|
6295
9228
|
if (process.getuid?.() !== 0) {
|
|
6296
9229
|
try {
|
|
6297
9230
|
execSync("sudo -n true", { stdio: "ignore", timeout: 5e3 });
|
|
@@ -6306,23 +9239,23 @@ async function main() {
|
|
|
6306
9239
|
execSync(sudoArgs.join(" "), { stdio: "inherit" });
|
|
6307
9240
|
process.exit(0);
|
|
6308
9241
|
}
|
|
6309
|
-
const
|
|
6310
|
-
const
|
|
6311
|
-
let srcPath =
|
|
6312
|
-
if (!
|
|
6313
|
-
srcPath =
|
|
9242
|
+
const __filename7 = fileURLToPath11(import.meta.url);
|
|
9243
|
+
const __dirname9 = path6.dirname(__filename7);
|
|
9244
|
+
let srcPath = path6.join(__dirname9, "data", "agent_moss.service");
|
|
9245
|
+
if (!fs7.existsSync(srcPath)) {
|
|
9246
|
+
srcPath = path6.join(__dirname9, "..", "src", "data", "agent_moss.service");
|
|
6314
9247
|
}
|
|
6315
|
-
if (!
|
|
9248
|
+
if (!fs7.existsSync(srcPath)) {
|
|
6316
9249
|
console.error("\u9519\u8BEF\uFF1A\u627E\u4E0D\u5230 agent_moss.service \u6A21\u677F\u6587\u4EF6");
|
|
6317
9250
|
process.exit(1);
|
|
6318
9251
|
}
|
|
6319
9252
|
const which = execSync("which agent-moss 2>/dev/null || true", { encoding: "utf-8" }).trim();
|
|
6320
|
-
const binPath = which || `${process.execPath} ${
|
|
6321
|
-
let content =
|
|
9253
|
+
const binPath = which || `${process.execPath} ${path6.join(__dirname9, "cli.js")}`;
|
|
9254
|
+
let content = fs7.readFileSync(srcPath, "utf-8");
|
|
6322
9255
|
content = content.replace("__AGENT_MOSS_BIN__", binPath);
|
|
6323
9256
|
const dst = "/etc/systemd/system/agent-moss.service";
|
|
6324
|
-
|
|
6325
|
-
|
|
9257
|
+
fs7.writeFileSync(dst, content, "utf-8");
|
|
9258
|
+
fs7.chmodSync(dst, 420);
|
|
6326
9259
|
console.log(`\u2705 \u5DF2\u5B89\u88C5 systemd service: ${dst}`);
|
|
6327
9260
|
execSync("systemctl daemon-reload", { stdio: "inherit" });
|
|
6328
9261
|
console.log("\u2705 systemctl daemon-reload \u5B8C\u6210");
|
|
@@ -6361,13 +9294,13 @@ async function main() {
|
|
|
6361
9294
|
let version2 = "";
|
|
6362
9295
|
for (let port = 9090; port <= 9095; port++) {
|
|
6363
9296
|
try {
|
|
6364
|
-
const data = await new Promise((
|
|
9297
|
+
const data = await new Promise((resolve9, reject) => {
|
|
6365
9298
|
const req = http.get(`http://127.0.0.1:${port}/api/v1/health`, { timeout: 2e3 }, (res) => {
|
|
6366
9299
|
let body = "";
|
|
6367
9300
|
res.on("data", (chunk) => body += chunk);
|
|
6368
9301
|
res.on("end", () => {
|
|
6369
9302
|
try {
|
|
6370
|
-
|
|
9303
|
+
resolve9(JSON.parse(body));
|
|
6371
9304
|
} catch {
|
|
6372
9305
|
reject();
|
|
6373
9306
|
}
|
|
@@ -6395,13 +9328,13 @@ async function main() {
|
|
|
6395
9328
|
console.log(` Systemd: \u672A\u6CE8\u518C\uFF08\u8FD0\u884C agent-moss install --enable \u5B89\u88C5\uFF09`);
|
|
6396
9329
|
}
|
|
6397
9330
|
try {
|
|
6398
|
-
const data = await new Promise((
|
|
9331
|
+
const data = await new Promise((resolve9, reject) => {
|
|
6399
9332
|
const req = http.get(`http://127.0.0.1:${foundPort}/console/api/config`, { timeout: 3e3 }, (res) => {
|
|
6400
9333
|
let body = "";
|
|
6401
9334
|
res.on("data", (chunk) => body += chunk);
|
|
6402
9335
|
res.on("end", () => {
|
|
6403
9336
|
try {
|
|
6404
|
-
|
|
9337
|
+
resolve9(JSON.parse(body));
|
|
6405
9338
|
} catch {
|
|
6406
9339
|
reject();
|
|
6407
9340
|
}
|
|
@@ -6448,9 +9381,9 @@ async function main() {
|
|
|
6448
9381
|
var isMain = (() => {
|
|
6449
9382
|
try {
|
|
6450
9383
|
if (!process.argv[1]) return false;
|
|
6451
|
-
const thisFile = realpathSync(
|
|
9384
|
+
const thisFile = realpathSync(fileURLToPath10(import.meta.url));
|
|
6452
9385
|
const argv1 = realpathSync(
|
|
6453
|
-
process.argv[1].startsWith("file://") ?
|
|
9386
|
+
process.argv[1].startsWith("file://") ? fileURLToPath10(process.argv[1]) : process.argv[1]
|
|
6454
9387
|
);
|
|
6455
9388
|
return thisFile === argv1;
|
|
6456
9389
|
} catch {
|