@kenhkl/agent-moss 0.16.12 → 0.16.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -22,103 +22,14 @@ var init_esm_shims = __esm({
22
22
  }
23
23
  });
24
24
 
25
- // src/package-json.ts
26
- import { createRequire } from "module";
27
- var require2, pkg, version;
28
- var init_package_json = __esm({
29
- "src/package-json.ts"() {
30
- "use strict";
31
- init_esm_shims();
32
- require2 = createRequire(import.meta.url);
33
- pkg = require2("../package.json");
34
- version = pkg.version;
35
- }
36
- });
37
-
38
- // src/models.ts
39
- import { z } from "zod";
40
- var ActionItemSchema, PromptTurnSchema, AnalyzeRequestSchema, AnalyzeResponseSchema, HealthResponseSchema, ReviewRequestSchema, ReviewResponseSchema;
41
- var init_models = __esm({
42
- "src/models.ts"() {
43
- "use strict";
44
- init_esm_shims();
45
- init_package_json();
46
- ActionItemSchema = z.object({
47
- action_type: z.string().describe("Action type, e.g. bash, file_read, file_write"),
48
- action_detail: z.string().describe("Action detail / command content"),
49
- // 工具返回给 agent 的内容。仅 L2 的授权(consent)判定用:判断「用户是否已授权」
50
- // 看的是 ask_user_question 的**回答**,不是提问正文。历史 bug:模型里没有这个字段、
51
- // 路由也没透传,TS 的授权判定退化成「提问正文非空」,等于问过任何问题即已授权。
52
- output: z.string().optional().describe("Tool result / user answer (needed by L2 consent checks)")
53
- });
54
- PromptTurnSchema = z.object({
55
- text: z.string().default("").describe("User prompt text at this turn"),
56
- actions: z.array(ActionItemSchema).default([]).describe("Actions triggered by this prompt, in order, until the next prompt")
57
- });
58
- AnalyzeRequestSchema = z.object({
59
- session_id: z.string().describe("Session / agent unique identifier"),
60
- prompt_session: z.string().default("").describe("User original prompt (most recent user message; backward compat)"),
61
- prompt_history: z.array(PromptTurnSchema).default([]).describe("Full interleaved prompt history (ordered turns, each with its triggered actions). Optional; empty = fall back to prompt_session only."),
62
- action_history: z.array(ActionItemSchema).default([]).describe("Historical action sequence"),
63
- session_history: z.array(z.object({
64
- session_id: z.string(),
65
- action_history: z.array(ActionItemSchema).default([])
66
- })).default([]).describe("Multi-session historical actions (optional): for L2.2 formal engine cross-session combo-chain / sensitive-data-flow verification. Provided by caller (incl. future agentgate)."),
67
- a_next: ActionItemSchema.describe("Next action to be executed"),
68
- reason: z.string().default("").describe("Reason for executing this action"),
69
- os_type: z.string().default("").describe("OS type: 'linux', 'windows', or empty for auto-detect"),
70
- cwd: z.string().default("").describe("Current working directory"),
71
- agent_id: z.string().default("").describe("Caller agent identifier, e.g. 'xiaoo' / 'opencode' / 'opendesk'. Empty = generic mode (no agent-specific rules)."),
72
- metadata: z.record(z.unknown()).optional().describe("Extended metadata")
73
- });
74
- AnalyzeResponseSchema = z.object({
75
- decision: z.enum(["Allow", "Deny"]).describe("Allow or Deny"),
76
- reason: z.string().describe("Decision reason"),
77
- risk_level: z.enum(["low", "medium", "high", "critical"]).default("low"),
78
- risk_type: z.string().default(""),
79
- violated_layers: z.array(z.string()).default([]),
80
- policy: z.string().default(""),
81
- violated_policy: z.string().default(""),
82
- confidence: z.number().int().min(0).max(100).default(100),
83
- analysis_duration_ms: z.number().default(0),
84
- // 组合攻击链反例路径:每步 [session_id, seq, stage, timestamp],含真实发生时间
85
- violation_chain: z.array(z.array(z.union([z.string(), z.number()]).nullable())).default([])
86
- });
87
- HealthResponseSchema = z.object({
88
- status: z.string().default("healthy"),
89
- version: z.string().default(version),
90
- // 归属实例标识:值来自 env AGENT_MOSS_INSTANCE(spawn/systemd 注入),默认空串。
91
- // 多个 agentmoss 同机并存时,调用方按 instance 过滤避免连到别人的进程。
92
- // 空=未配置(独立部署或旧版),调用方可选择兼容或严格过滤。
93
- instance: z.string().default("")
94
- });
95
- ReviewRequestSchema = z.object({
96
- agent_id: z.string().default("").describe("\u76EE\u6807 agent \u6807\u8BC6\uFF08\u51B3\u5B9A\u72B6\u6001\u5E93\u5206\u6876\uFF09"),
97
- db_path: z.string().default("").describe("AgentGate \u672C\u5730\u5B58\u50A8\u8DEF\u5F84\uFF08SQLite \u6587\u4EF6 \u6216 tool_calls JSON \u76EE\u5F55\uFF09\uFF1B\u4E3A\u7A7A\u5219\u7528 sessions"),
98
- sessions: z.array(z.object({
99
- session_id: z.string(),
100
- action_history: z.array(ActionItemSchema).default([])
101
- })).default([]).describe("\u591A session \u5386\u53F2\u52A8\u4F5C\uFF08\u66FF\u4EE3/\u515C\u5E95 db_path\uFF09"),
102
- a_next: ActionItemSchema.optional().describe("\u53EF\u9009\uFF1Aseed \u540E\u8FDE\u540C\u5F53\u524D\u52A8\u4F5C\u4E00\u8D77\u5224\u5B9A"),
103
- is_error: z.boolean().default(false).describe("\u5F53\u524D\u52A8\u4F5C\u6267\u884C\u662F\u5426\u5931\u8D25"),
104
- output_pending: z.boolean().default(false).describe("\u8FD4\u56DE\u524D\u5E8F\u94FE\u6E05\u5355 pending_prefixes")
105
- });
106
- ReviewResponseSchema = z.object({
107
- agent_id: z.string().default(""),
108
- seeded_nodes: z.number().default(0),
109
- verified: z.boolean().default(true),
110
- violations: z.array(z.record(z.unknown())).default([]),
111
- pending_prefixes: z.array(z.record(z.unknown())).default([]),
112
- state_nodes: z.number().default(0),
113
- discovered_patterns: z.array(z.record(z.unknown())).default([]).describe("\u6570\u636E\u9A71\u52A8\u53D1\u73B0\uFF1A\u8DE8 session \u9891\u7E41\u5371\u9669\u7EC4\u5408\uFF08\u9884\u7F6E\u94FE\u76F2\u533A\uFF0CPatternMiner\uFF09")
114
- });
115
- }
116
- });
117
-
118
25
  // src/infra/paths.ts
119
26
  import { join } from "path";
120
27
  import { homedir } from "os";
121
28
  import { readFileSync } from "fs";
29
+ function getRulesRoot() {
30
+ const raw = (process.env[ENV_RULES_ROOT] ?? "").trim();
31
+ return raw ? raw : null;
32
+ }
122
33
  function isRoot() {
123
34
  const getuid = process.getuid;
124
35
  return typeof getuid === "function" && getuid() === 0;
@@ -188,7 +99,7 @@ function getTokenStatsPath() {
188
99
  }
189
100
  return join(getDataRoot(), "agent_moss_token_stats.json");
190
101
  }
