@kenhkl/agent-moss 0.2.1 → 0.3.1

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.
package/dist/cli.js CHANGED
@@ -18,11 +18,114 @@ var init_esm_shims = __esm({
18
18
  }
19
19
  });
20
20
 
21
+ // src/config.ts
22
+ function loadCustomRules() {
23
+ try {
24
+ const raw = process.env.AGENT_MOSS_CUSTOM_RULES;
25
+ if (!raw) return [];
26
+ const parsed = JSON.parse(raw);
27
+ if (!Array.isArray(parsed)) return [];
28
+ return parsed.filter((r) => r.pattern && r.action);
29
+ } catch {
30
+ return [];
31
+ }
32
+ }
33
+ function getLLMConfig() {
34
+ return llmConfig;
35
+ }
36
+ function getCustomRules() {
37
+ return customRules;
38
+ }
39
+ function isLLMAvailable() {
40
+ if (process.env.AGENT_MOSS_DISABLE_LLM === "1") return false;
41
+ return llmConfig.apiKey.length > 0;
42
+ }
43
+ function detectProvider(baseUrl, providerHint = "") {
44
+ if (providerHint) {
45
+ const hint = providerHint.toLowerCase();
46
+ if (["claude", "anthropic"].includes(hint)) return "anthropic";
47
+ if (["google", "gemini"].includes(hint)) return "gemini";
48
+ if (["zai-coding-plan", "zhipu-coding-plan", "zhipuai-coding-plan"].includes(hint)) return "zai-coding-plan";
49
+ if (["zai", "z-ai", "z.ai", "zai-cn", "zai-china", "zhipu", "glm-cn", "bigmodel"].includes(hint)) return "zhipu";
50
+ if (["xai", "xai-grok"].includes(hint)) return "xai";
51
+ if (["minimax", "minimax-openai"].includes(hint)) return "minimax";
52
+ if (["minimax-anthropic"].includes(hint)) return "minimax-anthropic";
53
+ if (["other", "custom", "local"].includes(hint)) return "openai-compatible";
54
+ return hint;
55
+ }
56
+ const url = baseUrl.toLowerCase();
57
+ if (url.includes("openrouter.ai")) return "openrouter";
58
+ if (url.includes("api.openai.com")) return "openai";
59
+ if (url.includes("anthropic.com") || url.includes("api.anthropic.com")) return "anthropic";
60
+ if (url.includes("generativelanguage.googleapis.com") || url.includes("gemini")) return "gemini";
61
+ if (url.includes("api.deepseek.com")) return "deepseek";
62
+ if (url.includes("api.groq.com")) return "groq";
63
+ if (url.includes("api.mistral.ai")) return "mistral";
64
+ if (url.includes("api.together.xyz")) return "together";
65
+ if (url.includes("api.x.ai")) return "xai";
66
+ if (url.includes("api.minimaxi.com")) return "minimax";
67
+ if (url.includes("api-ai.gitcode.com")) return "gitcode";
68
+ if (url.includes("api.z.ai")) return "zai-coding-plan";
69
+ if (url.includes("open.bigmodel.cn") || url.includes("bigmodel")) return "zhipu";
70
+ if (url.includes(":11434")) return "ollama";
71
+ return "openai-compatible";
72
+ }
73
+ function getProviderHeaders(provider) {
74
+ return PROVIDER_HEADERS[provider] || {};
75
+ }
76
+ function isHeuristicEnabled() {
77
+ return process.env.AGENT_MOSS_DISABLE_HEURISTIC !== "1";
78
+ }
79
+ function isLogicRulesEnabled() {
80
+ return process.env.AGENT_MOSS_DISABLE_LOGIC_RULES !== "1";
81
+ }
82
+ var llmConfig, customRules, PROVIDER_HEADERS;
83
+ var init_config = __esm({
84
+ "src/config.ts"() {
85
+ "use strict";
86
+ init_esm_shims();
87
+ llmConfig = {
88
+ provider: "openai",
89
+ baseUrl: process.env.AGENT_MOSS_LLM_BASE_URL || "https://api.openai.com/v1",
90
+ apiKey: process.env.AGENT_MOSS_LLM_API_KEY || process.env.OPENAI_API_KEY || "",
91
+ model: process.env.AGENT_MOSS_LLM_MODEL || "gpt-4o",
92
+ temperature: parseFloat(process.env.AGENT_MOSS_LLM_TEMPERATURE || "0.1"),
93
+ maxTokens: parseInt(process.env.AGENT_MOSS_LLM_MAX_TOKENS || "4096", 10),
94
+ timeout: parseInt(process.env.AGENT_MOSS_LLM_TIMEOUT || "300", 10)
95
+ };
96
+ customRules = loadCustomRules();
97
+ PROVIDER_HEADERS = {
98
+ "openrouter": {
99
+ "HTTP-Referer": "shturl.cc/LlVD5pyWViHjM",
100
+ "X-OpenRouter-Title": "AgentMoss"
101
+ },
102
+ "xai": {
103
+ "HTTP-Referer": "shturl.cc/LlVD5pyWViHjM"
104
+ }
105
+ };
106
+ }
107
+ });
108
+
21
109
  // src/engine/heuristic.ts
