@kenhkl/agent-moss 0.15.2 → 0.15.5

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.
@@ -7,7 +7,7 @@
7
7
  "reason": "检测到递归强制删除根目录 (rm -rf /)"
8
8
  },
9
9
  {
10
- "pattern": "\\brm\\s+-[a-zA-Z]*[rf][a-zA-Z]*\\s+/(etc|var(?!/tmp)|home|usr|boot|root|opt|srv|sys|proc)\\b",
10
+ "pattern": "\\brm\\s+-[a-zA-Z]*[rf][a-zA-Z]*\\s+/(?:etc|var(?!/tmp)|usr|boot|root|opt|srv|sys|proc|home(?:/|/[A-Za-z0-9_.-]+/?)?(?=[\\s;|&\\\"']|$))",
11
11
  "risk_level": "critical",
12
12
  "risk_type": "script_execution",
13
13
  "reason": "检测到删除关键系统目录"
@@ -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",
@@ -67,16 +73,16 @@
67
73
  "reason": "检测到访问系统组密码文件 (/etc/gshadow)"
68
74
  },
69
75
  {
70
- "pattern": "\\.ssh/id_rsa(?!\\.pub)",
76
+ "pattern": "dd\\s+if=/dev/zero.*\\bof\\s*=\\s*/(?:etc|var|usr|boot|root|opt|srv|sys|proc|home(?:/|/[A-Za-z0-9_.-]+/?)?(?=[\\s;|&\\\"']|$))",
71
77
  "risk_level": "critical",
72
- "risk_type": "file_access",
73
- "reason": "检测到访问 SSH 私钥文件 (.ssh/id_rsa)"
78
+ "risk_type": "script_execution",
79
+ "reason": "检测到磁盘清零写入关键系统目录 (dd if=/dev/zero of=/系统目录)"
74
80
  },
75
81
  {
76
- "pattern": "dd\\s+if=/dev/zero.*\\bof\\s*=\\s*/(etc|var|home|usr|boot|root|opt|srv|sys|proc)\\b",
82
+ "pattern": "\\.ssh/id_rsa(?!\\.pub)",
77
83
  "risk_level": "critical",
78
- "risk_type": "script_execution",
79
- "reason": "检测到磁盘清零写入关键系统目录 (dd if=/dev/zero of=/系统目录)"
84
+ "risk_type": "file_access",
85
+ "reason": "检测到访问 SSH 私钥文件 (.ssh/id_rsa)"
80
86
  },
81
87
  {
82
88
  "pattern": "mkfs\\.\\w+\\s+/dev/(sd|hd|nvme|vd|loop|mapper|md|dm)\\S+",
@@ -150,6 +156,12 @@
150
156
  "risk_type": "script_execution",
151
157
  "reason": "检测到远程下载脚本到本地 (curl -o script.sh),存在后续执行风险"
152
158
  },
159
+ {
160
+ "pattern": "base64\\s+-d\\s+.*\\|\\s*bash",
161
+ "risk_level": "high",
162
+ "risk_type": "sensitive_action",
163
+ "reason": "base64 解码执行(编码绕过,下载或读取内容后解码执行)"
164
+ },
153
165
  {
154
166
  "pattern": "(?:curl|wget)\\s+\\S+.*\\b(?:chmod|chown)\\s+\\+x\\b",
155
167
  "risk_level": "high",
@@ -256,7 +268,7 @@
256
268
  "pattern": "curl\\s+.*\\|\\s*bash",
257
269
  "risk_level": "high",
258
270
  "risk_type": "sensitive_action",
259
- "reason": "curl pipe bash 执行 (user_rules)"
271
+ "reason": "curl pipe bash 执行(下载远程脚本直接执行)"
260
272
  },
261
273
  {
262
274
  "pattern": "curl\\s+.*\\|\\s*(sh|zsh|dash)",
@@ -276,12 +288,6 @@
276
288
  "risk_type": "script_execution",
277
289
  "reason": "eval 远程代码执行"
278
290
  },
