@kenhkl/agent-moss 0.12.7 → 0.12.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -760,6 +760,53 @@ function runLogicCheck(input) {
760
760
  if (pwdResult) return pwdResult;
761
761
  const userDelResult = checkUserDeletionConsent(actionType, actionDetail, actionHistory);
762
762
  if (userDelResult) return userDelResult;
763
+ const comboResult = checkComboChain(actionHistory, aNext.actionDetail);
764
+ if (comboResult) return comboResult;
765
+ return null;
766
+ }
767
+ function checkComboChain(actionHistory, actionDetailRaw) {
768
+ INLINE_SCRIPT_RE.lastIndex = 0;
769
+ if (INLINE_SCRIPT_RE.test(actionDetailRaw)) return null;
770
+ const details = [];
771
+ for (const h of actionHistory) {
772
+ if (h.actionDetail) details.push(h.actionDetail.toLowerCase());
773
+ }
774
+ details.push(actionDetailRaw.toLowerCase());
775
+ const seq = details.map((d) => {
776
+ const hits = /* @__PURE__ */ new Set();
777
+ for (const [stage, pats] of Object.entries(COMBO_STAGES)) {
778
+ if (pats.some((p) => {
779
+ p.lastIndex = 0;
780
+ return p.test(d);
781
+ })) hits.add(stage);
782
+ }
783
+ return hits;
784
+ });
785
+ for (const chain of COMBO_CHAINS) {
786
+ const chainStages = chain.stages;
787
+ if (!chainStages.length || chain.min_stages <= 0) continue;
788
+ let matched = 0;
789
+ let stageIdx = 0;
790
+ for (const hits of seq) {
791
+ if (stageIdx < chainStages.length && hits.has(chainStages[stageIdx])) {
792
+ matched += 1;
793
+ stageIdx += 1;
794
+ if (matched >= chain.min_stages) break;
795
+ }
796
+ }
797
+ if (matched >= chain.min_stages) {
798
+ const riskLevel = chain.risk_level === "critical" || chain.risk_level === "high" || chain.risk_level === "medium" || chain.risk_level === "low" ? chain.risk_level : "high";
799
+ return {
800
+ allowed: false,
801
+ reason: chain.reason,
802
+ riskLevel,
803
+ riskType: "combo_chain",
804
+ confidence: 85,
805
+ source: "logic_rule",
806
+ violatedLayers: ["2"]
807
+ };
808
+ }
809
+ }
763
810
  return null;
764
811
  }
765
812
  function extractFilePaths(text) {
@@ -859,17 +906,17 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
859
906
  }
860
907
  }
861
908
  const unreadPaths = [];
862
- for (const path3 of uniqueTargetPaths) {
909
+ for (const path5 of uniqueTargetPaths) {
863
910
  const hasRead = [...historyReadPaths].some(
864
- (rp) => path3 === rp || path3.startsWith(rp) || rp.startsWith(path3)
911
+ (rp) => path5 === rp || path5.startsWith(rp) || rp.startsWith(path5)
865
912
  );
866
913
  if (!hasRead) {
867
- let isCreate = !fs2.existsSync(path3);
914
+ let isCreate = !fs2.existsSync(path5);
868
915
  if (!isCreate) {
869
916
  isCreate = CREATE_KEYWORDS.some((kw) => reason.toLowerCase().includes(kw) || actionDetail.includes(kw));
870
917
  }
871
918
  if (!isCreate) {
872
- unreadPaths.push(path3);
919
+ unreadPaths.push(path5);
873
920
  }
874
921
  }
875
922
  }
@@ -1012,7 +1059,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1012
1059
  violatedLayers: ["2"]
1013
1060
  };
1014
1061
  }
