@kenhkl/agent-moss 0.15.2 → 0.15.4

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.
@@ -24,6 +24,12 @@
24
24
  "risk_type": "script_execution",
25
25
  "reason": "检测到强制删除文件 (rm -f /...)"
26
26
  },
27
+ {
28
+ "pattern": "\\bln\\s+(-[a-zA-Z]+\\s+)*(?:--\\s+)?-s[a-zA-Z]*\\s+(?:--\\s+)?(/(?![a-zA-Z0-9._~])|~/\\.\\.(?:\\s|$|\\\"|'|;|&|\\)|\\]|/)|~/(?![.a-zA-Z0-9_~-])|~/$|~(?:\\s|$|\\\"|'|;|&|\\)|\\])|\\$HOME/\\.\\.(?:\\s|$|\\\"|'|;|&|\\)|\\]|/)|\\$HOME/(?![.a-zA-Z0-9_~-])|\\$HOME/$|\\$HOME(?:\\s|$|\\\"|'|;|&|\\)|\\]))",
29
+ "risk_level": "high",
30
+ "risk_type": "file_access",
31
+ "reason": "检测到创建指向根目录/主目录本身的符号链接(symlink 逃逸原语:链接点进入后 ../ 可穿越到根/主目录层级,配合删除/写入实现越权)"
32
+ },
27
33
  {
28
34
  "pattern": "chmod\\s+(?:(?:-[a-zA-Z]*|--)\\s+)*[0-7]{0,3}7\\b",
29
35
  "risk_level": "critical",
@@ -867,4 +873,4 @@
867
873
  "scope": "home"
868
874
  }
869
875
  ]
870
- }
876
+ }
package/dist/server.js CHANGED
@@ -1438,6 +1438,7 @@ __export(runtime_config_exports, {
1438
1438
  getRuntimeConfigPath: () => getRuntimeConfigPath,
1439
1439
  getScenarioRules: () => getScenarioRules,
1440
1440
  hasRuntimeConfig: () => hasRuntimeConfig,
1441
+ invalidateRuntimeConfigCache: () => invalidateRuntimeConfigCache,
1441
1442
  isL2CategoryEnabled: () => isL2CategoryEnabled,
1442
1443
  isLayerEnabled: () => isLayerEnabled,
1443
1444
  loadRuntimeConfig: () => loadRuntimeConfig,
@@ -1456,7 +1457,7 @@ __export(runtime_config_exports, {
1456
1457
  updateSkillCategoryEnabled: () => updateSkillCategoryEnabled,
1457
1458
  updateSkillEnabled: () => updateSkillEnabled
1458
1459
  });
1459
- import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync4, mkdirSync, unlinkSync, readdirSync as readdirSync3 } from "fs";
1460
+ import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync4, mkdirSync, unlinkSync, readdirSync as readdirSync3, statSync } from "fs";
1460
1461
  import { join as join3 } from "path";
1461
1462
  import { fileURLToPath as fileURLToPath5 } from "url";
1462
1463
  import { dirname as dirname3 } from "path";
@@ -1473,6 +1474,7 @@ function saveRuntimeConfig(config) {
1473
1474
  const dir = p.substring(0, p.lastIndexOf("/"));
1474
1475
  if (dir && !existsSync4(dir)) mkdirSync(dir, { recursive: true });
1475
1476
  writeFileSync(p, JSON.stringify(config, null, 2), "utf-8");
1477
+ invalidateRuntimeConfigCache();
1476
1478
  }
