@kenhkl/agent-moss 0.15.1 → 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
@@ -160,8 +160,12 @@ function resolveConfigPath() {
160
160
  if (envPath) return envPath;
161
161
  const userConfig = path2.join(getConfigDir(), "config.json");
162
162
  if (fs.existsSync(userConfig)) return userConfig;
163
- const defaultConfig = path2.join(__dirname, "..", "..", "agent_moss", "infra", "config.json.example");
164
- if (fs.existsSync(defaultConfig)) {
163
+ const defaultCandidates = [
164
+ path2.join(__dirname, "config.json.example"),
165
+ path2.join(__dirname, "..", "..", "agent_moss", "infra", "config.json.example")
166
+ ];
167
+ const defaultConfig = defaultCandidates.find((p) => fs.existsSync(p));
168
+ if (defaultConfig) {
165
169
  const configDir = path2.dirname(userConfig);
166
170
  fs.mkdirSync(configDir, { recursive: true });
167
171
  const pkgConfig = loadConfigFile(defaultConfig);
@@ -1434,6 +1438,7 @@ __export(runtime_config_exports, {
1434
1438
  getRuntimeConfigPath: () => getRuntimeConfigPath,
1435
1439
  getScenarioRules: () => getScenarioRules,
1436
1440
  hasRuntimeConfig: () => hasRuntimeConfig,
1441
+ invalidateRuntimeConfigCache: () => invalidateRuntimeConfigCache,
1437
1442
  isL2CategoryEnabled: () => isL2CategoryEnabled,
1438
1443
  isLayerEnabled: () => isLayerEnabled,
1439
1444
  loadRuntimeConfig: () => loadRuntimeConfig,
@@ -1452,7 +1457,7 @@ __export(runtime_config_exports, {
1452
1457
  updateSkillCategoryEnabled: () => updateSkillCategoryEnabled,
1453
1458
  updateSkillEnabled: () => updateSkillEnabled
1454
1459
  });
1455
- 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";
1456
1461
  import { join as join3 } from "path";
1457
1462
  import { fileURLToPath as fileURLToPath5 } from "url";
1458
1463
  import { dirname as dirname3 } from "path";
@@ -1469,6 +1474,7 @@ function saveRuntimeConfig(config) {
1469
1474
  const dir = p.substring(0, p.lastIndexOf("/"));
1470
1475
  if (dir && !existsSync4(dir)) mkdirSync(dir, { recursive: true });
1471
1476
  writeFileSync(p, JSON.stringify(config, null, 2), "utf-8");
1477
+ invalidateRuntimeConfigCache();
1472
1478
  }
1473
1479
  function emptyRuntimeConfig() {
1474
1480
  return {
@@ -1482,6 +1488,9 @@ function emptyRuntimeConfig() {
1482
1488
  scenario_rules: {}
1483
1489
  };
1484
1490
  }
1491
+ function invalidateRuntimeConfigCache() {
1492
+ runtimeConfigCache = null;
1493
+ }
1485
1494
  function loadRuntimeConfig() {
1486
1495
  const p = getRuntimeConfigPath();
1487
1496
  if (!existsSync4(p)) {
@@ -1493,9 +1502,14 @@ function loadRuntimeConfig() {
1493
1502
  return seeded;
1494
1503
  }
1495
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
+ }
1496
1510
  const data = JSON.parse(readFileSync4(p, "utf-8"));
1497
1511
  const merged = mergeConfig(data, generateSourceDefaults());
1498
- return {
1512
+ const result = {
1499
1513
  version: data.version || 1,
1500
1514
  layers: {
1501
1515
  L1_heuristic: data.layers?.L1_heuristic ?? true,
@@ -1509,6 +1523,8 @@ function loadRuntimeConfig() {
1509
1523
  agent_rules: merged.agent_rules || {},
1510
1524
  scenario_rules: merged.scenario_rules || {}
1511
1525
  };
1526
+ runtimeConfigCache = { key: cacheKey, value: result };
1527
+ return result;
1512
1528
  } catch {
1513
1529
  return emptyRuntimeConfig();
1514
1530
  }
@@ -2498,7 +2514,7 @@ function updateScenarioCategoryEnabled(scenarioId, category, enabled) {
2498
2514
  saveRuntimeConfig(runtime);
2499
2515
  return runtime;
2500
2516
  }
2501
- 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;
2502
2518
  var init_runtime_config = __esm({
2503
2519
  "src/infra/runtime-config.ts"() {
2504
2520
  "use strict";
@@ -2528,6 +2544,7 @@ var init_runtime_config = __esm({
2528
2544
  deny_read: "\u4EC5\u62E6\u622A\u8BFB\u53D6",
2529
2545
  deny_both: "\u8BFB\u5199\u5747\u62E6\u622A"
2530
2546
  };
2547
+ runtimeConfigCache = null;
2531
2548
  _AGENTS_DIR_PROD = join3(__dirname4, "agents");
2532
2549
  _AGENTS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "agents");
2533
2550
  _SCENARIOS_DIR_PROD = join3(__dirname4, "scenarios");
@@ -2822,6 +2839,56 @@ function isInlineScriptCommand(actionDetail) {
2822
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;
2823
2840
  return re.test(actionDetail);
2824
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
+ }
2825
2892
  function stripLiteralTextSegments(actionDetail) {
2826
2893
  const writeScriptExec = />\s*[/~]\S*\.(sh|py|pl|rb)\b/.test(actionDetail) && /(?:&&|\|\||;|\n|\|)\s*(?:bash|sh|zsh|dash|python|perl|ruby|node)/.test(actionDetail);
2827
2894
  const pipeToInterpreter = /\|\s*(?:bash|sh|zsh|dash|python|perl|ruby|node)\b/.test(actionDetail);
@@ -3186,7 +3253,7 @@ function isArchiveReadOfDir(command, dirPath) {
3186
3253
  }
3187
3254
  if (fw === "cp" || fw === "rsync") {
3188
3255
  const segPaths = s.match(/[/~][^\s;|&><'"]*/g) || [];
3189
- if (segPaths.length && segPaths[0].includes(dpath)) return true;
3256
+ if (segPaths[0]?.includes(dpath)) return true;
3190
3257
  }
3191
3258
  }
3192
3259
  return false;
@@ -3348,7 +3415,7 @@ function checkLateralMovement(actionDetail) {
3348
3415
  }
3349
3416
  return null;
3350
3417
  }
3351
- 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;
3352
3419
  var init_heuristic = __esm({
3353
3420
  "src/engine/heuristic.ts"() {
3354
3421
  "use strict";
@@ -3376,6 +3443,8 @@ var init_heuristic = __esm({
3376
3443
  description: c.reason
3377
3444
  }));
3378
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;
3379
3448
  SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
3380
3449
  SEARCH_ACTION_PREFIXES = ["search-", "web-", "browser-", "query-", "websearch", "web_search"];
3381
3450
  HOME_TARGET_RE = /(?:^|[\s="'($])(?:~|(?:\$home|\$\{home\}))(?:\/(?:\.\*|\*|\.)?)?(?=[\s"';}|&)]|$)/i;
@@ -4668,7 +4737,7 @@ init_esm_shims();
4668
4737
  init_heuristic();
4669
4738
  init_logic_rules();
4670
4739
  init_inline_analyzer();
4671
- import { statSync as statSync2, existsSync as existsSync9 } from "fs";
4740
+ import { statSync as statSync3, existsSync as existsSync9 } from "fs";
4672
4741
  import { resolve as resolve8, join as join6, isAbsolute as isAbsolute2 } from "path";
4673
4742
  import { homedir as homedir4 } from "os";
4674
4743
 
@@ -6329,7 +6398,7 @@ function extractInlineScriptPath(actionDetail) {
6329
6398
  try {
6330
6399
  const resolved = resolvePath2(tok.replace(/^['"]|['"]$/g, ""));
6331
6400
  if (existsSync9(resolved)) {
6332
- const stat = statSync2(resolved);
6401
+ const stat = statSync3(resolved);
6333
6402
  if (stat.isFile()) return resolved;
6334
6403
  }
6335
6404
  } catch {
@@ -6343,7 +6412,7 @@ function extractInlineScriptPath(actionDetail) {
6343
6412
  try {
6344
6413
  const resolved = resolvePath2(p);
6345
6414
  if (existsSync9(resolved)) {
6346
- const stat = statSync2(resolved);
6415
+ const stat = statSync3(resolved);
6347
6416
  if (stat.isFile()) return resolved;
6348
6417
  }
6349
6418
  } catch {
@@ -6359,7 +6428,7 @@ function extractInlineScriptPath(actionDetail) {
6359
6428
  try {
6360
6429
  resolved = resolvePath2(p);
6361
6430
  if (existsSync9(resolved)) {
6362
- const stat = statSync2(resolved);
6431
+ const stat = statSync3(resolved);
6363
6432
  if (stat.isFile()) return resolved;
6364
6433
  }
6365
6434
  } catch {
@@ -6570,7 +6639,7 @@ async function runLLMAnalysis(input, previous) {
6570
6639
  } else {
6571
6640
  scriptAnalysis = analyzeScriptContent(input.aNext.actionType, input.aNext.actionDetail);
6572
6641
  }
6573
- 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 ?? "");
6574
6643
  logPrompt(prompt, input.aNext, previous.llmLogPath);
6575
6644
  const result = await analyzeWithLLM(prompt, config);
6576
6645
  if (llmCache.size >= LLM_CACHE_MAX) {
@@ -6594,7 +6663,7 @@ ${prompt}
6594
6663
  console.warn("[AgentMoss] Failed to write LLM prompt to log:", e);
6595
6664
  }
6596
6665
  }
6597
- function buildPrompt(input, previous, matchedSkills, templates, scriptAnalysis, skipL3Hints = "", scriptChainText = "") {
6666
+ function buildPrompt(input, previous, matchedSkills, templates, scriptAnalysis, skipL3Hints = "", scriptChainText = "", pathContext = "") {
6598
6667
  const skillsText = formatSkillsForPrompt(matchedSkills);
6599
6668
  const historyStr = input.actionHistory.map((h, i) => `${i + 1}. ${h.actionType}: ${h.actionDetail}`).join("\n") || "(\u65E0\u5386\u53F2\u64CD\u4F5C)";
6600
6669
  const promptHistoryText = formatPromptHistory(input.promptHistory);
@@ -6602,7 +6671,7 @@ function buildPrompt(input, previous, matchedSkills, templates, scriptAnalysis,
6602
6671
  const hintsText = formatHints(previous);
6603
6672
  const securityJudgeTemplate = templates["security_judge"];
6604
6673
  if (securityJudgeTemplate) {
6605
- 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("}}", "}");
6606
6675
  }
6607
6676
  console.warn("[AgentMoss] security_judge template missing, using sparse fallback prompt");
6608
6677
  return `## \u5F53\u524D\u64CD\u4F5C\u4E0A\u4E0B\u6587
@@ -8985,6 +9054,21 @@ async function runAnalysis(input) {
8985
9054
  if ((effectiveResult.riskLevel === "high" || effectiveResult.riskLevel === "critical") && (!skipInlineFileAccess || isCredentialPath)) {
8986
9055
  return effectiveResult;
8987
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
+ }
8988
9072
  }
8989
9073
  if (isLogicRulesEnabled()) {
8990
9074
  const formalSessions = [];
@@ -9086,12 +9170,14 @@ async function runAnalysis(input) {
9086
9170
  }
9087
9171
  let llmResult;
9088
9172
  try {
9173
+ const pathContext = buildPathContext(input.aNext.actionDetail);
9089
9174
  llmResult = await runLLMAnalysis(input, {
9090
9175
  heuristic: heuristicResult,
9091
9176
  logic: logicResult,
9092
9177
  skipL3Hints,
9093
9178
  scriptChain: scriptChain ?? void 0,
9094
- llmLogPath: input.llmLogPath
9179
+ llmLogPath: input.llmLogPath,
9180
+ pathContext
9095
9181
  });
9096
9182
  } catch (e) {
9097
9183
  const { getLLMFailMode: getLLMFailMode2 } = await Promise.resolve().then(() => (init_config(), config_exports));