@kenhkl/agent-moss 0.12.6 → 0.12.8

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