1477
1479
  function emptyRuntimeConfig() {
1478
1480
  return {
@@ -1486,6 +1488,9 @@ function emptyRuntimeConfig() {
1486
1488
  scenario_rules: {}
1487
1489
  };
1488
1490
  }
1491
+ function invalidateRuntimeConfigCache() {
1492
+ runtimeConfigCache = null;
1493
+ }
1489
1494
  function loadRuntimeConfig() {
1490
1495
  const p = getRuntimeConfigPath();
1491
1496
  if (!existsSync4(p)) {
@@ -1497,9 +1502,14 @@ function loadRuntimeConfig() {
1497
1502
  return seeded;
1498
1503
  }
1499
1504
  try {
1505
+ const st = statSync(p);
1506
+ const cacheKey = `${p}|${st.mtimeMs}|${st.size}`;
1507
+ if (runtimeConfigCache && runtimeConfigCache.key === cacheKey) {
1508
+ return runtimeConfigCache.value;
1509
+ }
1500
1510
  const data = JSON.parse(readFileSync4(p, "utf-8"));
1501
1511
  const merged = mergeConfig(data, generateSourceDefaults());
1502
- return {
1512
+ const result = {
1503
1513
  version: data.version || 1,
1504
1514
  layers: {
1505
1515
  L1_heuristic: data.layers?.L1_heuristic ?? true,
@@ -1513,6 +1523,8 @@ function loadRuntimeConfig() {
1513
1523
  agent_rules: merged.agent_rules || {},
1514
1524
  scenario_rules: merged.scenario_rules || {}
1515
1525
  };
1526
+ runtimeConfigCache = { key: cacheKey, value: result };
1527
+ return result;
1516
1528
  } catch {
1517
1529
  return emptyRuntimeConfig();
1518
1530
  }
@@ -2502,7 +2514,7 @@ function updateScenarioCategoryEnabled(scenarioId, category, enabled) {
2502
2514
  saveRuntimeConfig(runtime);
2503
2515
  return runtime;
2504
2516
  }
2505
- var __dirname4, ENV_RUNTIME_CONFIG_PATH, RUNTIME_CONFIG_DIR, RUNTIME_CONFIG_PATH, LAYER_DISABLE_ENV, DEFAULT_LAYERS, DENY_MODE_LABELS, _AGENTS_DIR_PROD, _AGENTS_DIR_DEV, _SCENARIOS_DIR_PROD, _SCENARIOS_DIR_DEV, SKILL_ID_RE;
2517
+ var __dirname4, ENV_RUNTIME_CONFIG_PATH, RUNTIME_CONFIG_DIR, RUNTIME_CONFIG_PATH, LAYER_DISABLE_ENV, DEFAULT_LAYERS, DENY_MODE_LABELS, runtimeConfigCache, _AGENTS_DIR_PROD, _AGENTS_DIR_DEV, _SCENARIOS_DIR_PROD, _SCENARIOS_DIR_DEV, SKILL_ID_RE;
2506
2518
  var init_runtime_config = __esm({
2507
2519
  "src/infra/runtime-config.ts"() {
2508
2520
  "use strict";
@@ -2532,6 +2544,7 @@ var init_runtime_config = __esm({
2532
2544
  deny_read: "\u4EC5\u62E6\u622A\u8BFB\u53D6",
2533
2545
  deny_both: "\u8BFB\u5199\u5747\u62E6\u622A"
2534
2546
  };
2547
+ runtimeConfigCache = null;
2535
2548
  _AGENTS_DIR_PROD = join3(__dirname4, "agents");
2536
2549
  _AGENTS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "agents");
2537
2550
  _SCENARIOS_DIR_PROD = join3(__dirname4, "scenarios");
@@ -2826,6 +2839,56 @@ function isInlineScriptCommand(actionDetail) {
2826
2839
  const re = /(?:^|\s)(?:python|python3|python3\.\d+|perl|perl5|ruby|jruby|mruby|node|php|lua)\s+(?:-[a-zA-Z]*[cer][a-zA-Z]*\s+)/i;
2827
2840
  return re.test(actionDetail);
2828
2841
  }
2842
+ function resolvePathTokens(segment) {
2843
+ const out = [];
2844
+ const low = segment.toLowerCase();
2845
+ for (const m of low.matchAll(HOME_PREFIX_RE)) {
2846
+ const prefix = m[0];
2847
+ const endIdx = (m.index ?? 0) + m[0].length;
2848
+ const tail = low.slice(endIdx);
2849
+ if (!tail || !(tail[0] === "/" || tail[0] === " ")) {
2850
+ out.push([prefix, prefix]);
2851
+ continue;
2852
+ }
2853
+ const token = tail.trim().split(/\s+/)[0] || "";
2854
+ if (!token.startsWith("/")) continue;
2855
+ const cleaned = token.replace(/\/+$/, "").replace(/^['"]|['"]$/g, "");
2856
+ const segments = cleaned.split("/").filter((s) => s && s !== ".");
2857
+ if (!segments.some((s) => s === "..")) continue;
2858
+ let depth = 0;
2859
+ const kept = [];
2860
+ let escaped = false;
2861
+ for (const s of segments) {
2862
+ if (s === "..") {
2863
+ depth -= 1;
2864
+ if (kept.length) kept.pop();
2865
+ if (depth < 0) {
2866
+ escaped = true;
2867
+ depth = 0;
2868
+ }
2869
+ } else {
2870
+ depth += 1;
2871
+ kept.push(s);
2872
+ }
2873
+ }
2874
+ let resolved = kept.length ? "~/" + kept.join("/") : "~";
2875
+ if (escaped) resolved += "\uFF08\u26A0\uFE0F .. \u5F39\u6808\u8D85\u51FA\u4E3B\u76EE\u5F55\u6839\uFF0C\u5B9E\u9645\u76EE\u6807\u5728\u4E3B\u76EE\u5F55\u4E4B\u5916\uFF09";
2876
+ out.push([prefix + token, resolved]);
2877
+ }
2878
+ return out;
2879
+ }
2880
+ function buildPathContext(actionDetail) {
2881
+ if (!PATH_CONTEXT_TRIGGER_RE.test(actionDetail)) return "";
2882
+ const stripped = stripLiteralTextSegments(actionDetail);
2883
+ const lines = [];
2884
+ for (const seg of splitCommandSegments(stripped)) {
2885
+ for (const [raw, lex] of resolvePathTokens(seg)) {
2886
+ lines.push(`- \u547D\u4EE4\u6BB5 \`${seg.trim().slice(0, 60)}\`\uFF1A\`${raw}\` \u8BCD\u6CD5\u89E3\u6790\u4E3A \`${lex}\``);
2887
+ }
2888
+ }
2889
+ if (!lines.length) return "";
2890
+ return "## \u8DEF\u5F84\u89E3\u6790\u4E8B\u5B9E\uFF08\u8BCD\u6CD5\u5206\u6790\uFF0C\u786E\u5B9A\u6027\u7ED3\u8BBA\uFF09\n" + lines.join("\n") + "\n\n## \u8FD0\u884C\u65F6\u672A\u9A8C\u8BC1\uFF08\u4F9B\u53C2\u8003\uFF0C\u975E\u4E8B\u5B9E\uFF09\n- \u5206\u6790\u5668\u4E0D\u8BFB\u6587\u4EF6\u7CFB\u7EDF\uFF1A\u65E0\u6CD5\u786E\u8BA4\u8DEF\u5F84\u4E2D\u7684\u4E2D\u95F4\u76EE\u5F55\u662F\u5426\u4E3A\u7B26\u53F7\u94FE\u63A5\uFF1B\u82E5\u4E2D\u95F4\u6BB5\u5728\u6267\u884C\u65F6\u662F\uFF08\u6216\u88AB\u66FF\u6362\u4E3A\uFF09\u6307\u5411\u4E3B\u76EE\u5F55\u5916\u7684\u8F6F\u94FE\uFF0C\u5B9E\u9645\u64CD\u4F5C\u76EE\u6807\u53EF\u80FD\u968F\u4E4B\u6539\u53D8\u3002\u8BCD\u6CD5\u89E3\u6790\u7ED3\u679C\u57FA\u4E8E\u6807\u51C6\u8DEF\u5F84\u8BED\u4E49\uFF0C\u4E0D\u5E94\u56E0\u731C\u6D4B\u300C\u89E3\u6790\u4F9D\u8D56\u67D0\u6BB5\u662F\u5426\u5B58\u5728\u300D\u800C\u5426\u5B9A\u5B83\u3002";
2891
+ }
2829
2892
  function stripLiteralTextSegments(actionDetail) {
2830
2893
  const writeScriptExec = />\s*[/~]\S*\.(sh|py|pl|rb)\b/.test(actionDetail) && /(?:&&|\|\||;|\n|\|)\s*(?:bash|sh|zsh|dash|python|perl|ruby|node)/.test(actionDetail);
2831
2894
  const pipeToInterpreter = /\|\s*(?:bash|sh|zsh|dash|python|perl|ruby|node)\b/.test(actionDetail);
@@ -3352,7 +3415,7 @@ function checkLateralMovement(actionDetail) {
3352
3415
  }
3353
3416
  return null;
3354
3417
  }
3355
- var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES, HOME_TARGET_RE, HOME_ASSIGN_RE;
3418
+ var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, PATH_CONTEXT_TRIGGER_RE, HOME_PREFIX_RE, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES, HOME_TARGET_RE, HOME_ASSIGN_RE;
3356
3419
  var init_heuristic = __esm({
3357
3420
  "src/engine/heuristic.ts"() {
3358
3421
  "use strict";
@@ -3380,6 +3443,8 @@ var init_heuristic = __esm({
3380
3443
  description: c.reason
3381
3444
  }));
3382
3445
  })();
3446
+ PATH_CONTEXT_TRIGGER_RE = /(?:^|[;&|]\s*)(?:rm|rmdir|unlink|ln|mv|cp|dd|truncate|shred|chmod|chown)\b/i;
3447
+ HOME_PREFIX_RE = /(?:~|~(?=\/)|\$HOME\b|\$\{HOME\}|\/home\/[a-z0-9_.-]+)/gi;
3383
3448
  SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
3384
3449
  SEARCH_ACTION_PREFIXES = ["search-", "web-", "browser-", "query-", "websearch", "web_search"];
3385
3450
  HOME_TARGET_RE = /(?:^|[\s="'($])(?:~|(?:\$home|\$\{home\}))(?:\/(?:\.\*|\*|\.)?)?(?=[\s"';}|&)]|$)/i;
@@ -4672,7 +4737,7 @@ init_esm_shims();
4672
4737
  init_heuristic();
4673
4738
  init_logic_rules();
4674
4739
  init_inline_analyzer();
4675
- import { statSync as statSync2, existsSync as existsSync9 } from "fs";
4740
+ import { statSync as statSync3, existsSync as existsSync9 } from "fs";
4676
4741
  import { resolve as resolve8, join as join6, isAbsolute as isAbsolute2 } from "path";
4677
4742
  import { homedir as homedir4 } from "os";
4678
4743
 
@@ -6333,7 +6398,7 @@ function extractInlineScriptPath(actionDetail) {
6333
6398
  try {
6334
6399
  const resolved = resolvePath2(tok.replace(/^['"]|['"]$/g, ""));
6335
6400
  if (existsSync9(resolved)) {
6336
- const stat = statSync2(resolved);
6401
+ const stat = statSync3(resolved);
6337
6402
  if (stat.isFile()) return resolved;
6338
6403
  }
6339
6404
  } catch {
@@ -6347,7 +6412,7 @@ function extractInlineScriptPath(actionDetail) {
6347
6412
  try {
6348
6413
  const resolved = resolvePath2(p);
6349
6414
  if (existsSync9(resolved)) {
6350
- const stat = statSync2(resolved);
6415
+ const stat = statSync3(resolved);
6351
6416
  if (stat.isFile()) return resolved;
6352
6417
  }
6353
6418
  } catch {
@@ -6363,7 +6428,7 @@ function extractInlineScriptPath(actionDetail) {
6363
6428
  try {
6364
6429
  resolved = resolvePath2(p);
6365
6430
  if (existsSync9(resolved)) {
6366
- const stat = statSync2(resolved);
6431
+ const stat = statSync3(resolved);
6367
6432
  if (stat.isFile()) return resolved;
6368
6433
  }
6369
6434
  } catch {
@@ -6574,7 +6639,7 @@ async function runLLMAnalysis(input, previous) {
6574
6639
  } else {
6575
6640
  scriptAnalysis = analyzeScriptContent(input.aNext.actionType, input.aNext.actionDetail);
6576
6641
  }
6577
- const prompt = buildPrompt(input, previous, matchedSkills, templates, scriptAnalysis, previous.skipL3Hints ?? "", scriptChainText);
6642
+ const prompt = buildPrompt(input, previous, matchedSkills, templates, scriptAnalysis, previous.skipL3Hints ?? "", scriptChainText, previous.pathContext ?? "");
6578
6643
  logPrompt(prompt, input.aNext, previous.llmLogPath);
6579
6644
  const result = await analyzeWithLLM(prompt, config);
6580
6645
  if (llmCache.size >= LLM_CACHE_MAX) {
@@ -6598,7 +6663,7 @@ ${prompt}
6598
6663
  console.warn("[AgentMoss] Failed to write LLM prompt to log:", e);
6599
6664
  }
6600
6665
  }
6601
- function buildPrompt(input, previous, matchedSkills, templates, scriptAnalysis, skipL3Hints = "", scriptChainText = "") {
6666
+ function buildPrompt(input, previous, matchedSkills, templates, scriptAnalysis, skipL3Hints = "", scriptChainText = "", pathContext = "") {
6602
6667
  const skillsText = formatSkillsForPrompt(matchedSkills);
6603
6668
  const historyStr = input.actionHistory.map((h, i) => `${i + 1}. ${h.actionType}: ${h.actionDetail}`).join("\n") || "(\u65E0\u5386\u53F2\u64CD\u4F5C)";
6604
6669
  const promptHistoryText = formatPromptHistory(input.promptHistory);
@@ -6606,7 +6671,7 @@ function buildPrompt(input, previous, matchedSkills, templates, scriptAnalysis,
6606
6671
  const hintsText = formatHints(previous);
6607
6672
  const securityJudgeTemplate = templates["security_judge"];
6608
6673
  if (securityJudgeTemplate) {
6609
- return securityJudgeTemplate.replaceAll("{prompt_session}", input.promptSession || "\uFF08\u672A\u63D0\u4F9B\uFF09").replaceAll("{prompt_history_text}", promptHistoryText).replaceAll("{action_history}", historyStr).replaceAll("{action_type}", input.aNext.actionType).replaceAll("{action_detail}", input.aNext.actionDetail).replaceAll("{reason}", input.reason || "\u672A\u8BF4\u660E").replaceAll("{skills_text}", skillsText).replaceAll("{hints_text}", hintsText).replaceAll("{script_analysis_text}", scriptAnalysisText).replaceAll("{skip_l3_hints}", skipL3Hints).replaceAll("{{", "{").replaceAll("}}", "}");
6674
+ return securityJudgeTemplate.replaceAll("{prompt_session}", input.promptSession || "\uFF08\u672A\u63D0\u4F9B\uFF09").replaceAll("{prompt_history_text}", promptHistoryText).replaceAll("{action_history}", historyStr).replaceAll("{action_type}", input.aNext.actionType).replaceAll("{action_detail}", input.aNext.actionDetail).replaceAll("{reason}", input.reason || "\u672A\u8BF4\u660E").replaceAll("{skills_text}", skillsText).replaceAll("{hints_text}", hintsText).replaceAll("{script_analysis_text}", scriptAnalysisText).replaceAll("{skip_l3_hints}", skipL3Hints).replaceAll("{path_context}", pathContext || "\uFF08\u672C\u547D\u4EE4\u65E0\u4E3B\u76EE\u5F55\u76F8\u5173\u8DEF\u5F84\u6BB5\uFF0C\u4E0D\u9002\u7528\uFF09").replaceAll("{{", "{").replaceAll("}}", "}");
6610
6675
  }
6611
6676
  console.warn("[AgentMoss] security_judge template missing, using sparse fallback prompt");
6612
6677
  return `## \u5F53\u524D\u64CD\u4F5C\u4E0A\u4E0B\u6587
@@ -8989,6 +9054,21 @@ async function runAnalysis(input) {
8989
9054
  if ((effectiveResult.riskLevel === "high" || effectiveResult.riskLevel === "critical") && (!skipInlineFileAccess || isCredentialPath)) {
8990
9055
  return effectiveResult;
8991
9056
  }
9057
+ } else {
9058
+ if (isInlineScriptCmd || isLiteralTextCmd) {
9059
+ const inlineJudgment = analyzeInlineScript(input.aNext.actionDetail);
9060
+ if (inlineJudgment.actionable && !inlineJudgment.allowed) {
9061
+ return {
9062
+ allowed: false,
9063
+ reason: inlineJudgment.reason,
9064
+ riskLevel: inlineJudgment.riskLevel,
9065
+ riskType: inlineJudgment.riskType,
9066
+ confidence: 90,
9067
+ source: "logic_rule",
9068
+ violatedLayers: ["L2.1"]
9069
+ };
9070
+ }
9071
+ }
8992
9072
  }
8993
9073
  if (isLogicRulesEnabled()) {
8994
9074
  const formalSessions = [];
@@ -9090,12 +9170,14 @@ async function runAnalysis(input) {
9090
9170
  }
9091
9171
  let llmResult;
9092
9172
  try {
9173
+ const pathContext = buildPathContext(input.aNext.actionDetail);
9093
9174
  llmResult = await runLLMAnalysis(input, {
9094
9175
  heuristic: heuristicResult,
9095
9176
  logic: logicResult,
9096
9177
  skipL3Hints,
9097
9178
  scriptChain: scriptChain ?? void 0,
9098
- llmLogPath: input.llmLogPath
9179
+ llmLogPath: input.llmLogPath,
9180
+ pathContext
9099
9181
  });
9100
9182
  } catch (e) {
9101
9183
  const { getLLMFailMode: getLLMFailMode2 } = await Promise.resolve().then(() => (init_config(), config_exports));