279
- {
280
- "pattern": "base64\\s+-d\\s+.*\\|\\s*bash",
281
- "risk_level": "high",
282
- "risk_type": "sensitive_action",
283
- "reason": "base64 解码执行 (user_rules)"
284
- },
285
291
  {
286
292
  "pattern": "/dev/tcp/|bash\\s+-i\\s+>&",
287
293
  "risk_level": "critical",
@@ -335,6 +341,42 @@
335
341
  "risk_level": "critical",
336
342
  "risk_type": "script_execution",
337
343
  "reason": "检测到命令替换隐藏执行($(...)/反引号载荷含写/执行命令),可能产生文件系统副作用"
344
+ },
345
+ {
346
+ "pattern": "\\biptables\\s+-(?:[A-Za-z]*[FPIX][A-Za-z]*|[A-Za-z]*[FPIX]\\b)",
347
+ "risk_level": "high",
348
+ "risk_type": "script_execution",
349
+ "reason": "检测到修改防火墙规则(iptables -F/-P/-I/-X),可能关闭安全防护或建立隧道(ATT&CK T1562.004)"
350
+ },
351
+ {
352
+ "pattern": "\\b(?:ssh|scp)\\b[^;\\n]{0,200}?StrictHostKeyChecking\\s*=\\s*(?:no|accept-new)",
353
+ "risk_level": "high",
354
+ "risk_type": "lateral_movement",
355
+ "reason": "检测到 SSH/SCP 跳过主机密钥验证(-o StrictHostKeyChecking=no),允许中间人攻击,可能用于横向移动"
356
+ },
357
+ {
358
+ "pattern": "\\bwget\\s+(?:-\\S+\\s+)*--post-(?:data|file)\\s*=\\s*(?:\\$\\([^)]+\\)|[^ ]+|\\\"[^\\\"]+\\\"|'[^']+')\\s+(?!https?://(?:127\\.|localhost))https?://[a-zA-Z0-9.-]+",
359
+ "risk_level": "high",
360
+ "risk_type": "data_exfiltration",
361
+ "reason": "检测到 wget --post-data/--post-file 向外部地址发送数据(可能为数据外传,回环地址除外)"
362
+ },
363
+ {
364
+ "pattern": "\\bcurl\\b(?:[^;\\n]|&(?![&\\s]))*-X\\s+POST\\b[^;\\n]*https?://(?!(?:127\\.|localhost|\\[::1\\]))[a-zA-Z0-9.-]+",
365
+ "risk_level": "high",
366
+ "risk_type": "data_exfiltration",
367
+ "reason": "检测到 curl -X POST 到外部地址(可能为数据外传,回环地址除外)"
368
+ },
369
+ {
370
+ "pattern": "\\bcurl\\b[^;&\\n]*-F\\s+\\S+[^;&\\n]*https?://(?!(?:127\\.|localhost|\\[::1\\]))[a-zA-Z0-9.-]+",
371
+ "risk_level": "high",
372
+ "risk_type": "data_exfiltration",
373
+ "reason": "检测到 curl -F 表单上传到外部地址(可能为数据外传,回环地址除外)"
374
+ },
375
+ {
376
+ "pattern": "[|]\\s*crontab\\b",
377
+ "risk_level": "high",
378
+ "risk_type": "sensitive_action",
379
+ "reason": "检测到通过管道安装 crontab 定时任务(cron 后门/持久化)"
338
380
  }
339
381
  ],
340
382
  "injection_detection": [
@@ -866,5 +908,6 @@
866
908
  "deny_mode": "deny_write",
867
909
  "scope": "home"
868
910
  }