1015
- var ROOT_SENSITIVE_PATHS, HOME_SENSITIVE_PATHS, CREDENTIAL_PATHS, NON_WRITE_TOOLS, NON_WRITE_TOOL_PREFIXES, WRITE_KEYWORDS, WRITE_COMMAND_RES, READ_ONLY_COMMANDS, DEVNULL_REDIRECT_RE, READ_KEYWORDS, CREATE_KEYWORDS, PASSWORD_MODIFY_PATTERNS, USER_DELETION_PATTERNS, INTENT_DEVIATION_PATTERNS, INLINE_SCRIPT_RE, DIR_TRAVERSAL_KW;
1062
+ var ROOT_SENSITIVE_PATHS, HOME_SENSITIVE_PATHS, CREDENTIAL_PATHS, NON_WRITE_TOOLS, NON_WRITE_TOOL_PREFIXES, WRITE_KEYWORDS, WRITE_COMMAND_RES, READ_ONLY_COMMANDS, DEVNULL_REDIRECT_RE, READ_KEYWORDS, CREATE_KEYWORDS, PASSWORD_MODIFY_PATTERNS, USER_DELETION_PATTERNS, INTENT_DEVIATION_PATTERNS, COMBO_STAGES, COMBO_CHAINS, INLINE_SCRIPT_RE, DIR_TRAVERSAL_KW;
1016
1063
  var init_logic_rules = __esm({
1017
1064
  "src/engine/logic-rules.ts"() {
1018
1065
  "use strict";
@@ -1021,7 +1068,7 @@ var init_logic_rules = __esm({
1021
1068
  init_rules_loader();
1022
1069
  ROOT_SENSITIVE_PATHS = _loadSensitivePathScope("root");
1023
1070
  HOME_SENSITIVE_PATHS = _loadSensitivePathScope("home");
1024
- CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: 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 }));
1025
1072
  NON_WRITE_TOOLS = /* @__PURE__ */ new Set([
1026
1073
  "ask_user_question",
1027
1074
  "glob",
@@ -1165,6 +1212,24 @@ var init_logic_rules = __esm({
1165
1212
  reason: p.reason
1166
1213
  }));
1167
1214
  })();
1215
+ COMBO_STAGES = (() => {
1216
+ const combo = loadL2().combo_chains || {};
1217
+ const stages = {};
1218
+ for (const [name, pats] of Object.entries(combo.stages || {})) {
1219
+ stages[name] = pats.map((p) => new RegExp(p, "i"));
1220
+ }
1221
+ return stages;
1222
+ })();
1223
+ COMBO_CHAINS = (() => {
1224
+ const combo = loadL2().combo_chains || {};
1225
+ return (combo.chains || []).map((c) => ({
1226
+ id: String(c.id || "combo_chain"),
1227
+ stages: c.stages || [],
1228
+ min_stages: Number(c.min_stages) || (c.stages || []).length,
1229
+ risk_level: String(c.risk_level || "high"),
1230
+ reason: String(c.reason || "\u68C0\u6D4B\u5230\u7EC4\u5408\u653B\u51FB\u94FE")
1231
+ }));
1232
+ })();
1168
1233
  INLINE_SCRIPT_RE = /(?:^|\s)(?:python|python3|python3\.\d+|perl|perl5|ruby|jruby|mruby|node|php|lua)\s+(?:-[a-zA-Z]*[cer][a-zA-Z]*\s+)/i;
1169
1234
  DIR_TRAVERSAL_KW = [
1170
1235
  "os.listdir",
@@ -1282,9 +1347,11 @@ __export(runtime_config_exports, {
1282
1347
  addAgentRule: () => addAgentRule,
1283
1348
  addCustomRule: () => addCustomRule,
1284
1349
  addCustomSkill: () => addCustomSkill,
1350
+ addScenarioRule: () => addScenarioRule,
1285
1351
  deleteAgentRule: () => deleteAgentRule,
1286
1352
  deleteCustomRule: () => deleteCustomRule,
1287
1353
  deleteCustomSkill: () => deleteCustomSkill,
1354
+ deleteScenarioRule: () => deleteScenarioRule,
1288
1355
  filterEnabledRules: () => filterEnabledRules,
1289
1356
  generateSourceDefaults: () => generateSourceDefaults,
1290
1357
  getAgentRules: () => getAgentRules,
@@ -1303,9 +1370,11 @@ __export(runtime_config_exports, {
1303
1370
  getEnabledL2SensitivePathsOrDefault: () => getEnabledL2SensitivePathsOrDefault,
1304
1371
  getEnabledL2UserDeletionPatterns: () => getEnabledL2UserDeletionPatterns,
1305
1372
  getEnabledL2UserDeletionPatternsOrDefault: () => getEnabledL2UserDeletionPatternsOrDefault,
1373
+ getEnabledScenarioExtensions: () => getEnabledScenarioExtensions,
1306
1374
  getEnvOverrides: () => getEnvOverrides,
1307
1375
  getProfileOrDetect: () => getProfileOrDetect,
1308
1376
  getRuntimeConfigPath: () => getRuntimeConfigPath,
1377
+ getScenarioRules: () => getScenarioRules,
1309
1378
  hasRuntimeConfig: () => hasRuntimeConfig,
1310
1379
  isL2CategoryEnabled: () => isL2CategoryEnabled,
1311
1380
  isLayerEnabled: () => isLayerEnabled,
@@ -1319,6 +1388,8 @@ __export(runtime_config_exports, {
1319
1388
  updateRuleDenyMode: () => updateRuleDenyMode,
1320
1389
  updateRuleEnabled: () => updateRuleEnabled,
1321
1390
  updateRuleSkipL3: () => updateRuleSkipL3,
1391
+ updateScenarioCategoryEnabled: () => updateScenarioCategoryEnabled,
1392
+ updateScenarioRuleEnabled: () => updateScenarioRuleEnabled,
1322
1393
  updateSkillCategoryEnabled: () => updateSkillCategoryEnabled,
1323
1394
  updateSkillEnabled: () => updateSkillEnabled
1324
1395
  });
@@ -1347,7 +1418,8 @@ function emptyRuntimeConfig() {
1347
1418
  L1_rules: {},
1348
1419
  L2_rules: {},
1349
1420
  L3_skills: {},
1350
- agent_rules: {}
1421
+ agent_rules: {},
1422
+ scenario_rules: {}
1351
1423
  };
1352
1424
  }
1353
1425
  function loadRuntimeConfig() {
@@ -1373,7 +1445,8 @@ function loadRuntimeConfig() {
1373
1445
  L1_rules: merged.L1_rules || {},
1374
1446
  L2_rules: merged.L2_rules || {},
1375
1447
  L3_skills: merged.L3_skills || {},
1376
- agent_rules: merged.agent_rules || {}
1448
+ agent_rules: merged.agent_rules || {},
1449
+ scenario_rules: merged.scenario_rules || {}
1377
1450
  };
1378
1451
  } catch {
1379
1452
  return emptyRuntimeConfig();
@@ -1608,6 +1681,55 @@ function _l3SkillsToRules() {
1608
1681
  out.user_custom = { category_enabled: true, category_desc: "", skills: [] };
1609
1682
  return out;
1610
1683
  }
1684
+ function _scenarioRulesToDefaults() {
1685
+ const out = {};
1686
+ const scenDir = existsSync4(_SCENARIOS_DIR_PROD) ? _SCENARIOS_DIR_PROD : existsSync4(_SCENARIOS_DIR_DEV) ? _SCENARIOS_DIR_DEV : _SCENARIOS_DIR_PROD;
1687
+ if (!existsSync4(scenDir)) return out;
1688
+ let entries;
1689
+ try {
1690
+ entries = readdirSync3(scenDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
1691
+ } catch {
1692
+ return out;
1693
+ }
1694
+ for (const scenarioId of entries) {
1695
+ const rulesFile = join2(scenDir, scenarioId, "rules.json");
1696
+ if (!existsSync4(rulesFile)) continue;
1697
+ let data;
1698
+ try {
1699
+ data = JSON.parse(readFileSync4(rulesFile, "utf-8"));
1700
+ } catch {
1701
+ continue;
1702
+ }
1703
+ out[scenarioId] = {
1704
+ builtin_safe_action_types: {
1705
+ category_enabled: true,
1706
+ category_desc: "\u573A\u666F\u5B8C\u5168\u5B89\u5168\u64CD\u4F5C\u767D\u540D\u5355\uFF08\u573A\u666F\u901A\u5E38\u4E0D\u4F7F\u7528\uFF09",
1707
+ rules: []
1708
+ },
1709
+ builtin_safe_skills: {
1710
+ category_enabled: true,
1711
+ category_desc: "\u573A\u666F\u4FE1\u4EFB\u7684\u5185\u7F6E Skill\uFF08\u573A\u666F\u901A\u5E38\u4E0D\u4F7F\u7528\uFF09",
1712
+ rules: []
1713
+ },
1714
+ extra_command_patterns: {
1715
+ category_enabled: true,
1716
+ category_desc: "\u573A\u666F\u4E13\u5C5E\u5371\u9669\u547D\u4EE4\u6B63\u5219\uFF08L1 \u672B\u5C3E\u8FFD\u52A0\uFF09",
1717
+ rules: (data.extra_command_patterns || []).map((p) => ({ ...p, enabled: true, builtin: true }))
1718
+ },
1719
+ extra_injection_keywords: {
1720
+ category_enabled: true,
1721
+ category_desc: "\u573A\u666F\u4E13\u5C5E\u6CE8\u5165\u5173\u952E\u8BCD\uFF08L1 \u672B\u5C3E\u8FFD\u52A0\uFF09",
1722
+ rules: (data.extra_injection_keywords || []).map((k) => ({ ...k, enabled: true, builtin: true }))
1723
+ },
1724
+ extra_sensitive_paths: {
1725
+ category_enabled: true,
1726
+ category_desc: "\u573A\u666F\u4E13\u5C5E\u654F\u611F\u8DEF\u5F84\uFF08L1 \u672B\u5C3E\u8FFD\u52A0\uFF09",
1727
+ rules: (data.extra_sensitive_paths || []).map((sp) => ({ ...sp, enabled: true, builtin: true }))
1728
+ }
1729
+ };
1730
+ }
1731
+ return out;
1732
+ }
1611
1733
  function _agentRulesToDefaults() {
1612
1734
  const out = {};
1613
1735
  const agentsDir = existsSync4(_AGENTS_DIR_PROD) ? _AGENTS_DIR_PROD : existsSync4(_AGENTS_DIR_DEV) ? _AGENTS_DIR_DEV : _AGENTS_DIR_PROD;
@@ -1699,7 +1821,8 @@ function generateSourceDefaults() {
1699
1821
  user_custom: { category_enabled: true, category_desc: "\u7528\u6237\u81EA\u5B9A\u4E49 L2 \u903B\u8F91\u89C4\u5219", rules: [] }
1700
1822
  },
1701
1823
  L3_skills: _l3SkillsToRules(),
1702
- agent_rules: _agentRulesToDefaults()
1824
+ agent_rules: _agentRulesToDefaults(),
1825
+ scenario_rules: _scenarioRulesToDefaults()
1703
1826
  };
1704
1827
  }
1705
1828
  function normalizePatternForFingerprint(pattern) {
@@ -1792,9 +1915,9 @@ function _mergeSkillCategories(userCfg, srcDefaults, layerKey) {
1792
1915
  }
1793
1916
  }
1794
1917
  }
1795
- function _mergeAgentRules(userCfg, srcDefaults) {
1796
- const userAgent = userCfg.agent_rules || (userCfg.agent_rules = {});
1797
- 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] || {};
1798
1921
  for (const [agentId, srcCats] of Object.entries(srcAgent)) {
1799
1922
  if (!(agentId in userAgent)) {
1800
1923
  userAgent[agentId] = JSON.parse(JSON.stringify(srcCats));
@@ -1853,6 +1976,7 @@ function mergeConfig(userCfg, srcDefaults) {
1853
1976
  _mergeRuleCategories(cfg, srcDefaults, "L2_rules");
1854
1977
  _mergeSkillCategories(cfg, srcDefaults, "L3_skills");
1855
1978
  _mergeAgentRules(cfg, srcDefaults);
1979
+ _mergeAgentRules(cfg, srcDefaults, "scenario_rules");
1856
1980
  return cfg;
1857
1981
  }
1858
1982
  function _migrateCategoriesAcrossLayers(cfg, srcLayer, dstLayer, catNames) {
@@ -2178,7 +2302,85 @@ function updateAgentCategoryEnabled(agentId, category, enabled) {
2178
2302
  saveRuntimeConfig(runtime);
2179
2303
  return runtime;
2180
2304
  }
2181
- 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;
2182
2384
  var init_runtime_config = __esm({
2183
2385
  "src/infra/runtime-config.ts"() {
2184
2386
  "use strict";
@@ -2203,6 +2405,8 @@ var init_runtime_config = __esm({
2203
2405
  };
2204
2406
  _AGENTS_DIR_PROD = join2(__dirname4, "agents");
2205
2407
  _AGENTS_DIR_DEV = join2(__dirname4, "..", "..", "..", "agent_moss", "agents");
2408
+ _SCENARIOS_DIR_PROD = join2(__dirname4, "scenarios");
2409
+ _SCENARIOS_DIR_DEV = join2(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
2206
2410
  SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
2207
2411
  }
2208
2412
  });
@@ -2254,6 +2458,21 @@ function getAgentExtensions(agentId) {
2254
2458
  );
2255
2459
  return ext;
2256
2460
  }
2461
+ function mergeAgentExtensions(...exts) {
2462
+ const nonNull = exts.filter((e) => !!e);
2463
+ if (nonNull.length === 0) return null;
2464
+ const base = nonNull[0];
2465
+ return {
2466
+ agent_id: base.agent_id,
2467
+ description: base.description,
2468
+ extra_command_patterns: nonNull.flatMap((e) => e.extra_command_patterns || []),
2469
+ extra_injection_keywords: nonNull.flatMap((e) => e.extra_injection_keywords || []),
2470
+ extra_sensitive_paths: nonNull.flatMap((e) => e.extra_sensitive_paths || []),
2471
+ builtin_safe_skills: nonNull.flatMap((e) => e.builtin_safe_skills || []),
2472
+ builtin_safe_action_types: nonNull.flatMap((e) => e.builtin_safe_action_types || []),
2473
+ extra_skills: nonNull.flatMap((e) => e.extra_skills || [])
2474
+ };
2475
+ }
2257
2476
  var __filename4, __dirname5, AGENTS_DIR_PROD, AGENTS_DIR_DEV_FALLBACK, cache;
2258
2477
  var init_agents = __esm({
2259
2478
  "src/agents/index.ts"() {
@@ -2267,6 +2486,77 @@ var init_agents = __esm({
2267
2486
  }
2268
2487
  });
2269
2488
 
2489
+ // src/scenarios/index.ts
2490
+ import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
2491
+ import { resolve as resolve4, dirname as dirname5 } from "path";
2492
+ import { fileURLToPath as fileURLToPath7 } from "url";
2493
+ function resolveScenariosDir() {
2494
+ if (existsSync6(SCENARIOS_DIR_PROD)) return SCENARIOS_DIR_PROD;
2495
+ if (existsSync6(SCENARIOS_DIR_DEV_FALLBACK)) return SCENARIOS_DIR_DEV_FALLBACK;
2496
+ return SCENARIOS_DIR_PROD;
2497
+ }
2498
+ function getScenarioExtensions(scenarioId) {
2499
+ if (!scenarioId) return null;
2500
+ if (cache2.has(scenarioId)) return cache2.get(scenarioId) ?? null;
2501
+ let ext = null;
2502
+ try {
2503
+ const { hasRuntimeConfig: hasRuntimeConfig2, loadRuntimeConfig: loadRuntimeConfig2, getEnabledScenarioExtensions: getEnabledScenarioExtensions2 } = (init_runtime_config(), __toCommonJS(runtime_config_exports));
2504
+ if (hasRuntimeConfig2()) {
2505
+ ext = getEnabledScenarioExtensions2(loadRuntimeConfig2(), scenarioId);
2506
+ }
2507
+ } catch {
2508
+ }
2509
+ if (ext === null) {
2510
+ const scenariosDir = resolveScenariosDir();
2511
+ const rulesFile = resolve4(scenariosDir, scenarioId, "rules.json");
2512
+ if (!existsSync6(rulesFile)) {
2513
+ cache2.set(scenarioId, null);
2514
+ return null;
2515
+ }
2516
+ try {
2517
+ const raw = readFileSync6(rulesFile, "utf-8");
2518
+ const data = JSON.parse(raw);
2519
+ const id = data.scenario_id || data.agent_id || "";
2520
+ if (!id) {
2521
+ console.warn(`[AgentMoss scenarios] rules.json \u7F3A\u5C11 scenario_id \u5B57\u6BB5: ${rulesFile}`);
2522
+ cache2.set(scenarioId, null);
2523
+ return null;
2524
+ }
2525
+ ext = {
2526
+ agent_id: id,
2527
+ description: data.description || "",
2528
+ extra_command_patterns: data.extra_command_patterns || [],
2529
+ extra_injection_keywords: data.extra_injection_keywords || [],
2530
+ extra_sensitive_paths: data.extra_sensitive_paths || [],
2531
+ builtin_safe_skills: data.builtin_safe_skills || [],
2532
+ builtin_safe_action_types: data.builtin_safe_action_types || [],
2533
+ extra_skills: data.extra_skills || []
2534
+ };
2535
+ } catch (e) {
2536
+ console.error(`[AgentMoss scenarios] \u52A0\u8F7D\u573A\u666F ${scenarioId} rules.json \u5931\u8D25:`, e.message);
2537
+ cache2.set(scenarioId, null);
2538
+ return null;
2539
+ }
2540
+ }
2541
+ cache2.set(scenarioId, ext);
2542
+ console.log(
2543
+ `[AgentMoss scenarios] \u52A0\u8F7D\u573A\u666F ${scenarioId} \u5B9A\u5236\u89C4\u5219: ${ext.extra_command_patterns?.length ?? 0} cmd patterns, ${ext.extra_injection_keywords?.length ?? 0} injection keywords, ${ext.extra_sensitive_paths?.length ?? 0} sensitive paths`
2544
+ );
2545
+ return ext;
2546
+ }
2547
+ var __filename5, __dirname6, SCENARIOS_DIR_PROD, SCENARIOS_DIR_DEV_FALLBACK, cache2;
2548
+ var init_scenarios = __esm({
2549
+ "src/scenarios/index.ts"() {
2550
+ "use strict";
2551
+ init_esm_shims();
2552
+ __filename5 = fileURLToPath7(import.meta.url);
2553
+ __dirname6 = dirname5(__filename5);
2554
+ SCENARIOS_DIR_PROD = resolve4(__dirname6, "scenarios");
2555
+ SCENARIOS_DIR_DEV_FALLBACK = resolve4(__dirname6, "../../../agent_moss/scenarios");
2556
+ cache2 = /* @__PURE__ */ new Map();
2557
+ }
2558
+ });
2559
+
2270
2560
  // src/engine/heuristic.ts
2271
2561
  function runHeuristicCheck(input) {
2272
2562
  const command = `${input.aNext.actionType}: ${input.aNext.actionDetail}`;
@@ -2306,7 +2596,10 @@ function runHeuristicCheck(input) {
2306
2596
  }
2307
2597
  }
2308
2598
  highestRiskType = topRiskType || highestRiskType;
2309
- const agentExt = getAgentExtensions(input.agentId);
2599
+ const agentExt = mergeAgentExtensions(
2600
+ getAgentExtensions(input.agentId),
2601
+ getScenarioExtensions(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "")
2602
+ );
2310
2603
  if (agentExt?.extra_command_patterns) {
2311
2604
  for (const rule of agentExt.extra_command_patterns) {
2312
2605
  try {
@@ -2479,13 +2772,13 @@ function checkSensitivePath(command, cwd, agentExt) {
2479
2772
  const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
2480
2773
  const isWriteOpFlag = isWriteOperation("", command);
2481
2774
  const pathScanText = normalizePathSegments(stripLiteralTextSegments(command));
2482
- const pathMatches = (text, path3) => {
2483
- const parts = path3.split(/(\(\?!.*?\))/);
2775
+ const pathMatches = (text, path5) => {
2776
+ const parts = path5.split(/(\(\?!.*?\))/);
2484
2777
  const escaped = parts.map((part) => {
2485
2778
  if (part.startsWith("(?!")) return part;
2486
2779
  return part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2487
2780
  }).join("");
2488
- const re = 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");
2489
2782
  return re.test(text);
2490
2783
  };
2491
2784
  for (const sp of ROOT_SENSITIVE_PATHS) {
@@ -2749,6 +3042,7 @@ var init_heuristic = __esm({
2749
3042
  init_esm_shims();
2750
3043
  init_config();
2751
3044
  init_agents();
3045
+ init_scenarios();
2752
3046
  init_rules_loader();
2753
3047
  init_logic_rules();
2754
3048
  INJECTION_KEYWORDS = (() => {
@@ -2783,16 +3077,16 @@ __export(token_stats_exports, {
2783
3077
  recordTokenUsage: () => recordTokenUsage,
2784
3078
  resetTokenStats: () => resetTokenStats
2785
3079
  });
2786
- import { readFileSync as 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";
2787
3081
  function getStatsPath() {
2788
3082
  const envPath = process.env[ENV_STATS_PATH];
2789
3083
  if (envPath) return envPath;
2790
3084
  return getTokenStatsPath();
2791
3085
  }
2792
3086
  function loadAllRecords(statsPath) {
2793
- if (!existsSync8(statsPath)) return [];
3087
+ if (!existsSync10(statsPath)) return [];
2794
3088
  try {
2795
- const data = JSON.parse(readFileSync9(statsPath, "utf-8"));
3089
+ const data = JSON.parse(readFileSync11(statsPath, "utf-8"));
2796
3090
  if (Array.isArray(data)) return data;
2797
3091
  if (data && Array.isArray(data.records)) return data.records;
2798
3092
  return [];
@@ -2803,7 +3097,7 @@ function loadAllRecords(statsPath) {
2803
3097
  function saveRecords(statsPath, records) {
2804
3098
  try {
2805
3099
  const dir = statsPath.substring(0, statsPath.lastIndexOf("/"));
2806
- if (dir && !existsSync8(dir)) mkdirSync2(dir, { recursive: true });
3100
+ if (dir && !existsSync10(dir)) mkdirSync2(dir, { recursive: true });
2807
3101
  writeFileSync2(statsPath, JSON.stringify(records, null, 2), "utf-8");
2808
3102
  } catch (e) {
2809
3103
  }
@@ -2930,7 +3224,7 @@ function getTokenTrend(days = 0, startDate, endDate) {
2930
3224
  }
2931
3225
  function resetTokenStats() {
2932
3226
  const statsPath = getStatsPath();
2933
- if (existsSync8(statsPath)) {
3227
+ if (existsSync10(statsPath)) {
2934
3228
  try {
2935
3229
  unlinkSync2(statsPath);
2936
3230
  } catch {
@@ -2961,28 +3255,28 @@ init_skill_loader();
2961
3255
 
2962
3256
  // src/engine/template-loader.ts
2963
3257
  init_esm_shims();
2964
- import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
2965
- import { resolve as resolve4, dirname as dirname5 } from "path";
2966
- import { fileURLToPath as fileURLToPath7 } from "url";
2967
- var __filename5 = fileURLToPath7(import.meta.url);
2968
- var __dirname6 = dirname5(__filename5);
2969
- var TEMPLATES_DIR = resolve4(__dirname6, "../templates");
2970
- 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");
2971
3265
  var TEMPLATE_FILES = ["prompt1_template.txt", "prompt2_template.txt", "security_judge_template.txt"];
2972
3266
  var cachedTemplates = null;
2973
3267
  function loadTemplates() {
2974
3268
  if (cachedTemplates) return cachedTemplates;
2975
3269
  cachedTemplates = {};
2976
- const dir = existsSync6(TEMPLATES_DIR) ? TEMPLATES_DIR : TEMPLATES_DIR_FALLBACK;
2977
- if (!existsSync6(dir)) {
3270
+ const dir = existsSync7(TEMPLATES_DIR) ? TEMPLATES_DIR : TEMPLATES_DIR_FALLBACK;
3271
+ if (!existsSync7(dir)) {
2978
3272
  console.warn("[AgentMoss] Templates directory not found:", TEMPLATES_DIR, "and fallback:", TEMPLATES_DIR_FALLBACK);
2979
3273
  return cachedTemplates;
2980
3274
  }
2981
3275
  for (const file of TEMPLATE_FILES) {
2982
- const filePath = resolve4(dir, file);
2983
- if (existsSync6(filePath)) {
3276
+ const filePath = resolve5(dir, file);
3277
+ if (existsSync7(filePath)) {
2984
3278
  const key = file.replace(".txt", "").replace("_template", "");
2985
- cachedTemplates[key] = readFileSync6(filePath, "utf-8");
3279
+ cachedTemplates[key] = readFileSync7(filePath, "utf-8");
2986
3280
  }
2987
3281
  }
2988
3282
  console.log(`[AgentMoss] Loaded ${Object.keys(cachedTemplates).length} templates from ${dir}`);
@@ -2991,8 +3285,8 @@ function loadTemplates() {
2991
3285
 
2992
3286
  // src/engine/script-analyzer.ts
2993
3287
  init_esm_shims();
2994
- import { readFileSync as readFileSync7 } from "fs";
2995
- 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";
2996
3290
  var SUSPICIOUS_PATTERNS = [
2997
3291
  // Network exfiltration (curl/wget POST in scripts is almost never legitimate)
2998
3292
  { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
@@ -3125,8 +3419,8 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
3125
3419
  }
3126
3420
  function readScriptContent(scriptPath, maxLines = 500) {
3127
3421
  try {
3128
- const expandedPath = scriptPath.startsWith("~") ? join3(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve5(scriptPath);
3129
- 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");
3130
3424
  const lines = raw.split("\n").slice(0, maxLines);
3131
3425
  return { content: lines.join("\n"), lineCount: lines.length, error: "" };
3132
3426
  } catch (e) {
@@ -3180,133 +3474,1524 @@ ${result.scriptContent}
3180
3474
  init_esm_shims();
3181
3475
  init_heuristic();
3182
3476
  init_logic_rules();
3183
- import { readFileSync as readFileSync8, statSync, existsSync as existsSync7 } from "fs";
3184
- import { resolve as resolve6, join as join4, isAbsolute, dirname as dirname6 } from "path";
3185
- import { homedir as homedir2 } from "os";
3186
- function emptyChain() {
3187
- return {
3188
- nodes: [],
3189
- entryPath: "",
3190
- maxDepthReached: false,
3191
- maxSizeReached: false,
3192
- totalSize: 0,
3193
- totalLines: 0,
3194
- hasCritical: false,
3195
- hasHigh: false,
3196
- firstHitNode: null,
3197
- error: ""
3198
- };
3477
+ import { statSync as statSync2, existsSync as existsSync9 } from "fs";
3478
+ import { resolve as resolve8, join as join5, isAbsolute as isAbsolute2 } from "path";
3479
+ import { homedir as homedir3 } from "os";
3480
+
3481
+ // src/engine/script-engine/tracker.ts
3482
+ init_esm_shims();
3483
+ import * as fs3 from "fs";
3484
+ import * as path3 from "path";
3485
+ import * as os from "os";
3486
+
3487
+ // src/engine/script-engine/backends/python-backend.ts
3488
+ init_esm_shims();
3489
+ import { parser } from "@lezer/python";
3490
+ var MAX_LINES = 500;
3491
+ var PURE_BUILTINS = /* @__PURE__ */ new Set([
3492
+ "len",
3493
+ "print",
3494
+ "str",
3495
+ "int",
3496
+ "float",
3497
+ "bool",
3498
+ "list",
3499
+ "dict",
3500
+ "set",
3501
+ "tuple",
3502
+ "range",
3503
+ "sorted",
3504
+ "enumerate",
3505
+ "zip",
3506
+ "map",
3507
+ "filter",
3508
+ "sum",
3509
+ "min",
3510
+ "max",
3511
+ "abs",
3512
+ "round",
3513
+ "isinstance",
3514
+ "type",
3515
+ "open",
3516
+ "input",
3517
+ "repr",
3518
+ "ord",
3519
+ "chr",
3520
+ "hex",
3521
+ "oct",
3522
+ "bin",
3523
+ "format",
3524
+ "id",
3525
+ "hash",
3526
+ "any",
3527
+ "all",
3528
+ "next",
3529
+ "iter",
3530
+ "reversed",
3531
+ "pow",
3532
+ "divmod"
3533
+ ]);
3534
+ var CHILD_CALL_RE = /(?:^|\s|&&|\|\|)\s*((?:\/bin\/)?bash|sh|zsh|(?:python|python3|node|ruby|perl|php|lua))\s+(\S+(?:\.(?:sh|shrc|py|js|rb|pl|php|lua))?)([^\n;|&]*?)$/gim;
3535
+ var SUBPROCESS_FUNCS = /* @__PURE__ */ new Set(["run", "call", "Popen", "check_call", "check_output"]);
3536
+ var OS_FUNCS = /* @__PURE__ */ new Set(["system", "popen", "popen2", "popen3", "popen4"]);
3537
+ function buildLineStarts(content) {
3538
+ const starts = [0];
3539
+ for (let i = 0; i < content.length; i++) {
3540
+ if (content[i] === "\n") starts.push(i + 1);
3541
+ }
3542
+ return starts;
3543
+ }
3544
+ function lineAt(lineStarts, pos) {
3545
+ let lo = 0;
3546
+ let hi = lineStarts.length - 1;
3547
+ while (lo < hi) {
3548
+ const mid = lo + hi + 1 >> 1;
3549
+ if (lineStarts[mid] <= pos) lo = mid;
3550
+ else hi = mid - 1;
3551
+ }
3552
+ return lo + 1;
3553
+ }
3554
+ function markNodeLines(node, lines, lineStarts) {
3555
+ const start = lineAt(lineStarts, node.from);
3556
+ const end = node.to > node.from ? lineAt(lineStarts, node.to - 1) : start;
3557
+ for (let i = start; i <= end; i++) lines.add(i);
3558
+ }
3559
+ function reconstruct(sourceLines, lines) {
3560
+ const out = [];
3561
+ for (const i of [...lines].sort((a, b) => a - b)) {
3562
+ if (i >= 1 && i <= sourceLines.length) out.push(sourceLines[i - 1]);
3563
+ }
3564
+ return out.join("\n");
3565
+ }
3566
+ function makeEnv(argv) {
3567
+ return { argv, vars: {} };
3568
+ }
3569
+ var SOURCE = "";
3570
+ function srcSlice(node) {
3571
+ return SOURCE.slice(node.from, node.to);
3572
+ }
3573
+ function stringValue(tokenText) {
3574
+ let s = tokenText;
3575
+ if (s.length >= 2) {
3576
+ const q = s[0];
3577
+ if ((q === '"' || q === "'") && s.endsWith(q)) s = s.slice(1, -1);
3578
+ }
3579
+ s = s.replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\r/g, "\r").replace(/\\\\/g, "\\").replace(/\\"/g, '"').replace(/\\'/g, "'");
3580
+ return s;
3581
+ }
3582
+ function isSysArgv(node) {
3583
+ if (!node || node.name !== "MemberExpression") return false;
3584
+ const first = node.firstChild;
3585
+ if (!first || first.name !== "VariableName" || srcSlice(first) !== "sys") return false;
3586
+ const prop = node.getChild("PropertyName");
3587
+ return prop !== null && srcSlice(prop) === "argv";
3588
+ }
3589
+ function evalExpr(node, env) {
3590
+ if (!node) return null;
3591
+ const name = node.name;
3592
+ if (name === "String") return stringValue(srcSlice(node));
3593
+ if (name === "Number") return srcSlice(node);
3594
+ if (name === "VariableName") {
3595
+ const id = srcSlice(node);
3596
+ return id in env.vars ? env.vars[id] : null;
3597
+ }
3598
+ if (name === "MemberExpression") {
3599
+ const bracket = node.getChild("[");
3600
+ if (bracket) {
3601
+ const inner = node.firstChild;
3602
+ if (inner && isSysArgv(inner)) {
3603
+ const idxNode = bracket.nextSibling;
3604
+ if (idxNode && idxNode.name === "Number") {
3605
+ const idx = parseInt(srcSlice(idxNode), 10);
3606
+ if (idx >= 1 && idx <= env.argv.length) return env.argv[idx - 1];
3607
+ return null;
3608
+ }
3609
+ }
3610
+ return null;
3611
+ }
3612
+ if (isSysArgv(node)) return env.argv[0] ?? null;
3613
+ return null;
3614
+ }
3615
+ if (name === "CallExpression") {
3616
+ const func = node.firstChild;
3617
+ if (func && func.name === "VariableName" && srcSlice(func) === "len") {
3618
+ const argList = node.getChild("ArgList");
3619
+ const inner = argList ? firstNamedChild(argList) : null;
3620
+ if (inner && isSysArgv(inner)) return String(env.argv.length + 1);
3621
+ }
3622
+ return null;
3623
+ }
3624
+ if (name === "ConditionalExpression") {
3625
+ const ifKw = node.getChild("if");
3626
+ const elseKw = node.getChild("else");
3627
+ if (!ifKw || !elseKw) return null;
3628
+ const thenNode = node.firstChild;
3629
+ const condNode = ifKw.nextSibling;
3630
+ const orelseNode = elseKw.nextSibling;
3631
+ if (!thenNode || !condNode || !orelseNode) return null;
3632
+ const cond = evalCondition(condNode, env);
3633
+ if (cond === true) return evalExpr(thenNode, env);
3634
+ if (cond === false) return evalExpr(orelseNode, env);
3635
+ return null;
3636
+ }
3637
+ if (name === "BinaryExpression") {
3638
+ const op = findOp(node);
3639
+ if (op === "+") {
3640
+ const left = evalExpr(node.firstChild, env);
3641
+ const right = evalExpr(lastNamedChild(node), env);
3642
+ if (left !== null && right !== null) return left + right;
3643
+ }
3644
+ return null;
3645
+ }
3646
+ return null;
3199
3647
  }
3200
- function newNode(path3, depth) {
3201
- return {
3202
- scriptPath: path3,
3203
- rawContent: "",
3204
- preprocessedContent: "",
3205
- lineCount: 0,
3206
- fileSize: 0,
3207
- depth,
3208
- childPaths: [],
3209
- error: "",
3210
- l1Hit: false,
3211
- l1RiskLevel: "",
3212
- l1RiskType: "",
3213
- l1Reason: "",
3214
- l1MatchedPatterns: [],
3215
- l2Hit: false,
3216
- l2ViolatedRule: "",
3217
- l2RiskLevel: "",
3218
- l2RiskType: "",
3219
- l2Reason: "",
3220
- matchedKeywords: [],
3221
- isSuspicious: false,
3222
- keywordRiskLevel: "",
3223
- keywordRiskReason: ""
3224
- };
3648
+ function firstNamedChild(node) {
3649
+ let c = node.firstChild;
3650
+ while (c) {
3651
+ if (c.name !== "(" && c.name !== ")" && c.name !== "," && c.name !== ":") return c;
3652
+ c = c.nextSibling;
3653
+ }
3654
+ return null;
3225
3655
  }
3226
- function stripComments(content) {
3227
- 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;
3228
3663
  }
3229
- function stripOutputText(content) {
3230
- let result = content.replace(/echo\s+".*?"/gs, 'echo ""');
3231
- result = result.replace(/echo\s+'.*?'/gs, "echo ''");
3232
- result = result.replace(/printf\s+".*?"[^\n]*/gs, 'printf ""');
3233
- result = result.replace(/printf\s+'.*?'[^\n]*/gs, "printf ''");
3234
- 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;
3235
3673
  }
3236
- function expandSimpleVariables(content) {
3237
- const varMap = {};
3238
- const assignmentRe = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.+)$/;
3239
- const collectAssignments = (text) => {
3240
- for (const line of text.split("\n")) {
3241
- const m = line.match(assignmentRe);
3242
- if (m) {
3243
- const varName = m[1];
3244
- let varVal = m[2].trim();
3245
- if (varVal && !varVal.includes("$") && !varVal.includes("`")) {
3246
- if (varVal.startsWith('"') && varVal.endsWith('"') || varVal.startsWith("'") && varVal.endsWith("'")) {
3247
- varVal = varVal.slice(1, -1);
3248
- }
3249
- varMap[varName] = varVal;
3250
- }
3674
+ function evalCondition(node, env) {
3675
+ if (!node) return null;
3676
+ const name = node.name;
3677
+ if (name === "Boolean") {
3678
+ const t = srcSlice(node).trim();
3679
+ return t === "True" ? true : t === "False" ? false : null;
3680
+ }
3681
+ if (name === "BinaryExpression") {
3682
+ const op = findOp(node);
3683
+ if (op === "and" || op === "or") {
3684
+ const parts2 = namedChildren(node);
3685
+ const vals = parts2.map((p) => evalCondition(p, env));
3686
+ if (op === "and") {
3687
+ if (vals.some((v) => v === false)) return false;
3688
+ if (vals.every((v) => v === true)) return true;
3689
+ return null;
3251
3690
  }
3691
+ if (vals.some((v) => v === true)) return true;
3692
+ if (vals.every((v) => v === false)) return false;
3693
+ return null;
3252
3694
  }
3253
- };
3254
- collectAssignments(content);
3255
- let expanded = content;
3256
- for (let round = 0; round < 3; round++) {
3257
- const sortedVars = Object.keys(varMap).sort((a, b) => b.length - a.length);
3258
- let newExpanded = expanded;
3259
- for (const v of sortedVars) {
3260
- const val = varMap[v];
3261
- newExpanded = newExpanded.split("${" + v + "}").join(val);
3262
- newExpanded = newExpanded.split("$" + v).join(val);
3263
- }
3264
- if (newExpanded === expanded) break;
3265
- expanded = newExpanded;
3266
- collectAssignments(expanded);
3267
- }
3268
- return expanded;
3269
- }
3270
- function preprocessScriptContent(content) {
3271
- let result = stripComments(content);
3272
- result = stripOutputText(result);
3273
- result = expandSimpleVariables(result);
3274
- return result;
3695
+ const parts = namedChildren(node);
3696
+ if (parts.length !== 3) return null;
3697
+ const [left, opNode, right] = parts;
3698
+ const opText = srcSlice(opNode).trim();
3699
+ return evalCompare(left, opText, right, env);
3700
+ }
3701
+ if (name === "UnaryExpression") {
3702
+ const kw = node.getChild("not");
3703
+ if (kw) {
3704
+ const operand = kw.nextSibling;
3705
+ const v = evalCondition(operand, env);
3706
+ return v === null ? null : !v;
3707
+ }
3708
+ return null;
3709
+ }
3710
+ if (name === "CallExpression") {
3711
+ return null;
3712
+ }
3713
+ return null;
3275
3714
  }
3276
- var SUSPICIOUS_PATTERNS2 = [
3277
- { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
3278
- { name: "curl --data", pattern: /curl\s+(.*\s)?--data/i },
3279
- { name: "curl -d", pattern: /curl\s+(.*\s)?-d\s/i },
3280
- { name: "wget --post-data", pattern: /wget\s+(.*\s)?--post-data/i },
3281
- { name: "wget --post-file", pattern: /wget\s+(.*\s)?--post-file/i },
3282
- { name: "nc -e", pattern: /\bnc\s+.*-e\b/ },
3283
- { name: "ncat -e", pattern: /\bncat\s+.*-e\b/ },
3284
- { name: "/dev/tcp", pattern: /\/dev\/tcp\// },
3285
- { name: "/dev/udp", pattern: /\/dev\/udp\// },
3286
- { name: "$(base64", pattern: /\$\(\s*base64/ },
3287
- { name: "`base64", pattern: /`\s*base64/ },
3288
- { name: "base64 decode pipe", pattern: /base64\s+(-d|--decode)\s*\|/ },
3289
- { name: "/etc/shadow", pattern: /\/etc\/shadow/ },
3290
- { name: "id_rsa", pattern: /\bid_rsa\b/ },
3291
- { name: "id_ed25519", pattern: /\bid_ed25519\b/ },
3292
- { name: "crontab install", pattern: /crontab\s+-/ },
3293
- { name: "authorized_keys", pattern: /authorized_keys/ },
3294
- { name: "ssh no host key check", pattern: /ssh\s+.*StrictHostKeyChecking\s*=\s*no/ }
3295
- ];
3296
- var SCRIPT_EXEC_PATTERNS2 = [
3297
- /(?:^|\s|&&|\|\|)\s*bash\s+(\S+)/i,
3298
- /(?:^|\s|&&|\|\|)\s*\/bin\/bash\s+(\S+)/i,
3299
- /(?:^|\s|&&|\|\|)\s*sh\s+(\S+)/i,
3300
- /(?:^|\s|&&|\|\|)\s*zsh\s+(\S+)/i,
3301
- /\.\/(\S+)/,
3302
- /(?:^|\s|&&|\|\|)\s*source\s+(\S+)/i,
3303
- /(?:^|\s|&&|\|\|)\s*\.\s+(\S+)/,
3304
- /(?:^|\s|&&|\|\|)\s*python3?\s+(\S+\.py)/i,
3305
- /(?:^|\s|&&|\|\|)\s*node\s+(\S+\.js)/i,
3306
- /(?:^|\s|&&|\|\|)\s*ruby\s+(\S+\.rb)/i,
3307
- /(?:^|\s|&&|\|\|)\s*perl\s+(\S+\.pl)/i,
3308
- /(?:^|\s|&&|\|\|)\s*php\s+(\S+\.php)/i,
3309
- /(?:^|\s|&&|\|\|)\s*lua\s+(\S+\.lua)/i
3715
+ function namedChildren(node) {
3716
+ const out = [];
3717
+ let c = node.firstChild;
3718
+ while (c) {
3719
+ if (c.name !== "(" && c.name !== ")" && c.name !== "," && c.name !== ":" && c.name !== "[" && c.name !== "]") {
3720
+ out.push(c);
3721
+ }
3722
+ c = c.nextSibling;
3723
+ }
3724
+ return out;
3725
+ }
3726
+ function evalCompare(left, opText, right, env) {
3727
+ if (opText === "==" && left.name === "VariableName" && srcSlice(left) === "__name__") {
3728
+ const rv = evalExpr(right, env);
3729
+ if (rv !== null) return rv === "__main__";
3730
+ return null;
3731
+ }
3732
+ if (opText === "==" || opText === "!=") {
3733
+ const lv = evalExpr(left, env);
3734
+ const rv = evalExpr(right, env);
3735
+ if (lv !== null && rv !== null) {
3736
+ return opText === "==" ? lv === rv : lv !== rv;
3737
+ }
3738
+ return null;
3739
+ }
3740
+ if (opText === "in") {
3741
+ const lv = evalExpr(left, env);
3742
+ if (lv === null) return null;
3743
+ const rightName = right.name;
3744
+ if (rightName === "TupleExpression" || rightName === "ArrayExpression" || rightName === "SetExpression" || rightName === "ParenthesizedExpression") {
3745
+ const container = rightName === "ParenthesizedExpression" && right.firstChild && right.firstChild.name === "TupleExpression" ? right.firstChild : right;
3746
+ for (const elt of namedChildren(container)) {
3747
+ const ev = evalExpr(elt, env);
3748
+ if (ev !== null && ev === lv) return true;
3749
+ }
3750
+ return false;
3751
+ }
3752
+ const rv = evalExpr(right, env);
3753
+ if (rv !== null) return rv.includes(lv);
3754
+ return null;
3755
+ }
3756
+ if (opText === ">" || opText === ">=" || opText === "<" || opText === "<=") {
3757
+ const lv = evalExpr(left, env);
3758
+ const rv = evalExpr(right, env);
3759
+ if (lv === null || rv === null) return null;
3760
+ const ln = parseInt(lv, 10);
3761
+ const rn = parseInt(rv, 10);
3762
+ if (Number.isNaN(ln) || Number.isNaN(rn)) return null;
3763
+ if (opText === ">") return ln > rn;
3764
+ if (opText === ">=") return ln >= rn;
3765
+ if (opText === "<") return ln < rn;
3766
+ return ln <= rn;
3767
+ }
3768
+ return null;
3769
+ }
3770
+ function processStmts(stmts, env, lineStarts, lines, branches) {
3771
+ for (const s of stmts) processStmt(s, env, lineStarts, lines, branches);
3772
+ }
3773
+ function invalidateEnvOnCalls(node, env) {
3774
+ const cursor = node.cursor();
3775
+ cursor.iterate((n) => {
3776
+ const nd = n.node;
3777
+ if (nd.name !== "CallExpression") return true;
3778
+ const func = nd.firstChild;
3779
+ if (func && func.name === "VariableName" && !PURE_BUILTINS.has(srcSlice(func))) {
3780
+ for (const k of Object.keys(env.vars)) env.vars[k] = null;
3781
+ return false;
3782
+ }
3783
+ return true;
3784
+ });
3785
+ }
3786
+ function processStmt(stmt, env, lineStarts, lines, branches) {
3787
+ const name = stmt.name;
3788
+ if (name === "AssignStatement") {
3789
+ const target = stmt.firstChild;
3790
+ if (target && target.name === "VariableName") {
3791
+ const valNode = target.nextSibling ? target.nextSibling.nextSibling : null;
3792
+ invalidateEnvOnCalls(stmt, env);
3793
+ const val = evalExpr(valNode, env);
3794
+ env.vars[srcSlice(target)] = val;
3795
+ }
3796
+ markNodeLines(stmt, lines, lineStarts);
3797
+ return;
3798
+ }
3799
+ if (name === "UpdateStatement") {
3800
+ const target = stmt.firstChild;
3801
+ if (target && target.name === "VariableName") env.vars[srcSlice(target)] = null;
3802
+ invalidateEnvOnCalls(stmt, env);
3803
+ markNodeLines(stmt, lines, lineStarts);
3804
+ return;
3805
+ }
3806
+ if (name === "IfStatement") {
3807
+ processIf(stmt, env, lineStarts, lines, branches);
3808
+ return;
3809
+ }
3810
+ if (name === "ForStatement" || name === "WhileStatement") {
3811
+ markNodeLines(stmt, lines, lineStarts);
3812
+ for (const body of stmt.getChildren("Body")) {
3813
+ processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
3814
+ }
3815
+ return;
3816
+ }
3817
+ if (name === "TryStatement") {
3818
+ markNodeLines(stmt, lines, lineStarts);
3819
+ for (const body of stmt.getChildren("Body")) {
3820
+ processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
3821
+ }
3822
+ return;
3823
+ }
3824
+ if (name === "WithStatement") {
3825
+ markNodeLines(stmt, lines, lineStarts);
3826
+ const body = stmt.getChild("Body");
3827
+ if (body) processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
3828
+ return;
3829
+ }
3830
+ if (name === "MatchStatement") {
3831
+ markNodeLines(stmt, lines, lineStarts);
3832
+ for (const clause of stmt.getChildren("MatchClause")) {
3833
+ const body = clause.getChild("Body");
3834
+ if (body) processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
3835
+ }
3836
+ return;
3837
+ }
3838
+ if (name === "FunctionDefinition" || name === "ClassDefinition") {
3839
+ markDefTree(stmt, lines, lineStarts);
3840
+ return;
3841
+ }
3842
+ if (name === "ExpressionStatement") {
3843
+ invalidateEnvOnCalls(stmt, env);
3844
+ }
3845
+ markNodeLines(stmt, lines, lineStarts);
3846
+ }
3847
+ function markDefTree(stmt, lines, lineStarts) {
3848
+ const cursor = stmt.cursor();
3849
+ cursor.iterate((n) => {
3850
+ markNodeLines(n.node, lines, lineStarts);
3851
+ return true;
3852
+ });
3853
+ }
3854
+ function bodyStmts(body) {
3855
+ const out = [];
3856
+ let c = body.firstChild;
3857
+ while (c) {
3858
+ if (c.name !== ":" && c.name !== ";") out.push(c);
3859
+ c = c.nextSibling;
3860
+ }
3861
+ return out;
3862
+ }
3863
+ function processIf(stmt, env, lineStarts, lines, branches) {
3864
+ const sections = [];
3865
+ let c = stmt.firstChild;
3866
+ while (c) {
3867
+ if (c.name === "if" || c.name === "elif") {
3868
+ sections.push({ cond: c.nextSibling ?? null, body: null, isElse: false });
3869
+ } else if (c.name === "else") {
3870
+ sections.push({ cond: null, body: null, isElse: true });
3871
+ } else if (c.name === "Body") {
3872
+ if (sections.length > 0 && sections[sections.length - 1].body === null) {
3873
+ sections[sections.length - 1].body = c;
3874
+ }
3875
+ }
3876
+ c = c.nextSibling;
3877
+ }
3878
+ processIfSections(stmt, sections, 0, env, lineStarts, lines, branches);
3879
+ }
3880
+ function processIfSections(_stmt, sections, idx, env, lineStarts, lines, branches) {
3881
+ if (idx >= sections.length) return;
3882
+ const s = sections[idx];
3883
+ if (s.cond) markNodeLines(s.cond, lines, lineStarts);
3884
+ if (s.isElse) {
3885
+ if (s.body) processStmts(bodyStmts(s.body), env, lineStarts, lines, branches);
3886
+ return;
3887
+ }
3888
+ const cond = s.cond ? evalCondition(s.cond, env) : null;
3889
+ branches.push({
3890
+ kind: "if",
3891
+ label: s.cond ? srcSlice(s.cond) : "",
3892
+ body: "",
3893
+ reachable: cond !== false
3894
+ });
3895
+ if (cond === true) {
3896
+ if (s.body) processStmts(bodyStmts(s.body), env, lineStarts, lines, branches);
3897
+ return;
3898
+ }
3899
+ if (cond === false) {
3900
+ processIfSections(_stmt, sections, idx + 1, env, lineStarts, lines, branches);
3901
+ return;
3902
+ }
3903
+ if (s.body) processStmts(bodyStmts(s.body), env, lineStarts, lines, branches);
3904
+ for (let j = idx + 1; j < sections.length; j++) {
3905
+ const sj = sections[j];
3906
+ if (sj.cond) markNodeLines(sj.cond, lines, lineStarts);
3907
+ if (sj.body) processStmts(bodyStmts(sj.body), env, lineStarts, lines, branches);
3908
+ }
3909
+ }
3910
+ function parseShellString(s) {
3911
+ CHILD_CALL_RE.lastIndex = 0;
3912
+ let m;
3913
+ while ((m = CHILD_CALL_RE.exec(s)) !== null) {
3914
+ const interp = m[1];
3915
+ const path5 = m[2].trim();
3916
+ let rest = m[3].trim();
3917
+ if (path5.startsWith("-") || path5.startsWith("$") || path5.includes("/dev/")) continue;
3918
+ rest = rest.split(/[;|&<>]/)[0].trim();
3919
+ const args = [];
3920
+ for (const tm of rest.matchAll(/(["'])(.*?)\1|(\S+)/g)) {
3921
+ args.push(tm[1] ? tm[2] : tm[3]);
3922
+ }
3923
+ return { path: path5, args, interpreter: interp };
3924
+ }
3925
+ return null;
3926
+ }
3927
+ function extractCallsFromTree(tree, reachableLines) {
3928
+ const calls = [];
3929
+ const seen = /* @__PURE__ */ new Set();
3930
+ const add = (c) => {
3931
+ const key = `${c.path}\0${c.args.join("\0")}\0${c.interpreter}`;
3932
+ if (!seen.has(key)) {
3933
+ seen.add(key);
3934
+ calls.push(c);
3935
+ }
3936
+ };
3937
+ const lineReachable = (node) => {
3938
+ if (reachableLines === null) return true;
3939
+ return reachableLines.has(lineAt(buildLineStarts(SOURCE), node.from));
3940
+ };
3941
+ const cursor = tree.cursor();
3942
+ cursor.iterate((n) => {
3943
+ const node = n.node;
3944
+ if (node.name !== "CallExpression") return true;
3945
+ if (!lineReachable(node)) return true;
3946
+ const func = node.firstChild;
3947
+ if (!func || func.name !== "MemberExpression") return true;
3948
+ const modName = func.firstChild;
3949
+ if (!modName || modName.name !== "VariableName") return true;
3950
+ const funcNameNode = func.getChild("PropertyName");
3951
+ if (!funcNameNode) return true;
3952
+ const mod = srcSlice(modName);
3953
+ const funcName = srcSlice(funcNameNode);
3954
+ const argList = node.getChild("ArgList");
3955
+ const firstArg = argList ? firstNamedChild(argList) : null;
3956
+ if (mod === "subprocess" && SUBPROCESS_FUNCS.has(funcName)) {
3957
+ if (firstArg && (firstArg.name === "ArrayExpression" || firstArg.name === "TupleExpression")) {
3958
+ const elts = [];
3959
+ let allStr = true;
3960
+ for (const elt of namedChildren(firstArg)) {
3961
+ if (elt.name === "String") elts.push(stringValue(srcSlice(elt)));
3962
+ else {
3963
+ allStr = false;
3964
+ break;
3965
+ }
3966
+ }
3967
+ if (allStr && elts.length >= 2) {
3968
+ const interp = elts[0];
3969
+ const path5 = elts[1];
3970
+ if (!path5.startsWith("-") && !path5.startsWith("$") && !path5.includes("/dev/")) {
3971
+ add({ path: path5, args: elts.slice(2), interpreter: interp });
3972
+ return true;
3973
+ }
3974
+ }
3975
+ }
3976
+ if (firstArg && firstArg.name === "String") {
3977
+ const sc = parseShellString(stringValue(srcSlice(firstArg)));
3978
+ if (sc) add(sc);
3979
+ }
3980
+ } else if (mod === "os" && OS_FUNCS.has(funcName)) {
3981
+ if (firstArg && firstArg.name === "String") {
3982
+ const sc = parseShellString(stringValue(srcSlice(firstArg)));
3983
+ if (sc) add(sc);
3984
+ }
3985
+ }
3986
+ return true;
3987
+ });
3988
+ const cursor2 = tree.cursor();
3989
+ cursor2.iterate((n) => {
3990
+ const node = n.node;
3991
+ if (node.name !== "ImportStatement") return true;
3992
+ const txt = srcSlice(node);
3993
+ if (!/^from\s+\./m.test(txt)) return true;
3994
+ const m = txt.match(/\bimport\s+(.+)$/m);
3995
+ if (m) {
3996
+ for (const part of m[1].split(",")) {
3997
+ const name = part.trim().replace(/\s+as\s+\w+$/, "");
3998
+ if (name && name !== "*") add({ path: name.replace(/\./g, "/") + ".py", args: [], interpreter: "python3" });
3999
+ }
4000
+ }
4001
+ return true;
4002
+ });
4003
+ return calls;
4004
+ }
4005
+ function extractCallsRegex(content) {
4006
+ const calls = [];
4007
+ CHILD_CALL_RE.lastIndex = 0;
4008
+ let m;
4009
+ while ((m = CHILD_CALL_RE.exec(content)) !== null) {
4010
+ const interp = m[1];
4011
+ const path5 = m[2].trim();
4012
+ let rest = m[3].trim();
4013
+ if (path5.startsWith("-") || path5.startsWith("$") || path5.includes("/dev/")) continue;
4014
+ rest = rest.split(/[;|&<>]/)[0].trim();
4015
+ const args = [];
4016
+ for (const tm of rest.matchAll(/(["'])(.*?)\1|(\S+)/g)) {
4017
+ args.push(tm[1] ? tm[2] : tm[3]);
4018
+ }
4019
+ calls.push({ path: path5, args, interpreter: interp });
4020
+ }
4021
+ return calls;
4022
+ }
4023
+ var PythonBackend = class {
4024
+ language = "python";
4025
+ parse(content, entryArgs) {
4026
+ const c = content || "";
4027
+ SOURCE = c;
4028
+ const sourceLines = c.split("\n");
4029
+ const lineStarts = buildLineStarts(c);
4030
+ const env = makeEnv(entryArgs || []);
4031
+ const lines = /* @__PURE__ */ new Set();
4032
+ const branches = [];
4033
+ if (sourceLines.length > MAX_LINES) {
4034
+ return {
4035
+ reachableContent: c,
4036
+ controlFlowResolved: false,
4037
+ calls: extractCallsRegex(c),
4038
+ branches: [],
4039
+ unresolvedSnippets: [],
4040
+ language: this.language
4041
+ };
4042
+ }
4043
+ let tree;
4044
+ try {
4045
+ tree = parser.parse(c).topNode;
4046
+ } catch {
4047
+ return {
4048
+ reachableContent: c,
4049
+ controlFlowResolved: false,
4050
+ calls: extractCallsRegex(c),
4051
+ branches: [],
4052
+ unresolvedSnippets: [],
4053
+ language: this.language
4054
+ };
4055
+ }
4056
+ const stmts = bodyStmts(tree);
4057
+ processStmts(stmts, env, lineStarts, lines, branches);
4058
+ const reachable = reconstruct(sourceLines, lines);
4059
+ let calls = extractCallsFromTree(tree, lines);
4060
+ if (calls.length === 0) calls = extractCallsRegex(c);
4061
+ const srcNonblank = sourceLines.filter((l) => l.trim().length > 0);
4062
+ const rcNonblank = reachable.split("\n").filter((l) => l.trim().length > 0);
4063
+ const resolved = rcNonblank.length !== srcNonblank.length || rcNonblank.some((l, i) => l !== srcNonblank[i]);
4064
+ return {
4065
+ reachableContent: reachable,
4066
+ controlFlowResolved: resolved,
4067
+ calls,
4068
+ branches,
4069
+ unresolvedSnippets: [],
4070
+ language: this.language
4071
+ };
4072
+ }
4073
+ };
4074
+
4075
+ // src/engine/script-engine/backends/index.ts
4076
+ init_esm_shims();
4077
+
4078
+ // src/engine/script-engine/backends/shell-backend.ts
4079
+ init_esm_shims();
4080
+
4081
+ // src/engine/script-engine/symbolic.ts
4082
+ init_esm_shims();
4083
+ function reescape(s) {
4084
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4085
+ }
4086
+ var RE_DOTALL = "s";
4087
+ var MAX_LINES2 = 500;
4088
+ var MAX_BRANCHES = 50;
4089
+ var MAX_VARS = 200;
4090
+ function svalConc(v) {
4091
+ return { kind: "concrete", value: v, children: [], op: "" };
4092
+ }
4093
+ function svalSym(name) {
4094
+ return { kind: "symbol", value: name, children: [], op: "" };
4095
+ }
4096
+ function svalExpr(children) {
4097
+ return { kind: "expr", value: null, children, op: "+" };
4098
+ }
4099
+ function fold(val) {
4100
+ if (val.kind !== "expr") return val;
4101
+ const parts = [];
4102
+ for (const c of val.children) {
4103
+ const cc = fold(c);
4104
+ if (cc.kind === "concrete") {
4105
+ parts.push(cc.value || "");
4106
+ } else {
4107
+ return cc;
4108
+ }
4109
+ }
4110
+ return svalConc(parts.join(""));
4111
+ }
4112
+ var ASGN_RE = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
4113
+ var CASE_START_RE = /^\s*case\s+/;
4114
+ var IF_START_RE = /^\s*if\s+/;
4115
+ var NOT_MODELED_RE = /\$\(|`[^`]*`/;
4116
+ var CASE_OPEN_RE = /\bcase\b/;
4117
+ var CASE_CLOSE_RE = /\besac\b/;
4118
+ var IF_OPEN_RE = /\bif\b/;
4119
+ var IF_CLOSE_RE = /\bfi\b/;
4120
+ function countMatches(re, s) {
4121
+ const m = s.match(re);
4122
+ return m ? m.length : 0;
4123
+ }
4124
+ function findBlockEnd(lines, startLine, openRe, closeRe) {
4125
+ let depth = 0;
4126
+ for (let idx = startLine; idx < lines.length; idx++) {
4127
+ depth += countMatches(openRe, lines[idx]) - countMatches(closeRe, lines[idx]);
4128
+ if (depth <= 0 && idx > startLine) return idx;
4129
+ }
4130
+ return -1;
4131
+ }
4132
+ function findCaseEnd(lines, start) {
4133
+ return findBlockEnd(lines, start, CASE_OPEN_RE, CASE_CLOSE_RE);
4134
+ }
4135
+ function findIfEnd(lines, start) {
4136
+ return findBlockEnd(lines, start, IF_OPEN_RE, IF_CLOSE_RE);
4137
+ }
4138
+ var LOOP_DO_RE = /\bdo\b/g;
4139
+ var LOOP_DONE_RE = /\bdone\b/g;
4140
+ function findLoopEnd(lines, start) {
4141
+ let depth = 0;
4142
+ for (let idx = start; idx < lines.length; idx++) {
4143
+ depth += countMatches(LOOP_DO_RE, lines[idx]) - countMatches(LOOP_DONE_RE, lines[idx]);
4144
+ if (depth <= 0 && idx > start) return idx;
4145
+ }
4146
+ return -1;
4147
+ }
4148
+ var FN_DEF_RE = /^\s*(?:function\s+)?[A-Za-z_][A-Za-z0-9_]*\s*\(\s*\)\s*\{/;
4149
+ var FN_DEF_KW_RE = /^\s*function\s+[A-Za-z_][A-Za-z0-9_]*\s*\{?/;
4150
+ function findFnEnd(lines, start) {
4151
+ let depth = 0;
4152
+ for (let idx = start; idx < lines.length; idx++) {
4153
+ depth += (lines[idx].match(/\{/g) || []).length - (lines[idx].match(/\}/g) || []).length;
4154
+ if (depth <= 0 && idx > start) return idx;
4155
+ }
4156
+ return -1;
4157
+ }
4158
+ function stripComments(content) {
4159
+ return content.split("\n").filter((l) => !l.trimStart().startsWith("#")).join("\n");
4160
+ }
4161
+ function parseCaseStmt(block) {
4162
+ const header = block.match(/\s*case\s+(\S[^;]*?)\s*\bin\b[\s\S]*/);
4163
+ if (!header) return null;
4164
+ const sel = header[1].trim();
4165
+ const lines = block.split("\n");
4166
+ let headerLine = 0;
4167
+ for (let i = 0; i < lines.length; i++) {
4168
+ if (/\bin\b/.test(lines[i]) && /\bcase\b/.test(lines[i])) {
4169
+ headerLine = i;
4170
+ break;
4171
+ }
4172
+ }
4173
+ const bodyStartLine = headerLine + 1;
4174
+ const endLine = findCaseEnd(lines, 0);
4175
+ if (endLine === -1) return null;
4176
+ const branches = splitCaseBranchesDepthAware(lines.slice(bodyStartLine, endLine));
4177
+ if (!branches.length) return null;
4178
+ return { kind: "case", line: block, modelable: true, selector: sel, branches };
4179
+ }
4180
+ function splitCaseBranchesDepthAware(bodyLines) {
4181
+ let depth = 0;
4182
+ let curPat = null;
4183
+ let curBody = [];
4184
+ const branches = [];
4185
+ const flush = () => {
4186
+ if (curPat !== null) {
4187
+ branches.push({ pat: curPat, body: curBody.join("\n") });
4188
+ }
4189
+ curPat = null;
4190
+ curBody = [];
4191
+ };
4192
+ for (const ln of bodyLines) {
4193
+ const stripped = ln.trim();
4194
+ if (depth === 0) {
4195
+ const bm = stripped.match(/^([^()]+?)\s*\)\s*(.*)$/);
4196
+ if (bm) {
4197
+ if (curPat !== null) flush();
4198
+ curPat = bm[1].trim().replace(/^["']|["']$/g, "");
4199
+ curBody = bm[2].trim() ? [bm[2].trim()] : [];
4200
+ continue;
4201
+ }
4202
+ }
4203
+ if (/\bcase\b/.test(stripped)) depth += 1;
4204
+ else if (/\besac\b/.test(stripped)) depth = Math.max(0, depth - 1);
4205
+ else if (curPat !== null) {
4206
+ curBody.push(ln);
4207
+ if (depth === 0 && stripped.endsWith(";;")) flush();
4208
+ }
4209
+ }
4210
+ if (curPat !== null && curBody.length) flush();
4211
+ return branches;
4212
+ }
4213
+ function parseIfStmt(block) {
4214
+ const m = block.match(/\s*if\s+([^\n;]+?)\s*;\s*then/);
4215
+ if (!m) return null;
4216
+ const cond = m[1].trim();
4217
+ const lines = block.split("\n");
4218
+ const fiLine = findIfEnd(lines, 0);
4219
+ if (fiLine === -1) return null;
4220
+ const body = lines.slice(1, fiLine).join("\n");
4221
+ const elseM = /\belse\b/.exec(body);
4222
+ let thenBody;
4223
+ let elseBody;
4224
+ if (elseM) {
4225
+ thenBody = body.slice(0, elseM.index);
4226
+ elseBody = body.slice(elseM.index + 4);
4227
+ } else {
4228
+ thenBody = body;
4229
+ elseBody = null;
4230
+ }
4231
+ return { kind: "if", line: block, modelable: true, cond, thenBody, elseBody };
4232
+ }
4233
+ function tokenize(content) {
4234
+ const lines = stripComments(content).split("\n");
4235
+ const stmts = [];
4236
+ let i = 0;
4237
+ while (i < lines.length) {
4238
+ const ln = lines[i];
4239
+ const stripped = ln.trim();
4240
+ if (!stripped) {
4241
+ i++;
4242
+ continue;
4243
+ }
4244
+ if (/\b(?:while|for|until)\b/.test(stripped) && !CASE_START_RE.test(stripped) && !IF_START_RE.test(stripped)) {
4245
+ const endLine = findLoopEnd(lines, i);
4246
+ if (endLine !== -1) {
4247
+ for (let k = i; k <= endLine; k++) stmts.push({ kind: "plain", line: lines[k], modelable: false });
4248
+ i = endLine + 1;
4249
+ continue;
4250
+ }
4251
+ stmts.push({ kind: "plain", line: ln, modelable: false });
4252
+ i++;
4253
+ continue;
4254
+ }
4255
+ if (FN_DEF_RE.test(stripped) || FN_DEF_KW_RE.test(stripped)) {
4256
+ const endLine = findFnEnd(lines, i);
4257
+ if (endLine !== -1) {
4258
+ for (let k = i; k <= endLine; k++) stmts.push({ kind: "plain", line: lines[k], modelable: false });
4259
+ i = endLine + 1;
4260
+ continue;
4261
+ }
4262
+ stmts.push({ kind: "plain", line: ln, modelable: false });
4263
+ i++;
4264
+ continue;
4265
+ }
4266
+ if (CASE_START_RE.test(stripped)) {
4267
+ const endLine = findCaseEnd(lines, i);
4268
+ if (endLine === -1) {
4269
+ stmts.push({ kind: "plain", line: ln, modelable: false });
4270
+ i++;
4271
+ continue;
4272
+ }
4273
+ const parsed = parseCaseStmt(lines.slice(i, endLine + 1).join("\n"));
4274
+ stmts.push(parsed || { kind: "plain", line: ln, modelable: false });
4275
+ i = endLine + 1;
4276
+ continue;
4277
+ }
4278
+ if (IF_START_RE.test(stripped)) {
4279
+ const endLine = findIfEnd(lines, i);
4280
+ if (endLine === -1) {
4281
+ stmts.push({ kind: "plain", line: ln, modelable: false });
4282
+ i++;
4283
+ continue;
4284
+ }
4285
+ const parsed = parseIfStmt(lines.slice(i, endLine + 1).join("\n"));
4286
+ stmts.push(parsed || { kind: "plain", line: ln, modelable: false });
4287
+ i = endLine + 1;
4288
+ continue;
4289
+ }
4290
+ if (/\bshift\b|\bset\s+--(?:\s|$)|[\s;|&]read\b|^\s*read\b/.test(stripped)) {
4291
+ stmts.push({ kind: "plain", line: ln, modelable: false });
4292
+ i++;
4293
+ continue;
4294
+ }
4295
+ const am = stripped.match(ASGN_RE);
4296
+ if (am && !NOT_MODELED_RE.test(stripped)) {
4297
+ stmts.push({ kind: "assign", line: stripped, modelable: true, name: am[1], rhs: am[2].trim() });
4298
+ i++;
4299
+ continue;
4300
+ }
4301
+ stmts.push(NOT_MODELED_RE.test(stripped) ? { kind: "plain", line: ln, modelable: false } : { kind: "plain", line: ln, modelable: true });
4302
+ i++;
4303
+ }
4304
+ return stmts;
4305
+ }
4306
+ var EXPR_SPLIT_RE = /(\$\{?\d+\}?|\$\{?[A-Za-z_][A-Za-z0-9_]*\}?)/;
4307
+ function evalExpr2(text, env) {
4308
+ const isSingleQuoted = text.length >= 2 && text[0] === "'" && text[text.length - 1] === "'";
4309
+ if (text.length >= 2 && (text[0] === '"' || text[0] === "'") && text[text.length - 1] === text[0]) {
4310
+ text = text.slice(1, -1);
4311
+ }
4312
+ if (isSingleQuoted) return svalConc(text);
4313
+ const parts = text.split(EXPR_SPLIT_RE);
4314
+ const result = [];
4315
+ for (const p of parts) {
4316
+ if (!p) continue;
4317
+ const brace = p.match(/^\$\{([^}]+)\}$/);
4318
+ if (brace) {
4319
+ const inner = brace[1];
4320
+ if (/^\d+$/.test(inner)) {
4321
+ const v = env.get(`$${inner}`);
4322
+ result.push(v || svalSym(`$${inner}`));
4323
+ } else {
4324
+ const v = env.get(inner);
4325
+ result.push(v || svalSym(inner));
4326
+ }
4327
+ continue;
4328
+ }
4329
+ const bare = p.match(/^\$([A-Za-z_][A-Za-z0-9_]*|\d+)$/);
4330
+ if (bare) {
4331
+ const varName = bare[1];
4332
+ const key = /^\d+$/.test(varName) ? `$${varName}` : varName;
4333
+ const v = env.get(key);
4334
+ result.push(v || svalSym(p));
4335
+ continue;
4336
+ }
4337
+ result.push(svalConc(p));
4338
+ }
4339
+ if (result.length === 1) return fold(result[0]);
4340
+ return fold(svalExpr(result));
4341
+ }
4342
+ function initEnv(entryArgs) {
4343
+ const env = /* @__PURE__ */ new Map();
4344
+ if (entryArgs && entryArgs.length) {
4345
+ entryArgs.forEach((a, idx) => env.set(`$${idx + 1}`, svalConc(a)));
4346
+ env.set("$@", svalConc(entryArgs.join(" ")));
4347
+ env.set("$*", svalConc(entryArgs.join(" ")));
4348
+ }
4349
+ return env;
4350
+ }
4351
+ var HAS_GLOB = /[*?[]/;
4352
+ function globToRegex(pattern) {
4353
+ let out = "^";
4354
+ let i = 0;
4355
+ while (i < pattern.length) {
4356
+ const c = pattern[i];
4357
+ if (c === "*") out += ".*";
4358
+ else if (c === "?") out += ".";
4359
+ else if (c === "[") {
4360
+ let j = i + 1;
4361
+ let neg = false;
4362
+ if (j < pattern.length && (pattern[j] === "!" || pattern[j] === "^")) {
4363
+ neg = true;
4364
+ j++;
4365
+ }
4366
+ let cls = "";
4367
+ while (j < pattern.length && pattern[j] !== "]") {
4368
+ cls += pattern[j];
4369
+ j++;
4370
+ }
4371
+ if (j >= pattern.length) {
4372
+ out += reescape(pattern.slice(i));
4373
+ break;
4374
+ }
4375
+ out += "[" + (neg ? "^" : "") + reescape(cls) + "]";
4376
+ i = j;
4377
+ } else {
4378
+ out += reescape(c);
4379
+ }
4380
+ i++;
4381
+ }
4382
+ out += "$";
4383
+ return new RegExp(out, RE_DOTALL);
4384
+ }
4385
+ function matchGlob(pattern, value) {
4386
+ if (!HAS_GLOB.test(pattern)) return pattern === value;
4387
+ return globToRegex(pattern).test(value);
4388
+ }
4389
+ function evalNumericCompare(op, left, right) {
4390
+ const li = parseInt(left, 10);
4391
+ const ri = parseInt(right, 10);
4392
+ if (isNaN(li) || isNaN(ri)) return null;
4393
+ switch (op) {
4394
+ case "-gt":
4395
+ return li > ri;
4396
+ case "-lt":
4397
+ return li < ri;
4398
+ case "-eq":
4399
+ return li === ri;
4400
+ case "-ge":
4401
+ return li >= ri;
4402
+ case "-le":
4403
+ return li <= ri;
4404
+ default:
4405
+ return null;
4406
+ }
4407
+ }
4408
+ function resolveVarSide(tok, env) {
4409
+ if (!tok.startsWith("$")) return tok;
4410
+ const ev = evalExpr2(tok, env);
4411
+ return ev.kind === "concrete" ? ev.value : null;
4412
+ }
4413
+ function evalCondition2(cond, env) {
4414
+ let inner = cond.trim().replace(/^\[\[?|\]\]?$/g, "").trim();
4415
+ if (/^-[A-Za-z]\s/.test(inner) || inner.startsWith("-d") || inner.startsWith("-f")) return null;
4416
+ const innerQ = inner.replace(/"/g, "").replace(/'/g, "");
4417
+ for (const op2 of ["-gt", "-lt", "-eq", "-ge", "-le"]) {
4418
+ const re = new RegExp(`\\s${op2}\\s`);
4419
+ const opm = re.exec(innerQ);
4420
+ if (!opm) continue;
4421
+ const left2 = innerQ.slice(0, opm.index).trim();
4422
+ const right2 = innerQ.slice(opm.index + opm[0].length).trim();
4423
+ const lv2 = resolveVarSide(left2, env);
4424
+ const rv2 = resolveVarSide(right2, env);
4425
+ if (lv2 === null || rv2 === null) return null;
4426
+ return evalNumericCompare(op2, lv2, rv2);
4427
+ }
4428
+ const sm = innerQ.match(/(.*?)\s*(==|=|!=|=~)\s*(.*)/);
4429
+ if (!sm) return null;
4430
+ const op = sm[2];
4431
+ let left = sm[1].trim();
4432
+ let right = sm[3].trim();
4433
+ if (op === "=~") {
4434
+ if (right.startsWith("/") && right.endsWith("/")) right = right.slice(1, -1);
4435
+ const lv2 = resolveVarSide(left, env);
4436
+ if (lv2 === null) return null;
4437
+ try {
4438
+ return new RegExp(right).test(lv2);
4439
+ } catch {
4440
+ return null;
4441
+ }
4442
+ }
4443
+ const lv = resolveVarSide(left, env);
4444
+ const rv = resolveVarSide(right, env);
4445
+ if (lv === null || rv === null) return null;
4446
+ if (op === "!=") return lv !== rv;
4447
+ return lv === rv;
4448
+ }
4449
+ function symbolicProcess(content, entryArgs) {
4450
+ if (!content) return null;
4451
+ const stmts = tokenize(content);
4452
+ if (!stmts.some((s) => s.kind === "case" || s.kind === "if" || s.kind === "assign")) return null;
4453
+ if (stmts.some((s) => !s.modelable)) return null;
4454
+ const env = initEnv(entryArgs);
4455
+ let linesBudget = 0;
4456
+ let branchesBudget = 0;
4457
+ let modelable = false;
4458
+ const out = [];
4459
+ for (const s of stmts) {
4460
+ linesBudget++;
4461
+ if (linesBudget > MAX_LINES2) return null;
4462
+ if (s.kind === "assign") {
4463
+ modelable = true;
4464
+ if (env.size >= MAX_VARS) return null;
4465
+ const v = evalExpr2(s.rhs, env);
4466
+ env.set(s.name, fold(v));
4467
+ out.push(s.line);
4468
+ continue;
4469
+ }
4470
+ if (s.kind === "case") {
4471
+ modelable = true;
4472
+ branchesBudget += s.branches.length;
4473
+ if (branchesBudget > MAX_BRANCHES) return null;
4474
+ const selVal = fold(evalExpr2(s.selector, env));
4475
+ if (selVal.kind !== "concrete") return null;
4476
+ const concrete = selVal.value || "";
4477
+ let selected = null;
4478
+ let fallback = "";
4479
+ for (const b of s.branches) {
4480
+ const patStripped = b.pat.trim();
4481
+ if (patStripped === "*") {
4482
+ fallback = b.body;
4483
+ continue;
4484
+ }
4485
+ if (matchGlob(patStripped, concrete)) {
4486
+ selected = b.body;
4487
+ break;
4488
+ }
4489
+ }
4490
+ const final = selected !== null ? selected : fallback || null;
4491
+ if (final === null) return null;
4492
+ out.push(`case ${s.selector} in
4493
+ ${final}
4494
+ esac`);
4495
+ continue;
4496
+ }
4497
+ if (s.kind === "if") {
4498
+ modelable = true;
4499
+ branchesBudget++;
4500
+ if (branchesBudget > MAX_BRANCHES) return null;
4501
+ const truth = evalCondition2(s.cond, env);
4502
+ if (truth === null) return null;
4503
+ const chosen = truth ? s.thenBody : s.elseBody;
4504
+ if (chosen === null) return null;
4505
+ out.push(`if ${s.cond}; then
4506
+ ${chosen}
4507
+ fi`);
4508
+ continue;
4509
+ }
4510
+ out.push(s.line);
4511
+ }
4512
+ if (!modelable) return null;
4513
+ return out.join("\n");
4514
+ }
4515
+
4516
+ // src/engine/script-engine/backends/shell-backend.ts
4517
+ function bindPositionalArgs(content, args) {
4518
+ if (!args.length) return content;
4519
+ const allArgs = args.join(" ");
4520
+ content = content.replace(/\$\{(\d+|@|\*)(?::?[-]?([^}]*))?\}/g, (full, kind, defv) => {
4521
+ if (kind === "@" || kind === "*") return allArgs;
4522
+ if (!/^\d+$/.test(kind)) return full;
4523
+ const idx = parseInt(kind, 10);
4524
+ if (1 <= idx && idx <= args.length) return args[idx - 1];
4525
+ return defv !== void 0 ? defv : "";
4526
+ });
4527
+ content = content.replace(/\$\{(\d+)\}/g, (full, kind) => {
4528
+ const idx = parseInt(kind, 10);
4529
+ if (1 <= idx && idx <= args.length) return args[idx - 1];
4530
+ return "";
4531
+ });
4532
+ content = content.replace(/\$(\d+)/g, (full, kind) => {
4533
+ const idx = parseInt(kind, 10);
4534
+ if (1 <= idx && idx <= args.length) return args[idx - 1];
4535
+ return "";
4536
+ });
4537
+ content = content.replace(/\$[@*]/g, allArgs);
4538
+ return content;
4539
+ }
4540
+ function findSelectorValue(selector, args) {
4541
+ if (!args || !args.length) return null;
4542
+ const m = selector.match(/^\$\{?(\d+)\}?$/);
4543
+ if (m) {
4544
+ const idx = parseInt(m[1], 10);
4545
+ if (1 <= idx && idx <= args.length) return args[idx - 1];
4546
+ return null;
4547
+ }
4548
+ return null;
4549
+ }
4550
+ function splitCaseBody(body) {
4551
+ const lines = body.split("\n");
4552
+ const branches = [];
4553
+ let cur = null;
4554
+ for (const ln of lines) {
4555
+ const stripped = ln.trim();
4556
+ if (/^[\w:-]+\s*\(\)\s*\{/.test(stripped)) continue;
4557
+ const m = stripped.match(/^([^()]+?)\s*\)\s*(.*)$/);
4558
+ if (m) {
4559
+ const pat = m[1].trim().replace(/^["']|["']$/g, "");
4560
+ const inline = m[2].trim();
4561
+ if (pat) {
4562
+ if (cur) branches.push(cur);
4563
+ cur = { label: pat, body: inline };
4564
+ }
4565
+ continue;
4566
+ }
4567
+ if (cur) {
4568
+ cur.body += cur.body ? "\n" + ln : ln;
4569
+ if (stripped.endsWith(";;")) {
4570
+ branches.push(cur);
4571
+ cur = null;
4572
+ }
4573
+ }
4574
+ }
4575
+ if (cur && cur.body.trim()) {
4576
+ if (!branches.includes(cur)) branches.push(cur);
4577
+ }
4578
+ return branches;
4579
+ }
4580
+ function parseCase(content, args) {
4581
+ const m = content.match(/\bcase\s+(\$\{?\d+\}?|[A-Za-z_]\w*|\$[A-Za-z_]\w*)\s+in/i);
4582
+ if (!m) return null;
4583
+ const selector = m[1].trim();
4584
+ const val = findSelectorValue(selector, args);
4585
+ if (val === null) return null;
4586
+ const lines = content.split("\n");
4587
+ const endMatch = findCaseEndInLines(lines, 0);
4588
+ if (endMatch === -1) return null;
4589
+ const bodyLines = lines.slice(1, endMatch);
4590
+ const body = bodyLines.join("\n");
4591
+ const hasNested = /\bcase\s+/.test(body) || /\bif\s+/.test(body);
4592
+ let final;
4593
+ if (hasNested) {
4594
+ final = splitNestedCaseBranches(bodyLines, val);
4595
+ if (final === null) return null;
4596
+ } else {
4597
+ const branches = splitCaseBody(body);
4598
+ let selected = null;
4599
+ let fallback = "";
4600
+ for (const b of branches) {
4601
+ const values = b.label.split("|").map((v) => v.trim()).filter(Boolean);
4602
+ if (values.includes("*")) fallback = b.body;
4603
+ else if (values.includes(val)) selected = b.body;
4604
+ }
4605
+ final = selected !== null ? selected : fallback || null;
4606
+ if (final === null) return null;
4607
+ }
4608
+ const prefix = content.slice(0, m.index ?? 0);
4609
+ const suffix = lines.slice(endMatch + 1).join("\n");
4610
+ return `${prefix}
4611
+ ${final}
4612
+ ${suffix}`;
4613
+ }
4614
+ function findCaseEndInLines(lines, headerLine) {
4615
+ let depth = 0;
4616
+ for (let idx = headerLine; idx < lines.length; idx++) {
4617
+ const ln = lines[idx];
4618
+ const opens = (ln.match(/\bcase\b/g) || []).length;
4619
+ const closes = (ln.match(/\besac\b/g) || []).length;
4620
+ depth += opens - closes;
4621
+ if (depth <= 0 && idx > headerLine) return idx;
4622
+ }
4623
+ return -1;
4624
+ }
4625
+ function splitNestedCaseBranches(bodyLines, selectorVal) {
4626
+ let depth = 0;
4627
+ let curPat = null;
4628
+ let curBody = [];
4629
+ let selected = null;
4630
+ let fallback = null;
4631
+ const flush = () => {
4632
+ if (curPat === null) return;
4633
+ const values = curPat.split("|").map((v) => v.trim()).filter(Boolean);
4634
+ const body = curBody.join("\n");
4635
+ if (values.includes("*")) {
4636
+ if (fallback === null) fallback = body;
4637
+ } else if (values.includes(selectorVal)) {
4638
+ selected = body;
4639
+ }
4640
+ curPat = null;
4641
+ curBody = [];
4642
+ };
4643
+ for (const ln of bodyLines) {
4644
+ const stripped = ln.trim();
4645
+ if (depth === 0) {
4646
+ const bm = stripped.match(/^([^()]+?)\s*\)\s*(.*)$/);
4647
+ if (bm) {
4648
+ if (curPat !== null) flush();
4649
+ curPat = bm[1].trim().replace(/^["']|["']$/g, "");
4650
+ curBody = bm[2].trim() ? [bm[2].trim()] : [];
4651
+ continue;
4652
+ }
4653
+ }
4654
+ if (/\bcase\b/.test(stripped)) depth += 1;
4655
+ else if (/\besac\b/.test(stripped)) depth = Math.max(0, depth - 1);
4656
+ else if (curPat !== null) {
4657
+ curBody.push(ln);
4658
+ if (depth === 0 && stripped.endsWith(";;")) flush();
4659
+ }
4660
+ }
4661
+ if (curPat !== null && curBody.length) flush();
4662
+ return selected !== null ? selected : fallback;
4663
+ }
4664
+ function evalIfCondition(cond, args) {
4665
+ if (!args || !args.length) return null;
4666
+ let inner = cond.replace(/^\[\[?|\]\]?$/g, "").replace(/"/g, "").replace(/'/g, "").trim();
4667
+ const m = inner.match(/(.*?)\s*(==|=)\s*(.*)/);
4668
+ if (!m) return null;
4669
+ let left = m[1].trim();
4670
+ let right = m[3].trim();
4671
+ const lm = left.match(/^\$(\d+)$/);
4672
+ if (lm) {
4673
+ const idx = parseInt(lm[1], 10);
4674
+ if (1 <= idx && idx <= args.length) left = args[idx - 1];
4675
+ else return null;
4676
+ } else if (left.startsWith("$")) return null;
4677
+ const rm = right.match(/^\$(\d+)$/);
4678
+ if (rm) {
4679
+ const idx = parseInt(rm[1], 10);
4680
+ if (1 <= idx && idx <= args.length) right = args[idx - 1];
4681
+ else return null;
4682
+ } else if (right.startsWith("$")) return null;
4683
+ return left === right;
4684
+ }
4685
+ function parseIf(content, args) {
4686
+ const m = content.match(/\bif\s+([^\n;]+?)\s*;\s*then/i);
4687
+ if (!m) return null;
4688
+ const truth = evalIfCondition(m[1].trim(), args);
4689
+ if (truth === null) return null;
4690
+ const start = m.index ?? 0;
4691
+ const thenBodyStart = start + m[0].length;
4692
+ const fiPos = content.indexOf("fi", thenBodyStart);
4693
+ if (fiPos === -1) return null;
4694
+ const segment = content.slice(thenBodyStart, fiPos);
4695
+ const elseM = segment.match(/\belse\b/);
4696
+ let chosen;
4697
+ if (truth) chosen = elseM ? segment.slice(0, elseM.index) : segment;
4698
+ else {
4699
+ if (!elseM) return null;
4700
+ chosen = segment.slice((elseM.index ?? 0) + 4);
4701
+ }
4702
+ return `${content.slice(0, start)}
4703
+ ${chosen}
4704
+ ${content.slice(fiPos + 2)}`;
4705
+ }
4706
+ function markLoopsReachable(content) {
4707
+ return content;
4708
+ }
4709
+ var CHILD_CALL_RE2 = /(?:^|\s|&&|\|\|)\s*((?:\/bin\/)?bash|sh|zsh|(?:python|python3|node|ruby|perl|php|lua))\s+(\S+(?:\.(?:sh|shrc|py|js|rb|pl|php|lua))?)([^\n;|&]*?)$/gmi;
4710
+ function extractCallsFromContent(content) {
4711
+ const calls = [];
4712
+ let m;
4713
+ CHILD_CALL_RE2.lastIndex = 0;
4714
+ while ((m = CHILD_CALL_RE2.exec(content)) !== null) {
4715
+ const interp = m[1];
4716
+ const path5 = m[2].trim();
4717
+ let rest = m[3].trim();
4718
+ if (path5.startsWith("-") || path5.startsWith("$") || path5.includes("/dev/")) continue;
4719
+ rest = rest.split(/[;|&<>]/)[0].trim();
4720
+ const args = [];
4721
+ for (const tm of rest.matchAll(/(["'])(.*?)\1|(\S+)/g)) {
4722
+ args.push(tm[1] ? tm[2] : tm[3]);
4723
+ }
4724
+ calls.push({ path: path5, args, interpreter: interp });
4725
+ }
4726
+ return calls;
4727
+ }
4728
+ var ShellBackend = class {
4729
+ language = "shell";
4730
+ parse(content, entryArgs) {
4731
+ const c = content || "";
4732
+ const orig = c;
4733
+ let candidate = c;
4734
+ let resolved = false;
4735
+ const p1Safe = !/(?:\bshift\b|\bset\s+--(?:\s|$)|[\s;|&]read\b|^\s*read\b|\(\)\s*\{|function\s+\w+)/.test(c);
4736
+ if (p1Safe) {
4737
+ const caseOut = parseCase(candidate, entryArgs);
4738
+ if (caseOut !== null && caseOut !== candidate) {
4739
+ resolved = true;
4740
+ candidate = caseOut;
4741
+ }
4742
+ const ifOut = parseIf(candidate, entryArgs);
4743
+ if (ifOut !== null && ifOut !== candidate) {
4744
+ resolved = true;
4745
+ candidate = ifOut;
4746
+ }
4747
+ }
4748
+ let bound = p1Safe ? bindPositionalArgs(candidate, entryArgs || []) : candidate;
4749
+ if (!resolved) {
4750
+ const symOut = symbolicProcess(c, entryArgs);
4751
+ if (symOut !== null && symOut !== c) {
4752
+ resolved = true;
4753
+ bound = symOut;
4754
+ }
4755
+ }
4756
+ const loopMarked = markLoopsReachable(bound);
4757
+ const calls = extractCallsFromContent(loopMarked);
4758
+ return {
4759
+ reachableContent: loopMarked,
4760
+ controlFlowResolved: resolved,
4761
+ calls,
4762
+ branches: [],
4763
+ unresolvedSnippets: [],
4764
+ language: this.language
4765
+ };
4766
+ }
4767
+ };
4768
+
4769
+ // src/engine/script-engine/tracker.ts
4770
+ var DEFAULT_MAX_DEPTH = 14;
4771
+ var DEFAULT_MAX_NODES = 64;
4772
+ var DEFAULT_MAX_FILE_SIZE = 2 * 1024 * 1024;
4773
+ var DEFAULT_MAX_TOTAL_SIZE = 8 * 1024 * 1024;
4774
+ var BACKENDS = {
4775
+ ".py": new PythonBackend()
4776
+ };
4777
+ var DEFAULT_BACKEND = new ShellBackend();
4778
+ function pickBackend(pathStr) {
4779
+ const lower = pathStr.toLowerCase();
4780
+ for (const ext of Object.keys(BACKENDS)) {
4781
+ if (lower.endsWith(ext)) return BACKENDS[ext];
4782
+ }
4783
+ return DEFAULT_BACKEND;
4784
+ }
4785
+ function resolvePath(p, baseDir) {
4786
+ let expanded = p;
4787
+ if (expanded.startsWith("~/") || expanded === "~") {
4788
+ expanded = path3.join(os.homedir(), expanded.slice(1));
4789
+ } else if (!path3.isAbsolute(expanded) && baseDir) {
4790
+ expanded = path3.join(baseDir, expanded);
4791
+ }
4792
+ return path3.resolve(expanded);
4793
+ }
4794
+ function readScript(pathStr, maxFileSize) {
4795
+ try {
4796
+ const p = resolvePath(pathStr);
4797
+ if (!fs3.existsSync(p)) return { content: "", lineCount: 0, fileSize: 0, error: `\u811A\u672C\u4E0D\u5B58\u5728: ${pathStr}` };
4798
+ const stat = fs3.statSync(p);
4799
+ if (!stat.isFile()) return { content: "", lineCount: 0, fileSize: 0, error: `\u4E0D\u662F\u6587\u4EF6: ${pathStr}` };
4800
+ if (stat.size > maxFileSize) {
4801
+ return { content: "", lineCount: 0, fileSize: stat.size, error: `\u811A\u672C\u6587\u4EF6\u8FC7\u5927 (${stat.size} bytes)\uFF0C\u8DF3\u8FC7\u8BFB\u53D6` };
4802
+ }
4803
+ const content = fs3.readFileSync(p, "utf-8");
4804
+ return { content, lineCount: content ? content.split("\n").length : 0, fileSize: stat.size, error: "" };
4805
+ } catch (e) {
4806
+ const msg = e instanceof Error ? e.message : String(e);
4807
+ return { content: "", lineCount: 0, fileSize: 0, error: `\u8BFB\u53D6\u811A\u672C\u5931\u8D25: ${msg}` };
4808
+ }
4809
+ }
4810
+ function emptyNode(pathStr, depth) {
4811
+ return {
4812
+ scriptPath: pathStr,
4813
+ rawContent: "",
4814
+ preprocessedContent: "",
4815
+ lineCount: 0,
4816
+ fileSize: 0,
4817
+ depth,
4818
+ childPaths: [],
4819
+ error: "",
4820
+ l1Hit: false,
4821
+ l1RiskLevel: "",
4822
+ l1RiskType: "",
4823
+ l1Reason: "",
4824
+ l1MatchedPatterns: [],
4825
+ l2Hit: false,
4826
+ l2ViolatedRule: "",
4827
+ l2RiskLevel: "",
4828
+ l2RiskType: "",
4829
+ l2Reason: "",
4830
+ matchedKeywords: [],
4831
+ isSuspicious: false,
4832
+ keywordRiskLevel: "",
4833
+ keywordRiskReason: ""
4834
+ };
4835
+ }
4836
+ var RecursiveTracker = class {
4837
+ constructor(commandScanner, maxDepth = DEFAULT_MAX_DEPTH, maxNodes = DEFAULT_MAX_NODES, maxFileSize = DEFAULT_MAX_FILE_SIZE, maxTotalSize = DEFAULT_MAX_TOTAL_SIZE) {
4838
+ this.maxDepth = maxDepth;
4839
+ this.maxNodes = maxNodes;
4840
+ this.maxFileSize = maxFileSize;
4841
+ this.maxTotalSize = maxTotalSize;
4842
+ this._scanner = commandScanner;
4843
+ this.result = {
4844
+ nodes: [],
4845
+ entryPath: "",
4846
+ maxDepthReached: false,
4847
+ maxSizeReached: false,
4848
+ maxNodesReached: false,
4849
+ totalSize: 0,
4850
+ totalLines: 0,
4851
+ totalNodes: 0,
4852
+ hasCritical: false,
4853
+ hasHigh: false,
4854
+ firstHitNode: null,
4855
+ error: ""
4856
+ };
4857
+ }
4858
+ maxDepth;
4859
+ maxNodes;
4860
+ maxFileSize;
4861
+ maxTotalSize;
4862
+ result;
4863
+ visiting = /* @__PURE__ */ new Set();
4864
+ visitedPaths = /* @__PURE__ */ new Set();
4865
+ _scanner;
4866
+ track(entryPath, entryArgs, cwd = "") {
4867
+ const baseDir = cwd || process.cwd();
4868
+ const resolved = resolvePath(entryPath);
4869
+ this.result.entryPath = resolved;
4870
+ this.visitScript(resolved, 0, path3.dirname(resolved), entryArgs);
4871
+ const nodes = this.result.nodes;
4872
+ this.result.hasCritical = nodes.some((n) => n.l1RiskLevel === "critical" || n.l2RiskLevel === "critical" || n.keywordRiskLevel === "critical") || this.result.hasCritical;
4873
+ this.result.hasHigh = nodes.some((n) => ["high", "critical"].includes(n.l1RiskLevel) || ["high", "critical"].includes(n.l2RiskLevel) || ["high", "critical"].includes(n.keywordRiskLevel));
4874
+ this.result.firstHitNode = nodes.find((n) => n.l1Hit || n.l2Hit || n.isSuspicious) || null;
4875
+ return this.result;
4876
+ }
4877
+ visitScript(pathStr, depth, baseDir, args) {
4878
+ pathStr = resolvePath(pathStr);
4879
+ if (this.visiting.has(pathStr)) return;
4880
+ if (this.visitedPaths.has(pathStr)) return;
4881
+ if (depth > this.maxDepth) {
4882
+ this.result.maxDepthReached = true;
4883
+ return;
4884
+ }
4885
+ if (this.result.nodes.length >= this.maxNodes) {
4886
+ this.result.maxNodesReached = true;
4887
+ return;
4888
+ }
4889
+ if (this.result.totalSize >= this.maxTotalSize) {
4890
+ this.result.maxSizeReached = true;
4891
+ return;
4892
+ }
4893
+ this.visiting.add(pathStr);
4894
+ this.visitedPaths.add(pathStr);
4895
+ const { content, lineCount, fileSize, error } = readScript(pathStr, this.maxFileSize);
4896
+ if (error) {
4897
+ const node2 = emptyNode(pathStr, depth);
4898
+ node2.error = error;
4899
+ node2.fileSize = fileSize;
4900
+ node2.lineCount = lineCount;
4901
+ this.result.nodes.push(node2);
4902
+ this.visiting.delete(pathStr);
4903
+ return;
4904
+ }
4905
+ this.result.totalSize += fileSize;
4906
+ this.result.totalLines += lineCount;
4907
+ const ir = pickBackend(pathStr).parse(content, args);
4908
+ const node = emptyNode(pathStr, depth);
4909
+ node.rawContent = content;
4910
+ node.preprocessedContent = ir.reachableContent;
4911
+ node.lineCount = lineCount;
4912
+ node.fileSize = fileSize;
4913
+ this.result.nodes.push(node);
4914
+ this.result.totalNodes = this.result.nodes.length;
4915
+ if (this._scanner && ir.reachableContent) {
4916
+ const l1 = this._scanner(ir.reachableContent);
4917
+ if (l1.hit) {
4918
+ node.l1Hit = true;
4919
+ node.l1RiskLevel = l1.riskLevel;
4920
+ node.l1RiskType = l1.riskType;
4921
+ node.l1Reason = l1.reason;
4922
+ node.l1MatchedPatterns = l1.matchedPatterns || [];
4923
+ if (l1.riskLevel === "critical") {
4924
+ this.result.hasCritical = true;
4925
+ this.result.firstHitNode = node;
4926
+ this.visiting.delete(pathStr);
4927
+ return;
4928
+ }
4929
+ }
4930
+ }
4931
+ const parentDir = path3.dirname(pathStr);
4932
+ const childPaths = [];
4933
+ for (const call of ir.calls) {
4934
+ const child = resolvePath(call.path, parentDir);
4935
+ childPaths.push(child);
4936
+ this.visitScript(child, depth + 1, path3.dirname(child), call.args);
4937
+ if (this.result.hasCritical) break;
4938
+ }
4939
+ node.childPaths = childPaths;
4940
+ this.visiting.delete(pathStr);
4941
+ }
4942
+ };
4943
+
4944
+ // src/engine/script-content-analyzer.ts
4945
+ function emptyChain() {
4946
+ return {
4947
+ nodes: [],
4948
+ entryPath: "",
4949
+ maxDepthReached: false,
4950
+ maxSizeReached: false,
4951
+ maxNodesReached: false,
4952
+ totalSize: 0,
4953
+ totalLines: 0,
4954
+ totalNodes: 0,
4955
+ hasCritical: false,
4956
+ hasHigh: false,
4957
+ firstHitNode: null,
4958
+ error: ""
4959
+ };
4960
+ }
4961
+ var SUSPICIOUS_PATTERNS2 = [
4962
+ { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
4963
+ { name: "curl --data", pattern: /curl\s+(.*\s)?--data/i },
4964
+ { name: "curl -d", pattern: /curl\s+(.*\s)?-d\s/i },
4965
+ { name: "wget --post-data", pattern: /wget\s+(.*\s)?--post-data/i },
4966
+ { name: "wget --post-file", pattern: /wget\s+(.*\s)?--post-file/i },
4967
+ { name: "nc -e", pattern: /\bnc\s+.*-e\b/ },
4968
+ { name: "ncat -e", pattern: /\bncat\s+.*-e\b/ },
4969
+ { name: "/dev/tcp", pattern: /\/dev\/tcp\// },
4970
+ { name: "/dev/udp", pattern: /\/dev\/udp\// },
4971
+ { name: "$(base64", pattern: /\$\(\s*base64/ },
4972
+ { name: "`base64", pattern: /`\s*base64/ },
4973
+ { name: "base64 decode pipe", pattern: /base64\s+(-d|--decode)\s*\|/ },
4974
+ { name: "/etc/shadow", pattern: /\/etc\/shadow/ },
4975
+ { name: "id_rsa", pattern: /\bid_rsa\b/ },
4976
+ { name: "id_ed25519", pattern: /\bid_ed25519\b/ },
4977
+ { name: "crontab install", pattern: /crontab\s+-/ },
4978
+ { name: "authorized_keys", pattern: /authorized_keys/ },
4979
+ { name: "ssh no host key check", pattern: /ssh\s+.*StrictHostKeyChecking\s*=\s*no/ }
4980
+ ];
4981
+ var SCRIPT_EXEC_PATTERNS2 = [
4982
+ /(?:^|\s|&&|\|\|)\s*bash\s+(\S+)/i,
4983
+ /(?:^|\s|&&|\|\|)\s*\/bin\/bash\s+(\S+)/i,
4984
+ /(?:^|\s|&&|\|\|)\s*sh\s+(\S+)/i,
4985
+ /(?:^|\s|&&|\|\|)\s*zsh\s+(\S+)/i,
4986
+ /\.\/(\S+)/,
4987
+ /(?:^|\s|&&|\|\|)\s*source\s+(\S+)/i,
4988
+ /(?:^|\s|&&|\|\|)\s*\.\s+(\S+)/,
4989
+ /(?:^|\s|&&|\|\|)\s*python3?\s+(\S+\.py)/i,
4990
+ /(?:^|\s|&&|\|\|)\s*node\s+(\S+\.js)/i,
4991
+ /(?:^|\s|&&|\|\|)\s*ruby\s+(\S+\.rb)/i,
4992
+ /(?:^|\s|&&|\|\|)\s*perl\s+(\S+\.pl)/i,
4993
+ /(?:^|\s|&&|\|\|)\s*php\s+(\S+\.php)/i,
4994
+ /(?:^|\s|&&|\|\|)\s*lua\s+(\S+\.lua)/i
3310
4995
  ];
3311
4996
  var KEYWORD_COMBINATION_RISKS2 = [
3312
4997
  {
@@ -3402,6 +5087,20 @@ function extractScriptPath2(command) {
3402
5087
  }
3403
5088
  return null;
3404
5089
  }
5090
+ function extractScriptPathWithArgs(command) {
5091
+ const scriptPath = extractScriptPath2(command);
5092
+ if (!scriptPath) return { path: null, args: [] };
5093
+ const idx = command.indexOf(scriptPath);
5094
+ if (idx < 0) return { path: scriptPath, args: [] };
5095
+ let rest = command.slice(idx + scriptPath.length);
5096
+ rest = rest.split(/[;|&<>]/)[0].trim();
5097
+ const tokens = [...rest.matchAll(/(["'])(.*?)\1|(\S+)/g)];
5098
+ const args = [];
5099
+ for (const m of tokens) {
5100
+ args.push(m[1] ? m[2] : m[3]);
5101
+ }
5102
+ return { path: scriptPath, args };
5103
+ }
3405
5104
  function extractInlineScriptPath(actionDetail) {
3406
5105
  const re = /open\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
3407
5106
  let m;
@@ -3409,9 +5108,9 @@ function extractInlineScriptPath(actionDetail) {
3409
5108
  const p = m[1];
3410
5109
  let resolved;
3411
5110
  try {
3412
- resolved = resolvePath(p);
3413
- if (existsSync7(resolved)) {
3414
- const stat = statSync(resolved);
5111
+ resolved = resolvePath2(p);
5112
+ if (existsSync9(resolved)) {
5113
+ const stat = statSync2(resolved);
3415
5114
  if (stat.isFile()) return resolved;
3416
5115
  }
3417
5116
  } catch {
@@ -3419,136 +5118,12 @@ function extractInlineScriptPath(actionDetail) {
3419
5118
  }
3420
5119
  return null;
3421
5120
  }
3422
- function resolvePath(p) {
5121
+ function resolvePath2(p) {
3423
5122
  let expanded = p;
3424
5123
  if (expanded.startsWith("~/") || expanded === "~") {
3425
- expanded = join4(homedir2(), expanded.slice(1));
3426
- }
3427
- return resolve6(expanded);
3428
- }
3429
- function readScriptContent2(scriptPath, maxFileSize = 2 * 1024 * 1024) {
3430
- try {
3431
- const path3 = resolvePath(scriptPath);
3432
- if (!existsSync7(path3)) return { content: "", lineCount: 0, fileSize: 0, error: `\u811A\u672C\u4E0D\u5B58\u5728: ${scriptPath}` };
3433
- const stat = statSync(path3);
3434
- if (!stat.isFile()) return { content: "", lineCount: 0, fileSize: 0, error: `\u4E0D\u662F\u6587\u4EF6: ${scriptPath}` };
3435
- if (stat.size > maxFileSize) {
3436
- return { content: "", lineCount: 0, fileSize: stat.size, error: `\u811A\u672C\u6587\u4EF6\u8FC7\u5927 (${stat.size} bytes)\uFF0C\u8DF3\u8FC7\u8BFB\u53D6` };
3437
- }
3438
- const content = readFileSync8(path3, "utf-8");
3439
- const lineCount = content ? content.split("\n").length : 0;
3440
- return { content, lineCount, fileSize: stat.size, error: "" };
3441
- } catch (e) {
3442
- const msg = e instanceof Error ? e.message : String(e);
3443
- return { content: "", lineCount: 0, fileSize: 0, error: `\u8BFB\u53D6\u811A\u672C\u5931\u8D25: ${msg}` };
3444
- }
3445
- }
3446
- function extractChildScriptPaths(content, baseDir) {
3447
- const paths = [];
3448
- for (const pattern of CHILD_SCRIPT_PATTERNS) {
3449
- pattern.lastIndex = 0;
3450
- let m;
3451
- while ((m = pattern.exec(content)) !== null) {
3452
- const pathStr = m[1];
3453
- if (pathStr.startsWith("-") || pathStr.startsWith("$")) continue;
3454
- if (pathStr.includes("/dev/") || pathStr.startsWith("|") || pathStr.startsWith(";")) continue;
3455
- const full = isAbsolute(pathStr) ? pathStr : join4(baseDir, pathStr);
3456
- const resolved = resolvePath(full);
3457
- if (existsSync7(resolved)) {
3458
- try {
3459
- if (statSync(resolved).isFile() && !paths.includes(resolved)) paths.push(resolved);
3460
- } catch {
3461
- }
3462
- }
3463
- }
3464
- }
3465
- return paths;
3466
- }
3467
- function resolveScriptChain(entryPath, input, maxDepth = 3, maxFileSize = 2 * 1024 * 1024, maxTotalSize = 6 * 1024 * 1024) {
3468
- const visited = /* @__PURE__ */ new Set();
3469
- const result = emptyChain();
3470
- result.entryPath = entryPath;
3471
- const entry = resolvePath(entryPath);
3472
- const baseDir = existsSync7(entry) ? dirname6(entry) : input.cwd || process.cwd();
3473
- resolveRecursive({
3474
- path: entry,
3475
- depth: 0,
3476
- maxDepth,
3477
- maxFileSize,
3478
- maxTotalSize,
3479
- visited,
3480
- result,
3481
- baseDir,
3482
- input
3483
- });
3484
- result.hasCritical = result.nodes.some((n) => n.l1RiskLevel === "critical" || n.l2RiskLevel === "critical" || n.keywordRiskLevel === "critical");
3485
- result.hasHigh = result.nodes.some((n) => ["high", "critical"].includes(n.l1RiskLevel) || ["high", "critical"].includes(n.l2RiskLevel) || ["high", "critical"].includes(n.keywordRiskLevel));
3486
- result.firstHitNode = result.nodes.find((n) => n.l1Hit || n.l2Hit || n.isSuspicious) || null;
3487
- return result;
3488
- }
3489
- function resolveRecursive(ctx) {
3490
- const { path: path3, depth, maxDepth, maxFileSize, maxTotalSize, visited, result, baseDir, input } = ctx;
3491
- if (visited.has(path3)) return;
3492
- if (depth > maxDepth) {
3493
- result.maxDepthReached = true;
3494
- return;
3495
- }
3496
- if (result.totalSize >= maxTotalSize) {
3497
- result.maxSizeReached = true;
3498
- return;
3499
- }
3500
- visited.add(path3);
3501
- const { content, lineCount, fileSize, error } = readScriptContent2(path3, maxFileSize);
3502
- if (error) {
3503
- const node2 = newNode(path3, depth);
3504
- node2.error = error;
3505
- node2.fileSize = fileSize;
3506
- node2.lineCount = lineCount;
3507
- result.nodes.push(node2);
3508
- return;
3509
- }
3510
- result.totalSize += fileSize;
3511
- result.totalLines += lineCount;
3512
- const preprocessed = preprocessScriptContent(content);
3513
- const node = newNode(path3, depth);
3514
- node.rawContent = content;
3515
- node.preprocessedContent = preprocessed;
3516
- node.lineCount = lineCount;
3517
- node.fileSize = fileSize;
3518
- result.nodes.push(node);
3519
- const virtualInput = {
3520
- ...input,
3521
- aNext: { actionType: "bash", actionDetail: preprocessed }
3522
- };
3523
- const l1 = runHeuristicCheck(virtualInput);
3524
- if (l1 && !l1.allowed) {
3525
- node.l1Hit = true;
3526
- node.l1RiskLevel = l1.riskLevel;
3527
- node.l1RiskType = l1.riskType;
3528
- node.l1Reason = l1.reason;
3529
- node.l1MatchedPatterns = [];
3530
- if (l1.riskLevel === "critical") {
3531
- result.hasCritical = true;
3532
- result.firstHitNode = node;
3533
- return;
3534
- }
3535
- }
3536
- const childPaths = extractChildScriptPaths(preprocessed, baseDir);
3537
- node.childPaths = childPaths;
3538
- for (const childPath of childPaths) {
3539
- resolveRecursive({
3540
- path: resolvePath(childPath),
3541
- depth: depth + 1,
3542
- maxDepth,
3543
- maxFileSize,
3544
- maxTotalSize,
3545
- visited,
3546
- result,
3547
- baseDir: dirname6(resolvePath(childPath)),
3548
- input
3549
- });
3550
- if (result.hasCritical) return;
5124
+ expanded = join5(homedir3(), expanded.slice(1));
3551
5125
  }
5126
+ return resolve8(expanded);
3552
5127
  }
3553
5128
  function scanScriptChainWithL1(chain, input) {
3554
5129
  for (const node of chain.nodes) {
@@ -3617,14 +5192,31 @@ function scanScriptChainKeywords(chain) {
3617
5192
  function analyzeScriptContent2(input) {
3618
5193
  const actionType = input.aNext.actionType.toLowerCase();
3619
5194
  if (actionType !== "bash") return emptyChain();
3620
- const scriptPath = extractScriptPath2(input.aNext.actionDetail);
5195
+ const { path: scriptPath, args: scriptArgs } = extractScriptPathWithArgs(input.aNext.actionDetail);
3621
5196
  let resolvedPath = scriptPath;
5197
+ let isInline = false;
3622
5198
  if (!resolvedPath) {
3623
5199
  const inline = extractInlineScriptPath(input.aNext.actionDetail);
3624
5200
  if (!inline) return emptyChain();
3625
5201
  resolvedPath = inline;
5202
+ isInline = true;
3626
5203
  }
3627
- const chain = resolveScriptChain(resolvedPath, input);
5204
+ if (resolvedPath && !isAbsolute2(resolvedPath) && !resolvedPath.startsWith("~/")) {
5205
+ resolvedPath = join5(input.cwd || process.cwd(), resolvedPath);
5206
+ }
5207
+ const l1Adapter = (content) => {
5208
+ const virtualInput = {
5209
+ ...input,
5210
+ aNext: { actionType: "bash", actionDetail: content }
5211
+ };
5212
+ const j = runHeuristicCheck(virtualInput);
5213
+ if (j && !j.allowed) {
5214
+ return { hit: true, riskLevel: j.riskLevel, riskType: j.riskType, reason: j.reason, matchedPatterns: [] };
5215
+ }
5216
+ return { hit: false, riskLevel: "", riskType: "", reason: "", matchedPatterns: [] };
5217
+ };
5218
+ const tracker = new RecursiveTracker(l1Adapter);
5219
+ const chain = tracker.track(resolvedPath, isInline ? null : scriptArgs, input.cwd || process.cwd());
3628
5220
  if (chain.nodes.length === 0) return chain;
3629
5221
  scanScriptChainWithL1(chain, input);
3630
5222
  scanScriptChainWithL2(chain, input);
@@ -3692,18 +5284,6 @@ ${contentPreview}
3692
5284
  if (chain.maxSizeReached) header += "\u26A0\uFE0F \u6CE8\u610F\uFF1A\u7D2F\u8BA1\u6587\u4EF6\u5927\u5C0F\u89E6\u8FBE\u4E0A\u9650\uFF0C\u90E8\u5206\u811A\u672C\u53EF\u80FD\u672A\u88AB\u5B8C\u6574\u8BFB\u53D6\u3002\n";
3693
5285
  return header + parts.join("\n---\n\n");
3694
5286
  }
3695
- var CHILD_SCRIPT_PATTERNS = [
3696
- /(?:^|\s|&&|\|\|)\s*bash\s+(\S+)/i,
3697
- /(?:^|\s|&&|\|\|)\s*sh\s+(\S+)/i,
3698
- /(?:^|\s|&&|\|\|)\s*source\s+(\S+)/i,
3699
- /(?:^|\s|&&|\|\|)\s*\.\s+(\S+)/,
3700
- /(?:^|\s|&&|\|\|)\s*python3?\s+(\S+\.py)/i,
3701
- /(?:^|\s|&&|\|\|)\s*node\s+(\S+\.js)/i,
3702
- /(?:^|\s|&&|\|\|)\s*ruby\s+(\S+\.rb)/i,
3703
- /(?:^|\s|&&|\|\|)\s*perl\s+(\S+\.pl)/i,
3704
- /(?:^|\s|&&|\|\|)\s*php\s+(\S+\.php)/i,
3705
- /(?:^|\s|&&|\|\|)\s*lua\s+(\S+\.lua)/i
3706
- ];
3707
5287
 
3708
5288
  // src/engine/llm-analyzer.ts
3709
5289
  import { appendFileSync } from "fs";
@@ -3919,7 +5499,7 @@ async function analyzeWithLLM(prompt, config) {
3919
5499
  }
3920
5500
  if (attempt < maxRetries) {
3921
5501
  console.error(`[AgentMoss] LLM call failed (attempt ${attempt + 1}/${maxRetries + 1}), retrying in ${retryCfg.retryInterval}s:`, err.message);
3922
- await new Promise((resolve7) => setTimeout(resolve7, retryCfg.retryInterval * 1e3));
5502
+ await new Promise((resolve9) => setTimeout(resolve9, retryCfg.retryInterval * 1e3));
3923
5503
  } else {
3924
5504
  clearTimeout(totalTimeoutTimer);
3925
5505
  console.error("[AgentMoss] LLM call failed after all retries:", err.message);
@@ -3934,16 +5514,17 @@ async function analyzeWithLLM(prompt, config) {
3934
5514
  // src/engine/coordinator.ts
3935
5515
  init_config();
3936
5516
  init_agents();
5517
+ init_scenarios();
3937
5518
 
3938
5519
  // src/brain/index.ts
3939
5520
  init_esm_shims();
3940
5521
 
3941
5522
  // src/brain/rule-store.ts
3942
5523
  init_esm_shims();
3943
- import { existsSync as existsSync9, readFileSync as readFileSync10, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
3944
- import { join as join5 } from "path";
3945
- import { homedir as homedir3 } from "os";
3946
- var DEFAULT_RULES_PATH = join5(homedir3(), ".agentmoss", "brain", "rules.json");
5524
+ import { existsSync as existsSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
5525
+ import { join as join6 } from "path";
5526
+ import { homedir as homedir4 } from "os";
5527
+ var DEFAULT_RULES_PATH = join6(homedir4(), ".agentmoss", "brain", "rules.json");
3947
5528
  var ENV_RULES_PATH = "AGENT_MOSS_BRAIN_RULES_PATH";
3948
5529
  var STATUS_DRAFT = "draft";
3949
5530
  var STATUS_APPROVED = "approved";
@@ -3967,16 +5548,16 @@ function uuid() {
3967
5548
  var BrainRuleStore = class {
3968
5549
  path;
3969
5550
  data;
3970
- constructor(path3) {
3971
- this.path = path3 || getRulesPath();
5551
+ constructor(path5) {
5552
+ this.path = path5 || getRulesPath();
3972
5553
  this.data = this.load();
3973
5554
  }
3974
5555
  load() {
3975
- if (!existsSync9(this.path)) {
5556
+ if (!existsSync11(this.path)) {
3976
5557
  return { version: 1, rules: [], pattern_candidates: [] };
3977
5558
  }
3978
5559
  try {
3979
- const raw = JSON.parse(readFileSync10(this.path, "utf-8"));
5560
+ const raw = JSON.parse(readFileSync12(this.path, "utf-8"));
3980
5561
  return {
3981
5562
  version: raw.version || 1,
3982
5563
  rules: raw.rules || [],
@@ -4274,10 +5855,10 @@ var PatternExtractor = class {
4274
5855
  const pathRe = new RegExp(pathPrefix + pathBody + pathSuffix, "g");
4275
5856
  let mm;
4276
5857
  while ((mm = pathRe.exec(src)) !== null) {
4277
- const path3 = mm[1];
4278
- if (path3.split("/").length < 3) continue;
4279
- if (["/dev/null", "/dev/zero", "/dev/random", "/dev/urandom"].includes(path3)) continue;
4280
- const gen = this.generalizePath(path3);
5858
+ const path5 = mm[1];
5859
+ if (path5.split("/").length < 3) continue;
5860
+ if (["/dev/null", "/dev/zero", "/dev/random", "/dev/urandom"].includes(path5)) continue;
5861
+ const gen = this.generalizePath(path5);
4281
5862
  if (seen.has(gen)) continue;
4282
5863
  seen.add(gen);
4283
5864
  patterns.push({ pattern: gen, layer: 1, scope: "openat", ruleType: "deny_path" });
@@ -4296,8 +5877,8 @@ var PatternExtractor = class {
4296
5877
  }
4297
5878
  return false;
4298
5879
  }
4299
- generalizePath(path3) {
4300
- const p = path3.toLowerCase();
5880
+ generalizePath(path5) {
5881
+ const p = path5.toLowerCase();
4301
5882
  if (/\/etc\/(shadow|passwd|sudoers|security|ssh)/.test(p)) {
4302
5883
  return "(?:^|[^a-z0-9_-])etc/(?:shadow|passwd|sudoers|security/|ssh/)";
4303
5884
  }
@@ -4460,6 +6041,468 @@ function checkLayer2Context(pattern, actionHistory, actionDetail) {
4460
6041
  return true;
4461
6042
  }
4462
6043
 
6044
+ // src/engine/formal-engine/index.ts
6045
+ init_esm_shims();
6046
+
6047
+ // src/engine/formal-engine/engine.ts
6048
+ init_esm_shims();
6049
+
6050
+ // src/engine/formal-engine/builder.ts
6051
+ init_esm_shims();
6052
+ init_rules_loader();
6053
+
6054
+ // src/engine/formal-engine/decode.ts
6055
+ init_esm_shims();
6056
+ var B64_TOKEN_RE = /[A-Za-z0-9+/]+={0,2}/g;
6057
+ function isValidBase64Token(tok) {
6058
+ if (tok.length < 8) return false;
6059
+ if (!(tok.length % 4 === 0 || tok.endsWith("==") || tok.endsWith("="))) return false;
6060
+ return true;
6061
+ }
6062
+ function isPrintable(s) {
6063
+ for (let i = 0; i < s.length; i++) {
6064
+ const c = s.charCodeAt(i);
6065
+ if (c < 32 || c > 126) return false;
6066
+ }
6067
+ return true;
6068
+ }
6069
+ function decodeBase64Literals(text) {
6070
+ const out = [];
6071
+ const seen = /* @__PURE__ */ new Set();
6072
+ for (const m of text.matchAll(B64_TOKEN_RE)) {
6073
+ const tok = m[0];
6074
+ if (!isValidBase64Token(tok)) continue;
6075
+ if (!/[A-Z]/.test(tok) || !/[a-z]/.test(tok)) continue;
6076
+ try {
6077
+ const padded = tok + "=".repeat((4 - tok.length % 4) % 4);
6078
+ const bin = Buffer.from(padded, "base64");
6079
+ const s = bin.toString("utf8");
6080
+ if (s && isPrintable(s) && !seen.has(s)) {
6081
+ seen.add(s);
6082
+ out.push(s);
6083
+ }
6084
+ } catch {
6085
+ }
6086
+ }
6087
+ return out;
6088
+ }
6089
+ function decodeUrlAndNumeric(text) {
6090
+ const out = [];
6091
+ let s = text;
6092
+ for (let i = 0; i < 3; i++) {
6093
+ let nxt = s;
6094
+ try {
6095
+ nxt = decodeURIComponent(s);
6096
+ } catch {
6097
+ break;
6098
+ }
6099
+ if (nxt === s) break;
6100
+ s = nxt;
6101
+ }
6102
+ if (s !== text) out.push(s);
6103
+ if (/\\[0-7]{3}/.test(text)) {
6104
+ let expanded = "";
6105
+ try {
6106
+ expanded = text.replace(/\\[0-7]{3}/g, (m) => String.fromCharCode(parseInt(m.slice(1), 8)));
6107
+ } catch {
6108
+ }
6109
+ if (expanded && expanded !== text) out.push(expanded);
6110
+ }
6111
+ if (/\\x[0-9a-fA-F]{2}/.test(text)) {
6112
+ let expanded = "";
6113
+ try {
6114
+ expanded = text.replace(/\\x[0-9a-fA-F]{2}/g, (m) => String.fromCharCode(parseInt(m.slice(2), 16)));
6115
+ } catch {
6116
+ }
6117
+ if (expanded && expanded !== text) out.push(expanded);
6118
+ }
6119
+ return out;
6120
+ }
6121
+ function allCandidates(detail, max = 8) {
6122
+ const cands = [detail];
6123
+ cands.push(...decodeBase64Literals(detail));
6124
+ cands.push(...decodeUrlAndNumeric(detail));
6125
+ const seen = /* @__PURE__ */ new Set();
6126
+ const out = [];
6127
+ for (const c of cands) {
6128
+ if (seen.has(c) || c.length > 4096) continue;
6129
+ seen.add(c);
6130
+ out.push(c);
6131
+ if (out.length >= max) break;
6132
+ }
6133
+ return out;
6134
+ }
6135
+ function matchStageAny(detail, stages) {
6136
+ for (const cand of allCandidates(detail)) {
6137
+ for (const [name, pats] of Object.entries(stages)) {
6138
+ for (const p of pats) {
6139
+ p.lastIndex = 0;
6140
+ if (p.test(cand)) return name;
6141
+ }
6142
+ }
6143
+ }
6144
+ return "";
6145
+ }
6146
+
6147
+ // src/engine/formal-engine/builder.ts
6148
+ var COMBO_STAGES2 = null;
6149
+ function loadStages() {
6150
+ if (COMBO_STAGES2) return COMBO_STAGES2;
6151
+ const combo = loadL2().combo_chains || {};
6152
+ const stages = {};
6153
+ for (const [name, pats] of Object.entries(combo.stages || {})) {
6154
+ stages[name] = pats.map((p) => new RegExp(p, "i"));
6155
+ }
6156
+ COMBO_STAGES2 = stages;
6157
+ return stages;
6158
+ }
6159
+ var PATH_RE = /(?:^|\s|[@=])((?:\/|~\/)[\w./~-]+)/g;
6160
+ function extractResources(actionDetail) {
6161
+ const resources = /* @__PURE__ */ new Set();
6162
+ const text = actionDetail.replace(/'[^']*'|"[^"]*"/g, "");
6163
+ PATH_RE.lastIndex = 0;
6164
+ let m;
6165
+ while ((m = PATH_RE.exec(text)) !== null) {
6166
+ const p = m[1];
6167
+ if (p.startsWith("/dev/") || p.includes("://")) continue;
6168
+ resources.add(p);
6169
+ }
6170
+ return resources;
6171
+ }
6172
+ function markStage(actionDetail) {
6173
+ return matchStageAny(actionDetail, loadStages());
6174
+ }
6175
+ function buildSessionGraph(actionHistory, aNext, sessionId = "current") {
6176
+ const nodes = [];
6177
+ const edges = [];
6178
+ const stageFlags = /* @__PURE__ */ new Set();
6179
+ const details = [];
6180
+ for (const a of actionHistory) {
6181
+ const ad = String(a.actionDetail || "");
6182
+ if (ad) details.push({ actionType: String(a.actionType || ""), actionDetail: ad });
6183
+ }
6184
+ if (aNext) {
6185
+ details.push({ actionType: String(aNext.actionType || ""), actionDetail: String(aNext.actionDetail || "") });
6186
+ }
6187
+ details.forEach((d, seq) => {
6188
+ const stage = markStage(d.actionDetail);
6189
+ const node = {
6190
+ sessionId,
6191
+ seq,
6192
+ actionType: d.actionType,
6193
+ actionDetail: d.actionDetail,
6194
+ stage,
6195
+ resources: extractResources(d.actionDetail)
6196
+ };
6197
+ nodes.push(node);
6198
+ if (stage) stageFlags.add(stage);
6199
+ });
6200
+ for (let i = 0; i < nodes.length - 1; i++) {
6201
+ edges.push({ src: i, dst: i + 1, kind: "next" });
6202
+ }
6203
+ return { sessionId, nodes, edges, stageFlags };
6204
+ }
6205
+ function buildMultiSessionGraph(sessions, aNext) {
6206
+ const mg = { sessions: [], crossEdges: [] };
6207
+ if (!sessions || sessions.length === 0) {
6208
+ mg.sessions.push(buildSessionGraph([], aNext, "current"));
6209
+ return mg;
6210
+ }
6211
+ for (const s of sessions) {
6212
+ const sid = String(s.sessionId || "s");
6213
+ mg.sessions.push(buildSessionGraph(s.actionHistory || [], void 0, sid));
6214
+ }
6215
+ if (aNext) {
6216
+ const last = mg.sessions[mg.sessions.length - 1];
6217
+ if (last.sessionId === "current") {
6218
+ const stage = markStage(String(aNext.actionDetail || ""));
6219
+ last.nodes.push({
6220
+ sessionId: last.sessionId,
6221
+ seq: last.nodes.length,
6222
+ actionType: String(aNext.actionType || ""),
6223
+ actionDetail: String(aNext.actionDetail || ""),
6224
+ stage,
6225
+ resources: extractResources(String(aNext.actionDetail || ""))
6226
+ });
6227
+ if (last.nodes.length > 1) {
6228
+ last.edges.push({ src: last.nodes.length - 2, dst: last.nodes.length - 1, kind: "next" });
6229
+ }
6230
+ } else {
6231
+ mg.sessions.push(buildSessionGraph([], aNext, "current"));
6232
+ }
6233
+ }
6234
+ addCrossDataEdges(mg);
6235
+ return mg;
6236
+ }
6237
+ function addCrossDataEdges(mg) {
6238
+ const nodes = allNodesFlat(mg);
6239
+ if (nodes.length < 2) return;
6240
+ for (let i = 0; i < nodes.length; i++) {
6241
+ for (let j = i + 1; j < nodes.length; j++) {
6242
+ if (nodes[i].sessionId === nodes[j].sessionId) continue;
6243
+ const shared = intersect(nodes[i].resources, nodes[j].resources);
6244
+ if (shared.size > 0) {
6245
+ mg.crossEdges.push({ src: i, dst: j, kind: "data" });
6246
+ }
6247
+ }
6248
+ }
6249
+ }
6250
+ function allNodesFlat(mg) {
6251
+ const out = [];
6252
+ for (const s of mg.sessions) out.push(...s.nodes);
6253
+ return out;
6254
+ }
6255
+ function intersect(a, b) {
6256
+ const out = /* @__PURE__ */ new Set();
6257
+ for (const v of a) if (b.has(v)) out.add(v);
6258
+ return out;
6259
+ }
6260
+
6261
+ // src/engine/formal-engine/ir.ts
6262
+ init_esm_shims();
6263
+ function allNodes(mg) {
6264
+ const out = [];
6265
+ for (const s of mg.sessions) out.push(...s.nodes);
6266
+ return out;
6267
+ }
6268
+ function allEdges(mg) {
6269
+ const out = [];
6270
+ for (const s of mg.sessions) out.push(...s.edges);
6271
+ out.push(...mg.crossEdges);
6272
+ return out;
6273
+ }
6274
+ function emptyVerificationResult() {
6275
+ return { verified: true, violations: [] };
6276
+ }
6277
+
6278
+ // src/engine/formal-engine/properties.ts
6279
+ init_esm_shims();
6280
+ init_rules_loader();
6281
+ var CROSS_SESSION_CHAINS = null;
6282
+ function loadCrossSessionChains() {
6283
+ if (CROSS_SESSION_CHAINS) return CROSS_SESSION_CHAINS;
6284
+ const combo = loadL2().combo_chains || {};
6285
+ CROSS_SESSION_CHAINS = (combo.chains || []).map((c) => ({
6286
+ id: String(c.id || "combo_chain"),
6287
+ stages: c.stages || [],
6288
+ riskLevel: String(c.riskLevel || c.risk_level || "high"),
6289
+ reason: "\u8DE8\u4F1A\u8BDD" + String(c.reason || "\u7EC4\u5408\u653B\u51FB\u94FE")
6290
+ }));
6291
+ return CROSS_SESSION_CHAINS;
6292
+ }
6293
+ var CRED_FILE_RE = /(known_hosts|\.ssh|id_rsa|\/etc\/shadow|\/etc\/passwd|credentials)/i;
6294
+ var EXFIL_RE = /https?:\/\/|scp\s+.*@|rsync\s+.*@/i;
6295
+ function checkCrossSessionChain(graph) {
6296
+ const violations = [];
6297
+ const nodes = allNodes(graph);
6298
+ if (nodes.length === 0) return violations;
6299
+ for (const chain of loadCrossSessionChains()) {
6300
+ const stages = chain.stages;
6301
+ if (!stages.length) continue;
6302
+ let matched = 0;
6303
+ let stageIdx = 0;
6304
+ const path5 = [];
6305
+ for (const n of nodes) {
6306
+ if (stageIdx < stages.length && n.stage === stages[stageIdx]) {
6307
+ matched += 1;
6308
+ path5.push({ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) });
6309
+ stageIdx += 1;
6310
+ if (matched >= stages.length) break;
6311
+ }
6312
+ }
6313
+ if (matched >= stages.length) {
6314
+ violations.push({
6315
+ propertyId: "cross-" + chain.id,
6316
+ reason: chain.reason,
6317
+ riskLevel: chain.riskLevel,
6318
+ path: path5
6319
+ });
6320
+ }
6321
+ }
6322
+ return violations;
6323
+ }
6324
+ function checkSensitiveDataFlow(graph) {
6325
+ const violations = [];
6326
+ const nodes = allNodes(graph);
6327
+ if (nodes.length === 0) return violations;
6328
+ const tainted = /* @__PURE__ */ new Map();
6329
+ for (let i = 0; i < nodes.length; i++) {
6330
+ const n = nodes[i];
6331
+ if (n.stage === "CRED" && [...n.resources].some((r) => CRED_FILE_RE.test(r))) {
6332
+ tainted.set(i, `${n.sessionId}#${n.seq} \u8BBF\u95EE ${[...n.resources][0]}`);
6333
+ }
6334
+ }
6335
+ if (tainted.size === 0) return violations;
6336
+ const edges = allEdges(graph);
6337
+ let changed = true;
6338
+ while (changed) {
6339
+ changed = false;
6340
+ for (const e of edges) {
6341
+ if (tainted.has(e.src) && !tainted.has(e.dst)) {
6342
+ const srcRes = nodes[e.src].resources;
6343
+ const dstRes = nodes[e.dst].resources;
6344
+ for (const r of srcRes) {
6345
+ if (dstRes.has(r)) {
6346
+ tainted.set(e.dst, tainted.get(e.src));
6347
+ changed = true;
6348
+ break;
6349
+ }
6350
+ }
6351
+ }
6352
+ }
6353
+ }
6354
+ for (const [i, n] of nodes.entries()) {
6355
+ if (tainted.has(i) && EXFIL_RE.test(n.actionDetail)) {
6356
+ violations.push({
6357
+ propertyId: "sensitive-data-exfil",
6358
+ reason: `\u654F\u611F\u6570\u636E\u4F20\u64AD\u5916\u4F20\uFF1A${tainted.get(i)} \u2192 ${n.sessionId}#${n.seq} \u7ECF\u5171\u4EAB\u8F7D\u4F53\u5916\u53D1`,
6359
+ riskLevel: "critical",
6360
+ path: [{ sessionId: n.sessionId, seq: n.seq, detail: n.actionDetail.slice(0, 60) }]
6361
+ });
6362
+ }
6363
+ }
6364
+ return violations;
6365
+ }
6366
+ var EBPF_NET_RE = /connect|sendto|network_connect/i;
6367
+ var REMOTE_RE = /to\s+\d+\.\d+\.\d+\.\d+|remote_addr/i;
6368
+ var PRIVESC_RE = /\bsudo\b|\bsu\b|chmod\s+u\s*\+\s*s|pkexec|setuid/i;
6369
+ var EXFIL_ACTION_RE = /\bcurl\b|\bwget\b|\bscp\b|\brsync\b|\brclone\b|\bftp\b|connect\s+.*\d+\.\d+\.\d+\.\d+|open\s+.*tcp|base64\s+.*\-o/i;
6370
+ var CRED_STRICT_RE = /(known_hosts|\.ssh|id_rsa|\.pem|\b\.key\b|\/etc\/shadow|\/etc\/passwd|\.aws\/credentials|credentials\.json|netrc|password|secrets?\.ya?ml|credentials?\s*(file|path)?\s*(=|\:)|\bconfig.*(password|token))/i;
6371
+ function isExfilNode(n) {
6372
+ if (n.stage === "EXFIL") return true;
6373
+ if (EXFIL_RE.test(n.actionDetail) && EXFIL_ACTION_RE.test(n.actionDetail)) return true;
6374
+ if (EBPF_NET_RE.test(n.actionDetail) && REMOTE_RE.test(n.actionDetail)) return true;
6375
+ return false;
6376
+ }
6377
+ function isCredNode(n) {
6378
+ if (n.stage !== "CRED") return false;
6379
+ for (const r of n.resources) {
6380
+ if (CRED_STRICT_RE.test(r)) return true;
6381
+ }
6382
+ return CRED_STRICT_RE.test(n.actionDetail);
6383
+ }
6384
+ function isPrivescNode(n) {
6385
+ if (n.stage === "PRIVESC") return true;
6386
+ return PRIVESC_RE.test(n.actionDetail);
6387
+ }
6388
+ function checkNeverExfilAfterPrivesc(graph) {
6389
+ const violations = [];
6390
+ const nodes = allNodes(graph);
6391
+ if (nodes.length < 2) return violations;
6392
+ for (let i = 0; i < nodes.length; i++) {
6393
+ if (!isPrivescNode(nodes[i])) continue;
6394
+ for (let j = i + 1; j < nodes.length; j++) {
6395
+ if (isExfilNode(nodes[j])) {
6396
+ violations.push({
6397
+ propertyId: "no-privesc-then-exfil",
6398
+ reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
6399
+ riskLevel: "critical",
6400
+ path: [
6401
+ { sessionId: nodes[i].sessionId, seq: nodes[i].seq, detail: nodes[i].actionDetail.slice(0, 60) },
6402
+ { sessionId: nodes[j].sessionId, seq: nodes[j].seq, detail: nodes[j].actionDetail.slice(0, 60) }
6403
+ ]
6404
+ });
6405
+ return violations;
6406
+ }
6407
+ }
6408
+ }
6409
+ return violations;
6410
+ }
6411
+ function checkNeverExfilAfterCred(graph) {
6412
+ const violations = [];
6413
+ const nodes = allNodes(graph);
6414
+ if (nodes.length < 2) return violations;
6415
+ for (let i = 0; i < nodes.length; i++) {
6416
+ if (!isCredNode(nodes[i])) continue;
6417
+ for (let j = i + 1; j < nodes.length; j++) {
6418
+ if (isExfilNode(nodes[j])) {
6419
+ violations.push({
6420
+ propertyId: "no-cred-then-exfil",
6421
+ reason: "\u8BFB\u53D6\u51ED\u636E\u540E\u51FA\u73B0\u5916\u4F20\uFF0C\u8FDD\u53CD \u25A1(CRED\u2192\xAC\u25C7EXFIL)",
6422
+ riskLevel: "critical",
6423
+ path: [
6424
+ { sessionId: nodes[i].sessionId, seq: nodes[i].seq, detail: nodes[i].actionDetail.slice(0, 60) },
6425
+ { sessionId: nodes[j].sessionId, seq: nodes[j].seq, detail: nodes[j].actionDetail.slice(0, 60) }
6426
+ ]
6427
+ });
6428
+ return violations;
6429
+ }
6430
+ }
6431
+ }
6432
+ return violations;
6433
+ }
6434
+ var ALL_PROPERTIES = [
6435
+ { id: "cross-session-chain", check: checkCrossSessionChain },
6436
+ { id: "sensitive-data-flow", check: checkSensitiveDataFlow },
6437
+ { id: "no-privesc-then-exfil", check: checkNeverExfilAfterPrivesc },
6438
+ { id: "no-cred-then-exfil", check: checkNeverExfilAfterCred }
6439
+ ];
6440
+
6441
+ // src/engine/formal-engine/engine.ts
6442
+ function ebpfEventToDetail(e) {
6443
+ const parts = [];
6444
+ const syscall = String(e.syscall || e.op || "");
6445
+ const argv = String(e.argv || e.pathname || "");
6446
+ const remote = String(e.remote_addr || "");
6447
+ const port = e.remote_port;
6448
+ if (syscall) parts.push(syscall);
6449
+ if (argv && argv !== "" && argv !== "None") parts.push(argv);
6450
+ if (remote) parts.push("to " + remote + (port ? ":" + String(port) : ""));
6451
+ return parts.join(" ");
6452
+ }
6453
+ var FormalEngine = class {
6454
+ properties;
6455
+ constructor(properties) {
6456
+ this.properties = properties && properties.length > 0 ? properties : ALL_PROPERTIES;
6457
+ }
6458
+ verify(sessions, aNext, ebpfEvents) {
6459
+ const all = sessions ? [...sessions] : [];
6460
+ if (ebpfEvents && ebpfEvents.length > 0) {
6461
+ all.push({
6462
+ sessionId: "ebpf-evidence",
6463
+ actionHistory: ebpfEvents.map((e) => ({
6464
+ actionType: "syscall",
6465
+ actionDetail: ebpfEventToDetail(e)
6466
+ }))
6467
+ });
6468
+ }
6469
+ const graph = buildMultiSessionGraph(all, aNext);
6470
+ const result = emptyVerificationResult();
6471
+ for (const prop of this.properties) {
6472
+ let violations;
6473
+ try {
6474
+ violations = prop.check(graph);
6475
+ } catch (e) {
6476
+ result.verified = false;
6477
+ result.violations.push({
6478
+ propertyId: prop.id,
6479
+ reason: `\u5F62\u5F0F\u5316\u6027\u8D28\u9A8C\u8BC1\u5F02\u5E38\uFF0C\u4FDD\u5B88\u62E6\u622A: ${e}`,
6480
+ riskLevel: "medium",
6481
+ path: []
6482
+ });
6483
+ return result;
6484
+ }
6485
+ if (violations.length > 0) {
6486
+ result.verified = false;
6487
+ result.violations.push(...violations);
6488
+ break;
6489
+ }
6490
+ }
6491
+ return result;
6492
+ }
6493
+ };
6494
+
6495
+ // src/engine/formal-engine/stateful.ts
6496
+ init_esm_shims();
6497
+ init_rules_loader();
6498
+
6499
+ // src/engine/formal-engine/chain_state.ts
6500
+ init_esm_shims();
6501
+ import * as fs4 from "fs";
6502
+ import * as os2 from "os";
6503
+ import * as path4 from "path";
6504
+ import { createHash } from "crypto";
6505
+
4463
6506
  // src/engine/coordinator.ts
4464
6507
  var FULLY_SAFE_TOOLS = /* @__PURE__ */ new Set([
4465
6508
  "ask_user_question",
@@ -4562,6 +6605,7 @@ function _resetBrainForTest() {
4562
6605
  _brainExtractor = null;
4563
6606
  _brainInitialized = false;
4564
6607
  }
6608
+ var formalEngine = new FormalEngine();
4565
6609
  async function runAnalysis(input) {
4566
6610
  const heuristicResult = isHeuristicEnabled() ? runHeuristicCheck(input) : null;
4567
6611
  const isInlineScriptCmd = isInlineScriptCommand(input.aNext.actionDetail.toLowerCase());
@@ -4616,7 +6660,10 @@ async function runAnalysis(input) {
4616
6660
  const actionType = input.aNext.actionType.toLowerCase();
4617
6661
  const rawDetail = extractRawCommand(input.aNext.actionDetail);
4618
6662
  const lowerDetail = rawDetail.toLowerCase();
4619
- const agentExt = getAgentExtensions(input.agentId);
6663
+ const agentExt = mergeAgentExtensions(
6664
+ getAgentExtensions(input.agentId),
6665
+ getScenarioExtensions(input.scenarioId || process.env.AGENT_MOSS_SCENARIO || "")
6666
+ );
4620
6667
  if (FULLY_SAFE_TOOLS.has(actionType)) {
4621
6668
  return {
4622
6669
  allowed: true,
@@ -4721,6 +6768,28 @@ async function runAnalysis(input) {
4721
6768
  return effectiveResult;
4722
6769
  }
4723
6770
  }
6771
+ if (isLogicRulesEnabled()) {
6772
+ const formalSessions = [];
6773
+ if (input.sessionHistory) {
6774
+ for (const s of input.sessionHistory) {
6775
+ formalSessions.push({ sessionId: s.sessionId || "s", actionHistory: s.actionHistory || [] });
6776
+ }
6777
+ }
6778
+ formalSessions.push({ sessionId: "current", actionHistory: input.actionHistory });
6779
+ const formalResult = formalEngine.verify(formalSessions, input.aNext);
6780
+ if (!formalResult.verified) {
6781
+ const v = formalResult.violations[0];
6782
+ return {
6783
+ allowed: false,
6784
+ reason: v ? v.reason : "\u5F62\u5F0F\u5316\u9A8C\u8BC1\u672A\u901A\u8FC7\uFF08\u884C\u4E3A\u6027\u8D28\u8FDD\u53CD\uFF09",
6785
+ riskLevel: v ? v.riskLevel : "high",
6786
+ riskType: "formal_verification",
6787
+ confidence: 90,
6788
+ source: "formal_engine",
6789
+ violatedLayers: ["2.4"]
6790
+ };
6791
+ }
6792
+ }
4724
6793
  if (scriptChain && scriptChain.nodes.length > 0 && isLogicRulesEnabled()) {
4725
6794
  for (const node of scriptChain.nodes) {
4726
6795
  if (node.error || !node.preprocessedContent) continue;