191
- var ENV_DATA_ROOT, ENV_INSTANCE, DEFAULT_DATA_ROOT;
102
+ var ENV_DATA_ROOT, ENV_INSTANCE, DEFAULT_DATA_ROOT, ENV_RULES_ROOT;
192
103
  var init_paths = __esm({
193
104
  "src/infra/paths.ts"() {
194
105
  "use strict";
@@ -196,282 +107,53 @@ var init_paths = __esm({
196
107
  ENV_DATA_ROOT = "AGENT_MOSS_DATA_DIR";
197
108
  ENV_INSTANCE = "AGENT_MOSS_INSTANCE";
198
109
  DEFAULT_DATA_ROOT = join(homedir(), ".config", "agentmoss");
110
+ ENV_RULES_ROOT = "AGENT_MOSS_RULES_ROOT";
199
111
  }
200
112
  });
201
113
 
202
- // src/engine/shell-split.ts
203
- function hasCommandSubstitution(text) {
204
- return CMD_SUBSTITUTION.test(text);
205
- }
206
- function splitCommandSegments(command) {
207
- const segments = [];
208
- let cur = "";
209
- let quote = null;
210
- for (let i = 0; i < command.length; i++) {
211
- const ch = command[i];
212
- if (quote) {
213
- cur += ch;
214
- if (ch === quote) quote = null;
215
- continue;
216
- }
217
- if (ch === "'" || ch === '"') {
218
- quote = ch;
219
- cur += ch;
220
- continue;
221
- }
222
- if (/\||&|;|\n/.test(ch)) {
223
- segments.push(cur);
224
- segments.push(ch);
225
- cur = "";
226
- continue;
227
- }
228
- cur += ch;
229
- }
230
- if (cur) segments.push(cur);
231
- return segments.map((s) => s.trim()).filter((s) => s.length > 0 && !/^(?:\|\||&&|[|;&])$/.test(s));
232
- }
233
- function extractFirstCommand(command) {
234
- const segments = splitCommandSegments(command);
235
- return segments.length ? segments[0] : "";
114
+ // src/infra/rules-mode.ts
115
+ function truthy(name) {
116
+ return TRUTHY.has((process.env[name] || "").trim().toLowerCase());
236
117
  }
237
- var CMD_SUBSTITUTION;
238
- var init_shell_split = __esm({
239
- "src/engine/shell-split.ts"() {
240
- "use strict";
241
- init_esm_shims();
242
- CMD_SUBSTITUTION = /\$\(|`|</;
243
- }
244
- });
245
-
246
- // src/engine/inline-analyzer.ts
247
- function isLiteralTextCommand(command) {
248
- const segments = splitCommandSegments(command);
249
- if (segments.length === 0 || segments.some((s) => !ECHO_SEG_RE.test(s))) return false;
250
- if (command.includes(">")) {
251
- let d = command.replace(/<=>|>=|<=|<>|=>/g, " ");
252
- d = d.replace(/(?:2|&|1)?\s*>\s*\/dev\/null\b/g, "");
253
- d = d.replace(new RegExp(`>{1,2}\\s*(['"])([~./][^'"]+)\\1`, "g"), (_m, _q, p) => ">" + p);
254
- d = d.replace(/'[^']*'|"[^"]*"/g, "");
255
- if (/>{1,2}\s*(?!&)\S/.test(d)) {
256
- return false;
118
+ function getRulesMode() {
119
+ if (cached !== null) return cached;
120
+ const raw = (process.env[ENV_RULES_MODE] || "").trim().toLowerCase();
121
+ if (raw) {
122
+ if (VALID_MODES.has(raw)) {
123
+ cached = raw;
124
+ } else {
125
+ console.error(
126
+ `[AgentMoss] ${ENV_RULES_MODE}=${JSON.stringify(raw)} \u975E\u6CD5\uFF08\u53EA\u652F\u6301 hot / cold / builtin\uFF09\uFF1B\u4E3A\u5B89\u5168\u8D77\u89C1\u6309 builtin\uFF08\u4EC5\u5185\u7F6E\u89C4\u5219\uFF09\u5904\u7406`
127
+ );
128
+ cached = "builtin";
257
129
  }
130
+ return cached;
258
131
  }
259
- if (/\|\s*\b(?:tee|tac|install)\b/i.test(command)) {
260
- return false;
261
- }
262
- return true;
263
- }
264
- function extractInlineCode(command) {
265
- const parts = [];
266
- let m;
267
- INLINE_CODE_RE.lastIndex = 0;
268
- while ((m = INLINE_CODE_RE.exec(command)) !== null) {
269
- const code = m[1] ?? m[2];
270
- if (code) parts.push(code);
271
- }
272
- ECHO_STRING_RE.lastIndex = 0;
273
- while ((m = ECHO_STRING_RE.exec(command)) !== null) {
274
- const code = m[1] ?? m[2];
275
- if (code) parts.push(code);
276
- }
277
- return parts.join("\n");
132
+ if (truthy(ENV_RULES_BUILTIN_ONLY)) cached = "builtin";
133
+ else if (truthy(ENV_DISABLE_RULE_HOT_RELOAD)) cached = "cold";
134
+ else cached = "hot";
135
+ return cached;
278
136
  }
279
- function hasWriteExecChain(command) {
280
- return WRITE_EXEC_CHAIN_PATTERNS.some((p) => {
281
- p.lastIndex = 0;
282
- return p.test(command);
283
- });
137
+ function isHotReloadEnabled() {
138
+ return getRulesMode() === "hot";
284
139
  }
285
- function hasDirTraversal(code) {
286
- const lower = code.toLowerCase();
287
- return DIR_TRAVERSAL_KEYWORDS.some((kw) => lower.includes(kw));
140
+ function isRuntimeConfigDisabled() {
141
+ return getRulesMode() !== "hot";
288
142
  }
289
- function hasReadWriteKeyword(code) {
290
- const lower = code.toLowerCase();
291
- return READ_WRITE_KEYWORDS.some((kw) => lower.includes(kw));
143
+ function isBuiltinOnly() {
144
+ return getRulesMode() === "builtin";
292
145
  }
293
- function analyzeInlineScript(command) {
294
- const code = extractInlineCode(command);
295
- if (!code) {
296
- return { actionable: false, allowed: true, reason: "", riskLevel: "low", riskType: "" };
297
- }
298
- if (hasWriteExecChain(command)) {
299
- return {
300
- actionable: true,
301
- allowed: false,
302
- 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",
303
- riskLevel: "critical",
304
- riskType: "script_execution"
305
- };
306
- }
307
- const redirMatch = command.match(REDIRECT_TO_SENSITIVE_RE);
308
- if (redirMatch) {
309
- return {
310
- actionable: true,
311
- allowed: false,
312
- reason: `\u91CD\u5B9A\u5411\u5199\u5165\u654F\u611F\u8DEF\u5F84: ${redirMatch[1]}`,
313
- riskLevel: "critical",
314
- riskType: "file_access"
315
- };
316
- }
317
- const isEchoOrPrintf1 = isLiteralTextCommand(command);
318
- if (!isEchoOrPrintf1 && hasDirTraversal(code) && hasReadWriteKeyword(code)) {
319
- return { actionable: false, allowed: true, reason: "", riskLevel: "low", riskType: "" };
320
- }
321
- const isEchoOrPrintf = isLiteralTextCommand(command);
322
- if (!isEchoOrPrintf && hasReadWriteKeyword(code)) {
323
- return {
324
- actionable: true,
325
- allowed: false,
326
- 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",
327
- riskLevel: "critical",
328
- riskType: "file_access"
329
- };
330
- }
331
- return {
332
- actionable: true,
333
- allowed: true,
334
- reason: "\u654F\u611F\u8DEF\u5F84\u4EC5\u4F5C\u4E3A\u6587\u672C\u5B57\u9762\u91CF\u51FA\u73B0\uFF0C\u672A\u4F34\u968F\u8BFB\u5199\u64CD\u4F5C",
335
- riskLevel: "low",
336
- riskType: "file_access"
337
- };
338
- }
339
- var INLINE_CODE_RE, ECHO_STRING_RE, ECHO_SEG_RE, READ_WRITE_KEYWORDS, WRITE_EXEC_CHAIN_PATTERNS, SHELL_RC_FRAGMENTS, SENSITIVE_PATH_FRAGMENTS, REDIRECT_TO_SENSITIVE_RE, DIR_TRAVERSAL_KEYWORDS;
340
- var init_inline_analyzer = __esm({
341
- "src/engine/inline-analyzer.ts"() {
146
+ var ENV_RULES_MODE, ENV_RULES_BUILTIN_ONLY, ENV_DISABLE_RULE_HOT_RELOAD, TRUTHY, VALID_MODES, cached;
147
+ var init_rules_mode = __esm({
148
+ "src/infra/rules-mode.ts"() {
342
149
  "use strict";
343
150
  init_esm_shims();
344
- init_shell_split();
345
- INLINE_CODE_RE = /(?:python|python3|perl|perl5|ruby|jruby|mruby|node|php|lua)\s+(?:-[a-zA-Z]*[cera][a-zA-Z]*\s+)"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
346
- ECHO_STRING_RE = /\b(?:echo|printf)\s+"([^"\\]*(?:\\.[^"\\]*)*)"|\b(?:echo|printf)\s+'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
347
- ECHO_SEG_RE = /^\s*(?:echo|printf)(?:\s|$)/i;
348
- READ_WRITE_KEYWORDS = [
349
- // 函数调用形态(关键词后跟 '(')
350
- "open(",
351
- "read(",
352
- "write(",
353
- "fopen(",
354
- "system(",
355
- "exec(",
356
- "eval(",
357
- "popen(",
358
- "load(",
359
- "dump(",
360
- // 文件读写(组合形态)
361
- "readline",
362
- "readlines",
363
- "writelines",
364
- "getpass",
365
- "pickle.load",
366
- "yaml.load",
367
- "json.load",
368
- "json.read",
369
- "configparser",
370
- "fread",
371
- "fwrite",
372
- "fgets",
373
- // 子进程执行(组合形态)
374
- "os.system",
375
- "os.popen",
376
- "subprocess",
377
- "os.exec",
378
- "os.spawn",
379
- "import os",
380
- "import subprocess",
381
- "shell=true",
382
- "check_output",
383
- "check_call",
384
- "run(",
385
- // 绕过变体检测
386
- "getattr(",
387
- "__builtins__",
388
- "globals(",
389
- "locals(",
390
- // PHP
391
- "file_get_contents",
392
- "file_put_contents",
393
- "passthru",
394
- "shell_exec",
395
- "proc_open",
396
- // Perl/Ruby
397
- "<stdin>",
398
- "open my",
399
- // Node/JS(2026-09 补,与 Python _READ_WRITE_KEYWORDS 同步):历史缺口 ——
400
- // 识别表里一条 Node API 都没有,于是 node -e "require('fs').readFileSync('/etc/shadow')"
401
- // 判为纯文本字面量 → 放行(真漏报)。
402
- "readfilesync(",
403
- "writefilesync(",
404
- "appendfilesync(",
405
- "unlinksync(",
406
- "rmsync(",
407
- "mkdirsync(",
408
- "execsync(",
409
- "spawnsync(",
410
- "execfile",
411
- "createreadstream",
412
- "createwritestream",
413
- "fs.read",
414
- "fs.write",
415
- "fs.promises",
416
- "require('fs')",
417
- 'require("fs")',
418
- "require('node:fs')",
419
- 'require("node:fs")',
420
- "require('child_process')",
421
- 'require("child_process")',
422
- "require('https')",
423
- 'require("https")'
424
- ];
425
- WRITE_EXEC_CHAIN_PATTERNS = [
426
- // echo/cat/printf "..." > file && bash/sh/python file
427
- /(?:echo|cat|printf)\b.*?>\s*\S+.*(?:&&|;|\|)\s*(?:bash|sh|zsh|python\d?|perl|ruby|node|php)\s/gis,
428
- // ... | bash / | sh / | python(管道喂解释器执行)
429
- /\|\s*(?:bash|sh|zsh|python\d?|perl|ruby|node|php)\b/gi,
430
- // eval "$(cat ...)" / eval "$(echo ...)"
431
- /eval\s+"\$\(/gi,
432
- // xargs bash / xargs sh
433
- /\|\s*xargs\s+(?:bash|sh|zsh)/gi
434
- ];
435
- SHELL_RC_FRAGMENTS = [
436
- ".bashrc",
437
- ".zshrc",
438
- ".profile",
439
- ".bash_profile",
440
- ".zprofile"
441
- ];
442
- SENSITIVE_PATH_FRAGMENTS = [
443
- "/etc/shadow",
444
- "/etc/gshadow",
445
- "/etc/passwd",
446
- "/etc/sudoers",
447
- "/etc/hosts",
448
- "/etc/crontab",
449
- "/etc/systemd/",
450
- "/etc/ssh/sshd_config",
451
- "/boot/",
452
- "/proc/sys/",
453
- "/sys/",
454
- "/dev/mem",
455
- "/dev/kmsg",
456
- ".ssh/id_rsa",
457
- ".ssh/id_ed25519",
458
- ".ssh/authorized_keys",
459
- ...SHELL_RC_FRAGMENTS
460
- ];
461
- REDIRECT_TO_SENSITIVE_RE = new RegExp(`>{1,2}\\s*(?:\\S*/)?(${SENSITIVE_PATH_FRAGMENTS.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})(?:\\s|$)`, "i");
462
- DIR_TRAVERSAL_KEYWORDS = [
463
- // Python 目录遍历
464
- "os.listdir",
465
- "os.walk",
466
- "os.scandir",
467
- "glob.glob",
468
- "glob.iglob",
469
- "pathlib.",
470
- "iterdir",
471
- "rglob",
472
- "scandir(",
473
- "listdir("
474
- ];
151
+ ENV_RULES_MODE = "AGENT_MOSS_RULES_MODE";
152
+ ENV_RULES_BUILTIN_ONLY = "AGENT_MOSS_RULES_BUILTIN_ONLY";
153
+ ENV_DISABLE_RULE_HOT_RELOAD = "AGENT_MOSS_DISABLE_RULE_HOT_RELOAD";
154
+ TRUTHY = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
155
+ VALID_MODES = /* @__PURE__ */ new Set(["hot", "cold", "builtin"]);
156
+ cached = null;
475
157
  }
476
158
  });
477
159
 
@@ -501,13 +183,32 @@ function parseDirEnv(envName) {
501
183
  return raw.split(/[,;]/).map((s) => s.trim()).filter(Boolean).map((s) => resolve(s));
502
184
  }
503
185
  function rulesDirs() {
186
+ if (isBuiltinOnly()) return [];
504
187
  const dirs = [];
505
188
  for (const env of RULES_DIR_ENVS) dirs.push(...parseDirEnv(env));
506
189
  dirs.push(resolve(dataRootDir(), "rules"));
507
190
  return [...new Set(dirs)];
508
191
  }
192
+ function rulesDirCands() {
193
+ const root = getRulesRoot();
194
+ if (root) return [join2(root, "rules")];
195
+ const cands = [
196
+ // 注:`<data_root>/rules/` 不再是"按内置文件名覆盖"的查找位 —— 2026-10 起
197
+ // 规则目录只认 override.json / extra.json(见 rulesDirs())。
198
+ // dist: __dirname=dist/ → dist/rules;__dirname=dist/engine/ → dist/engine/../rules=dist/rules
199
+ resolve(__dirname2, "../rules"),
200
+ resolve(__dirname2, "rules"),
201
+ // dev src: __dirname=ts/src/infra/ → ts/src/infra/../../../agent_moss/rules = 仓库根 agent_moss/rules
202
+ resolve(__dirname2, "../../../agent_moss/rules"),
203
+ // dist 但 __dirname 指向 dist/engine 再往上两级的仓库根 agent_moss/rules
204
+ resolve(__dirname2, "../../../../agent_moss/rules"),
205
+ // 相对当前工作目录的仓库根(兜底,兼容以仓库根为 cwd 运行)
206
+ resolve(process.cwd(), "agent_moss/rules")
207
+ ];
208
+ return cands.filter((c) => existsSync(c));
209
+ }
509
210
  function findRulesFile(name) {
510
- for (const dir of RULES_DIR_CANDS) {
211
+ for (const dir of rulesDirCands()) {
511
212
  const p = resolve(dir, name);
512
213
  if (existsSync(p)) return p;
513
214
  }
@@ -517,10 +218,16 @@ function resolveRulesFile(name) {
517
218
  return findRulesFile(name);
518
219
  }
519
220
  function loadJson(name) {
221
+ if (!isHotReloadEnabled()) {
222
+ const cached2 = frozenJsonCache.get(name);
223
+ if (cached2 !== void 0) return cached2;
224
+ }
520
225
  const p = findRulesFile(name);
521
226
  if (!p) return {};
522
227
  try {
523
- return JSON.parse(readFileSync2(p, "utf-8")) || {};
228
+ const parsed = JSON.parse(readFileSync2(p, "utf-8")) || {};
229
+ if (!isHotReloadEnabled()) frozenJsonCache.set(name, parsed);
230
+ return parsed;
524
231
  } catch {
525
232
  return {};
526
233
  }
@@ -557,8 +264,76 @@ function invalidScenarioRulesReason(data) {
557
264
  function loadL1() {
558
265
  return loadJson("l1_rules.json");
559
266
  }
560
- function loadL2() {
561
- return loadJson("l2_rules.json");
267
+ function loadL2() {
268
+ return loadJson("l2_rules.json");
269
+ }
270
+ function regexFlags(flags) {
271
+ let out = "";
272
+ for (const ch of String(flags ?? "")) if (RE_FLAG_MAP[ch]) out += RE_FLAG_MAP[ch];
273
+ return out;
274
+ }
275
+ function compileRuleRegex(rule, extraFlags = "") {
276
+ return new RegExp(String(rule.pattern), regexFlags(rule.flags) + extraFlags);
277
+ }
278
+ function resolveKeywordSet(group, keywordSets) {
279
+ if (typeof group === "string") return new Set(keywordSets[group.slice(1)] ?? []);
280
+ return new Set(Array.isArray(group) ? group : []);
281
+ }
282
+ function loadFormalRules() {
283
+ return loadJson("formal_rules.json");
284
+ }
285
+ function loadLogicRules() {
286
+ return loadJson("logic_rules.json");
287
+ }
288
+ function loadHeuristicRules() {
289
+ return loadJson("heuristic_rules.json");
290
+ }
291
+ function loadScriptRules() {
292
+ return loadJson("script_rules.json");
293
+ }
294
+ function loadInline() {
295
+ return loadJson("inline_rules.json");
296
+ }
297
+ function loadPlatformTraits(osName) {
298
+ const data = loadJson("platform_traits.json");
299
+ if (!data || Object.keys(data).length === 0) {
300
+ console.error(
301
+ "[AgentMoss] \u274C \u89C4\u5219\u6587\u4EF6 platform_traits.json \u7F3A\u5931\u6216\u635F\u574F \u2014\u2014 \u5E73\u53F0\u7279\u5F81\u56DE\u843D\u4E3A\u7A7A\uFF08\u767D\u540D\u5355\u4E3A\u7A7A\uFF0C\u5224\u5B9A\u4F1A\u53D8\u4E25\uFF09\u3002\u8BF7\u68C0\u67E5\u5305\u5B8C\u6574\u6027\u3002"
302
+ );
303
+ return {};
304
+ }
305
+ const name = resolveTraitsOs(osName);
306
+ const entry = data[name];
307
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
308
+ console.error(`[AgentMoss] platform_traits.json \u91CC\u6CA1\u6709\u53EF\u7528\u7684 ${name} \u6761\u76EE \u2014\u2014 \u5E73\u53F0\u7279\u5F81\u4E3A\u7A7A\u3002`);
309
+ return {};
310
+ }
311
+ const out = {};
312
+ for (const f of TRAITS_FIELDS) if (f in entry) out[f] = entry[f];
313
+ return out;
314
+ }
315
+ function resolveTraitsOs(osName) {
316
+ const data = loadJson("platform_traits.json");
317
+ let name = String(osName || "").trim().toLowerCase() || TRAITS_OS_FALLBACK;
318
+ const seen = /* @__PURE__ */ new Set();
319
+ for (; ; ) {
320
+ if (seen.has(name)) {
321
+ console.error(`[AgentMoss] platform_traits.json \u7684 inherits \u6210\u73AF\uFF08${name}\uFF09\u2014\u2014 \u56DE\u843D linux\u3002`);
322
+ return TRAITS_OS_FALLBACK;
323
+ }
324
+ seen.add(name);
325
+ const entry = data[name];
326
+ if (entry && typeof entry === "object" && !Array.isArray(entry)) {
327
+ const nxt = entry.inherits;
328
+ if (typeof nxt === "string" && nxt.trim().toLowerCase() !== name) {
329
+ name = nxt.trim().toLowerCase();
330
+ continue;
331
+ }
332
+ return name;
333
+ }
334
+ if (name === TRAITS_OS_FALLBACK) return name;
335
+ name = TRAITS_OS_FALLBACK;
336
+ }
562
337
  }
563
338
  function loadL3() {
564
339
  const data = { ...loadJson("l3_rules.json") };
@@ -845,6 +620,7 @@ function scanRuleDirs() {
845
620
  return { override, extra, loaded, ignored };
846
621
  }
847
622
  function rulesCached() {
623
+ if (!isHotReloadEnabled() && _RULES_CACHE) return _RULES_CACHE;
848
624
  const dirs = rulesDirs();
849
625
  const sig = RULES_DIR_ENVS.map((e) => `${e}=${process.env[e] || ""}`).join("|") + "|" + dirs.join("|");
850
626
  const files = [];
@@ -869,79 +645,517 @@ function rulesCached() {
869
645
  _RULES_CACHE = { sig, fp, ...scanRuleDirs() };
870
646
  return _RULES_CACHE;
871
647
  }
872
- function identicalToBuiltin(override) {
873
- const l1 = loadL1();
874
- const l2 = loadL2();
875
- for (const [cat, items] of Object.entries(override)) {
876
- const builtin = l1[cat] ?? l2[cat];
877
- if (!Array.isArray(builtin) || builtin.length !== items.length) return false;
878
- if (JSON.stringify(builtin) !== JSON.stringify(items)) return false;
648
+ function identicalToBuiltin(override) {
649
+ const l1 = loadL1();
650
+ const l2 = loadL2();
651
+ for (const [cat, items] of Object.entries(override)) {
652
+ const builtin = l1[cat] ?? l2[cat];
653
+ if (!Array.isArray(builtin) || builtin.length !== items.length) return false;
654
+ if (JSON.stringify(builtin) !== JSON.stringify(items)) return false;
655
+ }
656
+ return Object.keys(override).length > 0;
657
+ }
658
+ function loadOverrideRules() {
659
+ if (isBuiltinOnly()) return {};
660
+ return rulesCached().override;
661
+ }
662
+ function loadExtraRules() {
663
+ if (isBuiltinOnly()) return {};
664
+ return rulesCached().extra;
665
+ }
666
+ function ruleFilesLoaded() {
667
+ return rulesCached().loaded;
668
+ }
669
+ function ignoredRuleFiles() {
670
+ return rulesCached().ignored;
671
+ }
672
+ var __filename2, __dirname2, OVERRIDE_FILENAME, EXTRA_FILENAME, RULES_DIR_ENVS, frozenJsonCache, SCENARIO_LIST_KEYS, RE_FLAG_MAP, TRAITS_FIELDS, TRAITS_OS_FALLBACK, EXTRA_CATEGORIES, STRUCT_KEYS, EXTRA_FLAT_FIELD, _RULES_CACHE, warnedIgnored;
673
+ var init_rules_loader = __esm({
674
+ "src/infra/rules-loader.ts"() {
675
+ "use strict";
676
+ init_esm_shims();
677
+ init_paths();
678
+ init_rules_mode();
679
+ init_runtime_config_bridge();
680
+ __filename2 = fileURLToPath2(import.meta.url);
681
+ __dirname2 = dirname(__filename2);
682
+ OVERRIDE_FILENAME = "override.json";
683
+ EXTRA_FILENAME = "extra.json";
684
+ RULES_DIR_ENVS = ["AGENT_MOSS_EXTRA_RULES_DIR", "AGENT_MOSS_OVERRIDE_RULES_DIR"];
685
+ frozenJsonCache = /* @__PURE__ */ new Map();
686
+ SCENARIO_LIST_KEYS = ["extra_command_patterns", "extra_injection_keywords", "extra_sensitive_paths"];
687
+ RE_FLAG_MAP = { i: "i", s: "s", m: "m" };
688
+ TRAITS_FIELDS = [
689
+ "write_keywords",
690
+ "read_keywords",
691
+ "write_command_res",
692
+ "read_only_commands",
693
+ "fully_safe_commands",
694
+ "readonly_sensitive_commands",
695
+ "safe_action_types",
696
+ "readonly_sensitive_action_types"
697
+ ];
698
+ TRAITS_OS_FALLBACK = "linux";
699
+ EXTRA_CATEGORIES = {
700
+ dangerous_commands: ["pattern"],
701
+ injection_detection: ["keyword", "lang"],
702
+ sensitive_path_access: ["path"],
703
+ intent_consistency: ["intent_keywords"],
704
+ password_modify_consent: ["pattern"],
705
+ user_deletion_consent: ["pattern"]
706
+ };
707
+ STRUCT_KEYS = ["skills", "ltl_rules", "tools"];
708
+ EXTRA_FLAT_FIELD = [
709
+ [["path"], "sensitive_path_access"],
710
+ [["keyword", "lang"], "injection_detection"],
711
+ [["intent_keywords"], "intent_consistency"],
712
+ [["pattern", "risk_level"], "dangerous_commands"],
713
+ [["pattern", "risk_type"], "dangerous_commands"]
714
+ ];
715
+ _RULES_CACHE = null;
716
+ warnedIgnored = /* @__PURE__ */ new Set();
717
+ }
718
+ });
719
+
720
+ // src/infra/rules-contract.ts
721
+ var rules_contract_exports = {};
722
+ __export(rules_contract_exports, {
723
+ MANIFEST_FILE: () => MANIFEST_FILE,
724
+ REQUIRED_RULE_FILES: () => REQUIRED_RULE_FILES,
725
+ RULES_SCHEMA_VERSION: () => RULES_SCHEMA_VERSION,
726
+ checkRulesAssets: () => checkRulesAssets,
727
+ enforceRulesContract: () => enforceRulesContract,
728
+ formatRulesIntegrity: () => formatRulesIntegrity
729
+ });
730
+ import { createHash } from "crypto";
731
+ import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
732
+ import { join as join3 } from "path";
733
+ function sha256Short(data, n = 12) {
734
+ return createHash("sha256").update(data).digest("hex").slice(0, n);
735
+ }
736
+ function rulesDir() {
737
+ if (getRulesRoot()) return join3(getRulesRoot(), "rules");
738
+ const probe = resolveRulesFile("l1_rules.json");
739
+ return probe ? probe.slice(0, probe.length - "l1_rules.json".length - 1) : null;
740
+ }
741
+ function checkRulesAssets() {
742
+ const rep = {
743
+ ok: true,
744
+ schemaVersion: 0,
745
+ fingerprint: "",
746
+ fileHashes: {},
747
+ errors: [],
748
+ external: getRulesRoot() !== null
749
+ };
750
+ const dir = rulesDir();
751
+ if (!dir) {
752
+ rep.errors.push("\u627E\u4E0D\u5230\u89C4\u5219\u76EE\u5F55\uFF08\u5305\u5185\u89C4\u5219\u6587\u4EF6\u7F3A\u5931\uFF0C\u4E14\u672A\u8BBE AGENT_MOSS_RULES_ROOT\uFF09");
753
+ rep.ok = false;
754
+ return rep;
755
+ }
756
+ const manifestPath = join3(dir, MANIFEST_FILE);
757
+ if (!existsSync2(manifestPath)) {
758
+ rep.errors.push(`\u7F3A\u5C11 ${MANIFEST_FILE}\uFF08\u89C4\u5219\u5305\u5FC5\u987B\u58F0\u660E schema \u7248\u672C\uFF09`);
759
+ } else {
760
+ try {
761
+ const m = JSON.parse(readFileSync3(manifestPath, "utf-8")) || {};
762
+ const ver = m.schema_version;
763
+ if (typeof ver !== "number" || !Number.isInteger(ver)) {
764
+ rep.errors.push(`${MANIFEST_FILE} \u7F3A\u5C11\u6574\u6570 schema_version`);
765
+ } else {
766
+ rep.schemaVersion = ver;
767
+ if (ver !== RULES_SCHEMA_VERSION) {
768
+ rep.errors.push(
769
+ `schema \u7248\u672C\u4E0D\u5339\u914D\uFF1A\u89C4\u5219\u5305\u662F ${ver}\uFF0C\u672C\u5F15\u64CE\u8BA4\u8BC6 ${RULES_SCHEMA_VERSION}\u3002\u8BF7\u5347\u7EA7\u5F15\u64CE\u6216\u6539\u7528\u5BF9\u5E94\u7248\u672C\u7684\u89C4\u5219\u5305\u3002`
770
+ );
771
+ }
772
+ }
773
+ } catch (e) {
774
+ rep.errors.push(`${MANIFEST_FILE} \u8BFB\u53D6/\u89E3\u6790\u5931\u8D25: ${String(e)}`);
775
+ }
776
+ }
777
+ const fpParts = [];
778
+ for (const [name, requiredKeys] of Object.entries(REQUIRED_RULE_FILES)) {
779
+ const p = join3(dir, name);
780
+ if (!existsSync2(p)) {
781
+ rep.errors.push(`\u7F3A\u5C11\u89C4\u5219\u6587\u4EF6 ${name}`);
782
+ rep.fileHashes[name] = "MISSING";
783
+ fpParts.push(`${name}:MISSING`);
784
+ continue;
785
+ }
786
+ let raw = null;
787
+ try {
788
+ raw = readFileSync3(p);
789
+ } catch (e) {
790
+ rep.errors.push(`${name} \u8BFB\u53D6\u5931\u8D25: ${String(e)}`);
791
+ rep.fileHashes[name] = "UNREADABLE";
792
+ fpParts.push(`${name}:UNREADABLE`);
793
+ continue;
794
+ }
795
+ rep.fileHashes[name] = sha256Short(raw);
796
+ fpParts.push(`${name}:${rep.fileHashes[name]}`);
797
+ let data = null;
798
+ try {
799
+ data = JSON.parse(raw.toString("utf-8")) || {};
800
+ } catch (e) {
801
+ rep.errors.push(`${name} \u89E3\u6790\u5931\u8D25: ${String(e)}`);
802
+ rep.fileHashes[name] = "BADJSON";
803
+ continue;
804
+ }
805
+ if (typeof data !== "object" || Array.isArray(data)) {
806
+ rep.errors.push(`${name} \u9876\u5C42\u5FC5\u987B\u662F JSON \u5BF9\u8C61`);
807
+ continue;
808
+ }
809
+ const missing = requiredKeys.filter((k) => !(k in data));
810
+ if (missing.length) rep.errors.push(`${name} \u7F3A\u5C11\u9876\u5C42\u952E: ${missing.join(", ")}`);
811
+ }
812
+ rep.fingerprint = sha256Short([...fpParts].sort().join("\n"));
813
+ rep.ok = rep.errors.length === 0;
814
+ return rep;
815
+ }
816
+ function formatRulesIntegrity(rep) {
817
+ const src = rep.external ? `\u5916\u90E8\u89C4\u5219\u5305 ${getRulesRoot()}` : "\u5305\u5185\u89C4\u5219";
818
+ const head = `\u89C4\u5219\u5305: ${src} | schema v${rep.schemaVersion} | \u6307\u7EB9 ${rep.fingerprint || "-"}`;
819
+ if (rep.ok) return head;
820
+ return `${head} | \u274C ${rep.errors.length} \u4E2A\u95EE\u9898: ${rep.errors.slice(0, 5).join("\uFF1B")}`;
821
+ }
822
+ function enforceRulesContract() {
823
+ const rep = checkRulesAssets();
824
+ if (rep.ok) {
825
+ console.log(`[AgentMoss] ${formatRulesIntegrity(rep)}`);
826
+ return rep;
827
+ }
828
+ const msg = formatRulesIntegrity(rep);
829
+ if (rep.external) {
830
+ console.error(`[AgentMoss] \u274C ${msg}`);
831
+ throw new Error(
832
+ `\u89C4\u5219\u5305\u6821\u9A8C\u5931\u8D25\uFF0C\u62D2\u7EDD\u542F\u52A8\uFF08AGENT_MOSS_RULES_ROOT \u5DF2\u663E\u5F0F\u6307\u5B9A\uFF0C\u4E0D\u5141\u8BB8\u5E26\u7740\u6B8B\u7F3A\u89C4\u5219\u8FD0\u884C\uFF09\uFF1A${rep.errors.join("\uFF1B")}`
833
+ );
834
+ }
835
+ console.error(`[AgentMoss] \u26A0\uFE0F ${msg}\uFF08\u4F7F\u7528\u5305\u5185\u89C4\u5219\uFF0C\u7167\u5E38\u542F\u52A8\uFF1B\u82E5\u662F npm \u5B89\u88C5\u8BF7\u91CD\u88C5\uFF09`);
836
+ return rep;
837
+ }
838
+ var RULES_SCHEMA_VERSION, REQUIRED_RULE_FILES, MANIFEST_FILE;
839
+ var init_rules_contract = __esm({
840
+ "src/infra/rules-contract.ts"() {
841
+ "use strict";
842
+ init_esm_shims();
843
+ init_paths();
844
+ init_rules_loader();
845
+ RULES_SCHEMA_VERSION = 1;
846
+ REQUIRED_RULE_FILES = {
847
+ "l1_rules.json": ["dangerous_commands", "injection_detection", "sensitive_path_access"],
848
+ "l2_rules.json": ["intent_consistency", "password_modify_consent", "user_deletion_consent"],
849
+ "l3_rules.json": [],
850
+ "mc_rules.json": ["rules", "ap_patterns"],
851
+ "tool_registry.json": ["tools"],
852
+ "platform_traits.json": ["linux", "windows"],
853
+ "inline_rules.json": ["read_write_keywords", "dir_traversal_keywords", "write_exec_chain_patterns"],
854
+ "script_rules.json": [
855
+ "suspicious_patterns",
856
+ "keyword_combination_risks",
857
+ "single_keyword_risks",
858
+ "script_exec_patterns"
859
+ ],
860
+ "heuristic_rules.json": [
861
+ "sensitive_excluded_prefixes",
862
+ "search_action_prefixes",
863
+ "loopback_hosts",
864
+ "temp_dirs"
865
+ ],
866
+ "logic_rules.json": ["indirect_file_access", "write_commands", "source_arg_commands"],
867
+ "formal_rules.json": ["privesc", "compute_action_types", "pattern_miner", "output_commands"]
868
+ };
869
+ MANIFEST_FILE = "manifest.json";
870
+ }
871
+ });
872
+
873
+ // src/package-json.ts
874
+ import { createRequire } from "module";
875
+ var require2, pkg, version;
876
+ var init_package_json = __esm({
877
+ "src/package-json.ts"() {
878
+ "use strict";
879
+ init_esm_shims();
880
+ require2 = createRequire(import.meta.url);
881
+ pkg = require2("../package.json");
882
+ version = pkg.version;
883
+ }
884
+ });
885
+
886
+ // src/models.ts
887
+ import { z } from "zod";
888
+ var ActionItemSchema, PromptTurnSchema, AnalyzeRequestSchema, AnalyzeResponseSchema, HealthResponseSchema, ReviewRequestSchema, ReviewResponseSchema;
889
+ var init_models = __esm({
890
+ "src/models.ts"() {
891
+ "use strict";
892
+ init_esm_shims();
893
+ init_package_json();
894
+ ActionItemSchema = z.object({
895
+ action_type: z.string().describe("Action type, e.g. bash, file_read, file_write"),
896
+ action_detail: z.string().describe("Action detail / command content"),
897
+ // 工具返回给 agent 的内容。仅 L2 的授权(consent)判定用:判断「用户是否已授权」
898
+ // 看的是 ask_user_question 的**回答**,不是提问正文。历史 bug:模型里没有这个字段、
899
+ // 路由也没透传,TS 的授权判定退化成「提问正文非空」,等于问过任何问题即已授权。
900
+ output: z.string().optional().describe("Tool result / user answer (needed by L2 consent checks)")
901
+ });
902
+ PromptTurnSchema = z.object({
903
+ text: z.string().default("").describe("User prompt text at this turn"),
904
+ actions: z.array(ActionItemSchema).default([]).describe("Actions triggered by this prompt, in order, until the next prompt")
905
+ });
906
+ AnalyzeRequestSchema = z.object({
907
+ session_id: z.string().describe("Session / agent unique identifier"),
908
+ prompt_session: z.string().default("").describe("User original prompt (most recent user message; backward compat)"),
909
+ prompt_history: z.array(PromptTurnSchema).default([]).describe("Full interleaved prompt history (ordered turns, each with its triggered actions). Optional; empty = fall back to prompt_session only."),
910
+ action_history: z.array(ActionItemSchema).default([]).describe("Historical action sequence"),
911
+ session_history: z.array(z.object({
912
+ session_id: z.string(),
913
+ action_history: z.array(ActionItemSchema).default([])
914
+ })).default([]).describe("Multi-session historical actions (optional): for L2.2 formal engine cross-session combo-chain / sensitive-data-flow verification. Provided by caller (incl. future agentgate)."),
915
+ a_next: ActionItemSchema.describe("Next action to be executed"),
916
+ reason: z.string().default("").describe("Reason for executing this action"),
917
+ os_type: z.string().default("").describe("OS type: 'linux', 'windows', or empty for auto-detect"),
918
+ cwd: z.string().default("").describe("Current working directory"),
919
+ agent_id: z.string().default("").describe("Caller agent identifier, e.g. 'xiaoo' / 'opencode' / 'opendesk'. Empty = generic mode (no agent-specific rules)."),
920
+ metadata: z.record(z.unknown()).optional().describe("Extended metadata")
921
+ });
922
+ AnalyzeResponseSchema = z.object({
923
+ decision: z.enum(["Allow", "Deny"]).describe("Allow or Deny"),
924
+ reason: z.string().describe("Decision reason"),
925
+ risk_level: z.enum(["low", "medium", "high", "critical"]).default("low"),
926
+ risk_type: z.string().default(""),
927
+ violated_layers: z.array(z.string()).default([]),
928
+ policy: z.string().default(""),
929
+ violated_policy: z.string().default(""),
930
+ confidence: z.number().int().min(0).max(100).default(100),
931
+ analysis_duration_ms: z.number().default(0),
932
+ // 组合攻击链反例路径:每步 [session_id, seq, stage, timestamp],含真实发生时间
933
+ violation_chain: z.array(z.array(z.union([z.string(), z.number()]).nullable())).default([])
934
+ });
935
+ HealthResponseSchema = z.object({
936
+ status: z.string().default("healthy"),
937
+ version: z.string().default(version),
938
+ // 归属实例标识:值来自 env AGENT_MOSS_INSTANCE(spawn/systemd 注入),默认空串。
939
+ // 多个 agentmoss 同机并存时,调用方按 instance 过滤避免连到别人的进程。
940
+ // 空=未配置(独立部署或旧版),调用方可选择兼容或严格过滤。
941
+ instance: z.string().default("")
942
+ });
943
+ ReviewRequestSchema = z.object({
944
+ agent_id: z.string().default("").describe("\u76EE\u6807 agent \u6807\u8BC6\uFF08\u51B3\u5B9A\u72B6\u6001\u5E93\u5206\u6876\uFF09"),
945
+ db_path: z.string().default("").describe("AgentGate \u672C\u5730\u5B58\u50A8\u8DEF\u5F84\uFF08SQLite \u6587\u4EF6 \u6216 tool_calls JSON \u76EE\u5F55\uFF09\uFF1B\u4E3A\u7A7A\u5219\u7528 sessions"),
946
+ sessions: z.array(z.object({
947
+ session_id: z.string(),
948
+ action_history: z.array(ActionItemSchema).default([])
949
+ })).default([]).describe("\u591A session \u5386\u53F2\u52A8\u4F5C\uFF08\u66FF\u4EE3/\u515C\u5E95 db_path\uFF09"),
950
+ a_next: ActionItemSchema.optional().describe("\u53EF\u9009\uFF1Aseed \u540E\u8FDE\u540C\u5F53\u524D\u52A8\u4F5C\u4E00\u8D77\u5224\u5B9A"),
951
+ is_error: z.boolean().default(false).describe("\u5F53\u524D\u52A8\u4F5C\u6267\u884C\u662F\u5426\u5931\u8D25"),
952
+ output_pending: z.boolean().default(false).describe("\u8FD4\u56DE\u524D\u5E8F\u94FE\u6E05\u5355 pending_prefixes")
953
+ });
954
+ ReviewResponseSchema = z.object({
955
+ agent_id: z.string().default(""),
956
+ seeded_nodes: z.number().default(0),
957
+ verified: z.boolean().default(true),
958
+ violations: z.array(z.record(z.unknown())).default([]),
959
+ pending_prefixes: z.array(z.record(z.unknown())).default([]),
960
+ state_nodes: z.number().default(0),
961
+ discovered_patterns: z.array(z.record(z.unknown())).default([]).describe("\u6570\u636E\u9A71\u52A8\u53D1\u73B0\uFF1A\u8DE8 session \u9891\u7E41\u5371\u9669\u7EC4\u5408\uFF08\u9884\u7F6E\u94FE\u76F2\u533A\uFF0CPatternMiner\uFF09")
962
+ });
963
+ }
964
+ });
965
+
966
+ // src/engine/shell-split.ts
967
+ function hasCommandSubstitution(text) {
968
+ return CMD_SUBSTITUTION.test(text);
969
+ }
970
+ function splitCommandSegments(command) {
971
+ const segments = [];
972
+ let cur = "";
973
+ let quote = null;
974
+ for (let i = 0; i < command.length; i++) {
975
+ const ch = command[i];
976
+ if (quote) {
977
+ cur += ch;
978
+ if (ch === quote) quote = null;
979
+ continue;
980
+ }
981
+ if (ch === "'" || ch === '"') {
982
+ quote = ch;
983
+ cur += ch;
984
+ continue;
985
+ }
986
+ if (/\||&|;|\n/.test(ch)) {
987
+ segments.push(cur);
988
+ segments.push(ch);
989
+ cur = "";
990
+ continue;
991
+ }
992
+ cur += ch;
993
+ }
994
+ if (cur) segments.push(cur);
995
+ return segments.map((s) => s.trim()).filter((s) => s.length > 0 && !/^(?:\|\||&&|[|;&])$/.test(s));
996
+ }
997
+ function extractFirstCommand(command) {
998
+ const segments = splitCommandSegments(command);
999
+ return segments.length ? segments[0] : "";
1000
+ }
1001
+ var CMD_SUBSTITUTION;
1002
+ var init_shell_split = __esm({
1003
+ "src/engine/shell-split.ts"() {
1004
+ "use strict";
1005
+ init_esm_shims();
1006
+ CMD_SUBSTITUTION = /\$\(|`|</;
1007
+ }
1008
+ });
1009
+
1010
+ // src/engine/inline-analyzer.ts
1011
+ function buildChainRegex(pattern, flags) {
1012
+ return new RegExp(pattern, (flags || "") + "g");
1013
+ }
1014
+ function isLiteralTextCommand(command) {
1015
+ const segments = splitCommandSegments(command);
1016
+ if (segments.length === 0 || segments.some((s) => !ECHO_SEG_RE.test(s))) return false;
1017
+ if (command.includes(">")) {
1018
+ let d = command.replace(/<=>|>=|<=|<>|=>/g, " ");
1019
+ d = d.replace(/(?:2|&|1)?\s*>\s*\/dev\/null\b/g, "");
1020
+ d = d.replace(new RegExp(`>{1,2}\\s*(['"])([~./][^'"]+)\\1`, "g"), (_m, _q, p) => ">" + p);
1021
+ d = d.replace(/'[^']*'|"[^"]*"/g, "");
1022
+ if (/>{1,2}\s*(?!&)\S/.test(d)) {
1023
+ return false;
1024
+ }
1025
+ }
1026
+ if (/\|\s*\b(?:tee|tac|install)\b/i.test(command)) {
1027
+ return false;
1028
+ }
1029
+ return true;
1030
+ }
1031
+ function extractInlineCode(command) {
1032
+ const parts = [];
1033
+ let m;
1034
+ INLINE_CODE_RE.lastIndex = 0;
1035
+ while ((m = INLINE_CODE_RE.exec(command)) !== null) {
1036
+ const code = m[1] ?? m[2];
1037
+ if (code) parts.push(code);
879
1038
  }
880
- return Object.keys(override).length > 0;
1039
+ ECHO_STRING_RE.lastIndex = 0;
1040
+ while ((m = ECHO_STRING_RE.exec(command)) !== null) {
1041
+ const code = m[1] ?? m[2];
1042
+ if (code) parts.push(code);
1043
+ }
1044
+ return parts.join("\n");
881
1045
  }
882
- function loadOverrideRules() {
883
- return rulesCached().override;
1046
+ function hasWriteExecChain(command) {
1047
+ return WRITE_EXEC_CHAIN_PATTERNS.some((p) => {
1048
+ p.lastIndex = 0;
1049
+ return p.test(command);
1050
+ });
884
1051
  }
885
- function loadExtraRules() {
886
- return rulesCached().extra;
1052
+ function hasDirTraversal(code) {
1053
+ const lower = code.toLowerCase();
1054
+ return DIR_TRAVERSAL_KEYWORDS.some((kw) => lower.includes(kw));
887
1055
  }
888
- function ruleFilesLoaded() {
889
- return rulesCached().loaded;
1056
+ function hasReadWriteKeyword(code) {
1057
+ const lower = code.toLowerCase();
1058
+ return READ_WRITE_KEYWORDS.some((kw) => lower.includes(kw));
890
1059
  }
891
- function ignoredRuleFiles() {
892
- return rulesCached().ignored;
1060
+ function analyzeInlineScript(command) {
1061
+ const code = extractInlineCode(command);
1062
+ if (!code) {
1063
+ return { actionable: false, allowed: true, reason: "", riskLevel: "low", riskType: "" };
1064
+ }
1065
+ if (hasWriteExecChain(command)) {
1066
+ return {
1067
+ actionable: true,
1068
+ allowed: false,
1069
+ 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",
1070
+ riskLevel: "critical",
1071
+ riskType: "script_execution"
1072
+ };
1073
+ }
1074
+ const redirMatch = command.match(REDIRECT_TO_SENSITIVE_RE);
1075
+ if (redirMatch) {
1076
+ return {
1077
+ actionable: true,
1078
+ allowed: false,
1079
+ reason: `\u91CD\u5B9A\u5411\u5199\u5165\u654F\u611F\u8DEF\u5F84: ${redirMatch[1]}`,
1080
+ riskLevel: "critical",
1081
+ riskType: "file_access"
1082
+ };
1083
+ }
1084
+ const isEchoOrPrintf1 = isLiteralTextCommand(command);
1085
+ if (!isEchoOrPrintf1 && hasDirTraversal(code) && hasReadWriteKeyword(code)) {
1086
+ return { actionable: false, allowed: true, reason: "", riskLevel: "low", riskType: "" };
1087
+ }
1088
+ const isEchoOrPrintf = isLiteralTextCommand(command);
1089
+ if (!isEchoOrPrintf && hasReadWriteKeyword(code)) {
1090
+ return {
1091
+ actionable: true,
1092
+ allowed: false,
1093
+ 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",
1094
+ riskLevel: "critical",
1095
+ riskType: "file_access"
1096
+ };
1097
+ }
1098
+ return {
1099
+ actionable: true,
1100
+ allowed: true,
1101
+ reason: "\u654F\u611F\u8DEF\u5F84\u4EC5\u4F5C\u4E3A\u6587\u672C\u5B57\u9762\u91CF\u51FA\u73B0\uFF0C\u672A\u4F34\u968F\u8BFB\u5199\u64CD\u4F5C",
1102
+ riskLevel: "low",
1103
+ riskType: "file_access"
1104
+ };
893
1105
  }
894
- var __filename2, __dirname2, OVERRIDE_FILENAME, EXTRA_FILENAME, RULES_DIR_ENVS, RULES_DIR_CANDS, SCENARIO_LIST_KEYS, EXTRA_CATEGORIES, STRUCT_KEYS, EXTRA_FLAT_FIELD, _RULES_CACHE, warnedIgnored;
895
- var init_rules_loader = __esm({
896
- "src/infra/rules-loader.ts"() {
1106
+ var INLINE_RULES, INLINE_CODE_RE, ECHO_STRING_RE, ECHO_SEG_RE, READ_WRITE_KEYWORDS, WRITE_EXEC_CHAIN_PATTERNS, _REDIRECT_PATHS, SHELL_RC_FRAGMENTS, SENSITIVE_PATH_FRAGMENTS, REDIRECT_TO_SENSITIVE_RE, DIR_TRAVERSAL_KEYWORDS;
1107
+ var init_inline_analyzer = __esm({
1108
+ "src/engine/inline-analyzer.ts"() {
897
1109
  "use strict";
898
1110
  init_esm_shims();
899
- init_paths();
900
- init_runtime_config_bridge();
901
- __filename2 = fileURLToPath2(import.meta.url);
902
- __dirname2 = dirname(__filename2);
903
- OVERRIDE_FILENAME = "override.json";
904
- EXTRA_FILENAME = "extra.json";
905
- RULES_DIR_ENVS = ["AGENT_MOSS_EXTRA_RULES_DIR", "AGENT_MOSS_OVERRIDE_RULES_DIR"];
906
- RULES_DIR_CANDS = (() => {
907
- const cands = [
908
- // 注:`<data_root>/rules/` 不再是"按内置文件名覆盖"的查找位 —— 2026-10 起
909
- // 规则目录只认 override.json / extra.json(见 rulesDirs())。
910
- // dist: __dirname=dist/ → dist/rules;__dirname=dist/engine/ → dist/engine/../rules=dist/rules
911
- resolve(__dirname2, "../rules"),
912
- resolve(__dirname2, "rules"),
913
- // dev src: __dirname=ts/src/infra/ → ts/src/infra/../../../agent_moss/rules = 仓库根 agent_moss/rules
914
- resolve(__dirname2, "../../../agent_moss/rules"),
915
- // dist 但 __dirname 指向 dist/engine 再往上两级的仓库根 agent_moss/rules
916
- resolve(__dirname2, "../../../../agent_moss/rules"),
917
- // 相对当前工作目录的仓库根(兜底,兼容以仓库根为 cwd 运行)
918
- resolve(process.cwd(), "agent_moss/rules")
919
- ];
920
- return cands.filter((c) => existsSync(c));
921
- })();
922
- SCENARIO_LIST_KEYS = ["extra_command_patterns", "extra_injection_keywords", "extra_sensitive_paths"];
923
- EXTRA_CATEGORIES = {
924
- dangerous_commands: ["pattern"],
925
- injection_detection: ["keyword", "lang"],
926
- sensitive_path_access: ["path"],
927
- intent_consistency: ["intent_keywords"],
928
- password_modify_consent: ["pattern"],
929
- user_deletion_consent: ["pattern"]
930
- };
931
- STRUCT_KEYS = ["skills", "ltl_rules", "tools"];
932
- EXTRA_FLAT_FIELD = [
933
- [["path"], "sensitive_path_access"],
934
- [["keyword", "lang"], "injection_detection"],
935
- [["intent_keywords"], "intent_consistency"],
936
- [["pattern", "risk_level"], "dangerous_commands"],
937
- [["pattern", "risk_type"], "dangerous_commands"]
1111
+ init_shell_split();
1112
+ init_rules_loader();
1113
+ INLINE_RULES = loadInline();
1114
+ if (!INLINE_RULES || Object.keys(INLINE_RULES).length === 0) {
1115
+ console.error(
1116
+ "[AgentMoss] \u274C \u89C4\u5219\u6587\u4EF6 inline_rules.json \u7F3A\u5931\u6216\u635F\u574F \u2014\u2014 \u5185\u8054\u811A\u672C\u5224\u5B9A\u8BCD\u8868\u4E3A\u7A7A\uFF0C\u5185\u8054\u811A\u672C\u4E2D\u7684\u654F\u611F\u8DEF\u5F84\u8BFB\u5199\u5C06\u6309\u300C\u6587\u672C\u5B57\u9762\u91CF\u300D\u653E\u884C\uFF08\u6BD4\u6B63\u5E38\u66F4\u677E\uFF09\u3002\u8BF7\u68C0\u67E5\u5305\u5B8C\u6574\u6027\u3002"
1117
+ );
1118
+ }
1119
+ INLINE_CODE_RE = /(?:python|python3|perl|perl5|ruby|jruby|mruby|node|php|lua)\s+(?:-[a-zA-Z]*[cera][a-zA-Z]*\s+)"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
1120
+ ECHO_STRING_RE = /\b(?:echo|printf)\s+"([^"\\]*(?:\\.[^"\\]*)*)"|\b(?:echo|printf)\s+'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
1121
+ ECHO_SEG_RE = /^\s*(?:echo|printf)(?:\s|$)/i;
1122
+ READ_WRITE_KEYWORDS = INLINE_RULES.read_write_keywords ?? [];
1123
+ WRITE_EXEC_CHAIN_PATTERNS = (INLINE_RULES.write_exec_chain_patterns ?? []).filter((p) => p && typeof p.pattern === "string").map((p) => buildChainRegex(p.pattern, p.flags ?? ""));
1124
+ _REDIRECT_PATHS = INLINE_RULES.redirect_sensitive_paths ?? {};
1125
+ SHELL_RC_FRAGMENTS = _REDIRECT_PATHS.shell_rc_fragments ?? [];
1126
+ SENSITIVE_PATH_FRAGMENTS = [
1127
+ ..._REDIRECT_PATHS.ts_static ?? [],
1128
+ ...SHELL_RC_FRAGMENTS
938
1129
  ];
939
- _RULES_CACHE = null;
940
- warnedIgnored = /* @__PURE__ */ new Set();
1130
+ REDIRECT_TO_SENSITIVE_RE = new RegExp(`>{1,2}\\s*(?:\\S*/)?(${SENSITIVE_PATH_FRAGMENTS.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})(?:\\s|$)`, "i");
1131
+ DIR_TRAVERSAL_KEYWORDS = INLINE_RULES.dir_traversal_keywords ?? [];
941
1132
  }
942
1133
  });
943
1134
 
944
1135
  // src/engine/platform-traits.ts
1136
+ function buildTraits(osName) {
1137
+ const t = loadPlatformTraits(osName);
1138
+ const strs = (k) => Array.isArray(t[k]) ? t[k] : [];
1139
+ return {
1140
+ writeKeywords: strs("write_keywords"),
1141
+ readKeywords: strs("read_keywords"),
1142
+ writeCommandRes: strs("write_command_res").map((p) => new RegExp(p)),
1143
+ readOnlyCommands: new Set(strs("read_only_commands")),
1144
+ fullySafeCommands: strs("fully_safe_commands").map((p) => new RegExp(p, "i")),
1145
+ readonlySensitiveCommands: strs("readonly_sensitive_commands").map((p) => new RegExp(p, "i")),
1146
+ safeActionTypes: new Set(strs("safe_action_types")),
1147
+ readonlySensitiveActionTypes: new Set(strs("readonly_sensitive_action_types"))
1148
+ };
1149
+ }
1150
+ function traitsFor(key) {
1151
+ const resolved = resolveTraitsOs(key);
1152
+ let t = traitsCache.get(resolved);
1153
+ if (!t) {
1154
+ t = buildTraits(resolved);
1155
+ traitsCache.set(resolved, t);
1156
+ }
1157
+ return t;
1158
+ }
945
1159
  function getPlatformTraits(osType) {
946
1160
  const accepted = acceptedOsValues(osType);
947
1161
  if (accepted.includes("windows")) return WINDOWS_TRAITS;
@@ -949,368 +1163,18 @@ function getPlatformTraits(osType) {
949
1163
  if (accepted.includes("harmony")) return HARMONY_TRAITS;
950
1164
  return LINUX_TRAITS;
951
1165
  }
952
- var LINUX_TRAITS, WINDOWS_TRAITS, MACOS_TRAITS, HARMONY_TRAITS;
1166
+ var traitsCache, LINUX_TRAITS, WINDOWS_TRAITS, MACOS_TRAITS, HARMONY_TRAITS;
953
1167
  var init_platform_traits = __esm({
954
1168
  "src/engine/platform-traits.ts"() {
955
1169
  "use strict";
956
1170
  init_esm_shims();
957
1171
  init_logic_rules();
958
- LINUX_TRAITS = {
959
- writeKeywords: [
960
- "write",
961
- "\u5199\u5165",
962
- "save",
963
- "\u4FDD\u5B58",
964
- "modify",
965
- "\u4FEE\u6539",
966
- "update",
967
- "\u66F4\u65B0",
968
- "delete",
969
- "\u5220\u9664",
970
- "remove",
971
- "\u79FB\u9664",
972
- "create",
973
- "\u521B\u5EFA",
974
- "append",
975
- "\u8FFD\u52A0",
976
- "truncate",
977
- "\u622A\u65AD",
978
- "overwrite",
979
- "\u8986\u76D6",
980
- "replace",
981
- "\u66FF\u6362",
982
- "edit"
983
- ],
984
- readKeywords: [
985
- "read",
986
- "\u8BFB\u53D6",
987
- "cat",
988
- "head",
989
- "tail",
990
- "less",
991
- "more",
992
- "view",
993
- "\u67E5\u770B",
994
- "open",
995
- "\u6253\u5F00",
996
- "load",
997
- "\u52A0\u8F7D",
998
- "grep",
999
- "search",
1000
- "\u641C\u7D22",
1001
- "find",
1002
- "\u67E5\u627E"
1003
- ],
1004
- writeCommandRes: [
1005
- /\brm\b/,
1006
- /\bunlink\b/,
1007
- /\brmdir\b/,
1008
- /\bshred\b/,
1009
- /\bcp\b/,
1010
- /\bmv\b/,
1011
- /\binstall\b/,
1012
- /\btee\b/,
1013
- /\bdd\b/,
1014
- /\bmkfs\b/,
1015
- /\b(fdisk|parted|cfdisk|sfdisk)\b/,
1016
- /\bchmod\b/,
1017
- /\bchown\b/,
1018
- /\btruncate\b/
1019
- ],
1020
- readOnlyCommands: /* @__PURE__ */ new Set([
1021
- "cat",
1022
- "head",
1023
- "tail",
1024
- "less",
1025
- "more",
1026
- "grep",
1027
- "find",
1028
- "awk",
1029
- "sed",
1030
- "sort",
1031
- "uniq",
1032
- "wc",
1033
- "cut",
1034
- "strings",
1035
- "od",
1036
- "xxd",
1037
- "hexdump",
1038
- "tr",
1039
- "file",
1040
- "stat",
1041
- "du",
1042
- "df",
1043
- "ls",
1044
- "dir",
1045
- "tree",
1046
- "nl",
1047
- "tac",
1048
- "rev",
1049
- "echo",
1050
- "printf",
1051
- "lsblk",
1052
- "blockdev",
1053
- "smartctl",
1054
- "udevadm",
1055
- "dmidecode",
1056
- "lscpu",
1057
- "lspci",
1058
- "lsusb",
1059
- "lsmem",
1060
- "lsns",
1061
- "lsof",
1062
- "hwinfo",
1063
- "inxi",
1064
- "fdisk",
1065
- "parted",
1066
- "dmesg",
1067
- "journalctl",
1068
- "systemctl",
1069
- "hostnamectl",
1070
- "localectl",
1071
- "timedatectl",
1072
- "ps",
1073
- "top",
1074
- "free",
1075
- "vmstat",
1076
- "iostat",
1077
- "mpstat",
1078
- "pidof",
1079
- "pgrep",
1080
- "ip",
1081
- "ifconfig",
1082
- "route",
1083
- "ss",
1084
- "netstat",
1085
- "arp",
1086
- "ethtool",
1087
- "nmcli",
1088
- "uname",
1089
- "arch",
1090
- "nproc",
1091
- "getconf",
1092
- "getent",
1093
- "id",
1094
- "whoami",
1095
- "who",
1096
- "w",
1097
- "last",
1098
- "uptime",
1099
- "lsmod",
1100
- "modinfo",
1101
- "sysctl"
1102
- ]),
1103
- fullySafeCommands: [
1104
- /^ls(?:\s|$)/i,
1105
- /^dir(?:\s|$)/i,
1106
- /^pwd(?:\s|$)/i,
1107
- /^which\s+/i,
1108
- /^whereis\s+/i,
1109
- /^realpath\s+/i,
1110
- /^basename\s+/i,
1111
- /^dirname\s+/i,
1112
- /^file\s+/i,
1113
- /^stat\s+/i,
1114
- /^du(?:\s|$)/i,
1115
- /^echo\s+/i,
1116
- /^printf\s+/i,
1117
- /^type\s+/i,
1118
- /^command\s+/i,
1119
- // 系统信息查询(只读,不涉及文件/网络/权限)
1120
- /^whoami(?:\s|$)/i,
1121
- /^id(?:\s|$)/i,
1122
- /^hostname(?:\s|$)/i,
1123
- /^uname(?:\s|$)/i,
1124
- /^date(?:\s|$)/i,
1125
- /^env(?:\s|$)/i,
1126
- /^printenv(?:\s|$)/i,
1127
- /^tty(?:\s|$)/i,
1128
- /^arch(?:\s|$)/i,
1129
- /^uptime(?:\s|$)/i,
1130
- /^groups(?:\s|$)/i,
1131
- /^logname(?:\s|$)/i,
1132
- // 系统资源/硬件信息(只读,不读文件内容、无状态变更选项)
1133
- // 与上面 uptime/uname/id 同性质;缺了它们时开 L3 的部署每查一次磁盘/内存
1134
- // 都要走 LLM(实测 df -h 从 5ms 变成十几秒)。
1135
- /^df(?:\s|$)/i,
1136
- /^free(?:\s|$)/i,
1137
- /^vmstat(?:\s|$)/i,
1138
- /^nproc(?:\s|$)/i,
1139
- /^lscpu(?:\s|$)/i,
1140
- /^lsblk(?:\s|$)/i,
1141
- /^lspci(?:\s|$)/i,
1142
- /^lsusb(?:\s|$)/i,
1143
- /^lsmod(?:\s|$)/i
1144
- ],
1145
- readonlySensitiveCommands: [
1146
- /^cat\s+/i,
1147
- /^head(?:\s|$)/i,
1148
- /^tail(?:\s|$)/i,
1149
- /^less(?:\s|$)/i,
1150
- /^more(?:\s|$)/i,
1151
- /^wc(?:\s|$)/i,
1152
- /^grep(?:\s|$)/i,
1153
- /^find\s+/i,
1154
- /^ag(?:\s|$)/i,
1155
- /^rg(?:\s|$)/i,
1156
- /^awk\s+/i,
1157
- // NEW-FN-P(M03):sed -i/--in-place 是原地写,挡出白名单走 L2 read_before_write。
1158
- // 前瞻放最前(外层 ^sed\s+ 会消费 -i 前的空格)。与 Python profiles/linux.py 对齐。
1159
- /^(?!.*(?:\s|['"])-[a-zA-Z]*i[a-zA-Z]*\b)(?!.*(?:\s|['"])--in-place)sed\s+/i,
1160
- /^cut\s+/i,
1161
- /^sort(?:\s|$)/i,
1162
- /^uniq(?:\s|$)/i,
1163
- // 进程/网络/打开文件信息:只读,但会暴露进程命令行、连接对端、打开的文件
1164
- // 路径,故只跳 L3、保留 L2(L1 敏感路径检测照常生效)。
1165
- /^ps(?:\s|$)/i,
1166
- /^ss(?:\s|$)/i,
1167
- /^netstat(?:\s|$)/i,
1168
- /^lsof(?:\s|$)/i,
1169
- // 只读子命令级白名单:匹配的是「第一段命令」而非首词(extractFirstCommand
1170
- // 按 | && || 切分),所以能精确到子命令。systemctl stop/disable 等不在此列。
1171
- /^systemctl\s+(?:status|is-active|is-enabled|is-failed|list-units|list-unit-files|list-dependencies|show|cat)\b/i,
1172
- /^service\s+\S+\s+status\b/i,
1173
- // journalctl 整族是只读的,但 --vacuum-*/--rotate/--flush/--sync 会删改日志,
1174
- // 用否定前瞻挡掉后再放行。注意前瞻里必须用 .* 扫描**整段**:只写 (?!\s+--...)
1175
- // 的话只看紧跟 journalctl 的第一个参数,
1176
- // `journalctl -u sshd --vacuum-size=100M` 就漏过去了。
1177
- /^journalctl\b(?!.*--(?:vacuum-size|vacuum-time|vacuum-files|rotate|flush|sync)\b)/i
1178
- ]
1179
- };
1180
- WINDOWS_TRAITS = {
1181
- writeKeywords: [
1182
- "write",
1183
- "\u5199\u5165",
1184
- "save",
1185
- "\u4FDD\u5B58",
1186
- "modify",
1187
- "\u4FEE\u6539",
1188
- "update",
1189
- "\u66F4\u65B0",
1190
- "delete",
1191
- "\u5220\u9664",
1192
- "remove",
1193
- "\u79FB\u9664",
1194
- "create",
1195
- "\u521B\u5EFA",
1196
- "append",
1197
- "\u8FFD\u52A0",
1198
- "overwrite",
1199
- "\u8986\u76D6",
1200
- "replace",
1201
- "\u66FF\u6362",
1202
- "set",
1203
- "\u8BBE\u7F6E"
1204
- // 注意:这里**不能放 `reg` / `注册表`**。关键词是子串包含匹配,放进去等于
1205
- // 「只要命令里出现 reg 字样就算写操作」——`reg query`(读)会被判成写,
1206
- // 进而误拦对 deny_write 路径的只读查询。注册表的写判定交给下面的
1207
- // writeCommandRes(只认写子命令)。两端语义必须一致。
1208
- ],
1209
- readKeywords: [
1210
- "read",
1211
- "\u8BFB\u53D6",
1212
- "type",
1213
- "findstr",
1214
- "dir",
1215
- "view",
1216
- "\u67E5\u770B",
1217
- "open",
1218
- "\u6253\u5F00",
1219
- "load",
1220
- "\u52A0\u8F7D",
1221
- "search",
1222
- "\u641C\u7D22",
1223
- "find",
1224
- "\u67E5\u627E",
1225
- "get",
1226
- "\u83B7\u53D6"
1227
- ],
1228
- // Windows 写/删命令:del/copy/move/xcopy/replace/rmdir/icacls/reg delete 等
1229
- writeCommandRes: [
1230
- /\b(del|erase)\b/,
1231
- /\brmdir\b/,
1232
- /\b(rd)\b/,
1233
- /\bcopy\b/,
1234
- /\b(xcopy|robocopy)\b/,
1235
- /\b(move|movefile)\b/,
1236
- /\breplace\b/,
1237
- /\bicacls\b/,
1238
- /\battrib\b/,
1239
- // 注册表:**只认写子命令**(对齐 Python WindowsProfile.write_command_res)。
1240
- // 此前是 /\breg\b/,把 reg query(读)也判成写 → 误拦对 deny_write 路径的只读查询。
1241
- // 写子命令 add/delete/copy/save/restore/import/load/unload;
1242
- // 读子命令 query/compare/export/flags/help 不在此列。
1243
- /\breg(?:\.exe)?\s+(?:add|delete|copy|save|restore|import|load|unload)\b/,
1244
- /\bformat\b/,
1245
- /\bdiskpart\b/,
1246
- /\bmkdir\b/,
1247
- /\bmd\b/,
1248
- /\bsetx\b/,
1249
- /\bwmic\b/
1250
- ],
1251
- // Windows 只读命令(重定向写判定用):type/findstr/more/tree/dir 等
1252
- readOnlyCommands: /* @__PURE__ */ new Set([
1253
- "type",
1254
- "findstr",
1255
- "more",
1256
- "tree",
1257
- "dir",
1258
- "where",
1259
- "ver",
1260
- "cls",
1261
- "echo",
1262
- "help",
1263
- "fc",
1264
- "comp",
1265
- "find",
1266
- "sort",
1267
- "powershell",
1268
- "cmd",
1269
- "netstat",
1270
- "ipconfig",
1271
- "tasklist",
1272
- "systeminfo",
1273
- "driverquery",
1274
- "reg",
1275
- "wmic",
1276
- "whoami",
1277
- "hostname",
1278
- "getmac",
1279
- "query",
1280
- "w32tm",
1281
- "diskpart"
1282
- ]),
1283
- // Windows 完全安全命令(对齐 WindowsProfile.fully_safe_commands)
1284
- fullySafeCommands: [
1285
- /^dir(?:\s|$)/,
1286
- /^where\s+/,
1287
- /^echo\s+/,
1288
- /^cd(?:\s|$)/,
1289
- /^tree(?:\s|$)/,
1290
- /^help(?:\s|$)/,
1291
- /^cls(?:\s|$)/,
1292
- /^ver(?:\s|$)/,
1293
- // 系统信息查询(只读,不读文件内容、无状态变更选项),与 Linux 侧 df/free 对齐
1294
- /^whoami(?:\s|$)/,
1295
- /^hostname(?:\s|$)/,
1296
- /^systeminfo(?:\s|$)/,
1297
- /^tasklist(?:\s|$)/,
1298
- /^getmac(?:\s|$)/,
1299
- /^driverquery(?:\s|$)/,
1300
- /^netstat(?:\s|$)/
1301
- ],
1302
- // Windows 安全但可能访问敏感路径的命令(对齐 WindowsProfile.readonly_sensitive_commands)
1303
- readonlySensitiveCommands: [
1304
- /^type\s+/,
1305
- /^findstr\s+/,
1306
- // 只读子命令级白名单(匹配「第一段命令」而非首词);
1307
- // sc create/delete/start/stop、reg add/delete/set 均不在此列。
1308
- /^sc\s+query\b/,
1309
- /^reg\s+query\b/
1310
- ]
1311
- };
1312
- MACOS_TRAITS = LINUX_TRAITS;
1313
- HARMONY_TRAITS = LINUX_TRAITS;
1172
+ init_rules_loader();
1173
+ traitsCache = /* @__PURE__ */ new Map();
1174
+ LINUX_TRAITS = traitsFor("linux");
1175
+ WINDOWS_TRAITS = traitsFor("windows");
1176
+ MACOS_TRAITS = traitsFor("macos");
1177
+ HARMONY_TRAITS = traitsFor("harmony");
1314
1178
  }
1315
1179
  });
1316
1180
 
@@ -1469,7 +1333,6 @@ function checkIntentConsistency(input) {
1469
1333
  const { aNext, promptSession, promptHistory } = input;
1470
1334
  const actionDetail = aNext.actionDetail.toLowerCase();
1471
1335
  const actionType = aNext.actionType.toLowerCase();
1472
- const EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
1473
1336
  if (EXCLUDED_PREFIXES.some((p) => actionType.startsWith(p))) return null;
1474
1337
  if (INLINE_SCRIPT_RE.test(actionDetail)) return null;
1475
1338
  const intentTexts = collectIntentTexts(promptSession, promptHistory);
@@ -1497,7 +1360,6 @@ function runLogicCheck(input, opts) {
1497
1360
  const actionDetail = aNext.actionDetail.toLowerCase();
1498
1361
  const actionType = aNext.actionType.toLowerCase();
1499
1362
  const resolvedCwd = cwd || "/";
1500
- const EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
1501
1363
  const isExcludedType = EXCLUDED_PREFIXES.some((p) => actionType.startsWith(p));
1502
1364
  if (!opts?.skipReadBeforeWrite) {
1503
1365
  const rbwResult = checkReadBeforeWrite(actionType, actionDetail, actionHistory, input.reason, resolvedCwd, input.osType);
@@ -1609,7 +1471,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
1609
1471
  const isBashLike = actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash";
1610
1472
  const isWrite = isBashLike ? isWriteOperation(actionType, actionDetail, void 0, osType) : getPlatformTraits(osType).writeKeywords.some((kw) => actionType.includes(kw));
1611
1473
  if (!isWrite) return null;
1612
- const WRITE_CMDS = /* @__PURE__ */ new Set(["cp", "mv", "install", "tee", "dd", "truncate", "mkfs", "fdisk", "parted"]);
1474
+ const WRITE_CMDS = new Set(_LOGIC.write_commands ?? []);
1613
1475
  if (isBashLike) {
1614
1476
  const detailNoCompare = actionDetail.replace(/<=>|>=|<=|<>|=>/g, " ");
1615
1477
  const detailNoDevnull = detailNoCompare.replace(DEVNULL_REDIRECT_RE, "");
@@ -1641,7 +1503,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
1641
1503
  }
1642
1504
  if (!hasWriteSeg) return null;
1643
1505
  }
1644
- const SOURCE_ARG_CMDS = /* @__PURE__ */ new Set(["cp", "mv", "install", "rsync"]);
1506
+ const SOURCE_ARG_CMDS = new Set(_LOGIC.source_arg_commands ?? []);
1645
1507
  const REDIRECT_TARGET_RE = new RegExp(`>{1,2}\\s*(?!&)(?:'(/[^']+)'|"(/[^\\"]+)"|(/[^\\s;|&><'\\"]+))`);
1646
1508
  const targetPaths = [];
1647
1509
  for (const seg of splitCommandSegments(actionDetail)) {
@@ -1689,7 +1551,6 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
1689
1551
  }
1690
1552
  }
1691
1553
  }
1692
- const TEMP_DIRS = ["/tmp/", "/var/tmp/", "/dev/shm/", "/run/"];
1693
1554
  let filteredPaths = uniqueTargetPaths;
1694
1555
  if (/\bdd\b/i.test(actionDetail) && /\bif=\/dev\/zero\b/i.test(actionDetail)) {
1695
1556
  filteredPaths = uniqueTargetPaths.filter(
@@ -1790,10 +1651,10 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd, osType) {
1790
1651
  if (!hasTraversal) {
1791
1652
  return null;
1792
1653
  }
1793
- const READ_KEYWORDS = ["read(", "readline", "readlines", "fread", "fgets", "load(", "getpass", "file_get_contents"];
1794
- const WRITE_KEYWORDS2 = ["write(", "writelines", "fwrite", "file_put_contents"];
1795
- const EXEC_KEYWORDS = ["system(", "exec(", "eval(", "popen(", "os.system", "os.popen", "subprocess", "os.exec", "os.spawn", "shell=true", "check_output", "check_call", "run(", "passthru", "shell_exec", "proc_open"];
1796
- const OPEN_KEYWORDS = ["open(", "fopen("];
1654
+ const READ_KEYWORDS = IFA_READ_KEYWORDS;
1655
+ const WRITE_KEYWORDS2 = IFA_WRITE_KEYWORDS;
1656
+ const EXEC_KEYWORDS = IFA_EXEC_KEYWORDS;
1657
+ const OPEN_KEYWORDS = IFA_OPEN_KEYWORDS;
1797
1658
  const code = extractInlineCode(actionDetail);
1798
1659
  if (!code) return null;
1799
1660
  const codeLower = code.toLowerCase();
@@ -1856,7 +1717,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd, osType) {
1856
1717
  violatedLayers: ["L2.1"]
1857
1718
  };
1858
1719
  }
1859
- var ROOT_SENSITIVE_PATHS, HOME_SENSITIVE_PATHS, CREDENTIAL_PATHS, ROOT_SENSITIVE_PATHS_WINDOWS, HOME_SENSITIVE_PATHS_WINDOWS, CREDENTIAL_PATHS_WINDOWS, ROOT_SENSITIVE_PATHS_MACOS, HOME_SENSITIVE_PATHS_MACOS, CREDENTIAL_PATHS_MACOS, ROOT_SENSITIVE_PATHS_HARMONY, HOME_SENSITIVE_PATHS_HARMONY, CREDENTIAL_PATHS_HARMONY, _scopeIndex, NON_WRITE_TOOLS, NON_WRITE_TOOL_PREFIXES, WRITE_KEYWORDS, SED_INPLACE_RE, DEVNULL_REDIRECT_RE, CREATE_KEYWORDS, PASSWORD_MODIFY_PATTERNS, USER_DELETION_PATTERNS, INTENT_DEVIATION_PATTERNS, COMBO_STAGES, COMBO_CHAINS, INLINE_SCRIPT_RE, DIR_TRAVERSAL_KW;
1720
+ var _LOGIC, _HEURISTIC, EXCLUDED_PREFIXES, TEMP_DIRS, ROOT_SENSITIVE_PATHS, HOME_SENSITIVE_PATHS, CREDENTIAL_PATHS, ROOT_SENSITIVE_PATHS_WINDOWS, HOME_SENSITIVE_PATHS_WINDOWS, CREDENTIAL_PATHS_WINDOWS, ROOT_SENSITIVE_PATHS_MACOS, HOME_SENSITIVE_PATHS_MACOS, CREDENTIAL_PATHS_MACOS, ROOT_SENSITIVE_PATHS_HARMONY, HOME_SENSITIVE_PATHS_HARMONY, CREDENTIAL_PATHS_HARMONY, _scopeIndex, NON_WRITE_TOOLS, NON_WRITE_TOOL_PREFIXES, WRITE_KEYWORDS, SED_INPLACE_RE, DEVNULL_REDIRECT_RE, CREATE_KEYWORDS, PASSWORD_MODIFY_PATTERNS, USER_DELETION_PATTERNS, INTENT_DEVIATION_PATTERNS, COMBO_STAGES, COMBO_CHAINS, INLINE_SCRIPT_RE, DIR_TRAVERSAL_KW, _IFA, IFA_READ_KEYWORDS, IFA_WRITE_KEYWORDS, IFA_EXEC_KEYWORDS, IFA_OPEN_KEYWORDS;
1860
1721
  var init_logic_rules = __esm({
1861
1722
  "src/engine/logic-rules.ts"() {
1862
1723
  "use strict";
@@ -1866,6 +1727,10 @@ var init_logic_rules = __esm({
1866
1727
  init_rules_loader();
1867
1728
  init_platform_traits();
1868
1729
  init_shell_split();
1730
+ _LOGIC = loadLogicRules();
1731
+ _HEURISTIC = loadHeuristicRules();
1732
+ EXCLUDED_PREFIXES = _HEURISTIC.sensitive_excluded_prefixes ?? [];
1733
+ TEMP_DIRS = (_HEURISTIC.temp_dirs ?? []).map((d) => d + "/");
1869
1734
  ROOT_SENSITIVE_PATHS = _loadSensitivePathScope("root");
1870
1735
  HOME_SENSITIVE_PATHS = _loadSensitivePathScope("home");
1871
1736
  CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path6, riskLevel, desc, dirDelOnly }) => ({ path: path6, riskLevel, desc, credential: true, dirDelOnly }));
@@ -1886,7 +1751,7 @@ var init_logic_rules = __esm({
1886
1751
  "list_dir",
1887
1752
  "count_text_length",
1888
1753
  "search",
1889
- // filemgr 只读三件套(globfiles 在 coordinator FULLY_SAFE_TOOLS 提前放行,此处兜底防御非 coordinator 入口)
1754
+ // filemgr 只读三件套(globfiles 在 platform-traits 的 safeActionTypes 提前放行,此处兜底防御非 coordinator 入口)
1890
1755
  "filemgr-globfiles",
1891
1756
  "filemgr-readfile",
1892
1757
  "filemgr-grepfiles",
@@ -1898,7 +1763,7 @@ var init_logic_rules = __esm({
1898
1763
  "tail",
1899
1764
  "cat"
1900
1765
  ]);
1901
- NON_WRITE_TOOL_PREFIXES = ["memory-", "skill-manager-", "mailbox-", "app-"];
1766
+ NON_WRITE_TOOL_PREFIXES = _LOGIC.non_write_tool_prefixes ?? [];
1902
1767
  WRITE_KEYWORDS = ["write", "\u5199\u5165", "save", "\u4FDD\u5B58", "modify", "\u4FEE\u6539", "update", "\u66F4\u65B0", "delete", "\u5220\u9664", "remove", "\u79FB\u9664", "create", "\u521B\u5EFA", "append", "\u8FFD\u52A0", "truncate", "\u622A\u65AD", "overwrite", "\u8986\u76D6", "replace", "\u66FF\u6362", "edit"];
1903
1768
  SED_INPLACE_RE = /\bsed\s+(?:-\S+\s+)*(?:-[a-zA-Z]*i[a-zA-Z]*|--in-place)\b/i;
1904
1769
  DEVNULL_REDIRECT_RE = /(?:2|&|1)?\s*>\s*\/dev\/null\b/g;
@@ -1939,25 +1804,24 @@ var init_logic_rules = __esm({
1939
1804
  }));
1940
1805
  })();
1941
1806
  INLINE_SCRIPT_RE = /(?:^|\s)(?:python|python3|python3\.\d+|perl|perl5|ruby|jruby|mruby|node|php|lua)\s+(?:-[a-zA-Z]*[cer][a-zA-Z]*\s+)/i;
1942
- DIR_TRAVERSAL_KW = [
1943
- "os.listdir",
1944
- "os.walk",
1945
- "os.scandir",
1946
- "glob.glob",
1947
- "glob.iglob",
1948
- "pathlib.",
1949
- "iterdir",
1950
- "rglob",
1951
- "scandir(",
1952
- "listdir("
1953
- ];
1807
+ DIR_TRAVERSAL_KW = loadInline().dir_traversal_keywords ?? [];
1808
+ _IFA = loadLogicRules().indirect_file_access ?? {};
1809
+ IFA_READ_KEYWORDS = _IFA.read_keywords ?? [];
1810
+ IFA_WRITE_KEYWORDS = _IFA.write_keywords ?? [];
1811
+ IFA_EXEC_KEYWORDS = _IFA.exec_keywords ?? [];
1812
+ IFA_OPEN_KEYWORDS = _IFA.open_keywords ?? [];
1954
1813
  }
1955
1814
  });
1956
1815
 
1957
1816
  // src/engine/asset-dir.ts
1958
- import { existsSync as existsSync3 } from "fs";
1959
- import { resolve as resolve2 } from "path";
1817
+ import { existsSync as existsSync4 } from "fs";
1818
+ import { join as join4, resolve as resolve2 } from "path";
1960
1819
  function assetDirCandidates(moduleDir, name) {
1820
+ const root = getRulesRoot();
1821
+ if (root) {
1822
+ const p = join4(root, name);
1823
+ return [p, p];
1824
+ }
1961
1825
  return [
1962
1826
  resolve2(moduleDir, name),
1963
1827
  resolve2(moduleDir, "../../..", "agent_moss", name)
@@ -1965,7 +1829,7 @@ function assetDirCandidates(moduleDir, name) {
1965
1829
  }
1966
1830
  function resolveAssetDir(moduleDir, name) {
1967
1831
  for (const dir of assetDirCandidates(moduleDir, name)) {
1968
- if (existsSync3(dir)) return dir;
1832
+ if (existsSync4(dir)) return dir;
1969
1833
  }
1970
1834
  return null;
1971
1835
  }
@@ -1973,11 +1837,12 @@ var init_asset_dir = __esm({
1973
1837
  "src/engine/asset-dir.ts"() {
1974
1838
  "use strict";
1975
1839
  init_esm_shims();
1840
+ init_paths();
1976
1841
  }
1977
1842
  });
1978
1843
 
1979
1844
  // src/engine/skill-loader.ts
1980
- import { readFileSync as readFileSync3, readdirSync as readdirSync3, existsSync as existsSync4 } from "fs";
1845
+ import { readFileSync as readFileSync4, readdirSync as readdirSync3, existsSync as existsSync5 } from "fs";
1981
1846
  import { resolve as resolve3, dirname as dirname2 } from "path";
1982
1847
  import { fileURLToPath as fileURLToPath3 } from "url";
1983
1848
  function loadAllSkills() {
@@ -1992,7 +1857,7 @@ function loadAllSkills() {
1992
1857
  const files = readdirSync3(dir).filter((f) => f.endsWith(".md"));
1993
1858
  for (const f of files) {
1994
1859
  const name = f.replace(".md", "");
1995
- cachedSkills[name] = readFileSync3(resolve3(dir, f), "utf-8");
1860
+ cachedSkills[name] = readFileSync4(resolve3(dir, f), "utf-8");
1996
1861
  }
1997
1862
  console.log(`[AgentMoss] Loaded ${Object.keys(cachedSkills).length} skills from ${dir}`);
1998
1863
  return cachedSkills;
@@ -2001,6 +1866,7 @@ function userSkillsDir() {
2001
1866
  return resolve3(getDataRoot(), "agent_moss_skills");
2002
1867
  }
2003
1868
  function runtimeCustomSkillKeywords() {
1869
+ if (isRuntimeConfigDisabled()) return {};
2004
1870
  try {
2005
1871
  const rt = runtimeConfigBridge.current?.loadRuntimeConfig?.();
2006
1872
  if (!rt) return {};
@@ -2018,10 +1884,11 @@ function runtimeCustomSkillKeywords() {
2018
1884
  }
2019
1885
  }
2020
1886
  function loadUserSkill(name) {
1887
+ if (isRuntimeConfigDisabled()) return "";
2021
1888
  if (userSkillCache[name] !== void 0) return userSkillCache[name];
2022
1889
  try {
2023
1890
  const p = resolve3(userSkillsDir(), `${name}.md`);
2024
- userSkillCache[name] = existsSync4(p) ? readFileSync3(p, "utf-8") : "";
1891
+ userSkillCache[name] = existsSync5(p) ? readFileSync4(p, "utf-8") : "";
2025
1892
  } catch {
2026
1893
  userSkillCache[name] = "";
2027
1894
  }
@@ -2086,6 +1953,7 @@ var init_skill_loader = __esm({
2086
1953
  init_esm_shims();
2087
1954
  init_rules_loader();
2088
1955
  init_paths();
1956
+ init_rules_mode();
2089
1957
  init_asset_dir();
2090
1958
  init_runtime_config_bridge();
2091
1959
  __filename3 = fileURLToPath3(import.meta.url);
@@ -2146,6 +2014,7 @@ __export(runtime_config_exports, {
2146
2014
  getEnabledScenarioExtensions: () => getEnabledScenarioExtensions,
2147
2015
  getEnvOverrides: () => getEnvOverrides,
2148
2016
  getProfileOrDetect: () => getProfileOrDetect,
2017
+ getRuleSourceFingerprint: () => getRuleSourceFingerprint,
2149
2018
  getRuntimeConfigPath: () => getRuntimeConfigPath,
2150
2019
  getScenarioRules: () => getScenarioRules,
2151
2020
  hasRuntimeConfig: () => hasRuntimeConfig,
@@ -2168,8 +2037,8 @@ __export(runtime_config_exports, {
2168
2037
  updateSkillCategoryEnabled: () => updateSkillCategoryEnabled,
2169
2038
  updateSkillEnabled: () => updateSkillEnabled
2170
2039
  });
2171
- import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync5, mkdirSync, unlinkSync, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
2172
- import { join as join3 } from "path";
2040
+ import { readFileSync as readFileSync5, writeFileSync, existsSync as existsSync6, mkdirSync, unlinkSync, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
2041
+ import { join as join5 } from "path";
2173
2042
  import { fileURLToPath as fileURLToPath4 } from "url";
2174
2043
  import { dirname as dirname3 } from "path";
2175
2044
  function runtimeDir() {
@@ -2180,10 +2049,25 @@ function getRuntimeConfigPath() {
2180
2049
  if (envPath) return envPath;
2181
2050
  return RUNTIME_CONFIG_PATH;
2182
2051
  }
2052
+ function getRuleSourceFingerprint() {
2053
+ if (isRuntimeConfigDisabled()) return `mode:${getRulesMode()}`;
2054
+ const p = getRuntimeConfigPath();
2055
+ try {
2056
+ const st = statSync2(p);
2057
+ return `${p}|${st.mtimeMs}|${st.size}`;
2058
+ } catch {
2059
+ return `${p}|absent`;
2060
+ }
2061
+ }
2183
2062
  function saveRuntimeConfig(config) {
2063
+ if (isRuntimeConfigDisabled()) {
2064
+ throw new Error(
2065
+ `\u89C4\u5219\u6765\u6E90\u6A21\u5F0F\u4E3A ${getRulesMode()}\uFF08runtime \u914D\u7F6E\u5DF2\u7981\u7528\uFF09\uFF1A\u4FEE\u6539\u89C4\u5219\u4E0D\u4F1A\u751F\u6548\uFF0C\u8BF7\u5207\u6362\u89C4\u5219\u6765\u6E90\u6A21\u5F0F\u5E76\u91CD\u542F\u670D\u52A1\u3002`
2066
+ );
2067
+ }
2184
2068
  const p = getRuntimeConfigPath();
2185
2069
  const dir = p.substring(0, p.lastIndexOf("/"));
2186
- if (dir && !existsSync5(dir)) mkdirSync(dir, { recursive: true });
2070
+ if (dir && !existsSync6(dir)) mkdirSync(dir, { recursive: true });
2187
2071
  writeFileSync(p, JSON.stringify(config, null, 2), "utf-8");
2188
2072
  invalidateRuntimeConfigCache();
2189
2073
  }
@@ -2204,8 +2088,20 @@ function invalidateRuntimeConfigCache() {
2204
2088
  hasRuntimeConfigCache.clear();
2205
2089
  }
2206
2090
  function loadRuntimeConfig() {
2091
+ if (isRuntimeConfigDisabled()) {
2092
+ return {
2093
+ version: 1,
2094
+ layers: { ...DEFAULT_LAYERS },
2095
+ L1_rules: {},
2096
+ L2_rules: {},
2097
+ L3_skills: {},
2098
+ L2_2: {},
2099
+ agent_rules: {},
2100
+ scenario_rules: {}
2101
+ };
2102
+ }
2207
2103
  const p = getRuntimeConfigPath();
2208
- if (!existsSync5(p)) {
2104
+ if (!existsSync6(p)) {
2209
2105
  const seeded = mergeConfig({}, generateSourceDefaults());
2210
2106
  try {
2211
2107
  saveRuntimeConfig(seeded);
@@ -2219,7 +2115,7 @@ function loadRuntimeConfig() {
2219
2115
  if (runtimeConfigCache && runtimeConfigCache.key === cacheKey) {
2220
2116
  return runtimeConfigCache.value;
2221
2117
  }
2222
- const data = JSON.parse(readFileSync4(p, "utf-8"));
2118
+ const data = JSON.parse(readFileSync5(p, "utf-8"));
2223
2119
  const merged = mergeConfig(data, generateSourceDefaults());
2224
2120
  const result = {
2225
2121
  version: data.version || 1,
@@ -2250,6 +2146,7 @@ function loadRuntimeConfig() {
2250
2146
  function isLayerEnabled(layerKey) {
2251
2147
  const disableKey = LAYER_DISABLE_ENV[layerKey];
2252
2148
  if (disableKey && process.env[disableKey] === "1") return false;
2149
+ if (isRuntimeConfigDisabled()) return true;
2253
2150
  const runtime = loadRuntimeConfig();
2254
2151
  const layers = runtime.layers || {};
2255
2152
  if (layerKey in layers) return !!layers[layerKey];
@@ -2276,7 +2173,10 @@ function filterEnabledRules(rules, categoryEnabled = true) {
2276
2173
  }
2277
2174
  function _filterRulesByOs(rules, osType) {
2278
2175
  const accepted = acceptedOsValues(osType);
2279
- return rules.filter((r) => accepted.includes(String(r.os || "linux").toLowerCase()));
2176
+ return rules.filter((r) => {
2177
+ const os4 = String(r.os || "linux").toLowerCase();
2178
+ return os4 === "all" || accepted.includes(os4);
2179
+ });
2280
2180
  }
2281
2181
  function _userCustomRules(runtime, layers, requiredKey) {
2282
2182
  const list = Array.isArray(layers) ? layers : [layers];
@@ -2350,6 +2250,7 @@ function getEnabledL2SensitivePaths(runtime, osType) {
2350
2250
  });
2351
2251
  }
2352
2252
  function hasRuntimeConfig() {
2253
+ if (isRuntimeConfigDisabled()) return false;
2353
2254
  const p = getRuntimeConfigPath();
2354
2255
  let key;
2355
2256
  try {
@@ -2358,11 +2259,11 @@ function hasRuntimeConfig() {
2358
2259
  } catch {
2359
2260
  return false;
2360
2261
  }
2361
- const cached = hasRuntimeConfigCache.get(key);
2362
- if (cached !== void 0) return cached;
2262
+ const cached2 = hasRuntimeConfigCache.get(key);
2263
+ if (cached2 !== void 0) return cached2;
2363
2264
  let ok = false;
2364
2265
  try {
2365
- JSON.parse(readFileSync4(p, "utf-8"));
2266
+ JSON.parse(readFileSync5(p, "utf-8"));
2366
2267
  ok = true;
2367
2268
  } catch {
2368
2269
  ok = false;
@@ -2462,6 +2363,9 @@ function _l1InjectionKeywordsToRules() {
2462
2363
  keyword: kw.keyword,
2463
2364
  risk_level: kw.riskLevel,
2464
2365
  lang: kw.lang || "en",
2366
+ // 带上条目自己的 os(`all` = 全平台通用),别丢 —— 丢了 Windows 会拿到
2367
+ // 本不该给它的条目。与 Python `_l1_injection_keywords_to_rules` 对齐。
2368
+ os: kw.os,
2465
2369
  enabled: true,
2466
2370
  builtin: true
2467
2371
  }));
@@ -2579,8 +2483,9 @@ function _l3SkillsToRules() {
2579
2483
  }
2580
2484
  function _scenarioRulesToDefaults() {
2581
2485
  const out = {};
2582
- const scenDir = existsSync5(_SCENARIOS_DIR_PROD) ? _SCENARIOS_DIR_PROD : existsSync5(_SCENARIOS_DIR_DEV) ? _SCENARIOS_DIR_DEV : _SCENARIOS_DIR_PROD;
2583
- if (!existsSync5(scenDir)) return out;
2486
+ const _root = getRulesRoot();
2487
+ const scenDir = _root ? join5(_root, "scenarios") : existsSync6(_SCENARIOS_DIR_PROD) ? _SCENARIOS_DIR_PROD : existsSync6(_SCENARIOS_DIR_DEV) ? _SCENARIOS_DIR_DEV : _SCENARIOS_DIR_PROD;
2488
+ if (!existsSync6(scenDir)) return out;
2584
2489
  let entries;
2585
2490
  try {
2586
2491
  entries = readdirSync4(scenDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
@@ -2588,11 +2493,11 @@ function _scenarioRulesToDefaults() {
2588
2493
  return out;
2589
2494
  }
2590
2495
  for (const scenarioId of entries) {
2591
- const rulesFile = resolveScenarioRulesFile(scenarioId) ?? join3(scenDir, scenarioId, "rules.json");
2592
- if (!existsSync5(rulesFile)) continue;
2496
+ const rulesFile = resolveScenarioRulesFile(scenarioId) ?? join5(scenDir, scenarioId, "rules.json");
2497
+ if (!existsSync6(rulesFile)) continue;
2593
2498
  let data;
2594
2499
  try {
2595
- data = JSON.parse(readFileSync4(rulesFile, "utf-8"));
2500
+ data = JSON.parse(readFileSync5(rulesFile, "utf-8"));
2596
2501
  } catch {
2597
2502
  continue;
2598
2503
  }
@@ -2628,8 +2533,9 @@ function _scenarioRulesToDefaults() {
2628
2533
  }
2629
2534
  function _agentRulesToDefaults() {
2630
2535
  const out = {};
2631
- const agentsDir = existsSync5(_AGENTS_DIR_PROD) ? _AGENTS_DIR_PROD : existsSync5(_AGENTS_DIR_DEV) ? _AGENTS_DIR_DEV : _AGENTS_DIR_PROD;
2632
- if (!existsSync5(agentsDir)) return out;
2536
+ const _root = getRulesRoot();
2537
+ const agentsDir = _root ? join5(_root, "agents") : existsSync6(_AGENTS_DIR_PROD) ? _AGENTS_DIR_PROD : existsSync6(_AGENTS_DIR_DEV) ? _AGENTS_DIR_DEV : _AGENTS_DIR_PROD;
2538
+ if (!existsSync6(agentsDir)) return out;
2633
2539
  let entries;
2634
2540
  try {
2635
2541
  entries = readdirSync4(agentsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
@@ -2637,11 +2543,11 @@ function _agentRulesToDefaults() {
2637
2543
  return out;
2638
2544
  }
2639
2545
  for (const agentId of entries) {
2640
- const rulesFile = join3(agentsDir, agentId, "rules.json");
2641
- if (!existsSync5(rulesFile)) continue;
2546
+ const rulesFile = join5(agentsDir, agentId, "rules.json");
2547
+ if (!existsSync6(rulesFile)) continue;
2642
2548
  let data;
2643
2549
  try {
2644
- data = JSON.parse(readFileSync4(rulesFile, "utf-8"));
2550
+ data = JSON.parse(readFileSync5(rulesFile, "utf-8"));
2645
2551
  } catch {
2646
2552
  continue;
2647
2553
  }
@@ -3016,7 +2922,11 @@ function getDisabledSkillIds(runtime) {
3016
2922
  }
3017
2923
  function _filterDefaultsByOs(rules, osType) {
3018
2924
  const accepted = acceptedOsValues(osType);
3019
- return rules.filter((r) => !r.os || accepted.includes(String(r.os).toLowerCase()));
2925
+ return rules.filter((r) => {
2926
+ if (!r.os) return true;
2927
+ const os4 = String(r.os).toLowerCase();
2928
+ return os4 === "all" || accepted.includes(os4);
2929
+ });
3020
2930
  }
3021
2931
  function _hasRulesForOs(rules, osType) {
3022
2932
  return _filterRulesByOs(rules || [], osType || "linux").length > 0;
@@ -3239,13 +3149,18 @@ function deleteCustomRule(layer, category, ruleId) {
3239
3149
  }
3240
3150
  function addCustomSkill(skillId, category, keywords, content) {
3241
3151
  _validateSkillId(skillId);
3152
+ if (isRuntimeConfigDisabled()) {
3153
+ throw new Error(
3154
+ `\u89C4\u5219\u6765\u6E90\u6A21\u5F0F\u4E3A ${getRulesMode()}\uFF08runtime \u914D\u7F6E\u5DF2\u7981\u7528\uFF09\uFF0C\u4E0D\u5141\u8BB8\u65B0\u589E\u81EA\u5B9A\u4E49 skill\u3002`
3155
+ );
3156
+ }
3242
3157
  const runtime = loadRuntimeConfig();
3243
- const skillsDir = join3(getRuntimeConfigDir(), "agent_moss_skills");
3158
+ const skillsDir = join5(getRuntimeConfigDir(), "agent_moss_skills");
3244
3159
  try {
3245
3160
  mkdirSync(skillsDir, { recursive: true });
3246
3161
  } catch {
3247
3162
  }
3248
- writeFileSync(join3(skillsDir, `${skillId}.md`), content, "utf-8");
3163
+ writeFileSync(join5(skillsDir, `${skillId}.md`), content, "utf-8");
3249
3164
  const l3 = runtime.L3_skills || (runtime.L3_skills = {});
3250
3165
  const cat = l3[category] || (l3[category] = { category_enabled: true, category_desc: "", skills: [] });
3251
3166
  (cat.skills || (cat.skills = [])).push({ id: skillId, keywords, enabled: true, builtin: false });
@@ -3260,9 +3175,9 @@ function deleteCustomSkill(skillId, category) {
3260
3175
  if (idx >= 0) {
3261
3176
  if (skills[idx].builtin) return null;
3262
3177
  skills.splice(idx, 1);
3263
- const skillPath = join3(getRuntimeConfigDir(), "agent_moss_skills", `${skillId}.md`);
3178
+ const skillPath = join5(getRuntimeConfigDir(), "agent_moss_skills", `${skillId}.md`);
3264
3179
  try {
3265
- if (existsSync5(skillPath)) unlinkSync(skillPath);
3180
+ if (existsSync6(skillPath)) unlinkSync(skillPath);
3266
3181
  } catch {
3267
3182
  }
3268
3183
  saveRuntimeConfig(runtime);
@@ -3465,10 +3380,11 @@ var init_runtime_config = __esm({
3465
3380
  init_skill_loader();
3466
3381
  init_rules_loader();
3467
3382
  init_runtime_config_bridge();
3383
+ init_rules_mode();
3468
3384
  __dirname4 = dirname3(fileURLToPath4(import.meta.url));
3469
3385
  ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
3470
3386
  RUNTIME_CONFIG_DIR = runtimeDir();
3471
- RUNTIME_CONFIG_PATH = join3(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
3387
+ RUNTIME_CONFIG_PATH = join5(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
3472
3388
  LAYER_DISABLE_ENV = {
3473
3389
  L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
3474
3390
  L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
@@ -3496,10 +3412,10 @@ var init_runtime_config = __esm({
3496
3412
  intent_consistency: ["intent_keywords", "reason"]
3497
3413
  };
3498
3414
  hasRuntimeConfigCache = /* @__PURE__ */ new Map();
3499
- _AGENTS_DIR_PROD = join3(__dirname4, "agents");
3500
- _AGENTS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "agents");
3501
- _SCENARIOS_DIR_PROD = join3(__dirname4, "scenarios");
3502
- _SCENARIOS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
3415
+ _AGENTS_DIR_PROD = join5(__dirname4, "agents");
3416
+ _AGENTS_DIR_DEV = join5(__dirname4, "..", "..", "..", "agent_moss", "agents");
3417
+ _SCENARIOS_DIR_PROD = join5(__dirname4, "scenarios");
3418
+ _SCENARIOS_DIR_DEV = join5(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
3503
3419
  _effectiveCache = /* @__PURE__ */ new WeakMap();
3504
3420
  SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
3505
3421
  registerRuntimeConfigBridge({
@@ -3536,6 +3452,7 @@ __export(config_exports, {
3536
3452
  isPolicyGenEnabled: () => isPolicyGenEnabled,
3537
3453
  isPolicySaveEnabled: () => isPolicySaveEnabled,
3538
3454
  probeLLMConnection: () => probeLLMConnection,
3455
+ reloadCustomRules: () => reloadCustomRules,
3539
3456
  updateLLMConfig: () => updateLLMConfig,
3540
3457
  userRuleVariantPattern: () => userRuleVariantPattern,
3541
3458
  wildcardToRegExp: () => wildcardToRegExp
@@ -3616,17 +3533,19 @@ function wildcardToRegExp(pattern) {
3616
3533
  }
3617
3534
  function loadCustomRules() {
3618
3535
  const rules = [];
3619
- try {
3620
- const raw = process.env.AGENT_MOSS_CUSTOM_RULES;
3621
- if (raw) {
3622
- const parsed = JSON.parse(raw);
3623
- if (Array.isArray(parsed)) {
3624
- for (const r of parsed) {
3625
- if (r && r.pattern && r.action) rules.push({ pattern: r.pattern, action: r.action, severity: r.severity || "high" });
3536
+ if (!isBuiltinOnly()) {
3537
+ try {
3538
+ const raw = process.env.AGENT_MOSS_CUSTOM_RULES;
3539
+ if (raw) {
3540
+ const parsed = JSON.parse(raw);
3541
+ if (Array.isArray(parsed)) {
3542
+ for (const r of parsed) {
3543
+ if (r && r.pattern && r.action) rules.push({ pattern: r.pattern, action: r.action, severity: r.severity || "high" });
3544
+ }
3626
3545
  }
3627
3546
  }
3547
+ } catch {
3628
3548
  }
3629
- } catch {
3630
3549
  }
3631
3550
  for (const rulesPath of USER_RULES_PATHS) {
3632
3551
  try {
@@ -3655,6 +3574,9 @@ function userRuleVariantPattern(pattern) {
3655
3574
  }
3656
3575
  return null;
3657
3576
  }
3577
+ function reloadCustomRules() {
3578
+ customRules = loadCustomRules();
3579
+ }
3658
3580
  function getLLMConfig() {
3659
3581
  return llmConfig;
3660
3582
  }
@@ -3742,6 +3664,7 @@ function layerEnabledOrDefault(key) {
3742
3664
  }
3743
3665
  }
3744
3666
  function isBrainEnabled() {
3667
+ if (isRuntimeConfigDisabled()) return false;
3745
3668
  return getAgentMossSetting("AGENT_MOSS_BRAIN_ENABLED", "") === "1";
3746
3669
  }
3747
3670
  function getAgentMossSetting(key, def = "") {
@@ -3783,6 +3706,7 @@ var init_config = __esm({
3783
3706
  init_esm_shims();
3784
3707
  init_paths();
3785
3708
  init_runtime_config();
3709
+ init_rules_mode();
3786
3710
  __configModuleDir = (() => {
3787
3711
  try {
3788
3712
  return path2.dirname(fileURLToPath5(import.meta.url));
@@ -3829,17 +3753,21 @@ var init_config = __esm({
3829
3753
  });
3830
3754
 
3831
3755
  // src/agents/index.ts
3832
- import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
3833
- import { resolve as resolve4, dirname as dirname4 } from "path";
3756
+ import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
3757
+ import { resolve as resolve4, dirname as dirname4, join as join6 } from "path";
3834
3758
  import { fileURLToPath as fileURLToPath6 } from "url";
3835
3759
  function resolveAgentsDir() {
3836
- if (existsSync6(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
3837
- if (existsSync6(AGENTS_DIR_DEV_FALLBACK)) return AGENTS_DIR_DEV_FALLBACK;
3760
+ const root = getRulesRoot();
3761
+ if (root) return join6(root, "agents");
3762
+ if (existsSync7(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
3763
+ if (existsSync7(AGENTS_DIR_DEV_FALLBACK)) return AGENTS_DIR_DEV_FALLBACK;
3838
3764
  return AGENTS_DIR_PROD;
3839
3765
  }
3840
3766
  function getAgentExtensions(agentId) {
3841
3767
  if (!agentId) return null;
3842
- if (cache.has(agentId)) return cache.get(agentId) ?? null;
3768
+ const fp = getRuleSourceFingerprint();
3769
+ const cached2 = cache.get(agentId);
3770
+ if (cached2 && cached2.fp === fp) return cached2.ext;
3843
3771
  let ext = null;
3844
3772
  try {
3845
3773
  if (hasRuntimeConfig()) {
@@ -3850,25 +3778,25 @@ function getAgentExtensions(agentId) {
3850
3778
  if (ext === null) {
3851
3779
  const agentsDir = resolveAgentsDir();
3852
3780
  const rulesFile = resolve4(agentsDir, agentId, "rules.json");
3853
- if (!existsSync6(rulesFile)) {
3854
- cache.set(agentId, null);
3781
+ if (!existsSync7(rulesFile)) {
3782
+ cache.set(agentId, { fp, ext: null });
3855
3783
  return null;
3856
3784
  }
3857
3785
  try {
3858
- const raw = readFileSync5(rulesFile, "utf-8");
3786
+ const raw = readFileSync6(rulesFile, "utf-8");
3859
3787
  ext = JSON.parse(raw);
3860
3788
  if (!ext.agent_id) {
3861
3789
  console.warn(`[AgentMoss agents] rules.json \u7F3A\u5C11 agent_id \u5B57\u6BB5: ${rulesFile}`);
3862
- cache.set(agentId, null);
3790
+ cache.set(agentId, { fp, ext: null });
3863
3791
  return null;
3864
3792
  }
3865
3793
  } catch (e) {
3866
3794
  console.error(`[AgentMoss agents] \u52A0\u8F7D agent ${agentId} rules.json \u5931\u8D25:`, e.message);
3867
- cache.set(agentId, null);
3795
+ cache.set(agentId, { fp, ext: null });
3868
3796
  return null;
3869
3797
  }
3870
3798
  }
3871
- cache.set(agentId, ext);
3799
+ cache.set(agentId, { fp, ext });
3872
3800
  console.error(
3873
3801
  `[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`
3874
3802
  );
@@ -3894,6 +3822,7 @@ var init_agents = __esm({
3894
3822
  "src/agents/index.ts"() {
3895
3823
  "use strict";
3896
3824
  init_esm_shims();
3825
+ init_paths();
3897
3826
  init_runtime_config();
3898
3827
  __filename4 = fileURLToPath6(import.meta.url);
3899
3828
  __dirname5 = dirname4(__filename4);
@@ -3904,17 +3833,21 @@ var init_agents = __esm({
3904
3833
  });
3905
3834
 
3906
3835
  // src/scenarios/index.ts
3907
- import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
3908
- import { resolve as resolve5, dirname as dirname5 } from "path";
3836
+ import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
3837
+ import { resolve as resolve5, dirname as dirname5, join as join7 } from "path";
3909
3838
  import { fileURLToPath as fileURLToPath7 } from "url";
3910
3839
  function resolveScenariosDir() {
3911
- if (existsSync7(SCENARIOS_DIR_PROD)) return SCENARIOS_DIR_PROD;
3912
- if (existsSync7(SCENARIOS_DIR_DEV_FALLBACK)) return SCENARIOS_DIR_DEV_FALLBACK;
3840
+ const root = getRulesRoot();
3841
+ if (root) return join7(root, "scenarios");
3842
+ if (existsSync8(SCENARIOS_DIR_PROD)) return SCENARIOS_DIR_PROD;
3843
+ if (existsSync8(SCENARIOS_DIR_DEV_FALLBACK)) return SCENARIOS_DIR_DEV_FALLBACK;
3913
3844
  return SCENARIOS_DIR_PROD;
3914
3845
  }
3915
3846
  function getScenarioExtensions(scenarioId) {
3916
3847
  if (!scenarioId) return null;
3917
- if (cache2.has(scenarioId)) return cache2.get(scenarioId) ?? null;
3848
+ const fp = getRuleSourceFingerprint();
3849
+ const cached2 = cache2.get(scenarioId);
3850
+ if (cached2 && cached2.fp === fp) return cached2.ext;
3918
3851
  let ext = null;
3919
3852
  let overrideHit = null;
3920
3853
  try {
@@ -3937,17 +3870,17 @@ function getScenarioExtensions(scenarioId) {
3937
3870
  `[AgentMoss] \u26A0\uFE0F \u573A\u666F ${scenarioId} \u89C4\u5219\u88AB\u8986\u76D6\u76EE\u5F55\u8986\u76D6: ${overrideHit}\uFF08\u5185\u7F6E\u9ED8\u8BA4 ${builtinFile} \u88AB\u5FFD\u7565\uFF09\u3002\u8BE5\u6587\u4EF6\u7531\u90E8\u7F72\u4FA7\u81EA\u884C\u7EF4\u62A4\uFF0C\u5185\u7F6E\u573A\u666F\u89C4\u5219\u66F4\u65B0\u4E0D\u4F1A\u53CD\u6620\u5230\u5B83\u4E0A\u9762\u3002`
3938
3871
  );
3939
3872
  }
3940
- if (!existsSync7(rulesFile)) {
3941
- cache2.set(scenarioId, null);
3873
+ if (!existsSync8(rulesFile)) {
3874
+ cache2.set(scenarioId, { fp, ext: null });
3942
3875
  return null;
3943
3876
  }
3944
3877
  try {
3945
- const raw = readFileSync6(rulesFile, "utf-8");
3878
+ const raw = readFileSync7(rulesFile, "utf-8");
3946
3879
  const data = JSON.parse(raw);
3947
3880
  const id = data.scenario_id || data.agent_id || "";
3948
3881
  if (!id) {
3949
3882
  console.warn(`[AgentMoss scenarios] rules.json \u7F3A\u5C11 scenario_id \u5B57\u6BB5: ${rulesFile}`);
3950
- cache2.set(scenarioId, null);
3883
+ cache2.set(scenarioId, { fp, ext: null });
3951
3884
  return null;
3952
3885
  }
3953
3886
  ext = {
@@ -3962,11 +3895,11 @@ function getScenarioExtensions(scenarioId) {
3962
3895
  };
3963
3896
  } catch (e) {
3964
3897
  console.error(`[AgentMoss scenarios] \u52A0\u8F7D\u573A\u666F ${scenarioId} rules.json \u5931\u8D25:`, e.message);
3965
- cache2.set(scenarioId, null);
3898
+ cache2.set(scenarioId, { fp, ext: null });
3966
3899
  return null;
3967
3900
  }
3968
3901
  }
3969
- cache2.set(scenarioId, ext);
3902
+ cache2.set(scenarioId, { fp, ext });
3970
3903
  console.error(
3971
3904
  `[AgentMoss scenarios] \u52A0\u8F7D\u573A\u666F ${scenarioId} \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`
3972
3905
  );
@@ -3986,6 +3919,7 @@ var init_scenarios = __esm({
3986
3919
  "src/scenarios/index.ts"() {
3987
3920
  "use strict";
3988
3921
  init_esm_shims();
3922
+ init_paths();
3989
3923
  init_agents();
3990
3924
  init_runtime_config();
3991
3925
  init_rules_loader();
@@ -4392,7 +4326,7 @@ function severityRank(s) {
4392
4326
  }
4393
4327
  function isSearchActionType(actionType) {
4394
4328
  const at = actionType.toLowerCase();
4395
- return SEARCH_ACTION_PREFIXES.some((p) => at.startsWith(p)) || ["websearch", "web_search", "search", "query"].includes(at);
4329
+ return SEARCH_ACTION_PREFIXES.some((p) => at.startsWith(p)) || SEARCH_ACTION_EXACT.includes(at);
4396
4330
  }
4397
4331
  function checkSensitivePath(command, cwd, agentExt, osType) {
4398
4332
  const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
@@ -4682,8 +4616,7 @@ function isSshKeygenGenerate(command) {
4682
4616
  function checkDangerousPatterns(actionType, actionDetail) {
4683
4617
  if (actionType === "file_write" || actionType === "file_edit") return null;
4684
4618
  const scan = stripHeredocContent(actionDetail);
4685
- const TEMP_DIRS = ["/tmp", "/var/tmp", "/run", "/dev/shm"];
4686
- const isTempPath = TEMP_DIRS.some(
4619
+ const isTempPath = TEMP_DIRS2.some(
4687
4620
  (td) => new RegExp(`(?:^|[\\s/])${td.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[\\s/]|$)`).test(actionDetail)
4688
4621
  ) || /(?:^|[\s;]|&&\s*|\|\|\s*)var\/tmp(?:[/\s;]|$)/.test(actionDetail);
4689
4622
  const wildcardDeleteRe = /(?:\brm\b|\b(r?mdir|del|delete|remove|rmdir|unlink)\b|\b删除\b|\b移除\b)[\s\S]{0,40}?[*?]|[*?][\s\S]{0,40}?\b(?:rm|del|delete|remove|rmdir|unlink)\b|rm\s+-[a-zA-Z]*[rf][a-zA-Z]*\s+\S*[*?]/i;
@@ -4829,7 +4762,7 @@ function checkLateralMovement(actionDetail) {
4829
4762
  }
4830
4763
  return null;
4831
4764
  }
4832
- var INJECTION_KEYWORDS, DANGEROUS_PATTERNS2, DANGEROUS_PATTERNS_WINDOWS, DANGEROUS_PATTERNS_MACOS, DANGEROUS_PATTERNS_HARMONY, LOOPBACK_URL_RE, LOOPBACK_HOSTS, PATH_CONTEXT_TRIGGER_RE, HOME_PREFIX_RE, PY_EXEC_IO_RE, PY_PRINT_RE, PY_BIGNUM_RE, PY_INLINE_C_RE, PY_CMD_SUBST_C_RE, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES, HOME_TARGET_RE, HOME_ASSIGN_RE;
4765
+ var INJECTION_KEYWORDS, DANGEROUS_PATTERNS2, DANGEROUS_PATTERNS_WINDOWS, DANGEROUS_PATTERNS_MACOS, DANGEROUS_PATTERNS_HARMONY, LOOPBACK_URL_RE, HEURISTIC_RULES, LOOPBACK_HOSTS, TEMP_DIRS2, PATH_CONTEXT_TRIGGER_RE, HOME_PREFIX_RE, PY_EXEC_IO_RE, PY_PRINT_RE, PY_BIGNUM_RE, PY_INLINE_C_RE, PY_CMD_SUBST_C_RE, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES, SEARCH_ACTION_EXACT, HOME_TARGET_RE, HOME_ASSIGN_RE;
4833
4766
  var init_heuristic = __esm({
4834
4767
  "src/engine/heuristic.ts"() {
4835
4768
  "use strict";
@@ -4846,7 +4779,8 @@ var init_heuristic = __esm({
4846
4779
  return kws.map((k) => ({
4847
4780
  keyword: k.keyword,
4848
4781
  riskLevel: k.risk_level,
4849
- lang: k.lang || "en"
4782
+ lang: k.lang || "en",
4783
+ os: String(k.os || "all").toLowerCase()
4850
4784
  }));
4851
4785
  })();
4852
4786
  DANGEROUS_PATTERNS2 = _loadDangerousPatterns("linux");
@@ -4854,7 +4788,9 @@ var init_heuristic = __esm({
4854
4788
  DANGEROUS_PATTERNS_MACOS = _loadDangerousPatterns("macos");
4855
4789
  DANGEROUS_PATTERNS_HARMONY = _loadDangerousPatterns("harmony");
4856
4790
  LOOPBACK_URL_RE = /https?:\/\/([^\/\s;'"|&)]+)/i;
4857
- LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "::1", "0.0.0.0", "[::1]"]);
4791
+ HEURISTIC_RULES = loadHeuristicRules();
4792
+ LOOPBACK_HOSTS = new Set(HEURISTIC_RULES.loopback_hosts ?? []);
4793
+ TEMP_DIRS2 = HEURISTIC_RULES.temp_dirs ?? [];
4858
4794
  PATH_CONTEXT_TRIGGER_RE = /(?:^|[;&|]\s*)(?:rm|rmdir|unlink|ln|mv|cp|dd|truncate|shred|chmod|chown)\b/i;
4859
4795
  HOME_PREFIX_RE = /(?:~|~(?=\/)|\$HOME\b|\$\{HOME\}|\/home\/[a-z0-9_.-]+)/gi;
4860
4796
  PY_EXEC_IO_RE = /os\.system|os\.popen|subprocess|eval\s*\(|exec\s*\(|__import__|open\s*\(|socket|urllib|requests\.|shutil|os\.(?:remove|unlink|rename|makedirs?|listdir|scandir|walk|chmod|chown|kill|environ)|iterdir|glob\s*\(|path\(/i;
@@ -4862,15 +4798,16 @@ var init_heuristic = __esm({
4862
4798
  PY_BIGNUM_RE = /\d{6,}/;
4863
4799
  PY_INLINE_C_RE = /(python(?:3(?:\.\d+)?)?|perl5?|ruby|node|php|lua)\s+(?:-[a-zA-Z]+\s+)*-c\s+(?:"([^"]*)"|'([^']*)')/i;
4864
4800
  PY_CMD_SUBST_C_RE = /\$\(\s*(python(?:3(?:\.\d+)?)?|perl5?|ruby|node|php|lua)\s+(?:-[a-zA-Z]+\s+)*-c\s+(?:"([^"]*)"'|'([^']*)')\s*\)/i;
4865
- SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
4866
- SEARCH_ACTION_PREFIXES = ["search-", "web-", "browser-", "query-", "websearch", "web_search"];
4801
+ SENSITIVE_EXCLUDED_PREFIXES = HEURISTIC_RULES.sensitive_excluded_prefixes ?? [];
4802
+ SEARCH_ACTION_PREFIXES = HEURISTIC_RULES.search_action_prefixes ?? [];
4803
+ SEARCH_ACTION_EXACT = HEURISTIC_RULES.search_action_exact ?? [];
4867
4804
  HOME_TARGET_RE = /(?:^|[\s="'($])(?:~|(?:\$home|\$\{home\}))(?:\/(?:\.\*|\*|\.)?)?(?=[\s"';}|&)]|$)/i;
4868
4805
  HOME_ASSIGN_RE = /([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(~[A-Za-z0-9_./-]*|\$HOME[A-Za-z0-9_./-]*|\$\{HOME\}[A-Za-z0-9_./-]*|\/home\/[^\s;|&]+|\$\((?:[^()]|\([^()]*\))*\))(?=\s|;|$|\||&)/gi;
4869
4806
  }
4870
4807
  });
4871
4808
 
4872
4809
  // src/engine/template-loader.ts
4873
- import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
4810
+ import { readFileSync as readFileSync8, existsSync as existsSync9 } from "fs";
4874
4811
  import { resolve as resolve6, dirname as dirname6 } from "path";
4875
4812
  import { fileURLToPath as fileURLToPath8 } from "url";
4876
4813
  function loadTemplates() {
@@ -4884,9 +4821,9 @@ function loadTemplates() {
4884
4821
  }
4885
4822
  for (const file of TEMPLATE_FILES) {
4886
4823
  const filePath = resolve6(dir, file);
4887
- if (existsSync8(filePath)) {
4824
+ if (existsSync9(filePath)) {
4888
4825
  const key = file.replace(".txt", "").replace("_template", "");
4889
- cachedTemplates[key] = readFileSync7(filePath, "utf-8");
4826
+ cachedTemplates[key] = readFileSync8(filePath, "utf-8");
4890
4827
  }
4891
4828
  }
4892
4829
  console.log(`[AgentMoss] Loaded ${Object.keys(cachedTemplates).length} templates from ${dir}`);
@@ -4906,8 +4843,8 @@ var init_template_loader = __esm({
4906
4843
  });
4907
4844
 
4908
4845
  // src/engine/script-analyzer.ts
4909
- import { readFileSync as readFileSync8 } from "fs";
4910
- import { resolve as resolve7, join as join4 } from "path";
4846
+ import { readFileSync as readFileSync9 } from "fs";
4847
+ import { resolve as resolve7, join as join8 } from "path";
4911
4848
  function emptyResult() {
4912
4849
  return { scriptPath: "", scriptContent: "", matchedKeywords: [], lineCount: 0, isSuspicious: false, error: "", riskLevel: "", riskReason: "" };
4913
4850
  }
@@ -4933,10 +4870,10 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
4933
4870
  const hitGroup2 = rule.group2.size === 0 ? true : [...rule.group2].some((k) => keywordSet.has(k));
4934
4871
  if (hitGroup1 && hitGroup2) return { level: rule.level, reason: rule.reason };
4935
4872
  }
4936
- const networkPostKeywords = /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]);
4873
+ const networkPostKeywords = new Set(KEYWORD_SETS.network_post ?? []);
4937
4874
  const hasNetworkPost = [...networkPostKeywords].some((k) => keywordSet.has(k));
4938
4875
  if (hasNetworkPost) {
4939
- const envPatterns = ["$env", "$(env)", "$USER", "$(hostname)", "$(pwd)", "$HOME"];
4876
+ const envPatterns = SCRIPT_RULES.env_exfil_patterns ?? [];
4940
4877
  const contentLower = scriptContent.toLowerCase();
4941
4878
  for (const p of envPatterns) {
4942
4879
  if (contentLower.includes(p.toLowerCase())) {
@@ -4960,8 +4897,8 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
4960
4897
  }
4961
4898
  function readScriptContent(scriptPath, maxLines = 500) {
4962
4899
  try {
4963
- const expandedPath = scriptPath === "~" || scriptPath.startsWith("~/") ? join4(process.env.HOME || "/home/user", scriptPath.slice(1)) : resolve7(scriptPath);
4964
- const raw = readFileSync8(expandedPath, "utf-8");
4900
+ const expandedPath = scriptPath === "~" || scriptPath.startsWith("~/") ? join8(process.env.HOME || "/home/user", scriptPath.slice(1)) : resolve7(scriptPath);
4901
+ const raw = readFileSync9(expandedPath, "utf-8");
4965
4902
  const lines = raw.split("\n").slice(0, maxLines);
4966
4903
  return { content: lines.join("\n"), lineCount: lines.length, error: "" };
4967
4904
  } catch (e) {
@@ -5010,90 +4947,28 @@ ${result.scriptContent}
5010
4947
  \`\`\`
5011
4948
  `;
5012
4949
  }
5013
- var SUSPICIOUS_PATTERNS, SCRIPT_EXEC_PATTERNS, KEYWORD_COMBINATION_RISKS, SINGLE_KEYWORD_RISKS, LEVEL_ORDER;
4950
+ var SCRIPT_RULES, SUSPICIOUS_PATTERNS, SCRIPT_EXEC_PATTERNS, KEYWORD_SETS, KEYWORD_COMBINATION_RISKS, SINGLE_KEYWORD_RISKS, LEVEL_ORDER;
5014
4951
  var init_script_analyzer = __esm({
5015
4952
  "src/engine/script-analyzer.ts"() {
5016
4953
  "use strict";
5017
4954
  init_esm_shims();
5018
- SUSPICIOUS_PATTERNS = [
5019
- // Network exfiltration (curl/wget POST in scripts is almost never legitimate)
5020
- { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
5021
- { name: "curl --data", pattern: /curl\s+(.*\s)?--data/i },
5022
- { name: "curl -d", pattern: /curl\s+(.*\s)?-d\s/i },
5023
- { name: "wget --post-data", pattern: /wget\s+(.*\s)?--post-data/i },
5024
- { name: "wget --post-file", pattern: /wget\s+(.*\s)?--post-file/i },
5025
- // Reverse shell
5026
- { name: "nc -e", pattern: /\bnc\s+.*-e\b/ },
5027
- { name: "ncat -e", pattern: /\bncat\s+.*-e\b/ },
5028
- { name: "/dev/tcp", pattern: /\/dev\/tcp\// },
5029
- { name: "/dev/udp", pattern: /\/dev\/udp\// },
5030
- // Base64 obfuscation
5031
- { name: "$(base64", pattern: /\$\(\s*base64/ },
5032
- { name: "`base64", pattern: /`\s*base64/ },
5033
- { name: "base64 decode pipe", pattern: /base64\s+(-d|--decode)\s*\|/ },
5034
- // Sensitive file access
5035
- { name: "/etc/shadow", pattern: /\/etc\/shadow/ },
5036
- { name: "id_rsa", pattern: /\bid_rsa\b/ },
5037
- { name: "id_ed25519", pattern: /\bid_ed25519\b/ },
5038
- // Persistence backdoor
5039
- { name: "crontab install", pattern: /crontab\s+-/ },
5040
- { name: "authorized_keys", pattern: /authorized_keys/ },
5041
- { name: "ssh no host key check", pattern: /ssh\s+.*StrictHostKeyChecking\s*=\s*no/ }
5042
- ];
5043
- SCRIPT_EXEC_PATTERNS = [
5044
- /bash\s+(\S+\.sh)/,
5045
- /sh\s+(\S+\.sh)/,
5046
- /\.\/(\S+\.sh)/,
5047
- /source\s+(\S+\.sh)/,
5048
- /\.\s+(\S+\.sh)/,
5049
- /zsh\s+(\S+\.sh)/
5050
- ];
5051
- KEYWORD_COMBINATION_RISKS = [
5052
- {
5053
- group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
5054
- group2: /* @__PURE__ */ new Set(["$(base64", "`base64", "base64 decode pipe"]),
5055
- level: "critical",
5056
- reason: "\u68C0\u6D4B\u5230\u7F51\u7EDC\u6570\u636E\u53D1\u9001 + Base64\u7F16\u7801\u7EC4\u5408\uFF0C\u6784\u6210 Critical \u7EA7\u522B\u6570\u636E\u5916\u4F20\u98CE\u9669\u3002\u653B\u51FB\u8005\u53EF\u80FD\u5C06\u654F\u611F\u6570\u636E\u7F16\u7801\u540E\u5916\u4F20\u3002"
5057
- },
5058
- {
5059
- group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
5060
- group2: /* @__PURE__ */ new Set(["/etc/shadow", "id_rsa", "id_ed25519"]),
5061
- level: "critical",
5062
- reason: "\u68C0\u6D4B\u5230\u7F51\u7EDC\u6570\u636E\u53D1\u9001 + \u654F\u611F\u6587\u4EF6\u8BBF\u95EE\u7EC4\u5408\uFF0C\u6784\u6210 Critical \u7EA7\u522B\u51ED\u8BC1\u5916\u4F20\u98CE\u9669\u3002"
5063
- },
5064
- {
5065
- group1: /* @__PURE__ */ new Set(["nc -e", "ncat -e", "/dev/tcp", "/dev/udp"]),
5066
- group2: /* @__PURE__ */ new Set(),
5067
- level: "high",
5068
- reason: "\u68C0\u6D4B\u5230\u53CD\u5F39 Shell \u7279\u5F81\uFF0C\u653B\u51FB\u8005\u53EF\u80FD\u5EFA\u7ACB\u8FDC\u7A0B\u63A7\u5236\u901A\u9053\u3002"
5069
- },
5070
- {
5071
- group1: /* @__PURE__ */ new Set(["crontab install", "authorized_keys", "ssh no host key check"]),
5072
- group2: /* @__PURE__ */ new Set(),
5073
- level: "high",
5074
- reason: "\u68C0\u6D4B\u5230\u6301\u4E45\u5316\u540E\u95E8\u7279\u5F81\uFF0C\u653B\u51FB\u8005\u53EF\u80FD\u5728\u7CFB\u7EDF\u4E2D\u690D\u5165\u540E\u95E8\u3002"
5075
- }
5076
- ];
5077
- SINGLE_KEYWORD_RISKS = {
5078
- "curl POST": { level: "high", reason: "\u68C0\u6D4B\u5230 curl POST \u8BF7\u6C42\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
5079
- "curl --data": { level: "high", reason: "\u68C0\u6D4B\u5230 curl --data \u53C2\u6570\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
5080
- "curl -d": { level: "high", reason: "\u68C0\u6D4B\u5230 curl -d \u53C2\u6570\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
5081
- "wget --post-data": { level: "high", reason: "\u68C0\u6D4B\u5230 wget POST \u8BF7\u6C42\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
5082
- "wget --post-file": { level: "high", reason: "\u68C0\u6D4B\u5230 wget \u6587\u4EF6\u4E0A\u4F20\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6587\u4EF6\u3002" },
5083
- "nc -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 nc -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
5084
- "ncat -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 ncat -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
5085
- "/dev/tcp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/tcp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
5086
- "/dev/udp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/udp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
5087
- "$(base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
5088
- "`base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
5089
- "base64 decode pipe": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u89E3\u7801\u540E\u7BA1\u9053\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
5090
- "/etc/shadow": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6\u3002" },
5091
- "id_rsa": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
5092
- "id_ed25519": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
5093
- "crontab install": { level: "high", reason: "\u68C0\u6D4B\u5230\u4FEE\u6539\u5B9A\u65F6\u4EFB\u52A1\uFF0C\u53EF\u80FD\u662F\u6301\u4E45\u5316\u540E\u95E8\u3002" },
5094
- "authorized_keys": { level: "high", reason: "\u68C0\u6D4B\u5230\u4FEE\u6539 SSH \u6388\u6743\u5BC6\u94A5\uFF0C\u53EF\u80FD\u662F\u6301\u4E45\u5316\u540E\u95E8\u3002" },
5095
- "ssh no host key check": { level: "high", reason: "\u68C0\u6D4B\u5230 SSH \u8DF3\u8FC7\u4E3B\u673A\u5BC6\u94A5\u9A8C\u8BC1\uFF0C\u53EF\u80FD\u662F\u6A2A\u5411\u79FB\u52A8\u3002" }
5096
- };
4955
+ init_rules_loader();
4956
+ SCRIPT_RULES = loadScriptRules();
4957
+ if (!SCRIPT_RULES || Object.keys(SCRIPT_RULES).length === 0) {
4958
+ console.error(
4959
+ "[AgentMoss] \u274C \u89C4\u5219\u6587\u4EF6 script_rules.json \u7F3A\u5931\u6216\u635F\u574F \u2014\u2014 \u811A\u672C\u5173\u952E\u8BCD\u626B\u63CF\u5C06\u4E0D\u547D\u4E2D\u4EFB\u4F55\u6A21\u5F0F\uFF08\u6BD4\u6B63\u5E38\u66F4\u677E\uFF09\u3002\u8BF7\u68C0\u67E5\u5305\u5B8C\u6574\u6027\u3002"
4960
+ );
4961
+ }
4962
+ SUSPICIOUS_PATTERNS = (SCRIPT_RULES.suspicious_patterns ?? []).map((r) => ({ name: r.name, pattern: compileRuleRegex(r) }));
4963
+ SCRIPT_EXEC_PATTERNS = ((SCRIPT_RULES.script_exec_patterns ?? {}).legacy ?? []).map((r) => compileRuleRegex(r));
4964
+ KEYWORD_SETS = SCRIPT_RULES.keyword_sets ?? {};
4965
+ KEYWORD_COMBINATION_RISKS = (SCRIPT_RULES.keyword_combination_risks ?? []).map((r) => ({
4966
+ group1: resolveKeywordSet(r.group1, KEYWORD_SETS),
4967
+ group2: resolveKeywordSet(r.group2, KEYWORD_SETS),
4968
+ level: String(r.level),
4969
+ reason: String(r.reason)
4970
+ }));
4971
+ SINGLE_KEYWORD_RISKS = SCRIPT_RULES.single_keyword_risks ?? {};
5097
4972
  LEVEL_ORDER = { low: 1, medium: 2, high: 3, critical: 4 };
5098
4973
  }
5099
4974
  });
@@ -6637,8 +6512,8 @@ var init_tracker = __esm({
6637
6512
  });
6638
6513
 
6639
6514
  // src/engine/script-content-analyzer.ts
6640
- import { statSync as statSync4, existsSync as existsSync10 } from "fs";
6641
- import { resolve as resolve9, join as join6, isAbsolute as isAbsolute2 } from "path";
6515
+ import { statSync as statSync4, existsSync as existsSync11 } from "fs";
6516
+ import { resolve as resolve9, join as join10, isAbsolute as isAbsolute2 } from "path";
6642
6517
  import { homedir as homedir3 } from "os";
6643
6518
  function emptyChain() {
6644
6519
  return {
@@ -6671,10 +6546,10 @@ function assessKeywordCombinationRisk2(matchedKeywords, scriptContent) {
6671
6546
  const hitGroup2 = rule.group2.size === 0 ? true : [...rule.group2].some((k) => keywordSet.has(k));
6672
6547
  if (hitGroup1 && hitGroup2) return { level: rule.level, reason: rule.reason };
6673
6548
  }
6674
- const networkPostKeywords = /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]);
6549
+ const networkPostKeywords = new Set(KEYWORD_SETS2.network_post ?? []);
6675
6550
  const hasNetworkPost = [...networkPostKeywords].some((k) => keywordSet.has(k));
6676
6551
  if (hasNetworkPost) {
6677
- const envPatterns = ["$env", "$(env)", "$USER", "$(hostname)", "$(pwd)", "$HOME"];
6552
+ const envPatterns = SCRIPT_RULES2.env_exfil_patterns ?? [];
6678
6553
  const contentLower = scriptContent.toLowerCase();
6679
6554
  for (const p of envPatterns) {
6680
6555
  if (contentLower.includes(p.toLowerCase())) {
@@ -6750,7 +6625,7 @@ function extractInlineScriptPath(actionDetail) {
6750
6625
  if (looksLikeScriptPath(tok)) {
6751
6626
  try {
6752
6627
  const resolved = resolvePath2(tok.replace(/^['"]|['"]$/g, ""));
6753
- if (existsSync10(resolved)) {
6628
+ if (existsSync11(resolved)) {
6754
6629
  const stat = statSync4(resolved);
6755
6630
  if (stat.isFile()) return resolved;
6756
6631
  }
@@ -6764,7 +6639,7 @@ function extractInlineScriptPath(actionDetail) {
6764
6639
  const p = m[1];
6765
6640
  try {
6766
6641
  const resolved = resolvePath2(p);
6767
- if (existsSync10(resolved)) {
6642
+ if (existsSync11(resolved)) {
6768
6643
  const stat = statSync4(resolved);
6769
6644
  if (stat.isFile()) return resolved;
6770
6645
  }
@@ -6780,7 +6655,7 @@ function extractInlineScriptPath(actionDetail) {
6780
6655
  let resolved;
6781
6656
  try {
6782
6657
  resolved = resolvePath2(p);
6783
- if (existsSync10(resolved)) {
6658
+ if (existsSync11(resolved)) {
6784
6659
  const stat = statSync4(resolved);
6785
6660
  if (stat.isFile()) return resolved;
6786
6661
  }
@@ -6821,7 +6696,7 @@ function shlexSplit(s) {
6821
6696
  function resolvePath2(p) {
6822
6697
  let expanded = p;
6823
6698
  if (expanded.startsWith("~/") || expanded === "~") {
6824
- expanded = join6(homedir3(), expanded.slice(1));
6699
+ expanded = join10(homedir3(), expanded.slice(1));
6825
6700
  }
6826
6701
  return resolve9(expanded);
6827
6702
  }
@@ -6945,7 +6820,7 @@ function analyzeScriptContent2(input, ext) {
6945
6820
  isInline = true;
6946
6821
  }
6947
6822
  if (resolvedPath && !isAbsolute2(resolvedPath) && !resolvedPath.startsWith("~/")) {
6948
- resolvedPath = join6(input.cwd || process.cwd(), resolvedPath);
6823
+ resolvedPath = join10(input.cwd || process.cwd(), resolvedPath);
6949
6824
  }
6950
6825
  const l1Adapter = (content) => {
6951
6826
  const j = scanDangerousCommandPatterns("bash", content, input.osType);
@@ -7030,7 +6905,7 @@ ${contentPreview}
7030
6905
  if (chain.maxSizeReached) header += "\u26A0\uFE0F \u6CE8\u610F\uFF1A\u7D2F\u8BA1\u6587\u4EF6\u5927\u5C0F\u89E6\u8FBE\u4E0A\u9650\uFF0C\u90E8\u5206\u811A\u672C\u53EF\u80FD\u672A\u88AB\u5B8C\u6574\u8BFB\u53D6\u3002\n";
7031
6906
  return header + parts.join("\n---\n\n");
7032
6907
  }
7033
- var SUSPICIOUS_PATTERNS2, SCRIPT_EXEC_PATTERNS2, KEYWORD_COMBINATION_RISKS2, SINGLE_KEYWORD_RISKS2, LEVEL_ORDER2;
6908
+ var SCRIPT_RULES2, SUSPICIOUS_PATTERNS2, SCRIPT_EXEC_PATTERNS2, KEYWORD_SETS2, KEYWORD_COMBINATION_RISKS2, SINGLE_KEYWORD_RISKS2, LEVEL_ORDER2;
7034
6909
  var init_script_content_analyzer = __esm({
7035
6910
  "src/engine/script-content-analyzer.ts"() {
7036
6911
  "use strict";
@@ -7040,87 +6915,23 @@ var init_script_content_analyzer = __esm({
7040
6915
  init_logic_rules();
7041
6916
  init_inline_analyzer();
7042
6917
  init_tracker();
7043
- SUSPICIOUS_PATTERNS2 = [
7044
- { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
7045
- { name: "curl --data", pattern: /curl\s+(.*\s)?--data/i },
7046
- { name: "curl -d", pattern: /curl\s+(.*\s)?-d\s/i },
7047
- { name: "wget --post-data", pattern: /wget\s+(.*\s)?--post-data/i },
7048
- { name: "wget --post-file", pattern: /wget\s+(.*\s)?--post-file/i },
7049
- { name: "nc -e", pattern: /\bnc\s+.*-e\b/ },
7050
- { name: "ncat -e", pattern: /\bncat\s+.*-e\b/ },
7051
- { name: "/dev/tcp", pattern: /\/dev\/tcp\// },
7052
- { name: "/dev/udp", pattern: /\/dev\/udp\// },
7053
- { name: "$(base64", pattern: /\$\(\s*base64/ },
7054
- { name: "`base64", pattern: /`\s*base64/ },
7055
- { name: "base64 decode pipe", pattern: /base64\s+(-d|--decode)\s*\|/ },
7056
- { name: "/etc/shadow", pattern: /\/etc\/shadow/ },
7057
- { name: "id_rsa", pattern: /\bid_rsa\b/ },
7058
- { name: "id_ed25519", pattern: /\bid_ed25519\b/ },
7059
- { name: "crontab install", pattern: /crontab\s+-/ },
7060
- { name: "authorized_keys", pattern: /authorized_keys/ },
7061
- { name: "ssh no host key check", pattern: /ssh\s+.*StrictHostKeyChecking\s*=\s*no/ }
7062
- ];
7063
- SCRIPT_EXEC_PATTERNS2 = [
7064
- /(?:^|\s|&&|\|\|)\s*bash\s+(\S+)/i,
7065
- /(?:^|\s|&&|\|\|)\s*\/bin\/bash\s+(\S+)/i,
7066
- /(?:^|\s|&&|\|\|)\s*sh\s+(\S+)/i,
7067
- /(?:^|\s|&&|\|\|)\s*zsh\s+(\S+)/i,
7068
- /\.\/(\S+)/,
7069
- /(?:^|\s|&&|\|\|)\s*source\s+(\S+)/i,
7070
- /(?:^|\s|&&|\|\|)\s*\.\s+(\S+)/,
7071
- /(?:^|\s|&&|\|\|)\s*python3?\s+(\S+\.py)/i,
7072
- /(?:^|\s|&&|\|\|)\s*node\s+(\S+\.js)/i,
7073
- /(?:^|\s|&&|\|\|)\s*ruby\s+(\S+\.rb)/i,
7074
- /(?:^|\s|&&|\|\|)\s*perl\s+(\S+\.pl)/i,
7075
- /(?:^|\s|&&|\|\|)\s*php\s+(\S+\.php)/i,
7076
- /(?:^|\s|&&|\|\|)\s*lua\s+(\S+\.lua)/i
7077
- ];
7078
- KEYWORD_COMBINATION_RISKS2 = [
7079
- {
7080
- group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
7081
- group2: /* @__PURE__ */ new Set(["$(base64", "`base64", "base64 decode pipe"]),
7082
- level: "critical",
7083
- reason: "\u68C0\u6D4B\u5230\u7F51\u7EDC\u6570\u636E\u53D1\u9001 + Base64\u7F16\u7801\u7EC4\u5408\uFF0C\u6784\u6210 Critical \u7EA7\u522B\u6570\u636E\u5916\u4F20\u98CE\u9669\u3002\u653B\u51FB\u8005\u53EF\u80FD\u5C06\u654F\u611F\u6570\u636E\u7F16\u7801\u540E\u5916\u4F20\u3002"
7084
- },
7085
- {
7086
- group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
7087
- group2: /* @__PURE__ */ new Set(["/etc/shadow", "id_rsa", "id_ed25519"]),
7088
- level: "critical",
7089
- reason: "\u68C0\u6D4B\u5230\u7F51\u7EDC\u6570\u636E\u53D1\u9001 + \u654F\u611F\u6587\u4EF6\u8BBF\u95EE\u7EC4\u5408\uFF0C\u6784\u6210 Critical \u7EA7\u522B\u51ED\u8BC1\u5916\u4F20\u98CE\u9669\u3002"
7090
- },
7091
- {
7092
- group1: /* @__PURE__ */ new Set(["nc -e", "ncat -e", "/dev/tcp", "/dev/udp"]),
7093
- group2: /* @__PURE__ */ new Set(),
7094
- level: "high",
7095
- reason: "\u68C0\u6D4B\u5230\u53CD\u5F39 Shell \u7279\u5F81\uFF0C\u653B\u51FB\u8005\u53EF\u80FD\u5EFA\u7ACB\u8FDC\u7A0B\u63A7\u5236\u901A\u9053\u3002"
7096
- },
7097
- {
7098
- group1: /* @__PURE__ */ new Set(["crontab install", "authorized_keys", "ssh no host key check"]),
7099
- group2: /* @__PURE__ */ new Set(),
7100
- level: "high",
7101
- reason: "\u68C0\u6D4B\u5230\u6301\u4E45\u5316\u540E\u95E8\u7279\u5F81\uFF0C\u653B\u51FB\u8005\u53EF\u80FD\u5728\u7CFB\u7EDF\u4E2D\u690D\u5165\u540E\u95E8\u3002"
7102
- }
7103
- ];
7104
- SINGLE_KEYWORD_RISKS2 = {
7105
- "curl POST": { level: "high", reason: "\u68C0\u6D4B\u5230 curl POST \u8BF7\u6C42\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
7106
- "curl --data": { level: "high", reason: "\u68C0\u6D4B\u5230 curl --data \u53C2\u6570\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
7107
- "curl -d": { level: "high", reason: "\u68C0\u6D4B\u5230 curl -d \u53C2\u6570\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
7108
- "wget --post-data": { level: "high", reason: "\u68C0\u6D4B\u5230 wget POST \u8BF7\u6C42\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
7109
- "wget --post-file": { level: "high", reason: "\u68C0\u6D4B\u5230 wget \u6587\u4EF6\u4E0A\u4F20\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6587\u4EF6\u3002" },
7110
- "nc -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 nc -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
7111
- "ncat -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 ncat -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
7112
- "/dev/tcp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/tcp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
7113
- "/dev/udp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/udp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
7114
- "$(base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
7115
- "`base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
7116
- "base64 decode pipe": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u89E3\u7801\u540E\u7BA1\u9053\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
7117
- "/etc/shadow": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6\u3002" },
7118
- "id_rsa": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
7119
- "id_ed25519": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
7120
- "crontab install": { level: "high", reason: "\u68C0\u6D4B\u5230\u4FEE\u6539\u5B9A\u65F6\u4EFB\u52A1\uFF0C\u53EF\u80FD\u662F\u6301\u4E45\u5316\u540E\u95E8\u3002" },
7121
- "authorized_keys": { level: "high", reason: "\u68C0\u6D4B\u5230\u4FEE\u6539 SSH \u6388\u6743\u5BC6\u94A5\uFF0C\u53EF\u80FD\u662F\u6301\u4E45\u5316\u540E\u95E8\u3002" },
7122
- "ssh no host key check": { level: "high", reason: "\u68C0\u6D4B\u5230 SSH \u8DF3\u8FC7\u4E3B\u673A\u5BC6\u94A5\u9A8C\u8BC1\uFF0C\u53EF\u80FD\u662F\u6A2A\u5411\u79FB\u52A8\u3002" }
7123
- };
6918
+ init_rules_loader();
6919
+ SCRIPT_RULES2 = loadScriptRules();
6920
+ if (!SCRIPT_RULES2 || Object.keys(SCRIPT_RULES2).length === 0) {
6921
+ console.error(
6922
+ "[AgentMoss] \u274C \u89C4\u5219\u6587\u4EF6 script_rules.json \u7F3A\u5931\u6216\u635F\u574F \u2014\u2014 \u811A\u672C\u5185\u5BB9\u5173\u952E\u8BCD\u626B\u63CF\u5C06\u4E0D\u547D\u4E2D\u4EFB\u4F55\u6A21\u5F0F\uFF08\u6BD4\u6B63\u5E38\u66F4\u677E\uFF09\u3002\u8BF7\u68C0\u67E5\u5305\u5B8C\u6574\u6027\u3002"
6923
+ );
6924
+ }
6925
+ SUSPICIOUS_PATTERNS2 = (SCRIPT_RULES2.suspicious_patterns ?? []).map((r) => ({ name: r.name, pattern: compileRuleRegex(r) }));
6926
+ SCRIPT_EXEC_PATTERNS2 = ((SCRIPT_RULES2.script_exec_patterns ?? {}).chain ?? []).map((r) => compileRuleRegex(r));
6927
+ KEYWORD_SETS2 = SCRIPT_RULES2.keyword_sets ?? {};
6928
+ KEYWORD_COMBINATION_RISKS2 = (SCRIPT_RULES2.keyword_combination_risks ?? []).map((r) => ({
6929
+ group1: resolveKeywordSet(r.group1, KEYWORD_SETS2),
6930
+ group2: resolveKeywordSet(r.group2, KEYWORD_SETS2),
6931
+ level: String(r.level),
6932
+ reason: String(r.reason)
6933
+ }));
6934
+ SINGLE_KEYWORD_RISKS2 = SCRIPT_RULES2.single_keyword_risks ?? {};
7124
6935
  LEVEL_ORDER2 = { low: 1, medium: 2, high: 3, critical: 4 };
7125
6936
  }
7126
6937
  });
@@ -7135,16 +6946,16 @@ __export(token_stats_exports, {
7135
6946
  recordTokenUsage: () => recordTokenUsage,
7136
6947
  resetTokenStats: () => resetTokenStats
7137
6948
  });
7138
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync2, existsSync as existsSync11, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
6949
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync2, existsSync as existsSync12, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
7139
6950
  function getStatsPath() {
7140
6951
  const envPath = process.env[ENV_STATS_PATH];
7141
6952
  if (envPath) return envPath;
7142
6953
  return getTokenStatsPath();
7143
6954
  }
7144
6955
  function loadAllRecords(statsPath) {
7145
- if (!existsSync11(statsPath)) return [];
6956
+ if (!existsSync12(statsPath)) return [];
7146
6957
  try {
7147
- const data = JSON.parse(readFileSync11(statsPath, "utf-8"));
6958
+ const data = JSON.parse(readFileSync12(statsPath, "utf-8"));
7148
6959
  if (Array.isArray(data)) return data;
7149
6960
  if (data && Array.isArray(data.records)) return data.records;
7150
6961
  return [];
@@ -7155,7 +6966,7 @@ function loadAllRecords(statsPath) {
7155
6966
  function saveRecords(statsPath, records) {
7156
6967
  try {
7157
6968
  const dir = statsPath.substring(0, statsPath.lastIndexOf("/"));
7158
- if (dir && !existsSync11(dir)) mkdirSync2(dir, { recursive: true });
6969
+ if (dir && !existsSync12(dir)) mkdirSync2(dir, { recursive: true });
7159
6970
  writeFileSync2(statsPath, JSON.stringify(records, null, 2), "utf-8");
7160
6971
  } catch (e) {
7161
6972
  }
@@ -7282,7 +7093,7 @@ function getTokenTrend(days = 0, startDate, endDate) {
7282
7093
  }
7283
7094
  function resetTokenStats() {
7284
7095
  const statsPath = getStatsPath();
7285
- if (existsSync11(statsPath)) {
7096
+ if (existsSync12(statsPath)) {
7286
7097
  try {
7287
7098
  unlinkSync2(statsPath);
7288
7099
  } catch {
@@ -7320,10 +7131,10 @@ async function runLLMAnalysis(input, previous) {
7320
7131
  const config = input.llmConfig?.apiKey ? { ...globalConfig, ...input.llmConfig } : globalConfig;
7321
7132
  const rawDetail = input.aNext.actionDetail.replace(/^terminal-\w+:\s*command=([^,]+).*/, "$1").trim();
7322
7133
  const cacheKey = `${input.aNext.actionType}:${rawDetail || input.aNext.actionDetail}`;
7323
- const cached = llmCache.get(cacheKey);
7324
- if (cached) {
7134
+ const cached2 = llmCache.get(cacheKey);
7135
+ if (cached2) {
7325
7136
  console.log("[AgentMoss] LLM cache hit");
7326
- return cached;
7137
+ return cached2;
7327
7138
  }
7328
7139
  const matchedSkills = matchSkills(input.aNext.actionType, input.aNext.actionDetail, input.reason);
7329
7140
  const templates = loadTemplates();
@@ -7539,8 +7350,8 @@ var init_llm_analyzer = __esm({
7539
7350
  });
7540
7351
 
7541
7352
  // src/brain/rule-store.ts
7542
- import { existsSync as existsSync12, readFileSync as readFileSync12, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
7543
- import { join as join7 } from "path";
7353
+ import { existsSync as existsSync13, readFileSync as readFileSync13, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
7354
+ import { join as join11 } from "path";
7544
7355
  import { homedir as homedir4 } from "os";
7545
7356
  function getRulesPath() {
7546
7357
  const envPath = process.env[ENV_RULES_PATH];
@@ -7559,7 +7370,7 @@ var init_rule_store = __esm({
7559
7370
  "src/brain/rule-store.ts"() {
7560
7371
  "use strict";
7561
7372
  init_esm_shims();
7562
- DEFAULT_RULES_PATH = join7(homedir4(), ".agentmoss", "brain", "rules.json");
7373
+ DEFAULT_RULES_PATH = join11(homedir4(), ".agentmoss", "brain", "rules.json");
7563
7374
  ENV_RULES_PATH = "AGENT_MOSS_BRAIN_RULES_PATH";
7564
7375
  STATUS_DRAFT = "draft";
7565
7376
  STATUS_APPROVED = "approved";
@@ -7576,11 +7387,11 @@ var init_rule_store = __esm({
7576
7387
  this.data = this.load();
7577
7388
  }
7578
7389
  load() {
7579
- if (!existsSync12(this.path)) {
7390
+ if (!existsSync13(this.path)) {
7580
7391
  return { version: 1, rules: [], pattern_candidates: [] };
7581
7392
  }
7582
7393
  try {
7583
- const raw = JSON.parse(readFileSync12(this.path, "utf-8"));
7394
+ const raw = JSON.parse(readFileSync13(this.path, "utf-8"));
7584
7395
  return {
7585
7396
  version: raw.version || 1,
7586
7397
  rules: raw.rules || [],
@@ -8174,9 +7985,9 @@ var init_reviewer = __esm({
8174
7985
  });
8175
7986
 
8176
7987
  // src/brain/skill-manager.ts
8177
- import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync13, renameSync, writeFileSync as writeFileSync4 } from "fs";
7988
+ import { existsSync as existsSync14, mkdirSync as mkdirSync4, readFileSync as readFileSync14, renameSync, writeFileSync as writeFileSync4 } from "fs";
8178
7989
  import { homedir as homedir5 } from "os";
8179
- import { join as join8 } from "path";
7990
+ import { join as join12 } from "path";
8180
7991
  import { randomUUID } from "crypto";
8181
7992
  function getMetaPath(override) {
8182
7993
  return override || process.env[ENV_SKILL_META_PATH] || DEFAULT_SKILL_META_PATH;
@@ -8186,14 +7997,14 @@ var init_skill_manager = __esm({
8186
7997
  "src/brain/skill-manager.ts"() {
8187
7998
  "use strict";
8188
7999
  init_esm_shims();
8189
- DEFAULT_SKILL_META_PATH = join8(homedir5(), ".agentmoss", "brain", "skill_meta.json");
8000
+ DEFAULT_SKILL_META_PATH = join12(homedir5(), ".agentmoss", "brain", "skill_meta.json");
8190
8001
  ENV_SKILL_META_PATH = "AGENT_MOSS_BRAIN_SKILL_META_PATH";
8191
8002
  BrainSkillManager = class {
8192
8003
  constructor(skillsDir, metaPath) {
8193
8004
  this.skillsDir = skillsDir;
8194
8005
  this.metaPath = getMetaPath(metaPath);
8195
8006
  try {
8196
- mkdirSync4(join8(this.metaPath, ".."), { recursive: true });
8007
+ mkdirSync4(join12(this.metaPath, ".."), { recursive: true });
8197
8008
  } catch {
8198
8009
  }
8199
8010
  this.load();
@@ -8203,9 +8014,9 @@ var init_skill_manager = __esm({
8203
8014
  skills = [];
8204
8015
  metaPath;
8205
8016
  load() {
8206
- if (!existsSync13(this.metaPath)) return;
8017
+ if (!existsSync14(this.metaPath)) return;
8207
8018
  try {
8208
- const data = JSON.parse(readFileSync13(this.metaPath, "utf-8"));
8019
+ const data = JSON.parse(readFileSync14(this.metaPath, "utf-8"));
8209
8020
  this.skills = data.skills || [];
8210
8021
  } catch {
8211
8022
  this.skills = [];
@@ -8228,7 +8039,7 @@ var init_skill_manager = __esm({
8228
8039
  let changed = false;
8229
8040
  for (const sk of this.skills) {
8230
8041
  if (sk.status !== "active") continue;
8231
- if (!existsSync13(join8(this.skillsDir, `${sk.name}.md`))) {
8042
+ if (!existsSync14(join12(this.skillsDir, `${sk.name}.md`))) {
8232
8043
  sk.status = "stale";
8233
8044
  changed = true;
8234
8045
  }
@@ -8239,9 +8050,9 @@ var init_skill_manager = __esm({
8239
8050
  addSkill(skill) {
8240
8051
  const id = skill.id || randomUUID();
8241
8052
  const name = skill.name || `brain_${id.slice(0, 8)}`;
8242
- if (!existsSync13(this.skillsDir)) return "";
8243
- const mdPath = join8(this.skillsDir, `${name}.md`);
8244
- if (existsSync13(mdPath)) return "";
8053
+ if (!existsSync14(this.skillsDir)) return "";
8054
+ const mdPath = join12(this.skillsDir, `${name}.md`);
8055
+ if (existsSync14(mdPath)) return "";
8245
8056
  try {
8246
8057
  writeFileSync4(mdPath, skill.content, "utf-8");
8247
8058
  } catch {
@@ -8290,12 +8101,12 @@ var init_skill_manager = __esm({
8290
8101
  const out = [];
8291
8102
  for (const sk of this.skills) {
8292
8103
  if (sk.origin_node !== "local" || sk.status !== "active") continue;
8293
- const mdPath = join8(this.skillsDir, `${sk.name}.md`);
8294
- if (!existsSync13(mdPath)) continue;
8104
+ const mdPath = join12(this.skillsDir, `${sk.name}.md`);
8105
+ if (!existsSync14(mdPath)) continue;
8295
8106
  out.push({
8296
8107
  name: sk.name,
8297
8108
  category: sk.category || "",
8298
- content: readFileSync13(mdPath, "utf-8"),
8109
+ content: readFileSync14(mdPath, "utf-8"),
8299
8110
  keywords: sk.keywords || []
8300
8111
  });
8301
8112
  }
@@ -8407,7 +8218,7 @@ __export(contributor_exports, {
8407
8218
  });
8408
8219
  import { mkdirSync as mkdirSync5, mkdtempSync, rmSync, writeFileSync as writeFileSync5 } from "fs";
8409
8220
  import { tmpdir } from "os";
8410
- import { join as join9 } from "path";
8221
+ import { join as join13 } from "path";
8411
8222
  import { spawnSync } from "child_process";
8412
8223
  import { dirname as dirname9 } from "path";
8413
8224
  import { fileURLToPath as fileURLToPath9 } from "url";
@@ -8428,32 +8239,32 @@ function collectSkills(skillMgr) {
8428
8239
  }
8429
8240
  function writeExportFiles(outDir, rules, skills) {
8430
8241
  const written = [];
8431
- const rulesDir = join9(outDir, "rules");
8432
- mkdirSync5(rulesDir, { recursive: true });
8242
+ const rulesDir2 = join13(outDir, "rules");
8243
+ mkdirSync5(rulesDir2, { recursive: true });
8433
8244
  const byLayer = /* @__PURE__ */ new Map();
8434
8245
  for (const r of rules) {
8435
8246
  if (!byLayer.has(r.layer)) byLayer.set(r.layer, []);
8436
8247
  byLayer.get(r.layer).push(r);
8437
8248
  }
8438
8249
  for (const layer of [...byLayer.keys()].sort((a, b) => a - b)) {
8439
- const path6 = join9(rulesDir, `l${layer}_brain_rules.json`);
8250
+ const path6 = join13(rulesDir2, `l${layer}_brain_rules.json`);
8440
8251
  writeFileSync5(path6, JSON.stringify(byLayer.get(layer), null, 2), "utf-8");
8441
8252
  written.push(path6);
8442
8253
  }
8443
8254
  if (rules.length === 0) {
8444
- const path6 = join9(rulesDir, "empty.txt");
8255
+ const path6 = join13(rulesDir2, "empty.txt");
8445
8256
  writeFileSync5(path6, "(no brain rules to contribute)", "utf-8");
8446
8257
  written.push(path6);
8447
8258
  }
8448
- const skillsDir = join9(outDir, "skills");
8259
+ const skillsDir = join13(outDir, "skills");
8449
8260
  mkdirSync5(skillsDir, { recursive: true });
8450
8261
  for (const sk of skills) {
8451
- const path6 = join9(skillsDir, `${sk.name}.md`);
8262
+ const path6 = join13(skillsDir, `${sk.name}.md`);
8452
8263
  writeFileSync5(path6, sk.content || "", "utf-8");
8453
8264
  written.push(path6);
8454
8265
  }
8455
8266
  if (skills.length === 0) {
8456
- const path6 = join9(skillsDir, "empty.txt");
8267
+ const path6 = join13(skillsDir, "empty.txt");
8457
8268
  writeFileSync5(path6, "(no brain skills to contribute)", "utf-8");
8458
8269
  written.push(path6);
8459
8270
  }
@@ -8583,7 +8394,7 @@ async function contribute(opts = {}) {
8583
8394
  console.log(`[Brain] push \u76EE\u6807: ${pushTarget}`);
8584
8395
  console.log(`[Brain] MR \u6307\u5411: ${DEFAULT_TARGET_REPO}`);
8585
8396
  const store = new BrainRuleStore();
8586
- const skillsDir = resolveAssetDir(__dirname8, "skills") ?? join9(__dirname8, "..", "..", "agent_moss", "skills");
8397
+ const skillsDir = resolveAssetDir(__dirname8, "skills") ?? join13(__dirname8, "..", "..", "agent_moss", "skills");
8587
8398
  const skillMgr = new BrainSkillManager(skillsDir);
8588
8399
  let rules;
8589
8400
  let skills;
@@ -8598,9 +8409,9 @@ async function contribute(opts = {}) {
8598
8409
  return 0;
8599
8410
  }
8600
8411
  console.log(`[Brain] \u5BFC\u51FA: ${rules.length} \u6761\u89C4\u5219, ${skills.length} \u4E2A Skill`);
8601
- const workdir = mkdtempSync(join9(tmpdir(), "agentmoss-contrib-"));
8412
+ const workdir = mkdtempSync(join13(tmpdir(), "agentmoss-contrib-"));
8602
8413
  try {
8603
- writeExportFiles(join9(workdir, "agent_moss", "brain_contrib"), rules, skills);
8414
+ writeExportFiles(join13(workdir, "agent_moss", "brain_contrib"), rules, skills);
8604
8415
  console.log(`[Brain] \u63A2\u6D4B ${pushTarget} \u8BBF\u95EE\u6743\u9650 ...`);
8605
8416
  if (!probeAccess(pushTarget)) {
8606
8417
  const username = getCurrentUsername();
@@ -8848,9 +8659,10 @@ var init_decode = __esm({
8848
8659
  "src/engine/formal-engine/decode.ts"() {
8849
8660
  "use strict";
8850
8661
  init_esm_shims();
8662
+ init_rules_loader();
8851
8663
  B64_TOKEN_RE = /[A-Za-z0-9+/]+={0,2}/g;
8852
8664
  QUOTED_STR_RE = /('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`)/g;
8853
- OUTPUT_CMDS = /* @__PURE__ */ new Set(["echo", "printf", "print"]);
8665
+ OUTPUT_CMDS = new Set(loadFormalRules().output_commands ?? []);
8854
8666
  STAGE_PRIORITY = ["EXFIL", "PRIVESC", "PERSIST", "SCAN", "LISTEN"];
8855
8667
  PURPOSE_EXEC_RE = /subprocess\.\w+\s*\(|os\.system\s*\(|shell\s*=\s*True|\beval\s*\(|\bexec\s*\(|os\.popen\s*\(|\/exec\b/i;
8856
8668
  LISTEN_LEAD_RE = /http\.server|flask\s+run|nohup\b|nc\s+-l|socat\s+TCP-LISTEN/i;
@@ -9568,7 +9380,7 @@ function checkSensitiveDataAggregation(graph) {
9568
9380
  }
9569
9381
  return violations;
9570
9382
  }
9571
- var CROSS_SESSION_CHAINS, CRED_FILE_RE, EXFIL_RE, EBPF_NET_RE, REMOTE_RE, EXFIL_ACTION_RE, CRED_STRICT_RE, INLINE_SHELL_RE, PKG_MGR, SHELLS, TOOL_REGISTRY, COMPUTE_RE, STRONG_COMPUTE_TYPES, DL_OR_LOW_RE, ALL_PROPERTIES;
9383
+ var CROSS_SESSION_CHAINS, CRED_FILE_RE, EXFIL_RE, EBPF_NET_RE, REMOTE_RE, EXFIL_ACTION_RE, CRED_STRICT_RE, INLINE_SHELL_RE, FORMAL_RULES, PKG_MGR, SHELLS, TOOL_REGISTRY, COMPUTE_RE, STRONG_COMPUTE_TYPES, DL_OR_LOW_RE, ALL_PROPERTIES;
9572
9384
  var init_properties = __esm({
9573
9385
  "src/engine/formal-engine/properties.ts"() {
9574
9386
  "use strict";
@@ -9585,11 +9397,12 @@ var init_properties = __esm({
9585
9397
  EXFIL_ACTION_RE = /\bcurl\b|\bwget\b|\bscp\b|\brsync\b|\brclone\b|\bftp\b|connect\s+.*\d+\.\d+\.\d+\.\d+|open\s+.*tcp|base64\s+.*\-o/i;
9586
9398
  CRED_STRICT_RE = /(known_hosts|\.ssh|id_rsa|\.pem|\b\.key\b|\/etc\/shadow|\/etc\/passwd|\.aws\/credentials|credentials\.json|netrc|password|secrets?\.ya?ml|credentials?\s*(file|path)?\s*(=|\:)|\bconfig.*(password|token))/i;
9587
9399
  INLINE_SHELL_RE = /(?:os\.system|os\.popen)\s*\(\s*['"]([^'"]+)['"]|subprocess\.[a-z_]+\(\s*\[([^\]]+)\]|subprocess\.[a-z_]+\(\s*['"]([^'"]+)['"]|(?:bash|sh|zsh)\s+-c\s*['"]([^'"]+)['"]/gi;
9588
- PKG_MGR = /* @__PURE__ */ new Set(["apt", "apt-get", "aptitude", "yum", "dnf", "pacman", "zypper", "apk", "brew"]);
9589
- SHELLS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "fish", "ksh", "csh", "tcsh", "nologin"]);
9400
+ FORMAL_RULES = loadFormalRules();
9401
+ PKG_MGR = new Set(FORMAL_RULES.privesc?.package_managers ?? []);
9402
+ SHELLS = new Set(FORMAL_RULES.privesc?.bare_shells ?? []);
9590
9403
  TOOL_REGISTRY = null;
9591
9404
  COMPUTE_RE = /subprocess|os\.system|shell\s*=\s*True|\beval\(|\bexec\(|bash\s+[^\s]|sh\s+-c|python3?\s+|node\s+|\btimeout\s+|nohup\s+|\b\.\/[\w./-]+/i;
9592
- STRONG_COMPUTE_TYPES = /* @__PURE__ */ new Set(["python", "node", "exec", "terminal", "cmd"]);
9405
+ STRONG_COMPUTE_TYPES = new Set(FORMAL_RULES.compute_action_types ?? []);
9593
9406
  DL_OR_LOW_RE = /curl\s+.*-o\b|wget\s+.*\s+-O\b|curl\s+.*\|\s*(bash|sh|python)|wget\s+.*\|\s*(bash|sh|python)|git\s+clone|docker\s+pull|pip\s+install|npm\s+install|base64\s+-d/i;
9594
9407
  ALL_PROPERTIES = [
9595
9408
  { id: "cross-session-chain", check: checkCrossSessionChain, shortName: "P1" },
@@ -10113,10 +9926,10 @@ var init_stateful = __esm({
10113
9926
  import * as fs4 from "fs";
10114
9927
  import * as os2 from "os";
10115
9928
  import * as path4 from "path";
10116
- import { createHash } from "crypto";
9929
+ import { createHash as createHash2 } from "crypto";
10117
9930
  function defaultStateDbPath() {
10118
9931
  const dir = process.env.AGENT_MOSS_STATE_DIR || path4.join(os2.homedir(), ".agentmoss");
10119
- const tag = createHash("sha1").update("agentmoss-stateful").digest("hex").slice(0, 8);
9932
+ const tag = createHash2("sha1").update("agentmoss-stateful").digest("hex").slice(0, 8);
10120
9933
  return path4.join(dir, `stateful_chains_${tag}.json`);
10121
9934
  }
10122
9935
  var ChainStateStore;
@@ -10376,7 +10189,7 @@ async function runAnalysis(input) {
10376
10189
  const actionType = input.aNext.actionType.toLowerCase();
10377
10190
  const rawDetail = extractRawCommand(input.aNext.actionDetail);
10378
10191
  const lowerDetail = rawDetail.toLowerCase();
10379
- if (FULLY_SAFE_TOOLS.has(actionType)) {
10192
+ if (getPlatformTraits(input.osType).safeActionTypes.has(actionType)) {
10380
10193
  return {
10381
10194
  allowed: true,
10382
10195
  reason: `\u5B8C\u5168\u5B89\u5168\u5DE5\u5177\uFF0C\u8DF3\u8FC7\u6DF1\u5EA6\u5206\u6790: ${actionType}`,
@@ -10419,7 +10232,7 @@ async function runAnalysis(input) {
10419
10232
  violatedLayers: []
10420
10233
  };
10421
10234
  }
10422
- let skipLLM = READONLY_SENSITIVE_TOOLS.has(actionType);
10235
+ let skipLLM = getPlatformTraits(input.osType).readonlySensitiveActionTypes.has(actionType);
10423
10236
  if (actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash") {
10424
10237
  const firstCmd = extractFirstCommand(lowerDetail);
10425
10238
  const { fullySafeCommands, readonlySensitiveCommands } = getPlatformTraits(input.osType);
@@ -10651,7 +10464,7 @@ async function runAnalysis(input) {
10651
10464
  }
10652
10465
  return llmResult;
10653
10466
  }
10654
- var FULLY_SAFE_TOOLS, TRUSTED_SKILLS_BASE, READONLY_SENSITIVE_TOOLS, _brainStore, _brainExtractor, _brainSkillManager, _brainCurator, _brainInitialized, _brainLastMaintenance, BRAIN_MAINT_INTERVAL_MS, formalEngine;
10467
+ var TRUSTED_SKILLS_BASE, _brainStore, _brainExtractor, _brainSkillManager, _brainCurator, _brainInitialized, _brainLastMaintenance, BRAIN_MAINT_INTERVAL_MS, formalEngine;
10655
10468
  var init_coordinator = __esm({
10656
10469
  "src/engine/coordinator.ts"() {
10657
10470
  "use strict";
@@ -10674,25 +10487,7 @@ var init_coordinator = __esm({
10674
10487
  init_asset_dir();
10675
10488
  init_runtime_config_bridge();
10676
10489
  init_formal_engine();
10677
- FULLY_SAFE_TOOLS = /* @__PURE__ */ new Set([
10678
- "ask_user_question",
10679
- "glob",
10680
- "list_dir",
10681
- "ls",
10682
- "count_text_length",
10683
- "filemgr-globfiles"
10684
- ]);
10685
10490
  TRUSTED_SKILLS_BASE = /* @__PURE__ */ new Set([]);
10686
- READONLY_SENSITIVE_TOOLS = /* @__PURE__ */ new Set([
10687
- "read",
10688
- "file_read",
10689
- "read_file",
10690
- "head",
10691
- "tail",
10692
- "grep",
10693
- "filemgr-readfile",
10694
- "filemgr-grepfiles"
10695
- ]);
10696
10491
  _brainStore = null;
10697
10492
  _brainExtractor = null;
10698
10493
  _brainSkillManager = null;
@@ -10705,10 +10500,10 @@ var init_coordinator = __esm({
10705
10500
  });
10706
10501
 
10707
10502
  // src/infra/policy-cache.ts
10708
- import { createHash as createHash2 } from "crypto";
10709
- import { existsSync as existsSync15, mkdirSync as mkdirSync7, readdirSync as readdirSync5, readFileSync as readFileSync15, rmdirSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync7 } from "fs";
10503
+ import { createHash as createHash3 } from "crypto";
10504
+ import { existsSync as existsSync16, mkdirSync as mkdirSync7, readdirSync as readdirSync5, readFileSync as readFileSync16, rmdirSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync7 } from "fs";
10710
10505
  import { tmpdir as tmpdir2 } from "os";
10711
- import { join as join11 } from "path";
10506
+ import { join as join15 } from "path";
10712
10507
  function cacheDir() {
10713
10508
  return cacheDirOverride ?? CACHE_DIR;
10714
10509
  }
@@ -10722,7 +10517,7 @@ var init_policy_cache = __esm({
10722
10517
  "use strict";
10723
10518
  init_esm_shims();
10724
10519
  init_config();
10725
- CACHE_DIR = join11(tmpdir2(), "agent_moss_policy_cache");
10520
+ CACHE_DIR = join15(tmpdir2(), "agent_moss_policy_cache");
10726
10521
  cacheDirOverride = null;
10727
10522
  PolicyCache = class {
10728
10523
  // Map 的插入序即 LRU 序(get 时重新 set 到末尾)
@@ -10735,9 +10530,9 @@ var init_policy_cache = __esm({
10735
10530
  return `${sessionId}::${promptSession}`;
10736
10531
  }
10737
10532
  cacheFilePath(sessionId, promptSession) {
10738
- const promptHash = createHash2("md5").update(promptSession).digest("hex").slice(0, 12);
10533
+ const promptHash = createHash3("md5").update(promptSession).digest("hex").slice(0, 12);
10739
10534
  const safeSessionId = sessionId.replace(/\//g, "_").replace(/\\/g, "_");
10740
- return join11(cacheDir(), safeSessionId, `${promptHash}.toml`);
10535
+ return join15(cacheDir(), safeSessionId, `${promptHash}.toml`);
10741
10536
  }
10742
10537
  /** 取缓存:内存 → 文件;未命中返回 null。 */
10743
10538
  get(sessionId, promptSession) {
@@ -10750,9 +10545,9 @@ var init_policy_cache = __esm({
10750
10545
  return hit;
10751
10546
  }
10752
10547
  const file = this.cacheFilePath(sessionId, promptSession);
10753
- if (existsSync15(file)) {
10548
+ if (existsSync16(file)) {
10754
10549
  try {
10755
- const policy = readFileSync15(file, "utf-8");
10550
+ const policy = readFileSync16(file, "utf-8");
10756
10551
  this.remember(key, policy);
10757
10552
  return policy;
10758
10553
  } catch {
@@ -10766,7 +10561,7 @@ var init_policy_cache = __esm({
10766
10561
  this.remember(this.cacheKey(sessionId, promptSession), policy);
10767
10562
  const file = this.cacheFilePath(sessionId, promptSession);
10768
10563
  try {
10769
- mkdirSync7(join11(cacheDir(), sessionId.replace(/\//g, "_").replace(/\\/g, "_")), { recursive: true });
10564
+ mkdirSync7(join15(cacheDir(), sessionId.replace(/\//g, "_").replace(/\\/g, "_")), { recursive: true });
10770
10565
  writeFileSync7(file, policy, "utf-8");
10771
10566
  } catch {
10772
10567
  }
@@ -10792,12 +10587,12 @@ var init_policy_cache = __esm({
10792
10587
  if (k.startsWith(`${sessionId}::`)) this.memory.delete(k);
10793
10588
  }
10794
10589
  const safeSessionId = sessionId.replace(/\//g, "_").replace(/\\/g, "_");
10795
- const dir = join11(cacheDir(), safeSessionId);
10590
+ const dir = join15(cacheDir(), safeSessionId);
10796
10591
  try {
10797
- if (existsSync15(dir)) {
10592
+ if (existsSync16(dir)) {
10798
10593
  for (const f of readdirSync5(dir)) {
10799
10594
  try {
10800
- unlinkSync3(join11(dir, f));
10595
+ unlinkSync3(join15(dir, f));
10801
10596
  } catch {
10802
10597
  }
10803
10598
  }
@@ -10809,11 +10604,11 @@ var init_policy_cache = __esm({
10809
10604
  clear() {
10810
10605
  this.memory.clear();
10811
10606
  try {
10812
- if (existsSync15(cacheDir())) {
10607
+ if (existsSync16(cacheDir())) {
10813
10608
  for (const sessionDir of readdirSync5(cacheDir())) {
10814
- const dir = join11(cacheDir(), sessionDir);
10609
+ const dir = join15(cacheDir(), sessionDir);
10815
10610
  try {
10816
- for (const f of readdirSync5(dir)) unlinkSync3(join11(dir, f));
10611
+ for (const f of readdirSync5(dir)) unlinkSync3(join15(dir, f));
10817
10612
  rmdirSync(dir);
10818
10613
  } catch {
10819
10614
  }
@@ -10909,9 +10704,9 @@ __export(analyzer_exports, {
10909
10704
  savePolicyToFile: () => savePolicyToFile,
10910
10705
  validateToml: () => validateToml
10911
10706
  });
10912
- import { existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync16, writeFileSync as writeFileSync8 } from "fs";
10707
+ import { existsSync as existsSync17, mkdirSync as mkdirSync8, readFileSync as readFileSync17, writeFileSync as writeFileSync8 } from "fs";
10913
10708
  import { tmpdir as tmpdir3 } from "os";
10914
- import { join as join12, resolve as resolve10 } from "path";
10709
+ import { join as join16, resolve as resolve10 } from "path";
10915
10710
  import { parse as parseToml } from "smol-toml";
10916
10711
  function _setPolicyOutputDirForTest(dir) {
10917
10712
  policyOutputDirOverride = dir;
@@ -10925,7 +10720,7 @@ function savePolicyToFile(sessionId, policyToml) {
10925
10720
  const outDir = policyOutputDir();
10926
10721
  mkdirSync8(outDir, { recursive: true });
10927
10722
  const safeName = sessionId.replace(/\//g, "_").replace(/\\/g, "_");
10928
- const filepath = join12(outDir, `${safeName}.toml`);
10723
+ const filepath = join16(outDir, `${safeName}.toml`);
10929
10724
  writeFileSync8(filepath, policyToml, "utf-8");
10930
10725
  return filepath;
10931
10726
  } catch (err) {
@@ -10964,11 +10759,11 @@ function loadPolicyMappingDoc() {
10964
10759
  const dir = resolveAssetDir(__dirname, "templates");
10965
10760
  if (dir) {
10966
10761
  const p = resolve10(dir, "policy_mapping.md");
10967
- if (existsSync16(p)) return readFileSync16(p, "utf-8");
10762
+ if (existsSync17(p)) return readFileSync17(p, "utf-8");
10968
10763
  }
10969
10764
  for (const cand of assetDirCandidates(__dirname, "templates")) {
10970
10765
  const p = resolve10(cand, "policy_mapping.md");
10971
- if (existsSync16(p)) return readFileSync16(p, "utf-8");
10766
+ if (existsSync17(p)) return readFileSync17(p, "utf-8");
10972
10767
  }
10973
10768
  return "";
10974
10769
  }
@@ -11073,12 +10868,12 @@ async function analyzeInner(input, mossLog) {
11073
10868
  }
11074
10869
  const cache3 = getDefaultCache();
11075
10870
  mossLog.startStep("step2_cache_lookup");
11076
- const cached = cache3.get(input.sessionId, input.promptSession);
11077
- if (cached !== null) {
10871
+ const cached2 = cache3.get(input.sessionId, input.promptSession);
10872
+ if (cached2 !== null) {
11078
10873
  mossLog.endStep("step2_cache_lookup", "\u7F13\u5B58\u547D\u4E2D");
11079
- savePolicyToFile(input.sessionId, cached);
11080
- const res = allowResult(cached, judgment);
11081
- mossLog.logResult("Allow", res.reason, cached);
10874
+ savePolicyToFile(input.sessionId, cached2);
10875
+ const res = allowResult(cached2, judgment);
10876
+ mossLog.logResult("Allow", res.reason, cached2);
11082
10877
  return res;
11083
10878
  }
11084
10879
  if (!isPolicyGenEnabled()) {
@@ -11138,7 +10933,7 @@ var init_analyzer = __esm({
11138
10933
  init_asset_dir();
11139
10934
  init_token_stats();
11140
10935
  init_logging();
11141
- POLICY_OUTPUT_DIR = join12(tmpdir3(), "agent_moss");
10936
+ POLICY_OUTPUT_DIR = join16(tmpdir3(), "agent_moss");
11142
10937
  policyOutputDirOverride = null;
11143
10938
  DEFAULT_READONLY_POLICY = `landlock_optional = false
11144
10939
  mount_isolation_fallback = false
@@ -11361,14 +11156,16 @@ function mineDangerousPatterns(sessions, minSup = 2, maxLen = 4) {
11361
11156
  out.sort((a, b) => b.support - a.support || b.pattern.length - a.pattern.length);
11362
11157
  return out;
11363
11158
  }
11364
- var DANGER_STAGES, SENSITIVE_PAIRS;
11159
+ var _PM, DANGER_STAGES, SENSITIVE_PAIRS;
11365
11160
  var init_pattern_miner = __esm({
11366
11161
  "src/engine/formal-engine/pattern_miner.ts"() {
11367
11162
  "use strict";
11368
11163
  init_esm_shims();
11164
+ init_rules_loader();
11369
11165
  init_builder();
11370
- DANGER_STAGES = /* @__PURE__ */ new Set(["PRIVESC", "EXFIL", "LISTEN"]);
11371
- SENSITIVE_PAIRS = /* @__PURE__ */ new Set(["CRED|LISTEN", "LISTEN|CRED"]);
11166
+ _PM = loadFormalRules().pattern_miner ?? {};
11167
+ DANGER_STAGES = new Set(_PM.danger_stages ?? []);
11168
+ SENSITIVE_PAIRS = new Set((_PM.sensitive_pairs ?? []).map((p) => p.join("|")));
11372
11169
  }
11373
11170
  });
11374
11171
 
@@ -11548,7 +11345,7 @@ var init_observable = __esm({
11548
11345
  });
11549
11346
 
11550
11347
  // src/routes.ts
11551
- import { join as join14, dirname as dirname11 } from "path";
11348
+ import { join as join18, dirname as dirname11 } from "path";
11552
11349
  import { fileURLToPath as fileURLToPath10 } from "url";
11553
11350
  async function healthRoute(c) {
11554
11351
  return c.json(HealthResponseSchema.parse({
@@ -11797,7 +11594,7 @@ function getBrainRuleStore() {
11797
11594
  return new BrainRuleStore();
11798
11595
  }
11799
11596
  function getBrainSkillManager() {
11800
- const dir = resolveAssetDir(__dirname9, "skills") ?? join14(__dirname9, "..", "..", "agent_moss", "skills");
11597
+ const dir = resolveAssetDir(__dirname9, "skills") ?? join18(__dirname9, "..", "..", "agent_moss", "skills");
11801
11598
  return new BrainSkillManager(dir);
11802
11599
  }
11803
11600
  async function brainRulesRoute(c) {
@@ -11910,8 +11707,8 @@ var init_routes = __esm({
11910
11707
 
11911
11708
  // src/console/router.ts
11912
11709
  import { Hono } from "hono";
11913
- import { readFileSync as readFileSync18, existsSync as existsSync18, unlinkSync as unlinkSync4 } from "fs";
11914
- import { join as join15, dirname as dirname12 } from "path";
11710
+ import { readFileSync as readFileSync19, existsSync as existsSync19, unlinkSync as unlinkSync4 } from "fs";
11711
+ import { join as join19, dirname as dirname12 } from "path";
11915
11712
  import { fileURLToPath as fileURLToPath11 } from "url";
11916
11713
  function getConsoleToken() {
11917
11714
  return process.env[CONSOLE_TOKEN_ENV] || "";
@@ -11931,21 +11728,35 @@ function authMiddleware() {
11931
11728
  await next();
11932
11729
  };
11933
11730
  }
11731
+ function displayRuntimeConfig() {
11732
+ return isRuntimeConfigDisabled() ? generateSourceDefaults() : loadRuntimeConfig();
11733
+ }
11734
+ function builtinOnlyReadOnlyMiddleware() {
11735
+ return async (c, next) => {
11736
+ if (isRuntimeConfigDisabled() && !["GET", "HEAD", "OPTIONS"].includes(c.req.method)) {
11737
+ return c.json({
11738
+ detail: `\u89C4\u5219\u6765\u6E90\u6A21\u5F0F\u4E3A ${getRulesMode()}\uFF08runtime \u914D\u7F6E\u5DF2\u7981\u7528\uFF09\uFF1AConsole \u53EA\u8BFB\uFF0C\u4FEE\u6539\u4E0D\u4F1A\u4FDD\u5B58\u3002\u8BF7\u5207\u6362\u89C4\u5219\u6765\u6E90\u6A21\u5F0F\u5E76\u91CD\u542F\u670D\u52A1\u540E\u518D\u6539\u89C4\u5219\u3002`
11739
+ }, 403);
11740
+ }
11741
+ await next();
11742
+ };
11743
+ }
11934
11744
  function createConsoleRouter() {
11935
11745
  const console2 = new Hono();
11936
11746
  console2.use("/api/*", authMiddleware());
11747
+ console2.use("/api/*", builtinOnlyReadOnlyMiddleware());
11937
11748
  const serveIndex = (c) => {
11938
- const indexFile = join15(STATIC_DIR, "index.html");
11939
- if (existsSync18(indexFile)) {
11940
- return c.html(readFileSync18(indexFile, "utf-8"));
11749
+ const indexFile = join19(STATIC_DIR, "index.html");
11750
+ if (existsSync19(indexFile)) {
11751
+ return c.html(readFileSync19(indexFile, "utf-8"));
11941
11752
  }
11942
11753
  return c.html("<h1>AgentMoss Policy Console</h1><p>Frontend not built. Use /console/api/* endpoints.</p>");
11943
11754
  };
11944
11755
  console2.get("/", serveIndex);
11945
11756
  console2.get("", serveIndex);
11946
11757
  console2.get("/api/layers", (c) => {
11947
- const rc = loadRuntimeConfig();
11948
- return c.json({ layers: rc.layers, env_overrides: getEnvOverrides() });
11758
+ const rc = displayRuntimeConfig();
11759
+ return c.json({ layers: rc.layers, env_overrides: getEnvOverrides(), builtin_only: isBuiltinOnly(), rules_mode: getRulesMode() });
11949
11760
  });
11950
11761
  console2.put("/api/layers", async (c) => {
11951
11762
  const body = await c.req.json();
@@ -11980,7 +11791,7 @@ function createConsoleRouter() {
11980
11791
  const rules = mc.rules || [];
11981
11792
  let catEnabled = true;
11982
11793
  try {
11983
- const rt = loadRuntimeConfig();
11794
+ const rt = displayRuntimeConfig();
11984
11795
  catEnabled = rt.L2_2?.ltl_rules?.category_enabled !== false;
11985
11796
  } catch {
11986
11797
  }
@@ -12036,7 +11847,7 @@ function createConsoleRouter() {
12036
11847
  return rc;
12037
11848
  }
12038
11849
  console2.get("/api/rules", (c) => {
12039
- const rc = loadRuntimeConfig();
11850
+ const rc = displayRuntimeConfig();
12040
11851
  const layer = c.req.query("layer");
12041
11852
  const category = c.req.query("category");
12042
11853
  const out = {};
@@ -12101,8 +11912,8 @@ function createConsoleRouter() {
12101
11912
  return c.json(withL22View(rc));
12102
11913
  });
12103
11914
  console2.get("/api/skills", (c) => {
12104
- const rc = loadRuntimeConfig();
12105
- return c.json(rc.L3_skills);
11915
+ const rc = displayRuntimeConfig();
11916
+ return c.json(rc.L3_skills || {});
12106
11917
  });
12107
11918
  console2.put("/api/skills/enabled", async (c) => {
12108
11919
  const b = await c.req.json();
@@ -12132,18 +11943,21 @@ function createConsoleRouter() {
12132
11943
  if (!/^[A-Za-z0-9_-]+$/.test(skillId)) {
12133
11944
  return c.json({ detail: "\u975E\u6CD5 skill id" }, 400);
12134
11945
  }
12135
- const userPath = join15(dirname12(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
11946
+ const userPath = join19(dirname12(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
12136
11947
  const builtinDir = resolveAssetDir(__dirname10, "skills");
12137
- const candidates = [userPath, builtinDir ? join15(builtinDir, `${skillId}.md`) : ""].filter(Boolean);
11948
+ const candidates = [
11949
+ ...isRuntimeConfigDisabled() ? [] : [userPath],
11950
+ builtinDir ? join19(builtinDir, `${skillId}.md`) : ""
11951
+ ].filter(Boolean);
12138
11952
  for (const p of candidates) {
12139
- if (existsSync18(p)) {
12140
- return c.json({ skill_id: skillId, path: p, content: readFileSync18(p, "utf-8") });
11953
+ if (existsSync19(p)) {
11954
+ return c.json({ skill_id: skillId, path: p, content: readFileSync19(p, "utf-8") });
12141
11955
  }
12142
11956
  }
12143
11957
  return c.json({ detail: `skill ${skillId} \u4E0D\u5B58\u5728` }, 404);
12144
11958
  });
12145
11959
  console2.get("/api/agents", (c) => {
12146
- const rc = loadRuntimeConfig();
11960
+ const rc = displayRuntimeConfig();
12147
11961
  return c.json({ agents: getAgentRules(rc) });
12148
11962
  });
12149
11963
  console2.put("/api/agents/rules/enabled", async (c) => {
@@ -12182,7 +11996,7 @@ function createConsoleRouter() {
12182
11996
  });
12183
11997
  const enabledScenarioIds = () => (process.env.AGENT_MOSS_SCENARIO || "").split(",").map((s) => s.trim()).filter(Boolean);
12184
11998
  console2.get("/api/scenarios", (c) => {
12185
- const all = getScenarioRules(loadRuntimeConfig());
11999
+ const all = getScenarioRules(displayRuntimeConfig());
12186
12000
  const enabled = enabledScenarioIds();
12187
12001
  if (!enabled.length) return c.json({ scenarios: {}, enabled: [] });
12188
12002
  const shown = {};
@@ -12224,8 +12038,8 @@ function createConsoleRouter() {
12224
12038
  return c.json({ scenarios: getScenarioRules(rc), enabled: enabledScenarioIds() });
12225
12039
  });
12226
12040
  console2.get("/api/config", (c) => {
12227
- const rc = loadRuntimeConfig();
12228
- return c.json({ ...rc, L2_2: getL22View() });
12041
+ const rc = displayRuntimeConfig();
12042
+ return c.json({ ...rc, L2_2: getL22View(), builtin_only: isBuiltinOnly(), rules_mode: getRulesMode() });
12229
12043
  });
12230
12044
  console2.get("/api/env-overrides", (c) => {
12231
12045
  return c.json({ overrides: getEnvOverrides() });
@@ -12234,7 +12048,7 @@ function createConsoleRouter() {
12234
12048
  const defaults = generateSourceDefaults();
12235
12049
  try {
12236
12050
  const p = getRuntimeConfigPath();
12237
- if (existsSync18(p)) unlinkSync4(p);
12051
+ if (existsSync19(p)) unlinkSync4(p);
12238
12052
  } catch {
12239
12053
  }
12240
12054
  return c.json(withL22View(defaults));
@@ -12269,8 +12083,9 @@ var init_router = __esm({
12269
12083
  init_token_stats();
12270
12084
  init_rules_loader();
12271
12085
  init_asset_dir();
12086
+ init_rules_mode();
12272
12087
  __dirname10 = dirname12(fileURLToPath11(import.meta.url));
12273
- STATIC_DIR = join15(__dirname10, "static");
12088
+ STATIC_DIR = join19(__dirname10, "static");
12274
12089
  CONSOLE_TOKEN_ENV = "AGENT_MOSS_CONSOLE_TOKEN";
12275
12090
  }
12276
12091
  });
@@ -12297,37 +12112,57 @@ import { Hono as Hono2 } from "hono";
12297
12112
  import { cors } from "hono/cors";
12298
12113
  import { logger } from "hono/logger";
12299
12114
  import net from "net";
12300
- import { existsSync as existsSync19 } from "fs";
12115
+ import { existsSync as existsSync20 } from "fs";
12301
12116
  function logDeploymentSelfCheck() {
12302
12117
  try {
12118
+ const mode = getRulesMode();
12119
+ const builtinOnly = isBuiltinOnly();
12120
+ const runtimeDisabled = isRuntimeConfigDisabled();
12303
12121
  console.log("================ AgentMoss \u90E8\u7F72\u81EA\u68C0 ================");
12304
- console.log(`runtime \u914D\u7F6E: ${getRuntimeConfigPath()}`);
12305
- const layerKeys = ["L1_heuristic", "L2_logic_rules", "L3_llm_analysis", "agent_rules_enabled", "scenario_rules_enabled"];
12306
- console.log("\u5C42\u7EA7\u5F00\u5173: " + layerKeys.map((k) => `${k}=${isLayerEnabled(k) ? "on" : "OFF"}`).join(" "));
12307
- try {
12308
- const dirs = rulesDirs().filter((d) => existsSync19(d));
12309
- if (dirs.length > 0) {
12310
- console.log(`\u89C4\u5219\u76EE\u5F55: ${dirs.join(", ")}\uFF08\u53EA\u8BFB\u5176\u4E2D ${OVERRIDE_FILENAME} / ${EXTRA_FILENAME}\uFF09`);
12311
- }
12312
- const loaded = ruleFilesLoaded();
12313
- if (loaded.length > 0) {
12314
- const ov = loadOverrideRules();
12315
- const ex = loadExtraRules();
12316
- console.error(`\u26A0\uFE0F \u5DF2\u4ECE\u89C4\u5219\u76EE\u5F55\u52A0\u8F7D ${loaded.length} \u4E2A\u6587\u4EF6\uFF08\u6574\u4EFD\u66FF\u6362 / \u8FFD\u52A0\uFF0C\u5185\u7F6E\u66F4\u65B0\u4E0D\u4F1A\u53CD\u6620\u5230\u5B83\u4EEC\u4E0A\u9762\uFF09:`);
12317
- for (const f of loaded) console.error(` ${f}`);
12318
- const ovs = Object.entries(ov).sort().map(([k, v]) => `${k}\xD7${v.length}`).join(", ");
12319
- if (ovs) console.error(` \u8986\u76D6\u7C7B\u522B: ${ovs}\uFF08**\u6574\u4EFD\u66FF\u6362**\uFF1A\u672A\u5217\u51FA\u7684\u5185\u7F6E\u89C4\u5219\u4E0D\u518D\u751F\u6548\uFF09`);
12320
- const exs = Object.entries(ex).sort().map(([k, v]) => `${k}\xD7${v.length}`).join(", ");
12321
- if (exs) console.log(` \u8FFD\u52A0\u7C7B\u522B: ${exs}\uFF08\u5185\u7F6E\u4FDD\u7559\uFF0C\u8FFD\u52A0\u5728\u5176\u540E\u4E00\u8D77\u5224\u5B9A\uFF09`);
12322
- }
12323
- const ignored = ignoredRuleFiles();
12324
- if (ignored.length > 0) {
12122
+ if (runtimeDisabled) {
12123
+ console.log(`runtime \u914D\u7F6E: ${getRuntimeConfigPath()}\uFF08**\u5DF2\u5FFD\u7565**\uFF1A\u89C4\u5219\u6765\u6E90\u6A21\u5F0F=${mode}\uFF09`);
12124
+ if (builtinOnly) {
12125
+ console.error(
12126
+ "\u89C4\u5219\u6765\u6E90: **\u4EC5\u5185\u7F6E** \u2014\u2014 \u4E0D\u8BFB runtime \u914D\u7F6E / Console \u81EA\u5B9A\u4E49 / \u89C4\u5219\u76EE\u5F55 / AGENT_MOSS_CUSTOM_RULES\uFF1B\u4E0D seed\u3001\u4E0D merge\u3001\u4E0D\u5199\u56DE\u3002"
12127
+ );
12128
+ } else {
12325
12129
  console.error(
12326
- `\u26A0\uFE0F \u89C4\u5219\u76EE\u5F55\u91CC\u6709 ${ignored.length} \u4E2A json **\u4E0D\u4F1A\u88AB\u8BFB\u53D6**\uFF08\u53EA\u8BA4 ${OVERRIDE_FILENAME} / ${EXTRA_FILENAME}\uFF09:`
12130
+ "\u89C4\u5219\u6765\u6E90: **\u51B7\u542F\u52A8\uFF08cold\uFF09** \u2014\u2014 \u542F\u52A8\u65F6\u8BFB\u53D6\u5185\u7F6E\u89C4\u5219 + \u89C4\u5219\u76EE\u5F55 / AGENT_MOSS_CUSTOM_RULES / \u573A\u666F\uFF0C\u4E4B\u540E\u51BB\u7ED3\uFF1B\u4E0D\u8BFB runtime \u914D\u7F6E / Console\uFF0C\u4E0D seed\u3001\u4E0D merge\u3001\u4E0D\u5199\u56DE\u3002"
12327
12131
  );
12328
- for (const f of ignored) console.error(` ${f}`);
12132
+ console.error("\u89C4\u5219\u76EE\u5F55\u5185\u5BB9\u6539\u52A8\u9700\u91CD\u542F\u670D\u52A1\u624D\u751F\u6548\u3002");
12133
+ }
12134
+ console.error("Console: \u53EA\u8BFB\uFF08\u89C4\u5219\u5199\u63A5\u53E3\u8FD4\u56DE 403\uFF09\uFF1B\u4FEE\u6539\u89C4\u5219\u9700\u91CD\u542F\u670D\u52A1\u5E76\u5207\u6362\u89C4\u5219\u6765\u6E90\u6A21\u5F0F\u3002");
12135
+ } else {
12136
+ console.log(`runtime \u914D\u7F6E: ${getRuntimeConfigPath()}`);
12137
+ }
12138
+ const layerKeys = ["L1_heuristic", "L2_logic_rules", "L3_llm_analysis", "agent_rules_enabled", "scenario_rules_enabled"];
12139
+ console.log("\u5C42\u7EA7\u5F00\u5173: " + layerKeys.map((k) => `${k}=${isLayerEnabled(k) ? "on" : "OFF"}`).join(" "));
12140
+ if (!builtinOnly) {
12141
+ try {
12142
+ const dirs = rulesDirs().filter((d) => existsSync20(d));
12143
+ if (dirs.length > 0) {
12144
+ console.log(`\u89C4\u5219\u76EE\u5F55: ${dirs.join(", ")}\uFF08\u53EA\u8BFB\u5176\u4E2D ${OVERRIDE_FILENAME} / ${EXTRA_FILENAME}\uFF09`);
12145
+ }
12146
+ const loaded = ruleFilesLoaded();
12147
+ if (loaded.length > 0) {
12148
+ const ov = loadOverrideRules();
12149
+ const ex = loadExtraRules();
12150
+ console.error(`\u26A0\uFE0F \u5DF2\u4ECE\u89C4\u5219\u76EE\u5F55\u52A0\u8F7D ${loaded.length} \u4E2A\u6587\u4EF6\uFF08\u6574\u4EFD\u66FF\u6362 / \u8FFD\u52A0\uFF0C\u5185\u7F6E\u66F4\u65B0\u4E0D\u4F1A\u53CD\u6620\u5230\u5B83\u4EEC\u4E0A\u9762\uFF09:`);
12151
+ for (const f of loaded) console.error(` ${f}`);
12152
+ const ovs = Object.entries(ov).sort().map(([k, v]) => `${k}\xD7${v.length}`).join(", ");
12153
+ if (ovs) console.error(` \u8986\u76D6\u7C7B\u522B: ${ovs}\uFF08**\u6574\u4EFD\u66FF\u6362**\uFF1A\u672A\u5217\u51FA\u7684\u5185\u7F6E\u89C4\u5219\u4E0D\u518D\u751F\u6548\uFF09`);
12154
+ const exs = Object.entries(ex).sort().map(([k, v]) => `${k}\xD7${v.length}`).join(", ");
12155
+ if (exs) console.log(` \u8FFD\u52A0\u7C7B\u522B: ${exs}\uFF08\u5185\u7F6E\u4FDD\u7559\uFF0C\u8FFD\u52A0\u5728\u5176\u540E\u4E00\u8D77\u5224\u5B9A\uFF09`);
12156
+ }
12157
+ const ignored = ignoredRuleFiles();
12158
+ if (ignored.length > 0) {
12159
+ console.error(
12160
+ `\u26A0\uFE0F \u89C4\u5219\u76EE\u5F55\u91CC\u6709 ${ignored.length} \u4E2A json **\u4E0D\u4F1A\u88AB\u8BFB\u53D6**\uFF08\u53EA\u8BA4 ${OVERRIDE_FILENAME} / ${EXTRA_FILENAME}\uFF09:`
12161
+ );
12162
+ for (const f of ignored) console.error(` ${f}`);
12163
+ }
12164
+ } catch {
12329
12165
  }
12330
- } catch {
12331
12166
  }
12332
12167
  const scenarioRaw = (process.env.AGENT_MOSS_SCENARIO || "").trim();
12333
12168
  if (scenarioRaw) {
@@ -12343,6 +12178,7 @@ function logDeploymentSelfCheck() {
12343
12178
  }
12344
12179
  }
12345
12180
  function createApp() {
12181
+ enforceRulesContract();
12346
12182
  const app = new Hono2();
12347
12183
  app.use("*", cors({ origin: "*", allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], allowHeaders: ["*"] }));
12348
12184
  app.use("*", logger());
@@ -12431,7 +12267,7 @@ async function startUnixSocketServer(socketPath = "/var/run/agent_moss/agent_mos
12431
12267
  server = null;
12432
12268
  },
12433
12269
  serve: async () => {
12434
- const { mkdirSync: mkdirSync10, existsSync: existsSync21, unlinkSync: unlinkSync5 } = await import("fs");
12270
+ const { mkdirSync: mkdirSync10, existsSync: existsSync22, unlinkSync: unlinkSync5 } = await import("fs");
12435
12271
  const { dirname: dirname14 } = await import("path");
12436
12272
  const dir = dirname14(socketPath);
12437
12273
  if (dir) {
@@ -12440,7 +12276,7 @@ async function startUnixSocketServer(socketPath = "/var/run/agent_moss/agent_mos
12440
12276
  } catch {
12441
12277
  }
12442
12278
  }
12443
- if (existsSync21(socketPath)) {
12279
+ if (existsSync22(socketPath)) {
12444
12280
  const { connect } = await import("net");
12445
12281
  const inUse = await new Promise((resolve11) => {
12446
12282
  const probe = connect(socketPath);
@@ -12482,9 +12318,11 @@ var init_server = __esm({
12482
12318
  "src/server.ts"() {
12483
12319
  "use strict";
12484
12320
  init_esm_shims();
12321
+ init_rules_contract();
12485
12322
  init_routes();
12486
12323
  init_console();
12487
12324
  init_runtime_config();
12325
+ init_rules_mode();
12488
12326
  init_rules_loader();
12489
12327
  WARMUP_COMMANDS = {
12490
12328
  linux: "whoami",
@@ -12504,26 +12342,26 @@ __export(rules_tools_exports, {
12504
12342
  rulesStatusText: () => rulesStatusText,
12505
12343
  templateSourceDir: () => templateSourceDir
12506
12344
  });
12507
- import { existsSync as existsSync20, mkdirSync as mkdirSync9, readdirSync as readdirSync7, readFileSync as readFileSync19, writeFileSync as writeFileSync9 } from "fs";
12508
- import { dirname as dirname13, join as join16 } from "path";
12345
+ import { existsSync as existsSync21, mkdirSync as mkdirSync9, readdirSync as readdirSync7, readFileSync as readFileSync20, writeFileSync as writeFileSync9 } from "fs";
12346
+ import { dirname as dirname13, join as join20 } from "path";
12509
12347
  import { fileURLToPath as fileURLToPath12 } from "url";
12510
12348
  function initRulesDir(targetDir, opts = {}) {
12511
12349
  mkdirSync9(targetDir, { recursive: true });
12512
12350
  const written = [];
12513
- const extraPath = join16(targetDir, EXTRA_FILENAME);
12514
- if (opts.force || !existsSync20(extraPath)) {
12351
+ const extraPath = join20(targetDir, EXTRA_FILENAME);
12352
+ if (opts.force || !existsSync21(extraPath)) {
12515
12353
  writeFileSync9(extraPath, JSON.stringify(EXTRA_TEMPLATE, null, 2), "utf-8");
12516
12354
  written.push(extraPath);
12517
12355
  }
12518
- const samplePath = join16(targetDir, OVERRIDE_FILENAME);
12519
- if (opts.force || !existsSync20(samplePath)) {
12356
+ const samplePath = join20(targetDir, OVERRIDE_FILENAME);
12357
+ if (opts.force || !existsSync21(samplePath)) {
12520
12358
  let payload = OVERRIDE_TEMPLATE;
12521
12359
  if (opts.withBuiltin) {
12522
12360
  let data = {};
12523
12361
  const builtin = resolveRulesFile("l1_rules.json");
12524
- if (builtin && existsSync20(builtin)) {
12362
+ if (builtin && existsSync21(builtin)) {
12525
12363
  try {
12526
- data = JSON.parse(readFileSync19(builtin, "utf-8")) || {};
12364
+ data = JSON.parse(readFileSync20(builtin, "utf-8")) || {};
12527
12365
  } catch {
12528
12366
  data = {};
12529
12367
  }
@@ -12534,17 +12372,17 @@ function initRulesDir(targetDir, opts = {}) {
12534
12372
  writeFileSync9(samplePath, JSON.stringify(payload, null, 2), "utf-8");
12535
12373
  written.push(samplePath);
12536
12374
  }
12537
- const readmeSrc = join16(templateSourceDir(), "README.md");
12538
- const readmeDst = join16(targetDir, "README.md");
12539
- if (existsSync20(readmeSrc) && (opts.force || !existsSync20(readmeDst))) {
12540
- writeFileSync9(readmeDst, readFileSync19(readmeSrc, "utf-8"), "utf-8");
12375
+ const readmeSrc = join20(templateSourceDir(), "README.md");
12376
+ const readmeDst = join20(targetDir, "README.md");
12377
+ if (existsSync21(readmeSrc) && (opts.force || !existsSync21(readmeDst))) {
12378
+ writeFileSync9(readmeDst, readFileSync20(readmeSrc, "utf-8"), "utf-8");
12541
12379
  written.push(readmeDst);
12542
12380
  }
12543
12381
  return written;
12544
12382
  }
12545
12383
  function templateSourceDir() {
12546
- const cands = [join16(__dirname11, "data", "rules_template"), join16(__dirname11, "..", "..", "agent_moss", "data", "rules_template")];
12547
- for (const c of cands) if (existsSync20(c)) return c;
12384
+ const cands = [join20(__dirname11, "data", "rules_template"), join20(__dirname11, "..", "..", "agent_moss", "data", "rules_template")];
12385
+ for (const c of cands) if (existsSync21(c)) return c;
12548
12386
  return cands[0];
12549
12387
  }
12550
12388
  function templateFiles(dirs) {
@@ -12552,7 +12390,7 @@ function templateFiles(dirs) {
12552
12390
  for (const d of dirs) {
12553
12391
  try {
12554
12392
  for (const name of readdirSync7(d)) {
12555
- if (name.endsWith(".json.\u793A\u4F8B")) out.push(join16(d, name));
12393
+ if (name.endsWith(".json.\u793A\u4F8B")) out.push(join20(d, name));
12556
12394
  }
12557
12395
  } catch {
12558
12396
  }
@@ -12561,7 +12399,7 @@ function templateFiles(dirs) {
12561
12399
  }
12562
12400
  function rulesStatusText() {
12563
12401
  const lines = [];
12564
- const existing = rulesDirs().filter((d) => existsSync20(d));
12402
+ const existing = rulesDirs().filter((d) => existsSync21(d));
12565
12403
  lines.push("\u89C4\u5219\u76EE\u5F55: " + (existing.length > 0 ? existing.join(", ") : "\uFF08\u672A\u8BBE\u7F6E / \u4E0D\u5B58\u5728\uFF09"));
12566
12404
  if (existing.length > 0) {
12567
12405
  lines.push(` \uFF08\u53EA\u8BFB\u5176\u4E2D\u7684 ${OVERRIDE_FILENAME} \u4E0E ${EXTRA_FILENAME}\uFF1B\u5176\u4ED6\u6587\u4EF6\u540D\u4E00\u5F8B\u4E0D\u8BFB\uFF09`);
@@ -12713,6 +12551,17 @@ async function analyzeRawCommand(command, useJson = false) {
12713
12551
  async function main() {
12714
12552
  const args = process.argv.slice(2);
12715
12553
  const command = args[0];
12554
+ try {
12555
+ const { checkRulesAssets: checkRulesAssets2, enforceRulesContract: enforceRulesContract2, formatRulesIntegrity: formatRulesIntegrity2 } = await Promise.resolve().then(() => (init_rules_contract(), rules_contract_exports));
12556
+ const rep = checkRulesAssets2();
12557
+ if (!rep.ok) {
12558
+ enforceRulesContract2();
12559
+ }
12560
+ if (rep.external) console.error(formatRulesIntegrity2(rep));
12561
+ } catch (e) {
12562
+ console.error(`\u274C ${e instanceof Error ? e.message : String(e)}`);
12563
+ process.exit(2);
12564
+ }
12716
12565
  if (command === "--version" || command === "-V") {
12717
12566
  console.log(`agent-moss ${version}`);
12718
12567
  process.exit(0);