869
- ]
911
+ ],
912
+ "_comment": "\n2026-09-11 新增:iptables 防火墙规则修改(T1562.004)+ SSH 跳过密钥验证(横向移动),原在 user_rules.json 下沉至此——攻击形态清晰、可静态判定,user_rules 回归仅存用户自定义例外的本位。"
870
913
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "_comment": "L2.2 模型检测规则单一事实源(Python + TS 共用)。原子命题正则 + LTL 公式(前缀 AST)。公式在事件迹位置 0 处评估:LTL 公式满足 = 攻击存在。对应站点 detection-l2-2.html 的 10 条 LTL + 5 条 CTL 规则(CTL 活性降级为 G(!φ) 安全不变量,见 docs/DETECTION_METHODS.md 5.4)。",
2
+ "_comment": "L2.2 模型检测规则单一事实源(Python + TS 共用)。原子命题正则 + LTL 公式(前缀 AST)。公式在事件迹位置 0 处评估:LTL 公式满足 = 攻击存在。对应站点 detection-l2-2.html 的 9 条 LTL + 5 条 CTL 规则(2026-09 移除 2 条单动作冗余:mc-ltl-010 F(install_persistence) 与 mc-ctl-001 F(clear_logs) —— 它们误伤正常运维,且攻击场景已由链式规则 006/007/008 覆盖)(CTL 活性降级为 G(!φ) 安全不变量,见 docs/DETECTION_METHODS.md 5.4)。",
3
3
  "ap_patterns": {
4
4
  "reads_credential": [
5
5
  "(?:cat|head|tail|less|more|sed|awk|strings|base64|dd)\\s+[^;&|\\n]*[~]?/\\.ssh(?:/[\\w.-]+)?\\b",
@@ -336,34 +336,6 @@
336
336
  "risk_level": "critical",
337
337
  "action": "block",
338
338
  "reason": "敏感数据→归档→外传(mc-ltl-009)"
339
- },
340
- {
341
- "id": "mc-ltl-010",
342
- "type": "ltl",
343
- "formula": [
344
- "F",
345
- [
346
- "ap",
347
- "install_persistence"
348
- ]
349
- ],
350
- "risk_level": "medium",
351
- "action": "approval",
352
- "reason": "持久化机制安装,需审批(mc-ltl-010)"
353
- },
354
- {
355
- "id": "mc-ctl-001",
356
- "type": "ltl",
357
- "formula": [
358
- "F",
359
- [
360
- "ap",
361
- "clear_logs"
362
- ]
363
- ],
364
- "risk_level": "critical",
365
- "action": "block",
366
- "reason": "安全不变量:任何情况不得清除日志(mc-ctl-001,AG(!clear_logs) 线性迹降级:违反 ⟺ 出现 clear_logs ⟺ F(clear_logs) 成立)"
367
339
  }
368
340
  ]
369
- }
341
+ }
@@ -1,77 +1,8 @@
1
1
  {
2
- "sensitive_actions": [
3
- {
4
- "pattern": "写入系统文件",
5
- "description": "写入 /etc, /usr, /boot 等系统目录下的文件"
6
- },
7
- {
8
- "pattern": "发送外部邮件",
9
- "description": "通过 email 工具发送邮件到外部地址"
10
- },
11
- {
12
- "pattern": "危险 shell 命令",
13
- "description": "执行 rm -rf, chmod 777, sudo 等危险命令"
14
- },
15
- {
16
- "pattern": "修改全局配置",
17
- "description": "修改系统级配置文件或环境变量"
18
- },
19
- {
20
- "pattern": "支付操作",
21
- "description": "涉及支付、转账等金融操作"
22
- },
23
- {
24
- "pattern": "访问密钥文件",
25
- "description": "访问 .ssh/id_rsa, .env, credentials 等密钥/凭证文件"
26
- },
27
- {
28
- "pattern": "网络数据外传",
29
- "description": "通过 HTTP/wget/curl 等方式将数据发送到外部服务器"
30
- },
31
- {
32
- "pattern": "curl * -d *",
33
- "description": "使用 curl 发送数据到外部服务器(可能为数据外传)"
34
- },
35
- {
36
- "pattern": "curl * POST *",
37
- "description": "使用 curl POST 请求发送数据到外部服务器"
38
- },
39
- {
40
- "pattern": "wget *--post-data*",
41
- "description": "使用 wget 发送 POST 数据到外部服务器"
42
- },
43
- {
44
- "pattern": "crontab -e",
45
- "description": "编辑定时任务(可能用于持久化后门)"
46
- },
47
- {
48
- "pattern": "crontab -r",
49
- "description": "删除定时任务(可能用于清除审计痕迹)"
50
- },
51
- {
52
- "pattern": "* | crontab",
53
- "description": "管道方式安装/修改定时任务(可能用于持久化后门)"
54
- },
55
- {
56
- "pattern": "iptables -",
57
- "description": "修改防火墙规则(可能用于关闭安全防护)"
58
- },
59
- {
60
- "pattern": "ssh* -o StrictHostKeyChecking=no",
61
- "description": "SSH 跳过主机密钥验证(可能用于横向移动)"
62
- },
63
- {
64
- "pattern": "base64 -d * bash",
65
- "description": "Base64 解码后执行(可能用于编码绕过)"
66
- },
67
- {
68
- "pattern": "curl * | bash",
69
- "description": "下载远程脚本并执行(远程代码执行风险)"
70
- }
71
- ],
2
+ "sensitive_actions": [],
72
3
  "sensitive_tools": [
73
4
  "eval",
74
5
  "exec",
75
6
  "system"
76
7
  ]
77
- }
8
+ }
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));