22
110
  function runHeuristicCheck(input) {
23
111
  const command = `${input.aNext.actionType}: ${input.aNext.actionDetail}`;
24
112
  const matched = [];
25
113
  let highestSeverity = "low";
114
+ const userRules = getCustomRules();
115
+ for (const rule of userRules) {
116
+ try {
117
+ const re = new RegExp(rule.pattern);
118
+ if (re.test(command)) {
119
+ if (rule.action === "Deny") {
120
+ matched.push(`\u81EA\u5B9A\u4E49\u89C4\u5219: ${rule.pattern}`);
121
+ if (severityRank(rule.severity) > severityRank(highestSeverity)) {
122
+ highestSeverity = rule.severity;
123
+ }
124
+ }
125
+ }
126
+ } catch {
127
+ }
128
+ }
26
129
  for (const rule of DANGEROUS_PATTERNS) {
27
130
  if (rule.pattern.test(command)) {
28
131
  matched.push(rule.description);
@@ -31,12 +134,22 @@ function runHeuristicCheck(input) {
31
134
  }
32
135
  }
33
136
  }
137
+ const injectionText = `${input.aNext.actionType} ${input.aNext.actionDetail} ${input.reason || ""} ${input.promptSession || ""}`.toLowerCase();
138
+ const injectionHits = checkInjectionKeywords(injectionText);
139
+ if (injectionHits.length > 0) {
140
+ const topHit = injectionHits.reduce((a, b) => severityRank(a.riskLevel) >= severityRank(b.riskLevel) ? a : b);
141
+ matched.push(...injectionHits.map((h) => `\u6CE8\u5165\u5173\u952E\u8BCD [${h.lang}]: ${h.keyword}`));
142
+ if (severityRank(topHit.riskLevel) > severityRank(highestSeverity)) {
143
+ highestSeverity = topHit.riskLevel;
144
+ }
145
+ }
34
146
  if (matched.length > 0) {
147
+ const riskType = injectionHits.length > 0 ? "prompt_injection" : "dangerous_command";
35
148
  return {
36
149
  allowed: false,
37
150
  reason: `\u68C0\u6D4B\u5230\u9AD8\u5371\u64CD\u4F5C: ${matched.join(", ")}`,
38
151
  riskLevel: highestSeverity,
39
- riskType: "dangerous_command",
152
+ riskType,
40
153
  confidence: 95,
41
154
  source: "heuristic",
42
155
  violatedLayers: ["1"]
@@ -44,67 +157,197 @@ function runHeuristicCheck(input) {
44
157
  }
45
158
  return null;
46
159
  }
160
+ function checkInjectionKeywords(text) {
161
+ const hits = [];
162
+ for (const entry of INJECTION_KEYWORDS) {
163
+ if (text.includes(entry.keyword.toLowerCase())) {
164
+ hits.push(entry);
165
+ }
166
+ }
167
+ return hits;
168
+ }
47
169
  function severityRank(s) {
48
170
  const ranks = { low: 1, medium: 2, high: 3, critical: 4 };
49
171
  return ranks[s] || 0;
50
172
  }
51
- var DANGEROUS_PATTERNS;
173
+ var INJECTION_KEYWORDS, DANGEROUS_PATTERNS;
52
174
  var init_heuristic = __esm({
53
175
  "src/engine/heuristic.ts"() {
54
176
  "use strict";
55
177
  init_esm_shims();
178
+ init_config();
179
+ INJECTION_KEYWORDS = [
180
+ // ---- Instruction Override ----
181
+ { keyword: "ignore previous instructions", riskLevel: "critical", lang: "en" },
182
+ { keyword: "ignore all previous", riskLevel: "critical", lang: "en" },
183
+ { keyword: "disregard all prior", riskLevel: "critical", lang: "en" },
184
+ { keyword: "forget everything", riskLevel: "high", lang: "en" },
185
+ { keyword: "\u5FFD\u7565\u4E4B\u524D\u7684\u6307\u4EE4", riskLevel: "critical", lang: "zh" },
186
+ { keyword: "\u5FFD\u7565\u6240\u6709\u5148\u524D\u7684", riskLevel: "critical", lang: "zh" },
187
+ { keyword: "\u5FFD\u7565\u4E0A\u9762\u6240\u6709", riskLevel: "high", lang: "zh" },
188
+ { keyword: "\u8BF7\u5FD8\u8BB0\u4E4B\u524D", riskLevel: "high", lang: "zh" },
189
+ // ---- Role Hijacking ----
190
+ { keyword: "you are now", riskLevel: "high", lang: "en" },
191
+ { keyword: "you are no longer", riskLevel: "high", lang: "en" },
192
+ { keyword: "new instructions", riskLevel: "high", lang: "en" },
193
+ { keyword: "from now on, you", riskLevel: "high", lang: "en" },
194
+ { keyword: "\u4F60\u73B0\u5728\u662F\u4E00\u4E2A", riskLevel: "high", lang: "zh" },
195
+ { keyword: "\u4F60\u4E0D\u518D\u662F\u4E00\u4E2A", riskLevel: "high", lang: "zh" },
196
+ { keyword: "\u4ECE\u73B0\u5728\u5F00\u59CB\u4F60", riskLevel: "high", lang: "zh" },
197
+ { keyword: "\u4F60\u7684\u65B0\u89D2\u8272\u662F", riskLevel: "high", lang: "zh" },
198
+ // ---- Command Injection ----
199
+ { keyword: "system prompt", riskLevel: "high", lang: "en" },
200
+ { keyword: "reveal your instructions", riskLevel: "high", lang: "en" },
201
+ { keyword: "output your instructions", riskLevel: "high", lang: "en" },
202
+ { keyword: "repeat your instructions", riskLevel: "medium", lang: "en" },
203
+ { keyword: "\u7CFB\u7EDF\u63D0\u793A", riskLevel: "high", lang: "zh" },
204
+ { keyword: "\u8F93\u51FA\u4F60\u7684\u6307\u4EE4", riskLevel: "high", lang: "zh" },
205
+ { keyword: "\u91CD\u590D\u4F60\u7684\u6307\u4EE4", riskLevel: "medium", lang: "zh" },
206
+ // ---- Social Engineering ----
207
+ { keyword: "this is an authorized test", riskLevel: "high", lang: "en" },
208
+ { keyword: "security audit approved", riskLevel: "high", lang: "en" },
209
+ { keyword: "admin override", riskLevel: "critical", lang: "en" },
210
+ { keyword: "debug mode", riskLevel: "high", lang: "en" },
211
+ { keyword: "maintenance mode", riskLevel: "high", lang: "en" },
212
+ { keyword: "\u8FD9\u662F\u6388\u6743\u7684\u6D4B\u8BD5", riskLevel: "high", lang: "zh" },
213
+ { keyword: "\u5B89\u5168\u5BA1\u8BA1\u5DF2\u6279\u51C6", riskLevel: "high", lang: "zh" },
214
+ { keyword: "\u7BA1\u7406\u5458\u8986\u76D6", riskLevel: "critical", lang: "zh" },
215
+ { keyword: "\u8C03\u8BD5\u6A21\u5F0F", riskLevel: "high", lang: "zh" },
216
+ // ---- XSS ----
217
+ { keyword: "<script>", riskLevel: "high", lang: "en" },
218
+ { keyword: "javascript:", riskLevel: "high", lang: "en" },
219
+ { keyword: "onerror=", riskLevel: "high", lang: "en" },
220
+ { keyword: "onload=", riskLevel: "high", lang: "en" },
221
+ // ---- Fake Boundaries ----
222
+ { keyword: "=== END OF", riskLevel: "high", lang: "en" },
223
+ { keyword: "--- END SYSTEM", riskLevel: "high", lang: "en" },
224
+ { keyword: "[END OF", riskLevel: "high", lang: "en" },
225
+ { keyword: "=== \u7ED3\u675F", riskLevel: "high", lang: "zh" },
226
+ { keyword: "--- \u7ED3\u675F\u7CFB\u7EDF", riskLevel: "high", lang: "zh" },
227
+ // ---- Developer Mode ----
228
+ { keyword: "developer mode", riskLevel: "critical", lang: "en" },
229
+ { keyword: "DAN mode", riskLevel: "critical", lang: "en" },
230
+ { keyword: "jailbreak", riskLevel: "critical", lang: "en" },
231
+ { keyword: "\u5F00\u53D1\u8005\u6A21\u5F0F", riskLevel: "critical", lang: "zh" },
232
+ { keyword: "\u8D8A\u72F1", riskLevel: "critical", lang: "zh" },
233
+ // ---- xiaoO-specific bypass ----
234
+ { keyword: "bypass audit", riskLevel: "critical", lang: "en" },
235
+ { keyword: "skip security check", riskLevel: "critical", lang: "en" },
236
+ { keyword: "disable guard", riskLevel: "critical", lang: "en" },
237
+ { keyword: "\u7ED5\u8FC7\u5BA1\u8BA1", riskLevel: "critical", lang: "zh" },
238
+ { keyword: "\u7981\u7528\u5B89\u5168\u68C0\u67E5", riskLevel: "critical", lang: "zh" },
239
+ { keyword: "\u5173\u95ED\u9632\u62A4", riskLevel: "critical", lang: "zh" }
240
+ ];
56
241
  DANGEROUS_PATTERNS = [
57
- { pattern: /rm\s+-rf\s+\/\s*$|rm\s+-rf\s+\/\*/, severity: "critical", description: "\u9012\u5F52\u5F3A\u5236\u5220\u9664\u6839\u76EE\u5F55" },
58
- { pattern: /rm\s+-rf\s+\/home\/\*|rm\s+-rf\s+\/root/, severity: "critical", description: "\u5220\u9664\u7528\u6237\u4E3B\u76EE\u5F55" },
59
- { pattern: /rm\s+-rf\s+\/etc|rm\s+-rf\s+\/var/, severity: "critical", description: "\u5220\u9664\u7CFB\u7EDF\u76EE\u5F55" },
60
- { pattern: /curl\s+.*\|\s*(bash|sh|zsh)/, severity: "critical", description: "curl pipe shell \u6267\u884C" },
61
- { pattern: /wget\s+.*\|\s*(bash|sh)/, severity: "critical", description: "wget pipe shell \u6267\u884C" },
62
- { pattern: />\s*\/dev\/sda|dd\s+if=.*of=\/dev\//, severity: "critical", description: "\u78C1\u76D8\u76F4\u63A5\u5199\u5165" },
63
- { pattern: /mkfs\./, severity: "critical", description: "\u683C\u5F0F\u5316\u6587\u4EF6\u7CFB\u7EDF" },
64
- { pattern: /chmod\s+777\s+\//, severity: "high", description: "\u4FEE\u6539\u6839\u76EE\u5F55\u6743\u9650\u4E3A777" },
65
- { pattern: /chown\s+-R\s+\w+:\w+\s+\//, severity: "high", description: "\u9012\u5F52\u4FEE\u6539\u6839\u76EE\u5F55\u6240\u6709\u8005" },
66
- { pattern: /eval\s+\$\(curl|eval\s+\$\(wget/, severity: "critical", description: "eval \u8FDC\u7A0B\u4EE3\u7801\u6267\u884C" },
67
- { pattern: /\/dev\/tcp\/|bash\s+-i\s+>&/, severity: "critical", description: "\u53CD\u5F39 Shell" },
68
- { pattern: /nc\s+-[el]\s+.*\d{1,5}/, severity: "high", description: "Netcat \u53CD\u5F39\u8FDE\u63A5" },
69
- { pattern: /:\(\)\s*\{.*:\|:&.*\};:/, severity: "critical", description: "Fork Bomb" },
70
- { pattern: /sudo\s+rm\s+-rf/, severity: "high", description: "sudo \u5F3A\u5236\u5220\u9664" },
71
- { pattern: /git\s+clone\s+.*\|.*bash/, severity: "high", description: "git clone pipe shell" },
72
- { pattern: /pip\s+install\s+.*\|\s*bash/, severity: "high", description: "pip install pipe shell" },
73
- { pattern: /npm\s+install\s+-g\s+.*\|\s*bash/, severity: "high", description: "npm install pipe shell" },
242
+ // --- Critical: rm command graded detection (strict to loose, first match wins) ---
243
+ // 1. rm -rf root dir
244
+ { pattern: /\brm\s+-[a-zA-Z]*r[a-zA-Z]*f[a-zA-Z]*\s+\/\s/i, severity: "critical", description: "\u9012\u5F52\u5F3A\u5236\u5220\u9664\u6839\u76EE\u5F55 (rm -rf /)" },
245
+ // 2. rm -rf absolute path (both -r and -f)
246
+ { pattern: /\brm\s+-(?=[^\s]*r)(?=[^\s]*f)[a-zA-Z]*\s+\//i, severity: "critical", description: "\u9012\u5F52\u5F3A\u5236\u5220\u9664\u7EDD\u5BF9\u8DEF\u5F84 (rm -rf /...)" },
247
+ // 3. rm with -r or -f targeting system dirs
248
+ { pattern: /\brm\s+-[a-zA-Z]*[rf][a-zA-Z]*\s+\/(etc|var|home|usr|boot|root|opt|srv|sys|proc)\b/i, severity: "critical", description: "\u5220\u9664\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55" },
249
+ // 4. rm -r absolute path (no -f)
250
+ { pattern: /\brm\s+-[a-zA-Z]*r[a-zA-Z]*\s+\//i, severity: "high", description: "\u9012\u5F52\u5220\u9664\u64CD\u4F5C (rm -r /...)" },
251
+ // 5. rm -f absolute path (no -r)
252
+ { pattern: /\brm\s+-[a-zA-Z]*f[a-zA-Z]*\s+\//i, severity: "medium", description: "\u5F3A\u5236\u5220\u9664\u6587\u4EF6 (rm -f /...)" },
253
+ { pattern: /dd\s+if=\/dev\/zero|dd\s+if=.*of=\/dev\//i, severity: "critical", description: "\u78C1\u76D8\u6E05\u96F6/\u76F4\u63A5\u5199\u5165" },
254
+ { pattern: /mkfs\b/i, severity: "critical", description: "\u683C\u5F0F\u5316\u6587\u4EF6\u7CFB\u7EDF" },
255
+ { pattern: /:\(\)\s*\{.*:\|:&.*\};:/i, severity: "critical", description: "Fork Bomb" },
256
+ // --- Critical: remote code execution ---
257
+ { pattern: /curl\s+.*\|\s*(bash|sh|zsh|dash)/i, severity: "critical", description: "curl pipe shell \u6267\u884C" },
258
+ { pattern: /wget\s+.*\|\s*(bash|sh)/i, severity: "critical", description: "wget pipe shell \u6267\u884C" },
259
+ { pattern: /eval\s+\$\(curl|eval\s+\$\(wget/i, severity: "critical", description: "eval \u8FDC\u7A0B\u4EE3\u7801\u6267\u884C" },
260
+ { pattern: /base64\s+-d\s+.*\|\s*bash/i, severity: "critical", description: "base64 \u89E3\u7801\u6267\u884C" },
261
+ // --- Critical: reverse shell ---
262
+ { pattern: /\/dev\/tcp\/|bash\s+-i\s+>&/i, severity: "critical", description: "\u53CD\u5F39 Shell" },
263
+ { pattern: /nc\s+-[el]\s+.*\d{1,5}/i, severity: "high", description: "Netcat \u53CD\u5F39\u8FDE\u63A5" },
264
+ // --- Critical: sensitive file access ---
265
+ { pattern: /\/etc\/shadow/i, severity: "critical", description: "\u8BBF\u95EE\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6 (/etc/shadow)" },
266
+ { pattern: /\.ssh\/id_rsa/i, severity: "critical", description: "\u8BBF\u95EE SSH \u79C1\u94A5\u6587\u4EF6" },
267
+ // --- Critical: privilege escalation / container escape ---
268
+ { pattern: /\/var\/run\/docker\.sock/i, severity: "critical", description: "\u8BBF\u95EE Docker socket\uFF0C\u53EF\u80FD\u5BFC\u81F4\u5BB9\u5668\u9003\u9038" },
269
+ // --- High: dangerous permissions / ownership ---
270
+ { pattern: /chmod\s+[0-7]*777[0-7]*/i, severity: "high", description: "\u5168\u6743\u9650\u8BBE\u7F6E (chmod 777)" },
271
+ { pattern: /chown\s+root|chown\s+-R\s+\w+:\w+\s+\//i, severity: "high", description: "\u4FEE\u6539\u6240\u6709\u8005\u4E3A root / \u9012\u5F52\u4FEE\u6539\u6839\u76EE\u5F55\u6240\u6709\u8005" },
272
+ { pattern: /sudo\s+rm\s+-rf/i, severity: "high", description: "sudo \u5F3A\u5236\u5220\u9664" },
273
+ { pattern: /sudo\s+/i, severity: "high", description: "\u63D0\u6743\u6267\u884C (sudo)" },
274
+ // --- High: system config overwrite ---
275
+ { pattern: />\s*\/etc\//i, severity: "critical", description: "\u8986\u76D6\u5199\u5165\u7CFB\u7EDF\u914D\u7F6E\u76EE\u5F55" },
276
+ // --- High: download script + execute risk ---
277
+ { pattern: /(?:curl|wget)\s+.*(?:-o|--output|>)\s*\S+\.(?:sh|bash)\b/i, severity: "high", description: "\u8FDC\u7A0B\u4E0B\u8F7D\u811A\u672C\u5230\u672C\u5730" },
278
+ { pattern: /(?:curl|wget)\s+.*\s+&&?\s*(?:chmod\s+\+x|bash|sh)/i, severity: "high", description: "\u4E0B\u8F7D\u540E\u6267\u884C" },
279
+ // --- High: package manager pipe-to-shell ---
280
+ { pattern: /git\s+clone\s+.*\|.*bash/i, severity: "high", description: "git clone pipe shell" },
281
+ { pattern: /pip\s+install\s+.*\|\s*bash/i, severity: "high", description: "pip install pipe shell" },
282
+ { pattern: /npm\s+install\s+-g\s+.*\|\s*bash/i, severity: "high", description: "npm install pipe shell" },
283
+ // --- High: destructive git operations ---
284
+ { pattern: /git\s+push\s+.*--force/i, severity: "high", description: "\u5F3A\u5236\u63A8\u9001\u64CD\u4F5C (git push --force)" },
285
+ // --- Medium: credential export ---
74
286
  { pattern: /export\s+.*(?:TOKEN|KEY|SECRET|PASSWORD)/i, severity: "medium", description: "\u5BFC\u51FA\u654F\u611F\u51ED\u8BC1" },
75
- { pattern: /base64\s+-d\s+.*\|\s*bash/, severity: "critical", description: "base64 \u89E3\u7801\u6267\u884C" }
287
+ // --- High: device file access ---
288
+ { pattern: /\/dev\/(mem|kmsg)\b/i, severity: "high", description: "\u8BBF\u95EE\u8BBE\u5907\u6587\u4EF6 (\u7269\u7406\u5185\u5B58/\u5185\u6838\u6D88\u606F)" }
76
289
  ];
77
290
  }
78
291
  });
79
292
 
80
293
  // src/engine/logic-rules.ts
294
+ import * as fs from "fs";
81
295
  function runLogicCheck(input) {
82
296
  const { actionHistory, aNext, cwd } = input;
83
- const command = aNext.actionDetail;
297
+ const actionDetail = aNext.actionDetail.toLowerCase();
298
+ const actionType = aNext.actionType.toLowerCase();
84
299
  const resolvedCwd = cwd || "/";
85
- if (aNext.actionType === "write" || aNext.actionType === "file_write" || aNext.actionType === "write_to_file") {
86
- const hadRead = actionHistory.some(
87
- (h) => (h.actionType === "read" || h.actionType === "file_read" || h.actionType === "read_file") && h.actionDetail.includes(cwd)
88
- );
89
- if (!hadRead && actionHistory.length === 0) {
300
+ const rbwResult = checkReadBeforeWrite(actionType, actionDetail, actionHistory, input.reason, resolvedCwd);
301
+ if (rbwResult) return rbwResult;
302
+ if (input.promptSession && input.promptSession.trim()) {
303
+ const promptLower = input.promptSession.toLowerCase();
304
+ for (const pattern of INTENT_DEVIATION_PATTERNS) {
305
+ const intentMatched = pattern.intentKeywords.some((kw) => promptLower.includes(kw));
306
+ const dangerousMatched = pattern.dangerousActions.some((kw) => actionDetail.includes(kw));
307
+ if (intentMatched && dangerousMatched) {
308
+ return {
309
+ allowed: false,
310
+ reason: pattern.reason,
311
+ riskLevel: "high",
312
+ riskType: "intent_deviation",
313
+ confidence: 85,
314
+ source: "logic_rule",
315
+ violatedLayers: ["2"]
316
+ };
317
+ }
90
318
  }
91
319
  }
92
- if (containsSensitivePath(command, resolvedCwd)) {
93
- return {
94
- allowed: false,
95
- reason: "\u5C1D\u8BD5\u8BBF\u95EE\u7CFB\u7EDF\u654F\u611F\u8DEF\u5F84",
96
- riskLevel: "high",
97
- riskType: "sensitive_path_access",
98
- confidence: 90,
99
- source: "logic_rule",
100
- violatedLayers: ["2"]
101
- };
320
+ const pathResult = checkSensitivePath(actionDetail, resolvedCwd);
321
+ if (pathResult) return pathResult;
322
+ if (actionType !== "file_write" && actionType !== "file_edit") {
323
+ if ((actionDetail.includes("*") || actionDetail.includes("?")) && ["rm ", "rm ", "del ", "del ", "delete", "remove ", "remove ", "\u5220\u9664", "\u79FB\u9664"].some((kw) => actionDetail.includes(kw))) {
324
+ return {
325
+ allowed: false,
326
+ reason: "\u68C0\u6D4B\u5230\u901A\u914D\u7B26\u7ED3\u5408\u5220\u9664\u64CD\u4F5C\uFF0C\u53EF\u80FD\u9020\u6210\u6279\u91CF\u8BEF\u5220",
327
+ riskLevel: "high",
328
+ riskType: "script_execution",
329
+ confidence: 90,
330
+ source: "logic_rule",
331
+ violatedLayers: ["2"]
332
+ };
333
+ }
334
+ if (/>\s*\/etc\//.test(actionDetail) || />\s*\/boot\//.test(actionDetail) || />\s*\/proc\//.test(actionDetail)) {
335
+ return {
336
+ allowed: false,
337
+ reason: "\u68C0\u6D4B\u5230\u91CD\u5B9A\u5411\u8986\u76D6\u5199\u5165\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55",
338
+ riskLevel: "critical",
339
+ riskType: "file_access",
340
+ confidence: 95,
341
+ source: "logic_rule",
342
+ violatedLayers: ["2"]
343
+ };
344
+ }
102
345
  }
103
- if (command.includes("sudo") || command.includes("su -") || command.includes("chmod u+s") || command.includes("setuid")) {
346
+ if (actionDetail.includes("sudo") || actionDetail.includes("su -") || actionDetail.includes("chmod u+s") || actionDetail.includes("setuid")) {
104
347
  const adminContext = actionHistory.some(
105
- (h) => h.actionDetail.includes("sudo") || h.actionDetail.includes("apt") || h.actionDetail.includes("systemctl")
348
+ (h) => h.actionDetail.includes("sudo") || h.actionDetail.includes("apt") || h.actionDetail.includes("systemctl") || h.actionDetail.includes("yum") || h.actionDetail.includes("dnf") || h.actionDetail.includes("pacman") || h.actionDetail.includes("zypper") || h.actionDetail.includes("apk")
106
349
  );
107
- if (!adminContext && actionHistory.length === 0) {
350
+ if (!adminContext) {
108
351
  return {
109
352
  allowed: false,
110
353
  reason: "\u68C0\u6D4B\u5230\u63D0\u6743\u64CD\u4F5C\uFF0C\u672A\u53D1\u73B0\u5408\u6CD5\u7BA1\u7406\u4E0A\u4E0B\u6587",
@@ -116,8 +359,8 @@ function runLogicCheck(input) {
116
359
  };
117
360
  }
118
361
  }
119
- if (command.includes("ssh") || command.includes("scp") || command.includes("rsync")) {
120
- const targetHost = extractRemoteHost(command);
362
+ if (/(?:ssh|scp|rsync)\s+/.test(actionDetail)) {
363
+ const targetHost = extractRemoteHost(actionDetail);
121
364
  if (targetHost && !isLocalHost(targetHost)) {
122
365
  return {
123
366
  allowed: false,
@@ -132,18 +375,100 @@ function runLogicCheck(input) {
132
375
  }
133
376
  return null;
134
377
  }
135
- function containsSensitivePath(command, cwd) {
378
+ function extractFilePaths(text) {
379
+ const paths = [];
380
+ const unixPaths = text.match(/(?:^|\s)(\/[^\s;|&><'"]+)/g);
381
+ if (unixPaths) paths.push(...unixPaths.map((p) => p.trim()));
382
+ const quotedPaths = text.match(/["']\/([^"']+)["']/g);
383
+ if (quotedPaths) paths.push(...quotedPaths.map((p) => p.replace(/["']/g, "")));
384
+ return [...new Set(paths)];
385
+ }
386
+ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, cwd) {
387
+ if (NON_WRITE_TOOLS.has(actionType)) return null;
388
+ const isWrite = WRITE_KEYWORDS.some((kw) => actionType.includes(kw) || actionDetail.includes(kw));
389
+ if (!isWrite) return null;
390
+ const targetPaths = extractFilePaths(actionDetail);
391
+ if (targetPaths.length === 0) return null;
392
+ const historyReadPaths = /* @__PURE__ */ new Set();
393
+ for (const action of actionHistory) {
394
+ const histDetail = action.actionDetail.toLowerCase();
395
+ const histType = action.actionType.toLowerCase();
396
+ const isRead = READ_KEYWORDS.some((kw) => histType.includes(kw) || histDetail.includes(kw));
397
+ if (isRead) {
398
+ for (const p of extractFilePaths(histDetail)) {
399
+ historyReadPaths.add(p);
400
+ }
401
+ }
402
+ }
403
+ const unreadPaths = [];
404
+ for (const path2 of targetPaths) {
405
+ const hasRead = [...historyReadPaths].some(
406
+ (rp) => path2 === rp || path2.startsWith(rp) || rp.startsWith(path2)
407
+ );
408
+ if (!hasRead) {
409
+ let isCreate = !fs.existsSync(path2);
410
+ if (!isCreate) {
411
+ isCreate = CREATE_KEYWORDS.some((kw) => reason.toLowerCase().includes(kw) || actionDetail.includes(kw));
412
+ }
413
+ if (!isCreate) {
414
+ unreadPaths.push(path2);
415
+ }
416
+ }
417
+ }
418
+ if (unreadPaths.length > 0) {
419
+ return {
420
+ allowed: false,
421
+ reason: `\u8FDD\u53CD read_before_write \u5B89\u5168\u539F\u5219\uFF1A\u5BF9\u6587\u4EF6 ${unreadPaths[0]} \u6267\u884C\u5199\u5165\u64CD\u4F5C\uFF0C\u4F46\u5386\u53F2\u6267\u884C\u5E8F\u5217\u4E2D\u672A\u53D1\u73B0\u5BF9\u8BE5\u6587\u4EF6\u7684\u8BFB\u53D6\u64CD\u4F5C`,
422
+ riskLevel: "high",
423
+ riskType: "read_before_write",
424
+ confidence: 85,
425
+ source: "logic_rule",
426
+ violatedLayers: ["2"]
427
+ };
428
+ }
429
+ return null;
430
+ }
431
+ function checkSensitivePath(command, cwd) {
136
432
  const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
137
- for (const p of ROOT_SENSITIVE_PATHS) {
138
- if (command.includes(p)) return true;
433
+ for (const sp of ROOT_SENSITIVE_PATHS) {
434
+ if (command.includes(sp.path.toLowerCase())) {
435
+ return {
436
+ allowed: false,
437
+ reason: `\u5C1D\u8BD5\u8BBF\u95EE\u7CFB\u7EDF\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
438
+ riskLevel: sp.riskLevel,
439
+ riskType: "sensitive_path_access",
440
+ confidence: 90,
441
+ source: "logic_rule",
442
+ violatedLayers: ["2"]
443
+ };
444
+ }
139
445
  }
140
- for (const p of HOME_SENSITIVE_PATHS) {
141
- if (command.includes(p) || command.includes(`${expandedCwd}/${p}`)) return true;
446
+ for (const sp of HOME_SENSITIVE_PATHS) {
447
+ const expanded = `${expandedCwd}/${sp.path}`;
448
+ if (command.includes(sp.path.toLowerCase()) || command.includes(expanded.toLowerCase())) {
449
+ return {
450
+ allowed: false,
451
+ reason: `\u5C1D\u8BD5\u8BBF\u95EE\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
452
+ riskLevel: sp.riskLevel,
453
+ riskType: "sensitive_path_access",
454
+ confidence: 90,
455
+ source: "logic_rule",
456
+ violatedLayers: ["2"]
457
+ };
458
+ }
142
459
  }
143
460
  if ((command.includes("rm ") || command.includes("unlink")) && (command.includes("/etc/") || command.includes("/root/") || command.includes("/var/log/"))) {
144
- return true;
461
+ return {
462
+ allowed: false,
463
+ reason: "\u68C0\u6D4B\u5230\u5220\u9664\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55\u5185\u5BB9",
464
+ riskLevel: "critical",
465
+ riskType: "sensitive_path_access",
466
+ confidence: 90,
467
+ source: "logic_rule",
468
+ violatedLayers: ["2"]
469
+ };
145
470
  }
146
- return false;
471
+ return null;
147
472
  }
148
473
  function extractRemoteHost(command) {
149
474
  const m = command.match(/(?:ssh|scp|rsync)\s+(?:-[\w]+\s+)*(\S+@\S+|\b(?!-)[\w.-]+\b)/);
@@ -153,75 +478,123 @@ function isLocalHost(host) {
153
478
  const localNames = ["localhost", "127.0.0.1", "::1", process.env.HOSTNAME || "", "0.0.0.0"];
154
479
  return localNames.includes(host.replace(/.*@/, ""));
155
480
  }
156
- var ROOT_SENSITIVE_PATHS, HOME_SENSITIVE_PATHS;
481
+ var ROOT_SENSITIVE_PATHS, HOME_SENSITIVE_PATHS, NON_WRITE_TOOLS, WRITE_KEYWORDS, READ_KEYWORDS, CREATE_KEYWORDS, INTENT_DEVIATION_PATTERNS;
157
482
  var init_logic_rules = __esm({
158
483
  "src/engine/logic-rules.ts"() {
159
484
  "use strict";
160
485
  init_esm_shims();
161
486
  ROOT_SENSITIVE_PATHS = [
162
- "/etc/shadow",
163
- "/etc/passwd",
164
- "/etc/sudoers",
165
- "/root/.ssh",
166
- "/root/.bashrc",
167
- "/boot",
168
- "/sys",
169
- "/proc"
487
+ { path: "/etc/shadow", riskLevel: "critical", desc: "\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6" },
488
+ { path: "/etc/passwd", riskLevel: "high", desc: "\u7CFB\u7EDF\u7528\u6237\u6587\u4EF6" },
489
+ { path: "/etc/sudoers", riskLevel: "critical", desc: "sudo \u914D\u7F6E" },
490
+ { path: "/etc/hosts", riskLevel: "medium", desc: "DNS \u89E3\u6790\u914D\u7F6E" },
491
+ { path: "/etc/crontab", riskLevel: "high", desc: "\u7CFB\u7EDF\u5B9A\u65F6\u4EFB\u52A1" },
492
+ { path: "/etc/systemd/", riskLevel: "high", desc: "systemd \u670D\u52A1\u914D\u7F6E" },
493
+ { path: "/etc/ssh/sshd_config", riskLevel: "high", desc: "SSH \u670D\u52A1\u914D\u7F6E" },
494
+ { path: "/root/.ssh", riskLevel: "critical", desc: "root SSH \u76EE\u5F55" },
495
+ { path: "/root/.bashrc", riskLevel: "high", desc: "root shell \u914D\u7F6E" },
496
+ { path: "/boot/", riskLevel: "critical", desc: "\u542F\u52A8\u5F15\u5BFC\u76EE\u5F55" },
497
+ { path: "/proc/sys/", riskLevel: "high", desc: "\u5185\u6838\u53C2\u6570" },
498
+ { path: "/sys/", riskLevel: "high", desc: "sysfs \u5185\u6838\u63A5\u53E3" },
499
+ { path: "/dev/mem", riskLevel: "critical", desc: "\u7269\u7406\u5185\u5B58\u8BBF\u95EE\u8BBE\u5907" },
500
+ { path: "/dev/kmsg", riskLevel: "high", desc: "\u5185\u6838\u6D88\u606F\u7F13\u51B2\u533A" }
170
501
  ];
171
502
  HOME_SENSITIVE_PATHS = [
172
- ".ssh/id_rsa",
173
- ".ssh/authorized_keys",
174
- ".aws/credentials",
175
- ".gitconfig",
176
- ".gnupg"
503
+ { path: ".ssh/id_rsa", riskLevel: "critical", desc: "SSH \u79C1\u94A5" },
504
+ { path: ".ssh/id_ed25519", riskLevel: "critical", desc: "SSH \u79C1\u94A5 (ed25519)" },
505
+ { path: ".ssh/authorized_keys", riskLevel: "high", desc: "SSH \u6388\u6743\u5BC6\u94A5" },
506
+ { path: ".aws/credentials", riskLevel: "critical", desc: "AWS \u51ED\u8BC1" },
507
+ { path: ".gitconfig", riskLevel: "medium", desc: "Git \u914D\u7F6E" },
508
+ { path: ".gnupg", riskLevel: "high", desc: "GPG \u5BC6\u94A5\u76EE\u5F55" }
509
+ ];
510
+ NON_WRITE_TOOLS = /* @__PURE__ */ new Set(["ask_user_question", "glob", "grep", "list_dir", "count_text_length", "search"]);
511
+ WRITE_KEYWORDS = ["write", "\u5199\u5165", "save", "\u4FDD\u5B58", "modify", "\u4FEE\u6539", "update", "\u66F4\u65B0", "delete", "\u5220\u9664", "remove", "\u79FB\u9664", "create", "\u521B\u5EFA", "append", "\u8FFD\u52A0", "truncate", "\u622A\u65AD", "overwrite", "\u8986\u76D6", "replace", "\u66FF\u6362"];
512
+ READ_KEYWORDS = ["read", "\u8BFB\u53D6", "cat", "head", "tail", "less", "more", "view", "\u67E5\u770B", "open", "\u6253\u5F00", "load", "\u52A0\u8F7D", "grep", "search", "\u641C\u7D22", "find", "\u67E5\u627E"];
513
+ CREATE_KEYWORDS = ["\u521B\u5EFA", "\u65B0\u5EFA", "create", "new file", "touch"];
514
+ INTENT_DEVIATION_PATTERNS = [
515
+ {
516
+ intentKeywords: ["\u8BFB\u53D6", "\u67E5\u770B", "\u5206\u6790", "read", "analyze", "view", "list", "\u5217\u51FA"],
517
+ dangerousActions: ["\u5220\u9664", "remove", "delete", "rm ", "\u683C\u5F0F\u5316", "format"],
518
+ reason: "\u539F\u59CB\u610F\u56FE\u4E3A\u8BFB\u53D6/\u5206\u6790\uFF0C\u4F46\u6267\u884C\u4E86\u5220\u9664\u64CD\u4F5C"
519
+ },
520
+ {
521
+ intentKeywords: ["\u67E5\u8BE2", "\u641C\u7D22", "search", "query", "find"],
522
+ dangerousActions: ["\u4FEE\u6539", "update", "write", "\u5199\u5165", "alter"],
523
+ reason: "\u539F\u59CB\u610F\u56FE\u4E3A\u67E5\u8BE2/\u641C\u7D22\uFF0C\u4F46\u6267\u884C\u4E86\u4FEE\u6539\u64CD\u4F5C"
524
+ }
177
525
  ];
178
- }
179
- });
180
-
181
- // src/config.ts
182
- function getLLMConfig() {
183
- return llmConfig;
184
- }
185
- function isLLMAvailable() {
186
- return llmConfig.apiKey.length > 0;
187
- }
188
- var llmConfig;
189
- var init_config = __esm({
190
- "src/config.ts"() {
191
- "use strict";
192
- init_esm_shims();
193
- llmConfig = {
194
- provider: "openai",
195
- baseUrl: process.env.AGENT_MOSS_LLM_BASE_URL || "https://api.openai.com/v1",
196
- apiKey: process.env.AGENT_MOSS_LLM_API_KEY || "",
197
- model: process.env.AGENT_MOSS_LLM_MODEL || "gpt-4o",
198
- temperature: parseFloat(process.env.AGENT_MOSS_LLM_TEMPERATURE || "0.1"),
199
- maxTokens: parseInt(process.env.AGENT_MOSS_LLM_MAX_TOKENS || "4096", 10)
200
- };
201
526
  }
202
527
  });
203
528
 
204
529
  // src/engine/skill-loader.ts
205
- import { readFileSync, readdirSync, existsSync } from "fs";
530
+ import { readFileSync, readdirSync, existsSync as existsSync2 } from "fs";
206
531
  import { resolve, dirname } from "path";
207
532
  import { fileURLToPath as fileURLToPath2 } from "url";
208
- function loadSkills() {
533
+ function loadAllSkills() {
209
534
  if (cachedSkills) return cachedSkills;
210
- if (!existsSync(SKILLS_DIR)) {
535
+ cachedSkills = {};
536
+ if (!existsSync2(SKILLS_DIR)) {
211
537
  console.warn("[AgentMoss] Skills directory not found:", SKILLS_DIR);
212
- cachedSkills = [];
213
538
  return cachedSkills;
214
539
  }
215
540
  const files = readdirSync(SKILLS_DIR).filter((f) => f.endsWith(".md"));
216
- cachedSkills = files.map((f) => {
217
- const content = readFileSync(resolve(SKILLS_DIR, f), "utf-8");
218
- return `### ${f.replace(".md", "")}
219
- ${content}`;
220
- });
221
- console.log(`[AgentMoss] Loaded ${cachedSkills.length} skills`);
541
+ for (const f of files) {
542
+ const name = f.replace(".md", "");
543
+ cachedSkills[name] = readFileSync(resolve(SKILLS_DIR, f), "utf-8");
544
+ }
545
+ console.log(`[AgentMoss] Loaded ${Object.keys(cachedSkills).length} skills`);
222
546
  return cachedSkills;
223
547
  }
224
- var __filename2, __dirname2, SKILLS_DIR, cachedSkills;
548
+ function matchSkills(actionType, actionDetail, reason, maxSkills = 3) {
549
+ const skills = loadAllSkills();
550
+ const combined = `${actionType} ${actionDetail} ${reason}`.toLowerCase();
551
+ const actionDetailLower = actionDetail.toLowerCase();
552
+ const actionTypeLower = actionType.toLowerCase();
553
+ const reasonLower = reason.toLowerCase();
554
+ const scores = {};
555
+ for (const [skillName, keywords] of Object.entries(SKILL_KEYWORD_MAP)) {
556
+ let score = 0;
557
+ for (const kw of keywords) {
558
+ if (combined.includes(kw)) {
559
+ if (kw === actionTypeLower) score += 2;
560
+ else if (actionDetailLower.includes(kw)) score += 1.5;
561
+ else if (reasonLower.includes(kw)) score += 1;
562
+ else score += 0.5;
563
+ }
564
+ }
565
+ if (score > 0) scores[skillName] = score;
566
+ }
567
+ const sorted = Object.entries(scores).sort((a, b) => b[1] - a[1]);
568
+ const top = sorted.slice(0, maxSkills);
569
+ const maxScore = top[0]?.[1] || 1;
570
+ const results = [];
571
+ for (const [name, score] of top) {
572
+ const content = skills[name];
573
+ if (content) {
574
+ results.push({
575
+ skillName: name,
576
+ relevance: Math.min(score / maxScore, 1),
577
+ skillContent: content
578
+ });
579
+ }
580
+ }
581
+ if (results.length === 0) {
582
+ const fallback = skills["general_tool_risk_guard"];
583
+ if (fallback) {
584
+ results.push({ skillName: "general_tool_risk_guard", relevance: 0.3, skillContent: fallback });
585
+ }
586
+ }
587
+ return results;
588
+ }
589
+ function formatSkillsForPrompt(skills) {
590
+ if (skills.length === 0) return "(\u672A\u5339\u914D\u5230\u7279\u5B9A\u5B89\u5168\u68C0\u6D4B\u89C4\u5219)";
591
+ return skills.map(
592
+ (s) => `### \u5B89\u5168\u68C0\u6D4B\u89C4\u5219: ${s.skillName} (\u76F8\u5173\u5EA6: ${Math.round(s.relevance * 100)}%)
593
+
594
+ ${s.skillContent}`
595
+ ).join("\n\n---\n\n");
596
+ }
597
+ var __filename2, __dirname2, SKILLS_DIR, SKILL_KEYWORD_MAP, cachedSkills;
225
598
  var init_skill_loader = __esm({
226
599
  "src/engine/skill-loader.ts"() {
227
600
  "use strict";
@@ -229,24 +602,38 @@ var init_skill_loader = __esm({
229
602
  __filename2 = fileURLToPath2(import.meta.url);
230
603
  __dirname2 = dirname(__filename2);
231
604
  SKILLS_DIR = resolve(__dirname2, "../../agent_moss/skills");
605
+ SKILL_KEYWORD_MAP = {
606
+ "file_access_guard": ["file", "\u6587\u4EF6", "path", "\u8DEF\u5F84", "read", "write", "cat", "open", "access", "\u8BBF\u95EE", "\u8BFB\u5199", "copy", "cp", "mv", "move"],
607
+ "script_execution_guard": ["command", "shell", "bash", "exec", "execute", "\u6267\u884C", "\u547D\u4EE4", "script", "\u811A\u672C", "run", "\u8FD0\u884C", "subprocess", "system", "eval", "compile", "\u5B89\u88C5", "install", "pip", "npm"],
608
+ "data_exfiltration_guard": ["upload", "\u4E0A\u4F20", "send", "\u53D1\u9001", "transfer", "\u4F20\u8F93", "export", "\u5BFC\u51FA", "download", "\u4E0B\u8F7D", "post", "http", "request", "fetch", "curl", "wget", "\u5916\u4F20", "\u6CC4\u9732", "exfil", "network", "\u7F51\u7EDC"],
609
+ "browser_web_access_guard": ["browser", "\u6D4F\u89C8\u5668", "web", "\u7F51\u9875", "url", "navigate", "\u6D4F\u89C8", "scrape", "crawl", "\u722C\u53D6", "click", "screenshot"],
610
+ "email_operation_guard": ["email", "\u90AE\u4EF6", "mail", "smtp", "send_email", "\u53D1\u9001\u90AE\u4EF6", "inbox", "\u6536\u4EF6\u7BB1", "\u9644\u4EF6", "attachment"],
611
+ "general_tool_risk_guard": ["tool", "\u5DE5\u5177", "api", "invoke", "\u8C03\u7528", "function", "\u51FD\u6570"],
612
+ "lateral_movement_guard": ["ssh", "scp", "ftp", "sftp", "rdp", "vnc", "telnet", "\u5185\u7F51", "\u6A2A\u5411", "tunnel", "\u4EE3\u7406", "proxy", "port", "\u7AEF\u53E3", "scan", "\u626B\u63CF", "ping", "traceroute", "nslookup"],
613
+ "persistence_backdoor_guard": ["cron", "crontab", "\u5B9A\u65F6", "scheduled", "startup", "\u542F\u52A8\u9879", "service", "\u670D\u52A1", "daemon", "\u5B88\u62A4", "registry", "\u6CE8\u518C\u8868", "backdoor", "\u540E\u95E8", "persist", "\u6301\u4E45\u5316", "authorized_keys"],
614
+ "resource_exhaustion_guard": ["fork", "bomb", "\u70B8\u5F39", "memory", "\u5185\u5B58", "disk", "\u78C1\u76D8", "cpu", "process", "\u8FDB\u7A0B", "dd", "while true", "\u65E0\u9650\u5FAA\u73AF", "\u8017\u5C3D", "exhaust", "\u8D44\u6E90"],
615
+ "skill_installation_guard": ["skill", "plugin", "\u63D2\u4EF6", "extension", "\u6269\u5C55", "install skill", "\u5B89\u88C5\u63D2\u4EF6", "\u5B89\u88C5\u6280\u80FD", "marketplace", "\u5546\u5E97"],
616
+ "supply_chain_guard": ["dependency", "\u4F9D\u8D56", "package", "\u5305", "npm install", "pip install", "requirements", "import", "library", "\u5E93", "registry", "\u4ED3\u5E93", "supply", "\u4F9B\u5E94\u94FE", "typosquatting"],
617
+ "intent_deviation_guard": ["rm", "del", "delete", "remove", "\u5220\u9664", "format", "\u683C\u5F0F\u5316", "drop", "truncate", "\u6E05\u7A7A", "wipe", "\u64E6\u9664", "\u91CD\u7F6E", "reset", "sudo", "root", "admin", "\u7BA1\u7406\u5458", "privilege", "\u63D0\u6743"]
618
+ };
232
619
  cachedSkills = null;
233
620
  }
234
621
  });
235
622
 
236
623
  // src/engine/template-loader.ts
237
- import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
624
+ import { readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
238
625
  import { resolve as resolve2, dirname as dirname2 } from "path";
239
626
  import { fileURLToPath as fileURLToPath3 } from "url";
240
627
  function loadTemplates() {
241
628
  if (cachedTemplates) return cachedTemplates;
242
629
  cachedTemplates = {};
243
- if (!existsSync2(TEMPLATES_DIR)) {
630
+ if (!existsSync3(TEMPLATES_DIR)) {
244
631
  console.warn("[AgentMoss] Templates directory not found:", TEMPLATES_DIR);
245
632
  return cachedTemplates;
246
633
  }
247
634
  for (const file of TEMPLATE_FILES) {
248
635
  const filePath = resolve2(TEMPLATES_DIR, file);
249
- if (existsSync2(filePath)) {
636
+ if (existsSync3(filePath)) {
250
637
  const key = file.replace(".txt", "").replace("_template", "");
251
638
  cachedTemplates[key] = readFileSync2(filePath, "utf-8");
252
639
  }
@@ -267,6 +654,199 @@ var init_template_loader = __esm({
267
654
  }
268
655
  });
269
656
 
657
+ // src/engine/script-analyzer.ts
658
+ import { readFileSync as readFileSync3 } from "fs";
659
+ import { resolve as resolve3, join } from "path";
660
+ function emptyResult() {
661
+ return { scriptPath: "", scriptContent: "", matchedKeywords: [], lineCount: 0, isSuspicious: false, error: "", riskLevel: "", riskReason: "" };
662
+ }
663
+ function extractScriptPath(command) {
664
+ for (const pattern of SCRIPT_EXEC_PATTERNS) {
665
+ const m = command.match(pattern);
666
+ if (m) return m[1];
667
+ }
668
+ return null;
669
+ }
670
+ function scanScriptForKeywords(content) {
671
+ const matched = [];
672
+ for (const { name, pattern } of SUSPICIOUS_PATTERNS) {
673
+ if (pattern.test(content)) matched.push(name);
674
+ }
675
+ return matched;
676
+ }
677
+ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
678
+ if (matchedKeywords.length === 0) return { level: "", reason: "" };
679
+ const keywordSet = new Set(matchedKeywords);
680
+ for (const rule of KEYWORD_COMBINATION_RISKS) {
681
+ const hitGroup1 = [...rule.group1].some((k) => keywordSet.has(k));
682
+ const hitGroup2 = rule.group2.size === 0 ? true : [...rule.group2].some((k) => keywordSet.has(k));
683
+ if (hitGroup1 && hitGroup2) return { level: rule.level, reason: rule.reason };
684
+ }
685
+ const networkPostKeywords = /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]);
686
+ const hasNetworkPost = [...networkPostKeywords].some((k) => keywordSet.has(k));
687
+ if (hasNetworkPost) {
688
+ const envPatterns = ["$env", "$(env)", "$USER", "$(hostname)", "$(pwd)", "$HOME"];
689
+ const contentLower = scriptContent.toLowerCase();
690
+ for (const p of envPatterns) {
691
+ if (contentLower.includes(p.toLowerCase())) {
692
+ return {
693
+ level: "critical",
694
+ reason: "\u68C0\u6D4B\u5230\u7F51\u7EDC\u6570\u636E\u53D1\u9001 + \u7CFB\u7EDF\u73AF\u5883\u53D8\u91CF\u6536\u96C6\u7EC4\u5408\uFF0C\u6784\u6210 Critical \u7EA7\u522B\u6570\u636E\u5916\u4F20\u98CE\u9669\u3002\u811A\u672C\u53EF\u80FD\u5C06\u7528\u6237\u73AF\u5883\u53D8\u91CF\uFF08\u53EF\u80FD\u5305\u542B API Key\u3001\u5BC6\u94A5\u7B49\u654F\u611F\u4FE1\u606F\uFF09\u53D1\u9001\u5230\u8FDC\u7A0B\u670D\u52A1\u5668\u3002"
695
+ };
696
+ }
697
+ }
698
+ }
699
+ let bestLevel = "";
700
+ let bestReason = "";
701
+ for (const kw of matchedKeywords) {
702
+ const risk = SINGLE_KEYWORD_RISKS[kw];
703
+ if (risk && (LEVEL_ORDER[risk.level] || 0) > (LEVEL_ORDER[bestLevel] || 0)) {
704
+ bestLevel = risk.level;
705
+ bestReason = risk.reason;
706
+ }
707
+ }
708
+ return { level: bestLevel, reason: bestReason };
709
+ }
710
+ function readScriptContent(scriptPath, maxLines = 500) {
711
+ try {
712
+ const expandedPath = scriptPath.startsWith("~") ? join(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve3(scriptPath);
713
+ const raw = readFileSync3(expandedPath, "utf-8");
714
+ const lines = raw.split("\n").slice(0, maxLines);
715
+ return { content: lines.join("\n"), lineCount: lines.length, error: "" };
716
+ } catch (e) {
717
+ const msg = e instanceof Error ? e.message : String(e);
718
+ return { content: "", lineCount: 0, error: `\u8BFB\u53D6\u811A\u672C\u5931\u8D25: ${msg}` };
719
+ }
720
+ }
721
+ function analyzeScriptContent(actionType, actionDetail, maxLines = 500) {
722
+ if (actionType.toLowerCase() !== "bash") return emptyResult();
723
+ const scriptPath = extractScriptPath(actionDetail);
724
+ if (!scriptPath) return emptyResult();
725
+ const { content, lineCount, error } = readScriptContent(scriptPath, maxLines);
726
+ if (error) return { ...emptyResult(), scriptPath, error };
727
+ const matchedKeywords = scanScriptForKeywords(content);
728
+ const isSuspicious = matchedKeywords.length > 0;
729
+ let riskLevel = "";
730
+ let riskReason = "";
731
+ if (isSuspicious) {
732
+ const risk = assessKeywordCombinationRisk(matchedKeywords, content);
733
+ riskLevel = risk.level;
734
+ riskReason = risk.reason;
735
+ }
736
+ return { scriptPath, scriptContent: content, matchedKeywords, lineCount, isSuspicious, error: "", riskLevel, riskReason };
737
+ }
738
+ function formatScriptAnalysisForPrompt(result) {
739
+ if (!result.scriptPath) return "";
740
+ if (result.error) return `\u811A\u672C\u5185\u5BB9\u5206\u6790\u5931\u8D25: ${result.error}`;
741
+ if (!result.isSuspicious) return "";
742
+ const keywordsStr = result.matchedKeywords.join(", ");
743
+ let riskHint = "";
744
+ if (result.riskLevel && result.riskReason) {
745
+ riskHint = `
746
+
747
+ \u98CE\u9669\u8BC4\u4F30: ${result.riskLevel.toUpperCase()} \u7EA7\u522B
748
+ \u98CE\u9669\u539F\u56E0: ${result.riskReason}
749
+ \u5EFA\u8BAE: \u6B64\u811A\u672C\u5E94\u88AB\u62D2\u7EDD\uFF08Deny\uFF09\uFF0C\u9664\u975E\u6709\u660E\u786E\u7684\u4E1A\u52A1\u9700\u6C42\u4E14\u5DF2\u786E\u8BA4\u76EE\u6807\u670D\u52A1\u5668\u53EF\u4FE1\u3002`;
750
+ }
751
+ return `\u68C0\u6D4B\u5230\u811A\u672C\u6267\u884C\uFF0C\u4E14\u811A\u672C\u5185\u5BB9\u5305\u542B\u53EF\u7591\u5173\u952E\u8BCD:
752
+ \u811A\u672C\u8DEF\u5F84: ${result.scriptPath}
753
+ \u53EF\u7591\u5173\u952E\u8BCD: ${keywordsStr}
754
+ \u811A\u672C\u884C\u6570: ${result.lineCount}${riskHint}
755
+
756
+ ### \u811A\u672C\u5185\u5BB9 ###
757
+ \`\`\`
758
+ ${result.scriptContent}
759
+ \`\`\`
760
+ `;
761
+ }
762
+ var SUSPICIOUS_PATTERNS, SCRIPT_EXEC_PATTERNS, KEYWORD_COMBINATION_RISKS, SINGLE_KEYWORD_RISKS, LEVEL_ORDER;
763
+ var init_script_analyzer = __esm({
764
+ "src/engine/script-analyzer.ts"() {
765
+ "use strict";
766
+ init_esm_shims();
767
+ SUSPICIOUS_PATTERNS = [
768
+ // Network exfiltration (curl/wget POST in scripts is almost never legitimate)
769
+ { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
770
+ { name: "curl --data", pattern: /curl\s+(.*\s)?--data/i },
771
+ { name: "curl -d", pattern: /curl\s+(.*\s)?-d\s/i },
772
+ { name: "wget --post-data", pattern: /wget\s+(.*\s)?--post-data/i },
773
+ { name: "wget --post-file", pattern: /wget\s+(.*\s)?--post-file/i },
774
+ // Reverse shell
775
+ { name: "nc -e", pattern: /\bnc\s+.*-e\b/ },
776
+ { name: "ncat -e", pattern: /\bncat\s+.*-e\b/ },
777
+ { name: "/dev/tcp", pattern: /\/dev\/tcp\// },
778
+ { name: "/dev/udp", pattern: /\/dev\/udp\// },
779
+ // Base64 obfuscation
780
+ { name: "$(base64", pattern: /\$\(\s*base64/ },
781
+ { name: "`base64", pattern: /`\s*base64/ },
782
+ { name: "base64 decode pipe", pattern: /base64\s+(-d|--decode)\s*\|/ },
783
+ // Sensitive file access
784
+ { name: "/etc/shadow", pattern: /\/etc\/shadow/ },
785
+ { name: "id_rsa", pattern: /\bid_rsa\b/ },
786
+ { name: "id_ed25519", pattern: /\bid_ed25519\b/ },
787
+ // Persistence backdoor
788
+ { name: "crontab install", pattern: /crontab\s+-/ },
789
+ { name: "authorized_keys", pattern: /authorized_keys/ },
790
+ { name: "ssh no host key check", pattern: /ssh\s+.*StrictHostKeyChecking\s*=\s*no/ }
791
+ ];
792
+ SCRIPT_EXEC_PATTERNS = [
793
+ /bash\s+(\S+\.sh)/,
794
+ /sh\s+(\S+\.sh)/,
795
+ /\.\/(\S+\.sh)/,
796
+ /source\s+(\S+\.sh)/,
797
+ /\.\s+(\S+\.sh)/,
798
+ /zsh\s+(\S+\.sh)/
799
+ ];
800
+ KEYWORD_COMBINATION_RISKS = [
801
+ {
802
+ group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
803
+ group2: /* @__PURE__ */ new Set(["$(base64", "`base64", "base64 decode pipe"]),
804
+ level: "critical",
805
+ reason: "\u68C0\u6D4B\u5230\u7F51\u7EDC\u6570\u636E\u53D1\u9001 + Base64\u7F16\u7801\u7EC4\u5408\uFF0C\u6784\u6210 Critical \u7EA7\u522B\u6570\u636E\u5916\u4F20\u98CE\u9669\u3002\u653B\u51FB\u8005\u53EF\u80FD\u5C06\u654F\u611F\u6570\u636E\u7F16\u7801\u540E\u5916\u4F20\u3002"
806
+ },
807
+ {
808
+ group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
809
+ group2: /* @__PURE__ */ new Set(["/etc/shadow", "id_rsa", "id_ed25519"]),
810
+ level: "critical",
811
+ reason: "\u68C0\u6D4B\u5230\u7F51\u7EDC\u6570\u636E\u53D1\u9001 + \u654F\u611F\u6587\u4EF6\u8BBF\u95EE\u7EC4\u5408\uFF0C\u6784\u6210 Critical \u7EA7\u522B\u51ED\u8BC1\u5916\u4F20\u98CE\u9669\u3002"
812
+ },
813
+ {
814
+ group1: /* @__PURE__ */ new Set(["nc -e", "ncat -e", "/dev/tcp", "/dev/udp"]),
815
+ group2: /* @__PURE__ */ new Set(),
816
+ level: "high",
817
+ reason: "\u68C0\u6D4B\u5230\u53CD\u5F39 Shell \u7279\u5F81\uFF0C\u653B\u51FB\u8005\u53EF\u80FD\u5EFA\u7ACB\u8FDC\u7A0B\u63A7\u5236\u901A\u9053\u3002"
818
+ },
819
+ {
820
+ group1: /* @__PURE__ */ new Set(["crontab install", "authorized_keys", "ssh no host key check"]),
821
+ group2: /* @__PURE__ */ new Set(),
822
+ level: "high",
823
+ reason: "\u68C0\u6D4B\u5230\u6301\u4E45\u5316\u540E\u95E8\u7279\u5F81\uFF0C\u653B\u51FB\u8005\u53EF\u80FD\u5728\u7CFB\u7EDF\u4E2D\u690D\u5165\u540E\u95E8\u3002"
824
+ }
825
+ ];
826
+ SINGLE_KEYWORD_RISKS = {
827
+ "curl POST": { level: "high", reason: "\u68C0\u6D4B\u5230 curl POST \u8BF7\u6C42\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
828
+ "curl --data": { level: "high", reason: "\u68C0\u6D4B\u5230 curl --data \u53C2\u6570\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
829
+ "curl -d": { level: "high", reason: "\u68C0\u6D4B\u5230 curl -d \u53C2\u6570\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
830
+ "wget --post-data": { level: "high", reason: "\u68C0\u6D4B\u5230 wget POST \u8BF7\u6C42\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
831
+ "wget --post-file": { level: "high", reason: "\u68C0\u6D4B\u5230 wget \u6587\u4EF6\u4E0A\u4F20\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6587\u4EF6\u3002" },
832
+ "nc -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 nc -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
833
+ "ncat -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 ncat -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
834
+ "/dev/tcp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/tcp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
835
+ "/dev/udp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/udp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
836
+ "$(base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
837
+ "`base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
838
+ "base64 decode pipe": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u89E3\u7801\u540E\u7BA1\u9053\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
839
+ "/etc/shadow": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6\u3002" },
840
+ "id_rsa": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
841
+ "id_ed25519": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
842
+ "crontab install": { level: "high", reason: "\u68C0\u6D4B\u5230\u4FEE\u6539\u5B9A\u65F6\u4EFB\u52A1\uFF0C\u53EF\u80FD\u662F\u6301\u4E45\u5316\u540E\u95E8\u3002" },
843
+ "authorized_keys": { level: "high", reason: "\u68C0\u6D4B\u5230\u4FEE\u6539 SSH \u6388\u6743\u5BC6\u94A5\uFF0C\u53EF\u80FD\u662F\u6301\u4E45\u5316\u540E\u95E8\u3002" },
844
+ "ssh no host key check": { level: "high", reason: "\u68C0\u6D4B\u5230 SSH \u8DF3\u8FC7\u4E3B\u673A\u5BC6\u94A5\u9A8C\u8BC1\uFF0C\u53EF\u80FD\u662F\u6A2A\u5411\u79FB\u52A8\u3002" }
845
+ };
846
+ LEVEL_ORDER = { low: 1, medium: 2, high: 3, critical: 4 };
847
+ }
848
+ });
849
+
270
850
  // src/engine/llm-analyzer.ts
271
851
  import OpenAI from "openai";
272
852
  async function runLLMAnalysis(input, previous) {
@@ -283,15 +863,17 @@ async function runLLMAnalysis(input, previous) {
283
863
  };
284
864
  }
285
865
  const config = getLLMConfig();
286
- const cacheKey = `${input.aNext.actionType}:${input.aNext.actionDetail}`;
866
+ const rawDetail = input.aNext.actionDetail.replace(/^terminal-\w+:\s*command=([^,]+).*/, "$1").trim();
867
+ const cacheKey = `${input.aNext.actionType}:${rawDetail || input.aNext.actionDetail}`;
287
868
  const cached = llmCache.get(cacheKey);
288
869
  if (cached) {
289
870
  console.log("[AgentMoss] LLM cache hit");
290
871
  return cached;
291
872
  }
292
- const skills = loadSkills();
873
+ const matchedSkills = matchSkills(input.aNext.actionType, input.aNext.actionDetail, input.reason);
293
874
  const templates = loadTemplates();
294
- const prompt = buildPrompt(input, previous, skills, templates);
875
+ const scriptAnalysis = analyzeScriptContent(input.aNext.actionType, input.aNext.actionDetail);
876
+ const prompt = buildPrompt(input, previous, matchedSkills, templates, scriptAnalysis);
295
877
  const result = await analyzeWithLLM(prompt, config);
296
878
  if (llmCache.size >= LLM_CACHE_MAX) {
297
879
  const firstKey = llmCache.keys().next().value;
@@ -300,11 +882,13 @@ async function runLLMAnalysis(input, previous) {
300
882
  llmCache.set(cacheKey, result);
301
883
  return result;
302
884
  }
303
- function buildPrompt(input, previous, skills, templates) {
304
- const skillContext = skills.length > 0 ? skills.join("\n---\n") : "(\u65E0\u53EF\u7528 Skill)";
305
- const historyStr = input.actionHistory.map((h, i) => `${i + 1}. [${h.actionType}] ${h.actionDetail}`).join("\n") || "(\u65E0\u5386\u53F2\u64CD\u4F5C)";
885
+ function buildPrompt(input, previous, matchedSkills, templates, scriptAnalysis) {
886
+ const skillsText = formatSkillsForPrompt(matchedSkills);
887
+ const historyStr = input.actionHistory.map((h, i) => `${i + 1}. ${h.actionType}: ${h.actionDetail}`).join("\n") || "(\u65E0\u5386\u53F2\u64CD\u4F5C)";
306
888
  const templateName = previous.heuristic || previous.logic ? "prompt2" : "prompt1";
307
889
  const baseTemplate = templates[templateName] || templates["prompt1"] || "";
890
+ const scriptAnalysisText = scriptAnalysis ? formatScriptAnalysisForPrompt(scriptAnalysis) : "";
891
+ const hintsText = formatHints(previous);
308
892
  return `${baseTemplate}
309
893
 
310
894
  ## \u5F53\u524D\u64CD\u4F5C\u4E0A\u4E0B\u6587
@@ -321,7 +905,13 @@ ${historyStr}
321
905
  \u8BE6\u60C5: ${input.aNext.actionDetail}
322
906
 
323
907
  ## \u53EF\u7528\u5B89\u5168 Skill
324
- ${skillContext}
908
+ ${skillsText}
909
+
910
+ ## \u524D\u7F6E\u68C0\u6D4B\u7ED3\u679C
911
+ ${hintsText}
912
+ ${scriptAnalysisText ? `
913
+ ## \u811A\u672C\u5185\u5BB9\u5206\u6790
914
+ ${scriptAnalysisText}` : ""}
325
915
 
326
916
  \u8BF7\u6839\u636E\u4EE5\u4E0A\u4FE1\u606F\uFF0C\u5BF9\u5373\u5C06\u6267\u884C\u7684\u64CD\u4F5C\u8FDB\u884C\u5B89\u5168\u5224\u65AD\u3002\u4F60\u5FC5\u987B\u8F93\u51FA\u4E25\u683C\u7684 JSON \u683C\u5F0F\uFF0C
327
917
  \u4E0D\u8981\u5305\u542B\u4EFB\u4F55\u5176\u4ED6\u6587\u5B57\u3001\u6CE8\u91CA\u6216 markdown \u6807\u8BB0\u3002
@@ -329,52 +919,92 @@ ${skillContext}
329
919
  \u8F93\u51FA\u683C\u5F0F:
330
920
  {"allowed": true/false, "reason": "\u5224\u65AD\u7406\u7531", "riskLevel": "low|medium|high|critical", "riskType": "\u98CE\u9669\u7C7B\u578B", "confidence": 0-100}`;
331
921
  }
922
+ function formatHints(previous) {
923
+ const hints = [];
924
+ if (previous.heuristic) {
925
+ hints.push(
926
+ `\u542F\u53D1\u5F0F\u68C0\u6D4B\u53D1\u73B0\u98CE\u9669 [\u98CE\u9669\u7B49\u7EA7: ${previous.heuristic.riskLevel}]:
927
+ \u7C7B\u578B: ${previous.heuristic.riskType}
928
+ \u539F\u56E0: ${previous.heuristic.reason}`
929
+ );
930
+ }
931
+ if (previous.logic) {
932
+ hints.push(
933
+ `\u903B\u8F91\u89C4\u5219\u68C0\u6D4B\u53D1\u73B0\u98CE\u9669 [\u98CE\u9669\u7B49\u7EA7: ${previous.logic.riskLevel}]:
934
+ \u7C7B\u578B: ${previous.logic.riskType}
935
+ \u539F\u56E0: ${previous.logic.reason}`
936
+ );
937
+ }
938
+ return hints.length > 0 ? hints.join("\n\n") : "\u542F\u53D1\u5F0F\u68C0\u6D4B\u548C\u903B\u8F91\u89C4\u5219\u68C0\u6D4B\u5747\u672A\u53D1\u73B0\u98CE\u9669\u3002";
939
+ }
332
940
  async function analyzeWithLLM(prompt, config) {
333
941
  console.log(`[AgentMoss] LLM analyzing with model: ${config.model}, url: ${config.baseUrl}`);
942
+ const provider = detectProvider(config.baseUrl, config.provider);
943
+ const extraHeaders = getProviderHeaders(provider);
944
+ if (Object.keys(extraHeaders).length > 0) {
945
+ console.log(`[AgentMoss] Provider detected: ${provider}, injecting headers: ${Object.keys(extraHeaders).join(", ")}`);
946
+ }
334
947
  const client = new OpenAI({
335
948
  baseURL: config.baseUrl,
336
949
  apiKey: config.apiKey,
337
- timeout: 3e4
950
+ timeout: (config.timeout || 300) * 1e3,
951
+ defaultHeaders: Object.keys(extraHeaders).length > 0 ? extraHeaders : void 0
338
952
  });
339
- try {
340
- const response = await client.chat.completions.create({
341
- model: config.model,
342
- temperature: config.temperature || 0.1,
343
- max_tokens: config.maxTokens || 1024,
344
- messages: [
345
- { role: "system", content: "\u4F60\u662F\u4E00\u4E2A AI Agent \u5B89\u5168\u5BA1\u8BA1\u4E13\u5BB6\u3002\u4F60\u7684\u4EFB\u52A1\u662F\u5206\u6790\u5373\u5C06\u6267\u884C\u7684\u547D\u4EE4\u662F\u5426\u5B89\u5168\u3002\u4F60\u5FC5\u987B\u53EA\u8F93\u51FA\u7EAF JSON\uFF0C\u4E0D\u8981\u5305\u542B\u4EFB\u4F55 markdown \u6807\u8BB0\u3001\u4EE3\u7801\u5757\u6216\u5176\u4ED6\u6587\u5B57\u3002" },
346
- { role: "user", content: prompt }
347
- ]
348
- });
349
- const content = response.choices[0]?.message?.content || "{}";
350
- console.log(`[AgentMoss] LLM raw response: ${content.substring(0, 300)}`);
351
- let jsonStr = content.trim();
352
- if (jsonStr.startsWith("```")) {
353
- jsonStr = jsonStr.replace(/^```[\w]*\n?/, "").replace(/\n?```$/, "");
953
+ const maxRetries = parseInt(process.env.AGENT_MOSS_LLM_RETRIES || "2", 10);
954
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
955
+ try {
956
+ const response = await client.chat.completions.create({
957
+ model: config.model,
958
+ temperature: config.temperature || 0.1,
959
+ max_tokens: config.maxTokens || 1024,
960
+ messages: [
961
+ { role: "system", content: "\u4F60\u662F\u4E00\u4E2A AI Agent \u5B89\u5168\u5BA1\u8BA1\u4E13\u5BB6\u3002\u4F60\u7684\u4EFB\u52A1\u662F\u5206\u6790\u5373\u5C06\u6267\u884C\u7684\u547D\u4EE4\u662F\u5426\u5B89\u5168\u3002\u4F60\u5FC5\u987B\u53EA\u8F93\u51FA\u7EAF JSON\uFF0C\u4E0D\u8981\u5305\u542B\u4EFB\u4F55 markdown \u6807\u8BB0\u3001\u4EE3\u7801\u5757\u6216\u5176\u4ED6\u6587\u5B57\u3002" },
962
+ { role: "user", content: prompt }
963
+ ]
964
+ });
965
+ const content = response.choices[0]?.message?.content || "{}";
966
+ console.log(`[AgentMoss] LLM raw response (attempt ${attempt + 1}/${maxRetries + 1}): ${content.substring(0, 300)}`);
967
+ let jsonStr = content.trim();
968
+ if (jsonStr.startsWith("```")) {
969
+ jsonStr = jsonStr.replace(/^```[\w]*\n?/, "").replace(/\n?```$/, "");
970
+ }
971
+ const parsed = JSON.parse(jsonStr);
972
+ return {
973
+ allowed: parsed.allowed !== false,
974
+ reason: parsed.reason || "LLM \u5206\u6790\u5B8C\u6210",
975
+ riskLevel: ["low", "medium", "high", "critical"].includes(parsed.riskLevel) ? parsed.riskLevel : "low",
976
+ riskType: parsed.riskType || "",
977
+ confidence: typeof parsed.confidence === "number" ? Math.min(100, Math.max(0, parsed.confidence)) : 70,
978
+ source: "llm",
979
+ violatedLayers: ["3"]
980
+ };
981
+ } catch (err) {
982
+ if (attempt < maxRetries) {
983
+ console.error(`[AgentMoss] LLM call failed (attempt ${attempt + 1}/${maxRetries + 1}), retrying in 1s:`, err.message);
984
+ await new Promise((resolve4) => setTimeout(resolve4, 1e3));
985
+ } else {
986
+ console.error("[AgentMoss] LLM call failed after all retries:", err.message);
987
+ return {
988
+ allowed: false,
989
+ reason: `LLM \u5206\u6790\u5931\u8D25\uFF0C\u56E0\u65E0\u6CD5\u5B8C\u6210\u6DF1\u5EA6\u8BED\u4E49\u5206\u6790\uFF0C\u6309 fail-closed \u539F\u5219\u62D2\u7EDD (${maxRetries + 1} \u6B21\u5C1D\u8BD5): ${err.message}`,
990
+ riskLevel: "medium",
991
+ riskType: "",
992
+ confidence: 60,
993
+ source: "llm_failure",
994
+ violatedLayers: ["3"]
995
+ };
996
+ }
354
997
  }
355
- const parsed = JSON.parse(jsonStr);
356
- return {
357
- allowed: parsed.allowed !== false,
358
- // default to Allow if missing
359
- reason: parsed.reason || "LLM \u5206\u6790\u5B8C\u6210",
360
- riskLevel: ["low", "medium", "high", "critical"].includes(parsed.riskLevel) ? parsed.riskLevel : "low",
361
- riskType: parsed.riskType || "",
362
- confidence: typeof parsed.confidence === "number" ? Math.min(100, Math.max(0, parsed.confidence)) : 70,
363
- source: "llm",
364
- violatedLayers: ["3"]
365
- };
366
- } catch (err) {
367
- console.error("[AgentMoss] LLM call failed:", err.message);
368
- return {
369
- allowed: true,
370
- reason: `LLM \u8C03\u7528\u5931\u8D25: ${err.message}`,
371
- riskLevel: "low",
372
- riskType: "",
373
- confidence: 30,
374
- source: "llm",
375
- violatedLayers: []
376
- };
377
998
  }
999
+ return {
1000
+ allowed: false,
1001
+ reason: "LLM \u5206\u6790\u5F02\u5E38",
1002
+ riskLevel: "medium",
1003
+ riskType: "",
1004
+ confidence: 60,
1005
+ source: "llm_failure",
1006
+ violatedLayers: ["3"]
1007
+ };
378
1008
  }
379
1009
  var llmCache, LLM_CACHE_MAX;
380
1010
  var init_llm_analyzer = __esm({
@@ -384,6 +1014,7 @@ var init_llm_analyzer = __esm({
384
1014
  init_config();
385
1015
  init_skill_loader();
386
1016
  init_template_loader();
1017
+ init_script_analyzer();
387
1018
  llmCache = /* @__PURE__ */ new Map();
388
1019
  LLM_CACHE_MAX = 200;
389
1020
  }
@@ -394,21 +1025,83 @@ var coordinator_exports = {};
394
1025
  __export(coordinator_exports, {
395
1026
  runAnalysis: () => runAnalysis
396
1027
  });
1028
+ function extractFirstCommand(command) {
1029
+ let main2 = command.split("|")[0].trim();
1030
+ for (const sep of ["&&", "||"]) {
1031
+ const idx = main2.indexOf(sep);
1032
+ if (idx !== -1) {
1033
+ main2 = main2.substring(0, idx).trim();
1034
+ break;
1035
+ }
1036
+ }
1037
+ return main2;
1038
+ }
1039
+ function extractRawCommand(detail) {
1040
+ const m = detail.match(/command=([^,]+)/);
1041
+ return m ? m[1].trim() : detail;
1042
+ }
1043
+ function matchesAny(cmd, patterns) {
1044
+ return patterns.some((p) => p.test(cmd));
1045
+ }
397
1046
  async function runAnalysis(input) {
398
- const heuristicResult = runHeuristicCheck(input);
1047
+ const heuristicResult = isHeuristicEnabled() ? runHeuristicCheck(input) : null;
399
1048
  if (heuristicResult && !heuristicResult.allowed) {
400
1049
  return heuristicResult;
401
1050
  }
402
- const logicResult = runLogicCheck(input);
1051
+ const actionType = input.aNext.actionType.toLowerCase();
1052
+ const rawDetail = extractRawCommand(input.aNext.actionDetail);
1053
+ const lowerDetail = rawDetail.toLowerCase();
1054
+ if (FULLY_SAFE_TOOLS.has(actionType)) {
1055
+ return {
1056
+ allowed: true,
1057
+ reason: `\u5B8C\u5168\u5B89\u5168\u5DE5\u5177\uFF0C\u8DF3\u8FC7\u6DF1\u5EA6\u5206\u6790: ${actionType}`,
1058
+ riskLevel: heuristicResult?.riskLevel || "low",
1059
+ riskType: "",
1060
+ confidence: 95,
1061
+ source: "whitelist_bypass",
1062
+ violatedLayers: []
1063
+ };
1064
+ }
1065
+ let skipLLM = READONLY_SENSITIVE_TOOLS.has(actionType);
1066
+ if (actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash") {
1067
+ const firstCmd = extractFirstCommand(lowerDetail);
1068
+ if (matchesAny(firstCmd, FULLY_SAFE_BASH_PATTERNS)) {
1069
+ return {
1070
+ allowed: true,
1071
+ reason: `\u5B89\u5168\u7684\u53EA\u8BFB bash \u547D\u4EE4: ${firstCmd}`,
1072
+ riskLevel: "low",
1073
+ riskType: "",
1074
+ confidence: 95,
1075
+ source: "whitelist_bypass",
1076
+ violatedLayers: []
1077
+ };
1078
+ }
1079
+ if (matchesAny(firstCmd, READONLY_SENSITIVE_BASH_PATTERNS)) {
1080
+ skipLLM = true;
1081
+ }
1082
+ }
1083
+ const logicResult = isLogicRulesEnabled() ? runLogicCheck(input) : null;
403
1084
  if (logicResult && !logicResult.allowed) {
404
1085
  return logicResult;
405
1086
  }
1087
+ if (skipLLM || !isLLMAvailable()) {
1088
+ return {
1089
+ allowed: true,
1090
+ reason: "\u53EA\u8BFB\u64CD\u4F5C\u4E14\u65E0\u903B\u8F91\u89C4\u5219\u8FDD\u89C4\uFF0C\u8DF3\u8FC7 LLM \u5206\u6790",
1091
+ riskLevel: logicResult?.riskLevel || "low",
1092
+ riskType: "",
1093
+ confidence: 85,
1094
+ source: "heuristic_and_logic_only",
1095
+ violatedLayers: []
1096
+ };
1097
+ }
406
1098
  const llmResult = await runLLMAnalysis(input, {
407
1099
  heuristic: heuristicResult,
408
1100
  logic: logicResult
409
1101
  });
410
1102
  return llmResult;
411
1103
  }
1104
+ var FULLY_SAFE_TOOLS, READONLY_SENSITIVE_TOOLS, FULLY_SAFE_BASH_PATTERNS, READONLY_SENSITIVE_BASH_PATTERNS;
412
1105
  var init_coordinator = __esm({
413
1106
  "src/engine/coordinator.ts"() {
414
1107
  "use strict";
@@ -416,6 +1109,59 @@ var init_coordinator = __esm({
416
1109
  init_heuristic();
417
1110
  init_logic_rules();
418
1111
  init_llm_analyzer();
1112
+ init_config();
1113
+ FULLY_SAFE_TOOLS = /* @__PURE__ */ new Set([
1114
+ "ask_user_question",
1115
+ "glob",
1116
+ "list_dir",
1117
+ "ls",
1118
+ "count_text_length",
1119
+ "filemgr-globfiles"
1120
+ ]);
1121
+ READONLY_SENSITIVE_TOOLS = /* @__PURE__ */ new Set([
1122
+ "read",
1123
+ "file_read",
1124
+ "read_file",
1125
+ "head",
1126
+ "tail",
1127
+ "grep",
1128
+ "filemgr-readfile",
1129
+ "filemgr-grepfiles"
1130
+ ]);
1131
+ FULLY_SAFE_BASH_PATTERNS = [
1132
+ /^ls(?:\s|$)/i,
1133
+ /^dir(?:\s|$)/i,
1134
+ /^pwd(?:\s|$)/i,
1135
+ /^which\s+/i,
1136
+ /^whereis\s+/i,
1137
+ /^realpath\s+/i,
1138
+ /^basename\s+/i,
1139
+ /^dirname\s+/i,
1140
+ /^file\s+/i,
1141
+ /^stat\s+/i,
1142
+ /^du(?:\s|$)/i,
1143
+ /^echo\s+/i,
1144
+ /^printf\s+/i,
1145
+ /^type\s+/i,
1146
+ /^command\s+/i
1147
+ ];
1148
+ READONLY_SENSITIVE_BASH_PATTERNS = [
1149
+ /^cat\s+/i,
1150
+ /^head(?:\s|$)/i,
1151
+ /^tail(?:\s|$)/i,
1152
+ /^less(?:\s|$)/i,
1153
+ /^more(?:\s|$)/i,
1154
+ /^wc(?:\s|$)/i,
1155
+ /^grep(?:\s|$)/i,
1156
+ /^find\s+/i,
1157
+ /^ag(?:\s|$)/i,
1158
+ /^rg(?:\s|$)/i,
1159
+ /^awk\s+/i,
1160
+ /^sed\s+/i,
1161
+ /^cut\s+/i,
1162
+ /^sort(?:\s|$)/i,
1163
+ /^uniq(?:\s|$)/i
1164
+ ];
419
1165
  }
420
1166
  });
421
1167
 
@@ -553,7 +1299,7 @@ async function main() {
553
1299
  switch (command) {
554
1300
  case "init": {
555
1301
  const output = args[2] || "input.json";
556
- const fs = await import("fs");
1302
+ const fs2 = await import("fs");
557
1303
  const template = {
558
1304
  session_id: "my-session-001",
559
1305
  prompt_session: "Read /var/log/app.log and analyze errors",
@@ -563,7 +1309,7 @@ async function main() {
563
1309
  os_type: "",
564
1310
  cwd: "/home/user/project"
565
1311
  };
566
- fs.writeFileSync(output, JSON.stringify(template, null, 2), "utf-8");
1312
+ fs2.writeFileSync(output, JSON.stringify(template, null, 2), "utf-8");
567
1313
  console.log(`Template written to ${output}`);
568
1314
  break;
569
1315
  }
@@ -572,11 +1318,11 @@ async function main() {
572
1318
  console.error("Usage: agent-moss analyze <input.json>");
573
1319
  process.exit(1);
574
1320
  }
575
- const fs = await import("fs");
1321
+ const fs2 = await import("fs");
576
1322
  const path2 = await import("path");
577
1323
  const { runAnalysis: runAnalysis2 } = await Promise.resolve().then(() => (init_coordinator(), coordinator_exports));
578
1324
  const filePath = path2.resolve(args[1]);
579
- const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
1325
+ const data = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
580
1326
  const result = await runAnalysis2({
581
1327
  sessionId: data.session_id || "unknown",
582
1328
  promptSession: data.prompt_session || "",