@kenhkl/agent-moss 0.3.2 → 0.5.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
@@ -1,6 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  var __defProp = Object.defineProperty;
3
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
5
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
6
+ }) : x)(function(x) {
7
+ if (typeof require !== "undefined") return require.apply(this, arguments);
8
+ throw Error('Dynamic require of "' + x + '" is not supported');
9
+ });
4
10
  var __esm = (fn, res) => function __init() {
5
11
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
12
  };
@@ -12,13 +18,59 @@ var __export = (target, all) => {
12
18
  // node_modules/tsup/assets/esm_shims.js
13
19
  import path from "path";
14
20
  import { fileURLToPath } from "url";
21
+ var getFilename, getDirname, __dirname;
15
22
  var init_esm_shims = __esm({
16
23
  "node_modules/tsup/assets/esm_shims.js"() {
17
24
  "use strict";
25
+ getFilename = () => fileURLToPath(import.meta.url);
26
+ getDirname = () => path.dirname(getFilename());
27
+ __dirname = /* @__PURE__ */ getDirname();
18
28
  }
19
29
  });
20
30
 
21
31
  // src/config.ts
32
+ import fs from "fs";
33
+ import path2 from "path";
34
+ import os from "os";
35
+ function loadConfigFile(filePath) {
36
+ try {
37
+ const raw = fs.readFileSync(filePath, "utf-8");
38
+ return JSON.parse(raw);
39
+ } catch {
40
+ return null;
41
+ }
42
+ }
43
+ function resolveConfigPath() {
44
+ const envPath = process.env.AGENT_MOSS_CONFIG_PATH;
45
+ if (envPath) return envPath;
46
+ const userConfig = path2.join(os.homedir(), ".config", "agentmoss", "config.json");
47
+ if (fs.existsSync(userConfig)) return userConfig;
48
+ const defaultConfig = path2.join(__dirname, "..", "..", "agent_moss", "infra", "config.json.example");
49
+ if (fs.existsSync(defaultConfig)) {
50
+ const configDir = path2.dirname(userConfig);
51
+ fs.mkdirSync(configDir, { recursive: true });
52
+ const pkgConfig = loadConfigFile(defaultConfig);
53
+ const safeConfig = pkgConfig ? { ...pkgConfig, llm: { ...pkgConfig.llm, api_key: "" } } : {};
54
+ fs.writeFileSync(userConfig, JSON.stringify(safeConfig, null, 4), "utf-8");
55
+ console.log(`[AgentMoss] \u5DF2\u521B\u5EFA\u7528\u6237\u914D\u7F6E\u6587\u4EF6: ${userConfig}`);
56
+ console.log(`[AgentMoss] \u8BF7\u7F16\u8F91\u8BE5\u6587\u4EF6\u914D\u7F6E\u4F60\u7684 LLM API Key`);
57
+ }
58
+ return userConfig;
59
+ }
60
+ function loadLLMConfigFromFile(configPath) {
61
+ const data = loadConfigFile(configPath);
62
+ if (!data?.llm) return {};
63
+ const llm = data.llm;
64
+ return {
65
+ provider: llm.provider || "",
66
+ baseUrl: llm.base_url || "",
67
+ apiKey: llm.api_key || "",
68
+ model: llm.model || "",
69
+ temperature: llm.temperature ?? 0.1,
70
+ maxTokens: llm.max_tokens ?? 4096,
71
+ timeout: llm.timeout ?? 300
72
+ };
73
+ }
22
74
  function loadCustomRules() {
23
75
  try {
24
76
  const raw = process.env.AGENT_MOSS_CUSTOM_RULES;
@@ -84,15 +136,19 @@ var init_config = __esm({
84
136
  "src/config.ts"() {
85
137
  "use strict";
86
138
  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
- };
139
+ llmConfig = (() => {
140
+ const configPath = resolveConfigPath();
141
+ const fileConfig = fs.existsSync(configPath) ? loadLLMConfigFromFile(configPath) : {};
142
+ return {
143
+ provider: process.env.AGENT_MOSS_LLM_PROVIDER || fileConfig.provider || "openai",
144
+ baseUrl: process.env.AGENT_MOSS_LLM_BASE_URL || fileConfig.baseUrl || "https://api.openai.com/v1",
145
+ apiKey: process.env.AGENT_MOSS_LLM_API_KEY || process.env.OPENAI_API_KEY || fileConfig.apiKey || "",
146
+ model: process.env.AGENT_MOSS_LLM_MODEL || fileConfig.model || "gpt-4o",
147
+ temperature: parseFloat(process.env.AGENT_MOSS_LLM_TEMPERATURE || String(fileConfig.temperature ?? "0.1")),
148
+ maxTokens: parseInt(process.env.AGENT_MOSS_LLM_MAX_TOKENS || String(fileConfig.maxTokens ?? "4096"), 10),
149
+ timeout: parseInt(process.env.AGENT_MOSS_LLM_TIMEOUT || String(fileConfig.timeout ?? "300"), 10)
150
+ };
151
+ })();
96
152
  customRules = loadCustomRules();
97
153
  PROVIDER_HEADERS = {
98
154
  "openrouter": {
@@ -111,6 +167,7 @@ function runHeuristicCheck(input) {
111
167
  const command = `${input.aNext.actionType}: ${input.aNext.actionDetail}`;
112
168
  const matched = [];
113
169
  let highestSeverity = "low";
170
+ let highestRiskType = "";
114
171
  const userRules = getCustomRules();
115
172
  for (const rule of userRules) {
116
173
  try {
@@ -120,20 +177,24 @@ function runHeuristicCheck(input) {
120
177
  matched.push(`\u81EA\u5B9A\u4E49\u89C4\u5219: ${rule.pattern}`);
121
178
  if (severityRank(rule.severity) > severityRank(highestSeverity)) {
122
179
  highestSeverity = rule.severity;
180
+ highestRiskType = "custom_rule";
123
181
  }
124
182
  }
125
183
  }
126
184
  } catch {
127
185
  }
128
186
  }
187
+ let topRiskType = "";
129
188
  for (const rule of DANGEROUS_PATTERNS) {
130
189
  if (rule.pattern.test(command)) {
131
190
  matched.push(rule.description);
132
191
  if (severityRank(rule.severity) > severityRank(highestSeverity)) {
133
192
  highestSeverity = rule.severity;
193
+ topRiskType = rule.riskType;
134
194
  }
135
195
  }
136
196
  }
197
+ highestRiskType = topRiskType || highestRiskType;
137
198
  const injectionText = `${input.aNext.actionType} ${input.aNext.actionDetail} ${input.reason || ""} ${input.promptSession || ""}`.toLowerCase();
138
199
  const injectionHits = checkInjectionKeywords(injectionText);
139
200
  if (injectionHits.length > 0) {
@@ -141,15 +202,15 @@ function runHeuristicCheck(input) {
141
202
  matched.push(...injectionHits.map((h) => `\u6CE8\u5165\u5173\u952E\u8BCD [${h.lang}]: ${h.keyword}`));
142
203
  if (severityRank(topHit.riskLevel) > severityRank(highestSeverity)) {
143
204
  highestSeverity = topHit.riskLevel;
205
+ highestRiskType = "prompt_injection";
144
206
  }
145
207
  }
146
208
  if (matched.length > 0) {
147
- const riskType = injectionHits.length > 0 ? "prompt_injection" : "dangerous_command";
148
209
  return {
149
210
  allowed: false,
150
211
  reason: `\u68C0\u6D4B\u5230\u9AD8\u5371\u64CD\u4F5C: ${matched.join(", ")}`,
151
212
  riskLevel: highestSeverity,
152
- riskType,
213
+ riskType: highestRiskType,
153
214
  confidence: 95,
154
215
  source: "heuristic",
155
216
  violatedLayers: ["1"]
@@ -166,6 +227,10 @@ function checkInjectionKeywords(text) {
166
227
  }
167
228
  return hits;
168
229
  }
230
+ function isInlineScriptCommand(actionDetail) {
231
+ const re = /(?:^|\s)(?:python|python3|python3\.\d+|perl|perl5|ruby|jruby|mruby|node|php|lua)\s+(?:-[a-zA-Z]*[cer][a-zA-Z]*\s+)/i;
232
+ return re.test(actionDetail);
233
+ }
169
234
  function severityRank(s) {
170
235
  const ranks = { low: 1, medium: 2, high: 3, critical: 4 };
171
236
  return ranks[s] || 0;
@@ -241,59 +306,66 @@ var init_heuristic = __esm({
241
306
  DANGEROUS_PATTERNS = [
242
307
  // --- Critical: rm command graded detection (strict to loose, first match wins) ---
243
308
  // 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 /)" },
309
+ { 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 /)", riskType: "script_execution" },
245
310
  // 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 /...)" },
311
+ { 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 /...)", riskType: "script_execution" },
247
312
  // 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" },
313
+ { 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", riskType: "script_execution" },
249
314
  // 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 /...)" },
315
+ { pattern: /\brm\s+-[a-zA-Z]*r[a-zA-Z]*\s+\//i, severity: "high", description: "\u9012\u5F52\u5220\u9664\u64CD\u4F5C (rm -r /...)", riskType: "script_execution" },
251
316
  // 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" },
317
+ { pattern: /\brm\s+-[a-zA-Z]*f[a-zA-Z]*\s+\//i, severity: "medium", description: "\u5F3A\u5236\u5220\u9664\u6587\u4EF6 (rm -f /...)", riskType: "script_execution" },
318
+ { pattern: /dd\s+if=\/dev\/zero|dd\s+if=.*of=\/dev\//i, severity: "critical", description: "\u78C1\u76D8\u6E05\u96F6/\u76F4\u63A5\u5199\u5165", riskType: "script_execution" },
319
+ { pattern: /mkfs\b/i, severity: "critical", description: "\u683C\u5F0F\u5316\u6587\u4EF6\u7CFB\u7EDF", riskType: "script_execution" },
320
+ { pattern: /:\(\)\s*\{.*:\|:&.*\};:/i, severity: "critical", description: "Fork Bomb", riskType: "resource_exhaustion" },
256
321
  // --- 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" },
322
+ { pattern: /curl\s+.*\|\s*(bash|sh|zsh|dash)/i, severity: "critical", description: "curl pipe shell \u6267\u884C", riskType: "script_execution" },
323
+ { pattern: /wget\s+.*\|\s*(bash|sh)/i, severity: "critical", description: "wget pipe shell \u6267\u884C", riskType: "script_execution" },
324
+ { pattern: /eval\s+\$\(curl|eval\s+\$\(wget/i, severity: "critical", description: "eval \u8FDC\u7A0B\u4EE3\u7801\u6267\u884C", riskType: "script_execution" },
325
+ { pattern: /base64\s+-d\s+.*\|\s*bash/i, severity: "critical", description: "base64 \u89E3\u7801\u6267\u884C", riskType: "script_execution" },
261
326
  // --- 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" },
327
+ { pattern: /\/dev\/tcp\/|bash\s+-i\s+>&/i, severity: "critical", description: "\u53CD\u5F39 Shell", riskType: "script_execution" },
328
+ { pattern: /nc\s+-[el]\s+.*\d{1,5}/i, severity: "high", description: "Netcat \u53CD\u5F39\u8FDE\u63A5", riskType: "lateral_movement" },
264
329
  // --- 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" },
330
+ { pattern: /\/etc\/shadow/i, severity: "critical", description: "\u8BBF\u95EE\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6 (/etc/shadow)", riskType: "file_access" },
331
+ { pattern: /\.ssh\/id_rsa/i, severity: "critical", description: "\u8BBF\u95EE SSH \u79C1\u94A5\u6587\u4EF6", riskType: "file_access" },
267
332
  // --- 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" },
333
+ { pattern: /\/var\/run\/docker\.sock/i, severity: "critical", description: "\u8BBF\u95EE Docker socket\uFF0C\u53EF\u80FD\u5BFC\u81F4\u5BB9\u5668\u9003\u9038", riskType: "privilege_escalation" },
269
334
  // --- 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)" },
335
+ { pattern: /chmod\s+[0-7]*777[0-7]*/i, severity: "high", description: "\u5168\u6743\u9650\u8BBE\u7F6E (chmod 777)", riskType: "script_execution" },
336
+ { 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", riskType: "script_execution" },
337
+ { pattern: /sudo\s+rm\s+-rf/i, severity: "high", description: "sudo \u5F3A\u5236\u5220\u9664", riskType: "script_execution" },
338
+ { pattern: /sudo\s+/i, severity: "high", description: "\u63D0\u6743\u6267\u884C (sudo)", riskType: "privilege_escalation" },
274
339
  // --- High: system config overwrite ---
275
- { pattern: />\s*\/etc\//i, severity: "critical", description: "\u8986\u76D6\u5199\u5165\u7CFB\u7EDF\u914D\u7F6E\u76EE\u5F55" },
340
+ { pattern: />\s*\/etc\//i, severity: "critical", description: "\u8986\u76D6\u5199\u5165\u7CFB\u7EDF\u914D\u7F6E\u76EE\u5F55", riskType: "file_access" },
276
341
  // --- 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" },
342
+ { pattern: /(?:curl|wget)\s+.*(?:-o|--output|>)\s*\S+\.(?:sh|bash)\b/i, severity: "high", description: "\u8FDC\u7A0B\u4E0B\u8F7D\u811A\u672C\u5230\u672C\u5730", riskType: "script_execution" },
343
+ { pattern: /(?:curl|wget)\s+.*\s+&&?\s*(?:chmod\s+\+x|bash|sh)/i, severity: "high", description: "\u4E0B\u8F7D\u540E\u6267\u884C", riskType: "script_execution" },
279
344
  // --- 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" },
345
+ { pattern: /git\s+clone\s+.*\|.*bash/i, severity: "high", description: "git clone pipe shell", riskType: "script_execution" },
346
+ { pattern: /pip\s+install\s+.*\|\s*bash/i, severity: "high", description: "pip install pipe shell", riskType: "script_execution" },
347
+ { pattern: /npm\s+install\s+-g\s+.*\|\s*bash/i, severity: "high", description: "npm install pipe shell", riskType: "script_execution" },
283
348
  // --- High: destructive git operations ---
284
- { pattern: /git\s+push\s+.*--force/i, severity: "high", description: "\u5F3A\u5236\u63A8\u9001\u64CD\u4F5C (git push --force)" },
349
+ { pattern: /git\s+push\s+.*--force/i, severity: "high", description: "\u5F3A\u5236\u63A8\u9001\u64CD\u4F5C (git push --force)", riskType: "script_execution" },
285
350
  // --- Medium: credential export ---
286
- { pattern: /export\s+.*(?:TOKEN|KEY|SECRET|PASSWORD)/i, severity: "medium", description: "\u5BFC\u51FA\u654F\u611F\u51ED\u8BC1" },
351
+ { pattern: /export\s+.*(?:TOKEN|KEY|SECRET|PASSWORD)/i, severity: "medium", description: "\u5BFC\u51FA\u654F\u611F\u51ED\u8BC1", riskType: "data_exfiltration" },
287
352
  // --- High: shell rc file append (persistence) ---
288
- { pattern: />>\s*(~|\/home\/[^/]+)\/(\.bashrc|\.zshrc|\.profile|\.bash_profile|\.zprofile)/, severity: "high", description: "\u68C0\u6D4B\u5230\u4FEE\u6539 Shell \u914D\u7F6E\u6587\u4EF6\uFF08~/.bashrc \u7B49\uFF09\uFF0C\u53EF\u80FD\u7528\u4E8E\u6301\u4E45\u5316\u540E\u95E8" },
353
+ { pattern: />>\s*(~|\/home\/[^/]+)\/(\.bashrc|\.zshrc|\.profile|\.bash_profile|\.zprofile)/, severity: "high", description: "\u68C0\u6D4B\u5230\u4FEE\u6539 Shell \u914D\u7F6E\u6587\u4EF6\uFF08~/.bashrc \u7B49\uFF09\uFF0C\u53EF\u80FD\u7528\u4E8E\u6301\u4E45\u5316\u540E\u95E8", riskType: "persistence" },
289
354
  // --- High: device file access ---
290
- { pattern: /\/dev\/(mem|kmsg)\b/i, severity: "high", description: "\u8BBF\u95EE\u8BBE\u5907\u6587\u4EF6 (\u7269\u7406\u5185\u5B58/\u5185\u6838\u6D88\u606F)" }
355
+ { pattern: /\/dev\/(mem|kmsg)\b/i, severity: "high", description: "\u8BBF\u95EE\u8BBE\u5907\u6587\u4EF6 (\u7269\u7406\u5185\u5B58/\u5185\u6838\u6D88\u606F)", riskType: "file_access" },
356
+ // --- High: non-interactive password modification ---
357
+ { pattern: /\|\s*passwd\b/i, severity: "high", description: "\u68C0\u6D4B\u5230\u7BA1\u9053\u65B9\u5F0F\u8C03\u7528 passwd\uFF08\u975E\u4EA4\u4E92\u5F0F\u5BC6\u7801\u4FEE\u6539\uFF09", riskType: "script_execution" },
358
+ { pattern: /\bpasswd\s+--stdin\b/i, severity: "high", description: "\u68C0\u6D4B\u5230 passwd --stdin \u975E\u4EA4\u4E92\u5F0F\u5BC6\u7801\u8F93\u5165", riskType: "script_execution" },
359
+ { pattern: /\bchpasswd\b/i, severity: "high", description: "\u68C0\u6D4B\u5230 chpasswd \u6279\u91CF\u5BC6\u7801\u4FEE\u6539\u547D\u4EE4", riskType: "script_execution" },
360
+ { pattern: /\bnewusers\b/i, severity: "high", description: "\u68C0\u6D4B\u5230 newusers \u6279\u91CF\u7528\u6237\u6DFB\u52A0\u547D\u4EE4", riskType: "script_execution" },
361
+ { pattern: /\bpasswd\s+-[a-zA-Z]*d\b/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7528\u6237\u5BC6\u7801\u64CD\u4F5C (passwd -d)", riskType: "script_execution" },
362
+ { pattern: /\bpasswd\s+-[a-zA-Z]*l\b/i, severity: "high", description: "\u68C0\u6D4B\u5230\u9501\u5B9A\u7528\u6237\u8D26\u6237\u64CD\u4F5C (passwd -l)", riskType: "script_execution" }
291
363
  ];
292
364
  }
293
365
  });
294
366
 
295
367
  // src/engine/logic-rules.ts
296
- import * as fs from "fs";
368
+ import * as fs2 from "fs";
297
369
  function runLogicCheck(input) {
298
370
  const { actionHistory, aNext, cwd } = input;
299
371
  const actionDetail = aNext.actionDetail.toLowerCase();
@@ -375,6 +447,8 @@ function runLogicCheck(input) {
375
447
  };
376
448
  }
377
449
  }
450
+ const pwdResult = checkPasswordConsent(actionType, actionDetail, actionHistory);
451
+ if (pwdResult) return pwdResult;
378
452
  return null;
379
453
  }
380
454
  function extractFilePaths(text) {
@@ -387,7 +461,13 @@ function extractFilePaths(text) {
387
461
  }
388
462
  function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, cwd) {
389
463
  if (NON_WRITE_TOOLS.has(actionType)) return null;
390
- const isWrite = WRITE_KEYWORDS.some((kw) => actionType.includes(kw) || actionDetail.includes(kw));
464
+ let isWrite = WRITE_KEYWORDS.some((kw) => actionType.includes(kw) || actionDetail.includes(kw));
465
+ if (!isWrite && actionDetail.includes(">")) {
466
+ const firstWord = actionDetail.split(/\s/)[0]?.toLowerCase() || "";
467
+ if (!READ_ONLY_REDIR_CMDS.has(firstWord)) {
468
+ isWrite = true;
469
+ }
470
+ }
391
471
  if (!isWrite) return null;
392
472
  const targetPaths = extractFilePaths(actionDetail);
393
473
  if (targetPaths.length === 0) return null;
@@ -403,17 +483,17 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
403
483
  }
404
484
  }
405
485
  const unreadPaths = [];
406
- for (const path2 of targetPaths) {
486
+ for (const path3 of targetPaths) {
407
487
  const hasRead = [...historyReadPaths].some(
408
- (rp) => path2 === rp || path2.startsWith(rp) || rp.startsWith(path2)
488
+ (rp) => path3 === rp || path3.startsWith(rp) || rp.startsWith(path3)
409
489
  );
410
490
  if (!hasRead) {
411
- let isCreate = !fs.existsSync(path2);
491
+ let isCreate = !fs2.existsSync(path3);
412
492
  if (!isCreate) {
413
493
  isCreate = CREATE_KEYWORDS.some((kw) => reason.toLowerCase().includes(kw) || actionDetail.includes(kw));
414
494
  }
415
495
  if (!isCreate) {
416
- unreadPaths.push(path2);
496
+ unreadPaths.push(path3);
417
497
  }
418
498
  }
419
499
  }
@@ -495,7 +575,25 @@ function isLocalHost(host) {
495
575
  const localNames = ["localhost", "127.0.0.1", "::1", process.env.HOSTNAME || "", "0.0.0.0"];
496
576
  return localNames.includes(host.replace(/.*@/, ""));
497
577
  }
498
- var ROOT_SENSITIVE_PATHS, HOME_SENSITIVE_PATHS, CREDENTIAL_PATHS, NON_WRITE_TOOLS, WRITE_KEYWORDS, READ_KEYWORDS, CREATE_KEYWORDS, INTENT_DEVIATION_PATTERNS;
578
+ function checkPasswordConsent(actionType, actionDetail, actionHistory) {
579
+ if (actionType !== "bash") return null;
580
+ const isPasswordModify = PASSWORD_MODIFY_PATTERNS.some((p) => p.test(actionDetail));
581
+ if (!isPasswordModify) return null;
582
+ const hasConsent = actionHistory.some(
583
+ (h) => h.actionType.toLowerCase() === "ask_user_question" && h.actionDetail.trim().length > 0
584
+ );
585
+ if (hasConsent) return null;
586
+ return {
587
+ allowed: false,
588
+ reason: "\u6267\u884C\u975E\u4EA4\u4E92\u5F0F\u5BC6\u7801\u4FEE\u6539\u547D\u4EE4\uFF0C\u4F46\u5386\u53F2\u6267\u884C\u5E8F\u5217\u4E2D\u672A\u53D1\u73B0\u5411\u7528\u6237\u786E\u8BA4\u5BC6\u7801\u7684 ask_user_question \u64CD\u4F5C\uFF0C\u53EF\u80FD\u5B58\u5728\u672A\u6388\u6743\u7684\u5BC6\u7801\u4FEE\u6539\u98CE\u9669",
589
+ riskLevel: "high",
590
+ riskType: "consent_missing",
591
+ confidence: 85,
592
+ source: "logic_rule",
593
+ violatedLayers: ["2"]
594
+ };
595
+ }
596
+ var ROOT_SENSITIVE_PATHS, HOME_SENSITIVE_PATHS, CREDENTIAL_PATHS, NON_WRITE_TOOLS, READ_ONLY_REDIR_CMDS, WRITE_KEYWORDS, READ_KEYWORDS, CREATE_KEYWORDS, PASSWORD_MODIFY_PATTERNS, INTENT_DEVIATION_PATTERNS;
499
597
  var init_logic_rules = __esm({
500
598
  "src/engine/logic-rules.ts"() {
501
599
  "use strict";
@@ -532,9 +630,18 @@ var init_logic_rules = __esm({
532
630
  { path: ".env", riskLevel: "high", desc: "\u73AF\u5883\u53D8\u91CF/\u51ED\u636E\u914D\u7F6E\u6587\u4EF6" }
533
631
  ];
534
632
  NON_WRITE_TOOLS = /* @__PURE__ */ new Set(["ask_user_question", "glob", "grep", "list_dir", "count_text_length", "search"]);
633
+ READ_ONLY_REDIR_CMDS = /* @__PURE__ */ new Set(["cat", "head", "tail", "less", "more", "grep", "find", "awk", "sed", "sort", "uniq", "wc", "cut", "strings", "od", "xxd", "hexdump"]);
535
634
  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"];
536
635
  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"];
537
636
  CREATE_KEYWORDS = ["\u521B\u5EFA", "\u65B0\u5EFA", "create", "new file", "touch"];
637
+ PASSWORD_MODIFY_PATTERNS = [
638
+ /\|\s*passwd\b/i,
639
+ /\bpasswd\s+--stdin\b/i,
640
+ /\bchpasswd\b/i,
641
+ /\bnewusers\b/i,
642
+ /\bpasswd\s+-[a-zA-Z]*d\b/i,
643
+ /\bpasswd\s+-[a-zA-Z]*l\b/i
644
+ ];
538
645
  INTENT_DEVIATION_PATTERNS = [
539
646
  {
540
647
  intentKeywords: ["\u8BFB\u53D6", "\u67E5\u770B", "\u5206\u6790", "read", "analyze", "view", "list", "\u5217\u51FA"],
@@ -1069,8 +1176,12 @@ function matchesAny(cmd, patterns) {
1069
1176
  }
1070
1177
  async function runAnalysis(input) {
1071
1178
  const heuristicResult = isHeuristicEnabled() ? runHeuristicCheck(input) : null;
1179
+ const isInlineScriptCmd = isInlineScriptCommand(input.aNext.actionDetail.toLowerCase());
1072
1180
  if (heuristicResult && !heuristicResult.allowed) {
1073
- return heuristicResult;
1181
+ const skipInlineFileAccess = isInlineScriptCmd && heuristicResult.riskType === "file_access";
1182
+ if (!skipInlineFileAccess) {
1183
+ return heuristicResult;
1184
+ }
1074
1185
  }
1075
1186
  const actionType = input.aNext.actionType.toLowerCase();
1076
1187
  const rawDetail = extractRawCommand(input.aNext.actionDetail);
@@ -1086,19 +1197,37 @@ async function runAnalysis(input) {
1086
1197
  violatedLayers: []
1087
1198
  };
1088
1199
  }
1200
+ if (actionType === "skill" && TRUSTED_SKILLS.has(lowerDetail)) {
1201
+ return {
1202
+ allowed: true,
1203
+ reason: `\u5185\u7F6E\u5B89\u5168 Skill\uFF0C\u8DF3\u8FC7\u6DF1\u5EA6\u5206\u6790: ${lowerDetail}`,
1204
+ riskLevel: heuristicResult?.riskLevel || "low",
1205
+ riskType: "",
1206
+ confidence: 95,
1207
+ source: "whitelist_bypass",
1208
+ violatedLayers: []
1209
+ };
1210
+ }
1089
1211
  let skipLLM = READONLY_SENSITIVE_TOOLS.has(actionType);
1090
1212
  if (actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash") {
1091
1213
  const firstCmd = extractFirstCommand(lowerDetail);
1092
1214
  if (matchesAny(firstCmd, FULLY_SAFE_BASH_PATTERNS)) {
1093
- return {
1094
- allowed: true,
1095
- reason: `\u5B89\u5168\u7684\u53EA\u8BFB bash \u547D\u4EE4: ${firstCmd}`,
1096
- riskLevel: "low",
1097
- riskType: "",
1098
- confidence: 95,
1099
- source: "whitelist_bypass",
1100
- violatedLayers: []
1101
- };
1215
+ const fullCmdHit = DANGEROUS_PATTERNS.some(
1216
+ (p) => p.severity === "critical" || p.severity === "high"
1217
+ ) && DANGEROUS_PATTERNS.some(
1218
+ (p) => (p.severity === "critical" || p.severity === "high") && p.pattern.test(lowerDetail)
1219
+ );
1220
+ if (!fullCmdHit) {
1221
+ return {
1222
+ allowed: true,
1223
+ reason: `\u5B89\u5168\u7684\u53EA\u8BFB bash \u547D\u4EE4: ${firstCmd}`,
1224
+ riskLevel: "low",
1225
+ riskType: "",
1226
+ confidence: 95,
1227
+ source: "whitelist_bypass",
1228
+ violatedLayers: []
1229
+ };
1230
+ }
1102
1231
  }
1103
1232
  if (matchesAny(firstCmd, READONLY_SENSITIVE_BASH_PATTERNS)) {
1104
1233
  skipLLM = true;
@@ -1106,7 +1235,10 @@ async function runAnalysis(input) {
1106
1235
  }
1107
1236
  const logicResult = isLogicRulesEnabled() ? runLogicCheck(input) : null;
1108
1237
  if (logicResult && !logicResult.allowed) {
1109
- return logicResult;
1238
+ const skipInlineFileAccess = isInlineScriptCmd && logicResult.riskType === "sensitive_path_access";
1239
+ if (!skipInlineFileAccess) {
1240
+ return logicResult;
1241
+ }
1110
1242
  }
1111
1243
  if (skipLLM || !isLLMAvailable()) {
1112
1244
  return {
@@ -1125,7 +1257,7 @@ async function runAnalysis(input) {
1125
1257
  });
1126
1258
  return llmResult;
1127
1259
  }
1128
- var FULLY_SAFE_TOOLS, READONLY_SENSITIVE_TOOLS, FULLY_SAFE_BASH_PATTERNS, READONLY_SENSITIVE_BASH_PATTERNS;
1260
+ var FULLY_SAFE_TOOLS, TRUSTED_SKILLS, READONLY_SENSITIVE_TOOLS, FULLY_SAFE_BASH_PATTERNS, READONLY_SENSITIVE_BASH_PATTERNS;
1129
1261
  var init_coordinator = __esm({
1130
1262
  "src/engine/coordinator.ts"() {
1131
1263
  "use strict";
@@ -1142,6 +1274,7 @@ var init_coordinator = __esm({
1142
1274
  "count_text_length",
1143
1275
  "filemgr-globfiles"
1144
1276
  ]);
1277
+ TRUSTED_SKILLS = /* @__PURE__ */ new Set(["xiaoo-guardian"]);
1145
1278
  READONLY_SENSITIVE_TOOLS = /* @__PURE__ */ new Set([
1146
1279
  "read",
1147
1280
  "file_read",
@@ -1167,7 +1300,20 @@ var init_coordinator = __esm({
1167
1300
  /^echo\s+/i,
1168
1301
  /^printf\s+/i,
1169
1302
  /^type\s+/i,
1170
- /^command\s+/i
1303
+ /^command\s+/i,
1304
+ // 系统信息查询(只读,不涉及文件/网络/权限)
1305
+ /^whoami(?:\s|$)/i,
1306
+ /^id(?:\s|$)/i,
1307
+ /^hostname(?:\s|$)/i,
1308
+ /^uname(?:\s|$)/i,
1309
+ /^date(?:\s|$)/i,
1310
+ /^env(?:\s|$)/i,
1311
+ /^printenv(?:\s|$)/i,
1312
+ /^tty(?:\s|$)/i,
1313
+ /^arch(?:\s|$)/i,
1314
+ /^uptime(?:\s|$)/i,
1315
+ /^groups(?:\s|$)/i,
1316
+ /^logname(?:\s|$)/i
1171
1317
  ];
1172
1318
  READONLY_SENSITIVE_BASH_PATTERNS = [
1173
1319
  /^cat\s+/i,
@@ -1236,6 +1382,109 @@ var HealthResponseSchema = z.object({
1236
1382
 
1237
1383
  // src/routes.ts
1238
1384
  init_coordinator();
1385
+
1386
+ // src/actrail.ts
1387
+ init_esm_shims();
1388
+ import * as fs3 from "fs";
1389
+ function escape(value) {
1390
+ return value.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/=/g, "\\e");
1391
+ }
1392
+ function sessionToTraceId(sessionId) {
1393
+ let hash = 0;
1394
+ for (let i = 0; i < sessionId.length; i++) {
1395
+ const char = sessionId.charCodeAt(i);
1396
+ hash = (hash << 5) - hash + char | 0;
1397
+ }
1398
+ return Math.abs((hash * 31 + sessionId.length) % Number.MAX_SAFE_INTEGER);
1399
+ }
1400
+ function isEnabled() {
1401
+ return process.env.ACTRAIL_ENABLED === "1" || process.env.ACTRAIL_ENABLED === "true";
1402
+ }
1403
+ function getStoragePath() {
1404
+ return process.env.ACTRAIL_STORAGE_PATH || "/tmp/actrail.sqlite";
1405
+ }
1406
+ function reportEnforcementEvent(opts) {
1407
+ if (!isEnabled()) return false;
1408
+ const storagePath = getStoragePath();
1409
+ if (!fs3.existsSync(storagePath)) return false;
1410
+ try {
1411
+ let Database;
1412
+ try {
1413
+ Database = __require("better-sqlite3");
1414
+ } catch {
1415
+ console.debug("[AgentMoss] better-sqlite3 not installed, AcTrail reporting disabled");
1416
+ return false;
1417
+ }
1418
+ const db = new Database(storagePath, { timeout: 100, readonly: false });
1419
+ const tableCheck = db.prepare(
1420
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='events'"
1421
+ ).get();
1422
+ if (!tableCheck) {
1423
+ db.close();
1424
+ return false;
1425
+ }
1426
+ const nowNs = BigInt(Date.now()) * 1000000n;
1427
+ const eventId = Number(nowNs % BigInt(2 ** 62));
1428
+ const traceIdInt = sessionToTraceId(opts.traceId);
1429
+ const rawFields = {
1430
+ "backend": "agent-moss",
1431
+ "operation": "tool_call",
1432
+ "decision": opts.decision,
1433
+ "result": opts.decision === "allow" ? "allowed" : "denied",
1434
+ "tool.name": opts.toolName,
1435
+ "tool.command": (opts.toolCommand || "").slice(0, 500),
1436
+ "agent.session_id": opts.sessionId,
1437
+ "agent.prompt": (opts.prompt || "").slice(0, 500),
1438
+ "risk.level": opts.riskLevel || "",
1439
+ "risk.type": opts.riskType || "",
1440
+ "reason": (opts.reason || "").slice(0, 500),
1441
+ "violated_layers": (opts.violatedLayers || []).join(","),
1442
+ "confidence": String(opts.confidence ?? 100),
1443
+ "duration_ms": (opts.durationMs ?? 0).toFixed(1)
1444
+ };
1445
+ const payloadFields = Object.entries(rawFields).map(([k, v]) => `${escape(k)}=${escape(v)}`).join("\n");
1446
+ db.prepare(`
1447
+ INSERT OR REPLACE INTO events (
1448
+ event_id, trace_id, observed_at,
1449
+ process_pid, process_task_id, process_start_ticks,
1450
+ process_pid_namespace, process_generation,
1451
+ collector, kind,
1452
+ bootstrap_observed, metadata_partial, policy_modified,
1453
+ payload_variant, payload_fields, payload_bytes,
1454
+ policy_verdict, policy_note,
1455
+ policy_redactions, policy_truncations
1456
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1457
+ `).run(
1458
+ eventId,
1459
+ traceIdInt,
1460
+ Number(nowNs),
1461
+ process.pid,
1462
+ 0,
1463
+ 0,
1464
+ null,
1465
+ 0,
1466
+ "agent-moss",
1467
+ "enforcement",
1468
+ 0,
1469
+ 0,
1470
+ 0,
1471
+ "enforcement",
1472
+ payloadFields,
1473
+ "",
1474
+ opts.decision === "allow" ? "allow" : "drop",
1475
+ opts.reason || "",
1476
+ "[]",
1477
+ "[]"
1478
+ );
1479
+ db.close();
1480
+ return true;
1481
+ } catch (err) {
1482
+ console.debug(`[AgentMoss] AcTrail write failed (non-fatal): ${err.message}`);
1483
+ return false;
1484
+ }
1485
+ }
1486
+
1487
+ // src/routes.ts
1239
1488
  async function healthRoute(c) {
1240
1489
  return c.json(HealthResponseSchema.parse({}));
1241
1490
  }
@@ -1264,6 +1513,23 @@ async function analyzeRoute(c) {
1264
1513
  cwd: req.cwd
1265
1514
  });
1266
1515
  const durationMs = performance.now() - t0;
1516
+ try {
1517
+ reportEnforcementEvent({
1518
+ traceId: req.session_id,
1519
+ decision: result.allowed ? "allow" : "deny",
1520
+ toolName: req.a_next.action_type,
1521
+ toolCommand: req.a_next.action_detail,
1522
+ sessionId: req.session_id,
1523
+ prompt: req.prompt_session,
1524
+ riskLevel: result.riskLevel,
1525
+ riskType: result.riskType,
1526
+ reason: result.reason,
1527
+ violatedLayers: result.violatedLayers,
1528
+ confidence: result.confidence,
1529
+ durationMs: Math.round(durationMs * 10) / 10
1530
+ });
1531
+ } catch {
1532
+ }
1267
1533
  return c.json(
1268
1534
  AnalyzeResponseSchema.parse({
1269
1535
  decision: result.allowed ? "Allow" : "Deny",
@@ -1323,7 +1589,7 @@ async function main() {
1323
1589
  switch (command) {
1324
1590
  case "init": {
1325
1591
  const output = args[2] || "input.json";
1326
- const fs2 = await import("fs");
1592
+ const fs4 = await import("fs");
1327
1593
  const template = {
1328
1594
  session_id: "my-session-001",
1329
1595
  prompt_session: "Read /var/log/app.log and analyze errors",
@@ -1333,7 +1599,7 @@ async function main() {
1333
1599
  os_type: "",
1334
1600
  cwd: "/home/user/project"
1335
1601
  };
1336
- fs2.writeFileSync(output, JSON.stringify(template, null, 2), "utf-8");
1602
+ fs4.writeFileSync(output, JSON.stringify(template, null, 2), "utf-8");
1337
1603
  console.log(`Template written to ${output}`);
1338
1604
  break;
1339
1605
  }
@@ -1342,11 +1608,11 @@ async function main() {
1342
1608
  console.error("Usage: agent-moss analyze <input.json>");
1343
1609
  process.exit(1);
1344
1610
  }
1345
- const fs2 = await import("fs");
1346
- const path2 = await import("path");
1611
+ const fs4 = await import("fs");
1612
+ const path3 = await import("path");
1347
1613
  const { runAnalysis: runAnalysis2 } = await Promise.resolve().then(() => (init_coordinator(), coordinator_exports));
1348
- const filePath = path2.resolve(args[1]);
1349
- const data = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
1614
+ const filePath = path3.resolve(args[1]);
1615
+ const data = JSON.parse(fs4.readFileSync(filePath, "utf-8"));
1350
1616
  const result = await runAnalysis2({
1351
1617
  sessionId: data.session_id || "unknown",
1352
1618
  promptSession: data.prompt_session || "",