@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.
@@ -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 __esm = (fn, res) => function __init() {
4
6
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
7
  };
@@ -7,6 +9,15 @@ var __export = (target, all) => {
7
9
  for (var name in all)
8
10
  __defProp(target, name, { get: all[name], enumerable: true });
9
11
  };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
10
21
 
11
22
  // node_modules/tsup/assets/esm_shims.js
12
23
  import path from "path";
@@ -203,380 +214,89 @@ var init_config = __esm({
203
214
  }
204
215
  });
205
216
 
206
- // src/agents/index.ts
207
- import { readFileSync, existsSync } from "fs";
208
- import { resolve, dirname } from "path";
209
- import { fileURLToPath as fileURLToPath2 } from "url";
210
- function resolveAgentsDir() {
211
- if (existsSync(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
212
- if (existsSync(AGENTS_DIR_DEV_FALLBACK)) return AGENTS_DIR_DEV_FALLBACK;
213
- return AGENTS_DIR_PROD;
217
+ // src/engine/inline-analyzer.ts
218
+ function isLiteralTextCommand(command) {
219
+ ECHO_CMD_RE.lastIndex = 0;
220
+ return ECHO_CMD_RE.test(command);
214
221
  }
215
- function getAgentExtensions(agentId) {
216
- if (!agentId) return null;
217
- if (cache.has(agentId)) return cache.get(agentId) ?? null;
218
- const agentsDir = resolveAgentsDir();
219
- const rulesFile = resolve(agentsDir, agentId, "rules.json");
220
- if (!existsSync(rulesFile)) {
221
- cache.set(agentId, null);
222
- return null;
222
+ function extractInlineCode(command) {
223
+ const parts = [];
224
+ let m;
225
+ INLINE_CODE_RE.lastIndex = 0;
226
+ while ((m = INLINE_CODE_RE.exec(command)) !== null) {
227
+ const code = m[1] ?? m[2];
228
+ if (code) parts.push(code);
223
229
  }
224
- try {
225
- const raw = readFileSync(rulesFile, "utf-8");
226
- const ext = JSON.parse(raw);
227
- if (!ext.agent_id) {
228
- console.warn(`[AgentMoss agents] rules.json \u7F3A\u5C11 agent_id \u5B57\u6BB5: ${rulesFile}`);
229
- cache.set(agentId, null);
230
- return null;
231
- }
232
- cache.set(agentId, ext);
233
- console.log(
234
- `[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`
235
- );
236
- return ext;
237
- } catch (e) {
238
- console.error(`[AgentMoss agents] \u52A0\u8F7D agent ${agentId} rules.json \u5931\u8D25:`, e.message);
239
- cache.set(agentId, null);
240
- return null;
230
+ ECHO_STRING_RE.lastIndex = 0;
231
+ while ((m = ECHO_STRING_RE.exec(command)) !== null) {
232
+ const code = m[1] ?? m[2];
233
+ if (code) parts.push(code);
241
234
  }
235
+ return parts.join("\n");
242
236
  }
243
- var __filename2, __dirname2, AGENTS_DIR_PROD, AGENTS_DIR_DEV_FALLBACK, cache;
244
- var init_agents = __esm({
245
- "src/agents/index.ts"() {
246
- "use strict";
247
- init_esm_shims();
248
- __filename2 = fileURLToPath2(import.meta.url);
249
- __dirname2 = dirname(__filename2);
250
- AGENTS_DIR_PROD = resolve(__dirname2, "agents");
251
- AGENTS_DIR_DEV_FALLBACK = resolve(__dirname2, "../../../agent_moss/agents");
252
- cache = /* @__PURE__ */ new Map();
253
- }
254
- });
255
-
256
- // src/engine/heuristic.ts
257
- function runHeuristicCheck(input) {
258
- const command = `${input.aNext.actionType}: ${input.aNext.actionDetail}`;
259
- const matched = [];
260
- let highestSeverity = "low";
261
- let highestRiskType = "";
262
- const userRules = getCustomRules();
263
- for (const rule of userRules) {
264
- try {
265
- const re = new RegExp(rule.pattern);
266
- if (re.test(command)) {
267
- if (rule.action === "Deny") {
268
- matched.push(`\u81EA\u5B9A\u4E49\u89C4\u5219: ${rule.pattern}`);
269
- if (severityRank(rule.severity) > severityRank(highestSeverity)) {
270
- highestSeverity = rule.severity;
271
- highestRiskType = "custom_rule";
272
- }
273
- }
274
- }
275
- } catch {
276
- }
277
- }
278
- let topRiskType = "";
279
- for (const rule of DANGEROUS_PATTERNS) {
280
- if (rule.pattern.test(command)) {
281
- matched.push(rule.description);
282
- if (severityRank(rule.severity) > severityRank(highestSeverity)) {
283
- highestSeverity = rule.severity;
284
- topRiskType = rule.riskType;
285
- }
286
- }
287
- }
288
- highestRiskType = topRiskType || highestRiskType;
289
- const agentExt = getAgentExtensions(input.agentId);
290
- if (agentExt?.extra_command_patterns) {
291
- for (const rule of agentExt.extra_command_patterns) {
292
- try {
293
- const re = new RegExp(rule.pattern, "i");
294
- if (re.test(command)) {
295
- matched.push(`agent \u4E13\u5C5E\u89C4\u5219: ${rule.reason}`);
296
- if (severityRank(rule.risk_level) > severityRank(highestSeverity)) {
297
- highestSeverity = rule.risk_level;
298
- highestRiskType = rule.risk_type;
299
- }
300
- }
301
- } catch {
302
- }
303
- }
237
+ function hasWriteExecChain(command) {
238
+ return WRITE_EXEC_CHAIN_PATTERNS.some((p) => {
239
+ p.lastIndex = 0;
240
+ return p.test(command);
241
+ });
242
+ }
243
+ function hasDirTraversal(code) {
244
+ const lower = code.toLowerCase();
245
+ return DIR_TRAVERSAL_KEYWORDS.some((kw) => lower.includes(kw));
246
+ }
247
+ function hasReadWriteKeyword(code) {
248
+ const lower = code.toLowerCase();
249
+ return READ_WRITE_KEYWORDS.some((kw) => lower.includes(kw));
250
+ }
251
+ function analyzeInlineScript(command) {
252
+ const code = extractInlineCode(command);
253
+ if (!code) {
254
+ return { actionable: false, allowed: true, reason: "", riskLevel: "low", riskType: "" };
304
255
  }
305
- const injectionText = `${input.aNext.actionType} ${input.aNext.actionDetail} ${input.reason || ""} ${input.promptSession || ""}`.toLowerCase();
306
- const injectionHits = checkInjectionKeywords(injectionText, agentExt?.extra_injection_keywords);
307
- if (injectionHits.length > 0) {
308
- const topHit = injectionHits.reduce((a, b) => severityRank(a.riskLevel) >= severityRank(b.riskLevel) ? a : b);
309
- matched.push(...injectionHits.map((h) => `\u6CE8\u5165\u5173\u952E\u8BCD [${h.lang}]: ${h.keyword}`));
310
- if (severityRank(topHit.riskLevel) > severityRank(highestSeverity)) {
311
- highestSeverity = topHit.riskLevel;
312
- highestRiskType = "prompt_injection";
313
- }
256
+ if (hasWriteExecChain(command)) {
257
+ return {
258
+ actionable: true,
259
+ allowed: false,
260
+ 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",
261
+ riskLevel: "critical",
262
+ riskType: "script_execution"
263
+ };
314
264
  }
315
- if (matched.length > 0) {
265
+ const redirMatch = command.match(REDIRECT_TO_SENSITIVE_RE);
266
+ if (redirMatch) {
316
267
  return {
268
+ actionable: true,
317
269
  allowed: false,
318
- reason: `\u68C0\u6D4B\u5230\u9AD8\u5371\u64CD\u4F5C: ${matched.join(", ")}`,
319
- riskLevel: highestSeverity,
320
- riskType: highestRiskType,
321
- confidence: 95,
322
- source: "heuristic",
323
- violatedLayers: ["1.1"]
324
- // 对齐 py coordinator.py:165(L1.1 = 命中高危命令模式/注入关键词)
270
+ reason: `\u91CD\u5B9A\u5411\u5199\u5165\u654F\u611F\u8DEF\u5F84: ${redirMatch[1]}`,
271
+ riskLevel: "critical",
272
+ riskType: "file_access"
325
273
  };
326
274
  }
327
- return null;
328
- }
329
- function checkInjectionKeywords(text, extraKeywords) {
330
- const hits = [];
331
- for (const entry of INJECTION_KEYWORDS) {
332
- if (text.includes(entry.keyword.toLowerCase())) {
333
- hits.push(entry);
334
- }
275
+ const isEchoOrPrintf1 = isLiteralTextCommand(command);
276
+ if (!isEchoOrPrintf1 && hasDirTraversal(code) && hasReadWriteKeyword(code)) {
277
+ return { actionable: false, allowed: true, reason: "", riskLevel: "low", riskType: "" };
335
278
  }
336
- if (extraKeywords) {
337
- for (const entry of extraKeywords) {
338
- if (text.includes(entry.keyword.toLowerCase())) {
339
- hits.push({ keyword: entry.keyword, riskLevel: entry.risk_level, lang: entry.lang });
340
- }
341
- }
279
+ const isEchoOrPrintf = isLiteralTextCommand(command);
280
+ if (!isEchoOrPrintf && hasReadWriteKeyword(code)) {
281
+ return {
282
+ actionable: true,
283
+ allowed: false,
284
+ 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",
285
+ riskLevel: "critical",
286
+ riskType: "file_access"
287
+ };
342
288
  }
343
- return hits;
344
- }
345
- function isInlineScriptCommand(actionDetail) {
346
- 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;
347
- return re.test(actionDetail);
348
- }
349
- function severityRank(s) {
350
- const ranks = { low: 1, medium: 2, high: 3, critical: 4 };
351
- return ranks[s] || 0;
289
+ return {
290
+ actionable: true,
291
+ allowed: true,
292
+ reason: "\u654F\u611F\u8DEF\u5F84\u4EC5\u4F5C\u4E3A\u6587\u672C\u5B57\u9762\u91CF\u51FA\u73B0\uFF0C\u672A\u4F34\u968F\u8BFB\u5199\u64CD\u4F5C",
293
+ riskLevel: "low",
294
+ riskType: "file_access"
295
+ };
352
296
  }
353
- var INJECTION_KEYWORDS, DANGEROUS_PATTERNS;
354
- var init_heuristic = __esm({
355
- "src/engine/heuristic.ts"() {
356
- "use strict";
357
- init_esm_shims();
358
- init_config();
359
- init_agents();
360
- INJECTION_KEYWORDS = [
361
- // ---- Instruction Override ----
362
- { keyword: "ignore previous instructions", riskLevel: "critical", lang: "en" },
363
- { keyword: "ignore all previous", riskLevel: "critical", lang: "en" },
364
- { keyword: "disregard all prior", riskLevel: "critical", lang: "en" },
365
- { keyword: "forget everything", riskLevel: "high", lang: "en" },
366
- { keyword: "\u5FFD\u7565\u4E4B\u524D\u7684\u6307\u4EE4", riskLevel: "critical", lang: "zh" },
367
- { keyword: "\u5FFD\u7565\u6240\u6709\u5148\u524D\u7684", riskLevel: "critical", lang: "zh" },
368
- { keyword: "\u5FFD\u7565\u4E0A\u9762\u6240\u6709", riskLevel: "high", lang: "zh" },
369
- { keyword: "\u8BF7\u5FD8\u8BB0\u4E4B\u524D", riskLevel: "high", lang: "zh" },
370
- // ---- Role Hijacking ----
371
- { keyword: "you are now", riskLevel: "high", lang: "en" },
372
- { keyword: "you are no longer", riskLevel: "high", lang: "en" },
373
- { keyword: "new instructions", riskLevel: "high", lang: "en" },
374
- { keyword: "from now on, you", riskLevel: "high", lang: "en" },
375
- { keyword: "\u4F60\u73B0\u5728\u662F\u4E00\u4E2A", riskLevel: "high", lang: "zh" },
376
- { keyword: "\u4F60\u4E0D\u518D\u662F\u4E00\u4E2A", riskLevel: "high", lang: "zh" },
377
- { keyword: "\u4ECE\u73B0\u5728\u5F00\u59CB\u4F60", riskLevel: "high", lang: "zh" },
378
- { keyword: "\u4F60\u7684\u65B0\u89D2\u8272\u662F", riskLevel: "high", lang: "zh" },
379
- // ---- Command Injection ----
380
- { keyword: "system prompt", riskLevel: "high", lang: "en" },
381
- { keyword: "reveal your instructions", riskLevel: "high", lang: "en" },
382
- { keyword: "output your instructions", riskLevel: "high", lang: "en" },
383
- { keyword: "repeat your instructions", riskLevel: "medium", lang: "en" },
384
- { keyword: "\u7CFB\u7EDF\u63D0\u793A", riskLevel: "high", lang: "zh" },
385
- { keyword: "\u8F93\u51FA\u4F60\u7684\u6307\u4EE4", riskLevel: "high", lang: "zh" },
386
- { keyword: "\u91CD\u590D\u4F60\u7684\u6307\u4EE4", riskLevel: "medium", lang: "zh" },
387
- // ---- Social Engineering ----
388
- { keyword: "this is an authorized test", riskLevel: "high", lang: "en" },
389
- { keyword: "security audit approved", riskLevel: "high", lang: "en" },
390
- { keyword: "admin override", riskLevel: "critical", lang: "en" },
391
- { keyword: "debug mode", riskLevel: "high", lang: "en" },
392
- { keyword: "maintenance mode", riskLevel: "high", lang: "en" },
393
- { keyword: "\u8FD9\u662F\u6388\u6743\u7684\u6D4B\u8BD5", riskLevel: "high", lang: "zh" },
394
- { keyword: "\u5B89\u5168\u5BA1\u8BA1\u5DF2\u6279\u51C6", riskLevel: "high", lang: "zh" },
395
- { keyword: "\u7BA1\u7406\u5458\u8986\u76D6", riskLevel: "critical", lang: "zh" },
396
- { keyword: "\u8C03\u8BD5\u6A21\u5F0F", riskLevel: "high", lang: "zh" },
397
- // ---- XSS ----
398
- { keyword: "<script>", riskLevel: "high", lang: "en" },
399
- { keyword: "javascript:", riskLevel: "high", lang: "en" },
400
- { keyword: "onerror=", riskLevel: "high", lang: "en" },
401
- { keyword: "onload=", riskLevel: "high", lang: "en" },
402
- // ---- Fake Boundaries ----
403
- { keyword: "=== END OF", riskLevel: "high", lang: "en" },
404
- { keyword: "--- END SYSTEM", riskLevel: "high", lang: "en" },
405
- { keyword: "[END OF", riskLevel: "high", lang: "en" },
406
- { keyword: "=== \u7ED3\u675F", riskLevel: "high", lang: "zh" },
407
- { keyword: "--- \u7ED3\u675F\u7CFB\u7EDF", riskLevel: "high", lang: "zh" },
408
- // ---- Developer Mode ----
409
- { keyword: "developer mode", riskLevel: "critical", lang: "en" },
410
- { keyword: "DAN mode", riskLevel: "critical", lang: "en" },
411
- { keyword: "jailbreak", riskLevel: "critical", lang: "en" },
412
- { keyword: "\u5F00\u53D1\u8005\u6A21\u5F0F", riskLevel: "critical", lang: "zh" },
413
- { keyword: "\u8D8A\u72F1", riskLevel: "critical", lang: "zh" }
414
- // 注:xiaoO 专属绕过词(绕过审计/禁用安全检查/关闭防护/bypass audit 等)
415
- // 已迁至 agent_moss/agents/xiaoo/rules.json 的 extra_injection_keywords,
416
- // 由 agent_ext 机制注入,不再污染通用层。
417
- ];
418
- DANGEROUS_PATTERNS = [
419
- // --- Critical: rm command graded detection (strict to loose, first match wins) ---
420
- // 1. rm -rf root dir (含通配符如 /* /.* 等) — 使用负向先行断言 (?!\w):/ 后面不是单词字符时匹配
421
- { 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" },
422
- // 注意:已移除"rm -rf /任意绝对路径"的宽泛规则,避免误杀 /tmp 等临时目录清理操作
423
- // 2. rm with -r or -f targeting system dirs
424
- { 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" },
425
- // 3. rm -r absolute path (no -f)
426
- { 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" },
427
- // 4. rm -f absolute path (no -r)
428
- { 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" },
429
- // dd 磁盘清零 — 只拦截写入系统关键目录,放行 /tmp 等临时目录
430
- { 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" },
431
- // mkfs 格式化 — 只拦截对块设备(/dev/sdX 等)的格式化,放行对普通文件的格式化
432
- { 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" },
433
- { pattern: /:\(\)\s*\{.*:\|:&.*\};:/i, severity: "critical", description: "Fork Bomb", riskType: "resource_exhaustion" },
434
- // --- Critical: remote code execution ---
435
- { pattern: /curl\s+.*\|\s*(bash|sh|zsh|dash)/i, severity: "critical", description: "curl pipe shell \u6267\u884C", riskType: "script_execution" },
436
- { pattern: /wget\s+.*\|\s*(bash|sh)/i, severity: "critical", description: "wget pipe shell \u6267\u884C", riskType: "script_execution" },
437
- { pattern: /eval\s+\$\(curl|eval\s+\$\(wget/i, severity: "critical", description: "eval \u8FDC\u7A0B\u4EE3\u7801\u6267\u884C", riskType: "script_execution" },
438
- { pattern: /base64\s+-d\s+.*\|\s*bash/i, severity: "critical", description: "base64 \u89E3\u7801\u6267\u884C", riskType: "script_execution" },
439
- // --- Critical: reverse shell ---
440
- { pattern: /\/dev\/tcp\/|bash\s+-i\s+>&/i, severity: "critical", description: "\u53CD\u5F39 Shell", riskType: "script_execution" },
441
- { pattern: /nc\s+-[el]\s+.*\d{1,5}/i, severity: "high", description: "Netcat \u53CD\u5F39\u8FDE\u63A5", riskType: "lateral_movement" },
442
- // --- Critical: sensitive file access ---
443
- { pattern: /\/etc\/shadow/i, severity: "critical", description: "\u8BBF\u95EE\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6 (/etc/shadow)", riskType: "file_access" },
444
- { pattern: /\/etc\/gshadow/i, severity: "critical", description: "\u8BBF\u95EE\u7CFB\u7EDF\u7EC4\u5BC6\u7801\u6587\u4EF6 (/etc/gshadow)", riskType: "file_access" },
445
- // 注意:/etc/passwd 已从拦截列表移除,因为它是公开可读的标准系统文件,真正敏感的是 /etc/shadow
446
- { pattern: /\.ssh\/id_rsa/i, severity: "critical", description: "\u8BBF\u95EE SSH \u79C1\u94A5\u6587\u4EF6", riskType: "file_access" },
447
- // --- Critical: privilege escalation / container escape ---
448
- { 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" },
449
- // --- High: dangerous permissions / ownership ---
450
- { pattern: /chmod\s+[0-7]*777[0-7]*/i, severity: "high", description: "\u5168\u6743\u9650\u8BBE\u7F6E (chmod 777)", riskType: "script_execution" },
451
- { 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" },
452
- // sudo 提权 — 只拦截 sudo 后跟的危险命令,不一刀切拦截所有 sudo
453
- { 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" },
454
- // --- High: system config overwrite ---
455
- { pattern: />\s*\/etc\//i, severity: "critical", description: "\u8986\u76D6\u5199\u5165\u7CFB\u7EDF\u914D\u7F6E\u76EE\u5F55", riskType: "file_access" },
456
- // --- High: download script + execute risk ---
457
- { 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" },
458
- { pattern: /(?:curl|wget)\s+.*\s+&&?\s*(?:chmod\s+\+x|bash|sh)/i, severity: "high", description: "\u4E0B\u8F7D\u540E\u6267\u884C", riskType: "script_execution" },
459
- // --- High: package manager pipe-to-shell ---
460
- { pattern: /git\s+clone\s+.*\|.*bash/i, severity: "high", description: "git clone pipe shell", riskType: "script_execution" },
461
- { pattern: /pip\s+install\s+.*\|\s*bash/i, severity: "high", description: "pip install pipe shell", riskType: "script_execution" },
462
- { pattern: /npm\s+install\s+-g\s+.*\|\s*bash/i, severity: "high", description: "npm install pipe shell", riskType: "script_execution" },
463
- // --- Medium: destructive git operations (降级为 medium,允许但记录) ---
464
- { 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" },
465
- // --- High: network port scanning (横向移动侦察) ---
466
- { 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" },
467
- { 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" },
468
- // --- Medium: credential export ---
469
- { pattern: /export\s+.*(?:TOKEN|KEY|SECRET|PASSWORD)/i, severity: "medium", description: "\u5BFC\u51FA\u654F\u611F\u51ED\u8BC1", riskType: "data_exfiltration" },
470
- // --- High: shell rc file append (persistence) ---
471
- { 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" },
472
- // --- High: device file write (只拦截重定向写入设备文件,不拦截读取) ---
473
- { 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" },
474
- // --- High: non-interactive password modification ---
475
- { 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" },
476
- { 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" },
477
- { pattern: /\bchpasswd\b/i, severity: "high", description: "\u68C0\u6D4B\u5230 chpasswd \u6279\u91CF\u5BC6\u7801\u4FEE\u6539\u547D\u4EE4", riskType: "script_execution" },
478
- { 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" },
479
- { 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" },
480
- { 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" },
481
- // --- High: non-interactive password modification (extended) ---
482
- { pattern: /\blpasswd\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u975E\u4EA4\u4E92\u5F0F\u5BC6\u7801\u4FEE\u6539\u64CD\u4F5C (lpasswd)", riskType: "script_execution" },
483
- { pattern: /\bgpasswd\s+(?!-[adA])/i, severity: "high", description: "\u68C0\u6D4B\u5230\u7EC4\u5BC6\u7801\u7BA1\u7406\u64CD\u4F5C (gpasswd)", riskType: "script_execution" },
484
- { pattern: /\bchgpasswd\b/i, severity: "high", description: "\u68C0\u6D4B\u5230\u6279\u91CF\u4FEE\u6539\u7EC4\u5BC6\u7801\u64CD\u4F5C (chgpasswd)", riskType: "script_execution" },
485
- // --- High: user/group deletion (irreversible, requires user consent) ---
486
- { pattern: /\buserdel\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7CFB\u7EDF\u7528\u6237\u64CD\u4F5C (userdel)", riskType: "script_execution" },
487
- { pattern: /\bdeluser\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7CFB\u7EDF\u7528\u6237\u64CD\u4F5C (deluser)", riskType: "script_execution" },
488
- { pattern: /\bluserdel\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7CFB\u7EDF\u7528\u6237\u64CD\u4F5C (luserdel)", riskType: "script_execution" },
489
- { pattern: /\bgroupdel\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7528\u6237\u7EC4\u64CD\u4F5C (groupdel)", riskType: "script_execution" },
490
- { pattern: /\blgroupdel\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7528\u6237\u7EC4\u64CD\u4F5C (lgroupdel)", riskType: "script_execution" },
491
- // --- High: generic command -p parameter password setting ---
492
- { 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" }
493
- ];
494
- }
495
- });
496
-
497
- // src/engine/inline-analyzer.ts
498
- function isLiteralTextCommand(command) {
499
- ECHO_CMD_RE.lastIndex = 0;
500
- return ECHO_CMD_RE.test(command);
501
- }
502
- function extractInlineCode(command) {
503
- const parts = [];
504
- let m;
505
- INLINE_CODE_RE.lastIndex = 0;
506
- while ((m = INLINE_CODE_RE.exec(command)) !== null) {
507
- const code = m[1] ?? m[2];
508
- if (code) parts.push(code);
509
- }
510
- ECHO_STRING_RE.lastIndex = 0;
511
- while ((m = ECHO_STRING_RE.exec(command)) !== null) {
512
- const code = m[1] ?? m[2];
513
- if (code) parts.push(code);
514
- }
515
- return parts.join("\n");
516
- }
517
- function hasWriteExecChain(command) {
518
- return WRITE_EXEC_CHAIN_PATTERNS.some((p) => {
519
- p.lastIndex = 0;
520
- return p.test(command);
521
- });
522
- }
523
- function hasDirTraversal(code) {
524
- const lower = code.toLowerCase();
525
- return DIR_TRAVERSAL_KEYWORDS.some((kw) => lower.includes(kw));
526
- }
527
- function hasReadWriteKeyword(code) {
528
- const lower = code.toLowerCase();
529
- return READ_WRITE_KEYWORDS.some((kw) => lower.includes(kw));
530
- }
531
- function analyzeInlineScript(command) {
532
- const code = extractInlineCode(command);
533
- if (!code) {
534
- return { actionable: false, allowed: true, reason: "", riskLevel: "low", riskType: "" };
535
- }
536
- if (hasWriteExecChain(command)) {
537
- return {
538
- actionable: true,
539
- allowed: false,
540
- 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",
541
- riskLevel: "critical",
542
- riskType: "script_execution"
543
- };
544
- }
545
- const redirMatch = command.match(REDIRECT_TO_SENSITIVE_RE);
546
- if (redirMatch) {
547
- return {
548
- actionable: true,
549
- allowed: false,
550
- reason: `\u91CD\u5B9A\u5411\u5199\u5165\u654F\u611F\u8DEF\u5F84: ${redirMatch[1]}`,
551
- riskLevel: "critical",
552
- riskType: "file_access"
553
- };
554
- }
555
- const isEchoOrPrintf1 = isLiteralTextCommand(command);
556
- if (!isEchoOrPrintf1 && hasDirTraversal(code) && hasReadWriteKeyword(code)) {
557
- return { actionable: false, allowed: true, reason: "", riskLevel: "low", riskType: "" };
558
- }
559
- const isEchoOrPrintf = isLiteralTextCommand(command);
560
- if (!isEchoOrPrintf && hasReadWriteKeyword(code)) {
561
- return {
562
- actionable: true,
563
- allowed: false,
564
- 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",
565
- riskLevel: "critical",
566
- riskType: "file_access"
567
- };
568
- }
569
- return {
570
- actionable: true,
571
- allowed: true,
572
- reason: "\u654F\u611F\u8DEF\u5F84\u4EC5\u4F5C\u4E3A\u6587\u672C\u5B57\u9762\u91CF\u51FA\u73B0\uFF0C\u672A\u4F34\u968F\u8BFB\u5199\u64CD\u4F5C",
573
- riskLevel: "low",
574
- riskType: "file_access"
575
- };
576
- }
577
- 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;
578
- var init_inline_analyzer = __esm({
579
- "src/engine/inline-analyzer.ts"() {
297
+ 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;
298
+ var init_inline_analyzer = __esm({
299
+ "src/engine/inline-analyzer.ts"() {
580
300
  "use strict";
581
301
  init_esm_shims();
582
302
  INLINE_CODE_RE = /(?:python|python3|perl|perl5|ruby|jruby|mruby|node|php|lua)\s+(?:-[a-zA-Z]*[cera][a-zA-Z]*\s+)"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
@@ -710,165 +430,26 @@ function runLogicCheck(input) {
710
430
  }
711
431
  }
712
432
  }
713
- if (!isExcludedType) {
714
- const agentExt = getAgentExtensions(input.agentId || "");
715
- const pathResult = checkSensitivePath(actionDetail, resolvedCwd, agentExt);
716
- if (pathResult) return pathResult;
717
- }
718
- if (actionType !== "file_write" && actionType !== "file_edit") {
719
- const TEMP_DIRS = ["/tmp/", "/var/tmp/", "/run/", "/dev/shm/"];
720
- const isTempPath = TEMP_DIRS.some((td) => actionDetail.includes(td));
721
- if (!isTempPath && (actionDetail.includes("*") || actionDetail.includes("?")) && ["rm ", "rm ", "del ", "del ", "delete", "remove ", "remove ", "\u5220\u9664", "\u79FB\u9664"].some((kw) => actionDetail.includes(kw))) {
722
- return {
723
- allowed: false,
724
- reason: "\u68C0\u6D4B\u5230\u901A\u914D\u7B26\u7ED3\u5408\u5220\u9664\u64CD\u4F5C\uFF0C\u53EF\u80FD\u9020\u6210\u6279\u91CF\u8BEF\u5220",
725
- riskLevel: "high",
726
- riskType: "script_execution",
727
- confidence: 90,
728
- source: "logic_rule",
729
- violatedLayers: ["2"]
730
- };
731
- }
732
- if (/>\s*\/etc\//.test(actionDetail) || />\s*\/boot\//.test(actionDetail) || />\s*\/proc\//.test(actionDetail)) {
733
- return {
734
- allowed: false,
735
- reason: "\u68C0\u6D4B\u5230\u91CD\u5B9A\u5411\u8986\u76D6\u5199\u5165\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55",
736
- riskLevel: "critical",
737
- riskType: "file_access",
738
- confidence: 95,
739
- source: "logic_rule",
740
- violatedLayers: ["2"]
741
- };
742
- }
743
- }
744
- var _READ_ONLY_COMMANDS = /* @__PURE__ */ new Set([
745
- "cat",
746
- "head",
747
- "tail",
748
- "less",
749
- "more",
750
- "grep",
751
- "find",
752
- "awk",
753
- "sed",
754
- "sort",
755
- "uniq",
756
- "wc",
757
- "cut",
758
- "strings",
759
- "od",
760
- "xxd",
761
- "hexdump",
762
- "tr",
763
- "file",
764
- "stat",
765
- "du",
766
- "df",
767
- "ls",
768
- "dir",
769
- "tree",
770
- "nl",
771
- "tac",
772
- "rev",
773
- "lsblk",
774
- "blockdev",
775
- "smartctl",
776
- "udevadm",
777
- "dmidecode",
778
- "lscpu",
779
- "lspci",
780
- "lsusb",
781
- "lsmem",
782
- "lsns",
783
- "lsof",
784
- "hwinfo",
785
- "inxi",
786
- "fdisk",
787
- "parted",
788
- "dmesg",
789
- "journalctl",
790
- "systemctl",
791
- "hostnamectl",
792
- "localectl",
793
- "timedatectl",
794
- "ps",
795
- "top",
796
- "free",
797
- "vmstat",
798
- "iostat",
799
- "mpstat",
800
- "pidof",
801
- "pgrep",
802
- "ip",
803
- "ifconfig",
804
- "route",
805
- "ss",
806
- "netstat",
807
- "arp",
808
- "ethtool",
809
- "nmcli",
810
- "uname",
811
- "arch",
812
- "nproc",
813
- "getconf",
814
- "getent",
815
- "id",
816
- "whoami",
817
- "who",
818
- "w",
819
- "last",
820
- "uptime",
821
- "lsmod",
822
- "modinfo",
823
- "sysctl"
824
- ]);
825
- var _DEVNULL_REDIRECT_RE = /(?:2|&|1)?\s*>\s*\/dev\/null\b/;
826
- var _WRITE_COMMAND_RES = [
827
- /\brm\b/,
828
- /\bunlink\b/,
829
- /\brmdir\b/,
830
- /\bshred\b/,
831
- /\bcp\b/,
832
- /\bmv\b/,
833
- /\binstall\b/,
834
- /\btee\b/,
835
- /\bdd\b/,
836
- /\bmkfs\b/,
837
- /\b(fdisk|parted|cfdisk|sfdisk)\b/,
838
- /\bchmod\b/,
839
- /\bchown\b/,
840
- /\btruncate\b/
841
- ];
842
433
  const indirectResult = checkIndirectFileAccess(actionType, actionDetail, resolvedCwd);
843
434
  if (indirectResult) return indirectResult;
844
435
  if (actionDetail.includes("sudo") || actionDetail.includes("su -") || actionDetail.includes("chmod u+s") || actionDetail.includes("setuid")) {
845
- const adminContext = actionHistory.some(
846
- (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")
847
- );
848
- if (!adminContext) {
849
- return {
850
- allowed: false,
851
- reason: "\u68C0\u6D4B\u5230\u63D0\u6743\u64CD\u4F5C\uFF0C\u672A\u53D1\u73B0\u5408\u6CD5\u7BA1\u7406\u4E0A\u4E0B\u6587",
852
- riskLevel: "high",
853
- riskType: "privilege_escalation",
854
- confidence: 85,
855
- source: "logic_rule",
856
- violatedLayers: ["2"]
857
- };
858
- }
859
- }
860
- if (/(?:ssh|scp|rsync)\s+/.test(actionDetail)) {
861
- const targetHost = extractRemoteHost(actionDetail);
862
- if (targetHost && !isLocalHost(targetHost)) {
863
- return {
864
- allowed: false,
865
- reason: `\u5C1D\u8BD5\u6A2A\u5411\u79FB\u52A8\u5230\u8FDC\u7A0B\u4E3B\u673A: ${targetHost}`,
866
- riskLevel: "medium",
867
- riskType: "lateral_movement",
868
- confidence: 80,
869
- source: "logic_rule",
870
- violatedLayers: ["2"]
871
- };
436
+ const detailAfterSudo = actionDetail.replace(/(^|\|\||&&|\s)sudo\s+/g, " ");
437
+ const isAdminCmd = ["apt", "systemctl", "yum", "dnf", "pacman", "zypper", "apk"].some((kw) => detailAfterSudo.includes(kw));
438
+ if (!isAdminCmd) {
439
+ const adminContext = actionHistory.some(
440
+ (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")
441
+ );
442
+ if (!adminContext) {
443
+ return {
444
+ allowed: false,
445
+ reason: "\u68C0\u6D4B\u5230\u63D0\u6743\u64CD\u4F5C\uFF0C\u672A\u53D1\u73B0\u5408\u6CD5\u7BA1\u7406\u4E0A\u4E0B\u6587",
446
+ riskLevel: "high",
447
+ riskType: "privilege_escalation",
448
+ confidence: 85,
449
+ source: "logic_rule",
450
+ violatedLayers: ["2"]
451
+ };
452
+ }
872
453
  }
873
454
  }
874
455
  const pwdResult = checkPasswordConsent(actionType, actionDetail, actionHistory);
@@ -889,9 +470,12 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
889
470
  if (NON_WRITE_TOOLS.has(actionType)) return null;
890
471
  let isWrite = WRITE_KEYWORDS.some((kw) => actionType.includes(kw) || actionDetail.includes(kw));
891
472
  if (!isWrite && actionDetail.includes(">")) {
892
- const firstWord = actionDetail.split(/\s/)[0]?.toLowerCase() || "";
893
- if (!READ_ONLY_REDIR_CMDS.has(firstWord)) {
894
- isWrite = true;
473
+ const detailWithoutDevnull = actionDetail.replace(/(?:2|&|1)?\s*>\s*\/dev\/null\b/g, "");
474
+ if (detailWithoutDevnull.includes(">")) {
475
+ const firstWord = actionDetail.split(/\s/)[0]?.toLowerCase() || "";
476
+ if (!READ_ONLY_REDIR_CMDS.has(firstWord)) {
477
+ isWrite = true;
478
+ }
895
479
  }
896
480
  }
897
481
  if (!isWrite) return null;
@@ -936,119 +520,6 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
936
520
  }
937
521
  return null;
938
522
  }
939
- function checkSensitivePath(command, cwd, agentExt) {
940
- const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
941
- 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/];
942
- const _DEVNULL_RE = /(?:2|&|1)?\s*>\s*\/dev\/null\b/;
943
- 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"]);
944
- 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));
945
- for (const sp of ROOT_SENSITIVE_PATHS) {
946
- if (command.includes(sp.path.toLowerCase())) {
947
- if (sp.path === "/dev/random") {
948
- if (isWriteOpFlag) {
949
- continue;
950
- }
951
- return {
952
- allowed: false,
953
- reason: `\u8BFB\u53D6\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
954
- riskLevel: sp.riskLevel,
955
- riskType: "sensitive_path_access",
956
- confidence: 90,
957
- source: "logic_rule",
958
- violatedLayers: ["2"]
959
- };
960
- }
961
- if (!isWriteOpFlag && !sp.credential) {
962
- continue;
963
- }
964
- return {
965
- allowed: false,
966
- reason: `\u5C1D\u8BD5\u8BBF\u95EE\u7CFB\u7EDF\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
967
- riskLevel: sp.riskLevel,
968
- riskType: "sensitive_path_access",
969
- confidence: 90,
970
- source: "logic_rule",
971
- violatedLayers: ["2"]
972
- };
973
- }
974
- }
975
- for (const sp of HOME_SENSITIVE_PATHS) {
976
- const expanded = `${expandedCwd}/${sp.path}`;
977
- if (command.includes(sp.path.toLowerCase()) || command.includes(expanded.toLowerCase())) {
978
- if (!isWriteOpFlag && !sp.credential) {
979
- continue;
980
- }
981
- return {
982
- allowed: false,
983
- reason: `\u5C1D\u8BD5\u8BBF\u95EE\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
984
- riskLevel: sp.riskLevel,
985
- riskType: "sensitive_path_access",
986
- confidence: 90,
987
- source: "logic_rule",
988
- violatedLayers: ["2"]
989
- };
990
- }
991
- }
992
- if (agentExt?.extra_sensitive_paths && agentExt.extra_sensitive_paths.length > 0) {
993
- for (const sp of agentExt.extra_sensitive_paths) {
994
- const spPath = sp.path.toLowerCase();
995
- const expanded = `${expandedCwd}/${spPath}`;
996
- if (command.includes(spPath) || command.includes(expanded.toLowerCase())) {
997
- const denyMode = sp.deny_mode || (sp.credential ? "deny_both" : sp.read_only ? "deny_read" : "deny_write");
998
- if (denyMode === "deny_read") {
999
- if (isWriteOpFlag) continue;
1000
- } else if (denyMode === "deny_write") {
1001
- if (!isWriteOpFlag) continue;
1002
- }
1003
- return {
1004
- allowed: false,
1005
- reason: `\u5C1D\u8BD5\u8BBF\u95EE agent \u4E13\u5C5E\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
1006
- riskLevel: sp.risk_level,
1007
- riskType: "sensitive_path_access",
1008
- confidence: 90,
1009
- source: "logic_rule",
1010
- violatedLayers: ["2"]
1011
- };
1012
- }
1013
- }
1014
- }
1015
- for (const cp of CREDENTIAL_PATHS) {
1016
- const escaped = cp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1017
- const re = cp.path.startsWith(".") ? new RegExp(`(?:^|[\\s/\\\\'"])${escaped}\\b`) : new RegExp(`\\b${escaped}\\b`);
1018
- if (re.test(command)) {
1019
- return {
1020
- allowed: false,
1021
- reason: `\u8BBF\u95EE\u654F\u611F\u8DEF\u5F84: ${cp.desc} (${cp.path})`,
1022
- riskLevel: cp.riskLevel,
1023
- riskType: "sensitive_path_access",
1024
- confidence: 90,
1025
- source: "logic_rule",
1026
- violatedLayers: ["2"],
1027
- isCredential: true
1028
- };
1029
- }
1030
- }
1031
- if ((command.includes("rm ") || command.includes("unlink")) && (command.includes("/etc/") || command.includes("/root/") || command.includes("/var/log/"))) {
1032
- return {
1033
- allowed: false,
1034
- reason: "\u68C0\u6D4B\u5230\u5220\u9664\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55\u5185\u5BB9",
1035
- riskLevel: "critical",
1036
- riskType: "sensitive_path_access",
1037
- confidence: 90,
1038
- source: "logic_rule",
1039
- violatedLayers: ["2"]
1040
- };
1041
- }
1042
- return null;
1043
- }
1044
- function extractRemoteHost(command) {
1045
- const m = command.match(/(?:ssh|scp|rsync)\s+(?:-[\w]+\s+)*(\S+@\S+|\b(?!-)[\w.-]+\b)/);
1046
- return m ? m[1] : null;
1047
- }
1048
- function isLocalHost(host) {
1049
- const localNames = ["localhost", "127.0.0.1", "::1", process.env.HOSTNAME || "", "0.0.0.0"];
1050
- return localNames.includes(host.replace(/.*@/, ""));
1051
- }
1052
523
  function checkPasswordConsent(actionType, actionDetail, actionHistory) {
1053
524
  if (actionType !== "bash") return null;
1054
525
  const isPasswordModify = PASSWORD_MODIFY_PATTERNS.some((p) => p.test(actionDetail));
@@ -1169,11 +640,10 @@ var init_logic_rules = __esm({
1169
640
  "use strict";
1170
641
  init_esm_shims();
1171
642
  init_inline_analyzer();
1172
- init_agents();
1173
643
  ROOT_SENSITIVE_PATHS = [
1174
644
  { path: "/etc/shadow", riskLevel: "critical", desc: "\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6", credential: true },
1175
645
  { path: "/etc/gshadow", riskLevel: "critical", desc: "\u7CFB\u7EDF\u7EC4\u5BC6\u7801\u6587\u4EF6", credential: true },
1176
- // 注意:/etc/passwd 仅从 L1 启发式规则移除(公开可读),L2 敏感路径仍保留
646
+ // 注意:/etc/passwd 仅从 L1 特征匹配规则移除(公开可读),L2 敏感路径仍保留
1177
647
  { path: "/etc/passwd", riskLevel: "high", desc: "\u7CFB\u7EDF\u7528\u6237\u6587\u4EF6" },
1178
648
  { path: "/etc/sudoers", riskLevel: "critical", desc: "sudo \u914D\u7F6E", credential: true },
1179
649
  // 系统配置(仅拦截写入)
@@ -1266,21 +736,21 @@ var init_logic_rules = __esm({
1266
736
  });
1267
737
 
1268
738
  // src/engine/skill-loader.ts
1269
- import { readFileSync as readFileSync2, readdirSync as readdirSync2, existsSync as existsSync3 } from "fs";
1270
- import { resolve as resolve2, dirname as dirname2 } from "path";
1271
- import { fileURLToPath as fileURLToPath3 } from "url";
739
+ import { readFileSync, readdirSync as readdirSync2, existsSync as existsSync2 } from "fs";
740
+ import { resolve, dirname } from "path";
741
+ import { fileURLToPath as fileURLToPath2 } from "url";
1272
742
  function loadAllSkills() {
1273
743
  if (cachedSkills) return cachedSkills;
1274
744
  cachedSkills = {};
1275
- const dir = existsSync3(SKILLS_DIR) ? SKILLS_DIR : SKILLS_DIR_FALLBACK;
1276
- if (!existsSync3(dir)) {
745
+ const dir = existsSync2(SKILLS_DIR) ? SKILLS_DIR : SKILLS_DIR_FALLBACK;
746
+ if (!existsSync2(dir)) {
1277
747
  console.warn("[AgentMoss] Skills directory not found:", SKILLS_DIR, "and fallback:", SKILLS_DIR_FALLBACK);
1278
748
  return cachedSkills;
1279
749
  }
1280
750
  const files = readdirSync2(dir).filter((f) => f.endsWith(".md"));
1281
751
  for (const f of files) {
1282
752
  const name = f.replace(".md", "");
1283
- cachedSkills[name] = readFileSync2(resolve2(dir, f), "utf-8");
753
+ cachedSkills[name] = readFileSync(resolve(dir, f), "utf-8");
1284
754
  }
1285
755
  console.log(`[AgentMoss] Loaded ${Object.keys(cachedSkills).length} skills from ${dir}`);
1286
756
  return cachedSkills;
@@ -1334,15 +804,15 @@ function formatSkillsForPrompt(skills) {
1334
804
  ${s.skillContent}`
1335
805
  ).join("\n\n---\n\n");
1336
806
  }
1337
- var __filename3, __dirname3, SKILLS_DIR, SKILLS_DIR_FALLBACK, SKILL_KEYWORD_MAP, cachedSkills;
807
+ var __filename2, __dirname2, SKILLS_DIR, SKILLS_DIR_FALLBACK, SKILL_KEYWORD_MAP, cachedSkills;
1338
808
  var init_skill_loader = __esm({
1339
809
  "src/engine/skill-loader.ts"() {
1340
810
  "use strict";
1341
811
  init_esm_shims();
1342
- __filename3 = fileURLToPath3(import.meta.url);
1343
- __dirname3 = dirname2(__filename3);
1344
- SKILLS_DIR = resolve2(__dirname3, "../skills");
1345
- SKILLS_DIR_FALLBACK = resolve2(__dirname3, "../../../agent_moss/skills");
812
+ __filename2 = fileURLToPath2(import.meta.url);
813
+ __dirname2 = dirname(__filename2);
814
+ SKILLS_DIR = resolve(__dirname2, "../skills");
815
+ SKILLS_DIR_FALLBACK = resolve(__dirname2, "../../../agent_moss/skills");
1346
816
  SKILL_KEYWORD_MAP = {
1347
817
  "file_access_guard": ["file", "\u6587\u4EF6", "path", "\u8DEF\u5F84", "read", "write", "cat", "open", "access", "\u8BBF\u95EE", "\u8BFB\u5199", "copy", "cp", "mv", "move"],
1348
818
  "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"],
@@ -1364,189 +834,22 @@ var init_skill_loader = __esm({
1364
834
  }
1365
835
  });
1366
836
 
1367
- // src/engine/token-stats.ts
1368
- var token_stats_exports = {};
1369
- __export(token_stats_exports, {
1370
- getRecentRecords: () => getRecentRecords,
1371
- getTokenStats: () => getTokenStats,
1372
- getTokenTrend: () => getTokenTrend,
1373
- recordTokenUsage: () => recordTokenUsage,
1374
- resetTokenStats: () => resetTokenStats
1375
- });
1376
- import { readFileSync as readFileSync6, writeFileSync, existsSync as existsSync6, mkdirSync, unlinkSync } from "fs";
1377
- import { join as join3 } from "path";
1378
- import { homedir as homedir2 } from "os";
1379
- function getStatsPath() {
1380
- const envPath = process.env[ENV_STATS_PATH];
1381
- return envPath || STATS_FILE;
1382
- }
1383
- function loadAllRecords(statsPath) {
1384
- if (!existsSync6(statsPath)) return [];
1385
- try {
1386
- const data = JSON.parse(readFileSync6(statsPath, "utf-8"));
1387
- if (Array.isArray(data)) return data;
1388
- if (data && Array.isArray(data.records)) return data.records;
1389
- return [];
1390
- } catch (e) {
1391
- return [];
1392
- }
1393
- }
1394
- function saveRecords(statsPath, records) {
1395
- try {
1396
- const dir = statsPath.substring(0, statsPath.lastIndexOf("/"));
1397
- if (dir && !existsSync6(dir)) mkdirSync(dir, { recursive: true });
1398
- writeFileSync(statsPath, JSON.stringify(records, null, 2), "utf-8");
1399
- } catch (e) {
1400
- }
1401
- }
1402
- function recordTokenUsage(record) {
1403
- const statsPath = getStatsPath();
1404
- const records = loadAllRecords(statsPath);
1405
- records.push({
1406
- timestamp: record.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
1407
- session_id: record.sessionId,
1408
- step: record.step,
1409
- model: record.model,
1410
- prompt_tokens: record.promptTokens,
1411
- completion_tokens: record.completionTokens,
1412
- total_tokens: record.totalTokens
1413
- });
1414
- if (records.length > MAX_RECORDS) {
1415
- records.splice(0, records.length - MAX_RECORDS);
1416
- }
1417
- saveRecords(statsPath, records);
1418
- }
1419
- function filterByDate(records, days = 0, startDate, endDate) {
1420
- if (startDate || endDate) {
1421
- let startDt = startDate ? new Date(startDate) : /* @__PURE__ */ new Date(-864e13);
1422
- let endDt = endDate ? new Date(endDate) : void 0;
1423
- if (endDate) {
1424
- const e = new Date(endDate);
1425
- e.setHours(23, 59, 59, 999);
1426
- endDt = e;
1427
- }
1428
- const endMs = endDt ? endDt.getTime() : Number.MAX_SAFE_INTEGER;
1429
- return records.filter((r) => {
1430
- try {
1431
- const ts = new Date(r.timestamp).getTime();
1432
- return ts >= startDt.getTime() && ts <= endMs;
1433
- } catch {
1434
- return true;
1435
- }
1436
- });
1437
- } else if (days > 0) {
1438
- const cutoff = Date.now() - days * 864e5;
1439
- return records.filter((r) => {
1440
- try {
1441
- return new Date(r.timestamp).getTime() >= cutoff;
1442
- } catch {
1443
- return true;
1444
- }
1445
- });
1446
- }
1447
- return records;
1448
- }
1449
- function aggregate(records) {
1450
- let totalPrompt = 0, totalCompletion = 0, totalTotal = 0;
1451
- const byStep = {};
1452
- const byModel = {};
1453
- const byDate = {};
1454
- const bucket = (b, key, r) => {
1455
- const e = b[key] || (b[key] = { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0, calls: 0 });
1456
- e.prompt_tokens += r.prompt_tokens || 0;
1457
- e.completion_tokens += r.completion_tokens || 0;
1458
- e.total_tokens += r.total_tokens || 0;
1459
- e.calls += 1;
1460
- };
1461
- for (const r of records) {
1462
- totalPrompt += r.prompt_tokens || 0;
1463
- totalCompletion += r.completion_tokens || 0;
1464
- totalTotal += r.total_tokens || 0;
1465
- bucket(byStep, r.step || "unknown", r);
1466
- bucket(byModel, r.model || "unknown", r);
1467
- let dateKey = "unknown";
1468
- try {
1469
- dateKey = new Date(r.timestamp).toISOString().slice(0, 10);
1470
- } catch {
1471
- }
1472
- bucket(byDate, dateKey, r);
1473
- }
1474
- const sortedDate = Object.keys(byDate).sort((a, b) => a < b ? 1 : a > b ? -1 : 0);
1475
- const sortedByDate = {};
1476
- for (const k of sortedDate) sortedByDate[k] = byDate[k];
1477
- return { totalPrompt, totalCompletion, totalTotal, byStep, byModel, byDate: sortedByDate };
1478
- }
1479
- function getTokenStats(days = 0, startDate, endDate) {
1480
- const records = filterByDate(loadAllRecords(getStatsPath()), days, startDate, endDate);
1481
- const a = aggregate(records);
1482
- return {
1483
- total_prompt_tokens: a.totalPrompt,
1484
- total_completion_tokens: a.totalCompletion,
1485
- total_tokens: a.totalTotal,
1486
- total_calls: records.length,
1487
- by_step: a.byStep,
1488
- by_model: a.byModel,
1489
- by_date: a.byDate
1490
- };
1491
- }
1492
- function getRecentRecords(limit = 20) {
1493
- const records = loadAllRecords(getStatsPath());
1494
- return records.slice(-limit);
1495
- }
1496
- function getTokenTrend(days = 0, startDate, endDate) {
1497
- const records = filterByDate(loadAllRecords(getStatsPath()), days, startDate, endDate);
1498
- const series = {};
1499
- const datesSet = /* @__PURE__ */ new Set();
1500
- for (const r of records) {
1501
- let dateKey;
1502
- try {
1503
- dateKey = new Date(r.timestamp).toISOString().slice(0, 10);
1504
- } catch {
1505
- continue;
1506
- }
1507
- const model = r.model || "unknown";
1508
- const tokens = r.total_tokens || 0;
1509
- datesSet.add(dateKey);
1510
- series[model] = series[model] || {};
1511
- series[model][dateKey] = (series[model][dateKey] || 0) + tokens;
1512
- }
1513
- const dates = Array.from(datesSet).sort();
1514
- const models = Object.keys(series).sort();
1515
- return { dates, models, series };
1516
- }
1517
- function resetTokenStats() {
1518
- const statsPath = getStatsPath();
1519
- if (existsSync6(statsPath)) {
1520
- try {
1521
- unlinkSync(statsPath);
1522
- } catch {
1523
- }
1524
- }
1525
- }
1526
- var STATS_DIR, STATS_FILE, ENV_STATS_PATH, MAX_RECORDS;
1527
- var init_token_stats = __esm({
1528
- "src/engine/token-stats.ts"() {
1529
- "use strict";
1530
- init_esm_shims();
1531
- STATS_DIR = join3(homedir2(), ".config", "agentmoss");
1532
- STATS_FILE = join3(STATS_DIR, "agent_moss_token_stats.json");
1533
- ENV_STATS_PATH = "AGENT_MOSS_TOKEN_STATS_PATH";
1534
- MAX_RECORDS = 1e4;
1535
- }
1536
- });
1537
-
1538
837
  // src/infra/runtime-config.ts
1539
838
  var runtime_config_exports = {};
1540
839
  __export(runtime_config_exports, {
1541
840
  DENY_MODE_LABELS: () => DENY_MODE_LABELS,
841
+ addAgentRule: () => addAgentRule,
1542
842
  addCustomRule: () => addCustomRule,
1543
843
  addCustomSkill: () => addCustomSkill,
844
+ deleteAgentRule: () => deleteAgentRule,
1544
845
  deleteCustomRule: () => deleteCustomRule,
1545
846
  deleteCustomSkill: () => deleteCustomSkill,
1546
847
  filterEnabledRules: () => filterEnabledRules,
1547
848
  generateSourceDefaults: () => generateSourceDefaults,
849
+ getAgentRules: () => getAgentRules,
1548
850
  getDisabledL2RulesWithSkipL3: () => getDisabledL2RulesWithSkipL3,
1549
851
  getDisabledSkillIds: () => getDisabledSkillIds,
852
+ getEnabledAgentExtensions: () => getEnabledAgentExtensions,
1550
853
  getEnabledL1CommandPatterns: () => getEnabledL1CommandPatterns,
1551
854
  getEnabledL1CommandPatternsOrDefault: () => getEnabledL1CommandPatternsOrDefault,
1552
855
  getEnabledL1InjectionKeywords: () => getEnabledL1InjectionKeywords,
@@ -1568,6 +871,8 @@ __export(runtime_config_exports, {
1568
871
  loadRuntimeConfig: () => loadRuntimeConfig,
1569
872
  mergeConfig: () => mergeConfig,
1570
873
  saveRuntimeConfig: () => saveRuntimeConfig,
874
+ updateAgentCategoryEnabled: () => updateAgentCategoryEnabled,
875
+ updateAgentRuleEnabled: () => updateAgentRuleEnabled,
1571
876
  updateCategoryEnabled: () => updateCategoryEnabled,
1572
877
  updateLayerEnabled: () => updateLayerEnabled,
1573
878
  updateRuleDenyMode: () => updateRuleDenyMode,
@@ -1576,9 +881,11 @@ __export(runtime_config_exports, {
1576
881
  updateSkillCategoryEnabled: () => updateSkillCategoryEnabled,
1577
882
  updateSkillEnabled: () => updateSkillEnabled
1578
883
  });
1579
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync2, existsSync as existsSync7, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
1580
- import { join as join4 } from "path";
1581
- import { homedir as homedir3 } from "os";
884
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3, mkdirSync, unlinkSync, readdirSync as readdirSync3 } from "fs";
885
+ import { join } from "path";
886
+ import { homedir } from "os";
887
+ import { fileURLToPath as fileURLToPath3 } from "url";
888
+ import { dirname as dirname2 } from "path";
1582
889
  function getRuntimeConfigPath() {
1583
890
  const envPath = process.env[ENV_RUNTIME_CONFIG_PATH];
1584
891
  if (envPath) return envPath;
@@ -1587,8 +894,8 @@ function getRuntimeConfigPath() {
1587
894
  function saveRuntimeConfig(config) {
1588
895
  const p = getRuntimeConfigPath();
1589
896
  const dir = p.substring(0, p.lastIndexOf("/"));
1590
- if (dir && !existsSync7(dir)) mkdirSync2(dir, { recursive: true });
1591
- writeFileSync2(p, JSON.stringify(config, null, 2), "utf-8");
897
+ if (dir && !existsSync3(dir)) mkdirSync(dir, { recursive: true });
898
+ writeFileSync(p, JSON.stringify(config, null, 2), "utf-8");
1592
899
  }
1593
900
  function emptyRuntimeConfig() {
1594
901
  return {
@@ -1596,12 +903,13 @@ function emptyRuntimeConfig() {
1596
903
  layers: { L1_heuristic: true, L2_logic_rules: true, L3_llm_analysis: true },
1597
904
  L1_rules: {},
1598
905
  L2_rules: {},
1599
- L3_skills: {}
906
+ L3_skills: {},
907
+ agent_rules: {}
1600
908
  };
1601
909
  }
1602
910
  function loadRuntimeConfig() {
1603
911
  const p = getRuntimeConfigPath();
1604
- if (!existsSync7(p)) {
912
+ if (!existsSync3(p)) {
1605
913
  const seeded = mergeConfig({}, generateSourceDefaults());
1606
914
  try {
1607
915
  saveRuntimeConfig(seeded);
@@ -1610,7 +918,7 @@ function loadRuntimeConfig() {
1610
918
  return seeded;
1611
919
  }
1612
920
  try {
1613
- const data = JSON.parse(readFileSync7(p, "utf-8"));
921
+ const data = JSON.parse(readFileSync2(p, "utf-8"));
1614
922
  const merged = mergeConfig(data, generateSourceDefaults());
1615
923
  return {
1616
924
  version: data.version || 1,
@@ -1621,7 +929,8 @@ function loadRuntimeConfig() {
1621
929
  },
1622
930
  L1_rules: merged.L1_rules || {},
1623
931
  L2_rules: merged.L2_rules || {},
1624
- L3_skills: merged.L3_skills || {}
932
+ L3_skills: merged.L3_skills || {},
933
+ agent_rules: merged.agent_rules || {}
1625
934
  };
1626
935
  } catch {
1627
936
  return emptyRuntimeConfig();
@@ -1664,7 +973,10 @@ function getEnabledL1CommandPatterns(runtime) {
1664
973
  }));
1665
974
  }
1666
975
  function getEnabledL2SensitivePaths(runtime) {
1667
- const cat = runtime.L2_rules?.sensitive_path_access || { category_enabled: true, rules: [] };
976
+ let cat = runtime.L1_rules?.sensitive_path_access;
977
+ if (!cat || !cat.rules || cat.rules.length === 0) {
978
+ cat = runtime.L2_rules?.sensitive_path_access || { category_enabled: true, rules: [] };
979
+ }
1668
980
  return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => {
1669
981
  const out = { path: r.path || "", risk_level: r.risk_level || "", desc: r.desc || "", deny_mode: r.deny_mode || "deny_write" };
1670
982
  if (r.credential) out.credential = true;
@@ -1674,9 +986,9 @@ function getEnabledL2SensitivePaths(runtime) {
1674
986
  }
1675
987
  function hasRuntimeConfig() {
1676
988
  const p = getRuntimeConfigPath();
1677
- if (!existsSync7(p)) return false;
989
+ if (!existsSync3(p)) return false;
1678
990
  try {
1679
- JSON.parse(readFileSync7(p, "utf-8"));
991
+ JSON.parse(readFileSync2(p, "utf-8"));
1680
992
  return true;
1681
993
  } catch {
1682
994
  return false;
@@ -1690,22 +1002,24 @@ function getDisabledL2RulesWithSkipL3() {
1690
1002
  return {};
1691
1003
  }
1692
1004
  const result = {};
1693
- const l2 = runtime.L2_rules || {};
1694
- for (const [catName, cat] of Object.entries(l2)) {
1695
- if (!cat.category_enabled) continue;
1696
- const items = [];
1697
- for (const r of cat.rules || []) {
1698
- if (r.enabled === false && r.skip_l3_on_disabled) {
1699
- if (catName === "sensitive_path_access") {
1700
- if (r.path) items.push(r.path);
1701
- } else if (catName === "intent_consistency") {
1702
- items.push(...r.intent_keywords || []);
1703
- } else if (r.pattern) {
1704
- items.push(r.pattern);
1005
+ for (const layerKey of ["L1_rules", "L2_rules"]) {
1006
+ const layer = runtime[layerKey] || {};
1007
+ for (const [catName, cat] of Object.entries(layer)) {
1008
+ if (!cat.category_enabled) continue;
1009
+ const items = [];
1010
+ for (const r of cat.rules || []) {
1011
+ if (r.enabled === false && r.skip_l3_on_disabled) {
1012
+ if (catName === "sensitive_path_access") {
1013
+ if (r.path) items.push(r.path);
1014
+ } else if (catName === "intent_consistency") {
1015
+ items.push(...r.intent_keywords || []);
1016
+ } else if (r.pattern) {
1017
+ items.push(r.pattern);
1018
+ }
1705
1019
  }
1706
1020
  }
1021
+ if (items.length > 0) result[catName] = items;
1707
1022
  }
1708
- if (items.length > 0) result[catName] = items;
1709
1023
  }
1710
1024
  return result;
1711
1025
  }
@@ -1851,6 +1165,55 @@ function _l3SkillsToRules() {
1851
1165
  out.user_custom = { category_enabled: true, category_desc: "", skills: [] };
1852
1166
  return out;
1853
1167
  }
1168
+ function _agentRulesToDefaults() {
1169
+ const out = {};
1170
+ const agentsDir = existsSync3(_AGENTS_DIR_PROD) ? _AGENTS_DIR_PROD : existsSync3(_AGENTS_DIR_DEV) ? _AGENTS_DIR_DEV : _AGENTS_DIR_PROD;
1171
+ if (!existsSync3(agentsDir)) return out;
1172
+ let entries;
1173
+ try {
1174
+ entries = readdirSync3(agentsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
1175
+ } catch {
1176
+ return out;
1177
+ }
1178
+ for (const agentId of entries) {
1179
+ const rulesFile = join(agentsDir, agentId, "rules.json");
1180
+ if (!existsSync3(rulesFile)) continue;
1181
+ let data;
1182
+ try {
1183
+ data = JSON.parse(readFileSync2(rulesFile, "utf-8"));
1184
+ } catch {
1185
+ continue;
1186
+ }
1187
+ out[agentId] = {
1188
+ builtin_safe_action_types: {
1189
+ category_enabled: true,
1190
+ 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",
1191
+ rules: (data.builtin_safe_action_types || []).map((v) => ({ value: v, enabled: true, builtin: true }))
1192
+ },
1193
+ builtin_safe_skills: {
1194
+ category_enabled: true,
1195
+ category_desc: "agent \u4FE1\u4EFB\u7684\u5185\u7F6E Skill\uFF08\u547D\u4E2D\u8DF3 L2+L3\uFF09",
1196
+ rules: (data.builtin_safe_skills || []).map((v) => ({ value: v, enabled: true, builtin: true }))
1197
+ },
1198
+ extra_command_patterns: {
1199
+ category_enabled: true,
1200
+ category_desc: "agent \u4E13\u5C5E\u5371\u9669\u547D\u4EE4\u6B63\u5219\uFF08L1 \u672B\u5C3E\u8FFD\u52A0\uFF09",
1201
+ rules: (data.extra_command_patterns || []).map((p) => ({ ...p, enabled: true, builtin: true }))
1202
+ },
1203
+ extra_injection_keywords: {
1204
+ category_enabled: true,
1205
+ category_desc: "agent \u4E13\u5C5E\u6CE8\u5165\u5173\u952E\u8BCD\uFF08L1 \u672B\u5C3E\u8FFD\u52A0\uFF09",
1206
+ rules: (data.extra_injection_keywords || []).map((k) => ({ ...k, enabled: true, builtin: true }))
1207
+ },
1208
+ extra_sensitive_paths: {
1209
+ category_enabled: true,
1210
+ category_desc: "agent \u4E13\u5C5E\u654F\u611F\u8DEF\u5F84\uFF08L1 \u672B\u5C3E\u8FFD\u52A0\uFF09",
1211
+ rules: (data.extra_sensitive_paths || []).map((sp) => ({ ...sp, enabled: true, builtin: true }))
1212
+ }
1213
+ };
1214
+ }
1215
+ return out;
1216
+ }
1854
1217
  function generateSourceDefaults() {
1855
1218
  return {
1856
1219
  version: 1,
@@ -1866,15 +1229,14 @@ function generateSourceDefaults() {
1866
1229
  category_desc: "Prompt \u6CE8\u5165\u653B\u51FB\u68C0\u6D4B",
1867
1230
  rules: _l1InjectionKeywordsToRules()
1868
1231
  },
1869
- user_sensitive_actions: { category_enabled: true, category_desc: "\u7528\u6237\u81EA\u5B9A\u4E49\u654F\u611F\u52A8\u4F5C/\u5DE5\u5177\u89C4\u5219", rules: [] },
1870
- user_custom: { category_enabled: true, category_desc: "\u7528\u6237\u81EA\u5B9A\u4E49\u547D\u4EE4\u89C4\u5219", rules: [] }
1871
- },
1872
- L2_rules: {
1873
1232
  sensitive_path_access: {
1874
1233
  category_enabled: true,
1875
- category_desc: "\u654F\u611F\u8DEF\u5F84\u8BBF\u95EE\u68C0\u6D4B",
1234
+ category_desc: "\u654F\u611F\u8DEF\u5F84\u8BBF\u95EE\u68C0\u6D4B\uFF08deny_both/deny_read/deny_write\uFF09",
1876
1235
  rules: _l2SensitivePathsToRules()
1877
1236
  },
1237
+ user_custom: { category_enabled: true, category_desc: "\u7528\u6237\u81EA\u5B9A\u4E49\u547D\u4EE4\u89C4\u5219", rules: [] }
1238
+ },
1239
+ L2_rules: {
1878
1240
  intent_consistency: {
1879
1241
  category_enabled: true,
1880
1242
  category_desc: "\u610F\u56FE\u4E00\u81F4\u6027\u68C0\u6D4B",
@@ -1891,9 +1253,10 @@ function generateSourceDefaults() {
1891
1253
  rules: _l2UserDeletionPatternsToRules()
1892
1254
  },
1893
1255
  read_before_write: { category_enabled: true, category_desc: "read_before_write \u5B89\u5168\u539F\u5219\u68C0\u6D4B", rules: [] },
1894
- dangerous_patterns: { category_enabled: true, category_desc: "\u5371\u9669\u64CD\u4F5C\u6A21\u5F0F\u68C0\u6D4B", rules: [] }
1256
+ user_custom: { category_enabled: true, category_desc: "\u7528\u6237\u81EA\u5B9A\u4E49 L2 \u903B\u8F91\u89C4\u5219", rules: [] }
1895
1257
  },
1896
- L3_skills: _l3SkillsToRules()
1258
+ L3_skills: _l3SkillsToRules(),
1259
+ agent_rules: _agentRulesToDefaults()
1897
1260
  };
1898
1261
  }
1899
1262
  function _ruleFingerprint(rule) {
@@ -1906,6 +1269,7 @@ function _ruleFingerprint(rule) {
1906
1269
  if (rule.path) return JSON.stringify([rule.path]);
1907
1270
  if (rule.pattern) return JSON.stringify([rule.pattern]);
1908
1271
  if (rule.keyword) return JSON.stringify([rule.keyword, rule.lang || ""]);
1272
+ if (rule.value) return JSON.stringify([rule.value]);
1909
1273
  return JSON.stringify([rule.id || ""]);
1910
1274
  }
1911
1275
  function _mergeRuleCategories(userCfg, srcDefaults, layerKey) {
@@ -1959,6 +1323,10 @@ function _mergeRuleCategories(userCfg, srcDefaults, layerKey) {
1959
1323
  }
1960
1324
  }
1961
1325
  }
1326
+ for (const catName of Object.keys(userLayer)) {
1327
+ if (catName === "user_custom") continue;
1328
+ if (!(catName in srcLayer)) delete userLayer[catName];
1329
+ }
1962
1330
  }
1963
1331
  function _mergeSkillCategories(userCfg, srcDefaults, layerKey) {
1964
1332
  const userLayer = userCfg[layerKey] || (userCfg[layerKey] = {});
@@ -1978,6 +1346,50 @@ function _mergeSkillCategories(userCfg, srcDefaults, layerKey) {
1978
1346
  }
1979
1347
  }
1980
1348
  }
1349
+ function _mergeAgentRules(userCfg, srcDefaults) {
1350
+ const userAgent = userCfg.agent_rules || (userCfg.agent_rules = {});
1351
+ const srcAgent = srcDefaults.agent_rules || {};
1352
+ for (const [agentId, srcCats] of Object.entries(srcAgent)) {
1353
+ if (!(agentId in userAgent)) {
1354
+ userAgent[agentId] = JSON.parse(JSON.stringify(srcCats));
1355
+ continue;
1356
+ }
1357
+ const userCats = userAgent[agentId];
1358
+ for (const [catName, srcCat] of Object.entries(srcCats)) {
1359
+ if (!(catName in userCats)) {
1360
+ userCats[catName] = JSON.parse(JSON.stringify(srcCat));
1361
+ continue;
1362
+ }
1363
+ const userCat = userCats[catName];
1364
+ if (userCat.category_desc === void 0) userCat.category_desc = srcCat.category_desc || "";
1365
+ if (userCat.category_enabled === void 0) userCat.category_enabled = srcCat.category_enabled ?? true;
1366
+ const srcRules = srcCat.rules || [];
1367
+ const userRules = userCat.rules || (userCat.rules = []);
1368
+ const userByFp = /* @__PURE__ */ new Map();
1369
+ for (const r of userRules) userByFp.set(_ruleFingerprint(r), r);
1370
+ const srcFps = /* @__PURE__ */ new Set();
1371
+ for (const srcR of srcRules) {
1372
+ const fp = _ruleFingerprint(srcR);
1373
+ srcFps.add(fp);
1374
+ if (userByFp.has(fp)) {
1375
+ const existing = userByFp.get(fp);
1376
+ const userEnabled = existing.enabled;
1377
+ Object.keys(existing).forEach((k) => delete existing[k]);
1378
+ Object.assign(existing, srcR);
1379
+ existing.enabled = userEnabled;
1380
+ } else {
1381
+ userRules.push({ ...srcR, enabled: true });
1382
+ }
1383
+ }
1384
+ for (const r of userRules) {
1385
+ if (r.builtin && !srcFps.has(_ruleFingerprint(r))) {
1386
+ r.source_removed = true;
1387
+ r.enabled = false;
1388
+ }
1389
+ }
1390
+ }
1391
+ }
1392
+ }
1981
1393
  function mergeConfig(userCfg, srcDefaults) {
1982
1394
  const cfg = userCfg;
1983
1395
  if (cfg.version === void 0) cfg.version = srcDefaults.version || 1;
@@ -1985,15 +1397,47 @@ function mergeConfig(userCfg, srcDefaults) {
1985
1397
  for (const [layerKey, defaultVal] of Object.entries(srcDefaults.layers)) {
1986
1398
  if (!(layerKey in cfg.layers)) cfg.layers[layerKey] = defaultVal;
1987
1399
  }
1400
+ _migrateCategoriesAcrossLayers(
1401
+ cfg,
1402
+ "L2_rules",
1403
+ "L1_rules",
1404
+ ["sensitive_path_access"]
1405
+ );
1988
1406
  _mergeRuleCategories(cfg, srcDefaults, "L1_rules");
1989
1407
  _mergeRuleCategories(cfg, srcDefaults, "L2_rules");
1990
1408
  _mergeSkillCategories(cfg, srcDefaults, "L3_skills");
1409
+ _mergeAgentRules(cfg, srcDefaults);
1991
1410
  return cfg;
1992
1411
  }
1993
- function getEnabledL1InjectionKeywords(runtime) {
1994
- const cat = runtime.L1_rules?.injection_detection || { category_enabled: true, rules: [] };
1995
- return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => ({
1996
- keyword: r.keyword || "",
1412
+ function _migrateCategoriesAcrossLayers(cfg, srcLayer, dstLayer, catNames) {
1413
+ const src = cfg[srcLayer] || {};
1414
+ const dst = cfg[dstLayer] || (cfg[dstLayer] = {});
1415
+ for (const catName of catNames) {
1416
+ if (!(catName in src)) continue;
1417
+ const srcCat = src[catName];
1418
+ if (!dst[catName]) {
1419
+ dst[catName] = { category_enabled: true, category_desc: "", rules: [] };
1420
+ }
1421
+ const dstCat = dst[catName];
1422
+ if (!dstCat.category_desc && srcCat.category_desc) dstCat.category_desc = srcCat.category_desc;
1423
+ if (dstCat.category_enabled === void 0) dstCat.category_enabled = srcCat.category_enabled;
1424
+ if (srcCat.rules && srcCat.rules.length > 0) {
1425
+ const dstFps = new Set((dstCat.rules || []).map((r) => _ruleFingerprint(r)));
1426
+ for (const r of srcCat.rules) {
1427
+ const fp = _ruleFingerprint(r);
1428
+ if (!dstFps.has(fp)) {
1429
+ (dstCat.rules = dstCat.rules || []).push(r);
1430
+ dstFps.add(fp);
1431
+ }
1432
+ }
1433
+ }
1434
+ delete src[catName];
1435
+ }
1436
+ }
1437
+ function getEnabledL1InjectionKeywords(runtime) {
1438
+ const cat = runtime.L1_rules?.injection_detection || { category_enabled: true, rules: [] };
1439
+ return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => ({
1440
+ keyword: r.keyword || "",
1997
1441
  risk_level: r.risk_level || "",
1998
1442
  lang: r.lang || "en"
1999
1443
  }));
@@ -2069,162 +1513,898 @@ function getEnabledL2UserDeletionPatternsOrDefault(runtime) {
2069
1513
  if (!hasRuntimeConfig()) {
2070
1514
  return USER_DELETION_PATTERNS.map((p) => p.source);
2071
1515
  }
2072
- return [];
1516
+ return [];
1517
+ }
1518
+ function _validateSkillId(skillId) {
1519
+ if (!skillId || !SKILL_ID_RE.test(skillId)) {
1520
+ throw new Error(`skill_id '${skillId}' \u4E0D\u5408\u6CD5\uFF1A\u53EA\u5141\u8BB8\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\u548C\u8FDE\u5B57\u7B26`);
1521
+ }
1522
+ }
1523
+ function updateLayerEnabled(layerKey, enabled) {
1524
+ const runtime = loadRuntimeConfig();
1525
+ runtime.layers[layerKey] = enabled;
1526
+ saveRuntimeConfig(runtime);
1527
+ return runtime;
1528
+ }
1529
+ function updateRuleEnabled(layer, category, ruleId, enabled) {
1530
+ const runtime = loadRuntimeConfig();
1531
+ for (const r of runtime[layer]?.[category]?.rules || []) {
1532
+ if (r.id === ruleId) {
1533
+ r.enabled = enabled;
1534
+ break;
1535
+ }
1536
+ }
1537
+ saveRuntimeConfig(runtime);
1538
+ return runtime;
1539
+ }
1540
+ function updateRuleDenyMode(layer, category, ruleId, denyMode) {
1541
+ const runtime = loadRuntimeConfig();
1542
+ for (const r of runtime[layer]?.[category]?.rules || []) {
1543
+ if (r.id === ruleId) {
1544
+ r.deny_mode = denyMode;
1545
+ _syncDenyModeFields(r);
1546
+ break;
1547
+ }
1548
+ }
1549
+ saveRuntimeConfig(runtime);
1550
+ return runtime;
1551
+ }
1552
+ function updateRuleSkipL3(layer, category, ruleId, skipL3) {
1553
+ const runtime = loadRuntimeConfig();
1554
+ for (const r of runtime[layer]?.[category]?.rules || []) {
1555
+ if (r.id === ruleId) {
1556
+ r.skip_l3_on_disabled = skipL3;
1557
+ break;
1558
+ }
1559
+ }
1560
+ saveRuntimeConfig(runtime);
1561
+ return runtime;
1562
+ }
1563
+ function updateCategoryEnabled(layer, category, enabled) {
1564
+ const runtime = loadRuntimeConfig();
1565
+ const cat = runtime[layer]?.[category];
1566
+ if (cat) cat.category_enabled = enabled;
1567
+ saveRuntimeConfig(runtime);
1568
+ return runtime;
1569
+ }
1570
+ function addCustomRule(layer, category, rule) {
1571
+ const runtime = loadRuntimeConfig();
1572
+ const layerObj = runtime[layer] || (runtime[layer] = {});
1573
+ const cat = layerObj[category] || (layerObj[category] = { category_enabled: true, category_desc: "", rules: [] });
1574
+ const rules = cat.rules || (cat.rules = []);
1575
+ const newRule = { ...rule, builtin: false, enabled: true };
1576
+ if (!newRule.id) {
1577
+ newRule.id = `custom_${Date.now()}_${_slugify(JSON.stringify(rule).slice(0, 30))}`;
1578
+ }
1579
+ if (newRule.deny_mode) _syncDenyModeFields(newRule);
1580
+ rules.push(newRule);
1581
+ saveRuntimeConfig(runtime);
1582
+ return runtime;
1583
+ }
1584
+ function deleteCustomRule(layer, category, ruleId) {
1585
+ const runtime = loadRuntimeConfig();
1586
+ const rules = runtime[layer]?.[category]?.rules || [];
1587
+ const idx = rules.findIndex((r) => r.id === ruleId);
1588
+ if (idx >= 0) {
1589
+ const r = rules[idx];
1590
+ if (r.builtin && !r.source_removed) return null;
1591
+ rules.splice(idx, 1);
1592
+ saveRuntimeConfig(runtime);
1593
+ }
1594
+ return runtime;
1595
+ }
1596
+ function addCustomSkill(skillId, category, keywords, content) {
1597
+ _validateSkillId(skillId);
1598
+ const runtime = loadRuntimeConfig();
1599
+ const skillsDir = join(RUNTIME_CONFIG_DIR, "agent_moss_skills");
1600
+ try {
1601
+ mkdirSync(skillsDir, { recursive: true });
1602
+ } catch {
1603
+ }
1604
+ writeFileSync(join(skillsDir, `${skillId}.md`), content, "utf-8");
1605
+ const l3 = runtime.L3_skills || (runtime.L3_skills = {});
1606
+ const cat = l3[category] || (l3[category] = { category_enabled: true, category_desc: "", skills: [] });
1607
+ (cat.skills || (cat.skills = [])).push({ id: skillId, keywords, enabled: true, builtin: false });
1608
+ saveRuntimeConfig(runtime);
1609
+ return runtime;
1610
+ }
1611
+ function deleteCustomSkill(skillId, category) {
1612
+ _validateSkillId(skillId);
1613
+ const runtime = loadRuntimeConfig();
1614
+ const skills = runtime.L3_skills?.[category]?.skills || [];
1615
+ const idx = skills.findIndex((s) => s.id === skillId);
1616
+ if (idx >= 0) {
1617
+ if (skills[idx].builtin) return null;
1618
+ skills.splice(idx, 1);
1619
+ const skillPath = join(RUNTIME_CONFIG_DIR, "agent_moss_skills", `${skillId}.md`);
1620
+ try {
1621
+ if (existsSync3(skillPath)) unlinkSync(skillPath);
1622
+ } catch {
1623
+ }
1624
+ saveRuntimeConfig(runtime);
1625
+ }
1626
+ return runtime;
1627
+ }
1628
+ function updateSkillEnabled(skillId, enabled) {
1629
+ const runtime = loadRuntimeConfig();
1630
+ for (const catData of Object.values(runtime.L3_skills || {})) {
1631
+ for (const s of catData.skills || []) {
1632
+ if (s.id === skillId) {
1633
+ s.enabled = enabled;
1634
+ break;
1635
+ }
1636
+ }
1637
+ }
1638
+ saveRuntimeConfig(runtime);
1639
+ return runtime;
1640
+ }
1641
+ function updateSkillCategoryEnabled(category, enabled) {
1642
+ const runtime = loadRuntimeConfig();
1643
+ const cat = runtime.L3_skills?.[category];
1644
+ if (cat) cat.category_enabled = enabled;
1645
+ saveRuntimeConfig(runtime);
1646
+ return runtime;
1647
+ }
1648
+ function getProfileOrDetect() {
1649
+ return null;
1650
+ }
1651
+ function getAgentRules(runtime) {
1652
+ return runtime.agent_rules || {};
1653
+ }
1654
+ function _agentCategoryEnabledRules(cat) {
1655
+ if (!cat || cat.category_enabled === false) return [];
1656
+ return (cat.rules || []).filter((r) => r.enabled !== false);
1657
+ }
1658
+ function getEnabledAgentExtensions(runtime, agentId) {
1659
+ const agentData = runtime.agent_rules?.[agentId];
1660
+ if (!agentData) return null;
1661
+ const safeAction = _agentCategoryEnabledRules(agentData.builtin_safe_action_types);
1662
+ const safeSkills = _agentCategoryEnabledRules(agentData.builtin_safe_skills);
1663
+ const cmdPatterns = _agentCategoryEnabledRules(agentData.extra_command_patterns).filter((r) => r.pattern).map((r) => ({
1664
+ pattern: r.pattern,
1665
+ risk_level: r.risk_level || "",
1666
+ risk_type: r.risk_type || "",
1667
+ reason: r.reason || ""
1668
+ }));
1669
+ const injKeywords = _agentCategoryEnabledRules(agentData.extra_injection_keywords).filter((r) => r.keyword).map((r) => ({
1670
+ keyword: r.keyword,
1671
+ risk_level: r.risk_level || "",
1672
+ lang: r.lang || "en"
1673
+ }));
1674
+ const sensitivePaths = _agentCategoryEnabledRules(agentData.extra_sensitive_paths).filter((r) => r.path).map((r) => ({
1675
+ path: r.path,
1676
+ risk_level: r.risk_level || "",
1677
+ desc: r.desc || "",
1678
+ credential: r.credential,
1679
+ read_only: r.read_only,
1680
+ deny_mode: r.deny_mode
1681
+ }));
1682
+ return {
1683
+ agent_id: agentId,
1684
+ description: "",
1685
+ extra_command_patterns: cmdPatterns,
1686
+ extra_injection_keywords: injKeywords,
1687
+ extra_sensitive_paths: sensitivePaths,
1688
+ builtin_safe_skills: safeSkills.filter((r) => r.value).map((r) => r.value),
1689
+ builtin_safe_action_types: safeAction.filter((r) => r.value).map((r) => r.value),
1690
+ extra_skills: []
1691
+ };
1692
+ }
1693
+ function updateAgentRuleEnabled(agentId, category, ruleFp, enabled) {
1694
+ const runtime = loadRuntimeConfig();
1695
+ const cat = runtime.agent_rules?.[agentId]?.[category];
1696
+ if (cat) {
1697
+ const targetFp = _ruleFingerprint(ruleFp);
1698
+ for (const r of cat.rules || []) {
1699
+ if (_ruleFingerprint(r) === targetFp) {
1700
+ r.enabled = enabled;
1701
+ break;
1702
+ }
1703
+ }
1704
+ }
1705
+ saveRuntimeConfig(runtime);
1706
+ return runtime;
1707
+ }
1708
+ function addAgentRule(agentId, category, rule) {
1709
+ const runtime = loadRuntimeConfig();
1710
+ if (!runtime.agent_rules) runtime.agent_rules = {};
1711
+ if (!runtime.agent_rules[agentId]) runtime.agent_rules[agentId] = {};
1712
+ const cats = runtime.agent_rules[agentId];
1713
+ if (!cats[category]) cats[category] = { category_enabled: true, category_desc: "", rules: [] };
1714
+ (cats[category].rules || (cats[category].rules = [])).push({ ...rule, enabled: true, builtin: false });
1715
+ saveRuntimeConfig(runtime);
1716
+ return runtime;
1717
+ }
1718
+ function deleteAgentRule(agentId, category, ruleFp) {
1719
+ const runtime = loadRuntimeConfig();
1720
+ const cat = runtime.agent_rules?.[agentId]?.[category];
1721
+ if (cat) {
1722
+ const targetFp = _ruleFingerprint(ruleFp);
1723
+ cat.rules = (cat.rules || []).filter((r) => _ruleFingerprint(r) !== targetFp);
1724
+ }
1725
+ saveRuntimeConfig(runtime);
1726
+ return runtime;
1727
+ }
1728
+ function updateAgentCategoryEnabled(agentId, category, enabled) {
1729
+ const runtime = loadRuntimeConfig();
1730
+ const cat = runtime.agent_rules?.[agentId]?.[category];
1731
+ if (cat) cat.category_enabled = enabled;
1732
+ saveRuntimeConfig(runtime);
1733
+ return runtime;
1734
+ }
1735
+ 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;
1736
+ var init_runtime_config = __esm({
1737
+ "src/infra/runtime-config.ts"() {
1738
+ "use strict";
1739
+ init_esm_shims();
1740
+ init_heuristic();
1741
+ init_logic_rules();
1742
+ init_skill_loader();
1743
+ __dirname3 = dirname2(fileURLToPath3(import.meta.url));
1744
+ RUNTIME_CONFIG_DIR = join(homedir(), ".config", "agentmoss");
1745
+ RUNTIME_CONFIG_PATH = join(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
1746
+ ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
1747
+ LAYER_DISABLE_ENV = {
1748
+ L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
1749
+ L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
1750
+ L3_llm_analysis: "AGENT_MOSS_DISABLE_LLM"
1751
+ };
1752
+ DENY_MODE_LABELS = {
1753
+ deny_write: "\u4EC5\u62E6\u622A\u5199\u5165",
1754
+ deny_read: "\u4EC5\u62E6\u622A\u8BFB\u53D6",
1755
+ deny_both: "\u8BFB\u5199\u5747\u62E6\u622A"
1756
+ };
1757
+ _AGENTS_DIR_PROD = join(__dirname3, "agents");
1758
+ _AGENTS_DIR_DEV = join(__dirname3, "..", "..", "..", "agent_moss", "agents");
1759
+ SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
1760
+ }
1761
+ });
1762
+
1763
+ // src/agents/index.ts
1764
+ import { readFileSync as readFileSync3, existsSync as existsSync4 } from "fs";
1765
+ import { resolve as resolve2, dirname as dirname3 } from "path";
1766
+ import { fileURLToPath as fileURLToPath4 } from "url";
1767
+ function resolveAgentsDir() {
1768
+ if (existsSync4(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
1769
+ if (existsSync4(AGENTS_DIR_DEV_FALLBACK)) return AGENTS_DIR_DEV_FALLBACK;
1770
+ return AGENTS_DIR_PROD;
1771
+ }
1772
+ function getAgentExtensions(agentId) {
1773
+ if (!agentId) return null;
1774
+ if (cache.has(agentId)) return cache.get(agentId) ?? null;
1775
+ let ext = null;
1776
+ try {
1777
+ const { hasRuntimeConfig: hasRuntimeConfig2, loadRuntimeConfig: loadRuntimeConfig2, getEnabledAgentExtensions: getEnabledAgentExtensions2 } = (init_runtime_config(), __toCommonJS(runtime_config_exports));
1778
+ if (hasRuntimeConfig2()) {
1779
+ ext = getEnabledAgentExtensions2(loadRuntimeConfig2(), agentId);
1780
+ }
1781
+ } catch {
1782
+ }
1783
+ if (ext === null) {
1784
+ const agentsDir = resolveAgentsDir();
1785
+ const rulesFile = resolve2(agentsDir, agentId, "rules.json");
1786
+ if (!existsSync4(rulesFile)) {
1787
+ cache.set(agentId, null);
1788
+ return null;
1789
+ }
1790
+ try {
1791
+ const raw = readFileSync3(rulesFile, "utf-8");
1792
+ ext = JSON.parse(raw);
1793
+ if (!ext.agent_id) {
1794
+ console.warn(`[AgentMoss agents] rules.json \u7F3A\u5C11 agent_id \u5B57\u6BB5: ${rulesFile}`);
1795
+ cache.set(agentId, null);
1796
+ return null;
1797
+ }
1798
+ } catch (e) {
1799
+ console.error(`[AgentMoss agents] \u52A0\u8F7D agent ${agentId} rules.json \u5931\u8D25:`, e.message);
1800
+ cache.set(agentId, null);
1801
+ return null;
1802
+ }
1803
+ }
1804
+ cache.set(agentId, ext);
1805
+ console.log(
1806
+ `[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`
1807
+ );
1808
+ return ext;
1809
+ }
1810
+ var __filename3, __dirname4, AGENTS_DIR_PROD, AGENTS_DIR_DEV_FALLBACK, cache;
1811
+ var init_agents = __esm({
1812
+ "src/agents/index.ts"() {
1813
+ "use strict";
1814
+ init_esm_shims();
1815
+ __filename3 = fileURLToPath4(import.meta.url);
1816
+ __dirname4 = dirname3(__filename3);
1817
+ AGENTS_DIR_PROD = resolve2(__dirname4, "agents");
1818
+ AGENTS_DIR_DEV_FALLBACK = resolve2(__dirname4, "../../../agent_moss/agents");
1819
+ cache = /* @__PURE__ */ new Map();
1820
+ }
1821
+ });
1822
+
1823
+ // src/engine/heuristic.ts
1824
+ function runHeuristicCheck(input) {
1825
+ const command = `${input.aNext.actionType}: ${input.aNext.actionDetail}`;
1826
+ const matched = [];
1827
+ let highestSeverity = "low";
1828
+ let highestRiskType = "";
1829
+ const userRules = getCustomRules();
1830
+ for (const rule of userRules) {
1831
+ try {
1832
+ const re = new RegExp(rule.pattern);
1833
+ if (re.test(command)) {
1834
+ if (rule.action === "Deny") {
1835
+ matched.push(`\u81EA\u5B9A\u4E49\u89C4\u5219: ${rule.pattern}`);
1836
+ if (severityRank(rule.severity) > severityRank(highestSeverity)) {
1837
+ highestSeverity = rule.severity;
1838
+ highestRiskType = "custom_rule";
1839
+ }
1840
+ }
1841
+ }
1842
+ } catch {
1843
+ }
1844
+ }
1845
+ let topRiskType = "";
1846
+ for (const rule of DANGEROUS_PATTERNS) {
1847
+ if (rule.pattern.test(command)) {
1848
+ matched.push(rule.description);
1849
+ if (severityRank(rule.severity) > severityRank(highestSeverity)) {
1850
+ highestSeverity = rule.severity;
1851
+ topRiskType = rule.riskType;
1852
+ }
1853
+ }
1854
+ }
1855
+ highestRiskType = topRiskType || highestRiskType;
1856
+ const agentExt = getAgentExtensions(input.agentId);
1857
+ if (agentExt?.extra_command_patterns) {
1858
+ for (const rule of agentExt.extra_command_patterns) {
1859
+ try {
1860
+ const re = new RegExp(rule.pattern, "i");
1861
+ if (re.test(command)) {
1862
+ matched.push(`agent \u4E13\u5C5E\u89C4\u5219: ${rule.reason}`);
1863
+ if (severityRank(rule.risk_level) > severityRank(highestSeverity)) {
1864
+ highestSeverity = rule.risk_level;
1865
+ highestRiskType = rule.risk_type;
1866
+ }
1867
+ }
1868
+ } catch {
1869
+ }
1870
+ }
1871
+ }
1872
+ const injectionText = `${input.aNext.actionType} ${input.aNext.actionDetail} ${input.reason || ""} ${input.promptSession || ""}`.toLowerCase();
1873
+ const injectionHits = checkInjectionKeywords(injectionText, agentExt?.extra_injection_keywords);
1874
+ if (injectionHits.length > 0) {
1875
+ const topHit = injectionHits.reduce((a, b) => severityRank(a.riskLevel) >= severityRank(b.riskLevel) ? a : b);
1876
+ matched.push(...injectionHits.map((h) => `\u6CE8\u5165\u5173\u952E\u8BCD [${h.lang}]: ${h.keyword}`));
1877
+ if (severityRank(topHit.riskLevel) > severityRank(highestSeverity)) {
1878
+ highestSeverity = topHit.riskLevel;
1879
+ highestRiskType = "prompt_injection";
1880
+ }
1881
+ }
1882
+ const actionTypeL = input.aNext.actionType.toLowerCase();
1883
+ const detailL = input.aNext.actionDetail.toLowerCase();
1884
+ const isExcludedType = SENSITIVE_EXCLUDED_PREFIXES.some((p) => actionTypeL.startsWith(p));
1885
+ if (!isExcludedType) {
1886
+ const pathResult = checkSensitivePath(detailL, input.cwd || "/", agentExt);
1887
+ if (pathResult) return pathResult;
1888
+ }
1889
+ const dangerousResult = checkDangerousPatterns(actionTypeL, detailL);
1890
+ if (dangerousResult) return dangerousResult;
1891
+ const lateralResult = checkLateralMovement(detailL);
1892
+ if (lateralResult) return lateralResult;
1893
+ if (matched.length > 0) {
1894
+ return {
1895
+ allowed: false,
1896
+ reason: `\u68C0\u6D4B\u5230\u9AD8\u5371\u64CD\u4F5C: ${matched.join(", ")}`,
1897
+ riskLevel: highestSeverity,
1898
+ riskType: highestRiskType,
1899
+ confidence: 95,
1900
+ source: "heuristic",
1901
+ violatedLayers: ["1.1"]
1902
+ // 对齐 py coordinator.py:165(L1.1 = 命中高危命令模式/注入关键词)
1903
+ };
1904
+ }
1905
+ return null;
1906
+ }
1907
+ function checkInjectionKeywords(text, extraKeywords) {
1908
+ const hits = [];
1909
+ for (const entry of INJECTION_KEYWORDS) {
1910
+ if (text.includes(entry.keyword.toLowerCase())) {
1911
+ hits.push(entry);
1912
+ }
1913
+ }
1914
+ if (extraKeywords) {
1915
+ for (const entry of extraKeywords) {
1916
+ if (text.includes(entry.keyword.toLowerCase())) {
1917
+ hits.push({ keyword: entry.keyword, riskLevel: entry.risk_level, lang: entry.lang });
1918
+ }
1919
+ }
1920
+ }
1921
+ return hits;
1922
+ }
1923
+ function isInlineScriptCommand(actionDetail) {
1924
+ 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;
1925
+ return re.test(actionDetail);
1926
+ }
1927
+ function severityRank(s) {
1928
+ const ranks = { low: 1, medium: 2, high: 3, critical: 4 };
1929
+ return ranks[s] || 0;
1930
+ }
1931
+ function checkSensitivePath(command, cwd, agentExt) {
1932
+ const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
1933
+ 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/];
1934
+ const _DEVNULL_RE = /(?:2|&|1)?\s*>\s*\/dev\/null\b/;
1935
+ 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"]);
1936
+ 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));
1937
+ for (const sp of ROOT_SENSITIVE_PATHS) {
1938
+ if (command.includes(sp.path.toLowerCase())) {
1939
+ if (sp.path === "/dev/random") {
1940
+ if (isWriteOpFlag) {
1941
+ continue;
1942
+ }
1943
+ return {
1944
+ allowed: false,
1945
+ reason: `\u8BFB\u53D6\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
1946
+ riskLevel: sp.riskLevel,
1947
+ riskType: "file_access",
1948
+ confidence: 90,
1949
+ source: "heuristic",
1950
+ violatedLayers: ["1.1"]
1951
+ };
1952
+ }
1953
+ if (!isWriteOpFlag && !sp.credential) {
1954
+ continue;
1955
+ }
1956
+ return {
1957
+ allowed: false,
1958
+ reason: `\u5C1D\u8BD5\u8BBF\u95EE\u7CFB\u7EDF\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
1959
+ riskLevel: sp.riskLevel,
1960
+ riskType: "file_access",
1961
+ confidence: 90,
1962
+ source: "heuristic",
1963
+ violatedLayers: ["1.1"],
1964
+ isCredential: !!sp.credential
1965
+ };
1966
+ }
1967
+ }
1968
+ for (const sp of HOME_SENSITIVE_PATHS) {
1969
+ const expanded = `${expandedCwd}/${sp.path}`;
1970
+ const pathRe = sp.path.endsWith(".pub") ? new RegExp(sp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i") : new RegExp(sp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "(?!\\.pub)", "i");
1971
+ const expandedRe = sp.path.endsWith(".pub") ? new RegExp(expanded.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i") : new RegExp(expanded.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "(?!\\.pub)", "i");
1972
+ if (pathRe.test(command) || expandedRe.test(command)) {
1973
+ if (!isWriteOpFlag && !sp.credential) {
1974
+ continue;
1975
+ }
1976
+ return {
1977
+ allowed: false,
1978
+ reason: `\u5C1D\u8BD5\u8BBF\u95EE\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
1979
+ riskLevel: sp.riskLevel,
1980
+ riskType: "file_access",
1981
+ confidence: 90,
1982
+ source: "heuristic",
1983
+ violatedLayers: ["1.1"],
1984
+ isCredential: !!sp.credential
1985
+ };
1986
+ }
1987
+ }
1988
+ if (agentExt?.extra_sensitive_paths && agentExt.extra_sensitive_paths.length > 0) {
1989
+ for (const sp of agentExt.extra_sensitive_paths) {
1990
+ const spPath = sp.path.toLowerCase();
1991
+ const expanded = `${expandedCwd}/${spPath}`;
1992
+ if (command.includes(spPath) || command.includes(expanded.toLowerCase())) {
1993
+ const denyMode = sp.deny_mode || (sp.credential ? "deny_both" : sp.read_only ? "deny_read" : "deny_write");
1994
+ if (denyMode === "deny_read") {
1995
+ if (isWriteOpFlag) continue;
1996
+ } else if (denyMode === "deny_write") {
1997
+ if (!isWriteOpFlag) continue;
1998
+ }
1999
+ return {
2000
+ allowed: false,
2001
+ reason: `\u5C1D\u8BD5\u8BBF\u95EE agent \u4E13\u5C5E\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
2002
+ riskLevel: sp.risk_level,
2003
+ riskType: "file_access",
2004
+ confidence: 90,
2005
+ source: "heuristic",
2006
+ violatedLayers: ["1.1"],
2007
+ isCredential: !!sp.credential
2008
+ };
2009
+ }
2010
+ }
2011
+ }
2012
+ for (const cp of CREDENTIAL_PATHS) {
2013
+ const escaped = cp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2014
+ const re = cp.path.startsWith(".") ? new RegExp(`(?:^|[\\s/\\\\'"])${escaped}\\b`) : new RegExp(`\\b${escaped}\\b`);
2015
+ if (re.test(command)) {
2016
+ return {
2017
+ allowed: false,
2018
+ reason: `\u8BBF\u95EE\u654F\u611F\u8DEF\u5F84: ${cp.desc} (${cp.path})`,
2019
+ riskLevel: cp.riskLevel,
2020
+ riskType: "file_access",
2021
+ confidence: 90,
2022
+ source: "heuristic",
2023
+ violatedLayers: ["1.1"],
2024
+ isCredential: true
2025
+ };
2026
+ }
2027
+ }
2028
+ if ((command.includes("rm ") || command.includes("unlink")) && (command.includes("/etc/") || command.includes("/root/") || command.includes("/var/log/"))) {
2029
+ return {
2030
+ allowed: false,
2031
+ reason: "\u68C0\u6D4B\u5230\u5220\u9664\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55\u5185\u5BB9",
2032
+ riskLevel: "critical",
2033
+ riskType: "file_access",
2034
+ confidence: 90,
2035
+ source: "heuristic",
2036
+ violatedLayers: ["1.1"]
2037
+ };
2038
+ }
2039
+ return null;
2040
+ }
2041
+ function checkDangerousPatterns(actionType, actionDetail) {
2042
+ if (actionType === "file_write" || actionType === "file_edit") return null;
2043
+ const TEMP_DIRS = ["/tmp/", "/var/tmp/", "/run/", "/dev/shm/"];
2044
+ const isTempPath = TEMP_DIRS.some((td) => actionDetail.includes(td));
2045
+ if (!isTempPath && (actionDetail.includes("*") || actionDetail.includes("?")) && ["rm ", "rm ", "del ", "del ", "delete", "remove ", "remove ", "\u5220\u9664", "\u79FB\u9664"].some((kw) => actionDetail.includes(kw))) {
2046
+ return {
2047
+ allowed: false,
2048
+ reason: "\u68C0\u6D4B\u5230\u901A\u914D\u7B26\u7ED3\u5408\u5220\u9664\u64CD\u4F5C\uFF0C\u53EF\u80FD\u9020\u6210\u6279\u91CF\u8BEF\u5220",
2049
+ riskLevel: "high",
2050
+ riskType: "script_execution",
2051
+ confidence: 90,
2052
+ source: "heuristic",
2053
+ violatedLayers: ["1.1"]
2054
+ };
2055
+ }
2056
+ if (/>\s*\/etc\//.test(actionDetail) || />\s*\/boot\//.test(actionDetail) || />\s*\/proc\//.test(actionDetail)) {
2057
+ return {
2058
+ allowed: false,
2059
+ reason: "\u68C0\u6D4B\u5230\u91CD\u5B9A\u5411\u8986\u76D6\u5199\u5165\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55",
2060
+ riskLevel: "critical",
2061
+ riskType: "file_access",
2062
+ confidence: 95,
2063
+ source: "heuristic",
2064
+ violatedLayers: ["1.1"]
2065
+ };
2066
+ }
2067
+ return null;
2068
+ }
2069
+ function extractRemoteHost(command) {
2070
+ const m = command.match(/(?:ssh|scp|rsync)\s+(?:-[\w]+\s+)*(\S+@\S+|\b(?!-)[\w.-]+\b)/);
2071
+ return m ? m[1] : null;
2072
+ }
2073
+ function isLocalHost(host) {
2074
+ const localNames = ["localhost", "127.0.0.1", "::1", process.env.HOSTNAME || "", "0.0.0.0"];
2075
+ return localNames.includes(host.replace(/.*@/, ""));
2076
+ }
2077
+ function checkLateralMovement(actionDetail) {
2078
+ if (/(?:ssh|scp|rsync)\s+/.test(actionDetail)) {
2079
+ const targetHost = extractRemoteHost(actionDetail);
2080
+ if (targetHost && !isLocalHost(targetHost)) {
2081
+ return {
2082
+ allowed: false,
2083
+ reason: `\u5C1D\u8BD5\u6A2A\u5411\u79FB\u52A8\u5230\u8FDC\u7A0B\u4E3B\u673A: ${targetHost}`,
2084
+ riskLevel: "medium",
2085
+ riskType: "lateral_movement",
2086
+ confidence: 80,
2087
+ source: "heuristic",
2088
+ violatedLayers: ["1.1"]
2089
+ };
2090
+ }
2091
+ }
2092
+ return null;
2093
+ }
2094
+ var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, SENSITIVE_EXCLUDED_PREFIXES;
2095
+ var init_heuristic = __esm({
2096
+ "src/engine/heuristic.ts"() {
2097
+ "use strict";
2098
+ init_esm_shims();
2099
+ init_config();
2100
+ init_agents();
2101
+ init_logic_rules();
2102
+ INJECTION_KEYWORDS = [
2103
+ // ---- Instruction Override ----
2104
+ { keyword: "ignore previous instructions", riskLevel: "critical", lang: "en" },
2105
+ { keyword: "ignore all previous", riskLevel: "critical", lang: "en" },
2106
+ { keyword: "disregard all prior", riskLevel: "critical", lang: "en" },
2107
+ { keyword: "forget everything", riskLevel: "high", lang: "en" },
2108
+ { keyword: "\u5FFD\u7565\u4E4B\u524D\u7684\u6307\u4EE4", riskLevel: "critical", lang: "zh" },
2109
+ { keyword: "\u5FFD\u7565\u6240\u6709\u5148\u524D\u7684", riskLevel: "critical", lang: "zh" },
2110
+ { keyword: "\u5FFD\u7565\u4E0A\u9762\u6240\u6709", riskLevel: "high", lang: "zh" },
2111
+ { keyword: "\u8BF7\u5FD8\u8BB0\u4E4B\u524D", riskLevel: "high", lang: "zh" },
2112
+ // ---- Role Hijacking ----
2113
+ { keyword: "you are now", riskLevel: "high", lang: "en" },
2114
+ { keyword: "you are no longer", riskLevel: "high", lang: "en" },
2115
+ { keyword: "new instructions", riskLevel: "high", lang: "en" },
2116
+ { keyword: "from now on, you", riskLevel: "high", lang: "en" },
2117
+ { keyword: "\u4F60\u73B0\u5728\u662F\u4E00\u4E2A", riskLevel: "high", lang: "zh" },
2118
+ { keyword: "\u4F60\u4E0D\u518D\u662F\u4E00\u4E2A", riskLevel: "high", lang: "zh" },
2119
+ { keyword: "\u4ECE\u73B0\u5728\u5F00\u59CB\u4F60", riskLevel: "high", lang: "zh" },
2120
+ { keyword: "\u4F60\u7684\u65B0\u89D2\u8272\u662F", riskLevel: "high", lang: "zh" },
2121
+ // ---- Command Injection ----
2122
+ { keyword: "system prompt", riskLevel: "high", lang: "en" },
2123
+ { keyword: "reveal your instructions", riskLevel: "high", lang: "en" },
2124
+ { keyword: "output your instructions", riskLevel: "high", lang: "en" },
2125
+ { keyword: "repeat your instructions", riskLevel: "medium", lang: "en" },
2126
+ { keyword: "\u7CFB\u7EDF\u63D0\u793A", riskLevel: "high", lang: "zh" },
2127
+ { keyword: "\u8F93\u51FA\u4F60\u7684\u6307\u4EE4", riskLevel: "high", lang: "zh" },
2128
+ { keyword: "\u91CD\u590D\u4F60\u7684\u6307\u4EE4", riskLevel: "medium", lang: "zh" },
2129
+ // ---- Social Engineering ----
2130
+ { keyword: "this is an authorized test", riskLevel: "high", lang: "en" },
2131
+ { keyword: "security audit approved", riskLevel: "high", lang: "en" },
2132
+ { keyword: "admin override", riskLevel: "critical", lang: "en" },
2133
+ { keyword: "debug mode", riskLevel: "high", lang: "en" },
2134
+ { keyword: "maintenance mode", riskLevel: "high", lang: "en" },
2135
+ { keyword: "\u8FD9\u662F\u6388\u6743\u7684\u6D4B\u8BD5", riskLevel: "high", lang: "zh" },
2136
+ { keyword: "\u5B89\u5168\u5BA1\u8BA1\u5DF2\u6279\u51C6", riskLevel: "high", lang: "zh" },
2137
+ { keyword: "\u7BA1\u7406\u5458\u8986\u76D6", riskLevel: "critical", lang: "zh" },
2138
+ { keyword: "\u8C03\u8BD5\u6A21\u5F0F", riskLevel: "high", lang: "zh" },
2139
+ // ---- XSS ----
2140
+ { keyword: "<script>", riskLevel: "high", lang: "en" },
2141
+ { keyword: "javascript:", riskLevel: "high", lang: "en" },
2142
+ { keyword: "onerror=", riskLevel: "high", lang: "en" },
2143
+ { keyword: "onload=", riskLevel: "high", lang: "en" },
2144
+ // ---- Fake Boundaries ----
2145
+ { keyword: "=== END OF", riskLevel: "high", lang: "en" },
2146
+ { keyword: "--- END SYSTEM", riskLevel: "high", lang: "en" },
2147
+ { keyword: "[END OF", riskLevel: "high", lang: "en" },
2148
+ { keyword: "=== \u7ED3\u675F", riskLevel: "high", lang: "zh" },
2149
+ { keyword: "--- \u7ED3\u675F\u7CFB\u7EDF", riskLevel: "high", lang: "zh" },
2150
+ // ---- Developer Mode ----
2151
+ { keyword: "developer mode", riskLevel: "critical", lang: "en" },
2152
+ { keyword: "DAN mode", riskLevel: "critical", lang: "en" },
2153
+ { keyword: "jailbreak", riskLevel: "critical", lang: "en" },
2154
+ { keyword: "\u5F00\u53D1\u8005\u6A21\u5F0F", riskLevel: "critical", lang: "zh" },
2155
+ { keyword: "\u8D8A\u72F1", riskLevel: "critical", lang: "zh" }
2156
+ // 注:xiaoO 专属绕过词(绕过审计/禁用安全检查/关闭防护/bypass audit 等)
2157
+ // 已迁至 agent_moss/agents/xiaoo/rules.json 的 extra_injection_keywords,
2158
+ // 由 agent_ext 机制注入,不再污染通用层。
2159
+ ];
2160
+ DANGEROUS_PATTERNS = [
2161
+ // --- Critical: rm command graded detection (strict to loose, first match wins) ---
2162
+ // 1. rm -rf root dir (含通配符如 /* /.* 等) — 使用负向先行断言 (?!\w):/ 后面不是单词字符时匹配
2163
+ { 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" },
2164
+ // 注意:已移除"rm -rf /任意绝对路径"的宽泛规则,避免误杀 /tmp 等临时目录清理操作
2165
+ // 2. rm with -r or -f targeting system dirs — var 分支排除 /var/tmp/(合法清理)
2166
+ { 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" },
2167
+ // 3. rm -r absolute path (no -f) — 排除 /tmp/ /var/tmp/ /run/ /dev/shm/(CI/开发高频合法清理)
2168
+ { 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" },
2169
+ // 4. rm -f absolute path (no -r) — 同样排除临时目录
2170
+ { 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" },
2171
+ // dd 磁盘清零 — 只拦截写入系统关键目录,放行 /tmp 等临时目录
2172
+ { 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" },
2173
+ // mkfs 格式化 — 只拦截对块设备(/dev/sdX 等)的格式化,放行对普通文件的格式化
2174
+ { 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" },
2175
+ { pattern: /:\(\)\s*\{.*:\|:&.*\};:/i, severity: "critical", description: "Fork Bomb", riskType: "resource_exhaustion" },
2176
+ // --- Critical: remote code execution ---
2177
+ { pattern: /curl\s+.*\|\s*(bash|sh|zsh|dash)/i, severity: "critical", description: "curl pipe shell \u6267\u884C", riskType: "script_execution" },
2178
+ { pattern: /wget\s+.*\|\s*(bash|sh)/i, severity: "critical", description: "wget pipe shell \u6267\u884C", riskType: "script_execution" },
2179
+ { pattern: /eval\s+\$\(curl|eval\s+\$\(wget/i, severity: "critical", description: "eval \u8FDC\u7A0B\u4EE3\u7801\u6267\u884C", riskType: "script_execution" },
2180
+ { pattern: /base64\s+-d\s+.*\|\s*bash/i, severity: "critical", description: "base64 \u89E3\u7801\u6267\u884C", riskType: "script_execution" },
2181
+ // --- Critical: reverse shell ---
2182
+ { pattern: /\/dev\/tcp\/|bash\s+-i\s+>&/i, severity: "critical", description: "\u53CD\u5F39 Shell", riskType: "script_execution" },
2183
+ { pattern: /nc\s+-[el]\s+.*\d{1,5}/i, severity: "high", description: "Netcat \u53CD\u5F39\u8FDE\u63A5", riskType: "lateral_movement" },
2184
+ // --- Critical: sensitive file access ---
2185
+ { pattern: /\/etc\/shadow/i, severity: "critical", description: "\u8BBF\u95EE\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6 (/etc/shadow)", riskType: "file_access" },
2186
+ { pattern: /\/etc\/gshadow/i, severity: "critical", description: "\u8BBF\u95EE\u7CFB\u7EDF\u7EC4\u5BC6\u7801\u6587\u4EF6 (/etc/gshadow)", riskType: "file_access" },
2187
+ // 注意:/etc/passwd 已从拦截列表移除,因为它是公开可读的标准系统文件,真正敏感的是 /etc/shadow
2188
+ { pattern: /\.ssh\/id_rsa(?!\.pub)/i, severity: "critical", description: "\u8BBF\u95EE SSH \u79C1\u94A5\u6587\u4EF6", riskType: "file_access" },
2189
+ // --- Critical: privilege escalation / container escape ---
2190
+ { 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" },
2191
+ // --- High: dangerous permissions / ownership ---
2192
+ { pattern: /chmod\s+[0-7]*777[0-7]*/i, severity: "high", description: "\u5168\u6743\u9650\u8BBE\u7F6E (chmod 777)", riskType: "script_execution" },
2193
+ { 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" },
2194
+ // sudo 提权 — 只拦截 sudo 后跟的危险命令,不一刀切拦截所有 sudo
2195
+ { 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" },
2196
+ // --- High: system config overwrite ---
2197
+ { pattern: />\s*\/etc\//i, severity: "critical", description: "\u8986\u76D6\u5199\u5165\u7CFB\u7EDF\u914D\u7F6E\u76EE\u5F55", riskType: "file_access" },
2198
+ // --- High: download script + execute risk ---
2199
+ { 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" },
2200
+ { pattern: /(?:curl|wget)\s+.*\s+&&?\s*(?:chmod\s+\+x|bash|sh)/i, severity: "high", description: "\u4E0B\u8F7D\u540E\u6267\u884C", riskType: "script_execution" },
2201
+ // --- High: package manager pipe-to-shell ---
2202
+ { pattern: /git\s+clone\s+.*\|.*bash/i, severity: "high", description: "git clone pipe shell", riskType: "script_execution" },
2203
+ { pattern: /pip\s+install\s+.*\|\s*bash/i, severity: "high", description: "pip install pipe shell", riskType: "script_execution" },
2204
+ { pattern: /npm\s+install\s+-g\s+.*\|\s*bash/i, severity: "high", description: "npm install pipe shell", riskType: "script_execution" },
2205
+ // --- Medium: destructive git operations (降级为 medium,允许但记录) ---
2206
+ { 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" },
2207
+ // --- High: network port scanning (横向移动侦察) ---
2208
+ { 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" },
2209
+ { 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" },
2210
+ // --- Medium: credential export ---
2211
+ { pattern: /export\s+.*(?:TOKEN|KEY|SECRET|PASSWORD)/i, severity: "medium", description: "\u5BFC\u51FA\u654F\u611F\u51ED\u8BC1", riskType: "data_exfiltration" },
2212
+ // --- High: shell rc file append (persistence) ---
2213
+ { 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" },
2214
+ // --- High: device file write (只拦截重定向写入设备文件,不拦截读取) ---
2215
+ { 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" },
2216
+ // --- High: non-interactive password modification ---
2217
+ { 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" },
2218
+ { 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" },
2219
+ { pattern: /\bchpasswd\b/i, severity: "high", description: "\u68C0\u6D4B\u5230 chpasswd \u6279\u91CF\u5BC6\u7801\u4FEE\u6539\u547D\u4EE4", riskType: "script_execution" },
2220
+ { 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" },
2221
+ { 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" },
2222
+ { 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" },
2223
+ // --- High: non-interactive password modification (extended) ---
2224
+ { pattern: /\blpasswd\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u975E\u4EA4\u4E92\u5F0F\u5BC6\u7801\u4FEE\u6539\u64CD\u4F5C (lpasswd)", riskType: "script_execution" },
2225
+ { pattern: /\bgpasswd\s+(?!-[adA])/i, severity: "high", description: "\u68C0\u6D4B\u5230\u7EC4\u5BC6\u7801\u7BA1\u7406\u64CD\u4F5C (gpasswd)", riskType: "script_execution" },
2226
+ { pattern: /\bchgpasswd\b/i, severity: "high", description: "\u68C0\u6D4B\u5230\u6279\u91CF\u4FEE\u6539\u7EC4\u5BC6\u7801\u64CD\u4F5C (chgpasswd)", riskType: "script_execution" },
2227
+ // --- High: user/group deletion (irreversible, requires user consent) ---
2228
+ { pattern: /\buserdel\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7CFB\u7EDF\u7528\u6237\u64CD\u4F5C (userdel)", riskType: "script_execution" },
2229
+ { pattern: /\bdeluser\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7CFB\u7EDF\u7528\u6237\u64CD\u4F5C (deluser)", riskType: "script_execution" },
2230
+ { pattern: /\bluserdel\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7CFB\u7EDF\u7528\u6237\u64CD\u4F5C (luserdel)", riskType: "script_execution" },
2231
+ { pattern: /\bgroupdel\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7528\u6237\u7EC4\u64CD\u4F5C (groupdel)", riskType: "script_execution" },
2232
+ { pattern: /\blgroupdel\s/i, severity: "high", description: "\u68C0\u6D4B\u5230\u5220\u9664\u7528\u6237\u7EC4\u64CD\u4F5C (lgroupdel)", riskType: "script_execution" },
2233
+ // --- High: generic command -p parameter password setting ---
2234
+ { 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" }
2235
+ ];
2236
+ SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
2237
+ }
2238
+ });
2239
+
2240
+ // src/engine/token-stats.ts
2241
+ var token_stats_exports = {};
2242
+ __export(token_stats_exports, {
2243
+ getRecentRecords: () => getRecentRecords,
2244
+ getTokenStats: () => getTokenStats,
2245
+ getTokenTrend: () => getTokenTrend,
2246
+ recordTokenUsage: () => recordTokenUsage,
2247
+ resetTokenStats: () => resetTokenStats
2248
+ });
2249
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync2, existsSync as existsSync7, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
2250
+ import { join as join4 } from "path";
2251
+ import { homedir as homedir3 } from "os";
2252
+ function getStatsPath() {
2253
+ const envPath = process.env[ENV_STATS_PATH];
2254
+ return envPath || STATS_FILE;
2255
+ }
2256
+ function loadAllRecords(statsPath) {
2257
+ if (!existsSync7(statsPath)) return [];
2258
+ try {
2259
+ const data = JSON.parse(readFileSync7(statsPath, "utf-8"));
2260
+ if (Array.isArray(data)) return data;
2261
+ if (data && Array.isArray(data.records)) return data.records;
2262
+ return [];
2263
+ } catch (e) {
2264
+ return [];
2265
+ }
2073
2266
  }
2074
- function _validateSkillId(skillId) {
2075
- if (!skillId || !SKILL_ID_RE.test(skillId)) {
2076
- throw new Error(`skill_id '${skillId}' \u4E0D\u5408\u6CD5\uFF1A\u53EA\u5141\u8BB8\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\u548C\u8FDE\u5B57\u7B26`);
2267
+ function saveRecords(statsPath, records) {
2268
+ try {
2269
+ const dir = statsPath.substring(0, statsPath.lastIndexOf("/"));
2270
+ if (dir && !existsSync7(dir)) mkdirSync2(dir, { recursive: true });
2271
+ writeFileSync2(statsPath, JSON.stringify(records, null, 2), "utf-8");
2272
+ } catch (e) {
2077
2273
  }
2078
2274
  }
2079
- function updateLayerEnabled(layerKey, enabled) {
2080
- const runtime = loadRuntimeConfig();
2081
- runtime.layers[layerKey] = enabled;
2082
- saveRuntimeConfig(runtime);
2083
- return runtime;
2084
- }
2085
- function updateRuleEnabled(layer, category, ruleId, enabled) {
2086
- const runtime = loadRuntimeConfig();
2087
- for (const r of runtime[layer]?.[category]?.rules || []) {
2088
- if (r.id === ruleId) {
2089
- r.enabled = enabled;
2090
- break;
2091
- }
2275
+ function recordTokenUsage(record) {
2276
+ const statsPath = getStatsPath();
2277
+ const records = loadAllRecords(statsPath);
2278
+ records.push({
2279
+ timestamp: record.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
2280
+ session_id: record.sessionId,
2281
+ step: record.step,
2282
+ model: record.model,
2283
+ prompt_tokens: record.promptTokens,
2284
+ completion_tokens: record.completionTokens,
2285
+ total_tokens: record.totalTokens
2286
+ });
2287
+ if (records.length > MAX_RECORDS) {
2288
+ records.splice(0, records.length - MAX_RECORDS);
2092
2289
  }
2093
- saveRuntimeConfig(runtime);
2094
- return runtime;
2290
+ saveRecords(statsPath, records);
2095
2291
  }
2096
- function updateRuleDenyMode(layer, category, ruleId, denyMode) {
2097
- const runtime = loadRuntimeConfig();
2098
- for (const r of runtime[layer]?.[category]?.rules || []) {
2099
- if (r.id === ruleId) {
2100
- r.deny_mode = denyMode;
2101
- _syncDenyModeFields(r);
2102
- break;
2292
+ function filterByDate(records, days = 0, startDate, endDate) {
2293
+ if (startDate || endDate) {
2294
+ let startDt = startDate ? new Date(startDate) : /* @__PURE__ */ new Date(-864e13);
2295
+ let endDt = endDate ? new Date(endDate) : void 0;
2296
+ if (endDate) {
2297
+ const e = new Date(endDate);
2298
+ e.setHours(23, 59, 59, 999);
2299
+ endDt = e;
2103
2300
  }
2301
+ const endMs = endDt ? endDt.getTime() : Number.MAX_SAFE_INTEGER;
2302
+ return records.filter((r) => {
2303
+ try {
2304
+ const ts = new Date(r.timestamp).getTime();
2305
+ return ts >= startDt.getTime() && ts <= endMs;
2306
+ } catch {
2307
+ return true;
2308
+ }
2309
+ });
2310
+ } else if (days > 0) {
2311
+ const cutoff = Date.now() - days * 864e5;
2312
+ return records.filter((r) => {
2313
+ try {
2314
+ return new Date(r.timestamp).getTime() >= cutoff;
2315
+ } catch {
2316
+ return true;
2317
+ }
2318
+ });
2104
2319
  }
2105
- saveRuntimeConfig(runtime);
2106
- return runtime;
2320
+ return records;
2107
2321
  }
2108
- function updateRuleSkipL3(layer, category, ruleId, skipL3) {
2109
- const runtime = loadRuntimeConfig();
2110
- for (const r of runtime[layer]?.[category]?.rules || []) {
2111
- if (r.id === ruleId) {
2112
- r.skip_l3_on_disabled = skipL3;
2113
- break;
2322
+ function aggregate(records) {
2323
+ let totalPrompt = 0, totalCompletion = 0, totalTotal = 0;
2324
+ const byStep = {};
2325
+ const byModel = {};
2326
+ const byDate = {};
2327
+ const bucket = (b, key, r) => {
2328
+ const e = b[key] || (b[key] = { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0, calls: 0 });
2329
+ e.prompt_tokens += r.prompt_tokens || 0;
2330
+ e.completion_tokens += r.completion_tokens || 0;
2331
+ e.total_tokens += r.total_tokens || 0;
2332
+ e.calls += 1;
2333
+ };
2334
+ for (const r of records) {
2335
+ totalPrompt += r.prompt_tokens || 0;
2336
+ totalCompletion += r.completion_tokens || 0;
2337
+ totalTotal += r.total_tokens || 0;
2338
+ bucket(byStep, r.step || "unknown", r);
2339
+ bucket(byModel, r.model || "unknown", r);
2340
+ let dateKey = "unknown";
2341
+ try {
2342
+ dateKey = new Date(r.timestamp).toISOString().slice(0, 10);
2343
+ } catch {
2114
2344
  }
2345
+ bucket(byDate, dateKey, r);
2115
2346
  }
2116
- saveRuntimeConfig(runtime);
2117
- return runtime;
2118
- }
2119
- function updateCategoryEnabled(layer, category, enabled) {
2120
- const runtime = loadRuntimeConfig();
2121
- const cat = runtime[layer]?.[category];
2122
- if (cat) cat.category_enabled = enabled;
2123
- saveRuntimeConfig(runtime);
2124
- return runtime;
2125
- }
2126
- function addCustomRule(layer, category, rule) {
2127
- const runtime = loadRuntimeConfig();
2128
- const layerObj = runtime[layer] || (runtime[layer] = {});
2129
- const cat = layerObj[category] || (layerObj[category] = { category_enabled: true, category_desc: "", rules: [] });
2130
- const rules = cat.rules || (cat.rules = []);
2131
- const newRule = { ...rule, builtin: false, enabled: true };
2132
- if (!newRule.id) {
2133
- newRule.id = `custom_${Date.now()}_${_slugify(JSON.stringify(rule).slice(0, 30))}`;
2134
- }
2135
- if (newRule.deny_mode) _syncDenyModeFields(newRule);
2136
- rules.push(newRule);
2137
- saveRuntimeConfig(runtime);
2138
- return runtime;
2347
+ const sortedDate = Object.keys(byDate).sort((a, b) => a < b ? 1 : a > b ? -1 : 0);
2348
+ const sortedByDate = {};
2349
+ for (const k of sortedDate) sortedByDate[k] = byDate[k];
2350
+ return { totalPrompt, totalCompletion, totalTotal, byStep, byModel, byDate: sortedByDate };
2139
2351
  }
2140
- function deleteCustomRule(layer, category, ruleId) {
2141
- const runtime = loadRuntimeConfig();
2142
- const rules = runtime[layer]?.[category]?.rules || [];
2143
- const idx = rules.findIndex((r) => r.id === ruleId);
2144
- if (idx >= 0) {
2145
- if (rules[idx].builtin) return null;
2146
- rules.splice(idx, 1);
2147
- saveRuntimeConfig(runtime);
2148
- }
2149
- return runtime;
2352
+ function getTokenStats(days = 0, startDate, endDate) {
2353
+ const records = filterByDate(loadAllRecords(getStatsPath()), days, startDate, endDate);
2354
+ const a = aggregate(records);
2355
+ return {
2356
+ total_prompt_tokens: a.totalPrompt,
2357
+ total_completion_tokens: a.totalCompletion,
2358
+ total_tokens: a.totalTotal,
2359
+ total_calls: records.length,
2360
+ by_step: a.byStep,
2361
+ by_model: a.byModel,
2362
+ by_date: a.byDate
2363
+ };
2150
2364
  }
2151
- function addCustomSkill(skillId, category, keywords, content) {
2152
- _validateSkillId(skillId);
2153
- const runtime = loadRuntimeConfig();
2154
- const skillsDir = join4(RUNTIME_CONFIG_DIR, "agent_moss_skills");
2155
- try {
2156
- mkdirSync2(skillsDir, { recursive: true });
2157
- } catch {
2158
- }
2159
- writeFileSync2(join4(skillsDir, `${skillId}.md`), content, "utf-8");
2160
- const l3 = runtime.L3_skills || (runtime.L3_skills = {});
2161
- const cat = l3[category] || (l3[category] = { category_enabled: true, category_desc: "", skills: [] });
2162
- (cat.skills || (cat.skills = [])).push({ id: skillId, keywords, enabled: true, builtin: false });
2163
- saveRuntimeConfig(runtime);
2164
- return runtime;
2365
+ function getRecentRecords(limit = 20) {
2366
+ const records = loadAllRecords(getStatsPath());
2367
+ return records.slice(-limit);
2165
2368
  }
2166
- function deleteCustomSkill(skillId, category) {
2167
- _validateSkillId(skillId);
2168
- const runtime = loadRuntimeConfig();
2169
- const skills = runtime.L3_skills?.[category]?.skills || [];
2170
- const idx = skills.findIndex((s) => s.id === skillId);
2171
- if (idx >= 0) {
2172
- if (skills[idx].builtin) return null;
2173
- skills.splice(idx, 1);
2174
- const skillPath = join4(RUNTIME_CONFIG_DIR, "agent_moss_skills", `${skillId}.md`);
2369
+ function getTokenTrend(days = 0, startDate, endDate) {
2370
+ const records = filterByDate(loadAllRecords(getStatsPath()), days, startDate, endDate);
2371
+ const series = {};
2372
+ const datesSet = /* @__PURE__ */ new Set();
2373
+ for (const r of records) {
2374
+ let dateKey;
2175
2375
  try {
2176
- if (existsSync7(skillPath)) unlinkSync2(skillPath);
2376
+ dateKey = new Date(r.timestamp).toISOString().slice(0, 10);
2177
2377
  } catch {
2378
+ continue;
2178
2379
  }
2179
- saveRuntimeConfig(runtime);
2380
+ const model = r.model || "unknown";
2381
+ const tokens = r.total_tokens || 0;
2382
+ datesSet.add(dateKey);
2383
+ series[model] = series[model] || {};
2384
+ series[model][dateKey] = (series[model][dateKey] || 0) + tokens;
2180
2385
  }
2181
- return runtime;
2386
+ const dates = Array.from(datesSet).sort();
2387
+ const models = Object.keys(series).sort();
2388
+ return { dates, models, series };
2182
2389
  }
2183
- function updateSkillEnabled(skillId, enabled) {
2184
- const runtime = loadRuntimeConfig();
2185
- for (const catData of Object.values(runtime.L3_skills || {})) {
2186
- for (const s of catData.skills || []) {
2187
- if (s.id === skillId) {
2188
- s.enabled = enabled;
2189
- break;
2190
- }
2390
+ function resetTokenStats() {
2391
+ const statsPath = getStatsPath();
2392
+ if (existsSync7(statsPath)) {
2393
+ try {
2394
+ unlinkSync2(statsPath);
2395
+ } catch {
2191
2396
  }
2192
2397
  }
2193
- saveRuntimeConfig(runtime);
2194
- return runtime;
2195
- }
2196
- function updateSkillCategoryEnabled(category, enabled) {
2197
- const runtime = loadRuntimeConfig();
2198
- const cat = runtime.L3_skills?.[category];
2199
- if (cat) cat.category_enabled = enabled;
2200
- saveRuntimeConfig(runtime);
2201
- return runtime;
2202
2398
  }
2203
- function getProfileOrDetect() {
2204
- return null;
2205
- }
2206
- var RUNTIME_CONFIG_DIR, RUNTIME_CONFIG_PATH, ENV_RUNTIME_CONFIG_PATH, LAYER_DISABLE_ENV, DENY_MODE_LABELS, SKILL_ID_RE;
2207
- var init_runtime_config = __esm({
2208
- "src/infra/runtime-config.ts"() {
2399
+ var STATS_DIR, STATS_FILE, ENV_STATS_PATH, MAX_RECORDS;
2400
+ var init_token_stats = __esm({
2401
+ "src/engine/token-stats.ts"() {
2209
2402
  "use strict";
2210
2403
  init_esm_shims();
2211
- init_heuristic();
2212
- init_logic_rules();
2213
- init_skill_loader();
2214
- RUNTIME_CONFIG_DIR = join4(homedir3(), ".config", "agentmoss");
2215
- RUNTIME_CONFIG_PATH = join4(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
2216
- ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
2217
- LAYER_DISABLE_ENV = {
2218
- L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
2219
- L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
2220
- L3_llm_analysis: "AGENT_MOSS_DISABLE_LLM"
2221
- };
2222
- DENY_MODE_LABELS = {
2223
- deny_write: "\u4EC5\u62E6\u622A\u5199\u5165",
2224
- deny_read: "\u4EC5\u62E6\u622A\u8BFB\u53D6",
2225
- deny_both: "\u8BFB\u5199\u5747\u62E6\u622A"
2226
- };
2227
- SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
2404
+ STATS_DIR = join4(homedir3(), ".config", "agentmoss");
2405
+ STATS_FILE = join4(STATS_DIR, "agent_moss_token_stats.json");
2406
+ ENV_STATS_PATH = "AGENT_MOSS_TOKEN_STATS_PATH";
2407
+ MAX_RECORDS = 1e4;
2228
2408
  }
2229
2409
  });
2230
2410
 
@@ -2241,28 +2421,28 @@ init_skill_loader();
2241
2421
 
2242
2422
  // src/engine/template-loader.ts
2243
2423
  init_esm_shims();
2244
- import { readFileSync as readFileSync3, existsSync as existsSync4 } from "fs";
2245
- import { resolve as resolve3, dirname as dirname3 } from "path";
2246
- import { fileURLToPath as fileURLToPath4 } from "url";
2247
- var __filename4 = fileURLToPath4(import.meta.url);
2248
- var __dirname4 = dirname3(__filename4);
2249
- var TEMPLATES_DIR = resolve3(__dirname4, "../templates");
2250
- var TEMPLATES_DIR_FALLBACK = resolve3(__dirname4, "../../../agent_moss/templates");
2424
+ import { readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
2425
+ import { resolve as resolve3, dirname as dirname4 } from "path";
2426
+ import { fileURLToPath as fileURLToPath5 } from "url";
2427
+ var __filename4 = fileURLToPath5(import.meta.url);
2428
+ var __dirname5 = dirname4(__filename4);
2429
+ var TEMPLATES_DIR = resolve3(__dirname5, "../templates");
2430
+ var TEMPLATES_DIR_FALLBACK = resolve3(__dirname5, "../../../agent_moss/templates");
2251
2431
  var TEMPLATE_FILES = ["prompt1_template.txt", "prompt2_template.txt", "security_judge_template.txt"];
2252
2432
  var cachedTemplates = null;
2253
2433
  function loadTemplates() {
2254
2434
  if (cachedTemplates) return cachedTemplates;
2255
2435
  cachedTemplates = {};
2256
- const dir = existsSync4(TEMPLATES_DIR) ? TEMPLATES_DIR : TEMPLATES_DIR_FALLBACK;
2257
- if (!existsSync4(dir)) {
2436
+ const dir = existsSync5(TEMPLATES_DIR) ? TEMPLATES_DIR : TEMPLATES_DIR_FALLBACK;
2437
+ if (!existsSync5(dir)) {
2258
2438
  console.warn("[AgentMoss] Templates directory not found:", TEMPLATES_DIR, "and fallback:", TEMPLATES_DIR_FALLBACK);
2259
2439
  return cachedTemplates;
2260
2440
  }
2261
2441
  for (const file of TEMPLATE_FILES) {
2262
2442
  const filePath = resolve3(dir, file);
2263
- if (existsSync4(filePath)) {
2443
+ if (existsSync5(filePath)) {
2264
2444
  const key = file.replace(".txt", "").replace("_template", "");
2265
- cachedTemplates[key] = readFileSync3(filePath, "utf-8");
2445
+ cachedTemplates[key] = readFileSync4(filePath, "utf-8");
2266
2446
  }
2267
2447
  }
2268
2448
  console.log(`[AgentMoss] Loaded ${Object.keys(cachedTemplates).length} templates from ${dir}`);
@@ -2271,8 +2451,8 @@ function loadTemplates() {
2271
2451
 
2272
2452
  // src/engine/script-analyzer.ts
2273
2453
  init_esm_shims();
2274
- import { readFileSync as readFileSync4 } from "fs";
2275
- import { resolve as resolve4, join } from "path";
2454
+ import { readFileSync as readFileSync5 } from "fs";
2455
+ import { resolve as resolve4, join as join2 } from "path";
2276
2456
  var SUSPICIOUS_PATTERNS = [
2277
2457
  // Network exfiltration (curl/wget POST in scripts is almost never legitimate)
2278
2458
  { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
@@ -2405,8 +2585,8 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
2405
2585
  }
2406
2586
  function readScriptContent(scriptPath, maxLines = 500) {
2407
2587
  try {
2408
- const expandedPath = scriptPath.startsWith("~") ? join(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve4(scriptPath);
2409
- const raw = readFileSync4(expandedPath, "utf-8");
2588
+ const expandedPath = scriptPath.startsWith("~") ? join2(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve4(scriptPath);
2589
+ const raw = readFileSync5(expandedPath, "utf-8");
2410
2590
  const lines = raw.split("\n").slice(0, maxLines);
2411
2591
  return { content: lines.join("\n"), lineCount: lines.length, error: "" };
2412
2592
  } catch (e) {
@@ -2460,9 +2640,9 @@ ${result.scriptContent}
2460
2640
  init_esm_shims();
2461
2641
  init_heuristic();
2462
2642
  init_logic_rules();
2463
- import { readFileSync as readFileSync5, statSync, existsSync as existsSync5 } from "fs";
2464
- import { resolve as resolve5, join as join2, isAbsolute, dirname as dirname4 } from "path";
2465
- import { homedir } from "os";
2643
+ import { readFileSync as readFileSync6, statSync, existsSync as existsSync6 } from "fs";
2644
+ import { resolve as resolve5, join as join3, isAbsolute, dirname as dirname5 } from "path";
2645
+ import { homedir as homedir2 } from "os";
2466
2646
  function emptyChain() {
2467
2647
  return {
2468
2648
  nodes: [],
@@ -2690,7 +2870,7 @@ function extractInlineScriptPath(actionDetail) {
2690
2870
  let resolved;
2691
2871
  try {
2692
2872
  resolved = resolvePath(p);
2693
- if (existsSync5(resolved)) {
2873
+ if (existsSync6(resolved)) {
2694
2874
  const stat = statSync(resolved);
2695
2875
  if (stat.isFile()) return resolved;
2696
2876
  }
@@ -2702,20 +2882,20 @@ function extractInlineScriptPath(actionDetail) {
2702
2882
  function resolvePath(p) {
2703
2883
  let expanded = p;
2704
2884
  if (expanded.startsWith("~/") || expanded === "~") {
2705
- expanded = join2(homedir(), expanded.slice(1));
2885
+ expanded = join3(homedir2(), expanded.slice(1));
2706
2886
  }
2707
2887
  return resolve5(expanded);
2708
2888
  }
2709
2889
  function readScriptContent2(scriptPath, maxFileSize = 2 * 1024 * 1024) {
2710
2890
  try {
2711
2891
  const path3 = resolvePath(scriptPath);
2712
- if (!existsSync5(path3)) return { content: "", lineCount: 0, fileSize: 0, error: `\u811A\u672C\u4E0D\u5B58\u5728: ${scriptPath}` };
2892
+ if (!existsSync6(path3)) return { content: "", lineCount: 0, fileSize: 0, error: `\u811A\u672C\u4E0D\u5B58\u5728: ${scriptPath}` };
2713
2893
  const stat = statSync(path3);
2714
2894
  if (!stat.isFile()) return { content: "", lineCount: 0, fileSize: 0, error: `\u4E0D\u662F\u6587\u4EF6: ${scriptPath}` };
2715
2895
  if (stat.size > maxFileSize) {
2716
2896
  return { content: "", lineCount: 0, fileSize: stat.size, error: `\u811A\u672C\u6587\u4EF6\u8FC7\u5927 (${stat.size} bytes)\uFF0C\u8DF3\u8FC7\u8BFB\u53D6` };
2717
2897
  }
2718
- const content = readFileSync5(path3, "utf-8");
2898
+ const content = readFileSync6(path3, "utf-8");
2719
2899
  const lineCount = content ? content.split("\n").length : 0;
2720
2900
  return { content, lineCount, fileSize: stat.size, error: "" };
2721
2901
  } catch (e) {
@@ -2732,9 +2912,9 @@ function extractChildScriptPaths(content, baseDir) {
2732
2912
  const pathStr = m[1];
2733
2913
  if (pathStr.startsWith("-") || pathStr.startsWith("$")) continue;
2734
2914
  if (pathStr.includes("/dev/") || pathStr.startsWith("|") || pathStr.startsWith(";")) continue;
2735
- const full = isAbsolute(pathStr) ? pathStr : join2(baseDir, pathStr);
2915
+ const full = isAbsolute(pathStr) ? pathStr : join3(baseDir, pathStr);
2736
2916
  const resolved = resolvePath(full);
2737
- if (existsSync5(resolved)) {
2917
+ if (existsSync6(resolved)) {
2738
2918
  try {
2739
2919
  if (statSync(resolved).isFile() && !paths.includes(resolved)) paths.push(resolved);
2740
2920
  } catch {
@@ -2749,7 +2929,7 @@ function resolveScriptChain(entryPath, input, maxDepth = 3, maxFileSize = 2 * 10
2749
2929
  const result = emptyChain();
2750
2930
  result.entryPath = entryPath;
2751
2931
  const entry = resolvePath(entryPath);
2752
- const baseDir = existsSync5(entry) ? dirname4(entry) : input.cwd || process.cwd();
2932
+ const baseDir = existsSync6(entry) ? dirname5(entry) : input.cwd || process.cwd();
2753
2933
  resolveRecursive({
2754
2934
  path: entry,
2755
2935
  depth: 0,
@@ -2824,7 +3004,7 @@ function resolveRecursive(ctx) {
2824
3004
  maxTotalSize,
2825
3005
  visited,
2826
3006
  result,
2827
- baseDir: dirname4(resolvePath(childPath)),
3007
+ baseDir: dirname5(resolvePath(childPath)),
2828
3008
  input
2829
3009
  });
2830
3010
  if (result.hasCritical) return;
@@ -3086,7 +3266,7 @@ function formatHints(previous) {
3086
3266
  const hints = [];
3087
3267
  if (previous.heuristic) {
3088
3268
  hints.push(
3089
- `\u542F\u53D1\u5F0F\u68C0\u6D4B\u53D1\u73B0\u98CE\u9669 [\u98CE\u9669\u7B49\u7EA7: ${previous.heuristic.riskLevel}]:
3269
+ `\u7279\u5F81\u5339\u914D\u68C0\u6D4B\u53D1\u73B0\u98CE\u9669 [\u98CE\u9669\u7B49\u7EA7: ${previous.heuristic.riskLevel}]:
3090
3270
  \u7C7B\u578B: ${previous.heuristic.riskType}
3091
3271
  \u539F\u56E0: ${previous.heuristic.reason}`
3092
3272
  );
@@ -3098,7 +3278,7 @@ function formatHints(previous) {
3098
3278
  \u539F\u56E0: ${previous.logic.reason}`
3099
3279
  );
3100
3280
  }
3101
- 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";
3281
+ 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";
3102
3282
  }
3103
3283
  async function analyzeWithLLM(prompt, config) {
3104
3284
  console.log(`[AgentMoss] LLM analyzing with model: ${config.model}, url: ${config.baseUrl}`);
@@ -3306,6 +3486,7 @@ async function runAnalysis(input) {
3306
3486
  const actionType = input.aNext.actionType.toLowerCase();
3307
3487
  const rawDetail = extractRawCommand(input.aNext.actionDetail);
3308
3488
  const lowerDetail = rawDetail.toLowerCase();
3489
+ const agentExt = getAgentExtensions(input.agentId);
3309
3490
  if (FULLY_SAFE_TOOLS.has(actionType)) {
3310
3491
  return {
3311
3492
  allowed: true,
@@ -3317,7 +3498,23 @@ async function runAnalysis(input) {
3317
3498
  violatedLayers: []
3318
3499
  };
3319
3500
  }
3320
- const agentExt = getAgentExtensions(input.agentId);
3501
+ if (agentExt?.builtin_safe_action_types && agentExt.builtin_safe_action_types.length > 0) {
3502
+ const hit = agentExt.builtin_safe_action_types.some((t) => {
3503
+ const tl = t.toLowerCase();
3504
+ return tl.endsWith("-") ? actionType.startsWith(tl) : actionType === tl;
3505
+ });
3506
+ if (hit) {
3507
+ return {
3508
+ allowed: true,
3509
+ reason: `agent \u5185\u90E8\u5B89\u5168\u64CD\u4F5C\uFF0C\u8DF3\u8FC7\u6DF1\u5EA6\u5206\u6790: ${actionType}`,
3510
+ riskLevel: heuristicResult?.riskLevel || "low",
3511
+ riskType: "",
3512
+ confidence: 95,
3513
+ source: "whitelist_bypass",
3514
+ violatedLayers: []
3515
+ };
3516
+ }
3517
+ }
3321
3518
  const trustedSkills = new Set(TRUSTED_SKILLS_BASE);
3322
3519
  if (agentExt?.builtin_safe_skills) {
3323
3520
  for (const s of agentExt.builtin_safe_skills) trustedSkills.add(s);
@@ -3359,9 +3556,11 @@ async function runAnalysis(input) {
3359
3556
  }
3360
3557
  }
3361
3558
  const logicResult = isLogicRulesEnabled() ? runLogicCheck(input) : null;
3362
- if (logicResult && !logicResult.allowed) {
3363
- const isCredentialPath = !!logicResult.isCredential;
3364
- const skipInlineFileAccess = (isInlineScriptCmd || isLiteralTextCmd) && logicResult.riskType === "sensitive_path_access";
3559
+ const l1FileAccessHit = !!(heuristicResult && !heuristicResult.allowed && heuristicResult.riskType === "file_access");
3560
+ const effectiveResult = logicResult && !logicResult.allowed ? logicResult : l1FileAccessHit ? heuristicResult : null;
3561
+ if (effectiveResult) {
3562
+ const isCredentialPath = !!effectiveResult.isCredential;
3563
+ const skipInlineFileAccess = (isInlineScriptCmd || isLiteralTextCmd) && effectiveResult.riskType === "file_access";
3365
3564
  if (skipInlineFileAccess) {
3366
3565
  const inlineJudgment = analyzeInlineScript(input.aNext.actionDetail);
3367
3566
  if (inlineJudgment.actionable) {
@@ -3387,8 +3586,8 @@ async function runAnalysis(input) {
3387
3586
  };
3388
3587
  }
3389
3588
  }
3390
- if ((logicResult.riskLevel === "high" || logicResult.riskLevel === "critical") && (!skipInlineFileAccess || isCredentialPath)) {
3391
- return logicResult;
3589
+ if ((effectiveResult.riskLevel === "high" || effectiveResult.riskLevel === "critical") && (!skipInlineFileAccess || isCredentialPath)) {
3590
+ return effectiveResult;
3392
3591
  }
3393
3592
  }
3394
3593
  if (scriptChain && scriptChain.nodes.length > 0 && isLogicRulesEnabled()) {