@kenhkl/agent-moss 0.2.0 → 0.3.0

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,35 +157,135 @@ 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
  });
@@ -80,31 +293,60 @@ var init_heuristic = __esm({
80
293
  // src/engine/logic-rules.ts
81
294
  function runLogicCheck(input) {
82
295
  const { actionHistory, aNext, cwd } = input;
83
- const command = aNext.actionDetail;
296
+ const actionDetail = aNext.actionDetail.toLowerCase();
297
+ const actionType = aNext.actionType.toLowerCase();
84
298
  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) {
299
+ const rbwResult = checkReadBeforeWrite(actionType, actionDetail, actionHistory, input.reason, resolvedCwd);
300
+ if (rbwResult) return rbwResult;
301
+ if (input.promptSession && input.promptSession.trim()) {
302
+ const promptLower = input.promptSession.toLowerCase();
303
+ for (const pattern of INTENT_DEVIATION_PATTERNS) {
304
+ const intentMatched = pattern.intentKeywords.some((kw) => promptLower.includes(kw));
305
+ const dangerousMatched = pattern.dangerousActions.some((kw) => actionDetail.includes(kw));
306
+ if (intentMatched && dangerousMatched) {
307
+ return {
308
+ allowed: false,
309
+ reason: pattern.reason,
310
+ riskLevel: "high",
311
+ riskType: "intent_deviation",
312
+ confidence: 85,
313
+ source: "logic_rule",
314
+ violatedLayers: ["2"]
315
+ };
316
+ }
90
317
  }
91
318
  }
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
- };
319
+ const pathResult = checkSensitivePath(actionDetail, resolvedCwd);
320
+ if (pathResult) return pathResult;
321
+ if (actionType !== "file_write" && actionType !== "file_edit") {
322
+ if ((actionDetail.includes("*") || actionDetail.includes("?")) && ["rm ", "rm ", "del ", "del ", "delete", "remove ", "remove ", "\u5220\u9664", "\u79FB\u9664"].some((kw) => actionDetail.includes(kw))) {
323
+ return {
324
+ allowed: false,
325
+ reason: "\u68C0\u6D4B\u5230\u901A\u914D\u7B26\u7ED3\u5408\u5220\u9664\u64CD\u4F5C\uFF0C\u53EF\u80FD\u9020\u6210\u6279\u91CF\u8BEF\u5220",
326
+ riskLevel: "high",
327
+ riskType: "script_execution",
328
+ confidence: 90,
329
+ source: "logic_rule",
330
+ violatedLayers: ["2"]
331
+ };
332
+ }
333
+ if (/>\s*\/etc\//.test(actionDetail) || />\s*\/boot\//.test(actionDetail) || />\s*\/proc\//.test(actionDetail)) {
334
+ return {
335
+ allowed: false,
336
+ reason: "\u68C0\u6D4B\u5230\u91CD\u5B9A\u5411\u8986\u76D6\u5199\u5165\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55",
337
+ riskLevel: "critical",
338
+ riskType: "file_access",
339
+ confidence: 95,
340
+ source: "logic_rule",
341
+ violatedLayers: ["2"]
342
+ };
343
+ }
102
344
  }
103
- if (command.includes("sudo") || command.includes("su -") || command.includes("chmod u+s") || command.includes("setuid")) {
345
+ if (actionDetail.includes("sudo") || actionDetail.includes("su -") || actionDetail.includes("chmod u+s") || actionDetail.includes("setuid")) {
104
346
  const adminContext = actionHistory.some(
105
- (h) => h.actionDetail.includes("sudo") || h.actionDetail.includes("apt") || h.actionDetail.includes("systemctl")
347
+ (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
348
  );
107
- if (!adminContext && actionHistory.length === 0) {
349
+ if (!adminContext) {
108
350
  return {
109
351
  allowed: false,
110
352
  reason: "\u68C0\u6D4B\u5230\u63D0\u6743\u64CD\u4F5C\uFF0C\u672A\u53D1\u73B0\u5408\u6CD5\u7BA1\u7406\u4E0A\u4E0B\u6587",
@@ -116,8 +358,8 @@ function runLogicCheck(input) {
116
358
  };
117
359
  }
118
360
  }
119
- if (command.includes("ssh") || command.includes("scp") || command.includes("rsync")) {
120
- const targetHost = extractRemoteHost(command);
361
+ if (/(?:ssh|scp|rsync)\s+/.test(actionDetail)) {
362
+ const targetHost = extractRemoteHost(actionDetail);
121
363
  if (targetHost && !isLocalHost(targetHost)) {
122
364
  return {
123
365
  allowed: false,
@@ -132,18 +374,97 @@ function runLogicCheck(input) {
132
374
  }
133
375
  return null;
134
376
  }
135
- function containsSensitivePath(command, cwd) {
377
+ function extractFilePaths(text) {
378
+ const paths = [];
379
+ const unixPaths = text.match(/(?:^|\s)(\/[^\s;|&><'"]+)/g);
380
+ if (unixPaths) paths.push(...unixPaths.map((p) => p.trim()));
381
+ const quotedPaths = text.match(/["']\/([^"']+)["']/g);
382
+ if (quotedPaths) paths.push(...quotedPaths.map((p) => p.replace(/["']/g, "")));
383
+ return [...new Set(paths)];
384
+ }
385
+ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, cwd) {
386
+ if (NON_WRITE_TOOLS.has(actionType)) return null;
387
+ const isWrite = WRITE_KEYWORDS.some((kw) => actionType.includes(kw) || actionDetail.includes(kw));
388
+ if (!isWrite) return null;
389
+ const targetPaths = extractFilePaths(actionDetail);
390
+ if (targetPaths.length === 0) return null;
391
+ const historyReadPaths = /* @__PURE__ */ new Set();
392
+ for (const action of actionHistory) {
393
+ const histDetail = action.actionDetail.toLowerCase();
394
+ const histType = action.actionType.toLowerCase();
395
+ const isRead = READ_KEYWORDS.some((kw) => histType.includes(kw) || histDetail.includes(kw));
396
+ if (isRead) {
397
+ for (const p of extractFilePaths(histDetail)) {
398
+ historyReadPaths.add(p);
399
+ }
400
+ }
401
+ }
402
+ const unreadPaths = [];
403
+ for (const path2 of targetPaths) {
404
+ const hasRead = [...historyReadPaths].some(
405
+ (rp) => path2 === rp || path2.startsWith(rp) || rp.startsWith(path2)
406
+ );
407
+ if (!hasRead) {
408
+ const isCreate = CREATE_KEYWORDS.some((kw) => reason.toLowerCase().includes(kw) || actionDetail.includes(kw));
409
+ if (!isCreate) {
410
+ unreadPaths.push(path2);
411
+ }
412
+ }
413
+ }
414
+ if (unreadPaths.length > 0) {
415
+ return {
416
+ allowed: false,
417
+ 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`,
418
+ riskLevel: "high",
419
+ riskType: "read_before_write",
420
+ confidence: 85,
421
+ source: "logic_rule",
422
+ violatedLayers: ["2"]
423
+ };
424
+ }
425
+ return null;
426
+ }
427
+ function checkSensitivePath(command, cwd) {
136
428
  const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
137
- for (const p of ROOT_SENSITIVE_PATHS) {
138
- if (command.includes(p)) return true;
429
+ for (const sp of ROOT_SENSITIVE_PATHS) {
430
+ if (command.includes(sp.path.toLowerCase())) {
431
+ return {
432
+ allowed: false,
433
+ reason: `\u5C1D\u8BD5\u8BBF\u95EE\u7CFB\u7EDF\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
434
+ riskLevel: sp.riskLevel,
435
+ riskType: "sensitive_path_access",
436
+ confidence: 90,
437
+ source: "logic_rule",
438
+ violatedLayers: ["2"]
439
+ };
440
+ }
139
441
  }
140
- for (const p of HOME_SENSITIVE_PATHS) {
141
- if (command.includes(p) || command.includes(`${expandedCwd}/${p}`)) return true;
442
+ for (const sp of HOME_SENSITIVE_PATHS) {
443
+ const expanded = `${expandedCwd}/${sp.path}`;
444
+ if (command.includes(sp.path.toLowerCase()) || command.includes(expanded.toLowerCase())) {
445
+ return {
446
+ allowed: false,
447
+ reason: `\u5C1D\u8BD5\u8BBF\u95EE\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
448
+ riskLevel: sp.riskLevel,
449
+ riskType: "sensitive_path_access",
450
+ confidence: 90,
451
+ source: "logic_rule",
452
+ violatedLayers: ["2"]
453
+ };
454
+ }
142
455
  }
143
456
  if ((command.includes("rm ") || command.includes("unlink")) && (command.includes("/etc/") || command.includes("/root/") || command.includes("/var/log/"))) {
144
- return true;
457
+ return {
458
+ allowed: false,
459
+ reason: "\u68C0\u6D4B\u5230\u5220\u9664\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55\u5185\u5BB9",
460
+ riskLevel: "critical",
461
+ riskType: "sensitive_path_access",
462
+ confidence: 90,
463
+ source: "logic_rule",
464
+ violatedLayers: ["2"]
465
+ };
145
466
  }
146
- return false;
467
+ return null;
147
468
  }
148
469
  function extractRemoteHost(command) {
149
470
  const m = command.match(/(?:ssh|scp|rsync)\s+(?:-[\w]+\s+)*(\S+@\S+|\b(?!-)[\w.-]+\b)/);
@@ -153,51 +474,51 @@ function isLocalHost(host) {
153
474
  const localNames = ["localhost", "127.0.0.1", "::1", process.env.HOSTNAME || "", "0.0.0.0"];
154
475
  return localNames.includes(host.replace(/.*@/, ""));
155
476
  }
156
- var ROOT_SENSITIVE_PATHS, HOME_SENSITIVE_PATHS;
477
+ var ROOT_SENSITIVE_PATHS, HOME_SENSITIVE_PATHS, NON_WRITE_TOOLS, WRITE_KEYWORDS, READ_KEYWORDS, CREATE_KEYWORDS, INTENT_DEVIATION_PATTERNS;
157
478
  var init_logic_rules = __esm({
158
479
  "src/engine/logic-rules.ts"() {
159
480
  "use strict";
160
481
  init_esm_shims();
161
482
  ROOT_SENSITIVE_PATHS = [
162
- "/etc/shadow",
163
- "/etc/passwd",
164
- "/etc/sudoers",
165
- "/root/.ssh",
166
- "/root/.bashrc",
167
- "/boot",
168
- "/sys",
169
- "/proc"
483
+ { path: "/etc/shadow", riskLevel: "critical", desc: "\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6" },
484
+ { path: "/etc/passwd", riskLevel: "high", desc: "\u7CFB\u7EDF\u7528\u6237\u6587\u4EF6" },
485
+ { path: "/etc/sudoers", riskLevel: "critical", desc: "sudo \u914D\u7F6E" },
486
+ { path: "/etc/hosts", riskLevel: "medium", desc: "DNS \u89E3\u6790\u914D\u7F6E" },
487
+ { path: "/etc/crontab", riskLevel: "high", desc: "\u7CFB\u7EDF\u5B9A\u65F6\u4EFB\u52A1" },
488
+ { path: "/etc/systemd/", riskLevel: "high", desc: "systemd \u670D\u52A1\u914D\u7F6E" },
489
+ { path: "/etc/ssh/sshd_config", riskLevel: "high", desc: "SSH \u670D\u52A1\u914D\u7F6E" },
490
+ { path: "/root/.ssh", riskLevel: "critical", desc: "root SSH \u76EE\u5F55" },
491
+ { path: "/root/.bashrc", riskLevel: "high", desc: "root shell \u914D\u7F6E" },
492
+ { path: "/boot/", riskLevel: "critical", desc: "\u542F\u52A8\u5F15\u5BFC\u76EE\u5F55" },
493
+ { path: "/proc/sys/", riskLevel: "high", desc: "\u5185\u6838\u53C2\u6570" },
494
+ { path: "/sys/", riskLevel: "high", desc: "sysfs \u5185\u6838\u63A5\u53E3" },
495
+ { path: "/dev/mem", riskLevel: "critical", desc: "\u7269\u7406\u5185\u5B58\u8BBF\u95EE\u8BBE\u5907" },
496
+ { path: "/dev/kmsg", riskLevel: "high", desc: "\u5185\u6838\u6D88\u606F\u7F13\u51B2\u533A" }
170
497
  ];
171
498
  HOME_SENSITIVE_PATHS = [
172
- ".ssh/id_rsa",
173
- ".ssh/authorized_keys",
174
- ".aws/credentials",
175
- ".gitconfig",
176
- ".gnupg"
499
+ { path: ".ssh/id_rsa", riskLevel: "critical", desc: "SSH \u79C1\u94A5" },
500
+ { path: ".ssh/id_ed25519", riskLevel: "critical", desc: "SSH \u79C1\u94A5 (ed25519)" },
501
+ { path: ".ssh/authorized_keys", riskLevel: "high", desc: "SSH \u6388\u6743\u5BC6\u94A5" },
502
+ { path: ".aws/credentials", riskLevel: "critical", desc: "AWS \u51ED\u8BC1" },
503
+ { path: ".gitconfig", riskLevel: "medium", desc: "Git \u914D\u7F6E" },
504
+ { path: ".gnupg", riskLevel: "high", desc: "GPG \u5BC6\u94A5\u76EE\u5F55" }
505
+ ];
506
+ NON_WRITE_TOOLS = /* @__PURE__ */ new Set(["ask_user_question", "glob", "grep", "list_dir", "count_text_length", "search"]);
507
+ 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"];
508
+ 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"];
509
+ CREATE_KEYWORDS = ["\u521B\u5EFA", "\u65B0\u5EFA", "create", "new file", "touch"];
510
+ INTENT_DEVIATION_PATTERNS = [
511
+ {
512
+ intentKeywords: ["\u8BFB\u53D6", "\u67E5\u770B", "\u5206\u6790", "read", "analyze", "view", "list", "\u5217\u51FA"],
513
+ dangerousActions: ["\u5220\u9664", "remove", "delete", "rm ", "\u683C\u5F0F\u5316", "format"],
514
+ reason: "\u539F\u59CB\u610F\u56FE\u4E3A\u8BFB\u53D6/\u5206\u6790\uFF0C\u4F46\u6267\u884C\u4E86\u5220\u9664\u64CD\u4F5C"
515
+ },
516
+ {
517
+ intentKeywords: ["\u67E5\u8BE2", "\u641C\u7D22", "search", "query", "find"],
518
+ dangerousActions: ["\u4FEE\u6539", "update", "write", "\u5199\u5165", "alter"],
519
+ reason: "\u539F\u59CB\u610F\u56FE\u4E3A\u67E5\u8BE2/\u641C\u7D22\uFF0C\u4F46\u6267\u884C\u4E86\u4FEE\u6539\u64CD\u4F5C"
520
+ }
177
521
  ];
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
522
  }
202
523
  });
203
524
 
@@ -205,23 +526,71 @@ var init_config = __esm({
205
526
  import { readFileSync, readdirSync, existsSync } from "fs";
206
527
  import { resolve, dirname } from "path";
207
528
  import { fileURLToPath as fileURLToPath2 } from "url";
208
- function loadSkills() {
529
+ function loadAllSkills() {
209
530
  if (cachedSkills) return cachedSkills;
531
+ cachedSkills = {};
210
532
  if (!existsSync(SKILLS_DIR)) {
211
533
  console.warn("[AgentMoss] Skills directory not found:", SKILLS_DIR);
212
- cachedSkills = [];
213
534
  return cachedSkills;
214
535
  }
215
536
  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`);
537
+ for (const f of files) {
538
+ const name = f.replace(".md", "");
539
+ cachedSkills[name] = readFileSync(resolve(SKILLS_DIR, f), "utf-8");
540
+ }
541
+ console.log(`[AgentMoss] Loaded ${Object.keys(cachedSkills).length} skills`);
222
542
  return cachedSkills;
223
543
  }
224
- var __filename2, __dirname2, SKILLS_DIR, cachedSkills;
544
+ function matchSkills(actionType, actionDetail, reason, maxSkills = 3) {
545
+ const skills = loadAllSkills();
546
+ const combined = `${actionType} ${actionDetail} ${reason}`.toLowerCase();
547
+ const actionDetailLower = actionDetail.toLowerCase();
548
+ const actionTypeLower = actionType.toLowerCase();
549
+ const reasonLower = reason.toLowerCase();
550
+ const scores = {};
551
+ for (const [skillName, keywords] of Object.entries(SKILL_KEYWORD_MAP)) {
552
+ let score = 0;
553
+ for (const kw of keywords) {
554
+ if (combined.includes(kw)) {
555
+ if (kw === actionTypeLower) score += 2;
556
+ else if (actionDetailLower.includes(kw)) score += 1.5;
557
+ else if (reasonLower.includes(kw)) score += 1;
558
+ else score += 0.5;
559
+ }
560
+ }
561
+ if (score > 0) scores[skillName] = score;
562
+ }
563
+ const sorted = Object.entries(scores).sort((a, b) => b[1] - a[1]);
564
+ const top = sorted.slice(0, maxSkills);
565
+ const maxScore = top[0]?.[1] || 1;
566
+ const results = [];
567
+ for (const [name, score] of top) {
568
+ const content = skills[name];
569
+ if (content) {
570
+ results.push({
571
+ skillName: name,
572
+ relevance: Math.min(score / maxScore, 1),
573
+ skillContent: content
574
+ });
575
+ }
576
+ }
577
+ if (results.length === 0) {
578
+ const fallback = skills["general_tool_risk_guard"];
579
+ if (fallback) {
580
+ results.push({ skillName: "general_tool_risk_guard", relevance: 0.3, skillContent: fallback });
581
+ }
582
+ }
583
+ return results;
584
+ }
585
+ function formatSkillsForPrompt(skills) {
586
+ if (skills.length === 0) return "(\u672A\u5339\u914D\u5230\u7279\u5B9A\u5B89\u5168\u68C0\u6D4B\u89C4\u5219)";
587
+ return skills.map(
588
+ (s) => `### \u5B89\u5168\u68C0\u6D4B\u89C4\u5219: ${s.skillName} (\u76F8\u5173\u5EA6: ${Math.round(s.relevance * 100)}%)
589
+
590
+ ${s.skillContent}`
591
+ ).join("\n\n---\n\n");
592
+ }
593
+ var __filename2, __dirname2, SKILLS_DIR, SKILL_KEYWORD_MAP, cachedSkills;
225
594
  var init_skill_loader = __esm({
226
595
  "src/engine/skill-loader.ts"() {
227
596
  "use strict";
@@ -229,6 +598,20 @@ var init_skill_loader = __esm({
229
598
  __filename2 = fileURLToPath2(import.meta.url);
230
599
  __dirname2 = dirname(__filename2);
231
600
  SKILLS_DIR = resolve(__dirname2, "../../agent_moss/skills");
601
+ SKILL_KEYWORD_MAP = {
602
+ "file_access_guard": ["file", "\u6587\u4EF6", "path", "\u8DEF\u5F84", "read", "write", "cat", "open", "access", "\u8BBF\u95EE", "\u8BFB\u5199", "copy", "cp", "mv", "move"],
603
+ "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"],
604
+ "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"],
605
+ "browser_web_access_guard": ["browser", "\u6D4F\u89C8\u5668", "web", "\u7F51\u9875", "url", "navigate", "\u6D4F\u89C8", "scrape", "crawl", "\u722C\u53D6", "click", "screenshot"],
606
+ "email_operation_guard": ["email", "\u90AE\u4EF6", "mail", "smtp", "send_email", "\u53D1\u9001\u90AE\u4EF6", "inbox", "\u6536\u4EF6\u7BB1", "\u9644\u4EF6", "attachment"],
607
+ "general_tool_risk_guard": ["tool", "\u5DE5\u5177", "api", "invoke", "\u8C03\u7528", "function", "\u51FD\u6570"],
608
+ "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"],
609
+ "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"],
610
+ "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"],
611
+ "skill_installation_guard": ["skill", "plugin", "\u63D2\u4EF6", "extension", "\u6269\u5C55", "install skill", "\u5B89\u88C5\u63D2\u4EF6", "\u5B89\u88C5\u6280\u80FD", "marketplace", "\u5546\u5E97"],
612
+ "supply_chain_guard": ["dependency", "\u4F9D\u8D56", "package", "\u5305", "npm install", "pip install", "requirements", "import", "library", "\u5E93", "registry", "\u4ED3\u5E93", "supply", "\u4F9B\u5E94\u94FE", "typosquatting"],
613
+ "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"]
614
+ };
232
615
  cachedSkills = null;
233
616
  }
234
617
  });
@@ -267,6 +650,199 @@ var init_template_loader = __esm({
267
650
  }
268
651
  });
269
652
 
653
+ // src/engine/script-analyzer.ts
654
+ import { readFileSync as readFileSync3 } from "fs";
655
+ import { resolve as resolve3, join } from "path";
656
+ function emptyResult() {
657
+ return { scriptPath: "", scriptContent: "", matchedKeywords: [], lineCount: 0, isSuspicious: false, error: "", riskLevel: "", riskReason: "" };
658
+ }
659
+ function extractScriptPath(command) {
660
+ for (const pattern of SCRIPT_EXEC_PATTERNS) {
661
+ const m = command.match(pattern);
662
+ if (m) return m[1];
663
+ }
664
+ return null;
665
+ }
666
+ function scanScriptForKeywords(content) {
667
+ const matched = [];
668
+ for (const { name, pattern } of SUSPICIOUS_PATTERNS) {
669
+ if (pattern.test(content)) matched.push(name);
670
+ }
671
+ return matched;
672
+ }
673
+ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
674
+ if (matchedKeywords.length === 0) return { level: "", reason: "" };
675
+ const keywordSet = new Set(matchedKeywords);
676
+ for (const rule of KEYWORD_COMBINATION_RISKS) {
677
+ const hitGroup1 = [...rule.group1].some((k) => keywordSet.has(k));
678
+ const hitGroup2 = rule.group2.size === 0 ? true : [...rule.group2].some((k) => keywordSet.has(k));
679
+ if (hitGroup1 && hitGroup2) return { level: rule.level, reason: rule.reason };
680
+ }
681
+ const networkPostKeywords = /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]);
682
+ const hasNetworkPost = [...networkPostKeywords].some((k) => keywordSet.has(k));
683
+ if (hasNetworkPost) {
684
+ const envPatterns = ["$env", "$(env)", "$USER", "$(hostname)", "$(pwd)", "$HOME"];
685
+ const contentLower = scriptContent.toLowerCase();
686
+ for (const p of envPatterns) {
687
+ if (contentLower.includes(p.toLowerCase())) {
688
+ return {
689
+ level: "critical",
690
+ 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"
691
+ };
692
+ }
693
+ }
694
+ }
695
+ let bestLevel = "";
696
+ let bestReason = "";
697
+ for (const kw of matchedKeywords) {
698
+ const risk = SINGLE_KEYWORD_RISKS[kw];
699
+ if (risk && (LEVEL_ORDER[risk.level] || 0) > (LEVEL_ORDER[bestLevel] || 0)) {
700
+ bestLevel = risk.level;
701
+ bestReason = risk.reason;
702
+ }
703
+ }
704
+ return { level: bestLevel, reason: bestReason };
705
+ }
706
+ function readScriptContent(scriptPath, maxLines = 500) {
707
+ try {
708
+ const expandedPath = scriptPath.startsWith("~") ? join(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve3(scriptPath);
709
+ const raw = readFileSync3(expandedPath, "utf-8");
710
+ const lines = raw.split("\n").slice(0, maxLines);
711
+ return { content: lines.join("\n"), lineCount: lines.length, error: "" };
712
+ } catch (e) {
713
+ const msg = e instanceof Error ? e.message : String(e);
714
+ return { content: "", lineCount: 0, error: `\u8BFB\u53D6\u811A\u672C\u5931\u8D25: ${msg}` };
715
+ }
716
+ }
717
+ function analyzeScriptContent(actionType, actionDetail, maxLines = 500) {
718
+ if (actionType.toLowerCase() !== "bash") return emptyResult();
719
+ const scriptPath = extractScriptPath(actionDetail);
720
+ if (!scriptPath) return emptyResult();
721
+ const { content, lineCount, error } = readScriptContent(scriptPath, maxLines);
722
+ if (error) return { ...emptyResult(), scriptPath, error };
723
+ const matchedKeywords = scanScriptForKeywords(content);
724
+ const isSuspicious = matchedKeywords.length > 0;
725
+ let riskLevel = "";
726
+ let riskReason = "";
727
+ if (isSuspicious) {
728
+ const risk = assessKeywordCombinationRisk(matchedKeywords, content);
729
+ riskLevel = risk.level;
730
+ riskReason = risk.reason;
731
+ }
732
+ return { scriptPath, scriptContent: content, matchedKeywords, lineCount, isSuspicious, error: "", riskLevel, riskReason };
733
+ }
734
+ function formatScriptAnalysisForPrompt(result) {
735
+ if (!result.scriptPath) return "";
736
+ if (result.error) return `\u811A\u672C\u5185\u5BB9\u5206\u6790\u5931\u8D25: ${result.error}`;
737
+ if (!result.isSuspicious) return "";
738
+ const keywordsStr = result.matchedKeywords.join(", ");
739
+ let riskHint = "";
740
+ if (result.riskLevel && result.riskReason) {
741
+ riskHint = `
742
+
743
+ \u98CE\u9669\u8BC4\u4F30: ${result.riskLevel.toUpperCase()} \u7EA7\u522B
744
+ \u98CE\u9669\u539F\u56E0: ${result.riskReason}
745
+ \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`;
746
+ }
747
+ return `\u68C0\u6D4B\u5230\u811A\u672C\u6267\u884C\uFF0C\u4E14\u811A\u672C\u5185\u5BB9\u5305\u542B\u53EF\u7591\u5173\u952E\u8BCD:
748
+ \u811A\u672C\u8DEF\u5F84: ${result.scriptPath}
749
+ \u53EF\u7591\u5173\u952E\u8BCD: ${keywordsStr}
750
+ \u811A\u672C\u884C\u6570: ${result.lineCount}${riskHint}
751
+
752
+ ### \u811A\u672C\u5185\u5BB9 ###
753
+ \`\`\`
754
+ ${result.scriptContent}
755
+ \`\`\`
756
+ `;
757
+ }
758
+ var SUSPICIOUS_PATTERNS, SCRIPT_EXEC_PATTERNS, KEYWORD_COMBINATION_RISKS, SINGLE_KEYWORD_RISKS, LEVEL_ORDER;
759
+ var init_script_analyzer = __esm({
760
+ "src/engine/script-analyzer.ts"() {
761
+ "use strict";
762
+ init_esm_shims();
763
+ SUSPICIOUS_PATTERNS = [
764
+ // Network exfiltration (curl/wget POST in scripts is almost never legitimate)
765
+ { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
766
+ { name: "curl --data", pattern: /curl\s+(.*\s)?--data/i },
767
+ { name: "curl -d", pattern: /curl\s+(.*\s)?-d\s/i },
768
+ { name: "wget --post-data", pattern: /wget\s+(.*\s)?--post-data/i },
769
+ { name: "wget --post-file", pattern: /wget\s+(.*\s)?--post-file/i },
770
+ // Reverse shell
771
+ { name: "nc -e", pattern: /\bnc\s+.*-e\b/ },
772
+ { name: "ncat -e", pattern: /\bncat\s+.*-e\b/ },
773
+ { name: "/dev/tcp", pattern: /\/dev\/tcp\// },
774
+ { name: "/dev/udp", pattern: /\/dev\/udp\// },
775
+ // Base64 obfuscation
776
+ { name: "$(base64", pattern: /\$\(\s*base64/ },
777
+ { name: "`base64", pattern: /`\s*base64/ },
778
+ { name: "base64 decode pipe", pattern: /base64\s+(-d|--decode)\s*\|/ },
779
+ // Sensitive file access
780
+ { name: "/etc/shadow", pattern: /\/etc\/shadow/ },
781
+ { name: "id_rsa", pattern: /\bid_rsa\b/ },
782
+ { name: "id_ed25519", pattern: /\bid_ed25519\b/ },
783
+ // Persistence backdoor
784
+ { name: "crontab install", pattern: /crontab\s+-/ },
785
+ { name: "authorized_keys", pattern: /authorized_keys/ },
786
+ { name: "ssh no host key check", pattern: /ssh\s+.*StrictHostKeyChecking\s*=\s*no/ }
787
+ ];
788
+ SCRIPT_EXEC_PATTERNS = [
789
+ /bash\s+(\S+\.sh)/,
790
+ /sh\s+(\S+\.sh)/,
791
+ /\.\/(\S+\.sh)/,
792
+ /source\s+(\S+\.sh)/,
793
+ /\.\s+(\S+\.sh)/,
794
+ /zsh\s+(\S+\.sh)/
795
+ ];
796
+ KEYWORD_COMBINATION_RISKS = [
797
+ {
798
+ group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
799
+ group2: /* @__PURE__ */ new Set(["$(base64", "`base64", "base64 decode pipe"]),
800
+ level: "critical",
801
+ 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"
802
+ },
803
+ {
804
+ group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
805
+ group2: /* @__PURE__ */ new Set(["/etc/shadow", "id_rsa", "id_ed25519"]),
806
+ level: "critical",
807
+ 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"
808
+ },
809
+ {
810
+ group1: /* @__PURE__ */ new Set(["nc -e", "ncat -e", "/dev/tcp", "/dev/udp"]),
811
+ group2: /* @__PURE__ */ new Set(),
812
+ level: "high",
813
+ reason: "\u68C0\u6D4B\u5230\u53CD\u5F39 Shell \u7279\u5F81\uFF0C\u653B\u51FB\u8005\u53EF\u80FD\u5EFA\u7ACB\u8FDC\u7A0B\u63A7\u5236\u901A\u9053\u3002"
814
+ },
815
+ {
816
+ group1: /* @__PURE__ */ new Set(["crontab install", "authorized_keys", "ssh no host key check"]),
817
+ group2: /* @__PURE__ */ new Set(),
818
+ level: "high",
819
+ 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"
820
+ }
821
+ ];
822
+ SINGLE_KEYWORD_RISKS = {
823
+ "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" },
824
+ "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" },
825
+ "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" },
826
+ "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" },
827
+ "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" },
828
+ "nc -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 nc -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
829
+ "ncat -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 ncat -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
830
+ "/dev/tcp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/tcp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
831
+ "/dev/udp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/udp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
832
+ "$(base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
833
+ "`base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
834
+ "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" },
835
+ "/etc/shadow": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6\u3002" },
836
+ "id_rsa": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
837
+ "id_ed25519": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
838
+ "crontab install": { level: "high", reason: "\u68C0\u6D4B\u5230\u4FEE\u6539\u5B9A\u65F6\u4EFB\u52A1\uFF0C\u53EF\u80FD\u662F\u6301\u4E45\u5316\u540E\u95E8\u3002" },
839
+ "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" },
840
+ "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" }
841
+ };
842
+ LEVEL_ORDER = { low: 1, medium: 2, high: 3, critical: 4 };
843
+ }
844
+ });
845
+
270
846
  // src/engine/llm-analyzer.ts
271
847
  import OpenAI from "openai";
272
848
  async function runLLMAnalysis(input, previous) {
@@ -283,15 +859,17 @@ async function runLLMAnalysis(input, previous) {
283
859
  };
284
860
  }
285
861
  const config = getLLMConfig();
286
- const cacheKey = `${input.aNext.actionType}:${input.aNext.actionDetail}`;
862
+ const rawDetail = input.aNext.actionDetail.replace(/^terminal-\w+:\s*command=([^,]+).*/, "$1").trim();
863
+ const cacheKey = `${input.aNext.actionType}:${rawDetail || input.aNext.actionDetail}`;
287
864
  const cached = llmCache.get(cacheKey);
288
865
  if (cached) {
289
866
  console.log("[AgentMoss] LLM cache hit");
290
867
  return cached;
291
868
  }
292
- const skills = loadSkills();
869
+ const matchedSkills = matchSkills(input.aNext.actionType, input.aNext.actionDetail, input.reason);
293
870
  const templates = loadTemplates();
294
- const prompt = buildPrompt(input, previous, skills, templates);
871
+ const scriptAnalysis = analyzeScriptContent(input.aNext.actionType, input.aNext.actionDetail);
872
+ const prompt = buildPrompt(input, previous, matchedSkills, templates, scriptAnalysis);
295
873
  const result = await analyzeWithLLM(prompt, config);
296
874
  if (llmCache.size >= LLM_CACHE_MAX) {
297
875
  const firstKey = llmCache.keys().next().value;
@@ -300,11 +878,13 @@ async function runLLMAnalysis(input, previous) {
300
878
  llmCache.set(cacheKey, result);
301
879
  return result;
302
880
  }
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)";
881
+ function buildPrompt(input, previous, matchedSkills, templates, scriptAnalysis) {
882
+ const skillsText = formatSkillsForPrompt(matchedSkills);
883
+ const historyStr = input.actionHistory.map((h, i) => `${i + 1}. ${h.actionType}: ${h.actionDetail}`).join("\n") || "(\u65E0\u5386\u53F2\u64CD\u4F5C)";
306
884
  const templateName = previous.heuristic || previous.logic ? "prompt2" : "prompt1";
307
885
  const baseTemplate = templates[templateName] || templates["prompt1"] || "";
886
+ const scriptAnalysisText = scriptAnalysis ? formatScriptAnalysisForPrompt(scriptAnalysis) : "";
887
+ const hintsText = formatHints(previous);
308
888
  return `${baseTemplate}
309
889
 
310
890
  ## \u5F53\u524D\u64CD\u4F5C\u4E0A\u4E0B\u6587
@@ -321,7 +901,13 @@ ${historyStr}
321
901
  \u8BE6\u60C5: ${input.aNext.actionDetail}
322
902
 
323
903
  ## \u53EF\u7528\u5B89\u5168 Skill
324
- ${skillContext}
904
+ ${skillsText}
905
+
906
+ ## \u524D\u7F6E\u68C0\u6D4B\u7ED3\u679C
907
+ ${hintsText}
908
+ ${scriptAnalysisText ? `
909
+ ## \u811A\u672C\u5185\u5BB9\u5206\u6790
910
+ ${scriptAnalysisText}` : ""}
325
911
 
326
912
  \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
913
  \u4E0D\u8981\u5305\u542B\u4EFB\u4F55\u5176\u4ED6\u6587\u5B57\u3001\u6CE8\u91CA\u6216 markdown \u6807\u8BB0\u3002
@@ -329,52 +915,92 @@ ${skillContext}
329
915
  \u8F93\u51FA\u683C\u5F0F:
330
916
  {"allowed": true/false, "reason": "\u5224\u65AD\u7406\u7531", "riskLevel": "low|medium|high|critical", "riskType": "\u98CE\u9669\u7C7B\u578B", "confidence": 0-100}`;
331
917
  }
918
+ function formatHints(previous) {
919
+ const hints = [];
920
+ if (previous.heuristic) {
921
+ hints.push(
922
+ `\u542F\u53D1\u5F0F\u68C0\u6D4B\u53D1\u73B0\u98CE\u9669 [\u98CE\u9669\u7B49\u7EA7: ${previous.heuristic.riskLevel}]:
923
+ \u7C7B\u578B: ${previous.heuristic.riskType}
924
+ \u539F\u56E0: ${previous.heuristic.reason}`
925
+ );
926
+ }
927
+ if (previous.logic) {
928
+ hints.push(
929
+ `\u903B\u8F91\u89C4\u5219\u68C0\u6D4B\u53D1\u73B0\u98CE\u9669 [\u98CE\u9669\u7B49\u7EA7: ${previous.logic.riskLevel}]:
930
+ \u7C7B\u578B: ${previous.logic.riskType}
931
+ \u539F\u56E0: ${previous.logic.reason}`
932
+ );
933
+ }
934
+ 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";
935
+ }
332
936
  async function analyzeWithLLM(prompt, config) {
333
937
  console.log(`[AgentMoss] LLM analyzing with model: ${config.model}, url: ${config.baseUrl}`);
938
+ const provider = detectProvider(config.baseUrl, config.provider);
939
+ const extraHeaders = getProviderHeaders(provider);
940
+ if (Object.keys(extraHeaders).length > 0) {
941
+ console.log(`[AgentMoss] Provider detected: ${provider}, injecting headers: ${Object.keys(extraHeaders).join(", ")}`);
942
+ }
334
943
  const client = new OpenAI({
335
944
  baseURL: config.baseUrl,
336
945
  apiKey: config.apiKey,
337
- timeout: 3e4
946
+ timeout: (config.timeout || 300) * 1e3,
947
+ defaultHeaders: Object.keys(extraHeaders).length > 0 ? extraHeaders : void 0
338
948
  });
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?```$/, "");
949
+ const maxRetries = parseInt(process.env.AGENT_MOSS_LLM_RETRIES || "2", 10);
950
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
951
+ try {
952
+ const response = await client.chat.completions.create({
953
+ model: config.model,
954
+ temperature: config.temperature || 0.1,
955
+ max_tokens: config.maxTokens || 1024,
956
+ messages: [
957
+ { 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" },
958
+ { role: "user", content: prompt }
959
+ ]
960
+ });
961
+ const content = response.choices[0]?.message?.content || "{}";
962
+ console.log(`[AgentMoss] LLM raw response (attempt ${attempt + 1}/${maxRetries + 1}): ${content.substring(0, 300)}`);
963
+ let jsonStr = content.trim();
964
+ if (jsonStr.startsWith("```")) {
965
+ jsonStr = jsonStr.replace(/^```[\w]*\n?/, "").replace(/\n?```$/, "");
966
+ }
967
+ const parsed = JSON.parse(jsonStr);
968
+ return {
969
+ allowed: parsed.allowed !== false,
970
+ reason: parsed.reason || "LLM \u5206\u6790\u5B8C\u6210",
971
+ riskLevel: ["low", "medium", "high", "critical"].includes(parsed.riskLevel) ? parsed.riskLevel : "low",
972
+ riskType: parsed.riskType || "",
973
+ confidence: typeof parsed.confidence === "number" ? Math.min(100, Math.max(0, parsed.confidence)) : 70,
974
+ source: "llm",
975
+ violatedLayers: ["3"]
976
+ };
977
+ } catch (err) {
978
+ if (attempt < maxRetries) {
979
+ console.error(`[AgentMoss] LLM call failed (attempt ${attempt + 1}/${maxRetries + 1}), retrying in 1s:`, err.message);
980
+ await new Promise((resolve4) => setTimeout(resolve4, 1e3));
981
+ } else {
982
+ console.error("[AgentMoss] LLM call failed after all retries:", err.message);
983
+ return {
984
+ allowed: false,
985
+ 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}`,
986
+ riskLevel: "medium",
987
+ riskType: "",
988
+ confidence: 60,
989
+ source: "llm_failure",
990
+ violatedLayers: ["3"]
991
+ };
992
+ }
354
993
  }
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
994
  }
995
+ return {
996
+ allowed: false,
997
+ reason: "LLM \u5206\u6790\u5F02\u5E38",
998
+ riskLevel: "medium",
999
+ riskType: "",
1000
+ confidence: 60,
1001
+ source: "llm_failure",
1002
+ violatedLayers: ["3"]
1003
+ };
378
1004
  }
379
1005
  var llmCache, LLM_CACHE_MAX;
380
1006
  var init_llm_analyzer = __esm({
@@ -384,6 +1010,7 @@ var init_llm_analyzer = __esm({
384
1010
  init_config();
385
1011
  init_skill_loader();
386
1012
  init_template_loader();
1013
+ init_script_analyzer();
387
1014
  llmCache = /* @__PURE__ */ new Map();
388
1015
  LLM_CACHE_MAX = 200;
389
1016
  }
@@ -394,21 +1021,83 @@ var coordinator_exports = {};
394
1021
  __export(coordinator_exports, {
395
1022
  runAnalysis: () => runAnalysis
396
1023
  });
1024
+ function extractFirstCommand(command) {
1025
+ let main2 = command.split("|")[0].trim();
1026
+ for (const sep of ["&&", "||"]) {
1027
+ const idx = main2.indexOf(sep);
1028
+ if (idx !== -1) {
1029
+ main2 = main2.substring(0, idx).trim();
1030
+ break;
1031
+ }
1032
+ }
1033
+ return main2;
1034
+ }
1035
+ function extractRawCommand(detail) {
1036
+ const m = detail.match(/command=([^,]+)/);
1037
+ return m ? m[1].trim() : detail;
1038
+ }
1039
+ function matchesAny(cmd, patterns) {
1040
+ return patterns.some((p) => p.test(cmd));
1041
+ }
397
1042
  async function runAnalysis(input) {
398
- const heuristicResult = runHeuristicCheck(input);
1043
+ const heuristicResult = isHeuristicEnabled() ? runHeuristicCheck(input) : null;
399
1044
  if (heuristicResult && !heuristicResult.allowed) {
400
1045
  return heuristicResult;
401
1046
  }
402
- const logicResult = runLogicCheck(input);
1047
+ const actionType = input.aNext.actionType.toLowerCase();
1048
+ const rawDetail = extractRawCommand(input.aNext.actionDetail);
1049
+ const lowerDetail = rawDetail.toLowerCase();
1050
+ if (FULLY_SAFE_TOOLS.has(actionType)) {
1051
+ return {
1052
+ allowed: true,
1053
+ reason: `\u5B8C\u5168\u5B89\u5168\u5DE5\u5177\uFF0C\u8DF3\u8FC7\u6DF1\u5EA6\u5206\u6790: ${actionType}`,
1054
+ riskLevel: heuristicResult?.riskLevel || "low",
1055
+ riskType: "",
1056
+ confidence: 95,
1057
+ source: "whitelist_bypass",
1058
+ violatedLayers: []
1059
+ };
1060
+ }
1061
+ let skipLLM = READONLY_SENSITIVE_TOOLS.has(actionType);
1062
+ if (actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash") {
1063
+ const firstCmd = extractFirstCommand(lowerDetail);
1064
+ if (matchesAny(firstCmd, FULLY_SAFE_BASH_PATTERNS)) {
1065
+ return {
1066
+ allowed: true,
1067
+ reason: `\u5B89\u5168\u7684\u53EA\u8BFB bash \u547D\u4EE4: ${firstCmd}`,
1068
+ riskLevel: "low",
1069
+ riskType: "",
1070
+ confidence: 95,
1071
+ source: "whitelist_bypass",
1072
+ violatedLayers: []
1073
+ };
1074
+ }
1075
+ if (matchesAny(firstCmd, READONLY_SENSITIVE_BASH_PATTERNS)) {
1076
+ skipLLM = true;
1077
+ }
1078
+ }
1079
+ const logicResult = isLogicRulesEnabled() ? runLogicCheck(input) : null;
403
1080
  if (logicResult && !logicResult.allowed) {
404
1081
  return logicResult;
405
1082
  }
1083
+ if (skipLLM || !isLLMAvailable()) {
1084
+ return {
1085
+ allowed: true,
1086
+ reason: "\u53EA\u8BFB\u64CD\u4F5C\u4E14\u65E0\u903B\u8F91\u89C4\u5219\u8FDD\u89C4\uFF0C\u8DF3\u8FC7 LLM \u5206\u6790",
1087
+ riskLevel: logicResult?.riskLevel || "low",
1088
+ riskType: "",
1089
+ confidence: 85,
1090
+ source: "heuristic_and_logic_only",
1091
+ violatedLayers: []
1092
+ };
1093
+ }
406
1094
  const llmResult = await runLLMAnalysis(input, {
407
1095
  heuristic: heuristicResult,
408
1096
  logic: logicResult
409
1097
  });
410
1098
  return llmResult;
411
1099
  }
1100
+ var FULLY_SAFE_TOOLS, READONLY_SENSITIVE_TOOLS, FULLY_SAFE_BASH_PATTERNS, READONLY_SENSITIVE_BASH_PATTERNS;
412
1101
  var init_coordinator = __esm({
413
1102
  "src/engine/coordinator.ts"() {
414
1103
  "use strict";
@@ -416,6 +1105,59 @@ var init_coordinator = __esm({
416
1105
  init_heuristic();
417
1106
  init_logic_rules();
418
1107
  init_llm_analyzer();
1108
+ init_config();
1109
+ FULLY_SAFE_TOOLS = /* @__PURE__ */ new Set([
1110
+ "ask_user_question",
1111
+ "glob",
1112
+ "list_dir",
1113
+ "ls",
1114
+ "count_text_length",
1115
+ "filemgr-globfiles"
1116
+ ]);
1117
+ READONLY_SENSITIVE_TOOLS = /* @__PURE__ */ new Set([
1118
+ "read",
1119
+ "file_read",
1120
+ "read_file",
1121
+ "head",
1122
+ "tail",
1123
+ "grep",
1124
+ "filemgr-readfile",
1125
+ "filemgr-grepfiles"
1126
+ ]);
1127
+ FULLY_SAFE_BASH_PATTERNS = [
1128
+ /^ls(?:\s|$)/i,
1129
+ /^dir(?:\s|$)/i,
1130
+ /^pwd(?:\s|$)/i,
1131
+ /^which\s+/i,
1132
+ /^whereis\s+/i,
1133
+ /^realpath\s+/i,
1134
+ /^basename\s+/i,
1135
+ /^dirname\s+/i,
1136
+ /^file\s+/i,
1137
+ /^stat\s+/i,
1138
+ /^du(?:\s|$)/i,
1139
+ /^echo\s+/i,
1140
+ /^printf\s+/i,
1141
+ /^type\s+/i,
1142
+ /^command\s+/i
1143
+ ];
1144
+ READONLY_SENSITIVE_BASH_PATTERNS = [
1145
+ /^cat\s+/i,
1146
+ /^head(?:\s|$)/i,
1147
+ /^tail(?:\s|$)/i,
1148
+ /^less(?:\s|$)/i,
1149
+ /^more(?:\s|$)/i,
1150
+ /^wc(?:\s|$)/i,
1151
+ /^grep(?:\s|$)/i,
1152
+ /^find\s+/i,
1153
+ /^ag(?:\s|$)/i,
1154
+ /^rg(?:\s|$)/i,
1155
+ /^awk\s+/i,
1156
+ /^sed\s+/i,
1157
+ /^cut\s+/i,
1158
+ /^sort(?:\s|$)/i,
1159
+ /^uniq(?:\s|$)/i
1160
+ ];
419
1161
  }
420
1162
  });
421
1163