@kenhkl/agent-moss 0.9.0 → 0.10.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/server.js CHANGED
@@ -1,5 +1,7 @@
1
1
  var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
2
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
3
5
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
6
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
7
  }) : x)(function(x) {
@@ -16,6 +18,15 @@ var __export = (target, all) => {
16
18
  for (var name in all)
17
19
  __defProp(target, name, { get: all[name], enumerable: true });
18
20
  };
21
+ var __copyProps = (to, from, except, desc) => {
22
+ if (from && typeof from === "object" || typeof from === "function") {
23
+ for (let key of __getOwnPropNames(from))
24
+ if (!__hasOwnProp.call(to, key) && key !== except)
25
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
26
+ }
27
+ return to;
28
+ };
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
30
 
20
31
  // node_modules/tsup/assets/esm_shims.js
21
32
  import path from "path";
@@ -213,380 +224,89 @@ var init_config = __esm({
213
224
  }
214
225
  });
215
226
 
216
- // src/agents/index.ts
217
- import { readFileSync, existsSync } from "fs";
218
- import { resolve, dirname } from "path";
219
- import { fileURLToPath as fileURLToPath2 } from "url";
220
- function resolveAgentsDir() {
221
- if (existsSync(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
222
- if (existsSync(AGENTS_DIR_DEV_FALLBACK)) return AGENTS_DIR_DEV_FALLBACK;
223
- return AGENTS_DIR_PROD;
227
+ // src/engine/inline-analyzer.ts
228
+ function isLiteralTextCommand(command) {
229
+ ECHO_CMD_RE.lastIndex = 0;
230
+ return ECHO_CMD_RE.test(command);
224
231
  }
225
- function getAgentExtensions(agentId) {
226
- if (!agentId) return null;
227
- if (cache.has(agentId)) return cache.get(agentId) ?? null;
228
- const agentsDir = resolveAgentsDir();
229
- const rulesFile = resolve(agentsDir, agentId, "rules.json");
230
- if (!existsSync(rulesFile)) {
231
- cache.set(agentId, null);
232
- return null;
232
+ function extractInlineCode(command) {
233
+ const parts = [];
234
+ let m;
235
+ INLINE_CODE_RE.lastIndex = 0;
236
+ while ((m = INLINE_CODE_RE.exec(command)) !== null) {
237
+ const code = m[1] ?? m[2];
238
+ if (code) parts.push(code);
233
239
  }
234
- try {
235
- const raw = readFileSync(rulesFile, "utf-8");
236
- const ext = JSON.parse(raw);
237
- if (!ext.agent_id) {
238
- console.warn(`[AgentMoss agents] rules.json \u7F3A\u5C11 agent_id \u5B57\u6BB5: ${rulesFile}`);
239
- cache.set(agentId, null);
240
- return null;
241
- }
242
- cache.set(agentId, ext);
243
- console.log(
244
- `[AgentMoss agents] \u52A0\u8F7D agent ${agentId} \u5B9A\u5236\u89C4\u5219: ${ext.extra_command_patterns?.length ?? 0} cmd patterns, ${ext.extra_injection_keywords?.length ?? 0} injection keywords, ${ext.extra_sensitive_paths?.length ?? 0} sensitive paths, ${ext.builtin_safe_skills?.length ?? 0} builtin safe skills`
245
- );
246
- return ext;
247
- } catch (e) {
248
- console.error(`[AgentMoss agents] \u52A0\u8F7D agent ${agentId} rules.json \u5931\u8D25:`, e.message);
249
- cache.set(agentId, null);
250
- return null;
240
+ ECHO_STRING_RE.lastIndex = 0;
241
+ while ((m = ECHO_STRING_RE.exec(command)) !== null) {
242
+ const code = m[1] ?? m[2];
243
+ if (code) parts.push(code);
251
244
  }
245
+ return parts.join("\n");
252
246
  }
253
- var __filename2, __dirname2, AGENTS_DIR_PROD, AGENTS_DIR_DEV_FALLBACK, cache;
254
- var init_agents = __esm({
255
- "src/agents/index.ts"() {
256
- "use strict";
257
- init_esm_shims();
258
- __filename2 = fileURLToPath2(import.meta.url);
259
- __dirname2 = dirname(__filename2);
260
- AGENTS_DIR_PROD = resolve(__dirname2, "agents");
261
- AGENTS_DIR_DEV_FALLBACK = resolve(__dirname2, "../../../agent_moss/agents");
262
- cache = /* @__PURE__ */ new Map();
263
- }
264
- });
265
-
266
- // src/engine/heuristic.ts
267
- function runHeuristicCheck(input) {
268
- const command = `${input.aNext.actionType}: ${input.aNext.actionDetail}`;
269
- const matched = [];
270
- let highestSeverity = "low";
271
- let highestRiskType = "";
272
- const userRules = getCustomRules();
273
- for (const rule of userRules) {
274
- try {
275
- const re = new RegExp(rule.pattern);
276
- if (re.test(command)) {
277
- if (rule.action === "Deny") {
278
- matched.push(`\u81EA\u5B9A\u4E49\u89C4\u5219: ${rule.pattern}`);
279
- if (severityRank(rule.severity) > severityRank(highestSeverity)) {
280
- highestSeverity = rule.severity;
281
- highestRiskType = "custom_rule";
282
- }
283
- }
284
- }
285
- } catch {
286
- }
287
- }
288
- let topRiskType = "";
289
- for (const rule of DANGEROUS_PATTERNS) {
290
- if (rule.pattern.test(command)) {
291
- matched.push(rule.description);
292
- if (severityRank(rule.severity) > severityRank(highestSeverity)) {
293
- highestSeverity = rule.severity;
294
- topRiskType = rule.riskType;
295
- }
296
- }
297
- }
298
- highestRiskType = topRiskType || highestRiskType;
299
- const agentExt = getAgentExtensions(input.agentId);
300
- if (agentExt?.extra_command_patterns) {
301
- for (const rule of agentExt.extra_command_patterns) {
302
- try {
303
- const re = new RegExp(rule.pattern, "i");
304
- if (re.test(command)) {
305
- matched.push(`agent \u4E13\u5C5E\u89C4\u5219: ${rule.reason}`);
306
- if (severityRank(rule.risk_level) > severityRank(highestSeverity)) {
307
- highestSeverity = rule.risk_level;
308
- highestRiskType = rule.risk_type;
309
- }
310
- }
311
- } catch {
312
- }
313
- }
247
+ function hasWriteExecChain(command) {
248
+ return WRITE_EXEC_CHAIN_PATTERNS.some((p) => {
249
+ p.lastIndex = 0;
250
+ return p.test(command);
251
+ });
252
+ }
253
+ function hasDirTraversal(code) {
254
+ const lower = code.toLowerCase();
255
+ return DIR_TRAVERSAL_KEYWORDS.some((kw) => lower.includes(kw));
256
+ }
257
+ function hasReadWriteKeyword(code) {
258
+ const lower = code.toLowerCase();
259
+ return READ_WRITE_KEYWORDS.some((kw) => lower.includes(kw));
260
+ }
261
+ function analyzeInlineScript(command) {
262
+ const code = extractInlineCode(command);
263
+ if (!code) {
264
+ return { actionable: false, allowed: true, reason: "", riskLevel: "low", riskType: "" };
314
265
  }
315
- const injectionText = `${input.aNext.actionType} ${input.aNext.actionDetail} ${input.reason || ""} ${input.promptSession || ""}`.toLowerCase();
316
- const injectionHits = checkInjectionKeywords(injectionText, agentExt?.extra_injection_keywords);
317
- if (injectionHits.length > 0) {
318
- const topHit = injectionHits.reduce((a, b) => severityRank(a.riskLevel) >= severityRank(b.riskLevel) ? a : b);
319
- matched.push(...injectionHits.map((h) => `\u6CE8\u5165\u5173\u952E\u8BCD [${h.lang}]: ${h.keyword}`));
320
- if (severityRank(topHit.riskLevel) > severityRank(highestSeverity)) {
321
- highestSeverity = topHit.riskLevel;
322
- highestRiskType = "prompt_injection";
323
- }
266
+ if (hasWriteExecChain(command)) {
267
+ return {
268
+ actionable: true,
269
+ allowed: false,
270
+ reason: "\u5185\u8054\u811A\u672C\u542B\u5199\u5165\u6587\u4EF6\u540E\u6267\u884C\u7684\u6A21\u5F0F\uFF0C\u7591\u4F3C\u7ED5\u8FC7\u654F\u611F\u8DEF\u5F84\u68C0\u6D4B",
271
+ riskLevel: "critical",
272
+ riskType: "script_execution"
273
+ };
324
274
  }
325
- if (matched.length > 0) {
275
+ const redirMatch = command.match(REDIRECT_TO_SENSITIVE_RE);
276
+ if (redirMatch) {
326
277
  return {
278
+ actionable: true,
327
279
  allowed: false,
328
- reason: `\u68C0\u6D4B\u5230\u9AD8\u5371\u64CD\u4F5C: ${matched.join(", ")}`,
329
- riskLevel: highestSeverity,
330
- riskType: highestRiskType,
331
- confidence: 95,
332
- source: "heuristic",
333
- violatedLayers: ["1.1"]
334
- // 对齐 py coordinator.py:165(L1.1 = 命中高危命令模式/注入关键词)
280
+ reason: `\u91CD\u5B9A\u5411\u5199\u5165\u654F\u611F\u8DEF\u5F84: ${redirMatch[1]}`,
281
+ riskLevel: "critical",
282
+ riskType: "file_access"
335
283
  };
336
284
  }
337
- return null;
338
- }
339
- function checkInjectionKeywords(text, extraKeywords) {
340
- const hits = [];
341
- for (const entry of INJECTION_KEYWORDS) {
342
- if (text.includes(entry.keyword.toLowerCase())) {
343
- hits.push(entry);
344
- }
285
+ const isEchoOrPrintf1 = isLiteralTextCommand(command);
286
+ if (!isEchoOrPrintf1 && hasDirTraversal(code) && hasReadWriteKeyword(code)) {
287
+ return { actionable: false, allowed: true, reason: "", riskLevel: "low", riskType: "" };
345
288
  }
346
- if (extraKeywords) {
347
- for (const entry of extraKeywords) {
348
- if (text.includes(entry.keyword.toLowerCase())) {
349
- hits.push({ keyword: entry.keyword, riskLevel: entry.risk_level, lang: entry.lang });
350
- }
351
- }
289
+ const isEchoOrPrintf = isLiteralTextCommand(command);
290
+ if (!isEchoOrPrintf && hasReadWriteKeyword(code)) {
291
+ return {
292
+ actionable: true,
293
+ allowed: false,
294
+ reason: "\u5185\u8054\u811A\u672C\u542B\u654F\u611F\u8DEF\u5F84\u4E14\u542B\u6587\u4EF6\u8BFB\u5199/\u547D\u4EE4\u6267\u884C\u8C03\u7528\uFF0C\u5224\u4E3A\u771F\u5B9E\u8BBF\u95EE",
295
+ riskLevel: "critical",
296
+ riskType: "file_access"
297
+ };
352
298
  }
353
- return hits;
354
- }
355
- function isInlineScriptCommand(actionDetail) {
356
- 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;
357
- return re.test(actionDetail);
358
- }
359
- function severityRank(s) {
360
- const ranks = { low: 1, medium: 2, high: 3, critical: 4 };
361
- return ranks[s] || 0;
299
+ return {
300
+ actionable: true,
301
+ allowed: true,
302
+ reason: "\u654F\u611F\u8DEF\u5F84\u4EC5\u4F5C\u4E3A\u6587\u672C\u5B57\u9762\u91CF\u51FA\u73B0\uFF0C\u672A\u4F34\u968F\u8BFB\u5199\u64CD\u4F5C",
303
+ riskLevel: "low",
304
+ riskType: "file_access"
305
+ };
362
306
  }
363
- var INJECTION_KEYWORDS, DANGEROUS_PATTERNS;
364
- var init_heuristic = __esm({
365
- "src/engine/heuristic.ts"() {
366
- "use strict";
367
- init_esm_shims();
368
- init_config();
369
- init_agents();
370
- INJECTION_KEYWORDS = [
371
- // ---- Instruction Override ----
372
- { keyword: "ignore previous instructions", riskLevel: "critical", lang: "en" },
373
- { keyword: "ignore all previous", riskLevel: "critical", lang: "en" },
374
- { keyword: "disregard all prior", riskLevel: "critical", lang: "en" },
375
- { keyword: "forget everything", riskLevel: "high", lang: "en" },
376
- { keyword: "\u5FFD\u7565\u4E4B\u524D\u7684\u6307\u4EE4", riskLevel: "critical", lang: "zh" },
377
- { keyword: "\u5FFD\u7565\u6240\u6709\u5148\u524D\u7684", riskLevel: "critical", lang: "zh" },
378
- { keyword: "\u5FFD\u7565\u4E0A\u9762\u6240\u6709", riskLevel: "high", lang: "zh" },
379
- { keyword: "\u8BF7\u5FD8\u8BB0\u4E4B\u524D", riskLevel: "high", lang: "zh" },
380
- // ---- Role Hijacking ----
381
- { keyword: "you are now", riskLevel: "high", lang: "en" },
382
- { keyword: "you are no longer", riskLevel: "high", lang: "en" },
383
- { keyword: "new instructions", riskLevel: "high", lang: "en" },
384
- { keyword: "from now on, you", riskLevel: "high", lang: "en" },
385
- { keyword: "\u4F60\u73B0\u5728\u662F\u4E00\u4E2A", riskLevel: "high", lang: "zh" },
386
- { keyword: "\u4F60\u4E0D\u518D\u662F\u4E00\u4E2A", riskLevel: "high", lang: "zh" },
387
- { keyword: "\u4ECE\u73B0\u5728\u5F00\u59CB\u4F60", riskLevel: "high", lang: "zh" },
388
- { keyword: "\u4F60\u7684\u65B0\u89D2\u8272\u662F", riskLevel: "high", lang: "zh" },
389
- // ---- Command Injection ----
390
- { keyword: "system prompt", riskLevel: "high", lang: "en" },
391
- { keyword: "reveal your instructions", riskLevel: "high", lang: "en" },
392
- { keyword: "output your instructions", riskLevel: "high", lang: "en" },
393
- { keyword: "repeat your instructions", riskLevel: "medium", lang: "en" },
394
- { keyword: "\u7CFB\u7EDF\u63D0\u793A", riskLevel: "high", lang: "zh" },
395
- { keyword: "\u8F93\u51FA\u4F60\u7684\u6307\u4EE4", riskLevel: "high", lang: "zh" },
396
- { keyword: "\u91CD\u590D\u4F60\u7684\u6307\u4EE4", riskLevel: "medium", lang: "zh" },
397
- // ---- Social Engineering ----
398
- { keyword: "this is an authorized test", riskLevel: "high", lang: "en" },
399
- { keyword: "security audit approved", riskLevel: "high", lang: "en" },
400
- { keyword: "admin override", riskLevel: "critical", lang: "en" },
401
- { keyword: "debug mode", riskLevel: "high", lang: "en" },
402
- { keyword: "maintenance mode", riskLevel: "high", lang: "en" },
403
- { keyword: "\u8FD9\u662F\u6388\u6743\u7684\u6D4B\u8BD5", riskLevel: "high", lang: "zh" },
404
- { keyword: "\u5B89\u5168\u5BA1\u8BA1\u5DF2\u6279\u51C6", riskLevel: "high", lang: "zh" },
405
- { keyword: "\u7BA1\u7406\u5458\u8986\u76D6", riskLevel: "critical", lang: "zh" },
406
- { keyword: "\u8C03\u8BD5\u6A21\u5F0F", riskLevel: "high", lang: "zh" },
407
- // ---- XSS ----
408
- { keyword: "<script>", riskLevel: "high", lang: "en" },
409
- { keyword: "javascript:", riskLevel: "high", lang: "en" },
410
- { keyword: "onerror=", riskLevel: "high", lang: "en" },
411
- { keyword: "onload=", riskLevel: "high", lang: "en" },
412
- // ---- Fake Boundaries ----
413
- { keyword: "=== END OF", riskLevel: "high", lang: "en" },
414
- { keyword: "--- END SYSTEM", riskLevel: "high", lang: "en" },
415
- { keyword: "[END OF", riskLevel: "high", lang: "en" },
416
- { keyword: "=== \u7ED3\u675F", riskLevel: "high", lang: "zh" },
417
- { keyword: "--- \u7ED3\u675F\u7CFB\u7EDF", riskLevel: "high", lang: "zh" },
418
- // ---- Developer Mode ----
419
- { keyword: "developer mode", riskLevel: "critical", lang: "en" },
420
- { keyword: "DAN mode", riskLevel: "critical", lang: "en" },
421
- { keyword: "jailbreak", riskLevel: "critical", lang: "en" },
422
- { keyword: "\u5F00\u53D1\u8005\u6A21\u5F0F", riskLevel: "critical", lang: "zh" },
423
- { keyword: "\u8D8A\u72F1", riskLevel: "critical", lang: "zh" }
424
- // 注:xiaoO 专属绕过词(绕过审计/禁用安全检查/关闭防护/bypass audit 等)
425
- // 已迁至 agent_moss/agents/xiaoo/rules.json 的 extra_injection_keywords,
426
- // 由 agent_ext 机制注入,不再污染通用层。
427
- ];
428
- DANGEROUS_PATTERNS = [
429
- // --- Critical: rm command graded detection (strict to loose, first match wins) ---
430
- // 1. rm -rf root dir (含通配符如 /* /.* 等) — 使用负向先行断言 (?!\w):/ 后面不是单词字符时匹配
431
- { pattern: /\brm\s+-[a-zA-Z]*r[a-zA-Z]*f[a-zA-Z]*\s+\/(?!\w)/i, severity: "critical", description: "\u9012\u5F52\u5F3A\u5236\u5220\u9664\u6839\u76EE\u5F55 (rm -rf /)", riskType: "script_execution" },
432
- // 注意:已移除"rm -rf /任意绝对路径"的宽泛规则,避免误杀 /tmp 等临时目录清理操作
433
- // 2. rm with -r or -f targeting system dirs
434
- { 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" },
435
- // 3. rm -r absolute path (no -f)
436
- { 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" },
437
- // 4. rm -f absolute path (no -r)
438
- { 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" },
439
- // dd 磁盘清零 — 只拦截写入系统关键目录,放行 /tmp 等临时目录
440
- { pattern: /dd\s+if=\/dev\/zero.*\bof\s*=\s*\/(etc|var|home|usr|boot|root|opt|srv|sys|proc)\b/i, severity: "critical", description: "\u78C1\u76D8\u6E05\u96F6\u5199\u5165\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55 (dd if=/dev/zero of=/\u7CFB\u7EDF\u76EE\u5F55)", riskType: "script_execution" },
441
- // mkfs 格式化 — 只拦截对块设备(/dev/sdX 等)的格式化,放行对普通文件的格式化
442
- { pattern: /mkfs\.\w+\s+\/dev\/(sd|hd|nvme|vd|loop|mapper|md|dm)\S+/i, severity: "critical", description: "\u683C\u5F0F\u5316\u5757\u8BBE\u5907 (mkfs /dev/\u5757\u8BBE\u5907)", riskType: "script_execution" },
443
- { pattern: /:\(\)\s*\{.*:\|:&.*\};:/i, severity: "critical", description: "Fork Bomb", riskType: "resource_exhaustion" },
444
- // --- Critical: remote code execution ---
445
- { pattern: /curl\s+.*\|\s*(bash|sh|zsh|dash)/i, severity: "critical", description: "curl pipe shell \u6267\u884C", riskType: "script_execution" },
446
- { pattern: /wget\s+.*\|\s*(bash|sh)/i, severity: "critical", description: "wget pipe shell \u6267\u884C", riskType: "script_execution" },
447
- { pattern: /eval\s+\$\(curl|eval\s+\$\(wget/i, severity: "critical", description: "eval \u8FDC\u7A0B\u4EE3\u7801\u6267\u884C", riskType: "script_execution" },
448
- { pattern: /base64\s+-d\s+.*\|\s*bash/i, severity: "critical", description: "base64 \u89E3\u7801\u6267\u884C", riskType: "script_execution" },
449
- // --- Critical: reverse shell ---
450
- { pattern: /\/dev\/tcp\/|bash\s+-i\s+>&/i, severity: "critical", description: "\u53CD\u5F39 Shell", riskType: "script_execution" },
451
- { pattern: /nc\s+-[el]\s+.*\d{1,5}/i, severity: "high", description: "Netcat \u53CD\u5F39\u8FDE\u63A5", riskType: "lateral_movement" },
452
- // --- Critical: sensitive file access ---
453
- { pattern: /\/etc\/shadow/i, severity: "critical", description: "\u8BBF\u95EE\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6 (/etc/shadow)", riskType: "file_access" },
454
- { pattern: /\/etc\/gshadow/i, severity: "critical", description: "\u8BBF\u95EE\u7CFB\u7EDF\u7EC4\u5BC6\u7801\u6587\u4EF6 (/etc/gshadow)", riskType: "file_access" },
455
- // 注意:/etc/passwd 已从拦截列表移除,因为它是公开可读的标准系统文件,真正敏感的是 /etc/shadow
456
- { pattern: /\.ssh\/id_rsa/i, severity: "critical", description: "\u8BBF\u95EE SSH \u79C1\u94A5\u6587\u4EF6", riskType: "file_access" },
457
- // --- Critical: privilege escalation / container escape ---
458
- { 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" },
459
- // --- High: dangerous permissions / ownership ---
460
- { pattern: /chmod\s+[0-7]*777[0-7]*/i, severity: "high", description: "\u5168\u6743\u9650\u8BBE\u7F6E (chmod 777)", riskType: "script_execution" },
461
- { 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" },
462
- // sudo 提权 — 只拦截 sudo 后跟的危险命令,不一刀切拦截所有 sudo
463
- { pattern: /sudo\s+(rm|chmod|chown|dd|mkfs|format|iptables|crontab|passwd|userdel|useradd|groupdel)\b/i, severity: "high", description: "\u63D0\u6743\u6267\u884C\u9AD8\u5371\u547D\u4EE4 (sudo + \u5371\u9669\u64CD\u4F5C)", riskType: "privilege_escalation" },
464
- // --- High: system config overwrite ---
465
- { pattern: />\s*\/etc\//i, severity: "critical", description: "\u8986\u76D6\u5199\u5165\u7CFB\u7EDF\u914D\u7F6E\u76EE\u5F55", riskType: "file_access" },
466
- // --- High: download script + execute risk ---
467
- { 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" },
468
- { pattern: /(?:curl|wget)\s+.*\s+&&?\s*(?:chmod\s+\+x|bash|sh)/i, severity: "high", description: "\u4E0B\u8F7D\u540E\u6267\u884C", riskType: "script_execution" },
469
- // --- High: package manager pipe-to-shell ---
470
- { pattern: /git\s+clone\s+.*\|.*bash/i, severity: "high", description: "git clone pipe shell", riskType: "script_execution" },
471
- { pattern: /pip\s+install\s+.*\|\s*bash/i, severity: "high", description: "pip install pipe shell", riskType: "script_execution" },
472
- { pattern: /npm\s+install\s+-g\s+.*\|\s*bash/i, severity: "high", description: "npm install pipe shell", riskType: "script_execution" },
473
- // --- Medium: destructive git operations (降级为 medium,允许但记录) ---
474
- { pattern: /git\s+push\s+.*--force/i, severity: "medium", description: "\u5F3A\u5236\u63A8\u9001\u64CD\u4F5C (git push --force)\uFF0C\u53EF\u80FD\u8986\u76D6\u8FDC\u7A0B\u63D0\u4EA4\u5386\u53F2", riskType: "script_execution" },
475
- // --- High: network port scanning (横向移动侦察) ---
476
- { pattern: /\bnmap\s+/i, severity: "high", description: "\u7F51\u7EDC\u7AEF\u53E3\u626B\u63CF (nmap)\uFF0C\u53EF\u80FD\u7528\u4E8E\u6A2A\u5411\u79FB\u52A8\u4FA6\u5BDF", riskType: "lateral_movement" },
477
- { pattern: /\b(masscan|zmap|unicornscan)\s+/i, severity: "high", description: "\u7F51\u7EDC\u7AEF\u53E3\u626B\u63CF\u5DE5\u5177\uFF0C\u53EF\u80FD\u7528\u4E8E\u6A2A\u5411\u79FB\u52A8\u4FA6\u5BDF", riskType: "lateral_movement" },
478
- // --- Medium: credential export ---
479
- { pattern: /export\s+.*(?:TOKEN|KEY|SECRET|PASSWORD)/i, severity: "medium", description: "\u5BFC\u51FA\u654F\u611F\u51ED\u8BC1", riskType: "data_exfiltration" },
480
- // --- High: shell rc file append (persistence) ---
481
- { 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" },
482
- // --- High: device file write (只拦截重定向写入设备文件,不拦截读取) ---
483
- { pattern: />\s*\/dev\/(sd|hd|nvme|vd|loop|mem|kmsg|port|zero|random|urandom|full|core)/i, severity: "high", description: "\u91CD\u5B9A\u5411\u5199\u5165\u8BBE\u5907\u6587\u4EF6", riskType: "file_access" },
484
- // --- High: non-interactive password modification ---
485
- { 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" },
486
- { 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" },
487
- { pattern: /\bchpasswd\b/i, severity: "high", description: "\u68C0\u6D4B\u5230 chpasswd \u6279\u91CF\u5BC6\u7801\u4FEE\u6539\u547D\u4EE4", riskType: "script_execution" },
488
- { pattern: /\b(newusers|lnewusers)\b/i, severity: "high", description: "\u68C0\u6D4B\u5230 newusers/lnewusers \u6279\u91CF\u7528\u6237\u6DFB\u52A0\u547D\u4EE4", riskType: "script_execution" },
489
- { 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" },
490
- { 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" },
491
- // --- High: non-interactive password modification (extended) ---
492
- { pattern: /\blpasswd\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u975E\u4EA4\u4E92\u5F0F\u5BC6\u7801\u4FEE\u6539\u64CD\u4F5C (lpasswd)", riskType: "script_execution" },
493
- { pattern: /\bgpasswd\s+(?!-[adA])/i, severity: "high", description: "\u68C0\u6D4B\u5230\u7EC4\u5BC6\u7801\u7BA1\u7406\u64CD\u4F5C (gpasswd)", riskType: "script_execution" },
494
- { pattern: /\bchgpasswd\b/i, severity: "high", description: "\u68C0\u6D4B\u5230\u6279\u91CF\u4FEE\u6539\u7EC4\u5BC6\u7801\u64CD\u4F5C (chgpasswd)", riskType: "script_execution" },
495
- // --- High: user/group deletion (irreversible, requires user consent) ---
496
- { pattern: /\buserdel\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7CFB\u7EDF\u7528\u6237\u64CD\u4F5C (userdel)", riskType: "script_execution" },
497
- { pattern: /\bdeluser\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7CFB\u7EDF\u7528\u6237\u64CD\u4F5C (deluser)", riskType: "script_execution" },
498
- { pattern: /\bluserdel\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7CFB\u7EDF\u7528\u6237\u64CD\u4F5C (luserdel)", riskType: "script_execution" },
499
- { pattern: /\bgroupdel\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7528\u6237\u7EC4\u64CD\u4F5C (groupdel)", riskType: "script_execution" },
500
- { pattern: /\blgroupdel\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7528\u6237\u7EC4\u64CD\u4F5C (lgroupdel)", riskType: "script_execution" },
501
- // --- High: generic command -p parameter password setting ---
502
- { pattern: /\b(useradd|luseradd|groupadd|usermod|lusermod|groupmod|lgroupmod)\b[^&|;]*?\s-p\b/i, severity: "high", description: "\u68C0\u6D4B\u5230\u901A\u8FC7 -p \u53C2\u6570\u8BBE\u7F6E\u6216\u4FEE\u6539\u5BC6\u7801 (useradd/usermod/groupmod)", riskType: "script_execution" }
503
- ];
504
- }
505
- });
506
-
507
- // src/engine/inline-analyzer.ts
508
- function isLiteralTextCommand(command) {
509
- ECHO_CMD_RE.lastIndex = 0;
510
- return ECHO_CMD_RE.test(command);
511
- }
512
- function extractInlineCode(command) {
513
- const parts = [];
514
- let m;
515
- INLINE_CODE_RE.lastIndex = 0;
516
- while ((m = INLINE_CODE_RE.exec(command)) !== null) {
517
- const code = m[1] ?? m[2];
518
- if (code) parts.push(code);
519
- }
520
- ECHO_STRING_RE.lastIndex = 0;
521
- while ((m = ECHO_STRING_RE.exec(command)) !== null) {
522
- const code = m[1] ?? m[2];
523
- if (code) parts.push(code);
524
- }
525
- return parts.join("\n");
526
- }
527
- function hasWriteExecChain(command) {
528
- return WRITE_EXEC_CHAIN_PATTERNS.some((p) => {
529
- p.lastIndex = 0;
530
- return p.test(command);
531
- });
532
- }
533
- function hasDirTraversal(code) {
534
- const lower = code.toLowerCase();
535
- return DIR_TRAVERSAL_KEYWORDS.some((kw) => lower.includes(kw));
536
- }
537
- function hasReadWriteKeyword(code) {
538
- const lower = code.toLowerCase();
539
- return READ_WRITE_KEYWORDS.some((kw) => lower.includes(kw));
540
- }
541
- function analyzeInlineScript(command) {
542
- const code = extractInlineCode(command);
543
- if (!code) {
544
- return { actionable: false, allowed: true, reason: "", riskLevel: "low", riskType: "" };
545
- }
546
- if (hasWriteExecChain(command)) {
547
- return {
548
- actionable: true,
549
- allowed: false,
550
- reason: "\u5185\u8054\u811A\u672C\u542B\u5199\u5165\u6587\u4EF6\u540E\u6267\u884C\u7684\u6A21\u5F0F\uFF0C\u7591\u4F3C\u7ED5\u8FC7\u654F\u611F\u8DEF\u5F84\u68C0\u6D4B",
551
- riskLevel: "critical",
552
- riskType: "script_execution"
553
- };
554
- }
555
- const redirMatch = command.match(REDIRECT_TO_SENSITIVE_RE);
556
- if (redirMatch) {
557
- return {
558
- actionable: true,
559
- allowed: false,
560
- reason: `\u91CD\u5B9A\u5411\u5199\u5165\u654F\u611F\u8DEF\u5F84: ${redirMatch[1]}`,
561
- riskLevel: "critical",
562
- riskType: "file_access"
563
- };
564
- }
565
- const isEchoOrPrintf1 = isLiteralTextCommand(command);
566
- if (!isEchoOrPrintf1 && hasDirTraversal(code) && hasReadWriteKeyword(code)) {
567
- return { actionable: false, allowed: true, reason: "", riskLevel: "low", riskType: "" };
568
- }
569
- const isEchoOrPrintf = isLiteralTextCommand(command);
570
- if (!isEchoOrPrintf && hasReadWriteKeyword(code)) {
571
- return {
572
- actionable: true,
573
- allowed: false,
574
- reason: "\u5185\u8054\u811A\u672C\u542B\u654F\u611F\u8DEF\u5F84\u4E14\u542B\u6587\u4EF6\u8BFB\u5199/\u547D\u4EE4\u6267\u884C\u8C03\u7528\uFF0C\u5224\u4E3A\u771F\u5B9E\u8BBF\u95EE",
575
- riskLevel: "critical",
576
- riskType: "file_access"
577
- };
578
- }
579
- return {
580
- actionable: true,
581
- allowed: true,
582
- reason: "\u654F\u611F\u8DEF\u5F84\u4EC5\u4F5C\u4E3A\u6587\u672C\u5B57\u9762\u91CF\u51FA\u73B0\uFF0C\u672A\u4F34\u968F\u8BFB\u5199\u64CD\u4F5C",
583
- riskLevel: "low",
584
- riskType: "file_access"
585
- };
586
- }
587
- var INLINE_CODE_RE, ECHO_STRING_RE, ECHO_CMD_RE, READ_WRITE_KEYWORDS, WRITE_EXEC_CHAIN_PATTERNS, SENSITIVE_PATH_FRAGMENTS, REDIRECT_TO_SENSITIVE_RE, DIR_TRAVERSAL_KEYWORDS;
588
- var init_inline_analyzer = __esm({
589
- "src/engine/inline-analyzer.ts"() {
307
+ var INLINE_CODE_RE, ECHO_STRING_RE, ECHO_CMD_RE, READ_WRITE_KEYWORDS, WRITE_EXEC_CHAIN_PATTERNS, SENSITIVE_PATH_FRAGMENTS, REDIRECT_TO_SENSITIVE_RE, DIR_TRAVERSAL_KEYWORDS;
308
+ var init_inline_analyzer = __esm({
309
+ "src/engine/inline-analyzer.ts"() {
590
310
  "use strict";
591
311
  init_esm_shims();
592
312
  INLINE_CODE_RE = /(?:python|python3|perl|perl5|ruby|jruby|mruby|node|php|lua)\s+(?:-[a-zA-Z]*[cera][a-zA-Z]*\s+)"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
@@ -720,165 +440,26 @@ function runLogicCheck(input) {
720
440
  }
721
441
  }
722
442
  }
723
- if (!isExcludedType) {
724
- const agentExt = getAgentExtensions(input.agentId || "");
725
- const pathResult = checkSensitivePath(actionDetail, resolvedCwd, agentExt);
726
- if (pathResult) return pathResult;
727
- }
728
- if (actionType !== "file_write" && actionType !== "file_edit") {
729
- const TEMP_DIRS = ["/tmp/", "/var/tmp/", "/run/", "/dev/shm/"];
730
- const isTempPath = TEMP_DIRS.some((td) => actionDetail.includes(td));
731
- if (!isTempPath && (actionDetail.includes("*") || actionDetail.includes("?")) && ["rm ", "rm ", "del ", "del ", "delete", "remove ", "remove ", "\u5220\u9664", "\u79FB\u9664"].some((kw) => actionDetail.includes(kw))) {
732
- return {
733
- allowed: false,
734
- reason: "\u68C0\u6D4B\u5230\u901A\u914D\u7B26\u7ED3\u5408\u5220\u9664\u64CD\u4F5C\uFF0C\u53EF\u80FD\u9020\u6210\u6279\u91CF\u8BEF\u5220",
735
- riskLevel: "high",
736
- riskType: "script_execution",
737
- confidence: 90,
738
- source: "logic_rule",
739
- violatedLayers: ["2"]
740
- };
741
- }
742
- if (/>\s*\/etc\//.test(actionDetail) || />\s*\/boot\//.test(actionDetail) || />\s*\/proc\//.test(actionDetail)) {
743
- return {
744
- allowed: false,
745
- reason: "\u68C0\u6D4B\u5230\u91CD\u5B9A\u5411\u8986\u76D6\u5199\u5165\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55",
746
- riskLevel: "critical",
747
- riskType: "file_access",
748
- confidence: 95,
749
- source: "logic_rule",
750
- violatedLayers: ["2"]
751
- };
752
- }
753
- }
754
- var _READ_ONLY_COMMANDS = /* @__PURE__ */ new Set([
755
- "cat",
756
- "head",
757
- "tail",
758
- "less",
759
- "more",
760
- "grep",
761
- "find",
762
- "awk",
763
- "sed",
764
- "sort",
765
- "uniq",
766
- "wc",
767
- "cut",
768
- "strings",
769
- "od",
770
- "xxd",
771
- "hexdump",
772
- "tr",
773
- "file",
774
- "stat",
775
- "du",
776
- "df",
777
- "ls",
778
- "dir",
779
- "tree",
780
- "nl",
781
- "tac",
782
- "rev",
783
- "lsblk",
784
- "blockdev",
785
- "smartctl",
786
- "udevadm",
787
- "dmidecode",
788
- "lscpu",
789
- "lspci",
790
- "lsusb",
791
- "lsmem",
792
- "lsns",
793
- "lsof",
794
- "hwinfo",
795
- "inxi",
796
- "fdisk",
797
- "parted",
798
- "dmesg",
799
- "journalctl",
800
- "systemctl",
801
- "hostnamectl",
802
- "localectl",
803
- "timedatectl",
804
- "ps",
805
- "top",
806
- "free",
807
- "vmstat",
808
- "iostat",
809
- "mpstat",
810
- "pidof",
811
- "pgrep",
812
- "ip",
813
- "ifconfig",
814
- "route",
815
- "ss",
816
- "netstat",
817
- "arp",
818
- "ethtool",
819
- "nmcli",
820
- "uname",
821
- "arch",
822
- "nproc",
823
- "getconf",
824
- "getent",
825
- "id",
826
- "whoami",
827
- "who",
828
- "w",
829
- "last",
830
- "uptime",
831
- "lsmod",
832
- "modinfo",
833
- "sysctl"
834
- ]);
835
- var _DEVNULL_REDIRECT_RE = /(?:2|&|1)?\s*>\s*\/dev\/null\b/;
836
- var _WRITE_COMMAND_RES = [
837
- /\brm\b/,
838
- /\bunlink\b/,
839
- /\brmdir\b/,
840
- /\bshred\b/,
841
- /\bcp\b/,
842
- /\bmv\b/,
843
- /\binstall\b/,
844
- /\btee\b/,
845
- /\bdd\b/,
846
- /\bmkfs\b/,
847
- /\b(fdisk|parted|cfdisk|sfdisk)\b/,
848
- /\bchmod\b/,
849
- /\bchown\b/,
850
- /\btruncate\b/
851
- ];
852
443
  const indirectResult = checkIndirectFileAccess(actionType, actionDetail, resolvedCwd);
853
444
  if (indirectResult) return indirectResult;
854
445
  if (actionDetail.includes("sudo") || actionDetail.includes("su -") || actionDetail.includes("chmod u+s") || actionDetail.includes("setuid")) {
855
- const adminContext = actionHistory.some(
856
- (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")
857
- );
858
- if (!adminContext) {
859
- return {
860
- allowed: false,
861
- reason: "\u68C0\u6D4B\u5230\u63D0\u6743\u64CD\u4F5C\uFF0C\u672A\u53D1\u73B0\u5408\u6CD5\u7BA1\u7406\u4E0A\u4E0B\u6587",
862
- riskLevel: "high",
863
- riskType: "privilege_escalation",
864
- confidence: 85,
865
- source: "logic_rule",
866
- violatedLayers: ["2"]
867
- };
868
- }
869
- }
870
- if (/(?:ssh|scp|rsync)\s+/.test(actionDetail)) {
871
- const targetHost = extractRemoteHost(actionDetail);
872
- if (targetHost && !isLocalHost(targetHost)) {
873
- return {
874
- allowed: false,
875
- reason: `\u5C1D\u8BD5\u6A2A\u5411\u79FB\u52A8\u5230\u8FDC\u7A0B\u4E3B\u673A: ${targetHost}`,
876
- riskLevel: "medium",
877
- riskType: "lateral_movement",
878
- confidence: 80,
879
- source: "logic_rule",
880
- violatedLayers: ["2"]
881
- };
446
+ const detailAfterSudo = actionDetail.replace(/(^|\|\||&&|\s)sudo\s+/g, " ");
447
+ const isAdminCmd = ["apt", "systemctl", "yum", "dnf", "pacman", "zypper", "apk"].some((kw) => detailAfterSudo.includes(kw));
448
+ if (!isAdminCmd) {
449
+ const adminContext = actionHistory.some(
450
+ (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")
451
+ );
452
+ if (!adminContext) {
453
+ return {
454
+ allowed: false,
455
+ reason: "\u68C0\u6D4B\u5230\u63D0\u6743\u64CD\u4F5C\uFF0C\u672A\u53D1\u73B0\u5408\u6CD5\u7BA1\u7406\u4E0A\u4E0B\u6587",
456
+ riskLevel: "high",
457
+ riskType: "privilege_escalation",
458
+ confidence: 85,
459
+ source: "logic_rule",
460
+ violatedLayers: ["2"]
461
+ };
462
+ }
882
463
  }
883
464
  }
884
465
  const pwdResult = checkPasswordConsent(actionType, actionDetail, actionHistory);
@@ -899,9 +480,12 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
899
480
  if (NON_WRITE_TOOLS.has(actionType)) return null;
900
481
  let isWrite = WRITE_KEYWORDS.some((kw) => actionType.includes(kw) || actionDetail.includes(kw));
901
482
  if (!isWrite && actionDetail.includes(">")) {
902
- const firstWord = actionDetail.split(/\s/)[0]?.toLowerCase() || "";
903
- if (!READ_ONLY_REDIR_CMDS.has(firstWord)) {
904
- isWrite = true;
483
+ const detailWithoutDevnull = actionDetail.replace(/(?:2|&|1)?\s*>\s*\/dev\/null\b/g, "");
484
+ if (detailWithoutDevnull.includes(">")) {
485
+ const firstWord = actionDetail.split(/\s/)[0]?.toLowerCase() || "";
486
+ if (!READ_ONLY_REDIR_CMDS.has(firstWord)) {
487
+ isWrite = true;
488
+ }
905
489
  }
906
490
  }
907
491
  if (!isWrite) return null;
@@ -946,119 +530,6 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
946
530
  }
947
531
  return null;
948
532
  }
949
- function checkSensitivePath(command, cwd, agentExt) {
950
- const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
951
- const _WRITE_CMD_RES = [/\brm\b/, /\bunlink\b/, /\brmdir\b/, /\bshred\b/, /\bcp\b/, /\bmv\b/, /\binstall\b/, /\btee\b/, /\bdd\b/, /\bmkfs\b/, /\b(fdisk|parted|cfdisk|sfdisk)\b/, /\bchmod\b/, /\bchown\b/, /\btruncate\b/];
952
- const _DEVNULL_RE = /(?:2|&|1)?\s*>\s*\/dev\/null\b/;
953
- const _READONLY_CMDS = /* @__PURE__ */ new Set(["cat", "head", "tail", "less", "more", "grep", "find", "awk", "sed", "sort", "uniq", "wc", "cut", "strings", "od", "xxd", "hexdump", "tr", "file", "stat", "du", "df", "ls", "dir", "tree", "nl", "tac", "rev", "lsblk", "blockdev", "smartctl", "udevadm", "dmidecode", "lscpu", "lspci", "lsusb", "lsmem", "lsns", "lsof", "hwinfo", "inxi", "fdisk", "parted", "dmesg", "journalctl", "systemctl", "hostnamectl", "localectl", "timedatectl", "ps", "top", "free", "vmstat", "iostat", "mpstat", "pidof", "pgrep", "ip", "ifconfig", "route", "ss", "netstat", "arp", "ethtool", "nmcli", "uname", "arch", "nproc", "getconf", "getent", "id", "whoami", "who", "w", "last", "uptime", "lsmod", "modinfo", "sysctl"]);
954
- const isWriteOpFlag = _WRITE_CMD_RES.some((rx) => rx.test(command)) || /\bsed\s+-[a-zA-Z]*i/.test(command) || command.includes(">") && !command.replace(_DEVNULL_RE, "").includes(">") || command.includes(">") && command.replace(_DEVNULL_RE, "").includes(">") && !_READONLY_CMDS.has(command.trim().split(/\s+/)[0]?.toLowerCase() || "") || WRITE_KEYWORDS.some((kw) => "".includes(kw) || command.includes(kw));
955
- for (const sp of ROOT_SENSITIVE_PATHS) {
956
- if (command.includes(sp.path.toLowerCase())) {
957
- if (sp.path === "/dev/random") {
958
- if (isWriteOpFlag) {
959
- continue;
960
- }
961
- return {
962
- allowed: false,
963
- reason: `\u8BFB\u53D6\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
964
- riskLevel: sp.riskLevel,
965
- riskType: "sensitive_path_access",
966
- confidence: 90,
967
- source: "logic_rule",
968
- violatedLayers: ["2"]
969
- };
970
- }
971
- if (!isWriteOpFlag && !sp.credential) {
972
- continue;
973
- }
974
- return {
975
- allowed: false,
976
- reason: `\u5C1D\u8BD5\u8BBF\u95EE\u7CFB\u7EDF\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
977
- riskLevel: sp.riskLevel,
978
- riskType: "sensitive_path_access",
979
- confidence: 90,
980
- source: "logic_rule",
981
- violatedLayers: ["2"]
982
- };
983
- }
984
- }
985
- for (const sp of HOME_SENSITIVE_PATHS) {
986
- const expanded = `${expandedCwd}/${sp.path}`;
987
- if (command.includes(sp.path.toLowerCase()) || command.includes(expanded.toLowerCase())) {
988
- if (!isWriteOpFlag && !sp.credential) {
989
- continue;
990
- }
991
- return {
992
- allowed: false,
993
- reason: `\u5C1D\u8BD5\u8BBF\u95EE\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
994
- riskLevel: sp.riskLevel,
995
- riskType: "sensitive_path_access",
996
- confidence: 90,
997
- source: "logic_rule",
998
- violatedLayers: ["2"]
999
- };
1000
- }
1001
- }
1002
- if (agentExt?.extra_sensitive_paths && agentExt.extra_sensitive_paths.length > 0) {
1003
- for (const sp of agentExt.extra_sensitive_paths) {
1004
- const spPath = sp.path.toLowerCase();
1005
- const expanded = `${expandedCwd}/${spPath}`;
1006
- if (command.includes(spPath) || command.includes(expanded.toLowerCase())) {
1007
- const denyMode = sp.deny_mode || (sp.credential ? "deny_both" : sp.read_only ? "deny_read" : "deny_write");
1008
- if (denyMode === "deny_read") {
1009
- if (isWriteOpFlag) continue;
1010
- } else if (denyMode === "deny_write") {
1011
- if (!isWriteOpFlag) continue;
1012
- }
1013
- return {
1014
- allowed: false,
1015
- reason: `\u5C1D\u8BD5\u8BBF\u95EE agent \u4E13\u5C5E\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
1016
- riskLevel: sp.risk_level,
1017
- riskType: "sensitive_path_access",
1018
- confidence: 90,
1019
- source: "logic_rule",
1020
- violatedLayers: ["2"]
1021
- };
1022
- }
1023
- }
1024
- }
1025
- for (const cp of CREDENTIAL_PATHS) {
1026
- const escaped = cp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1027
- const re = cp.path.startsWith(".") ? new RegExp(`(?:^|[\\s/\\\\'"])${escaped}\\b`) : new RegExp(`\\b${escaped}\\b`);
1028
- if (re.test(command)) {
1029
- return {
1030
- allowed: false,
1031
- reason: `\u8BBF\u95EE\u654F\u611F\u8DEF\u5F84: ${cp.desc} (${cp.path})`,
1032
- riskLevel: cp.riskLevel,
1033
- riskType: "sensitive_path_access",
1034
- confidence: 90,
1035
- source: "logic_rule",
1036
- violatedLayers: ["2"],
1037
- isCredential: true
1038
- };
1039
- }
1040
- }
1041
- if ((command.includes("rm ") || command.includes("unlink")) && (command.includes("/etc/") || command.includes("/root/") || command.includes("/var/log/"))) {
1042
- return {
1043
- allowed: false,
1044
- reason: "\u68C0\u6D4B\u5230\u5220\u9664\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55\u5185\u5BB9",
1045
- riskLevel: "critical",
1046
- riskType: "sensitive_path_access",
1047
- confidence: 90,
1048
- source: "logic_rule",
1049
- violatedLayers: ["2"]
1050
- };
1051
- }
1052
- return null;
1053
- }
1054
- function extractRemoteHost(command) {
1055
- const m = command.match(/(?:ssh|scp|rsync)\s+(?:-[\w]+\s+)*(\S+@\S+|\b(?!-)[\w.-]+\b)/);
1056
- return m ? m[1] : null;
1057
- }
1058
- function isLocalHost(host) {
1059
- const localNames = ["localhost", "127.0.0.1", "::1", process.env.HOSTNAME || "", "0.0.0.0"];
1060
- return localNames.includes(host.replace(/.*@/, ""));
1061
- }
1062
533
  function checkPasswordConsent(actionType, actionDetail, actionHistory) {
1063
534
  if (actionType !== "bash") return null;
1064
535
  const isPasswordModify = PASSWORD_MODIFY_PATTERNS.some((p) => p.test(actionDetail));
@@ -1179,11 +650,10 @@ var init_logic_rules = __esm({
1179
650
  "use strict";
1180
651
  init_esm_shims();
1181
652
  init_inline_analyzer();
1182
- init_agents();
1183
653
  ROOT_SENSITIVE_PATHS = [
1184
654
  { path: "/etc/shadow", riskLevel: "critical", desc: "\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6", credential: true },
1185
655
  { path: "/etc/gshadow", riskLevel: "critical", desc: "\u7CFB\u7EDF\u7EC4\u5BC6\u7801\u6587\u4EF6", credential: true },
1186
- // 注意:/etc/passwd 仅从 L1 启发式规则移除(公开可读),L2 敏感路径仍保留
656
+ // 注意:/etc/passwd 仅从 L1 特征匹配规则移除(公开可读),L2 敏感路径仍保留
1187
657
  { path: "/etc/passwd", riskLevel: "high", desc: "\u7CFB\u7EDF\u7528\u6237\u6587\u4EF6" },
1188
658
  { path: "/etc/sudoers", riskLevel: "critical", desc: "sudo \u914D\u7F6E", credential: true },
1189
659
  // 系统配置(仅拦截写入)
@@ -1276,21 +746,21 @@ var init_logic_rules = __esm({
1276
746
  });
1277
747
 
1278
748
  // src/engine/skill-loader.ts
1279
- import { readFileSync as readFileSync2, readdirSync as readdirSync2, existsSync as existsSync3 } from "fs";
1280
- import { resolve as resolve2, dirname as dirname2 } from "path";
1281
- import { fileURLToPath as fileURLToPath3 } from "url";
749
+ import { readFileSync, readdirSync as readdirSync2, existsSync as existsSync2 } from "fs";
750
+ import { resolve, dirname } from "path";
751
+ import { fileURLToPath as fileURLToPath2 } from "url";
1282
752
  function loadAllSkills() {
1283
753
  if (cachedSkills) return cachedSkills;
1284
754
  cachedSkills = {};
1285
- const dir = existsSync3(SKILLS_DIR) ? SKILLS_DIR : SKILLS_DIR_FALLBACK;
1286
- if (!existsSync3(dir)) {
755
+ const dir = existsSync2(SKILLS_DIR) ? SKILLS_DIR : SKILLS_DIR_FALLBACK;
756
+ if (!existsSync2(dir)) {
1287
757
  console.warn("[AgentMoss] Skills directory not found:", SKILLS_DIR, "and fallback:", SKILLS_DIR_FALLBACK);
1288
758
  return cachedSkills;
1289
759
  }
1290
760
  const files = readdirSync2(dir).filter((f) => f.endsWith(".md"));
1291
761
  for (const f of files) {
1292
762
  const name = f.replace(".md", "");
1293
- cachedSkills[name] = readFileSync2(resolve2(dir, f), "utf-8");
763
+ cachedSkills[name] = readFileSync(resolve(dir, f), "utf-8");
1294
764
  }
1295
765
  console.log(`[AgentMoss] Loaded ${Object.keys(cachedSkills).length} skills from ${dir}`);
1296
766
  return cachedSkills;
@@ -1344,15 +814,15 @@ function formatSkillsForPrompt(skills) {
1344
814
  ${s.skillContent}`
1345
815
  ).join("\n\n---\n\n");
1346
816
  }
1347
- var __filename3, __dirname3, SKILLS_DIR, SKILLS_DIR_FALLBACK, SKILL_KEYWORD_MAP, cachedSkills;
817
+ var __filename2, __dirname2, SKILLS_DIR, SKILLS_DIR_FALLBACK, SKILL_KEYWORD_MAP, cachedSkills;
1348
818
  var init_skill_loader = __esm({
1349
819
  "src/engine/skill-loader.ts"() {
1350
820
  "use strict";
1351
821
  init_esm_shims();
1352
- __filename3 = fileURLToPath3(import.meta.url);
1353
- __dirname3 = dirname2(__filename3);
1354
- SKILLS_DIR = resolve2(__dirname3, "../skills");
1355
- SKILLS_DIR_FALLBACK = resolve2(__dirname3, "../../../agent_moss/skills");
822
+ __filename2 = fileURLToPath2(import.meta.url);
823
+ __dirname2 = dirname(__filename2);
824
+ SKILLS_DIR = resolve(__dirname2, "../skills");
825
+ SKILLS_DIR_FALLBACK = resolve(__dirname2, "../../../agent_moss/skills");
1356
826
  SKILL_KEYWORD_MAP = {
1357
827
  "file_access_guard": ["file", "\u6587\u4EF6", "path", "\u8DEF\u5F84", "read", "write", "cat", "open", "access", "\u8BBF\u95EE", "\u8BFB\u5199", "copy", "cp", "mv", "move"],
1358
828
  "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"],
@@ -1374,189 +844,22 @@ var init_skill_loader = __esm({
1374
844
  }
1375
845
  });
1376
846
 
1377
- // src/engine/token-stats.ts
1378
- var token_stats_exports = {};
1379
- __export(token_stats_exports, {
1380
- getRecentRecords: () => getRecentRecords,
1381
- getTokenStats: () => getTokenStats,
1382
- getTokenTrend: () => getTokenTrend,
1383
- recordTokenUsage: () => recordTokenUsage,
1384
- resetTokenStats: () => resetTokenStats
1385
- });
1386
- import { readFileSync as readFileSync6, writeFileSync, existsSync as existsSync6, mkdirSync, unlinkSync } from "fs";
1387
- import { join as join3 } from "path";
1388
- import { homedir as homedir2 } from "os";
1389
- function getStatsPath() {
1390
- const envPath = process.env[ENV_STATS_PATH];
1391
- return envPath || STATS_FILE;
1392
- }
1393
- function loadAllRecords(statsPath) {
1394
- if (!existsSync6(statsPath)) return [];
1395
- try {
1396
- const data = JSON.parse(readFileSync6(statsPath, "utf-8"));
1397
- if (Array.isArray(data)) return data;
1398
- if (data && Array.isArray(data.records)) return data.records;
1399
- return [];
1400
- } catch (e) {
1401
- return [];
1402
- }
1403
- }
1404
- function saveRecords(statsPath, records) {
1405
- try {
1406
- const dir = statsPath.substring(0, statsPath.lastIndexOf("/"));
1407
- if (dir && !existsSync6(dir)) mkdirSync(dir, { recursive: true });
1408
- writeFileSync(statsPath, JSON.stringify(records, null, 2), "utf-8");
1409
- } catch (e) {
1410
- }
1411
- }
1412
- function recordTokenUsage(record) {
1413
- const statsPath = getStatsPath();
1414
- const records = loadAllRecords(statsPath);
1415
- records.push({
1416
- timestamp: record.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
1417
- session_id: record.sessionId,
1418
- step: record.step,
1419
- model: record.model,
1420
- prompt_tokens: record.promptTokens,
1421
- completion_tokens: record.completionTokens,
1422
- total_tokens: record.totalTokens
1423
- });
1424
- if (records.length > MAX_RECORDS) {
1425
- records.splice(0, records.length - MAX_RECORDS);
1426
- }
1427
- saveRecords(statsPath, records);
1428
- }
1429
- function filterByDate(records, days = 0, startDate, endDate) {
1430
- if (startDate || endDate) {
1431
- let startDt = startDate ? new Date(startDate) : /* @__PURE__ */ new Date(-864e13);
1432
- let endDt = endDate ? new Date(endDate) : void 0;
1433
- if (endDate) {
1434
- const e = new Date(endDate);
1435
- e.setHours(23, 59, 59, 999);
1436
- endDt = e;
1437
- }
1438
- const endMs = endDt ? endDt.getTime() : Number.MAX_SAFE_INTEGER;
1439
- return records.filter((r) => {
1440
- try {
1441
- const ts = new Date(r.timestamp).getTime();
1442
- return ts >= startDt.getTime() && ts <= endMs;
1443
- } catch {
1444
- return true;
1445
- }
1446
- });
1447
- } else if (days > 0) {
1448
- const cutoff = Date.now() - days * 864e5;
1449
- return records.filter((r) => {
1450
- try {
1451
- return new Date(r.timestamp).getTime() >= cutoff;
1452
- } catch {
1453
- return true;
1454
- }
1455
- });
1456
- }
1457
- return records;
1458
- }
1459
- function aggregate(records) {
1460
- let totalPrompt = 0, totalCompletion = 0, totalTotal = 0;
1461
- const byStep = {};
1462
- const byModel = {};
1463
- const byDate = {};
1464
- const bucket = (b, key, r) => {
1465
- const e = b[key] || (b[key] = { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0, calls: 0 });
1466
- e.prompt_tokens += r.prompt_tokens || 0;
1467
- e.completion_tokens += r.completion_tokens || 0;
1468
- e.total_tokens += r.total_tokens || 0;
1469
- e.calls += 1;
1470
- };
1471
- for (const r of records) {
1472
- totalPrompt += r.prompt_tokens || 0;
1473
- totalCompletion += r.completion_tokens || 0;
1474
- totalTotal += r.total_tokens || 0;
1475
- bucket(byStep, r.step || "unknown", r);
1476
- bucket(byModel, r.model || "unknown", r);
1477
- let dateKey = "unknown";
1478
- try {
1479
- dateKey = new Date(r.timestamp).toISOString().slice(0, 10);
1480
- } catch {
1481
- }
1482
- bucket(byDate, dateKey, r);
1483
- }
1484
- const sortedDate = Object.keys(byDate).sort((a, b) => a < b ? 1 : a > b ? -1 : 0);
1485
- const sortedByDate = {};
1486
- for (const k of sortedDate) sortedByDate[k] = byDate[k];
1487
- return { totalPrompt, totalCompletion, totalTotal, byStep, byModel, byDate: sortedByDate };
1488
- }
1489
- function getTokenStats(days = 0, startDate, endDate) {
1490
- const records = filterByDate(loadAllRecords(getStatsPath()), days, startDate, endDate);
1491
- const a = aggregate(records);
1492
- return {
1493
- total_prompt_tokens: a.totalPrompt,
1494
- total_completion_tokens: a.totalCompletion,
1495
- total_tokens: a.totalTotal,
1496
- total_calls: records.length,
1497
- by_step: a.byStep,
1498
- by_model: a.byModel,
1499
- by_date: a.byDate
1500
- };
1501
- }
1502
- function getRecentRecords(limit = 20) {
1503
- const records = loadAllRecords(getStatsPath());
1504
- return records.slice(-limit);
1505
- }
1506
- function getTokenTrend(days = 0, startDate, endDate) {
1507
- const records = filterByDate(loadAllRecords(getStatsPath()), days, startDate, endDate);
1508
- const series = {};
1509
- const datesSet = /* @__PURE__ */ new Set();
1510
- for (const r of records) {
1511
- let dateKey;
1512
- try {
1513
- dateKey = new Date(r.timestamp).toISOString().slice(0, 10);
1514
- } catch {
1515
- continue;
1516
- }
1517
- const model = r.model || "unknown";
1518
- const tokens = r.total_tokens || 0;
1519
- datesSet.add(dateKey);
1520
- series[model] = series[model] || {};
1521
- series[model][dateKey] = (series[model][dateKey] || 0) + tokens;
1522
- }
1523
- const dates = Array.from(datesSet).sort();
1524
- const models = Object.keys(series).sort();
1525
- return { dates, models, series };
1526
- }
1527
- function resetTokenStats() {
1528
- const statsPath = getStatsPath();
1529
- if (existsSync6(statsPath)) {
1530
- try {
1531
- unlinkSync(statsPath);
1532
- } catch {
1533
- }
1534
- }
1535
- }
1536
- var STATS_DIR, STATS_FILE, ENV_STATS_PATH, MAX_RECORDS;
1537
- var init_token_stats = __esm({
1538
- "src/engine/token-stats.ts"() {
1539
- "use strict";
1540
- init_esm_shims();
1541
- STATS_DIR = join3(homedir2(), ".config", "agentmoss");
1542
- STATS_FILE = join3(STATS_DIR, "agent_moss_token_stats.json");
1543
- ENV_STATS_PATH = "AGENT_MOSS_TOKEN_STATS_PATH";
1544
- MAX_RECORDS = 1e4;
1545
- }
1546
- });
1547
-
1548
847
  // src/infra/runtime-config.ts
1549
848
  var runtime_config_exports = {};
1550
849
  __export(runtime_config_exports, {
1551
850
  DENY_MODE_LABELS: () => DENY_MODE_LABELS,
851
+ addAgentRule: () => addAgentRule,
1552
852
  addCustomRule: () => addCustomRule,
1553
853
  addCustomSkill: () => addCustomSkill,
854
+ deleteAgentRule: () => deleteAgentRule,
1554
855
  deleteCustomRule: () => deleteCustomRule,
1555
856
  deleteCustomSkill: () => deleteCustomSkill,
1556
857
  filterEnabledRules: () => filterEnabledRules,
1557
858
  generateSourceDefaults: () => generateSourceDefaults,
859
+ getAgentRules: () => getAgentRules,
1558
860
  getDisabledL2RulesWithSkipL3: () => getDisabledL2RulesWithSkipL3,
1559
861
  getDisabledSkillIds: () => getDisabledSkillIds,
862
+ getEnabledAgentExtensions: () => getEnabledAgentExtensions,
1560
863
  getEnabledL1CommandPatterns: () => getEnabledL1CommandPatterns,
1561
864
  getEnabledL1CommandPatternsOrDefault: () => getEnabledL1CommandPatternsOrDefault,
1562
865
  getEnabledL1InjectionKeywords: () => getEnabledL1InjectionKeywords,
@@ -1578,6 +881,8 @@ __export(runtime_config_exports, {
1578
881
  loadRuntimeConfig: () => loadRuntimeConfig,
1579
882
  mergeConfig: () => mergeConfig,
1580
883
  saveRuntimeConfig: () => saveRuntimeConfig,
884
+ updateAgentCategoryEnabled: () => updateAgentCategoryEnabled,
885
+ updateAgentRuleEnabled: () => updateAgentRuleEnabled,
1581
886
  updateCategoryEnabled: () => updateCategoryEnabled,
1582
887
  updateLayerEnabled: () => updateLayerEnabled,
1583
888
  updateRuleDenyMode: () => updateRuleDenyMode,
@@ -1586,9 +891,11 @@ __export(runtime_config_exports, {
1586
891
  updateSkillCategoryEnabled: () => updateSkillCategoryEnabled,
1587
892
  updateSkillEnabled: () => updateSkillEnabled
1588
893
  });
1589
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync2, existsSync as existsSync7, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
1590
- import { join as join4 } from "path";
1591
- import { homedir as homedir3 } from "os";
894
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3, mkdirSync, unlinkSync, readdirSync as readdirSync3 } from "fs";
895
+ import { join } from "path";
896
+ import { homedir } from "os";
897
+ import { fileURLToPath as fileURLToPath3 } from "url";
898
+ import { dirname as dirname2 } from "path";
1592
899
  function getRuntimeConfigPath() {
1593
900
  const envPath = process.env[ENV_RUNTIME_CONFIG_PATH];
1594
901
  if (envPath) return envPath;
@@ -1597,8 +904,8 @@ function getRuntimeConfigPath() {
1597
904
  function saveRuntimeConfig(config) {
1598
905
  const p = getRuntimeConfigPath();
1599
906
  const dir = p.substring(0, p.lastIndexOf("/"));
1600
- if (dir && !existsSync7(dir)) mkdirSync2(dir, { recursive: true });
1601
- writeFileSync2(p, JSON.stringify(config, null, 2), "utf-8");
907
+ if (dir && !existsSync3(dir)) mkdirSync(dir, { recursive: true });
908
+ writeFileSync(p, JSON.stringify(config, null, 2), "utf-8");
1602
909
  }
1603
910
  function emptyRuntimeConfig() {
1604
911
  return {
@@ -1606,12 +913,13 @@ function emptyRuntimeConfig() {
1606
913
  layers: { L1_heuristic: true, L2_logic_rules: true, L3_llm_analysis: true },
1607
914
  L1_rules: {},
1608
915
  L2_rules: {},
1609
- L3_skills: {}
916
+ L3_skills: {},
917
+ agent_rules: {}
1610
918
  };
1611
919
  }
1612
920
  function loadRuntimeConfig() {
1613
921
  const p = getRuntimeConfigPath();
1614
- if (!existsSync7(p)) {
922
+ if (!existsSync3(p)) {
1615
923
  const seeded = mergeConfig({}, generateSourceDefaults());
1616
924
  try {
1617
925
  saveRuntimeConfig(seeded);
@@ -1620,7 +928,7 @@ function loadRuntimeConfig() {
1620
928
  return seeded;
1621
929
  }
1622
930
  try {
1623
- const data = JSON.parse(readFileSync7(p, "utf-8"));
931
+ const data = JSON.parse(readFileSync2(p, "utf-8"));
1624
932
  const merged = mergeConfig(data, generateSourceDefaults());
1625
933
  return {
1626
934
  version: data.version || 1,
@@ -1631,7 +939,8 @@ function loadRuntimeConfig() {
1631
939
  },
1632
940
  L1_rules: merged.L1_rules || {},
1633
941
  L2_rules: merged.L2_rules || {},
1634
- L3_skills: merged.L3_skills || {}
942
+ L3_skills: merged.L3_skills || {},
943
+ agent_rules: merged.agent_rules || {}
1635
944
  };
1636
945
  } catch {
1637
946
  return emptyRuntimeConfig();
@@ -1674,7 +983,10 @@ function getEnabledL1CommandPatterns(runtime) {
1674
983
  }));
1675
984
  }
1676
985
  function getEnabledL2SensitivePaths(runtime) {
1677
- const cat = runtime.L2_rules?.sensitive_path_access || { category_enabled: true, rules: [] };
986
+ let cat = runtime.L1_rules?.sensitive_path_access;
987
+ if (!cat || !cat.rules || cat.rules.length === 0) {
988
+ cat = runtime.L2_rules?.sensitive_path_access || { category_enabled: true, rules: [] };
989
+ }
1678
990
  return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => {
1679
991
  const out = { path: r.path || "", risk_level: r.risk_level || "", desc: r.desc || "", deny_mode: r.deny_mode || "deny_write" };
1680
992
  if (r.credential) out.credential = true;
@@ -1684,9 +996,9 @@ function getEnabledL2SensitivePaths(runtime) {
1684
996
  }
1685
997
  function hasRuntimeConfig() {
1686
998
  const p = getRuntimeConfigPath();
1687
- if (!existsSync7(p)) return false;
999
+ if (!existsSync3(p)) return false;
1688
1000
  try {
1689
- JSON.parse(readFileSync7(p, "utf-8"));
1001
+ JSON.parse(readFileSync2(p, "utf-8"));
1690
1002
  return true;
1691
1003
  } catch {
1692
1004
  return false;
@@ -1700,22 +1012,24 @@ function getDisabledL2RulesWithSkipL3() {
1700
1012
  return {};
1701
1013
  }
1702
1014
  const result = {};
1703
- const l2 = runtime.L2_rules || {};
1704
- for (const [catName, cat] of Object.entries(l2)) {
1705
- if (!cat.category_enabled) continue;
1706
- const items = [];
1707
- for (const r of cat.rules || []) {
1708
- if (r.enabled === false && r.skip_l3_on_disabled) {
1709
- if (catName === "sensitive_path_access") {
1710
- if (r.path) items.push(r.path);
1711
- } else if (catName === "intent_consistency") {
1712
- items.push(...r.intent_keywords || []);
1713
- } else if (r.pattern) {
1714
- items.push(r.pattern);
1015
+ for (const layerKey of ["L1_rules", "L2_rules"]) {
1016
+ const layer = runtime[layerKey] || {};
1017
+ for (const [catName, cat] of Object.entries(layer)) {
1018
+ if (!cat.category_enabled) continue;
1019
+ const items = [];
1020
+ for (const r of cat.rules || []) {
1021
+ if (r.enabled === false && r.skip_l3_on_disabled) {
1022
+ if (catName === "sensitive_path_access") {
1023
+ if (r.path) items.push(r.path);
1024
+ } else if (catName === "intent_consistency") {
1025
+ items.push(...r.intent_keywords || []);
1026
+ } else if (r.pattern) {
1027
+ items.push(r.pattern);
1028
+ }
1715
1029
  }
1716
1030
  }
1031
+ if (items.length > 0) result[catName] = items;
1717
1032
  }
1718
- if (items.length > 0) result[catName] = items;
1719
1033
  }
1720
1034
  return result;
1721
1035
  }
@@ -1861,6 +1175,55 @@ function _l3SkillsToRules() {
1861
1175
  out.user_custom = { category_enabled: true, category_desc: "", skills: [] };
1862
1176
  return out;
1863
1177
  }
1178
+ function _agentRulesToDefaults() {
1179
+ const out = {};
1180
+ const agentsDir = existsSync3(_AGENTS_DIR_PROD) ? _AGENTS_DIR_PROD : existsSync3(_AGENTS_DIR_DEV) ? _AGENTS_DIR_DEV : _AGENTS_DIR_PROD;
1181
+ if (!existsSync3(agentsDir)) return out;
1182
+ let entries;
1183
+ try {
1184
+ entries = readdirSync3(agentsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
1185
+ } catch {
1186
+ return out;
1187
+ }
1188
+ for (const agentId of entries) {
1189
+ const rulesFile = join(agentsDir, agentId, "rules.json");
1190
+ if (!existsSync3(rulesFile)) continue;
1191
+ let data;
1192
+ try {
1193
+ data = JSON.parse(readFileSync2(rulesFile, "utf-8"));
1194
+ } catch {
1195
+ continue;
1196
+ }
1197
+ out[agentId] = {
1198
+ builtin_safe_action_types: {
1199
+ category_enabled: true,
1200
+ category_desc: "agent \u5B8C\u5168\u5B89\u5168\u64CD\u4F5C\u767D\u540D\u5355\uFF08\u524D\u7F00\u5C3E\u968F - \u6216\u7CBE\u786E\u540D\uFF0C\u547D\u4E2D\u8DF3 L2+L3\uFF09",
1201
+ rules: (data.builtin_safe_action_types || []).map((v) => ({ value: v, enabled: true, builtin: true }))
1202
+ },
1203
+ builtin_safe_skills: {
1204
+ category_enabled: true,
1205
+ category_desc: "agent \u4FE1\u4EFB\u7684\u5185\u7F6E Skill\uFF08\u547D\u4E2D\u8DF3 L2+L3\uFF09",
1206
+ rules: (data.builtin_safe_skills || []).map((v) => ({ value: v, enabled: true, builtin: true }))
1207
+ },
1208
+ extra_command_patterns: {
1209
+ category_enabled: true,
1210
+ category_desc: "agent \u4E13\u5C5E\u5371\u9669\u547D\u4EE4\u6B63\u5219\uFF08L1 \u672B\u5C3E\u8FFD\u52A0\uFF09",
1211
+ rules: (data.extra_command_patterns || []).map((p) => ({ ...p, enabled: true, builtin: true }))
1212
+ },
1213
+ extra_injection_keywords: {
1214
+ category_enabled: true,
1215
+ category_desc: "agent \u4E13\u5C5E\u6CE8\u5165\u5173\u952E\u8BCD\uFF08L1 \u672B\u5C3E\u8FFD\u52A0\uFF09",
1216
+ rules: (data.extra_injection_keywords || []).map((k) => ({ ...k, enabled: true, builtin: true }))
1217
+ },
1218
+ extra_sensitive_paths: {
1219
+ category_enabled: true,
1220
+ category_desc: "agent \u4E13\u5C5E\u654F\u611F\u8DEF\u5F84\uFF08L1 \u672B\u5C3E\u8FFD\u52A0\uFF09",
1221
+ rules: (data.extra_sensitive_paths || []).map((sp) => ({ ...sp, enabled: true, builtin: true }))
1222
+ }
1223
+ };
1224
+ }
1225
+ return out;
1226
+ }
1864
1227
  function generateSourceDefaults() {
1865
1228
  return {
1866
1229
  version: 1,
@@ -1876,15 +1239,14 @@ function generateSourceDefaults() {
1876
1239
  category_desc: "Prompt \u6CE8\u5165\u653B\u51FB\u68C0\u6D4B",
1877
1240
  rules: _l1InjectionKeywordsToRules()
1878
1241
  },
1879
- user_sensitive_actions: { category_enabled: true, category_desc: "\u7528\u6237\u81EA\u5B9A\u4E49\u654F\u611F\u52A8\u4F5C/\u5DE5\u5177\u89C4\u5219", rules: [] },
1880
- user_custom: { category_enabled: true, category_desc: "\u7528\u6237\u81EA\u5B9A\u4E49\u547D\u4EE4\u89C4\u5219", rules: [] }
1881
- },
1882
- L2_rules: {
1883
1242
  sensitive_path_access: {
1884
1243
  category_enabled: true,
1885
- category_desc: "\u654F\u611F\u8DEF\u5F84\u8BBF\u95EE\u68C0\u6D4B",
1244
+ category_desc: "\u654F\u611F\u8DEF\u5F84\u8BBF\u95EE\u68C0\u6D4B\uFF08deny_both/deny_read/deny_write\uFF09",
1886
1245
  rules: _l2SensitivePathsToRules()
1887
1246
  },
1247
+ user_custom: { category_enabled: true, category_desc: "\u7528\u6237\u81EA\u5B9A\u4E49\u547D\u4EE4\u89C4\u5219", rules: [] }
1248
+ },
1249
+ L2_rules: {
1888
1250
  intent_consistency: {
1889
1251
  category_enabled: true,
1890
1252
  category_desc: "\u610F\u56FE\u4E00\u81F4\u6027\u68C0\u6D4B",
@@ -1901,9 +1263,10 @@ function generateSourceDefaults() {
1901
1263
  rules: _l2UserDeletionPatternsToRules()
1902
1264
  },
1903
1265
  read_before_write: { category_enabled: true, category_desc: "read_before_write \u5B89\u5168\u539F\u5219\u68C0\u6D4B", rules: [] },
1904
- dangerous_patterns: { category_enabled: true, category_desc: "\u5371\u9669\u64CD\u4F5C\u6A21\u5F0F\u68C0\u6D4B", rules: [] }
1266
+ user_custom: { category_enabled: true, category_desc: "\u7528\u6237\u81EA\u5B9A\u4E49 L2 \u903B\u8F91\u89C4\u5219", rules: [] }
1905
1267
  },
1906
- L3_skills: _l3SkillsToRules()
1268
+ L3_skills: _l3SkillsToRules(),
1269
+ agent_rules: _agentRulesToDefaults()
1907
1270
  };
1908
1271
  }
1909
1272
  function _ruleFingerprint(rule) {
@@ -1916,6 +1279,7 @@ function _ruleFingerprint(rule) {
1916
1279
  if (rule.path) return JSON.stringify([rule.path]);
1917
1280
  if (rule.pattern) return JSON.stringify([rule.pattern]);
1918
1281
  if (rule.keyword) return JSON.stringify([rule.keyword, rule.lang || ""]);
1282
+ if (rule.value) return JSON.stringify([rule.value]);
1919
1283
  return JSON.stringify([rule.id || ""]);
1920
1284
  }
1921
1285
  function _mergeRuleCategories(userCfg, srcDefaults, layerKey) {
@@ -1969,6 +1333,10 @@ function _mergeRuleCategories(userCfg, srcDefaults, layerKey) {
1969
1333
  }
1970
1334
  }
1971
1335
  }
1336
+ for (const catName of Object.keys(userLayer)) {
1337
+ if (catName === "user_custom") continue;
1338
+ if (!(catName in srcLayer)) delete userLayer[catName];
1339
+ }
1972
1340
  }
1973
1341
  function _mergeSkillCategories(userCfg, srcDefaults, layerKey) {
1974
1342
  const userLayer = userCfg[layerKey] || (userCfg[layerKey] = {});
@@ -1988,6 +1356,50 @@ function _mergeSkillCategories(userCfg, srcDefaults, layerKey) {
1988
1356
  }
1989
1357
  }
1990
1358
  }
1359
+ function _mergeAgentRules(userCfg, srcDefaults) {
1360
+ const userAgent = userCfg.agent_rules || (userCfg.agent_rules = {});
1361
+ const srcAgent = srcDefaults.agent_rules || {};
1362
+ for (const [agentId, srcCats] of Object.entries(srcAgent)) {
1363
+ if (!(agentId in userAgent)) {
1364
+ userAgent[agentId] = JSON.parse(JSON.stringify(srcCats));
1365
+ continue;
1366
+ }
1367
+ const userCats = userAgent[agentId];
1368
+ for (const [catName, srcCat] of Object.entries(srcCats)) {
1369
+ if (!(catName in userCats)) {
1370
+ userCats[catName] = JSON.parse(JSON.stringify(srcCat));
1371
+ continue;
1372
+ }
1373
+ const userCat = userCats[catName];
1374
+ if (userCat.category_desc === void 0) userCat.category_desc = srcCat.category_desc || "";
1375
+ if (userCat.category_enabled === void 0) userCat.category_enabled = srcCat.category_enabled ?? true;
1376
+ const srcRules = srcCat.rules || [];
1377
+ const userRules = userCat.rules || (userCat.rules = []);
1378
+ const userByFp = /* @__PURE__ */ new Map();
1379
+ for (const r of userRules) userByFp.set(_ruleFingerprint(r), r);
1380
+ const srcFps = /* @__PURE__ */ new Set();
1381
+ for (const srcR of srcRules) {
1382
+ const fp = _ruleFingerprint(srcR);
1383
+ srcFps.add(fp);
1384
+ if (userByFp.has(fp)) {
1385
+ const existing = userByFp.get(fp);
1386
+ const userEnabled = existing.enabled;
1387
+ Object.keys(existing).forEach((k) => delete existing[k]);
1388
+ Object.assign(existing, srcR);
1389
+ existing.enabled = userEnabled;
1390
+ } else {
1391
+ userRules.push({ ...srcR, enabled: true });
1392
+ }
1393
+ }
1394
+ for (const r of userRules) {
1395
+ if (r.builtin && !srcFps.has(_ruleFingerprint(r))) {
1396
+ r.source_removed = true;
1397
+ r.enabled = false;
1398
+ }
1399
+ }
1400
+ }
1401
+ }
1402
+ }
1991
1403
  function mergeConfig(userCfg, srcDefaults) {
1992
1404
  const cfg = userCfg;
1993
1405
  if (cfg.version === void 0) cfg.version = srcDefaults.version || 1;
@@ -1995,15 +1407,47 @@ function mergeConfig(userCfg, srcDefaults) {
1995
1407
  for (const [layerKey, defaultVal] of Object.entries(srcDefaults.layers)) {
1996
1408
  if (!(layerKey in cfg.layers)) cfg.layers[layerKey] = defaultVal;
1997
1409
  }
1410
+ _migrateCategoriesAcrossLayers(
1411
+ cfg,
1412
+ "L2_rules",
1413
+ "L1_rules",
1414
+ ["sensitive_path_access"]
1415
+ );
1998
1416
  _mergeRuleCategories(cfg, srcDefaults, "L1_rules");
1999
1417
  _mergeRuleCategories(cfg, srcDefaults, "L2_rules");
2000
1418
  _mergeSkillCategories(cfg, srcDefaults, "L3_skills");
1419
+ _mergeAgentRules(cfg, srcDefaults);
2001
1420
  return cfg;
2002
1421
  }
2003
- function getEnabledL1InjectionKeywords(runtime) {
2004
- const cat = runtime.L1_rules?.injection_detection || { category_enabled: true, rules: [] };
2005
- return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => ({
2006
- keyword: r.keyword || "",
1422
+ function _migrateCategoriesAcrossLayers(cfg, srcLayer, dstLayer, catNames) {
1423
+ const src = cfg[srcLayer] || {};
1424
+ const dst = cfg[dstLayer] || (cfg[dstLayer] = {});
1425
+ for (const catName of catNames) {
1426
+ if (!(catName in src)) continue;
1427
+ const srcCat = src[catName];
1428
+ if (!dst[catName]) {
1429
+ dst[catName] = { category_enabled: true, category_desc: "", rules: [] };
1430
+ }
1431
+ const dstCat = dst[catName];
1432
+ if (!dstCat.category_desc && srcCat.category_desc) dstCat.category_desc = srcCat.category_desc;
1433
+ if (dstCat.category_enabled === void 0) dstCat.category_enabled = srcCat.category_enabled;
1434
+ if (srcCat.rules && srcCat.rules.length > 0) {
1435
+ const dstFps = new Set((dstCat.rules || []).map((r) => _ruleFingerprint(r)));
1436
+ for (const r of srcCat.rules) {
1437
+ const fp = _ruleFingerprint(r);
1438
+ if (!dstFps.has(fp)) {
1439
+ (dstCat.rules = dstCat.rules || []).push(r);
1440
+ dstFps.add(fp);
1441
+ }
1442
+ }
1443
+ }
1444
+ delete src[catName];
1445
+ }
1446
+ }
1447
+ function getEnabledL1InjectionKeywords(runtime) {
1448
+ const cat = runtime.L1_rules?.injection_detection || { category_enabled: true, rules: [] };
1449
+ return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => ({
1450
+ keyword: r.keyword || "",
2007
1451
  risk_level: r.risk_level || "",
2008
1452
  lang: r.lang || "en"
2009
1453
  }));
@@ -2092,149 +1536,885 @@ function updateLayerEnabled(layerKey, enabled) {
2092
1536
  saveRuntimeConfig(runtime);
2093
1537
  return runtime;
2094
1538
  }
2095
- function updateRuleEnabled(layer, category, ruleId, enabled) {
2096
- const runtime = loadRuntimeConfig();
2097
- for (const r of runtime[layer]?.[category]?.rules || []) {
2098
- if (r.id === ruleId) {
2099
- r.enabled = enabled;
2100
- break;
2101
- }
1539
+ function updateRuleEnabled(layer, category, ruleId, enabled) {
1540
+ const runtime = loadRuntimeConfig();
1541
+ for (const r of runtime[layer]?.[category]?.rules || []) {
1542
+ if (r.id === ruleId) {
1543
+ r.enabled = enabled;
1544
+ break;
1545
+ }
1546
+ }
1547
+ saveRuntimeConfig(runtime);
1548
+ return runtime;
1549
+ }
1550
+ function updateRuleDenyMode(layer, category, ruleId, denyMode) {
1551
+ const runtime = loadRuntimeConfig();
1552
+ for (const r of runtime[layer]?.[category]?.rules || []) {
1553
+ if (r.id === ruleId) {
1554
+ r.deny_mode = denyMode;
1555
+ _syncDenyModeFields(r);
1556
+ break;
1557
+ }
1558
+ }
1559
+ saveRuntimeConfig(runtime);
1560
+ return runtime;
1561
+ }
1562
+ function updateRuleSkipL3(layer, category, ruleId, skipL3) {
1563
+ const runtime = loadRuntimeConfig();
1564
+ for (const r of runtime[layer]?.[category]?.rules || []) {
1565
+ if (r.id === ruleId) {
1566
+ r.skip_l3_on_disabled = skipL3;
1567
+ break;
1568
+ }
1569
+ }
1570
+ saveRuntimeConfig(runtime);
1571
+ return runtime;
1572
+ }
1573
+ function updateCategoryEnabled(layer, category, enabled) {
1574
+ const runtime = loadRuntimeConfig();
1575
+ const cat = runtime[layer]?.[category];
1576
+ if (cat) cat.category_enabled = enabled;
1577
+ saveRuntimeConfig(runtime);
1578
+ return runtime;
1579
+ }
1580
+ function addCustomRule(layer, category, rule) {
1581
+ const runtime = loadRuntimeConfig();
1582
+ const layerObj = runtime[layer] || (runtime[layer] = {});
1583
+ const cat = layerObj[category] || (layerObj[category] = { category_enabled: true, category_desc: "", rules: [] });
1584
+ const rules = cat.rules || (cat.rules = []);
1585
+ const newRule = { ...rule, builtin: false, enabled: true };
1586
+ if (!newRule.id) {
1587
+ newRule.id = `custom_${Date.now()}_${_slugify(JSON.stringify(rule).slice(0, 30))}`;
1588
+ }
1589
+ if (newRule.deny_mode) _syncDenyModeFields(newRule);
1590
+ rules.push(newRule);
1591
+ saveRuntimeConfig(runtime);
1592
+ return runtime;
1593
+ }
1594
+ function deleteCustomRule(layer, category, ruleId) {
1595
+ const runtime = loadRuntimeConfig();
1596
+ const rules = runtime[layer]?.[category]?.rules || [];
1597
+ const idx = rules.findIndex((r) => r.id === ruleId);
1598
+ if (idx >= 0) {
1599
+ const r = rules[idx];
1600
+ if (r.builtin && !r.source_removed) return null;
1601
+ rules.splice(idx, 1);
1602
+ saveRuntimeConfig(runtime);
1603
+ }
1604
+ return runtime;
1605
+ }
1606
+ function addCustomSkill(skillId, category, keywords, content) {
1607
+ _validateSkillId(skillId);
1608
+ const runtime = loadRuntimeConfig();
1609
+ const skillsDir = join(RUNTIME_CONFIG_DIR, "agent_moss_skills");
1610
+ try {
1611
+ mkdirSync(skillsDir, { recursive: true });
1612
+ } catch {
1613
+ }
1614
+ writeFileSync(join(skillsDir, `${skillId}.md`), content, "utf-8");
1615
+ const l3 = runtime.L3_skills || (runtime.L3_skills = {});
1616
+ const cat = l3[category] || (l3[category] = { category_enabled: true, category_desc: "", skills: [] });
1617
+ (cat.skills || (cat.skills = [])).push({ id: skillId, keywords, enabled: true, builtin: false });
1618
+ saveRuntimeConfig(runtime);
1619
+ return runtime;
1620
+ }
1621
+ function deleteCustomSkill(skillId, category) {
1622
+ _validateSkillId(skillId);
1623
+ const runtime = loadRuntimeConfig();
1624
+ const skills = runtime.L3_skills?.[category]?.skills || [];
1625
+ const idx = skills.findIndex((s) => s.id === skillId);
1626
+ if (idx >= 0) {
1627
+ if (skills[idx].builtin) return null;
1628
+ skills.splice(idx, 1);
1629
+ const skillPath = join(RUNTIME_CONFIG_DIR, "agent_moss_skills", `${skillId}.md`);
1630
+ try {
1631
+ if (existsSync3(skillPath)) unlinkSync(skillPath);
1632
+ } catch {
1633
+ }
1634
+ saveRuntimeConfig(runtime);
1635
+ }
1636
+ return runtime;
1637
+ }
1638
+ function updateSkillEnabled(skillId, enabled) {
1639
+ const runtime = loadRuntimeConfig();
1640
+ for (const catData of Object.values(runtime.L3_skills || {})) {
1641
+ for (const s of catData.skills || []) {
1642
+ if (s.id === skillId) {
1643
+ s.enabled = enabled;
1644
+ break;
1645
+ }
1646
+ }
1647
+ }
1648
+ saveRuntimeConfig(runtime);
1649
+ return runtime;
1650
+ }
1651
+ function updateSkillCategoryEnabled(category, enabled) {
1652
+ const runtime = loadRuntimeConfig();
1653
+ const cat = runtime.L3_skills?.[category];
1654
+ if (cat) cat.category_enabled = enabled;
1655
+ saveRuntimeConfig(runtime);
1656
+ return runtime;
1657
+ }
1658
+ function getProfileOrDetect() {
1659
+ return null;
1660
+ }
1661
+ function getAgentRules(runtime) {
1662
+ return runtime.agent_rules || {};
1663
+ }
1664
+ function _agentCategoryEnabledRules(cat) {
1665
+ if (!cat || cat.category_enabled === false) return [];
1666
+ return (cat.rules || []).filter((r) => r.enabled !== false);
1667
+ }
1668
+ function getEnabledAgentExtensions(runtime, agentId) {
1669
+ const agentData = runtime.agent_rules?.[agentId];
1670
+ if (!agentData) return null;
1671
+ const safeAction = _agentCategoryEnabledRules(agentData.builtin_safe_action_types);
1672
+ const safeSkills = _agentCategoryEnabledRules(agentData.builtin_safe_skills);
1673
+ const cmdPatterns = _agentCategoryEnabledRules(agentData.extra_command_patterns).filter((r) => r.pattern).map((r) => ({
1674
+ pattern: r.pattern,
1675
+ risk_level: r.risk_level || "",
1676
+ risk_type: r.risk_type || "",
1677
+ reason: r.reason || ""
1678
+ }));
1679
+ const injKeywords = _agentCategoryEnabledRules(agentData.extra_injection_keywords).filter((r) => r.keyword).map((r) => ({
1680
+ keyword: r.keyword,
1681
+ risk_level: r.risk_level || "",
1682
+ lang: r.lang || "en"
1683
+ }));
1684
+ const sensitivePaths = _agentCategoryEnabledRules(agentData.extra_sensitive_paths).filter((r) => r.path).map((r) => ({
1685
+ path: r.path,
1686
+ risk_level: r.risk_level || "",
1687
+ desc: r.desc || "",
1688
+ credential: r.credential,
1689
+ read_only: r.read_only,
1690
+ deny_mode: r.deny_mode
1691
+ }));
1692
+ return {
1693
+ agent_id: agentId,
1694
+ description: "",
1695
+ extra_command_patterns: cmdPatterns,
1696
+ extra_injection_keywords: injKeywords,
1697
+ extra_sensitive_paths: sensitivePaths,
1698
+ builtin_safe_skills: safeSkills.filter((r) => r.value).map((r) => r.value),
1699
+ builtin_safe_action_types: safeAction.filter((r) => r.value).map((r) => r.value),
1700
+ extra_skills: []
1701
+ };
1702
+ }
1703
+ function updateAgentRuleEnabled(agentId, category, ruleFp, enabled) {
1704
+ const runtime = loadRuntimeConfig();
1705
+ const cat = runtime.agent_rules?.[agentId]?.[category];
1706
+ if (cat) {
1707
+ const targetFp = _ruleFingerprint(ruleFp);
1708
+ for (const r of cat.rules || []) {
1709
+ if (_ruleFingerprint(r) === targetFp) {
1710
+ r.enabled = enabled;
1711
+ break;
1712
+ }
1713
+ }
1714
+ }
1715
+ saveRuntimeConfig(runtime);
1716
+ return runtime;
1717
+ }
1718
+ function addAgentRule(agentId, category, rule) {
1719
+ const runtime = loadRuntimeConfig();
1720
+ if (!runtime.agent_rules) runtime.agent_rules = {};
1721
+ if (!runtime.agent_rules[agentId]) runtime.agent_rules[agentId] = {};
1722
+ const cats = runtime.agent_rules[agentId];
1723
+ if (!cats[category]) cats[category] = { category_enabled: true, category_desc: "", rules: [] };
1724
+ (cats[category].rules || (cats[category].rules = [])).push({ ...rule, enabled: true, builtin: false });
1725
+ saveRuntimeConfig(runtime);
1726
+ return runtime;
1727
+ }
1728
+ function deleteAgentRule(agentId, category, ruleFp) {
1729
+ const runtime = loadRuntimeConfig();
1730
+ const cat = runtime.agent_rules?.[agentId]?.[category];
1731
+ if (cat) {
1732
+ const targetFp = _ruleFingerprint(ruleFp);
1733
+ cat.rules = (cat.rules || []).filter((r) => _ruleFingerprint(r) !== targetFp);
1734
+ }
1735
+ saveRuntimeConfig(runtime);
1736
+ return runtime;
1737
+ }
1738
+ function updateAgentCategoryEnabled(agentId, category, enabled) {
1739
+ const runtime = loadRuntimeConfig();
1740
+ const cat = runtime.agent_rules?.[agentId]?.[category];
1741
+ if (cat) cat.category_enabled = enabled;
1742
+ saveRuntimeConfig(runtime);
1743
+ return runtime;
1744
+ }
1745
+ var __dirname3, RUNTIME_CONFIG_DIR, RUNTIME_CONFIG_PATH, ENV_RUNTIME_CONFIG_PATH, LAYER_DISABLE_ENV, DENY_MODE_LABELS, _AGENTS_DIR_PROD, _AGENTS_DIR_DEV, SKILL_ID_RE;
1746
+ var init_runtime_config = __esm({
1747
+ "src/infra/runtime-config.ts"() {
1748
+ "use strict";
1749
+ init_esm_shims();
1750
+ init_heuristic();
1751
+ init_logic_rules();
1752
+ init_skill_loader();
1753
+ __dirname3 = dirname2(fileURLToPath3(import.meta.url));
1754
+ RUNTIME_CONFIG_DIR = join(homedir(), ".config", "agentmoss");
1755
+ RUNTIME_CONFIG_PATH = join(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
1756
+ ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
1757
+ LAYER_DISABLE_ENV = {
1758
+ L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
1759
+ L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
1760
+ L3_llm_analysis: "AGENT_MOSS_DISABLE_LLM"
1761
+ };
1762
+ DENY_MODE_LABELS = {
1763
+ deny_write: "\u4EC5\u62E6\u622A\u5199\u5165",
1764
+ deny_read: "\u4EC5\u62E6\u622A\u8BFB\u53D6",
1765
+ deny_both: "\u8BFB\u5199\u5747\u62E6\u622A"
1766
+ };
1767
+ _AGENTS_DIR_PROD = join(__dirname3, "agents");
1768
+ _AGENTS_DIR_DEV = join(__dirname3, "..", "..", "..", "agent_moss", "agents");
1769
+ SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
1770
+ }
1771
+ });
1772
+
1773
+ // src/agents/index.ts
1774
+ import { readFileSync as readFileSync3, existsSync as existsSync4 } from "fs";
1775
+ import { resolve as resolve2, dirname as dirname3 } from "path";
1776
+ import { fileURLToPath as fileURLToPath4 } from "url";
1777
+ function resolveAgentsDir() {
1778
+ if (existsSync4(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
1779
+ if (existsSync4(AGENTS_DIR_DEV_FALLBACK)) return AGENTS_DIR_DEV_FALLBACK;
1780
+ return AGENTS_DIR_PROD;
1781
+ }
1782
+ function getAgentExtensions(agentId) {
1783
+ if (!agentId) return null;
1784
+ if (cache.has(agentId)) return cache.get(agentId) ?? null;
1785
+ let ext = null;
1786
+ try {
1787
+ const { hasRuntimeConfig: hasRuntimeConfig2, loadRuntimeConfig: loadRuntimeConfig2, getEnabledAgentExtensions: getEnabledAgentExtensions2 } = (init_runtime_config(), __toCommonJS(runtime_config_exports));
1788
+ if (hasRuntimeConfig2()) {
1789
+ ext = getEnabledAgentExtensions2(loadRuntimeConfig2(), agentId);
1790
+ }
1791
+ } catch {
1792
+ }
1793
+ if (ext === null) {
1794
+ const agentsDir = resolveAgentsDir();
1795
+ const rulesFile = resolve2(agentsDir, agentId, "rules.json");
1796
+ if (!existsSync4(rulesFile)) {
1797
+ cache.set(agentId, null);
1798
+ return null;
1799
+ }
1800
+ try {
1801
+ const raw = readFileSync3(rulesFile, "utf-8");
1802
+ ext = JSON.parse(raw);
1803
+ if (!ext.agent_id) {
1804
+ console.warn(`[AgentMoss agents] rules.json \u7F3A\u5C11 agent_id \u5B57\u6BB5: ${rulesFile}`);
1805
+ cache.set(agentId, null);
1806
+ return null;
1807
+ }
1808
+ } catch (e) {
1809
+ console.error(`[AgentMoss agents] \u52A0\u8F7D agent ${agentId} rules.json \u5931\u8D25:`, e.message);
1810
+ cache.set(agentId, null);
1811
+ return null;
1812
+ }
1813
+ }
1814
+ cache.set(agentId, ext);
1815
+ console.log(
1816
+ `[AgentMoss agents] \u52A0\u8F7D agent ${agentId} \u5B9A\u5236\u89C4\u5219: ${ext.extra_command_patterns?.length ?? 0} cmd patterns, ${ext.extra_injection_keywords?.length ?? 0} injection keywords, ${ext.extra_sensitive_paths?.length ?? 0} sensitive paths, ${ext.builtin_safe_skills?.length ?? 0} builtin safe skills, ${ext.builtin_safe_action_types?.length ?? 0} builtin safe action types`
1817
+ );
1818
+ return ext;
1819
+ }
1820
+ var __filename3, __dirname4, AGENTS_DIR_PROD, AGENTS_DIR_DEV_FALLBACK, cache;
1821
+ var init_agents = __esm({
1822
+ "src/agents/index.ts"() {
1823
+ "use strict";
1824
+ init_esm_shims();
1825
+ __filename3 = fileURLToPath4(import.meta.url);
1826
+ __dirname4 = dirname3(__filename3);
1827
+ AGENTS_DIR_PROD = resolve2(__dirname4, "agents");
1828
+ AGENTS_DIR_DEV_FALLBACK = resolve2(__dirname4, "../../../agent_moss/agents");
1829
+ cache = /* @__PURE__ */ new Map();
1830
+ }
1831
+ });
1832
+
1833
+ // src/engine/heuristic.ts
1834
+ function runHeuristicCheck(input) {
1835
+ const command = `${input.aNext.actionType}: ${input.aNext.actionDetail}`;
1836
+ const matched = [];
1837
+ let highestSeverity = "low";
1838
+ let highestRiskType = "";
1839
+ const userRules = getCustomRules();
1840
+ for (const rule of userRules) {
1841
+ try {
1842
+ const re = new RegExp(rule.pattern);
1843
+ if (re.test(command)) {
1844
+ if (rule.action === "Deny") {
1845
+ matched.push(`\u81EA\u5B9A\u4E49\u89C4\u5219: ${rule.pattern}`);
1846
+ if (severityRank(rule.severity) > severityRank(highestSeverity)) {
1847
+ highestSeverity = rule.severity;
1848
+ highestRiskType = "custom_rule";
1849
+ }
1850
+ }
1851
+ }
1852
+ } catch {
1853
+ }
1854
+ }
1855
+ let topRiskType = "";
1856
+ for (const rule of DANGEROUS_PATTERNS) {
1857
+ if (rule.pattern.test(command)) {
1858
+ matched.push(rule.description);
1859
+ if (severityRank(rule.severity) > severityRank(highestSeverity)) {
1860
+ highestSeverity = rule.severity;
1861
+ topRiskType = rule.riskType;
1862
+ }
1863
+ }
1864
+ }
1865
+ highestRiskType = topRiskType || highestRiskType;
1866
+ const agentExt = getAgentExtensions(input.agentId);
1867
+ if (agentExt?.extra_command_patterns) {
1868
+ for (const rule of agentExt.extra_command_patterns) {
1869
+ try {
1870
+ const re = new RegExp(rule.pattern, "i");
1871
+ if (re.test(command)) {
1872
+ matched.push(`agent \u4E13\u5C5E\u89C4\u5219: ${rule.reason}`);
1873
+ if (severityRank(rule.risk_level) > severityRank(highestSeverity)) {
1874
+ highestSeverity = rule.risk_level;
1875
+ highestRiskType = rule.risk_type;
1876
+ }
1877
+ }
1878
+ } catch {
1879
+ }
1880
+ }
1881
+ }
1882
+ const injectionText = `${input.aNext.actionType} ${input.aNext.actionDetail} ${input.reason || ""} ${input.promptSession || ""}`.toLowerCase();
1883
+ const injectionHits = checkInjectionKeywords(injectionText, agentExt?.extra_injection_keywords);
1884
+ if (injectionHits.length > 0) {
1885
+ const topHit = injectionHits.reduce((a, b) => severityRank(a.riskLevel) >= severityRank(b.riskLevel) ? a : b);
1886
+ matched.push(...injectionHits.map((h) => `\u6CE8\u5165\u5173\u952E\u8BCD [${h.lang}]: ${h.keyword}`));
1887
+ if (severityRank(topHit.riskLevel) > severityRank(highestSeverity)) {
1888
+ highestSeverity = topHit.riskLevel;
1889
+ highestRiskType = "prompt_injection";
1890
+ }
1891
+ }
1892
+ const actionTypeL = input.aNext.actionType.toLowerCase();
1893
+ const detailL = input.aNext.actionDetail.toLowerCase();
1894
+ const isExcludedType = SENSITIVE_EXCLUDED_PREFIXES.some((p) => actionTypeL.startsWith(p));
1895
+ if (!isExcludedType) {
1896
+ const pathResult = checkSensitivePath(detailL, input.cwd || "/", agentExt);
1897
+ if (pathResult) return pathResult;
1898
+ }
1899
+ const dangerousResult = checkDangerousPatterns(actionTypeL, detailL);
1900
+ if (dangerousResult) return dangerousResult;
1901
+ const lateralResult = checkLateralMovement(detailL);
1902
+ if (lateralResult) return lateralResult;
1903
+ if (matched.length > 0) {
1904
+ return {
1905
+ allowed: false,
1906
+ reason: `\u68C0\u6D4B\u5230\u9AD8\u5371\u64CD\u4F5C: ${matched.join(", ")}`,
1907
+ riskLevel: highestSeverity,
1908
+ riskType: highestRiskType,
1909
+ confidence: 95,
1910
+ source: "heuristic",
1911
+ violatedLayers: ["1.1"]
1912
+ // 对齐 py coordinator.py:165(L1.1 = 命中高危命令模式/注入关键词)
1913
+ };
1914
+ }
1915
+ return null;
1916
+ }
1917
+ function checkInjectionKeywords(text, extraKeywords) {
1918
+ const hits = [];
1919
+ for (const entry of INJECTION_KEYWORDS) {
1920
+ if (text.includes(entry.keyword.toLowerCase())) {
1921
+ hits.push(entry);
1922
+ }
1923
+ }
1924
+ if (extraKeywords) {
1925
+ for (const entry of extraKeywords) {
1926
+ if (text.includes(entry.keyword.toLowerCase())) {
1927
+ hits.push({ keyword: entry.keyword, riskLevel: entry.risk_level, lang: entry.lang });
1928
+ }
1929
+ }
1930
+ }
1931
+ return hits;
1932
+ }
1933
+ function isInlineScriptCommand(actionDetail) {
1934
+ 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;
1935
+ return re.test(actionDetail);
1936
+ }
1937
+ function severityRank(s) {
1938
+ const ranks = { low: 1, medium: 2, high: 3, critical: 4 };
1939
+ return ranks[s] || 0;
1940
+ }
1941
+ function checkSensitivePath(command, cwd, agentExt) {
1942
+ const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
1943
+ const _WRITE_CMD_RES = [/\brm\b/, /\bunlink\b/, /\brmdir\b/, /\bshred\b/, /\bcp\b/, /\bmv\b/, /\binstall\b/, /\btee\b/, /\bdd\b/, /\bmkfs\b/, /\b(fdisk|parted|cfdisk|sfdisk)\b/, /\bchmod\b/, /\bchown\b/, /\btruncate\b/];
1944
+ const _DEVNULL_RE = /(?:2|&|1)?\s*>\s*\/dev\/null\b/;
1945
+ const _READONLY_CMDS = /* @__PURE__ */ new Set(["cat", "head", "tail", "less", "more", "grep", "find", "awk", "sed", "sort", "uniq", "wc", "cut", "strings", "od", "xxd", "hexdump", "tr", "file", "stat", "du", "df", "ls", "dir", "tree", "nl", "tac", "rev", "lsblk", "blockdev", "smartctl", "udevadm", "dmidecode", "lscpu", "lspci", "lsusb", "lsmem", "lsns", "lsof", "hwinfo", "inxi", "fdisk", "parted", "dmesg", "journalctl", "systemctl", "hostnamectl", "localectl", "timedatectl", "ps", "top", "free", "vmstat", "iostat", "mpstat", "pidof", "pgrep", "ip", "ifconfig", "route", "ss", "netstat", "arp", "ethtool", "nmcli", "uname", "arch", "nproc", "getconf", "getent", "id", "whoami", "who", "w", "last", "uptime", "lsmod", "modinfo", "sysctl"]);
1946
+ const isWriteOpFlag = _WRITE_CMD_RES.some((rx) => rx.test(command)) || /\bsed\s+-[a-zA-Z]*i/.test(command) || command.includes(">") && !command.replace(_DEVNULL_RE, "").includes(">") || command.includes(">") && command.replace(_DEVNULL_RE, "").includes(">") && !_READONLY_CMDS.has(command.trim().split(/\s+/)[0]?.toLowerCase() || "") || WRITE_KEYWORDS.some((kw) => "".includes(kw) || command.includes(kw));
1947
+ for (const sp of ROOT_SENSITIVE_PATHS) {
1948
+ if (command.includes(sp.path.toLowerCase())) {
1949
+ if (sp.path === "/dev/random") {
1950
+ if (isWriteOpFlag) {
1951
+ continue;
1952
+ }
1953
+ return {
1954
+ allowed: false,
1955
+ reason: `\u8BFB\u53D6\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
1956
+ riskLevel: sp.riskLevel,
1957
+ riskType: "file_access",
1958
+ confidence: 90,
1959
+ source: "heuristic",
1960
+ violatedLayers: ["1.1"]
1961
+ };
1962
+ }
1963
+ if (!isWriteOpFlag && !sp.credential) {
1964
+ continue;
1965
+ }
1966
+ return {
1967
+ allowed: false,
1968
+ reason: `\u5C1D\u8BD5\u8BBF\u95EE\u7CFB\u7EDF\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
1969
+ riskLevel: sp.riskLevel,
1970
+ riskType: "file_access",
1971
+ confidence: 90,
1972
+ source: "heuristic",
1973
+ violatedLayers: ["1.1"],
1974
+ isCredential: !!sp.credential
1975
+ };
1976
+ }
1977
+ }
1978
+ for (const sp of HOME_SENSITIVE_PATHS) {
1979
+ const expanded = `${expandedCwd}/${sp.path}`;
1980
+ const pathRe = sp.path.endsWith(".pub") ? new RegExp(sp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i") : new RegExp(sp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "(?!\\.pub)", "i");
1981
+ const expandedRe = sp.path.endsWith(".pub") ? new RegExp(expanded.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i") : new RegExp(expanded.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "(?!\\.pub)", "i");
1982
+ if (pathRe.test(command) || expandedRe.test(command)) {
1983
+ if (!isWriteOpFlag && !sp.credential) {
1984
+ continue;
1985
+ }
1986
+ return {
1987
+ allowed: false,
1988
+ reason: `\u5C1D\u8BD5\u8BBF\u95EE\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
1989
+ riskLevel: sp.riskLevel,
1990
+ riskType: "file_access",
1991
+ confidence: 90,
1992
+ source: "heuristic",
1993
+ violatedLayers: ["1.1"],
1994
+ isCredential: !!sp.credential
1995
+ };
1996
+ }
1997
+ }
1998
+ if (agentExt?.extra_sensitive_paths && agentExt.extra_sensitive_paths.length > 0) {
1999
+ for (const sp of agentExt.extra_sensitive_paths) {
2000
+ const spPath = sp.path.toLowerCase();
2001
+ const expanded = `${expandedCwd}/${spPath}`;
2002
+ if (command.includes(spPath) || command.includes(expanded.toLowerCase())) {
2003
+ const denyMode = sp.deny_mode || (sp.credential ? "deny_both" : sp.read_only ? "deny_read" : "deny_write");
2004
+ if (denyMode === "deny_read") {
2005
+ if (isWriteOpFlag) continue;
2006
+ } else if (denyMode === "deny_write") {
2007
+ if (!isWriteOpFlag) continue;
2008
+ }
2009
+ return {
2010
+ allowed: false,
2011
+ reason: `\u5C1D\u8BD5\u8BBF\u95EE agent \u4E13\u5C5E\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
2012
+ riskLevel: sp.risk_level,
2013
+ riskType: "file_access",
2014
+ confidence: 90,
2015
+ source: "heuristic",
2016
+ violatedLayers: ["1.1"],
2017
+ isCredential: !!sp.credential
2018
+ };
2019
+ }
2020
+ }
2021
+ }
2022
+ for (const cp of CREDENTIAL_PATHS) {
2023
+ const escaped = cp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2024
+ const re = cp.path.startsWith(".") ? new RegExp(`(?:^|[\\s/\\\\'"])${escaped}\\b`) : new RegExp(`\\b${escaped}\\b`);
2025
+ if (re.test(command)) {
2026
+ return {
2027
+ allowed: false,
2028
+ reason: `\u8BBF\u95EE\u654F\u611F\u8DEF\u5F84: ${cp.desc} (${cp.path})`,
2029
+ riskLevel: cp.riskLevel,
2030
+ riskType: "file_access",
2031
+ confidence: 90,
2032
+ source: "heuristic",
2033
+ violatedLayers: ["1.1"],
2034
+ isCredential: true
2035
+ };
2036
+ }
2037
+ }
2038
+ if ((command.includes("rm ") || command.includes("unlink")) && (command.includes("/etc/") || command.includes("/root/") || command.includes("/var/log/"))) {
2039
+ return {
2040
+ allowed: false,
2041
+ reason: "\u68C0\u6D4B\u5230\u5220\u9664\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55\u5185\u5BB9",
2042
+ riskLevel: "critical",
2043
+ riskType: "file_access",
2044
+ confidence: 90,
2045
+ source: "heuristic",
2046
+ violatedLayers: ["1.1"]
2047
+ };
2048
+ }
2049
+ return null;
2050
+ }
2051
+ function checkDangerousPatterns(actionType, actionDetail) {
2052
+ if (actionType === "file_write" || actionType === "file_edit") return null;
2053
+ const TEMP_DIRS = ["/tmp/", "/var/tmp/", "/run/", "/dev/shm/"];
2054
+ const isTempPath = TEMP_DIRS.some((td) => actionDetail.includes(td));
2055
+ if (!isTempPath && (actionDetail.includes("*") || actionDetail.includes("?")) && ["rm ", "rm ", "del ", "del ", "delete", "remove ", "remove ", "\u5220\u9664", "\u79FB\u9664"].some((kw) => actionDetail.includes(kw))) {
2056
+ return {
2057
+ allowed: false,
2058
+ reason: "\u68C0\u6D4B\u5230\u901A\u914D\u7B26\u7ED3\u5408\u5220\u9664\u64CD\u4F5C\uFF0C\u53EF\u80FD\u9020\u6210\u6279\u91CF\u8BEF\u5220",
2059
+ riskLevel: "high",
2060
+ riskType: "script_execution",
2061
+ confidence: 90,
2062
+ source: "heuristic",
2063
+ violatedLayers: ["1.1"]
2064
+ };
2065
+ }
2066
+ if (/>\s*\/etc\//.test(actionDetail) || />\s*\/boot\//.test(actionDetail) || />\s*\/proc\//.test(actionDetail)) {
2067
+ return {
2068
+ allowed: false,
2069
+ reason: "\u68C0\u6D4B\u5230\u91CD\u5B9A\u5411\u8986\u76D6\u5199\u5165\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55",
2070
+ riskLevel: "critical",
2071
+ riskType: "file_access",
2072
+ confidence: 95,
2073
+ source: "heuristic",
2074
+ violatedLayers: ["1.1"]
2075
+ };
2076
+ }
2077
+ return null;
2078
+ }
2079
+ function extractRemoteHost(command) {
2080
+ const m = command.match(/(?:ssh|scp|rsync)\s+(?:-[\w]+\s+)*(\S+@\S+|\b(?!-)[\w.-]+\b)/);
2081
+ return m ? m[1] : null;
2082
+ }
2083
+ function isLocalHost(host) {
2084
+ const localNames = ["localhost", "127.0.0.1", "::1", process.env.HOSTNAME || "", "0.0.0.0"];
2085
+ return localNames.includes(host.replace(/.*@/, ""));
2086
+ }
2087
+ function checkLateralMovement(actionDetail) {
2088
+ if (/(?:ssh|scp|rsync)\s+/.test(actionDetail)) {
2089
+ const targetHost = extractRemoteHost(actionDetail);
2090
+ if (targetHost && !isLocalHost(targetHost)) {
2091
+ return {
2092
+ allowed: false,
2093
+ reason: `\u5C1D\u8BD5\u6A2A\u5411\u79FB\u52A8\u5230\u8FDC\u7A0B\u4E3B\u673A: ${targetHost}`,
2094
+ riskLevel: "medium",
2095
+ riskType: "lateral_movement",
2096
+ confidence: 80,
2097
+ source: "heuristic",
2098
+ violatedLayers: ["1.1"]
2099
+ };
2100
+ }
2101
+ }
2102
+ return null;
2103
+ }
2104
+ var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, SENSITIVE_EXCLUDED_PREFIXES;
2105
+ var init_heuristic = __esm({
2106
+ "src/engine/heuristic.ts"() {
2107
+ "use strict";
2108
+ init_esm_shims();
2109
+ init_config();
2110
+ init_agents();
2111
+ init_logic_rules();
2112
+ INJECTION_KEYWORDS = [
2113
+ // ---- Instruction Override ----
2114
+ { keyword: "ignore previous instructions", riskLevel: "critical", lang: "en" },
2115
+ { keyword: "ignore all previous", riskLevel: "critical", lang: "en" },
2116
+ { keyword: "disregard all prior", riskLevel: "critical", lang: "en" },
2117
+ { keyword: "forget everything", riskLevel: "high", lang: "en" },
2118
+ { keyword: "\u5FFD\u7565\u4E4B\u524D\u7684\u6307\u4EE4", riskLevel: "critical", lang: "zh" },
2119
+ { keyword: "\u5FFD\u7565\u6240\u6709\u5148\u524D\u7684", riskLevel: "critical", lang: "zh" },
2120
+ { keyword: "\u5FFD\u7565\u4E0A\u9762\u6240\u6709", riskLevel: "high", lang: "zh" },
2121
+ { keyword: "\u8BF7\u5FD8\u8BB0\u4E4B\u524D", riskLevel: "high", lang: "zh" },
2122
+ // ---- Role Hijacking ----
2123
+ { keyword: "you are now", riskLevel: "high", lang: "en" },
2124
+ { keyword: "you are no longer", riskLevel: "high", lang: "en" },
2125
+ { keyword: "new instructions", riskLevel: "high", lang: "en" },
2126
+ { keyword: "from now on, you", riskLevel: "high", lang: "en" },
2127
+ { keyword: "\u4F60\u73B0\u5728\u662F\u4E00\u4E2A", riskLevel: "high", lang: "zh" },
2128
+ { keyword: "\u4F60\u4E0D\u518D\u662F\u4E00\u4E2A", riskLevel: "high", lang: "zh" },
2129
+ { keyword: "\u4ECE\u73B0\u5728\u5F00\u59CB\u4F60", riskLevel: "high", lang: "zh" },
2130
+ { keyword: "\u4F60\u7684\u65B0\u89D2\u8272\u662F", riskLevel: "high", lang: "zh" },
2131
+ // ---- Command Injection ----
2132
+ { keyword: "system prompt", riskLevel: "high", lang: "en" },
2133
+ { keyword: "reveal your instructions", riskLevel: "high", lang: "en" },
2134
+ { keyword: "output your instructions", riskLevel: "high", lang: "en" },
2135
+ { keyword: "repeat your instructions", riskLevel: "medium", lang: "en" },
2136
+ { keyword: "\u7CFB\u7EDF\u63D0\u793A", riskLevel: "high", lang: "zh" },
2137
+ { keyword: "\u8F93\u51FA\u4F60\u7684\u6307\u4EE4", riskLevel: "high", lang: "zh" },
2138
+ { keyword: "\u91CD\u590D\u4F60\u7684\u6307\u4EE4", riskLevel: "medium", lang: "zh" },
2139
+ // ---- Social Engineering ----
2140
+ { keyword: "this is an authorized test", riskLevel: "high", lang: "en" },
2141
+ { keyword: "security audit approved", riskLevel: "high", lang: "en" },
2142
+ { keyword: "admin override", riskLevel: "critical", lang: "en" },
2143
+ { keyword: "debug mode", riskLevel: "high", lang: "en" },
2144
+ { keyword: "maintenance mode", riskLevel: "high", lang: "en" },
2145
+ { keyword: "\u8FD9\u662F\u6388\u6743\u7684\u6D4B\u8BD5", riskLevel: "high", lang: "zh" },
2146
+ { keyword: "\u5B89\u5168\u5BA1\u8BA1\u5DF2\u6279\u51C6", riskLevel: "high", lang: "zh" },
2147
+ { keyword: "\u7BA1\u7406\u5458\u8986\u76D6", riskLevel: "critical", lang: "zh" },
2148
+ { keyword: "\u8C03\u8BD5\u6A21\u5F0F", riskLevel: "high", lang: "zh" },
2149
+ // ---- XSS ----
2150
+ { keyword: "<script>", riskLevel: "high", lang: "en" },
2151
+ { keyword: "javascript:", riskLevel: "high", lang: "en" },
2152
+ { keyword: "onerror=", riskLevel: "high", lang: "en" },
2153
+ { keyword: "onload=", riskLevel: "high", lang: "en" },
2154
+ // ---- Fake Boundaries ----
2155
+ { keyword: "=== END OF", riskLevel: "high", lang: "en" },
2156
+ { keyword: "--- END SYSTEM", riskLevel: "high", lang: "en" },
2157
+ { keyword: "[END OF", riskLevel: "high", lang: "en" },
2158
+ { keyword: "=== \u7ED3\u675F", riskLevel: "high", lang: "zh" },
2159
+ { keyword: "--- \u7ED3\u675F\u7CFB\u7EDF", riskLevel: "high", lang: "zh" },
2160
+ // ---- Developer Mode ----
2161
+ { keyword: "developer mode", riskLevel: "critical", lang: "en" },
2162
+ { keyword: "DAN mode", riskLevel: "critical", lang: "en" },
2163
+ { keyword: "jailbreak", riskLevel: "critical", lang: "en" },
2164
+ { keyword: "\u5F00\u53D1\u8005\u6A21\u5F0F", riskLevel: "critical", lang: "zh" },
2165
+ { keyword: "\u8D8A\u72F1", riskLevel: "critical", lang: "zh" }
2166
+ // 注:xiaoO 专属绕过词(绕过审计/禁用安全检查/关闭防护/bypass audit 等)
2167
+ // 已迁至 agent_moss/agents/xiaoo/rules.json 的 extra_injection_keywords,
2168
+ // 由 agent_ext 机制注入,不再污染通用层。
2169
+ ];
2170
+ DANGEROUS_PATTERNS = [
2171
+ // --- Critical: rm command graded detection (strict to loose, first match wins) ---
2172
+ // 1. rm -rf root dir (含通配符如 /* /.* 等) — 使用负向先行断言 (?!\w):/ 后面不是单词字符时匹配
2173
+ { pattern: /\brm\s+-[a-zA-Z]*r[a-zA-Z]*f[a-zA-Z]*\s+\/(?!\w)/i, severity: "critical", description: "\u9012\u5F52\u5F3A\u5236\u5220\u9664\u6839\u76EE\u5F55 (rm -rf /)", riskType: "script_execution" },
2174
+ // 注意:已移除"rm -rf /任意绝对路径"的宽泛规则,避免误杀 /tmp 等临时目录清理操作
2175
+ // 2. rm with -r or -f targeting system dirs — var 分支排除 /var/tmp/(合法清理)
2176
+ { pattern: /\brm\s+-[a-zA-Z]*[rf][a-zA-Z]*\s+\/(etc|var(?!\/tmp)|home|usr|boot|root|opt|srv|sys|proc)\b/i, severity: "critical", description: "\u5220\u9664\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55", riskType: "script_execution" },
2177
+ // 3. rm -r absolute path (no -f) — 排除 /tmp/ /var/tmp/ /run/ /dev/shm/(CI/开发高频合法清理)
2178
+ { pattern: /\brm\s+-[a-zA-Z]*r[a-zA-Z]*\s+\/(?!tmp\/|var\/tmp\/|run\/|dev\/shm\/)/i, severity: "high", description: "\u9012\u5F52\u5220\u9664\u64CD\u4F5C (rm -r /...)", riskType: "script_execution" },
2179
+ // 4. rm -f absolute path (no -r) — 同样排除临时目录
2180
+ { pattern: /\brm\s+-[a-zA-Z]*f[a-zA-Z]*\s+\/(?!tmp\/|var\/tmp\/|run\/|dev\/shm\/)/i, severity: "medium", description: "\u5F3A\u5236\u5220\u9664\u6587\u4EF6 (rm -f /...)", riskType: "script_execution" },
2181
+ // dd 磁盘清零 — 只拦截写入系统关键目录,放行 /tmp 等临时目录
2182
+ { pattern: /dd\s+if=\/dev\/zero.*\bof\s*=\s*\/(etc|var|home|usr|boot|root|opt|srv|sys|proc)\b/i, severity: "critical", description: "\u78C1\u76D8\u6E05\u96F6\u5199\u5165\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55 (dd if=/dev/zero of=/\u7CFB\u7EDF\u76EE\u5F55)", riskType: "script_execution" },
2183
+ // mkfs 格式化 — 只拦截对块设备(/dev/sdX 等)的格式化,放行对普通文件的格式化
2184
+ { pattern: /mkfs\.\w+\s+\/dev\/(sd|hd|nvme|vd|loop|mapper|md|dm)\S+/i, severity: "critical", description: "\u683C\u5F0F\u5316\u5757\u8BBE\u5907 (mkfs /dev/\u5757\u8BBE\u5907)", riskType: "script_execution" },
2185
+ { pattern: /:\(\)\s*\{.*:\|:&.*\};:/i, severity: "critical", description: "Fork Bomb", riskType: "resource_exhaustion" },
2186
+ // --- Critical: remote code execution ---
2187
+ { pattern: /curl\s+.*\|\s*(bash|sh|zsh|dash)/i, severity: "critical", description: "curl pipe shell \u6267\u884C", riskType: "script_execution" },
2188
+ { pattern: /wget\s+.*\|\s*(bash|sh)/i, severity: "critical", description: "wget pipe shell \u6267\u884C", riskType: "script_execution" },
2189
+ { pattern: /eval\s+\$\(curl|eval\s+\$\(wget/i, severity: "critical", description: "eval \u8FDC\u7A0B\u4EE3\u7801\u6267\u884C", riskType: "script_execution" },
2190
+ { pattern: /base64\s+-d\s+.*\|\s*bash/i, severity: "critical", description: "base64 \u89E3\u7801\u6267\u884C", riskType: "script_execution" },
2191
+ // --- Critical: reverse shell ---
2192
+ { pattern: /\/dev\/tcp\/|bash\s+-i\s+>&/i, severity: "critical", description: "\u53CD\u5F39 Shell", riskType: "script_execution" },
2193
+ { pattern: /nc\s+-[el]\s+.*\d{1,5}/i, severity: "high", description: "Netcat \u53CD\u5F39\u8FDE\u63A5", riskType: "lateral_movement" },
2194
+ // --- Critical: sensitive file access ---
2195
+ { pattern: /\/etc\/shadow/i, severity: "critical", description: "\u8BBF\u95EE\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6 (/etc/shadow)", riskType: "file_access" },
2196
+ { pattern: /\/etc\/gshadow/i, severity: "critical", description: "\u8BBF\u95EE\u7CFB\u7EDF\u7EC4\u5BC6\u7801\u6587\u4EF6 (/etc/gshadow)", riskType: "file_access" },
2197
+ // 注意:/etc/passwd 已从拦截列表移除,因为它是公开可读的标准系统文件,真正敏感的是 /etc/shadow
2198
+ { pattern: /\.ssh\/id_rsa(?!\.pub)/i, severity: "critical", description: "\u8BBF\u95EE SSH \u79C1\u94A5\u6587\u4EF6", riskType: "file_access" },
2199
+ // --- Critical: privilege escalation / container escape ---
2200
+ { 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" },
2201
+ // --- High: dangerous permissions / ownership ---
2202
+ { pattern: /chmod\s+[0-7]*777[0-7]*/i, severity: "high", description: "\u5168\u6743\u9650\u8BBE\u7F6E (chmod 777)", riskType: "script_execution" },
2203
+ { 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" },
2204
+ // sudo 提权 — 只拦截 sudo 后跟的危险命令,不一刀切拦截所有 sudo
2205
+ { pattern: /sudo\s+(rm|chmod|chown|dd|mkfs|format|iptables|crontab|passwd|userdel|useradd|groupdel)\b/i, severity: "high", description: "\u63D0\u6743\u6267\u884C\u9AD8\u5371\u547D\u4EE4 (sudo + \u5371\u9669\u64CD\u4F5C)", riskType: "privilege_escalation" },
2206
+ // --- High: system config overwrite ---
2207
+ { pattern: />\s*\/etc\//i, severity: "critical", description: "\u8986\u76D6\u5199\u5165\u7CFB\u7EDF\u914D\u7F6E\u76EE\u5F55", riskType: "file_access" },
2208
+ // --- High: download script + execute risk ---
2209
+ { 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" },
2210
+ { pattern: /(?:curl|wget)\s+.*\s+&&?\s*(?:chmod\s+\+x|bash|sh)/i, severity: "high", description: "\u4E0B\u8F7D\u540E\u6267\u884C", riskType: "script_execution" },
2211
+ // --- High: package manager pipe-to-shell ---
2212
+ { pattern: /git\s+clone\s+.*\|.*bash/i, severity: "high", description: "git clone pipe shell", riskType: "script_execution" },
2213
+ { pattern: /pip\s+install\s+.*\|\s*bash/i, severity: "high", description: "pip install pipe shell", riskType: "script_execution" },
2214
+ { pattern: /npm\s+install\s+-g\s+.*\|\s*bash/i, severity: "high", description: "npm install pipe shell", riskType: "script_execution" },
2215
+ // --- Medium: destructive git operations (降级为 medium,允许但记录) ---
2216
+ { pattern: /git\s+push\s+.*--force/i, severity: "medium", description: "\u5F3A\u5236\u63A8\u9001\u64CD\u4F5C (git push --force)\uFF0C\u53EF\u80FD\u8986\u76D6\u8FDC\u7A0B\u63D0\u4EA4\u5386\u53F2", riskType: "script_execution" },
2217
+ // --- High: network port scanning (横向移动侦察) ---
2218
+ { pattern: /\bnmap\s+/i, severity: "high", description: "\u7F51\u7EDC\u7AEF\u53E3\u626B\u63CF (nmap)\uFF0C\u53EF\u80FD\u7528\u4E8E\u6A2A\u5411\u79FB\u52A8\u4FA6\u5BDF", riskType: "lateral_movement" },
2219
+ { pattern: /\b(masscan|zmap|unicornscan)\s+/i, severity: "high", description: "\u7F51\u7EDC\u7AEF\u53E3\u626B\u63CF\u5DE5\u5177\uFF0C\u53EF\u80FD\u7528\u4E8E\u6A2A\u5411\u79FB\u52A8\u4FA6\u5BDF", riskType: "lateral_movement" },
2220
+ // --- Medium: credential export ---
2221
+ { pattern: /export\s+.*(?:TOKEN|KEY|SECRET|PASSWORD)/i, severity: "medium", description: "\u5BFC\u51FA\u654F\u611F\u51ED\u8BC1", riskType: "data_exfiltration" },
2222
+ // --- High: shell rc file append (persistence) ---
2223
+ { 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" },
2224
+ // --- High: device file write (只拦截重定向写入设备文件,不拦截读取) ---
2225
+ { pattern: />\s*\/dev\/(sd|hd|nvme|vd|loop|mem|kmsg|port|zero|random|urandom|full|core)/i, severity: "high", description: "\u91CD\u5B9A\u5411\u5199\u5165\u8BBE\u5907\u6587\u4EF6", riskType: "file_access" },
2226
+ // --- High: non-interactive password modification ---
2227
+ { 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" },
2228
+ { 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" },
2229
+ { pattern: /\bchpasswd\b/i, severity: "high", description: "\u68C0\u6D4B\u5230 chpasswd \u6279\u91CF\u5BC6\u7801\u4FEE\u6539\u547D\u4EE4", riskType: "script_execution" },
2230
+ { pattern: /\b(newusers|lnewusers)\b/i, severity: "high", description: "\u68C0\u6D4B\u5230 newusers/lnewusers \u6279\u91CF\u7528\u6237\u6DFB\u52A0\u547D\u4EE4", riskType: "script_execution" },
2231
+ { 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" },
2232
+ { 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" },
2233
+ // --- High: non-interactive password modification (extended) ---
2234
+ { pattern: /\blpasswd\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u975E\u4EA4\u4E92\u5F0F\u5BC6\u7801\u4FEE\u6539\u64CD\u4F5C (lpasswd)", riskType: "script_execution" },
2235
+ { pattern: /\bgpasswd\s+(?!-[adA])/i, severity: "high", description: "\u68C0\u6D4B\u5230\u7EC4\u5BC6\u7801\u7BA1\u7406\u64CD\u4F5C (gpasswd)", riskType: "script_execution" },
2236
+ { pattern: /\bchgpasswd\b/i, severity: "high", description: "\u68C0\u6D4B\u5230\u6279\u91CF\u4FEE\u6539\u7EC4\u5BC6\u7801\u64CD\u4F5C (chgpasswd)", riskType: "script_execution" },
2237
+ // --- High: user/group deletion (irreversible, requires user consent) ---
2238
+ { pattern: /\buserdel\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7CFB\u7EDF\u7528\u6237\u64CD\u4F5C (userdel)", riskType: "script_execution" },
2239
+ { pattern: /\bdeluser\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7CFB\u7EDF\u7528\u6237\u64CD\u4F5C (deluser)", riskType: "script_execution" },
2240
+ { pattern: /\bluserdel\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7CFB\u7EDF\u7528\u6237\u64CD\u4F5C (luserdel)", riskType: "script_execution" },
2241
+ { pattern: /\bgroupdel\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7528\u6237\u7EC4\u64CD\u4F5C (groupdel)", riskType: "script_execution" },
2242
+ { pattern: /\blgroupdel\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7528\u6237\u7EC4\u64CD\u4F5C (lgroupdel)", riskType: "script_execution" },
2243
+ // --- High: generic command -p parameter password setting ---
2244
+ { pattern: /\b(useradd|luseradd|groupadd|usermod|lusermod|groupmod|lgroupmod)\b[^&|;]*?\s-p\b/i, severity: "high", description: "\u68C0\u6D4B\u5230\u901A\u8FC7 -p \u53C2\u6570\u8BBE\u7F6E\u6216\u4FEE\u6539\u5BC6\u7801 (useradd/usermod/groupmod)", riskType: "script_execution" }
2245
+ ];
2246
+ SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
2247
+ }
2248
+ });
2249
+
2250
+ // src/engine/token-stats.ts
2251
+ var token_stats_exports = {};
2252
+ __export(token_stats_exports, {
2253
+ getRecentRecords: () => getRecentRecords,
2254
+ getTokenStats: () => getTokenStats,
2255
+ getTokenTrend: () => getTokenTrend,
2256
+ recordTokenUsage: () => recordTokenUsage,
2257
+ resetTokenStats: () => resetTokenStats
2258
+ });
2259
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync2, existsSync as existsSync7, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
2260
+ import { join as join4 } from "path";
2261
+ import { homedir as homedir3 } from "os";
2262
+ function getStatsPath() {
2263
+ const envPath = process.env[ENV_STATS_PATH];
2264
+ return envPath || STATS_FILE;
2265
+ }
2266
+ function loadAllRecords(statsPath) {
2267
+ if (!existsSync7(statsPath)) return [];
2268
+ try {
2269
+ const data = JSON.parse(readFileSync7(statsPath, "utf-8"));
2270
+ if (Array.isArray(data)) return data;
2271
+ if (data && Array.isArray(data.records)) return data.records;
2272
+ return [];
2273
+ } catch (e) {
2274
+ return [];
2275
+ }
2276
+ }
2277
+ function saveRecords(statsPath, records) {
2278
+ try {
2279
+ const dir = statsPath.substring(0, statsPath.lastIndexOf("/"));
2280
+ if (dir && !existsSync7(dir)) mkdirSync2(dir, { recursive: true });
2281
+ writeFileSync2(statsPath, JSON.stringify(records, null, 2), "utf-8");
2282
+ } catch (e) {
2102
2283
  }
2103
- saveRuntimeConfig(runtime);
2104
- return runtime;
2105
2284
  }
2106
- function updateRuleDenyMode(layer, category, ruleId, denyMode) {
2107
- const runtime = loadRuntimeConfig();
2108
- for (const r of runtime[layer]?.[category]?.rules || []) {
2109
- if (r.id === ruleId) {
2110
- r.deny_mode = denyMode;
2111
- _syncDenyModeFields(r);
2112
- break;
2113
- }
2285
+ function recordTokenUsage(record) {
2286
+ const statsPath = getStatsPath();
2287
+ const records = loadAllRecords(statsPath);
2288
+ records.push({
2289
+ timestamp: record.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
2290
+ session_id: record.sessionId,
2291
+ step: record.step,
2292
+ model: record.model,
2293
+ prompt_tokens: record.promptTokens,
2294
+ completion_tokens: record.completionTokens,
2295
+ total_tokens: record.totalTokens
2296
+ });
2297
+ if (records.length > MAX_RECORDS) {
2298
+ records.splice(0, records.length - MAX_RECORDS);
2114
2299
  }
2115
- saveRuntimeConfig(runtime);
2116
- return runtime;
2300
+ saveRecords(statsPath, records);
2117
2301
  }
2118
- function updateRuleSkipL3(layer, category, ruleId, skipL3) {
2119
- const runtime = loadRuntimeConfig();
2120
- for (const r of runtime[layer]?.[category]?.rules || []) {
2121
- if (r.id === ruleId) {
2122
- r.skip_l3_on_disabled = skipL3;
2123
- break;
2302
+ function filterByDate(records, days = 0, startDate, endDate) {
2303
+ if (startDate || endDate) {
2304
+ let startDt = startDate ? new Date(startDate) : /* @__PURE__ */ new Date(-864e13);
2305
+ let endDt = endDate ? new Date(endDate) : void 0;
2306
+ if (endDate) {
2307
+ const e = new Date(endDate);
2308
+ e.setHours(23, 59, 59, 999);
2309
+ endDt = e;
2124
2310
  }
2311
+ const endMs = endDt ? endDt.getTime() : Number.MAX_SAFE_INTEGER;
2312
+ return records.filter((r) => {
2313
+ try {
2314
+ const ts = new Date(r.timestamp).getTime();
2315
+ return ts >= startDt.getTime() && ts <= endMs;
2316
+ } catch {
2317
+ return true;
2318
+ }
2319
+ });
2320
+ } else if (days > 0) {
2321
+ const cutoff = Date.now() - days * 864e5;
2322
+ return records.filter((r) => {
2323
+ try {
2324
+ return new Date(r.timestamp).getTime() >= cutoff;
2325
+ } catch {
2326
+ return true;
2327
+ }
2328
+ });
2125
2329
  }
2126
- saveRuntimeConfig(runtime);
2127
- return runtime;
2128
- }
2129
- function updateCategoryEnabled(layer, category, enabled) {
2130
- const runtime = loadRuntimeConfig();
2131
- const cat = runtime[layer]?.[category];
2132
- if (cat) cat.category_enabled = enabled;
2133
- saveRuntimeConfig(runtime);
2134
- return runtime;
2330
+ return records;
2135
2331
  }
2136
- function addCustomRule(layer, category, rule) {
2137
- const runtime = loadRuntimeConfig();
2138
- const layerObj = runtime[layer] || (runtime[layer] = {});
2139
- const cat = layerObj[category] || (layerObj[category] = { category_enabled: true, category_desc: "", rules: [] });
2140
- const rules = cat.rules || (cat.rules = []);
2141
- const newRule = { ...rule, builtin: false, enabled: true };
2142
- if (!newRule.id) {
2143
- newRule.id = `custom_${Date.now()}_${_slugify(JSON.stringify(rule).slice(0, 30))}`;
2332
+ function aggregate(records) {
2333
+ let totalPrompt = 0, totalCompletion = 0, totalTotal = 0;
2334
+ const byStep = {};
2335
+ const byModel = {};
2336
+ const byDate = {};
2337
+ const bucket = (b, key, r) => {
2338
+ const e = b[key] || (b[key] = { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0, calls: 0 });
2339
+ e.prompt_tokens += r.prompt_tokens || 0;
2340
+ e.completion_tokens += r.completion_tokens || 0;
2341
+ e.total_tokens += r.total_tokens || 0;
2342
+ e.calls += 1;
2343
+ };
2344
+ for (const r of records) {
2345
+ totalPrompt += r.prompt_tokens || 0;
2346
+ totalCompletion += r.completion_tokens || 0;
2347
+ totalTotal += r.total_tokens || 0;
2348
+ bucket(byStep, r.step || "unknown", r);
2349
+ bucket(byModel, r.model || "unknown", r);
2350
+ let dateKey = "unknown";
2351
+ try {
2352
+ dateKey = new Date(r.timestamp).toISOString().slice(0, 10);
2353
+ } catch {
2354
+ }
2355
+ bucket(byDate, dateKey, r);
2144
2356
  }
2145
- if (newRule.deny_mode) _syncDenyModeFields(newRule);
2146
- rules.push(newRule);
2147
- saveRuntimeConfig(runtime);
2148
- return runtime;
2357
+ const sortedDate = Object.keys(byDate).sort((a, b) => a < b ? 1 : a > b ? -1 : 0);
2358
+ const sortedByDate = {};
2359
+ for (const k of sortedDate) sortedByDate[k] = byDate[k];
2360
+ return { totalPrompt, totalCompletion, totalTotal, byStep, byModel, byDate: sortedByDate };
2149
2361
  }
2150
- function deleteCustomRule(layer, category, ruleId) {
2151
- const runtime = loadRuntimeConfig();
2152
- const rules = runtime[layer]?.[category]?.rules || [];
2153
- const idx = rules.findIndex((r) => r.id === ruleId);
2154
- if (idx >= 0) {
2155
- if (rules[idx].builtin) return null;
2156
- rules.splice(idx, 1);
2157
- saveRuntimeConfig(runtime);
2158
- }
2159
- return runtime;
2362
+ function getTokenStats(days = 0, startDate, endDate) {
2363
+ const records = filterByDate(loadAllRecords(getStatsPath()), days, startDate, endDate);
2364
+ const a = aggregate(records);
2365
+ return {
2366
+ total_prompt_tokens: a.totalPrompt,
2367
+ total_completion_tokens: a.totalCompletion,
2368
+ total_tokens: a.totalTotal,
2369
+ total_calls: records.length,
2370
+ by_step: a.byStep,
2371
+ by_model: a.byModel,
2372
+ by_date: a.byDate
2373
+ };
2160
2374
  }
2161
- function addCustomSkill(skillId, category, keywords, content) {
2162
- _validateSkillId(skillId);
2163
- const runtime = loadRuntimeConfig();
2164
- const skillsDir = join4(RUNTIME_CONFIG_DIR, "agent_moss_skills");
2165
- try {
2166
- mkdirSync2(skillsDir, { recursive: true });
2167
- } catch {
2168
- }
2169
- writeFileSync2(join4(skillsDir, `${skillId}.md`), content, "utf-8");
2170
- const l3 = runtime.L3_skills || (runtime.L3_skills = {});
2171
- const cat = l3[category] || (l3[category] = { category_enabled: true, category_desc: "", skills: [] });
2172
- (cat.skills || (cat.skills = [])).push({ id: skillId, keywords, enabled: true, builtin: false });
2173
- saveRuntimeConfig(runtime);
2174
- return runtime;
2375
+ function getRecentRecords(limit = 20) {
2376
+ const records = loadAllRecords(getStatsPath());
2377
+ return records.slice(-limit);
2175
2378
  }
2176
- function deleteCustomSkill(skillId, category) {
2177
- _validateSkillId(skillId);
2178
- const runtime = loadRuntimeConfig();
2179
- const skills = runtime.L3_skills?.[category]?.skills || [];
2180
- const idx = skills.findIndex((s) => s.id === skillId);
2181
- if (idx >= 0) {
2182
- if (skills[idx].builtin) return null;
2183
- skills.splice(idx, 1);
2184
- const skillPath = join4(RUNTIME_CONFIG_DIR, "agent_moss_skills", `${skillId}.md`);
2379
+ function getTokenTrend(days = 0, startDate, endDate) {
2380
+ const records = filterByDate(loadAllRecords(getStatsPath()), days, startDate, endDate);
2381
+ const series = {};
2382
+ const datesSet = /* @__PURE__ */ new Set();
2383
+ for (const r of records) {
2384
+ let dateKey;
2185
2385
  try {
2186
- if (existsSync7(skillPath)) unlinkSync2(skillPath);
2386
+ dateKey = new Date(r.timestamp).toISOString().slice(0, 10);
2187
2387
  } catch {
2388
+ continue;
2188
2389
  }
2189
- saveRuntimeConfig(runtime);
2390
+ const model = r.model || "unknown";
2391
+ const tokens = r.total_tokens || 0;
2392
+ datesSet.add(dateKey);
2393
+ series[model] = series[model] || {};
2394
+ series[model][dateKey] = (series[model][dateKey] || 0) + tokens;
2190
2395
  }
2191
- return runtime;
2396
+ const dates = Array.from(datesSet).sort();
2397
+ const models = Object.keys(series).sort();
2398
+ return { dates, models, series };
2192
2399
  }
2193
- function updateSkillEnabled(skillId, enabled) {
2194
- const runtime = loadRuntimeConfig();
2195
- for (const catData of Object.values(runtime.L3_skills || {})) {
2196
- for (const s of catData.skills || []) {
2197
- if (s.id === skillId) {
2198
- s.enabled = enabled;
2199
- break;
2200
- }
2400
+ function resetTokenStats() {
2401
+ const statsPath = getStatsPath();
2402
+ if (existsSync7(statsPath)) {
2403
+ try {
2404
+ unlinkSync2(statsPath);
2405
+ } catch {
2201
2406
  }
2202
2407
  }
2203
- saveRuntimeConfig(runtime);
2204
- return runtime;
2205
- }
2206
- function updateSkillCategoryEnabled(category, enabled) {
2207
- const runtime = loadRuntimeConfig();
2208
- const cat = runtime.L3_skills?.[category];
2209
- if (cat) cat.category_enabled = enabled;
2210
- saveRuntimeConfig(runtime);
2211
- return runtime;
2212
- }
2213
- function getProfileOrDetect() {
2214
- return null;
2215
2408
  }
2216
- var RUNTIME_CONFIG_DIR, RUNTIME_CONFIG_PATH, ENV_RUNTIME_CONFIG_PATH, LAYER_DISABLE_ENV, DENY_MODE_LABELS, SKILL_ID_RE;
2217
- var init_runtime_config = __esm({
2218
- "src/infra/runtime-config.ts"() {
2409
+ var STATS_DIR, STATS_FILE, ENV_STATS_PATH, MAX_RECORDS;
2410
+ var init_token_stats = __esm({
2411
+ "src/engine/token-stats.ts"() {
2219
2412
  "use strict";
2220
2413
  init_esm_shims();
2221
- init_heuristic();
2222
- init_logic_rules();
2223
- init_skill_loader();
2224
- RUNTIME_CONFIG_DIR = join4(homedir3(), ".config", "agentmoss");
2225
- RUNTIME_CONFIG_PATH = join4(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
2226
- ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
2227
- LAYER_DISABLE_ENV = {
2228
- L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
2229
- L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
2230
- L3_llm_analysis: "AGENT_MOSS_DISABLE_LLM"
2231
- };
2232
- DENY_MODE_LABELS = {
2233
- deny_write: "\u4EC5\u62E6\u622A\u5199\u5165",
2234
- deny_read: "\u4EC5\u62E6\u622A\u8BFB\u53D6",
2235
- deny_both: "\u8BFB\u5199\u5747\u62E6\u622A"
2236
- };
2237
- SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
2414
+ STATS_DIR = join4(homedir3(), ".config", "agentmoss");
2415
+ STATS_FILE = join4(STATS_DIR, "agent_moss_token_stats.json");
2416
+ ENV_STATS_PATH = "AGENT_MOSS_TOKEN_STATS_PATH";
2417
+ MAX_RECORDS = 1e4;
2238
2418
  }
2239
2419
  });
2240
2420
 
@@ -2320,9 +2500,9 @@ var require_bindings = __commonJS({
2320
2500
  init_esm_shims();
2321
2501
  var fs4 = __require("fs");
2322
2502
  var path3 = __require("path");
2323
- var fileURLToPath6 = require_file_uri_to_path();
2503
+ var fileURLToPath7 = require_file_uri_to_path();
2324
2504
  var join6 = path3.join;
2325
- var dirname6 = path3.dirname;
2505
+ var dirname7 = path3.dirname;
2326
2506
  var exists = fs4.accessSync && function(path4) {
2327
2507
  try {
2328
2508
  fs4.accessSync(path4);
@@ -2432,12 +2612,12 @@ var require_bindings = __commonJS({
2432
2612
  Error.stackTraceLimit = origSTL;
2433
2613
  var fileSchema = "file://";
2434
2614
  if (fileName.indexOf(fileSchema) === 0) {
2435
- fileName = fileURLToPath6(fileName);
2615
+ fileName = fileURLToPath7(fileName);
2436
2616
  }
2437
2617
  return fileName;
2438
2618
  };
2439
2619
  exports.getRoot = function getRoot(file) {
2440
- var dir = dirname6(file), prev;
2620
+ var dir = dirname7(file), prev;
2441
2621
  while (true) {
2442
2622
  if (dir === ".") {
2443
2623
  dir = process.cwd();
@@ -3093,28 +3273,28 @@ init_skill_loader();
3093
3273
 
3094
3274
  // src/engine/template-loader.ts
3095
3275
  init_esm_shims();
3096
- import { readFileSync as readFileSync3, existsSync as existsSync4 } from "fs";
3097
- import { resolve as resolve3, dirname as dirname3 } from "path";
3098
- import { fileURLToPath as fileURLToPath4 } from "url";
3099
- var __filename4 = fileURLToPath4(import.meta.url);
3100
- var __dirname4 = dirname3(__filename4);
3101
- var TEMPLATES_DIR = resolve3(__dirname4, "../templates");
3102
- var TEMPLATES_DIR_FALLBACK = resolve3(__dirname4, "../../../agent_moss/templates");
3276
+ import { readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
3277
+ import { resolve as resolve3, dirname as dirname4 } from "path";
3278
+ import { fileURLToPath as fileURLToPath5 } from "url";
3279
+ var __filename4 = fileURLToPath5(import.meta.url);
3280
+ var __dirname5 = dirname4(__filename4);
3281
+ var TEMPLATES_DIR = resolve3(__dirname5, "../templates");
3282
+ var TEMPLATES_DIR_FALLBACK = resolve3(__dirname5, "../../../agent_moss/templates");
3103
3283
  var TEMPLATE_FILES = ["prompt1_template.txt", "prompt2_template.txt", "security_judge_template.txt"];
3104
3284
  var cachedTemplates = null;
3105
3285
  function loadTemplates() {
3106
3286
  if (cachedTemplates) return cachedTemplates;
3107
3287
  cachedTemplates = {};
3108
- const dir = existsSync4(TEMPLATES_DIR) ? TEMPLATES_DIR : TEMPLATES_DIR_FALLBACK;
3109
- if (!existsSync4(dir)) {
3288
+ const dir = existsSync5(TEMPLATES_DIR) ? TEMPLATES_DIR : TEMPLATES_DIR_FALLBACK;
3289
+ if (!existsSync5(dir)) {
3110
3290
  console.warn("[AgentMoss] Templates directory not found:", TEMPLATES_DIR, "and fallback:", TEMPLATES_DIR_FALLBACK);
3111
3291
  return cachedTemplates;
3112
3292
  }
3113
3293
  for (const file of TEMPLATE_FILES) {
3114
3294
  const filePath = resolve3(dir, file);
3115
- if (existsSync4(filePath)) {
3295
+ if (existsSync5(filePath)) {
3116
3296
  const key = file.replace(".txt", "").replace("_template", "");
3117
- cachedTemplates[key] = readFileSync3(filePath, "utf-8");
3297
+ cachedTemplates[key] = readFileSync4(filePath, "utf-8");
3118
3298
  }
3119
3299
  }
3120
3300
  console.log(`[AgentMoss] Loaded ${Object.keys(cachedTemplates).length} templates from ${dir}`);
@@ -3123,8 +3303,8 @@ function loadTemplates() {
3123
3303
 
3124
3304
  // src/engine/script-analyzer.ts
3125
3305
  init_esm_shims();
3126
- import { readFileSync as readFileSync4 } from "fs";
3127
- import { resolve as resolve4, join } from "path";
3306
+ import { readFileSync as readFileSync5 } from "fs";
3307
+ import { resolve as resolve4, join as join2 } from "path";
3128
3308
  var SUSPICIOUS_PATTERNS = [
3129
3309
  // Network exfiltration (curl/wget POST in scripts is almost never legitimate)
3130
3310
  { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
@@ -3257,8 +3437,8 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
3257
3437
  }
3258
3438
  function readScriptContent(scriptPath, maxLines = 500) {
3259
3439
  try {
3260
- const expandedPath = scriptPath.startsWith("~") ? join(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve4(scriptPath);
3261
- const raw = readFileSync4(expandedPath, "utf-8");
3440
+ const expandedPath = scriptPath.startsWith("~") ? join2(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve4(scriptPath);
3441
+ const raw = readFileSync5(expandedPath, "utf-8");
3262
3442
  const lines = raw.split("\n").slice(0, maxLines);
3263
3443
  return { content: lines.join("\n"), lineCount: lines.length, error: "" };
3264
3444
  } catch (e) {
@@ -3312,9 +3492,9 @@ ${result.scriptContent}
3312
3492
  init_esm_shims();
3313
3493
  init_heuristic();
3314
3494
  init_logic_rules();
3315
- import { readFileSync as readFileSync5, statSync, existsSync as existsSync5 } from "fs";
3316
- import { resolve as resolve5, join as join2, isAbsolute, dirname as dirname4 } from "path";
3317
- import { homedir } from "os";
3495
+ import { readFileSync as readFileSync6, statSync, existsSync as existsSync6 } from "fs";
3496
+ import { resolve as resolve5, join as join3, isAbsolute, dirname as dirname5 } from "path";
3497
+ import { homedir as homedir2 } from "os";
3318
3498
  function emptyChain() {
3319
3499
  return {
3320
3500
  nodes: [],
@@ -3542,7 +3722,7 @@ function extractInlineScriptPath(actionDetail) {
3542
3722
  let resolved;
3543
3723
  try {
3544
3724
  resolved = resolvePath(p);
3545
- if (existsSync5(resolved)) {
3725
+ if (existsSync6(resolved)) {
3546
3726
  const stat = statSync(resolved);
3547
3727
  if (stat.isFile()) return resolved;
3548
3728
  }
@@ -3554,20 +3734,20 @@ function extractInlineScriptPath(actionDetail) {
3554
3734
  function resolvePath(p) {
3555
3735
  let expanded = p;
3556
3736
  if (expanded.startsWith("~/") || expanded === "~") {
3557
- expanded = join2(homedir(), expanded.slice(1));
3737
+ expanded = join3(homedir2(), expanded.slice(1));
3558
3738
  }
3559
3739
  return resolve5(expanded);
3560
3740
  }
3561
3741
  function readScriptContent2(scriptPath, maxFileSize = 2 * 1024 * 1024) {
3562
3742
  try {
3563
3743
  const path3 = resolvePath(scriptPath);
3564
- if (!existsSync5(path3)) return { content: "", lineCount: 0, fileSize: 0, error: `\u811A\u672C\u4E0D\u5B58\u5728: ${scriptPath}` };
3744
+ if (!existsSync6(path3)) return { content: "", lineCount: 0, fileSize: 0, error: `\u811A\u672C\u4E0D\u5B58\u5728: ${scriptPath}` };
3565
3745
  const stat = statSync(path3);
3566
3746
  if (!stat.isFile()) return { content: "", lineCount: 0, fileSize: 0, error: `\u4E0D\u662F\u6587\u4EF6: ${scriptPath}` };
3567
3747
  if (stat.size > maxFileSize) {
3568
3748
  return { content: "", lineCount: 0, fileSize: stat.size, error: `\u811A\u672C\u6587\u4EF6\u8FC7\u5927 (${stat.size} bytes)\uFF0C\u8DF3\u8FC7\u8BFB\u53D6` };
3569
3749
  }
3570
- const content = readFileSync5(path3, "utf-8");
3750
+ const content = readFileSync6(path3, "utf-8");
3571
3751
  const lineCount = content ? content.split("\n").length : 0;
3572
3752
  return { content, lineCount, fileSize: stat.size, error: "" };
3573
3753
  } catch (e) {
@@ -3584,9 +3764,9 @@ function extractChildScriptPaths(content, baseDir) {
3584
3764
  const pathStr = m[1];
3585
3765
  if (pathStr.startsWith("-") || pathStr.startsWith("$")) continue;
3586
3766
  if (pathStr.includes("/dev/") || pathStr.startsWith("|") || pathStr.startsWith(";")) continue;
3587
- const full = isAbsolute(pathStr) ? pathStr : join2(baseDir, pathStr);
3767
+ const full = isAbsolute(pathStr) ? pathStr : join3(baseDir, pathStr);
3588
3768
  const resolved = resolvePath(full);
3589
- if (existsSync5(resolved)) {
3769
+ if (existsSync6(resolved)) {
3590
3770
  try {
3591
3771
  if (statSync(resolved).isFile() && !paths.includes(resolved)) paths.push(resolved);
3592
3772
  } catch {
@@ -3601,7 +3781,7 @@ function resolveScriptChain(entryPath, input, maxDepth = 3, maxFileSize = 2 * 10
3601
3781
  const result = emptyChain();
3602
3782
  result.entryPath = entryPath;
3603
3783
  const entry = resolvePath(entryPath);
3604
- const baseDir = existsSync5(entry) ? dirname4(entry) : input.cwd || process.cwd();
3784
+ const baseDir = existsSync6(entry) ? dirname5(entry) : input.cwd || process.cwd();
3605
3785
  resolveRecursive({
3606
3786
  path: entry,
3607
3787
  depth: 0,
@@ -3676,7 +3856,7 @@ function resolveRecursive(ctx) {
3676
3856
  maxTotalSize,
3677
3857
  visited,
3678
3858
  result,
3679
- baseDir: dirname4(resolvePath(childPath)),
3859
+ baseDir: dirname5(resolvePath(childPath)),
3680
3860
  input
3681
3861
  });
3682
3862
  if (result.hasCritical) return;
@@ -3938,7 +4118,7 @@ function formatHints(previous) {
3938
4118
  const hints = [];
3939
4119
  if (previous.heuristic) {
3940
4120
  hints.push(
3941
- `\u542F\u53D1\u5F0F\u68C0\u6D4B\u53D1\u73B0\u98CE\u9669 [\u98CE\u9669\u7B49\u7EA7: ${previous.heuristic.riskLevel}]:
4121
+ `\u7279\u5F81\u5339\u914D\u68C0\u6D4B\u53D1\u73B0\u98CE\u9669 [\u98CE\u9669\u7B49\u7EA7: ${previous.heuristic.riskLevel}]:
3942
4122
  \u7C7B\u578B: ${previous.heuristic.riskType}
3943
4123
  \u539F\u56E0: ${previous.heuristic.reason}`
3944
4124
  );
@@ -3950,7 +4130,7 @@ function formatHints(previous) {
3950
4130
  \u539F\u56E0: ${previous.logic.reason}`
3951
4131
  );
3952
4132
  }
3953
- 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";
4133
+ return hints.length > 0 ? hints.join("\n\n") : "\u7279\u5F81\u5339\u914D\u68C0\u6D4B\u548C\u903B\u8F91\u89C4\u5219\u68C0\u6D4B\u5747\u672A\u53D1\u73B0\u98CE\u9669\u3002";
3954
4134
  }
3955
4135
  async function analyzeWithLLM(prompt, config) {
3956
4136
  console.log(`[AgentMoss] LLM analyzing with model: ${config.model}, url: ${config.baseUrl}`);
@@ -4158,6 +4338,7 @@ async function runAnalysis(input) {
4158
4338
  const actionType = input.aNext.actionType.toLowerCase();
4159
4339
  const rawDetail = extractRawCommand(input.aNext.actionDetail);
4160
4340
  const lowerDetail = rawDetail.toLowerCase();
4341
+ const agentExt = getAgentExtensions(input.agentId);
4161
4342
  if (FULLY_SAFE_TOOLS.has(actionType)) {
4162
4343
  return {
4163
4344
  allowed: true,
@@ -4169,7 +4350,23 @@ async function runAnalysis(input) {
4169
4350
  violatedLayers: []
4170
4351
  };
4171
4352
  }
4172
- const agentExt = getAgentExtensions(input.agentId);
4353
+ if (agentExt?.builtin_safe_action_types && agentExt.builtin_safe_action_types.length > 0) {
4354
+ const hit = agentExt.builtin_safe_action_types.some((t) => {
4355
+ const tl = t.toLowerCase();
4356
+ return tl.endsWith("-") ? actionType.startsWith(tl) : actionType === tl;
4357
+ });
4358
+ if (hit) {
4359
+ return {
4360
+ allowed: true,
4361
+ reason: `agent \u5185\u90E8\u5B89\u5168\u64CD\u4F5C\uFF0C\u8DF3\u8FC7\u6DF1\u5EA6\u5206\u6790: ${actionType}`,
4362
+ riskLevel: heuristicResult?.riskLevel || "low",
4363
+ riskType: "",
4364
+ confidence: 95,
4365
+ source: "whitelist_bypass",
4366
+ violatedLayers: []
4367
+ };
4368
+ }
4369
+ }
4173
4370
  const trustedSkills = new Set(TRUSTED_SKILLS_BASE);
4174
4371
  if (agentExt?.builtin_safe_skills) {
4175
4372
  for (const s of agentExt.builtin_safe_skills) trustedSkills.add(s);
@@ -4211,9 +4408,11 @@ async function runAnalysis(input) {
4211
4408
  }
4212
4409
  }
4213
4410
  const logicResult = isLogicRulesEnabled() ? runLogicCheck(input) : null;
4214
- if (logicResult && !logicResult.allowed) {
4215
- const isCredentialPath = !!logicResult.isCredential;
4216
- const skipInlineFileAccess = (isInlineScriptCmd || isLiteralTextCmd) && logicResult.riskType === "sensitive_path_access";
4411
+ const l1FileAccessHit = !!(heuristicResult && !heuristicResult.allowed && heuristicResult.riskType === "file_access");
4412
+ const effectiveResult = logicResult && !logicResult.allowed ? logicResult : l1FileAccessHit ? heuristicResult : null;
4413
+ if (effectiveResult) {
4414
+ const isCredentialPath = !!effectiveResult.isCredential;
4415
+ const skipInlineFileAccess = (isInlineScriptCmd || isLiteralTextCmd) && effectiveResult.riskType === "file_access";
4217
4416
  if (skipInlineFileAccess) {
4218
4417
  const inlineJudgment = analyzeInlineScript(input.aNext.actionDetail);
4219
4418
  if (inlineJudgment.actionable) {
@@ -4239,8 +4438,8 @@ async function runAnalysis(input) {
4239
4438
  };
4240
4439
  }
4241
4440
  }
4242
- if ((logicResult.riskLevel === "high" || logicResult.riskLevel === "critical") && (!skipInlineFileAccess || isCredentialPath)) {
4243
- return logicResult;
4441
+ if ((effectiveResult.riskLevel === "high" || effectiveResult.riskLevel === "critical") && (!skipInlineFileAccess || isCredentialPath)) {
4442
+ return effectiveResult;
4244
4443
  }
4245
4444
  }
4246
4445
  if (scriptChain && scriptChain.nodes.length > 0 && isLogicRulesEnabled()) {
@@ -4457,7 +4656,10 @@ async function analyzeRoute(c) {
4457
4656
  reason: req.reason,
4458
4657
  osType,
4459
4658
  cwd: req.cwd,
4460
- agentId: req.agent_id,
4659
+ // agent_id 透传:优先顶层;为空兜底 metadata.agent_type(AgentGate V1
4660
+ // normalizer 历史上只把 agent_type 塞进 metadata,顶层 agent_id 为空 →
4661
+ // 定制规则不生效。AgentGate 已修,此处兜底防御其他只传 metadata 的调用方)。
4662
+ agentId: req.agent_id || req.metadata?.agent_type || "",
4461
4663
  llmConfig: req.metadata?.llm_config,
4462
4664
  llmLogPath: req.metadata?.llm_log_path
4463
4665
  });
@@ -4513,10 +4715,10 @@ init_runtime_config();
4513
4715
  init_token_stats();
4514
4716
  import { Hono } from "hono";
4515
4717
  import { readFileSync as readFileSync8, existsSync as existsSync9 } from "fs";
4516
- import { join as join5, dirname as dirname5 } from "path";
4517
- import { fileURLToPath as fileURLToPath5 } from "url";
4518
- var __dirname5 = dirname5(fileURLToPath5(import.meta.url));
4519
- var STATIC_DIR = join5(__dirname5, "static");
4718
+ import { join as join5, dirname as dirname6 } from "path";
4719
+ import { fileURLToPath as fileURLToPath6 } from "url";
4720
+ var __dirname6 = dirname6(fileURLToPath6(import.meta.url));
4721
+ var STATIC_DIR = join5(__dirname6, "static");
4520
4722
  var CONSOLE_TOKEN_ENV = "AGENT_MOSS_CONSOLE_TOKEN";
4521
4723
  function getConsoleToken() {
4522
4724
  return process.env[CONSOLE_TOKEN_ENV] || "";
@@ -4633,14 +4835,52 @@ function createConsoleRouter() {
4633
4835
  if (!/^[A-Za-z0-9_-]+$/.test(skillId)) {
4634
4836
  return c.json({ error: "invalid skill_id" }, 400);
4635
4837
  }
4636
- const skillPath = join5(dirname5(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
4838
+ const skillPath = join5(dirname6(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
4637
4839
  if (!existsSync9(skillPath)) {
4638
- const builtinPath = join5(__dirname5, "..", "..", "agent_moss", "skills", `${skillId}.md`);
4840
+ const builtinPath = join5(__dirname6, "..", "..", "agent_moss", "skills", `${skillId}.md`);
4639
4841
  if (existsSync9(builtinPath)) return c.text(readFileSync8(builtinPath, "utf-8"));
4640
4842
  return c.json({ error: "skill not found" }, 404);
4641
4843
  }
4642
4844
  return c.text(readFileSync8(skillPath, "utf-8"));
4643
4845
  });
4846
+ console2.get("/api/agents", (c) => {
4847
+ const rc = loadRuntimeConfig();
4848
+ return c.json({ agents: getAgentRules(rc) });
4849
+ });
4850
+ console2.put("/api/agents/rules/enabled", async (c) => {
4851
+ const b = await c.req.json();
4852
+ const fp = {};
4853
+ if (b.value) fp.value = b.value;
4854
+ if (b.pattern) fp.pattern = b.pattern;
4855
+ if (b.keyword) fp.keyword = b.keyword;
4856
+ if (b.path) fp.path = b.path;
4857
+ const rc = updateAgentRuleEnabled(b.agent_id, b.category, fp, b.enabled);
4858
+ return c.json({ agents: getAgentRules(rc) });
4859
+ });
4860
+ console2.post("/api/agents/rules", async (c) => {
4861
+ const b = await c.req.json();
4862
+ const rule = {};
4863
+ for (const [k, v] of Object.entries(b)) {
4864
+ if (!["agent_id", "category"].includes(k) && v !== void 0 && v !== null) rule[k] = v;
4865
+ }
4866
+ const rc = addAgentRule(b.agent_id, b.category, rule);
4867
+ return c.json({ agents: getAgentRules(rc) });
4868
+ });
4869
+ console2.delete("/api/agents/rules", async (c) => {
4870
+ const b = await c.req.json();
4871
+ const fp = {};
4872
+ if (b.value) fp.value = b.value;
4873
+ if (b.pattern) fp.pattern = b.pattern;
4874
+ if (b.keyword) fp.keyword = b.keyword;
4875
+ if (b.path) fp.path = b.path;
4876
+ const rc = deleteAgentRule(b.agent_id, b.category, fp);
4877
+ return c.json({ agents: getAgentRules(rc) });
4878
+ });
4879
+ console2.put("/api/agents/categories/enabled", async (c) => {
4880
+ const b = await c.req.json();
4881
+ const rc = updateAgentCategoryEnabled(b.agent_id, b.category, b.enabled);
4882
+ return c.json({ agents: getAgentRules(rc) });
4883
+ });
4644
4884
  console2.get("/api/config", (c) => {
4645
4885
  const rc = loadRuntimeConfig();
4646
4886
  return c.json({ config: rc, config_path: getRuntimeConfigPath() });