@kenhkl/agent-moss 0.16.13 → 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,367 +107,110 @@ 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);
114
+ // src/infra/rules-mode.ts
115
+ function truthy(name) {
116
+ return TRUTHY.has((process.env[name] || "").trim().toLowerCase());
205
117
  }
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;
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";
227
129
  }
228
- cur += ch;
130
+ return cached;
229
131
  }
230
- if (cur) segments.push(cur);
231
- return segments.map((s) => s.trim()).filter((s) => s.length > 0 && !/^(?:\|\||&&|[|;&])$/.test(s));
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;
232
136
  }
233
- function extractFirstCommand(command) {
234
- const segments = splitCommandSegments(command);
235
- return segments.length ? segments[0] : "";
137
+ function isHotReloadEnabled() {
138
+ return getRulesMode() === "hot";
236
139
  }
237
- var CMD_SUBSTITUTION;
238
- var init_shell_split = __esm({
239
- "src/engine/shell-split.ts"() {
140
+ function isRuntimeConfigDisabled() {
141
+ return getRulesMode() !== "hot";
142
+ }
143
+ function isBuiltinOnly() {
144
+ return getRulesMode() === "builtin";
145
+ }
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"() {
240
149
  "use strict";
241
150
  init_esm_shims();
242
- CMD_SUBSTITUTION = /\$\(|`|</;
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;
243
157
  }
244
158
  });
245
159
 
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;
257
- }
258
- }
259
- if (/\|\s*\b(?:tee|tac|install)\b/i.test(command)) {
260
- return false;
261
- }
262
- return true;
160
+ // src/infra/runtime-config-bridge.ts
161
+ function registerRuntimeConfigBridge(bridge) {
162
+ runtimeConfigBridge.current = bridge;
263
163
  }
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);
164
+ var runtimeConfigBridge;
165
+ var init_runtime_config_bridge = __esm({
166
+ "src/infra/runtime-config-bridge.ts"() {
167
+ "use strict";
168
+ init_esm_shims();
169
+ runtimeConfigBridge = { current: null };
276
170
  }
277
- return parts.join("\n");
171
+ });
172
+
173
+ // src/infra/rules-loader.ts
174
+ import { readFileSync as readFileSync2, existsSync, readdirSync, statSync } from "fs";
175
+ import { resolve, dirname, join as join2 } from "path";
176
+ import { fileURLToPath as fileURLToPath2 } from "url";
177
+ function dataRootDir() {
178
+ return getDataRoot();
278
179
  }
279
- function hasWriteExecChain(command) {
280
- return WRITE_EXEC_CHAIN_PATTERNS.some((p) => {
281
- p.lastIndex = 0;
282
- return p.test(command);
283
- });
180
+ function parseDirEnv(envName) {
181
+ const raw = (process.env[envName] || "").trim();
182
+ if (!raw) return [];
183
+ return raw.split(/[,;]/).map((s) => s.trim()).filter(Boolean).map((s) => resolve(s));
284
184
  }
285
- function hasDirTraversal(code) {
286
- const lower = code.toLowerCase();
287
- return DIR_TRAVERSAL_KEYWORDS.some((kw) => lower.includes(kw));
185
+ function rulesDirs() {
186
+ if (isBuiltinOnly()) return [];
187
+ const dirs = [];
188
+ for (const env of RULES_DIR_ENVS) dirs.push(...parseDirEnv(env));
189
+ dirs.push(resolve(dataRootDir(), "rules"));
190
+ return [...new Set(dirs)];
288
191
  }
289
- function hasReadWriteKeyword(code) {
290
- const lower = code.toLowerCase();
291
- return READ_WRITE_KEYWORDS.some((kw) => lower.includes(kw));
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));
292
209
  }
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"() {
342
- "use strict";
343
- 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
- ];
475
- }
476
- });
477
-
478
- // src/infra/runtime-config-bridge.ts
479
- function registerRuntimeConfigBridge(bridge) {
480
- runtimeConfigBridge.current = bridge;
481
- }
482
- var runtimeConfigBridge;
483
- var init_runtime_config_bridge = __esm({
484
- "src/infra/runtime-config-bridge.ts"() {
485
- "use strict";
486
- init_esm_shims();
487
- runtimeConfigBridge = { current: null };
488
- }
489
- });
490
-
491
- // src/infra/rules-mode.ts
492
- function truthy(name) {
493
- return TRUTHY.has((process.env[name] || "").trim().toLowerCase());
494
- }
495
- function getRulesMode() {
496
- if (cached !== null) return cached;
497
- const raw = (process.env[ENV_RULES_MODE] || "").trim().toLowerCase();
498
- if (raw) {
499
- if (VALID_MODES.has(raw)) {
500
- cached = raw;
501
- } else {
502
- console.error(
503
- `[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`
504
- );
505
- cached = "builtin";
506
- }
507
- return cached;
508
- }
509
- if (truthy(ENV_RULES_BUILTIN_ONLY)) cached = "builtin";
510
- else if (truthy(ENV_DISABLE_RULE_HOT_RELOAD)) cached = "cold";
511
- else cached = "hot";
512
- return cached;
513
- }
514
- function isHotReloadEnabled() {
515
- return getRulesMode() === "hot";
516
- }
517
- function isRuntimeConfigDisabled() {
518
- return getRulesMode() !== "hot";
519
- }
520
- function isBuiltinOnly() {
521
- return getRulesMode() === "builtin";
522
- }
523
- var ENV_RULES_MODE, ENV_RULES_BUILTIN_ONLY, ENV_DISABLE_RULE_HOT_RELOAD, TRUTHY, VALID_MODES, cached;
524
- var init_rules_mode = __esm({
525
- "src/infra/rules-mode.ts"() {
526
- "use strict";
527
- init_esm_shims();
528
- ENV_RULES_MODE = "AGENT_MOSS_RULES_MODE";
529
- ENV_RULES_BUILTIN_ONLY = "AGENT_MOSS_RULES_BUILTIN_ONLY";
530
- ENV_DISABLE_RULE_HOT_RELOAD = "AGENT_MOSS_DISABLE_RULE_HOT_RELOAD";
531
- TRUTHY = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
532
- VALID_MODES = /* @__PURE__ */ new Set(["hot", "cold", "builtin"]);
533
- cached = null;
534
- }
535
- });
536
-
537
- // src/infra/rules-loader.ts
538
- import { readFileSync as readFileSync2, existsSync, readdirSync, statSync } from "fs";
539
- import { resolve, dirname, join as join2 } from "path";
540
- import { fileURLToPath as fileURLToPath2 } from "url";
541
- function dataRootDir() {
542
- return getDataRoot();
543
- }
544
- function parseDirEnv(envName) {
545
- const raw = (process.env[envName] || "").trim();
546
- if (!raw) return [];
547
- return raw.split(/[,;]/).map((s) => s.trim()).filter(Boolean).map((s) => resolve(s));
548
- }
549
- function rulesDirs() {
550
- if (isBuiltinOnly()) return [];
551
- const dirs = [];
552
- for (const env of RULES_DIR_ENVS) dirs.push(...parseDirEnv(env));
553
- dirs.push(resolve(dataRootDir(), "rules"));
554
- return [...new Set(dirs)];
555
- }
556
- function findRulesFile(name) {
557
- for (const dir of RULES_DIR_CANDS) {
558
- const p = resolve(dir, name);
559
- if (existsSync(p)) return p;
210
+ function findRulesFile(name) {
211
+ for (const dir of rulesDirCands()) {
212
+ const p = resolve(dir, name);
213
+ if (existsSync(p)) return p;
560
214
  }
561
215
  return null;
562
216
  }
@@ -613,6 +267,74 @@ function loadL1() {
613
267
  function loadL2() {
614
268
  return loadJson("l2_rules.json");
615
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
+ }
337
+ }
616
338
  function loadL3() {
617
339
  const data = { ...loadJson("l3_rules.json") };
618
340
  try {
@@ -895,111 +617,545 @@ function scanRuleDirs() {
895
617
  );
896
618
  }
897
619
  }
898
- return { override, extra, loaded, ignored };
620
+ return { override, extra, loaded, ignored };
621
+ }
622
+ function rulesCached() {
623
+ if (!isHotReloadEnabled() && _RULES_CACHE) return _RULES_CACHE;
624
+ const dirs = rulesDirs();
625
+ const sig = RULES_DIR_ENVS.map((e) => `${e}=${process.env[e] || ""}`).join("|") + "|" + dirs.join("|");
626
+ const files = [];
627
+ for (const d of dirs) {
628
+ if (!existsSync(d)) continue;
629
+ try {
630
+ for (const name of readdirSync(d).sort()) {
631
+ if (name.toLowerCase().endsWith(".json")) files.push(resolve(d, name));
632
+ }
633
+ } catch {
634
+ }
635
+ }
636
+ const fp = JSON.stringify(files.map((p) => {
637
+ try {
638
+ const st = statSync(p);
639
+ return [p, st.mtimeMs, st.size];
640
+ } catch {
641
+ return [p, -1, -1];
642
+ }
643
+ }));
644
+ if (_RULES_CACHE && _RULES_CACHE.sig === sig && _RULES_CACHE.fp === fp) return _RULES_CACHE;
645
+ _RULES_CACHE = { sig, fp, ...scanRuleDirs() };
646
+ return _RULES_CACHE;
647
+ }
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");
899
1013
  }
900
- function rulesCached() {
901
- if (!isHotReloadEnabled() && _RULES_CACHE) return _RULES_CACHE;
902
- const dirs = rulesDirs();
903
- const sig = RULES_DIR_ENVS.map((e) => `${e}=${process.env[e] || ""}`).join("|") + "|" + dirs.join("|");
904
- const files = [];
905
- for (const d of dirs) {
906
- if (!existsSync(d)) continue;
907
- try {
908
- for (const name of readdirSync(d).sort()) {
909
- if (name.toLowerCase().endsWith(".json")) files.push(resolve(d, name));
910
- }
911
- } catch {
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;
912
1024
  }
913
1025
  }
914
- const fp = JSON.stringify(files.map((p) => {
915
- try {
916
- const st = statSync(p);
917
- return [p, st.mtimeMs, st.size];
918
- } catch {
919
- return [p, -1, -1];
920
- }
921
- }));
922
- if (_RULES_CACHE && _RULES_CACHE.sig === sig && _RULES_CACHE.fp === fp) return _RULES_CACHE;
923
- _RULES_CACHE = { sig, fp, ...scanRuleDirs() };
924
- return _RULES_CACHE;
1026
+ if (/\|\s*\b(?:tee|tac|install)\b/i.test(command)) {
1027
+ return false;
1028
+ }
1029
+ return true;
925
1030
  }
926
- function identicalToBuiltin(override) {
927
- const l1 = loadL1();
928
- const l2 = loadL2();
929
- for (const [cat, items] of Object.entries(override)) {
930
- const builtin = l1[cat] ?? l2[cat];
931
- if (!Array.isArray(builtin) || builtin.length !== items.length) return false;
932
- if (JSON.stringify(builtin) !== JSON.stringify(items)) return false;
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);
933
1038
  }
934
- 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");
935
1045
  }
936
- function loadOverrideRules() {
937
- if (isBuiltinOnly()) return {};
938
- 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
+ });
939
1051
  }
940
- function loadExtraRules() {
941
- if (isBuiltinOnly()) return {};
942
- return rulesCached().extra;
1052
+ function hasDirTraversal(code) {
1053
+ const lower = code.toLowerCase();
1054
+ return DIR_TRAVERSAL_KEYWORDS.some((kw) => lower.includes(kw));
943
1055
  }
944
- function ruleFilesLoaded() {
945
- return rulesCached().loaded;
1056
+ function hasReadWriteKeyword(code) {
1057
+ const lower = code.toLowerCase();
1058
+ return READ_WRITE_KEYWORDS.some((kw) => lower.includes(kw));
946
1059
  }
947
- function ignoredRuleFiles() {
948
- 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
+ };
949
1105
  }
950
- var __filename2, __dirname2, OVERRIDE_FILENAME, EXTRA_FILENAME, RULES_DIR_ENVS, RULES_DIR_CANDS, frozenJsonCache, SCENARIO_LIST_KEYS, EXTRA_CATEGORIES, STRUCT_KEYS, EXTRA_FLAT_FIELD, _RULES_CACHE, warnedIgnored;
951
- var init_rules_loader = __esm({
952
- "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"() {
953
1109
  "use strict";
954
1110
  init_esm_shims();
955
- init_paths();
956
- init_rules_mode();
957
- init_runtime_config_bridge();
958
- __filename2 = fileURLToPath2(import.meta.url);
959
- __dirname2 = dirname(__filename2);
960
- OVERRIDE_FILENAME = "override.json";
961
- EXTRA_FILENAME = "extra.json";
962
- RULES_DIR_ENVS = ["AGENT_MOSS_EXTRA_RULES_DIR", "AGENT_MOSS_OVERRIDE_RULES_DIR"];
963
- RULES_DIR_CANDS = (() => {
964
- const cands = [
965
- // 注:`<data_root>/rules/` 不再是"按内置文件名覆盖"的查找位 —— 2026-10 起
966
- // 规则目录只认 override.json / extra.json(见 rulesDirs())。
967
- // dist: __dirname=dist/ → dist/rules;__dirname=dist/engine/ → dist/engine/../rules=dist/rules
968
- resolve(__dirname2, "../rules"),
969
- resolve(__dirname2, "rules"),
970
- // dev src: __dirname=ts/src/infra/ → ts/src/infra/../../../agent_moss/rules = 仓库根 agent_moss/rules
971
- resolve(__dirname2, "../../../agent_moss/rules"),
972
- // dist 但 __dirname 指向 dist/engine 再往上两级的仓库根 agent_moss/rules
973
- resolve(__dirname2, "../../../../agent_moss/rules"),
974
- // 相对当前工作目录的仓库根(兜底,兼容以仓库根为 cwd 运行)
975
- resolve(process.cwd(), "agent_moss/rules")
976
- ];
977
- return cands.filter((c) => existsSync(c));
978
- })();
979
- frozenJsonCache = /* @__PURE__ */ new Map();
980
- SCENARIO_LIST_KEYS = ["extra_command_patterns", "extra_injection_keywords", "extra_sensitive_paths"];
981
- EXTRA_CATEGORIES = {
982
- dangerous_commands: ["pattern"],
983
- injection_detection: ["keyword", "lang"],
984
- sensitive_path_access: ["path"],
985
- intent_consistency: ["intent_keywords"],
986
- password_modify_consent: ["pattern"],
987
- user_deletion_consent: ["pattern"]
988
- };
989
- STRUCT_KEYS = ["skills", "ltl_rules", "tools"];
990
- EXTRA_FLAT_FIELD = [
991
- [["path"], "sensitive_path_access"],
992
- [["keyword", "lang"], "injection_detection"],
993
- [["intent_keywords"], "intent_consistency"],
994
- [["pattern", "risk_level"], "dangerous_commands"],
995
- [["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
996
1129
  ];
997
- _RULES_CACHE = null;
998
- 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 ?? [];
999
1132
  }
1000
1133
  });
1001
1134
 
1002
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
+ }
1003
1159
  function getPlatformTraits(osType) {
1004
1160
  const accepted = acceptedOsValues(osType);
1005
1161
  if (accepted.includes("windows")) return WINDOWS_TRAITS;
@@ -1007,368 +1163,18 @@ function getPlatformTraits(osType) {
1007
1163
  if (accepted.includes("harmony")) return HARMONY_TRAITS;
1008
1164
  return LINUX_TRAITS;
1009
1165
  }
1010
- var LINUX_TRAITS, WINDOWS_TRAITS, MACOS_TRAITS, HARMONY_TRAITS;
1166
+ var traitsCache, LINUX_TRAITS, WINDOWS_TRAITS, MACOS_TRAITS, HARMONY_TRAITS;
1011
1167
  var init_platform_traits = __esm({
1012
1168
  "src/engine/platform-traits.ts"() {
1013
1169
  "use strict";
1014
1170
  init_esm_shims();
1015
1171
  init_logic_rules();
1016
- LINUX_TRAITS = {
1017
- writeKeywords: [
1018
- "write",
1019
- "\u5199\u5165",
1020
- "save",
1021
- "\u4FDD\u5B58",
1022
- "modify",
1023
- "\u4FEE\u6539",
1024
- "update",
1025
- "\u66F4\u65B0",
1026
- "delete",
1027
- "\u5220\u9664",
1028
- "remove",
1029
- "\u79FB\u9664",
1030
- "create",
1031
- "\u521B\u5EFA",
1032
- "append",
1033
- "\u8FFD\u52A0",
1034
- "truncate",
1035
- "\u622A\u65AD",
1036
- "overwrite",
1037
- "\u8986\u76D6",
1038
- "replace",
1039
- "\u66FF\u6362",
1040
- "edit"
1041
- ],
1042
- readKeywords: [
1043
- "read",
1044
- "\u8BFB\u53D6",
1045
- "cat",
1046
- "head",
1047
- "tail",
1048
- "less",
1049
- "more",
1050
- "view",
1051
- "\u67E5\u770B",
1052
- "open",
1053
- "\u6253\u5F00",
1054
- "load",
1055
- "\u52A0\u8F7D",
1056
- "grep",
1057
- "search",
1058
- "\u641C\u7D22",
1059
- "find",
1060
- "\u67E5\u627E"
1061
- ],
1062
- writeCommandRes: [
1063
- /\brm\b/,
1064
- /\bunlink\b/,
1065
- /\brmdir\b/,
1066
- /\bshred\b/,
1067
- /\bcp\b/,
1068
- /\bmv\b/,
1069
- /\binstall\b/,
1070
- /\btee\b/,
1071
- /\bdd\b/,
1072
- /\bmkfs\b/,
1073
- /\b(fdisk|parted|cfdisk|sfdisk)\b/,
1074
- /\bchmod\b/,
1075
- /\bchown\b/,
1076
- /\btruncate\b/
1077
- ],
1078
- readOnlyCommands: /* @__PURE__ */ new Set([
1079
- "cat",
1080
- "head",
1081
- "tail",
1082
- "less",
1083
- "more",
1084
- "grep",
1085
- "find",
1086
- "awk",
1087
- "sed",
1088
- "sort",
1089
- "uniq",
1090
- "wc",
1091
- "cut",
1092
- "strings",
1093
- "od",
1094
- "xxd",
1095
- "hexdump",
1096
- "tr",
1097
- "file",
1098
- "stat",
1099
- "du",
1100
- "df",
1101
- "ls",
1102
- "dir",
1103
- "tree",
1104
- "nl",
1105
- "tac",
1106
- "rev",
1107
- "echo",
1108
- "printf",
1109
- "lsblk",
1110
- "blockdev",
1111
- "smartctl",
1112
- "udevadm",
1113
- "dmidecode",
1114
- "lscpu",
1115
- "lspci",
1116
- "lsusb",
1117
- "lsmem",
1118
- "lsns",
1119
- "lsof",
1120
- "hwinfo",
1121
- "inxi",
1122
- "fdisk",
1123
- "parted",
1124
- "dmesg",
1125
- "journalctl",
1126
- "systemctl",
1127
- "hostnamectl",
1128
- "localectl",
1129
- "timedatectl",
1130
- "ps",
1131
- "top",
1132
- "free",
1133
- "vmstat",
1134
- "iostat",
1135
- "mpstat",
1136
- "pidof",
1137
- "pgrep",
1138
- "ip",
1139
- "ifconfig",
1140
- "route",
1141
- "ss",
1142
- "netstat",
1143
- "arp",
1144
- "ethtool",
1145
- "nmcli",
1146
- "uname",
1147
- "arch",
1148
- "nproc",
1149
- "getconf",
1150
- "getent",
1151
- "id",
1152
- "whoami",
1153
- "who",
1154
- "w",
1155
- "last",
1156
- "uptime",
1157
- "lsmod",
1158
- "modinfo",
1159
- "sysctl"
1160
- ]),
1161
- fullySafeCommands: [
1162
- /^ls(?:\s|$)/i,
1163
- /^dir(?:\s|$)/i,
1164
- /^pwd(?:\s|$)/i,
1165
- /^which\s+/i,
1166
- /^whereis\s+/i,
1167
- /^realpath\s+/i,
1168
- /^basename\s+/i,
1169
- /^dirname\s+/i,
1170
- /^file\s+/i,
1171
- /^stat\s+/i,
1172
- /^du(?:\s|$)/i,
1173
- /^echo\s+/i,
1174
- /^printf\s+/i,
1175
- /^type\s+/i,
1176
- /^command\s+/i,
1177
- // 系统信息查询(只读,不涉及文件/网络/权限)
1178
- /^whoami(?:\s|$)/i,
1179
- /^id(?:\s|$)/i,
1180
- /^hostname(?:\s|$)/i,
1181
- /^uname(?:\s|$)/i,
1182
- /^date(?:\s|$)/i,
1183
- /^env(?:\s|$)/i,
1184
- /^printenv(?:\s|$)/i,
1185
- /^tty(?:\s|$)/i,
1186
- /^arch(?:\s|$)/i,
1187
- /^uptime(?:\s|$)/i,
1188
- /^groups(?:\s|$)/i,
1189
- /^logname(?:\s|$)/i,
1190
- // 系统资源/硬件信息(只读,不读文件内容、无状态变更选项)
1191
- // 与上面 uptime/uname/id 同性质;缺了它们时开 L3 的部署每查一次磁盘/内存
1192
- // 都要走 LLM(实测 df -h 从 5ms 变成十几秒)。
1193
- /^df(?:\s|$)/i,
1194
- /^free(?:\s|$)/i,
1195
- /^vmstat(?:\s|$)/i,
1196
- /^nproc(?:\s|$)/i,
1197
- /^lscpu(?:\s|$)/i,
1198
- /^lsblk(?:\s|$)/i,
1199
- /^lspci(?:\s|$)/i,
1200
- /^lsusb(?:\s|$)/i,
1201
- /^lsmod(?:\s|$)/i
1202
- ],
1203
- readonlySensitiveCommands: [
1204
- /^cat\s+/i,
1205
- /^head(?:\s|$)/i,
1206
- /^tail(?:\s|$)/i,
1207
- /^less(?:\s|$)/i,
1208
- /^more(?:\s|$)/i,
1209
- /^wc(?:\s|$)/i,
1210
- /^grep(?:\s|$)/i,
1211
- /^find\s+/i,
1212
- /^ag(?:\s|$)/i,
1213
- /^rg(?:\s|$)/i,
1214
- /^awk\s+/i,
1215
- // NEW-FN-P(M03):sed -i/--in-place 是原地写,挡出白名单走 L2 read_before_write。
1216
- // 前瞻放最前(外层 ^sed\s+ 会消费 -i 前的空格)。与 Python profiles/linux.py 对齐。
1217
- /^(?!.*(?:\s|['"])-[a-zA-Z]*i[a-zA-Z]*\b)(?!.*(?:\s|['"])--in-place)sed\s+/i,
1218
- /^cut\s+/i,
1219
- /^sort(?:\s|$)/i,
1220
- /^uniq(?:\s|$)/i,
1221
- // 进程/网络/打开文件信息:只读,但会暴露进程命令行、连接对端、打开的文件
1222
- // 路径,故只跳 L3、保留 L2(L1 敏感路径检测照常生效)。
1223
- /^ps(?:\s|$)/i,
1224
- /^ss(?:\s|$)/i,
1225
- /^netstat(?:\s|$)/i,
1226
- /^lsof(?:\s|$)/i,
1227
- // 只读子命令级白名单:匹配的是「第一段命令」而非首词(extractFirstCommand
1228
- // 按 | && || 切分),所以能精确到子命令。systemctl stop/disable 等不在此列。
1229
- /^systemctl\s+(?:status|is-active|is-enabled|is-failed|list-units|list-unit-files|list-dependencies|show|cat)\b/i,
1230
- /^service\s+\S+\s+status\b/i,
1231
- // journalctl 整族是只读的,但 --vacuum-*/--rotate/--flush/--sync 会删改日志,
1232
- // 用否定前瞻挡掉后再放行。注意前瞻里必须用 .* 扫描**整段**:只写 (?!\s+--...)
1233
- // 的话只看紧跟 journalctl 的第一个参数,
1234
- // `journalctl -u sshd --vacuum-size=100M` 就漏过去了。
1235
- /^journalctl\b(?!.*--(?:vacuum-size|vacuum-time|vacuum-files|rotate|flush|sync)\b)/i
1236
- ]
1237
- };
1238
- WINDOWS_TRAITS = {
1239
- writeKeywords: [
1240
- "write",
1241
- "\u5199\u5165",
1242
- "save",
1243
- "\u4FDD\u5B58",
1244
- "modify",
1245
- "\u4FEE\u6539",
1246
- "update",
1247
- "\u66F4\u65B0",
1248
- "delete",
1249
- "\u5220\u9664",
1250
- "remove",
1251
- "\u79FB\u9664",
1252
- "create",
1253
- "\u521B\u5EFA",
1254
- "append",
1255
- "\u8FFD\u52A0",
1256
- "overwrite",
1257
- "\u8986\u76D6",
1258
- "replace",
1259
- "\u66FF\u6362",
1260
- "set",
1261
- "\u8BBE\u7F6E"
1262
- // 注意:这里**不能放 `reg` / `注册表`**。关键词是子串包含匹配,放进去等于
1263
- // 「只要命令里出现 reg 字样就算写操作」——`reg query`(读)会被判成写,
1264
- // 进而误拦对 deny_write 路径的只读查询。注册表的写判定交给下面的
1265
- // writeCommandRes(只认写子命令)。两端语义必须一致。
1266
- ],
1267
- readKeywords: [
1268
- "read",
1269
- "\u8BFB\u53D6",
1270
- "type",
1271
- "findstr",
1272
- "dir",
1273
- "view",
1274
- "\u67E5\u770B",
1275
- "open",
1276
- "\u6253\u5F00",
1277
- "load",
1278
- "\u52A0\u8F7D",
1279
- "search",
1280
- "\u641C\u7D22",
1281
- "find",
1282
- "\u67E5\u627E",
1283
- "get",
1284
- "\u83B7\u53D6"
1285
- ],
1286
- // Windows 写/删命令:del/copy/move/xcopy/replace/rmdir/icacls/reg delete 等
1287
- writeCommandRes: [
1288
- /\b(del|erase)\b/,
1289
- /\brmdir\b/,
1290
- /\b(rd)\b/,
1291
- /\bcopy\b/,
1292
- /\b(xcopy|robocopy)\b/,
1293
- /\b(move|movefile)\b/,
1294
- /\breplace\b/,
1295
- /\bicacls\b/,
1296
- /\battrib\b/,
1297
- // 注册表:**只认写子命令**(对齐 Python WindowsProfile.write_command_res)。
1298
- // 此前是 /\breg\b/,把 reg query(读)也判成写 → 误拦对 deny_write 路径的只读查询。
1299
- // 写子命令 add/delete/copy/save/restore/import/load/unload;
1300
- // 读子命令 query/compare/export/flags/help 不在此列。
1301
- /\breg(?:\.exe)?\s+(?:add|delete|copy|save|restore|import|load|unload)\b/,
1302
- /\bformat\b/,
1303
- /\bdiskpart\b/,
1304
- /\bmkdir\b/,
1305
- /\bmd\b/,
1306
- /\bsetx\b/,
1307
- /\bwmic\b/
1308
- ],
1309
- // Windows 只读命令(重定向写判定用):type/findstr/more/tree/dir 等
1310
- readOnlyCommands: /* @__PURE__ */ new Set([
1311
- "type",
1312
- "findstr",
1313
- "more",
1314
- "tree",
1315
- "dir",
1316
- "where",
1317
- "ver",
1318
- "cls",
1319
- "echo",
1320
- "help",
1321
- "fc",
1322
- "comp",
1323
- "find",
1324
- "sort",
1325
- "powershell",
1326
- "cmd",
1327
- "netstat",
1328
- "ipconfig",
1329
- "tasklist",
1330
- "systeminfo",
1331
- "driverquery",
1332
- "reg",
1333
- "wmic",
1334
- "whoami",
1335
- "hostname",
1336
- "getmac",
1337
- "query",
1338
- "w32tm",
1339
- "diskpart"
1340
- ]),
1341
- // Windows 完全安全命令(对齐 WindowsProfile.fully_safe_commands)
1342
- fullySafeCommands: [
1343
- /^dir(?:\s|$)/,
1344
- /^where\s+/,
1345
- /^echo\s+/,
1346
- /^cd(?:\s|$)/,
1347
- /^tree(?:\s|$)/,
1348
- /^help(?:\s|$)/,
1349
- /^cls(?:\s|$)/,
1350
- /^ver(?:\s|$)/,
1351
- // 系统信息查询(只读,不读文件内容、无状态变更选项),与 Linux 侧 df/free 对齐
1352
- /^whoami(?:\s|$)/,
1353
- /^hostname(?:\s|$)/,
1354
- /^systeminfo(?:\s|$)/,
1355
- /^tasklist(?:\s|$)/,
1356
- /^getmac(?:\s|$)/,
1357
- /^driverquery(?:\s|$)/,
1358
- /^netstat(?:\s|$)/
1359
- ],
1360
- // Windows 安全但可能访问敏感路径的命令(对齐 WindowsProfile.readonly_sensitive_commands)
1361
- readonlySensitiveCommands: [
1362
- /^type\s+/,
1363
- /^findstr\s+/,
1364
- // 只读子命令级白名单(匹配「第一段命令」而非首词);
1365
- // sc create/delete/start/stop、reg add/delete/set 均不在此列。
1366
- /^sc\s+query\b/,
1367
- /^reg\s+query\b/
1368
- ]
1369
- };
1370
- MACOS_TRAITS = LINUX_TRAITS;
1371
- 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");
1372
1178
  }
1373
1179
  });
1374
1180
 
@@ -1527,7 +1333,6 @@ function checkIntentConsistency(input) {
1527
1333
  const { aNext, promptSession, promptHistory } = input;
1528
1334
  const actionDetail = aNext.actionDetail.toLowerCase();
1529
1335
  const actionType = aNext.actionType.toLowerCase();
1530
- const EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
1531
1336
  if (EXCLUDED_PREFIXES.some((p) => actionType.startsWith(p))) return null;
1532
1337
  if (INLINE_SCRIPT_RE.test(actionDetail)) return null;
1533
1338
  const intentTexts = collectIntentTexts(promptSession, promptHistory);
@@ -1555,7 +1360,6 @@ function runLogicCheck(input, opts) {
1555
1360
  const actionDetail = aNext.actionDetail.toLowerCase();
1556
1361
  const actionType = aNext.actionType.toLowerCase();
1557
1362
  const resolvedCwd = cwd || "/";
1558
- const EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
1559
1363
  const isExcludedType = EXCLUDED_PREFIXES.some((p) => actionType.startsWith(p));
1560
1364
  if (!opts?.skipReadBeforeWrite) {
1561
1365
  const rbwResult = checkReadBeforeWrite(actionType, actionDetail, actionHistory, input.reason, resolvedCwd, input.osType);
@@ -1667,7 +1471,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
1667
1471
  const isBashLike = actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash";
1668
1472
  const isWrite = isBashLike ? isWriteOperation(actionType, actionDetail, void 0, osType) : getPlatformTraits(osType).writeKeywords.some((kw) => actionType.includes(kw));
1669
1473
  if (!isWrite) return null;
1670
- const WRITE_CMDS = /* @__PURE__ */ new Set(["cp", "mv", "install", "tee", "dd", "truncate", "mkfs", "fdisk", "parted"]);
1474
+ const WRITE_CMDS = new Set(_LOGIC.write_commands ?? []);
1671
1475
  if (isBashLike) {
1672
1476
  const detailNoCompare = actionDetail.replace(/<=>|>=|<=|<>|=>/g, " ");
1673
1477
  const detailNoDevnull = detailNoCompare.replace(DEVNULL_REDIRECT_RE, "");
@@ -1699,7 +1503,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
1699
1503
  }
1700
1504
  if (!hasWriteSeg) return null;
1701
1505
  }
1702
- const SOURCE_ARG_CMDS = /* @__PURE__ */ new Set(["cp", "mv", "install", "rsync"]);
1506
+ const SOURCE_ARG_CMDS = new Set(_LOGIC.source_arg_commands ?? []);
1703
1507
  const REDIRECT_TARGET_RE = new RegExp(`>{1,2}\\s*(?!&)(?:'(/[^']+)'|"(/[^\\"]+)"|(/[^\\s;|&><'\\"]+))`);
1704
1508
  const targetPaths = [];
1705
1509
  for (const seg of splitCommandSegments(actionDetail)) {
@@ -1747,7 +1551,6 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
1747
1551
  }
1748
1552
  }
1749
1553
  }
1750
- const TEMP_DIRS = ["/tmp/", "/var/tmp/", "/dev/shm/", "/run/"];
1751
1554
  let filteredPaths = uniqueTargetPaths;
1752
1555
  if (/\bdd\b/i.test(actionDetail) && /\bif=\/dev\/zero\b/i.test(actionDetail)) {
1753
1556
  filteredPaths = uniqueTargetPaths.filter(
@@ -1848,10 +1651,10 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd, osType) {
1848
1651
  if (!hasTraversal) {
1849
1652
  return null;
1850
1653
  }
1851
- const READ_KEYWORDS = ["read(", "readline", "readlines", "fread", "fgets", "load(", "getpass", "file_get_contents"];
1852
- const WRITE_KEYWORDS2 = ["write(", "writelines", "fwrite", "file_put_contents"];
1853
- 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"];
1854
- 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;
1855
1658
  const code = extractInlineCode(actionDetail);
1856
1659
  if (!code) return null;
1857
1660
  const codeLower = code.toLowerCase();
@@ -1914,7 +1717,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd, osType) {
1914
1717
  violatedLayers: ["L2.1"]
1915
1718
  };
1916
1719
  }
1917
- 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;
1918
1721
  var init_logic_rules = __esm({
1919
1722
  "src/engine/logic-rules.ts"() {
1920
1723
  "use strict";
@@ -1924,6 +1727,10 @@ var init_logic_rules = __esm({
1924
1727
  init_rules_loader();
1925
1728
  init_platform_traits();
1926
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 + "/");
1927
1734
  ROOT_SENSITIVE_PATHS = _loadSensitivePathScope("root");
1928
1735
  HOME_SENSITIVE_PATHS = _loadSensitivePathScope("home");
1929
1736
  CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path6, riskLevel, desc, dirDelOnly }) => ({ path: path6, riskLevel, desc, credential: true, dirDelOnly }));
@@ -1944,7 +1751,7 @@ var init_logic_rules = __esm({
1944
1751
  "list_dir",
1945
1752
  "count_text_length",
1946
1753
  "search",
1947
- // filemgr 只读三件套(globfiles 在 coordinator FULLY_SAFE_TOOLS 提前放行,此处兜底防御非 coordinator 入口)
1754
+ // filemgr 只读三件套(globfiles 在 platform-traits 的 safeActionTypes 提前放行,此处兜底防御非 coordinator 入口)
1948
1755
  "filemgr-globfiles",
1949
1756
  "filemgr-readfile",
1950
1757
  "filemgr-grepfiles",
@@ -1956,7 +1763,7 @@ var init_logic_rules = __esm({
1956
1763
  "tail",
1957
1764
  "cat"
1958
1765
  ]);
1959
- NON_WRITE_TOOL_PREFIXES = ["memory-", "skill-manager-", "mailbox-", "app-"];
1766
+ NON_WRITE_TOOL_PREFIXES = _LOGIC.non_write_tool_prefixes ?? [];
1960
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"];
1961
1768
  SED_INPLACE_RE = /\bsed\s+(?:-\S+\s+)*(?:-[a-zA-Z]*i[a-zA-Z]*|--in-place)\b/i;
1962
1769
  DEVNULL_REDIRECT_RE = /(?:2|&|1)?\s*>\s*\/dev\/null\b/g;
@@ -1997,25 +1804,24 @@ var init_logic_rules = __esm({
1997
1804
  }));
1998
1805
  })();
1999
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;
2000
- DIR_TRAVERSAL_KW = [
2001
- "os.listdir",
2002
- "os.walk",
2003
- "os.scandir",
2004
- "glob.glob",
2005
- "glob.iglob",
2006
- "pathlib.",
2007
- "iterdir",
2008
- "rglob",
2009
- "scandir(",
2010
- "listdir("
2011
- ];
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 ?? [];
2012
1813
  }
2013
1814
  });
2014
1815
 
2015
1816
  // src/engine/asset-dir.ts
2016
- import { existsSync as existsSync3 } from "fs";
2017
- import { resolve as resolve2 } from "path";
1817
+ import { existsSync as existsSync4 } from "fs";
1818
+ import { join as join4, resolve as resolve2 } from "path";
2018
1819
  function assetDirCandidates(moduleDir, name) {
1820
+ const root = getRulesRoot();
1821
+ if (root) {
1822
+ const p = join4(root, name);
1823
+ return [p, p];
1824
+ }
2019
1825
  return [
2020
1826
  resolve2(moduleDir, name),
2021
1827
  resolve2(moduleDir, "../../..", "agent_moss", name)
@@ -2023,7 +1829,7 @@ function assetDirCandidates(moduleDir, name) {
2023
1829
  }
2024
1830
  function resolveAssetDir(moduleDir, name) {
2025
1831
  for (const dir of assetDirCandidates(moduleDir, name)) {
2026
- if (existsSync3(dir)) return dir;
1832
+ if (existsSync4(dir)) return dir;
2027
1833
  }
2028
1834
  return null;
2029
1835
  }
@@ -2031,11 +1837,12 @@ var init_asset_dir = __esm({
2031
1837
  "src/engine/asset-dir.ts"() {
2032
1838
  "use strict";
2033
1839
  init_esm_shims();
1840
+ init_paths();
2034
1841
  }
2035
1842
  });
2036
1843
 
2037
1844
  // src/engine/skill-loader.ts
2038
- 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";
2039
1846
  import { resolve as resolve3, dirname as dirname2 } from "path";
2040
1847
  import { fileURLToPath as fileURLToPath3 } from "url";
2041
1848
  function loadAllSkills() {
@@ -2050,7 +1857,7 @@ function loadAllSkills() {
2050
1857
  const files = readdirSync3(dir).filter((f) => f.endsWith(".md"));
2051
1858
  for (const f of files) {
2052
1859
  const name = f.replace(".md", "");
2053
- cachedSkills[name] = readFileSync3(resolve3(dir, f), "utf-8");
1860
+ cachedSkills[name] = readFileSync4(resolve3(dir, f), "utf-8");
2054
1861
  }
2055
1862
  console.log(`[AgentMoss] Loaded ${Object.keys(cachedSkills).length} skills from ${dir}`);
2056
1863
  return cachedSkills;
@@ -2081,7 +1888,7 @@ function loadUserSkill(name) {
2081
1888
  if (userSkillCache[name] !== void 0) return userSkillCache[name];
2082
1889
  try {
2083
1890
  const p = resolve3(userSkillsDir(), `${name}.md`);
2084
- userSkillCache[name] = existsSync4(p) ? readFileSync3(p, "utf-8") : "";
1891
+ userSkillCache[name] = existsSync5(p) ? readFileSync4(p, "utf-8") : "";
2085
1892
  } catch {
2086
1893
  userSkillCache[name] = "";
2087
1894
  }
@@ -2230,8 +2037,8 @@ __export(runtime_config_exports, {
2230
2037
  updateSkillCategoryEnabled: () => updateSkillCategoryEnabled,
2231
2038
  updateSkillEnabled: () => updateSkillEnabled
2232
2039
  });
2233
- import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync5, mkdirSync, unlinkSync, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
2234
- 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";
2235
2042
  import { fileURLToPath as fileURLToPath4 } from "url";
2236
2043
  import { dirname as dirname3 } from "path";
2237
2044
  function runtimeDir() {
@@ -2260,7 +2067,7 @@ function saveRuntimeConfig(config) {
2260
2067
  }
2261
2068
  const p = getRuntimeConfigPath();
2262
2069
  const dir = p.substring(0, p.lastIndexOf("/"));
2263
- if (dir && !existsSync5(dir)) mkdirSync(dir, { recursive: true });
2070
+ if (dir && !existsSync6(dir)) mkdirSync(dir, { recursive: true });
2264
2071
  writeFileSync(p, JSON.stringify(config, null, 2), "utf-8");
2265
2072
  invalidateRuntimeConfigCache();
2266
2073
  }
@@ -2294,7 +2101,7 @@ function loadRuntimeConfig() {
2294
2101
  };
2295
2102
  }
2296
2103
  const p = getRuntimeConfigPath();
2297
- if (!existsSync5(p)) {
2104
+ if (!existsSync6(p)) {
2298
2105
  const seeded = mergeConfig({}, generateSourceDefaults());
2299
2106
  try {
2300
2107
  saveRuntimeConfig(seeded);
@@ -2308,7 +2115,7 @@ function loadRuntimeConfig() {
2308
2115
  if (runtimeConfigCache && runtimeConfigCache.key === cacheKey) {
2309
2116
  return runtimeConfigCache.value;
2310
2117
  }
2311
- const data = JSON.parse(readFileSync4(p, "utf-8"));
2118
+ const data = JSON.parse(readFileSync5(p, "utf-8"));
2312
2119
  const merged = mergeConfig(data, generateSourceDefaults());
2313
2120
  const result = {
2314
2121
  version: data.version || 1,
@@ -2366,7 +2173,10 @@ function filterEnabledRules(rules, categoryEnabled = true) {
2366
2173
  }
2367
2174
  function _filterRulesByOs(rules, osType) {
2368
2175
  const accepted = acceptedOsValues(osType);
2369
- 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
+ });
2370
2180
  }
2371
2181
  function _userCustomRules(runtime, layers, requiredKey) {
2372
2182
  const list = Array.isArray(layers) ? layers : [layers];
@@ -2453,7 +2263,7 @@ function hasRuntimeConfig() {
2453
2263
  if (cached2 !== void 0) return cached2;
2454
2264
  let ok = false;
2455
2265
  try {
2456
- JSON.parse(readFileSync4(p, "utf-8"));
2266
+ JSON.parse(readFileSync5(p, "utf-8"));
2457
2267
  ok = true;
2458
2268
  } catch {
2459
2269
  ok = false;
@@ -2553,6 +2363,9 @@ function _l1InjectionKeywordsToRules() {
2553
2363
  keyword: kw.keyword,
2554
2364
  risk_level: kw.riskLevel,
2555
2365
  lang: kw.lang || "en",
2366
+ // 带上条目自己的 os(`all` = 全平台通用),别丢 —— 丢了 Windows 会拿到
2367
+ // 本不该给它的条目。与 Python `_l1_injection_keywords_to_rules` 对齐。
2368
+ os: kw.os,
2556
2369
  enabled: true,
2557
2370
  builtin: true
2558
2371
  }));
@@ -2670,8 +2483,9 @@ function _l3SkillsToRules() {
2670
2483
  }
2671
2484
  function _scenarioRulesToDefaults() {
2672
2485
  const out = {};
2673
- const scenDir = existsSync5(_SCENARIOS_DIR_PROD) ? _SCENARIOS_DIR_PROD : existsSync5(_SCENARIOS_DIR_DEV) ? _SCENARIOS_DIR_DEV : _SCENARIOS_DIR_PROD;
2674
- 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;
2675
2489
  let entries;
2676
2490
  try {
2677
2491
  entries = readdirSync4(scenDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
@@ -2679,11 +2493,11 @@ function _scenarioRulesToDefaults() {
2679
2493
  return out;
2680
2494
  }
2681
2495
  for (const scenarioId of entries) {
2682
- const rulesFile = resolveScenarioRulesFile(scenarioId) ?? join3(scenDir, scenarioId, "rules.json");
2683
- if (!existsSync5(rulesFile)) continue;
2496
+ const rulesFile = resolveScenarioRulesFile(scenarioId) ?? join5(scenDir, scenarioId, "rules.json");
2497
+ if (!existsSync6(rulesFile)) continue;
2684
2498
  let data;
2685
2499
  try {
2686
- data = JSON.parse(readFileSync4(rulesFile, "utf-8"));
2500
+ data = JSON.parse(readFileSync5(rulesFile, "utf-8"));
2687
2501
  } catch {
2688
2502
  continue;
2689
2503
  }
@@ -2719,8 +2533,9 @@ function _scenarioRulesToDefaults() {
2719
2533
  }
2720
2534
  function _agentRulesToDefaults() {
2721
2535
  const out = {};
2722
- const agentsDir = existsSync5(_AGENTS_DIR_PROD) ? _AGENTS_DIR_PROD : existsSync5(_AGENTS_DIR_DEV) ? _AGENTS_DIR_DEV : _AGENTS_DIR_PROD;
2723
- 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;
2724
2539
  let entries;
2725
2540
  try {
2726
2541
  entries = readdirSync4(agentsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
@@ -2728,11 +2543,11 @@ function _agentRulesToDefaults() {
2728
2543
  return out;
2729
2544
  }
2730
2545
  for (const agentId of entries) {
2731
- const rulesFile = join3(agentsDir, agentId, "rules.json");
2732
- if (!existsSync5(rulesFile)) continue;
2546
+ const rulesFile = join5(agentsDir, agentId, "rules.json");
2547
+ if (!existsSync6(rulesFile)) continue;
2733
2548
  let data;
2734
2549
  try {
2735
- data = JSON.parse(readFileSync4(rulesFile, "utf-8"));
2550
+ data = JSON.parse(readFileSync5(rulesFile, "utf-8"));
2736
2551
  } catch {
2737
2552
  continue;
2738
2553
  }
@@ -3107,7 +2922,11 @@ function getDisabledSkillIds(runtime) {
3107
2922
  }
3108
2923
  function _filterDefaultsByOs(rules, osType) {
3109
2924
  const accepted = acceptedOsValues(osType);
3110
- 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
+ });
3111
2930
  }
3112
2931
  function _hasRulesForOs(rules, osType) {
3113
2932
  return _filterRulesByOs(rules || [], osType || "linux").length > 0;
@@ -3336,12 +3155,12 @@ function addCustomSkill(skillId, category, keywords, content) {
3336
3155
  );
3337
3156
  }
3338
3157
  const runtime = loadRuntimeConfig();
3339
- const skillsDir = join3(getRuntimeConfigDir(), "agent_moss_skills");
3158
+ const skillsDir = join5(getRuntimeConfigDir(), "agent_moss_skills");
3340
3159
  try {
3341
3160
  mkdirSync(skillsDir, { recursive: true });
3342
3161
  } catch {
3343
3162
  }
3344
- writeFileSync(join3(skillsDir, `${skillId}.md`), content, "utf-8");
3163
+ writeFileSync(join5(skillsDir, `${skillId}.md`), content, "utf-8");
3345
3164
  const l3 = runtime.L3_skills || (runtime.L3_skills = {});
3346
3165
  const cat = l3[category] || (l3[category] = { category_enabled: true, category_desc: "", skills: [] });
3347
3166
  (cat.skills || (cat.skills = [])).push({ id: skillId, keywords, enabled: true, builtin: false });
@@ -3356,9 +3175,9 @@ function deleteCustomSkill(skillId, category) {
3356
3175
  if (idx >= 0) {
3357
3176
  if (skills[idx].builtin) return null;
3358
3177
  skills.splice(idx, 1);
3359
- const skillPath = join3(getRuntimeConfigDir(), "agent_moss_skills", `${skillId}.md`);
3178
+ const skillPath = join5(getRuntimeConfigDir(), "agent_moss_skills", `${skillId}.md`);
3360
3179
  try {
3361
- if (existsSync5(skillPath)) unlinkSync(skillPath);
3180
+ if (existsSync6(skillPath)) unlinkSync(skillPath);
3362
3181
  } catch {
3363
3182
  }
3364
3183
  saveRuntimeConfig(runtime);
@@ -3565,7 +3384,7 @@ var init_runtime_config = __esm({
3565
3384
  __dirname4 = dirname3(fileURLToPath4(import.meta.url));
3566
3385
  ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
3567
3386
  RUNTIME_CONFIG_DIR = runtimeDir();
3568
- RUNTIME_CONFIG_PATH = join3(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
3387
+ RUNTIME_CONFIG_PATH = join5(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
3569
3388
  LAYER_DISABLE_ENV = {
3570
3389
  L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
3571
3390
  L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
@@ -3593,10 +3412,10 @@ var init_runtime_config = __esm({
3593
3412
  intent_consistency: ["intent_keywords", "reason"]
3594
3413
  };
3595
3414
  hasRuntimeConfigCache = /* @__PURE__ */ new Map();
3596
- _AGENTS_DIR_PROD = join3(__dirname4, "agents");
3597
- _AGENTS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "agents");
3598
- _SCENARIOS_DIR_PROD = join3(__dirname4, "scenarios");
3599
- _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");
3600
3419
  _effectiveCache = /* @__PURE__ */ new WeakMap();
3601
3420
  SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
3602
3421
  registerRuntimeConfigBridge({
@@ -3934,12 +3753,14 @@ var init_config = __esm({
3934
3753
  });
3935
3754
 
3936
3755
  // src/agents/index.ts
3937
- import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
3938
- 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";
3939
3758
  import { fileURLToPath as fileURLToPath6 } from "url";
3940
3759
  function resolveAgentsDir() {
3941
- if (existsSync6(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
3942
- 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;
3943
3764
  return AGENTS_DIR_PROD;
3944
3765
  }
3945
3766
  function getAgentExtensions(agentId) {
@@ -3957,12 +3778,12 @@ function getAgentExtensions(agentId) {
3957
3778
  if (ext === null) {
3958
3779
  const agentsDir = resolveAgentsDir();
3959
3780
  const rulesFile = resolve4(agentsDir, agentId, "rules.json");
3960
- if (!existsSync6(rulesFile)) {
3781
+ if (!existsSync7(rulesFile)) {
3961
3782
  cache.set(agentId, { fp, ext: null });
3962
3783
  return null;
3963
3784
  }
3964
3785
  try {
3965
- const raw = readFileSync5(rulesFile, "utf-8");
3786
+ const raw = readFileSync6(rulesFile, "utf-8");
3966
3787
  ext = JSON.parse(raw);
3967
3788
  if (!ext.agent_id) {
3968
3789
  console.warn(`[AgentMoss agents] rules.json \u7F3A\u5C11 agent_id \u5B57\u6BB5: ${rulesFile}`);
@@ -4001,6 +3822,7 @@ var init_agents = __esm({
4001
3822
  "src/agents/index.ts"() {
4002
3823
  "use strict";
4003
3824
  init_esm_shims();
3825
+ init_paths();
4004
3826
  init_runtime_config();
4005
3827
  __filename4 = fileURLToPath6(import.meta.url);
4006
3828
  __dirname5 = dirname4(__filename4);
@@ -4011,12 +3833,14 @@ var init_agents = __esm({
4011
3833
  });
4012
3834
 
4013
3835
  // src/scenarios/index.ts
4014
- import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
4015
- 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";
4016
3838
  import { fileURLToPath as fileURLToPath7 } from "url";
4017
3839
  function resolveScenariosDir() {
4018
- if (existsSync7(SCENARIOS_DIR_PROD)) return SCENARIOS_DIR_PROD;
4019
- 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;
4020
3844
  return SCENARIOS_DIR_PROD;
4021
3845
  }
4022
3846
  function getScenarioExtensions(scenarioId) {
@@ -4046,12 +3870,12 @@ function getScenarioExtensions(scenarioId) {
4046
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`
4047
3871
  );
4048
3872
  }
4049
- if (!existsSync7(rulesFile)) {
3873
+ if (!existsSync8(rulesFile)) {
4050
3874
  cache2.set(scenarioId, { fp, ext: null });
4051
3875
  return null;
4052
3876
  }
4053
3877
  try {
4054
- const raw = readFileSync6(rulesFile, "utf-8");
3878
+ const raw = readFileSync7(rulesFile, "utf-8");
4055
3879
  const data = JSON.parse(raw);
4056
3880
  const id = data.scenario_id || data.agent_id || "";
4057
3881
  if (!id) {
@@ -4095,6 +3919,7 @@ var init_scenarios = __esm({
4095
3919
  "src/scenarios/index.ts"() {
4096
3920
  "use strict";
4097
3921
  init_esm_shims();
3922
+ init_paths();
4098
3923
  init_agents();
4099
3924
  init_runtime_config();
4100
3925
  init_rules_loader();
@@ -4501,7 +4326,7 @@ function severityRank(s) {
4501
4326
  }
4502
4327
  function isSearchActionType(actionType) {
4503
4328
  const at = actionType.toLowerCase();
4504
- 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);
4505
4330
  }
4506
4331
  function checkSensitivePath(command, cwd, agentExt, osType) {
4507
4332
  const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
@@ -4791,8 +4616,7 @@ function isSshKeygenGenerate(command) {
4791
4616
  function checkDangerousPatterns(actionType, actionDetail) {
4792
4617
  if (actionType === "file_write" || actionType === "file_edit") return null;
4793
4618
  const scan = stripHeredocContent(actionDetail);
4794
- const TEMP_DIRS = ["/tmp", "/var/tmp", "/run", "/dev/shm"];
4795
- const isTempPath = TEMP_DIRS.some(
4619
+ const isTempPath = TEMP_DIRS2.some(
4796
4620
  (td) => new RegExp(`(?:^|[\\s/])${td.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[\\s/]|$)`).test(actionDetail)
4797
4621
  ) || /(?:^|[\s;]|&&\s*|\|\|\s*)var\/tmp(?:[/\s;]|$)/.test(actionDetail);
4798
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;
@@ -4938,7 +4762,7 @@ function checkLateralMovement(actionDetail) {
4938
4762
  }
4939
4763
  return null;
4940
4764
  }
4941
- 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;
4942
4766
  var init_heuristic = __esm({
4943
4767
  "src/engine/heuristic.ts"() {
4944
4768
  "use strict";
@@ -4955,7 +4779,8 @@ var init_heuristic = __esm({
4955
4779
  return kws.map((k) => ({
4956
4780
  keyword: k.keyword,
4957
4781
  riskLevel: k.risk_level,
4958
- lang: k.lang || "en"
4782
+ lang: k.lang || "en",
4783
+ os: String(k.os || "all").toLowerCase()
4959
4784
  }));
4960
4785
  })();
4961
4786
  DANGEROUS_PATTERNS2 = _loadDangerousPatterns("linux");
@@ -4963,7 +4788,9 @@ var init_heuristic = __esm({
4963
4788
  DANGEROUS_PATTERNS_MACOS = _loadDangerousPatterns("macos");
4964
4789
  DANGEROUS_PATTERNS_HARMONY = _loadDangerousPatterns("harmony");
4965
4790
  LOOPBACK_URL_RE = /https?:\/\/([^\/\s;'"|&)]+)/i;
4966
- 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 ?? [];
4967
4794
  PATH_CONTEXT_TRIGGER_RE = /(?:^|[;&|]\s*)(?:rm|rmdir|unlink|ln|mv|cp|dd|truncate|shred|chmod|chown)\b/i;
4968
4795
  HOME_PREFIX_RE = /(?:~|~(?=\/)|\$HOME\b|\$\{HOME\}|\/home\/[a-z0-9_.-]+)/gi;
4969
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;
@@ -4971,15 +4798,16 @@ var init_heuristic = __esm({
4971
4798
  PY_BIGNUM_RE = /\d{6,}/;
4972
4799
  PY_INLINE_C_RE = /(python(?:3(?:\.\d+)?)?|perl5?|ruby|node|php|lua)\s+(?:-[a-zA-Z]+\s+)*-c\s+(?:"([^"]*)"|'([^']*)')/i;
4973
4800
  PY_CMD_SUBST_C_RE = /\$\(\s*(python(?:3(?:\.\d+)?)?|perl5?|ruby|node|php|lua)\s+(?:-[a-zA-Z]+\s+)*-c\s+(?:"([^"]*)"'|'([^']*)')\s*\)/i;
4974
- SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
4975
- 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 ?? [];
4976
4804
  HOME_TARGET_RE = /(?:^|[\s="'($])(?:~|(?:\$home|\$\{home\}))(?:\/(?:\.\*|\*|\.)?)?(?=[\s"';}|&)]|$)/i;
4977
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;
4978
4806
  }
4979
4807
  });
4980
4808
 
4981
4809
  // src/engine/template-loader.ts
4982
- import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
4810
+ import { readFileSync as readFileSync8, existsSync as existsSync9 } from "fs";
4983
4811
  import { resolve as resolve6, dirname as dirname6 } from "path";
4984
4812
  import { fileURLToPath as fileURLToPath8 } from "url";
4985
4813
  function loadTemplates() {
@@ -4993,9 +4821,9 @@ function loadTemplates() {
4993
4821
  }
4994
4822
  for (const file of TEMPLATE_FILES) {
4995
4823
  const filePath = resolve6(dir, file);
4996
- if (existsSync8(filePath)) {
4824
+ if (existsSync9(filePath)) {
4997
4825
  const key = file.replace(".txt", "").replace("_template", "");
4998
- cachedTemplates[key] = readFileSync7(filePath, "utf-8");
4826
+ cachedTemplates[key] = readFileSync8(filePath, "utf-8");
4999
4827
  }
5000
4828
  }
5001
4829
  console.log(`[AgentMoss] Loaded ${Object.keys(cachedTemplates).length} templates from ${dir}`);
@@ -5015,8 +4843,8 @@ var init_template_loader = __esm({
5015
4843
  });
5016
4844
 
5017
4845
  // src/engine/script-analyzer.ts
5018
- import { readFileSync as readFileSync8 } from "fs";
5019
- 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";
5020
4848
  function emptyResult() {
5021
4849
  return { scriptPath: "", scriptContent: "", matchedKeywords: [], lineCount: 0, isSuspicious: false, error: "", riskLevel: "", riskReason: "" };
5022
4850
  }
@@ -5042,10 +4870,10 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
5042
4870
  const hitGroup2 = rule.group2.size === 0 ? true : [...rule.group2].some((k) => keywordSet.has(k));
5043
4871
  if (hitGroup1 && hitGroup2) return { level: rule.level, reason: rule.reason };
5044
4872
  }
5045
- 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 ?? []);
5046
4874
  const hasNetworkPost = [...networkPostKeywords].some((k) => keywordSet.has(k));
5047
4875
  if (hasNetworkPost) {
5048
- const envPatterns = ["$env", "$(env)", "$USER", "$(hostname)", "$(pwd)", "$HOME"];
4876
+ const envPatterns = SCRIPT_RULES.env_exfil_patterns ?? [];
5049
4877
  const contentLower = scriptContent.toLowerCase();
5050
4878
  for (const p of envPatterns) {
5051
4879
  if (contentLower.includes(p.toLowerCase())) {
@@ -5069,8 +4897,8 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
5069
4897
  }
5070
4898
  function readScriptContent(scriptPath, maxLines = 500) {
5071
4899
  try {
5072
- const expandedPath = scriptPath === "~" || scriptPath.startsWith("~/") ? join4(process.env.HOME || "/home/user", scriptPath.slice(1)) : resolve7(scriptPath);
5073
- 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");
5074
4902
  const lines = raw.split("\n").slice(0, maxLines);
5075
4903
  return { content: lines.join("\n"), lineCount: lines.length, error: "" };
5076
4904
  } catch (e) {
@@ -5119,90 +4947,28 @@ ${result.scriptContent}
5119
4947
  \`\`\`
5120
4948
  `;
5121
4949
  }
5122
- 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;
5123
4951
  var init_script_analyzer = __esm({
5124
4952
  "src/engine/script-analyzer.ts"() {
5125
4953
  "use strict";
5126
4954
  init_esm_shims();
5127
- SUSPICIOUS_PATTERNS = [
5128
- // Network exfiltration (curl/wget POST in scripts is almost never legitimate)
5129
- { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
5130
- { name: "curl --data", pattern: /curl\s+(.*\s)?--data/i },
5131
- { name: "curl -d", pattern: /curl\s+(.*\s)?-d\s/i },
5132
- { name: "wget --post-data", pattern: /wget\s+(.*\s)?--post-data/i },
5133
- { name: "wget --post-file", pattern: /wget\s+(.*\s)?--post-file/i },
5134
- // Reverse shell
5135
- { name: "nc -e", pattern: /\bnc\s+.*-e\b/ },
5136
- { name: "ncat -e", pattern: /\bncat\s+.*-e\b/ },
5137
- { name: "/dev/tcp", pattern: /\/dev\/tcp\// },
5138
- { name: "/dev/udp", pattern: /\/dev\/udp\// },
5139
- // Base64 obfuscation
5140
- { name: "$(base64", pattern: /\$\(\s*base64/ },
5141
- { name: "`base64", pattern: /`\s*base64/ },
5142
- { name: "base64 decode pipe", pattern: /base64\s+(-d|--decode)\s*\|/ },
5143
- // Sensitive file access
5144
- { name: "/etc/shadow", pattern: /\/etc\/shadow/ },
5145
- { name: "id_rsa", pattern: /\bid_rsa\b/ },
5146
- { name: "id_ed25519", pattern: /\bid_ed25519\b/ },
5147
- // Persistence backdoor
5148
- { name: "crontab install", pattern: /crontab\s+-/ },
5149
- { name: "authorized_keys", pattern: /authorized_keys/ },
5150
- { name: "ssh no host key check", pattern: /ssh\s+.*StrictHostKeyChecking\s*=\s*no/ }
5151
- ];
5152
- SCRIPT_EXEC_PATTERNS = [
5153
- /bash\s+(\S+\.sh)/,
5154
- /sh\s+(\S+\.sh)/,
5155
- /\.\/(\S+\.sh)/,
5156
- /source\s+(\S+\.sh)/,
5157
- /\.\s+(\S+\.sh)/,
5158
- /zsh\s+(\S+\.sh)/
5159
- ];
5160
- KEYWORD_COMBINATION_RISKS = [
5161
- {
5162
- group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
5163
- group2: /* @__PURE__ */ new Set(["$(base64", "`base64", "base64 decode pipe"]),
5164
- level: "critical",
5165
- 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"
5166
- },
5167
- {
5168
- group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
5169
- group2: /* @__PURE__ */ new Set(["/etc/shadow", "id_rsa", "id_ed25519"]),
5170
- level: "critical",
5171
- 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"
5172
- },
5173
- {
5174
- group1: /* @__PURE__ */ new Set(["nc -e", "ncat -e", "/dev/tcp", "/dev/udp"]),
5175
- group2: /* @__PURE__ */ new Set(),
5176
- level: "high",
5177
- reason: "\u68C0\u6D4B\u5230\u53CD\u5F39 Shell \u7279\u5F81\uFF0C\u653B\u51FB\u8005\u53EF\u80FD\u5EFA\u7ACB\u8FDC\u7A0B\u63A7\u5236\u901A\u9053\u3002"
5178
- },
5179
- {
5180
- group1: /* @__PURE__ */ new Set(["crontab install", "authorized_keys", "ssh no host key check"]),
5181
- group2: /* @__PURE__ */ new Set(),
5182
- level: "high",
5183
- 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"
5184
- }
5185
- ];
5186
- SINGLE_KEYWORD_RISKS = {
5187
- "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" },
5188
- "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" },
5189
- "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" },
5190
- "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" },
5191
- "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" },
5192
- "nc -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 nc -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
5193
- "ncat -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 ncat -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
5194
- "/dev/tcp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/tcp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
5195
- "/dev/udp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/udp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
5196
- "$(base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
5197
- "`base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
5198
- "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" },
5199
- "/etc/shadow": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6\u3002" },
5200
- "id_rsa": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
5201
- "id_ed25519": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
5202
- "crontab install": { level: "high", reason: "\u68C0\u6D4B\u5230\u4FEE\u6539\u5B9A\u65F6\u4EFB\u52A1\uFF0C\u53EF\u80FD\u662F\u6301\u4E45\u5316\u540E\u95E8\u3002" },
5203
- "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" },
5204
- "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" }
5205
- };
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 ?? {};
5206
4972
  LEVEL_ORDER = { low: 1, medium: 2, high: 3, critical: 4 };
5207
4973
  }
5208
4974
  });
@@ -6746,8 +6512,8 @@ var init_tracker = __esm({
6746
6512
  });
6747
6513
 
6748
6514
  // src/engine/script-content-analyzer.ts
6749
- import { statSync as statSync4, existsSync as existsSync10 } from "fs";
6750
- 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";
6751
6517
  import { homedir as homedir3 } from "os";
6752
6518
  function emptyChain() {
6753
6519
  return {
@@ -6780,10 +6546,10 @@ function assessKeywordCombinationRisk2(matchedKeywords, scriptContent) {
6780
6546
  const hitGroup2 = rule.group2.size === 0 ? true : [...rule.group2].some((k) => keywordSet.has(k));
6781
6547
  if (hitGroup1 && hitGroup2) return { level: rule.level, reason: rule.reason };
6782
6548
  }
6783
- 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 ?? []);
6784
6550
  const hasNetworkPost = [...networkPostKeywords].some((k) => keywordSet.has(k));
6785
6551
  if (hasNetworkPost) {
6786
- const envPatterns = ["$env", "$(env)", "$USER", "$(hostname)", "$(pwd)", "$HOME"];
6552
+ const envPatterns = SCRIPT_RULES2.env_exfil_patterns ?? [];
6787
6553
  const contentLower = scriptContent.toLowerCase();
6788
6554
  for (const p of envPatterns) {
6789
6555
  if (contentLower.includes(p.toLowerCase())) {
@@ -6859,7 +6625,7 @@ function extractInlineScriptPath(actionDetail) {
6859
6625
  if (looksLikeScriptPath(tok)) {
6860
6626
  try {
6861
6627
  const resolved = resolvePath2(tok.replace(/^['"]|['"]$/g, ""));
6862
- if (existsSync10(resolved)) {
6628
+ if (existsSync11(resolved)) {
6863
6629
  const stat = statSync4(resolved);
6864
6630
  if (stat.isFile()) return resolved;
6865
6631
  }
@@ -6873,7 +6639,7 @@ function extractInlineScriptPath(actionDetail) {
6873
6639
  const p = m[1];
6874
6640
  try {
6875
6641
  const resolved = resolvePath2(p);
6876
- if (existsSync10(resolved)) {
6642
+ if (existsSync11(resolved)) {
6877
6643
  const stat = statSync4(resolved);
6878
6644
  if (stat.isFile()) return resolved;
6879
6645
  }
@@ -6889,7 +6655,7 @@ function extractInlineScriptPath(actionDetail) {
6889
6655
  let resolved;
6890
6656
  try {
6891
6657
  resolved = resolvePath2(p);
6892
- if (existsSync10(resolved)) {
6658
+ if (existsSync11(resolved)) {
6893
6659
  const stat = statSync4(resolved);
6894
6660
  if (stat.isFile()) return resolved;
6895
6661
  }
@@ -6930,7 +6696,7 @@ function shlexSplit(s) {
6930
6696
  function resolvePath2(p) {
6931
6697
  let expanded = p;
6932
6698
  if (expanded.startsWith("~/") || expanded === "~") {
6933
- expanded = join6(homedir3(), expanded.slice(1));
6699
+ expanded = join10(homedir3(), expanded.slice(1));
6934
6700
  }
6935
6701
  return resolve9(expanded);
6936
6702
  }
@@ -7054,7 +6820,7 @@ function analyzeScriptContent2(input, ext) {
7054
6820
  isInline = true;
7055
6821
  }
7056
6822
  if (resolvedPath && !isAbsolute2(resolvedPath) && !resolvedPath.startsWith("~/")) {
7057
- resolvedPath = join6(input.cwd || process.cwd(), resolvedPath);
6823
+ resolvedPath = join10(input.cwd || process.cwd(), resolvedPath);
7058
6824
  }
7059
6825
  const l1Adapter = (content) => {
7060
6826
  const j = scanDangerousCommandPatterns("bash", content, input.osType);
@@ -7139,7 +6905,7 @@ ${contentPreview}
7139
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";
7140
6906
  return header + parts.join("\n---\n\n");
7141
6907
  }
7142
- 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;
7143
6909
  var init_script_content_analyzer = __esm({
7144
6910
  "src/engine/script-content-analyzer.ts"() {
7145
6911
  "use strict";
@@ -7149,87 +6915,23 @@ var init_script_content_analyzer = __esm({
7149
6915
  init_logic_rules();
7150
6916
  init_inline_analyzer();
7151
6917
  init_tracker();
7152
- SUSPICIOUS_PATTERNS2 = [
7153
- { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
7154
- { name: "curl --data", pattern: /curl\s+(.*\s)?--data/i },
7155
- { name: "curl -d", pattern: /curl\s+(.*\s)?-d\s/i },
7156
- { name: "wget --post-data", pattern: /wget\s+(.*\s)?--post-data/i },
7157
- { name: "wget --post-file", pattern: /wget\s+(.*\s)?--post-file/i },
7158
- { name: "nc -e", pattern: /\bnc\s+.*-e\b/ },
7159
- { name: "ncat -e", pattern: /\bncat\s+.*-e\b/ },
7160
- { name: "/dev/tcp", pattern: /\/dev\/tcp\// },
7161
- { name: "/dev/udp", pattern: /\/dev\/udp\// },
7162
- { name: "$(base64", pattern: /\$\(\s*base64/ },
7163
- { name: "`base64", pattern: /`\s*base64/ },
7164
- { name: "base64 decode pipe", pattern: /base64\s+(-d|--decode)\s*\|/ },
7165
- { name: "/etc/shadow", pattern: /\/etc\/shadow/ },
7166
- { name: "id_rsa", pattern: /\bid_rsa\b/ },
7167
- { name: "id_ed25519", pattern: /\bid_ed25519\b/ },
7168
- { name: "crontab install", pattern: /crontab\s+-/ },
7169
- { name: "authorized_keys", pattern: /authorized_keys/ },
7170
- { name: "ssh no host key check", pattern: /ssh\s+.*StrictHostKeyChecking\s*=\s*no/ }
7171
- ];
7172
- SCRIPT_EXEC_PATTERNS2 = [
7173
- /(?:^|\s|&&|\|\|)\s*bash\s+(\S+)/i,
7174
- /(?:^|\s|&&|\|\|)\s*\/bin\/bash\s+(\S+)/i,
7175
- /(?:^|\s|&&|\|\|)\s*sh\s+(\S+)/i,
7176
- /(?:^|\s|&&|\|\|)\s*zsh\s+(\S+)/i,
7177
- /\.\/(\S+)/,
7178
- /(?:^|\s|&&|\|\|)\s*source\s+(\S+)/i,
7179
- /(?:^|\s|&&|\|\|)\s*\.\s+(\S+)/,
7180
- /(?:^|\s|&&|\|\|)\s*python3?\s+(\S+\.py)/i,
7181
- /(?:^|\s|&&|\|\|)\s*node\s+(\S+\.js)/i,
7182
- /(?:^|\s|&&|\|\|)\s*ruby\s+(\S+\.rb)/i,
7183
- /(?:^|\s|&&|\|\|)\s*perl\s+(\S+\.pl)/i,
7184
- /(?:^|\s|&&|\|\|)\s*php\s+(\S+\.php)/i,
7185
- /(?:^|\s|&&|\|\|)\s*lua\s+(\S+\.lua)/i
7186
- ];
7187
- KEYWORD_COMBINATION_RISKS2 = [
7188
- {
7189
- group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
7190
- group2: /* @__PURE__ */ new Set(["$(base64", "`base64", "base64 decode pipe"]),
7191
- level: "critical",
7192
- 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"
7193
- },
7194
- {
7195
- group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
7196
- group2: /* @__PURE__ */ new Set(["/etc/shadow", "id_rsa", "id_ed25519"]),
7197
- level: "critical",
7198
- 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"
7199
- },
7200
- {
7201
- group1: /* @__PURE__ */ new Set(["nc -e", "ncat -e", "/dev/tcp", "/dev/udp"]),
7202
- group2: /* @__PURE__ */ new Set(),
7203
- level: "high",
7204
- reason: "\u68C0\u6D4B\u5230\u53CD\u5F39 Shell \u7279\u5F81\uFF0C\u653B\u51FB\u8005\u53EF\u80FD\u5EFA\u7ACB\u8FDC\u7A0B\u63A7\u5236\u901A\u9053\u3002"
7205
- },
7206
- {
7207
- group1: /* @__PURE__ */ new Set(["crontab install", "authorized_keys", "ssh no host key check"]),
7208
- group2: /* @__PURE__ */ new Set(),
7209
- level: "high",
7210
- 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"
7211
- }
7212
- ];
7213
- SINGLE_KEYWORD_RISKS2 = {
7214
- "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" },
7215
- "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" },
7216
- "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" },
7217
- "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" },
7218
- "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" },
7219
- "nc -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 nc -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
7220
- "ncat -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 ncat -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
7221
- "/dev/tcp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/tcp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
7222
- "/dev/udp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/udp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
7223
- "$(base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
7224
- "`base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
7225
- "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" },
7226
- "/etc/shadow": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6\u3002" },
7227
- "id_rsa": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
7228
- "id_ed25519": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
7229
- "crontab install": { level: "high", reason: "\u68C0\u6D4B\u5230\u4FEE\u6539\u5B9A\u65F6\u4EFB\u52A1\uFF0C\u53EF\u80FD\u662F\u6301\u4E45\u5316\u540E\u95E8\u3002" },
7230
- "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" },
7231
- "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" }
7232
- };
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 ?? {};
7233
6935
  LEVEL_ORDER2 = { low: 1, medium: 2, high: 3, critical: 4 };
7234
6936
  }
7235
6937
  });
@@ -7244,16 +6946,16 @@ __export(token_stats_exports, {
7244
6946
  recordTokenUsage: () => recordTokenUsage,
7245
6947
  resetTokenStats: () => resetTokenStats
7246
6948
  });
7247
- 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";
7248
6950
  function getStatsPath() {
7249
6951
  const envPath = process.env[ENV_STATS_PATH];
7250
6952
  if (envPath) return envPath;
7251
6953
  return getTokenStatsPath();
7252
6954
  }
7253
6955
  function loadAllRecords(statsPath) {
7254
- if (!existsSync11(statsPath)) return [];
6956
+ if (!existsSync12(statsPath)) return [];
7255
6957
  try {
7256
- const data = JSON.parse(readFileSync11(statsPath, "utf-8"));
6958
+ const data = JSON.parse(readFileSync12(statsPath, "utf-8"));
7257
6959
  if (Array.isArray(data)) return data;
7258
6960
  if (data && Array.isArray(data.records)) return data.records;
7259
6961
  return [];
@@ -7264,7 +6966,7 @@ function loadAllRecords(statsPath) {
7264
6966
  function saveRecords(statsPath, records) {
7265
6967
  try {
7266
6968
  const dir = statsPath.substring(0, statsPath.lastIndexOf("/"));
7267
- if (dir && !existsSync11(dir)) mkdirSync2(dir, { recursive: true });
6969
+ if (dir && !existsSync12(dir)) mkdirSync2(dir, { recursive: true });
7268
6970
  writeFileSync2(statsPath, JSON.stringify(records, null, 2), "utf-8");
7269
6971
  } catch (e) {
7270
6972
  }
@@ -7391,7 +7093,7 @@ function getTokenTrend(days = 0, startDate, endDate) {
7391
7093
  }
7392
7094
  function resetTokenStats() {
7393
7095
  const statsPath = getStatsPath();
7394
- if (existsSync11(statsPath)) {
7096
+ if (existsSync12(statsPath)) {
7395
7097
  try {
7396
7098
  unlinkSync2(statsPath);
7397
7099
  } catch {
@@ -7648,8 +7350,8 @@ var init_llm_analyzer = __esm({
7648
7350
  });
7649
7351
 
7650
7352
  // src/brain/rule-store.ts
7651
- import { existsSync as existsSync12, readFileSync as readFileSync12, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
7652
- 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";
7653
7355
  import { homedir as homedir4 } from "os";
7654
7356
  function getRulesPath() {
7655
7357
  const envPath = process.env[ENV_RULES_PATH];
@@ -7668,7 +7370,7 @@ var init_rule_store = __esm({
7668
7370
  "src/brain/rule-store.ts"() {
7669
7371
  "use strict";
7670
7372
  init_esm_shims();
7671
- DEFAULT_RULES_PATH = join7(homedir4(), ".agentmoss", "brain", "rules.json");
7373
+ DEFAULT_RULES_PATH = join11(homedir4(), ".agentmoss", "brain", "rules.json");
7672
7374
  ENV_RULES_PATH = "AGENT_MOSS_BRAIN_RULES_PATH";
7673
7375
  STATUS_DRAFT = "draft";
7674
7376
  STATUS_APPROVED = "approved";
@@ -7685,11 +7387,11 @@ var init_rule_store = __esm({
7685
7387
  this.data = this.load();
7686
7388
  }
7687
7389
  load() {
7688
- if (!existsSync12(this.path)) {
7390
+ if (!existsSync13(this.path)) {
7689
7391
  return { version: 1, rules: [], pattern_candidates: [] };
7690
7392
  }
7691
7393
  try {
7692
- const raw = JSON.parse(readFileSync12(this.path, "utf-8"));
7394
+ const raw = JSON.parse(readFileSync13(this.path, "utf-8"));
7693
7395
  return {
7694
7396
  version: raw.version || 1,
7695
7397
  rules: raw.rules || [],
@@ -8283,9 +7985,9 @@ var init_reviewer = __esm({
8283
7985
  });
8284
7986
 
8285
7987
  // src/brain/skill-manager.ts
8286
- 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";
8287
7989
  import { homedir as homedir5 } from "os";
8288
- import { join as join8 } from "path";
7990
+ import { join as join12 } from "path";
8289
7991
  import { randomUUID } from "crypto";
8290
7992
  function getMetaPath(override) {
8291
7993
  return override || process.env[ENV_SKILL_META_PATH] || DEFAULT_SKILL_META_PATH;
@@ -8295,14 +7997,14 @@ var init_skill_manager = __esm({
8295
7997
  "src/brain/skill-manager.ts"() {
8296
7998
  "use strict";
8297
7999
  init_esm_shims();
8298
- DEFAULT_SKILL_META_PATH = join8(homedir5(), ".agentmoss", "brain", "skill_meta.json");
8000
+ DEFAULT_SKILL_META_PATH = join12(homedir5(), ".agentmoss", "brain", "skill_meta.json");
8299
8001
  ENV_SKILL_META_PATH = "AGENT_MOSS_BRAIN_SKILL_META_PATH";
8300
8002
  BrainSkillManager = class {
8301
8003
  constructor(skillsDir, metaPath) {
8302
8004
  this.skillsDir = skillsDir;
8303
8005
  this.metaPath = getMetaPath(metaPath);
8304
8006
  try {
8305
- mkdirSync4(join8(this.metaPath, ".."), { recursive: true });
8007
+ mkdirSync4(join12(this.metaPath, ".."), { recursive: true });
8306
8008
  } catch {
8307
8009
  }
8308
8010
  this.load();
@@ -8312,9 +8014,9 @@ var init_skill_manager = __esm({
8312
8014
  skills = [];
8313
8015
  metaPath;
8314
8016
  load() {
8315
- if (!existsSync13(this.metaPath)) return;
8017
+ if (!existsSync14(this.metaPath)) return;
8316
8018
  try {
8317
- const data = JSON.parse(readFileSync13(this.metaPath, "utf-8"));
8019
+ const data = JSON.parse(readFileSync14(this.metaPath, "utf-8"));
8318
8020
  this.skills = data.skills || [];
8319
8021
  } catch {
8320
8022
  this.skills = [];
@@ -8337,7 +8039,7 @@ var init_skill_manager = __esm({
8337
8039
  let changed = false;
8338
8040
  for (const sk of this.skills) {
8339
8041
  if (sk.status !== "active") continue;
8340
- if (!existsSync13(join8(this.skillsDir, `${sk.name}.md`))) {
8042
+ if (!existsSync14(join12(this.skillsDir, `${sk.name}.md`))) {
8341
8043
  sk.status = "stale";
8342
8044
  changed = true;
8343
8045
  }
@@ -8348,9 +8050,9 @@ var init_skill_manager = __esm({
8348
8050
  addSkill(skill) {
8349
8051
  const id = skill.id || randomUUID();
8350
8052
  const name = skill.name || `brain_${id.slice(0, 8)}`;
8351
- if (!existsSync13(this.skillsDir)) return "";
8352
- const mdPath = join8(this.skillsDir, `${name}.md`);
8353
- if (existsSync13(mdPath)) return "";
8053
+ if (!existsSync14(this.skillsDir)) return "";
8054
+ const mdPath = join12(this.skillsDir, `${name}.md`);
8055
+ if (existsSync14(mdPath)) return "";
8354
8056
  try {
8355
8057
  writeFileSync4(mdPath, skill.content, "utf-8");
8356
8058
  } catch {
@@ -8399,12 +8101,12 @@ var init_skill_manager = __esm({
8399
8101
  const out = [];
8400
8102
  for (const sk of this.skills) {
8401
8103
  if (sk.origin_node !== "local" || sk.status !== "active") continue;
8402
- const mdPath = join8(this.skillsDir, `${sk.name}.md`);
8403
- if (!existsSync13(mdPath)) continue;
8104
+ const mdPath = join12(this.skillsDir, `${sk.name}.md`);
8105
+ if (!existsSync14(mdPath)) continue;
8404
8106
  out.push({
8405
8107
  name: sk.name,
8406
8108
  category: sk.category || "",
8407
- content: readFileSync13(mdPath, "utf-8"),
8109
+ content: readFileSync14(mdPath, "utf-8"),
8408
8110
  keywords: sk.keywords || []
8409
8111
  });
8410
8112
  }
@@ -8516,7 +8218,7 @@ __export(contributor_exports, {
8516
8218
  });
8517
8219
  import { mkdirSync as mkdirSync5, mkdtempSync, rmSync, writeFileSync as writeFileSync5 } from "fs";
8518
8220
  import { tmpdir } from "os";
8519
- import { join as join9 } from "path";
8221
+ import { join as join13 } from "path";
8520
8222
  import { spawnSync } from "child_process";
8521
8223
  import { dirname as dirname9 } from "path";
8522
8224
  import { fileURLToPath as fileURLToPath9 } from "url";
@@ -8537,32 +8239,32 @@ function collectSkills(skillMgr) {
8537
8239
  }
8538
8240
  function writeExportFiles(outDir, rules, skills) {
8539
8241
  const written = [];
8540
- const rulesDir = join9(outDir, "rules");
8541
- mkdirSync5(rulesDir, { recursive: true });
8242
+ const rulesDir2 = join13(outDir, "rules");
8243
+ mkdirSync5(rulesDir2, { recursive: true });
8542
8244
  const byLayer = /* @__PURE__ */ new Map();
8543
8245
  for (const r of rules) {
8544
8246
  if (!byLayer.has(r.layer)) byLayer.set(r.layer, []);
8545
8247
  byLayer.get(r.layer).push(r);
8546
8248
  }
8547
8249
  for (const layer of [...byLayer.keys()].sort((a, b) => a - b)) {
8548
- const path6 = join9(rulesDir, `l${layer}_brain_rules.json`);
8250
+ const path6 = join13(rulesDir2, `l${layer}_brain_rules.json`);
8549
8251
  writeFileSync5(path6, JSON.stringify(byLayer.get(layer), null, 2), "utf-8");
8550
8252
  written.push(path6);
8551
8253
  }
8552
8254
  if (rules.length === 0) {
8553
- const path6 = join9(rulesDir, "empty.txt");
8255
+ const path6 = join13(rulesDir2, "empty.txt");
8554
8256
  writeFileSync5(path6, "(no brain rules to contribute)", "utf-8");
8555
8257
  written.push(path6);
8556
8258
  }
8557
- const skillsDir = join9(outDir, "skills");
8259
+ const skillsDir = join13(outDir, "skills");
8558
8260
  mkdirSync5(skillsDir, { recursive: true });
8559
8261
  for (const sk of skills) {
8560
- const path6 = join9(skillsDir, `${sk.name}.md`);
8262
+ const path6 = join13(skillsDir, `${sk.name}.md`);
8561
8263
  writeFileSync5(path6, sk.content || "", "utf-8");
8562
8264
  written.push(path6);
8563
8265
  }
8564
8266
  if (skills.length === 0) {
8565
- const path6 = join9(skillsDir, "empty.txt");
8267
+ const path6 = join13(skillsDir, "empty.txt");
8566
8268
  writeFileSync5(path6, "(no brain skills to contribute)", "utf-8");
8567
8269
  written.push(path6);
8568
8270
  }
@@ -8692,7 +8394,7 @@ async function contribute(opts = {}) {
8692
8394
  console.log(`[Brain] push \u76EE\u6807: ${pushTarget}`);
8693
8395
  console.log(`[Brain] MR \u6307\u5411: ${DEFAULT_TARGET_REPO}`);
8694
8396
  const store = new BrainRuleStore();
8695
- const skillsDir = resolveAssetDir(__dirname8, "skills") ?? join9(__dirname8, "..", "..", "agent_moss", "skills");
8397
+ const skillsDir = resolveAssetDir(__dirname8, "skills") ?? join13(__dirname8, "..", "..", "agent_moss", "skills");
8696
8398
  const skillMgr = new BrainSkillManager(skillsDir);
8697
8399
  let rules;
8698
8400
  let skills;
@@ -8707,9 +8409,9 @@ async function contribute(opts = {}) {
8707
8409
  return 0;
8708
8410
  }
8709
8411
  console.log(`[Brain] \u5BFC\u51FA: ${rules.length} \u6761\u89C4\u5219, ${skills.length} \u4E2A Skill`);
8710
- const workdir = mkdtempSync(join9(tmpdir(), "agentmoss-contrib-"));
8412
+ const workdir = mkdtempSync(join13(tmpdir(), "agentmoss-contrib-"));
8711
8413
  try {
8712
- writeExportFiles(join9(workdir, "agent_moss", "brain_contrib"), rules, skills);
8414
+ writeExportFiles(join13(workdir, "agent_moss", "brain_contrib"), rules, skills);
8713
8415
  console.log(`[Brain] \u63A2\u6D4B ${pushTarget} \u8BBF\u95EE\u6743\u9650 ...`);
8714
8416
  if (!probeAccess(pushTarget)) {
8715
8417
  const username = getCurrentUsername();
@@ -8957,9 +8659,10 @@ var init_decode = __esm({
8957
8659
  "src/engine/formal-engine/decode.ts"() {
8958
8660
  "use strict";
8959
8661
  init_esm_shims();
8662
+ init_rules_loader();
8960
8663
  B64_TOKEN_RE = /[A-Za-z0-9+/]+={0,2}/g;
8961
8664
  QUOTED_STR_RE = /('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`)/g;
8962
- OUTPUT_CMDS = /* @__PURE__ */ new Set(["echo", "printf", "print"]);
8665
+ OUTPUT_CMDS = new Set(loadFormalRules().output_commands ?? []);
8963
8666
  STAGE_PRIORITY = ["EXFIL", "PRIVESC", "PERSIST", "SCAN", "LISTEN"];
8964
8667
  PURPOSE_EXEC_RE = /subprocess\.\w+\s*\(|os\.system\s*\(|shell\s*=\s*True|\beval\s*\(|\bexec\s*\(|os\.popen\s*\(|\/exec\b/i;
8965
8668
  LISTEN_LEAD_RE = /http\.server|flask\s+run|nohup\b|nc\s+-l|socat\s+TCP-LISTEN/i;
@@ -9677,7 +9380,7 @@ function checkSensitiveDataAggregation(graph) {
9677
9380
  }
9678
9381
  return violations;
9679
9382
  }
9680
- 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;
9681
9384
  var init_properties = __esm({
9682
9385
  "src/engine/formal-engine/properties.ts"() {
9683
9386
  "use strict";
@@ -9694,11 +9397,12 @@ var init_properties = __esm({
9694
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;
9695
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;
9696
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;
9697
- PKG_MGR = /* @__PURE__ */ new Set(["apt", "apt-get", "aptitude", "yum", "dnf", "pacman", "zypper", "apk", "brew"]);
9698
- 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 ?? []);
9699
9403
  TOOL_REGISTRY = null;
9700
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;
9701
- STRONG_COMPUTE_TYPES = /* @__PURE__ */ new Set(["python", "node", "exec", "terminal", "cmd"]);
9405
+ STRONG_COMPUTE_TYPES = new Set(FORMAL_RULES.compute_action_types ?? []);
9702
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;
9703
9407
  ALL_PROPERTIES = [
9704
9408
  { id: "cross-session-chain", check: checkCrossSessionChain, shortName: "P1" },
@@ -10222,10 +9926,10 @@ var init_stateful = __esm({
10222
9926
  import * as fs4 from "fs";
10223
9927
  import * as os2 from "os";
10224
9928
  import * as path4 from "path";
10225
- import { createHash } from "crypto";
9929
+ import { createHash as createHash2 } from "crypto";
10226
9930
  function defaultStateDbPath() {
10227
9931
  const dir = process.env.AGENT_MOSS_STATE_DIR || path4.join(os2.homedir(), ".agentmoss");
10228
- 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);
10229
9933
  return path4.join(dir, `stateful_chains_${tag}.json`);
10230
9934
  }
10231
9935
  var ChainStateStore;
@@ -10485,7 +10189,7 @@ async function runAnalysis(input) {
10485
10189
  const actionType = input.aNext.actionType.toLowerCase();
10486
10190
  const rawDetail = extractRawCommand(input.aNext.actionDetail);
10487
10191
  const lowerDetail = rawDetail.toLowerCase();
10488
- if (FULLY_SAFE_TOOLS.has(actionType)) {
10192
+ if (getPlatformTraits(input.osType).safeActionTypes.has(actionType)) {
10489
10193
  return {
10490
10194
  allowed: true,
10491
10195
  reason: `\u5B8C\u5168\u5B89\u5168\u5DE5\u5177\uFF0C\u8DF3\u8FC7\u6DF1\u5EA6\u5206\u6790: ${actionType}`,
@@ -10528,7 +10232,7 @@ async function runAnalysis(input) {
10528
10232
  violatedLayers: []
10529
10233
  };
10530
10234
  }
10531
- let skipLLM = READONLY_SENSITIVE_TOOLS.has(actionType);
10235
+ let skipLLM = getPlatformTraits(input.osType).readonlySensitiveActionTypes.has(actionType);
10532
10236
  if (actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash") {
10533
10237
  const firstCmd = extractFirstCommand(lowerDetail);
10534
10238
  const { fullySafeCommands, readonlySensitiveCommands } = getPlatformTraits(input.osType);
@@ -10760,7 +10464,7 @@ async function runAnalysis(input) {
10760
10464
  }
10761
10465
  return llmResult;
10762
10466
  }
10763
- 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;
10764
10468
  var init_coordinator = __esm({
10765
10469
  "src/engine/coordinator.ts"() {
10766
10470
  "use strict";
@@ -10783,25 +10487,7 @@ var init_coordinator = __esm({
10783
10487
  init_asset_dir();
10784
10488
  init_runtime_config_bridge();
10785
10489
  init_formal_engine();
10786
- FULLY_SAFE_TOOLS = /* @__PURE__ */ new Set([
10787
- "ask_user_question",
10788
- "glob",
10789
- "list_dir",
10790
- "ls",
10791
- "count_text_length",
10792
- "filemgr-globfiles"
10793
- ]);
10794
10490
  TRUSTED_SKILLS_BASE = /* @__PURE__ */ new Set([]);
10795
- READONLY_SENSITIVE_TOOLS = /* @__PURE__ */ new Set([
10796
- "read",
10797
- "file_read",
10798
- "read_file",
10799
- "head",
10800
- "tail",
10801
- "grep",
10802
- "filemgr-readfile",
10803
- "filemgr-grepfiles"
10804
- ]);
10805
10491
  _brainStore = null;
10806
10492
  _brainExtractor = null;
10807
10493
  _brainSkillManager = null;
@@ -10814,10 +10500,10 @@ var init_coordinator = __esm({
10814
10500
  });
10815
10501
 
10816
10502
  // src/infra/policy-cache.ts
10817
- import { createHash as createHash2 } from "crypto";
10818
- 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";
10819
10505
  import { tmpdir as tmpdir2 } from "os";
10820
- import { join as join11 } from "path";
10506
+ import { join as join15 } from "path";
10821
10507
  function cacheDir() {
10822
10508
  return cacheDirOverride ?? CACHE_DIR;
10823
10509
  }
@@ -10831,7 +10517,7 @@ var init_policy_cache = __esm({
10831
10517
  "use strict";
10832
10518
  init_esm_shims();
10833
10519
  init_config();
10834
- CACHE_DIR = join11(tmpdir2(), "agent_moss_policy_cache");
10520
+ CACHE_DIR = join15(tmpdir2(), "agent_moss_policy_cache");
10835
10521
  cacheDirOverride = null;
10836
10522
  PolicyCache = class {
10837
10523
  // Map 的插入序即 LRU 序(get 时重新 set 到末尾)
@@ -10844,9 +10530,9 @@ var init_policy_cache = __esm({
10844
10530
  return `${sessionId}::${promptSession}`;
10845
10531
  }
10846
10532
  cacheFilePath(sessionId, promptSession) {
10847
- const promptHash = createHash2("md5").update(promptSession).digest("hex").slice(0, 12);
10533
+ const promptHash = createHash3("md5").update(promptSession).digest("hex").slice(0, 12);
10848
10534
  const safeSessionId = sessionId.replace(/\//g, "_").replace(/\\/g, "_");
10849
- return join11(cacheDir(), safeSessionId, `${promptHash}.toml`);
10535
+ return join15(cacheDir(), safeSessionId, `${promptHash}.toml`);
10850
10536
  }
10851
10537
  /** 取缓存:内存 → 文件;未命中返回 null。 */
10852
10538
  get(sessionId, promptSession) {
@@ -10859,9 +10545,9 @@ var init_policy_cache = __esm({
10859
10545
  return hit;
10860
10546
  }
10861
10547
  const file = this.cacheFilePath(sessionId, promptSession);
10862
- if (existsSync15(file)) {
10548
+ if (existsSync16(file)) {
10863
10549
  try {
10864
- const policy = readFileSync15(file, "utf-8");
10550
+ const policy = readFileSync16(file, "utf-8");
10865
10551
  this.remember(key, policy);
10866
10552
  return policy;
10867
10553
  } catch {
@@ -10875,7 +10561,7 @@ var init_policy_cache = __esm({
10875
10561
  this.remember(this.cacheKey(sessionId, promptSession), policy);
10876
10562
  const file = this.cacheFilePath(sessionId, promptSession);
10877
10563
  try {
10878
- mkdirSync7(join11(cacheDir(), sessionId.replace(/\//g, "_").replace(/\\/g, "_")), { recursive: true });
10564
+ mkdirSync7(join15(cacheDir(), sessionId.replace(/\//g, "_").replace(/\\/g, "_")), { recursive: true });
10879
10565
  writeFileSync7(file, policy, "utf-8");
10880
10566
  } catch {
10881
10567
  }
@@ -10901,12 +10587,12 @@ var init_policy_cache = __esm({
10901
10587
  if (k.startsWith(`${sessionId}::`)) this.memory.delete(k);
10902
10588
  }
10903
10589
  const safeSessionId = sessionId.replace(/\//g, "_").replace(/\\/g, "_");
10904
- const dir = join11(cacheDir(), safeSessionId);
10590
+ const dir = join15(cacheDir(), safeSessionId);
10905
10591
  try {
10906
- if (existsSync15(dir)) {
10592
+ if (existsSync16(dir)) {
10907
10593
  for (const f of readdirSync5(dir)) {
10908
10594
  try {
10909
- unlinkSync3(join11(dir, f));
10595
+ unlinkSync3(join15(dir, f));
10910
10596
  } catch {
10911
10597
  }
10912
10598
  }
@@ -10918,11 +10604,11 @@ var init_policy_cache = __esm({
10918
10604
  clear() {
10919
10605
  this.memory.clear();
10920
10606
  try {
10921
- if (existsSync15(cacheDir())) {
10607
+ if (existsSync16(cacheDir())) {
10922
10608
  for (const sessionDir of readdirSync5(cacheDir())) {
10923
- const dir = join11(cacheDir(), sessionDir);
10609
+ const dir = join15(cacheDir(), sessionDir);
10924
10610
  try {
10925
- for (const f of readdirSync5(dir)) unlinkSync3(join11(dir, f));
10611
+ for (const f of readdirSync5(dir)) unlinkSync3(join15(dir, f));
10926
10612
  rmdirSync(dir);
10927
10613
  } catch {
10928
10614
  }
@@ -11018,9 +10704,9 @@ __export(analyzer_exports, {
11018
10704
  savePolicyToFile: () => savePolicyToFile,
11019
10705
  validateToml: () => validateToml
11020
10706
  });
11021
- 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";
11022
10708
  import { tmpdir as tmpdir3 } from "os";
11023
- import { join as join12, resolve as resolve10 } from "path";
10709
+ import { join as join16, resolve as resolve10 } from "path";
11024
10710
  import { parse as parseToml } from "smol-toml";
11025
10711
  function _setPolicyOutputDirForTest(dir) {
11026
10712
  policyOutputDirOverride = dir;
@@ -11034,7 +10720,7 @@ function savePolicyToFile(sessionId, policyToml) {
11034
10720
  const outDir = policyOutputDir();
11035
10721
  mkdirSync8(outDir, { recursive: true });
11036
10722
  const safeName = sessionId.replace(/\//g, "_").replace(/\\/g, "_");
11037
- const filepath = join12(outDir, `${safeName}.toml`);
10723
+ const filepath = join16(outDir, `${safeName}.toml`);
11038
10724
  writeFileSync8(filepath, policyToml, "utf-8");
11039
10725
  return filepath;
11040
10726
  } catch (err) {
@@ -11073,11 +10759,11 @@ function loadPolicyMappingDoc() {
11073
10759
  const dir = resolveAssetDir(__dirname, "templates");
11074
10760
  if (dir) {
11075
10761
  const p = resolve10(dir, "policy_mapping.md");
11076
- if (existsSync16(p)) return readFileSync16(p, "utf-8");
10762
+ if (existsSync17(p)) return readFileSync17(p, "utf-8");
11077
10763
  }
11078
10764
  for (const cand of assetDirCandidates(__dirname, "templates")) {
11079
10765
  const p = resolve10(cand, "policy_mapping.md");
11080
- if (existsSync16(p)) return readFileSync16(p, "utf-8");
10766
+ if (existsSync17(p)) return readFileSync17(p, "utf-8");
11081
10767
  }
11082
10768
  return "";
11083
10769
  }
@@ -11247,7 +10933,7 @@ var init_analyzer = __esm({
11247
10933
  init_asset_dir();
11248
10934
  init_token_stats();
11249
10935
  init_logging();
11250
- POLICY_OUTPUT_DIR = join12(tmpdir3(), "agent_moss");
10936
+ POLICY_OUTPUT_DIR = join16(tmpdir3(), "agent_moss");
11251
10937
  policyOutputDirOverride = null;
11252
10938
  DEFAULT_READONLY_POLICY = `landlock_optional = false
11253
10939
  mount_isolation_fallback = false
@@ -11470,14 +11156,16 @@ function mineDangerousPatterns(sessions, minSup = 2, maxLen = 4) {
11470
11156
  out.sort((a, b) => b.support - a.support || b.pattern.length - a.pattern.length);
11471
11157
  return out;
11472
11158
  }
11473
- var DANGER_STAGES, SENSITIVE_PAIRS;
11159
+ var _PM, DANGER_STAGES, SENSITIVE_PAIRS;
11474
11160
  var init_pattern_miner = __esm({
11475
11161
  "src/engine/formal-engine/pattern_miner.ts"() {
11476
11162
  "use strict";
11477
11163
  init_esm_shims();
11164
+ init_rules_loader();
11478
11165
  init_builder();
11479
- DANGER_STAGES = /* @__PURE__ */ new Set(["PRIVESC", "EXFIL", "LISTEN"]);
11480
- 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("|")));
11481
11169
  }
11482
11170
  });
11483
11171
 
@@ -11657,7 +11345,7 @@ var init_observable = __esm({
11657
11345
  });
11658
11346
 
11659
11347
  // src/routes.ts
11660
- import { join as join14, dirname as dirname11 } from "path";
11348
+ import { join as join18, dirname as dirname11 } from "path";
11661
11349
  import { fileURLToPath as fileURLToPath10 } from "url";
11662
11350
  async function healthRoute(c) {
11663
11351
  return c.json(HealthResponseSchema.parse({
@@ -11906,7 +11594,7 @@ function getBrainRuleStore() {
11906
11594
  return new BrainRuleStore();
11907
11595
  }
11908
11596
  function getBrainSkillManager() {
11909
- const dir = resolveAssetDir(__dirname9, "skills") ?? join14(__dirname9, "..", "..", "agent_moss", "skills");
11597
+ const dir = resolveAssetDir(__dirname9, "skills") ?? join18(__dirname9, "..", "..", "agent_moss", "skills");
11910
11598
  return new BrainSkillManager(dir);
11911
11599
  }
11912
11600
  async function brainRulesRoute(c) {
@@ -12019,8 +11707,8 @@ var init_routes = __esm({
12019
11707
 
12020
11708
  // src/console/router.ts
12021
11709
  import { Hono } from "hono";
12022
- import { readFileSync as readFileSync18, existsSync as existsSync18, unlinkSync as unlinkSync4 } from "fs";
12023
- 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";
12024
11712
  import { fileURLToPath as fileURLToPath11 } from "url";
12025
11713
  function getConsoleToken() {
12026
11714
  return process.env[CONSOLE_TOKEN_ENV] || "";
@@ -12058,9 +11746,9 @@ function createConsoleRouter() {
12058
11746
  console2.use("/api/*", authMiddleware());
12059
11747
  console2.use("/api/*", builtinOnlyReadOnlyMiddleware());
12060
11748
  const serveIndex = (c) => {
12061
- const indexFile = join15(STATIC_DIR, "index.html");
12062
- if (existsSync18(indexFile)) {
12063
- 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"));
12064
11752
  }
12065
11753
  return c.html("<h1>AgentMoss Policy Console</h1><p>Frontend not built. Use /console/api/* endpoints.</p>");
12066
11754
  };
@@ -12255,15 +11943,15 @@ function createConsoleRouter() {
12255
11943
  if (!/^[A-Za-z0-9_-]+$/.test(skillId)) {
12256
11944
  return c.json({ detail: "\u975E\u6CD5 skill id" }, 400);
12257
11945
  }
12258
- const userPath = join15(dirname12(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
11946
+ const userPath = join19(dirname12(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
12259
11947
  const builtinDir = resolveAssetDir(__dirname10, "skills");
12260
11948
  const candidates = [
12261
11949
  ...isRuntimeConfigDisabled() ? [] : [userPath],
12262
- builtinDir ? join15(builtinDir, `${skillId}.md`) : ""
11950
+ builtinDir ? join19(builtinDir, `${skillId}.md`) : ""
12263
11951
  ].filter(Boolean);
12264
11952
  for (const p of candidates) {
12265
- if (existsSync18(p)) {
12266
- 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") });
12267
11955
  }
12268
11956
  }
12269
11957
  return c.json({ detail: `skill ${skillId} \u4E0D\u5B58\u5728` }, 404);
@@ -12360,7 +12048,7 @@ function createConsoleRouter() {
12360
12048
  const defaults = generateSourceDefaults();
12361
12049
  try {
12362
12050
  const p = getRuntimeConfigPath();
12363
- if (existsSync18(p)) unlinkSync4(p);
12051
+ if (existsSync19(p)) unlinkSync4(p);
12364
12052
  } catch {
12365
12053
  }
12366
12054
  return c.json(withL22View(defaults));
@@ -12397,7 +12085,7 @@ var init_router = __esm({
12397
12085
  init_asset_dir();
12398
12086
  init_rules_mode();
12399
12087
  __dirname10 = dirname12(fileURLToPath11(import.meta.url));
12400
- STATIC_DIR = join15(__dirname10, "static");
12088
+ STATIC_DIR = join19(__dirname10, "static");
12401
12089
  CONSOLE_TOKEN_ENV = "AGENT_MOSS_CONSOLE_TOKEN";
12402
12090
  }
12403
12091
  });
@@ -12424,7 +12112,7 @@ import { Hono as Hono2 } from "hono";
12424
12112
  import { cors } from "hono/cors";
12425
12113
  import { logger } from "hono/logger";
12426
12114
  import net from "net";
12427
- import { existsSync as existsSync19 } from "fs";
12115
+ import { existsSync as existsSync20 } from "fs";
12428
12116
  function logDeploymentSelfCheck() {
12429
12117
  try {
12430
12118
  const mode = getRulesMode();
@@ -12451,7 +12139,7 @@ function logDeploymentSelfCheck() {
12451
12139
  console.log("\u5C42\u7EA7\u5F00\u5173: " + layerKeys.map((k) => `${k}=${isLayerEnabled(k) ? "on" : "OFF"}`).join(" "));
12452
12140
  if (!builtinOnly) {
12453
12141
  try {
12454
- const dirs = rulesDirs().filter((d) => existsSync19(d));
12142
+ const dirs = rulesDirs().filter((d) => existsSync20(d));
12455
12143
  if (dirs.length > 0) {
12456
12144
  console.log(`\u89C4\u5219\u76EE\u5F55: ${dirs.join(", ")}\uFF08\u53EA\u8BFB\u5176\u4E2D ${OVERRIDE_FILENAME} / ${EXTRA_FILENAME}\uFF09`);
12457
12145
  }
@@ -12490,6 +12178,7 @@ function logDeploymentSelfCheck() {
12490
12178
  }
12491
12179
  }
12492
12180
  function createApp() {
12181
+ enforceRulesContract();
12493
12182
  const app = new Hono2();
12494
12183
  app.use("*", cors({ origin: "*", allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], allowHeaders: ["*"] }));
12495
12184
  app.use("*", logger());
@@ -12578,7 +12267,7 @@ async function startUnixSocketServer(socketPath = "/var/run/agent_moss/agent_mos
12578
12267
  server = null;
12579
12268
  },
12580
12269
  serve: async () => {
12581
- const { mkdirSync: mkdirSync10, existsSync: existsSync21, unlinkSync: unlinkSync5 } = await import("fs");
12270
+ const { mkdirSync: mkdirSync10, existsSync: existsSync22, unlinkSync: unlinkSync5 } = await import("fs");
12582
12271
  const { dirname: dirname14 } = await import("path");
12583
12272
  const dir = dirname14(socketPath);
12584
12273
  if (dir) {
@@ -12587,7 +12276,7 @@ async function startUnixSocketServer(socketPath = "/var/run/agent_moss/agent_mos
12587
12276
  } catch {
12588
12277
  }
12589
12278
  }
12590
- if (existsSync21(socketPath)) {
12279
+ if (existsSync22(socketPath)) {
12591
12280
  const { connect } = await import("net");
12592
12281
  const inUse = await new Promise((resolve11) => {
12593
12282
  const probe = connect(socketPath);
@@ -12629,6 +12318,7 @@ var init_server = __esm({
12629
12318
  "src/server.ts"() {
12630
12319
  "use strict";
12631
12320
  init_esm_shims();
12321
+ init_rules_contract();
12632
12322
  init_routes();
12633
12323
  init_console();
12634
12324
  init_runtime_config();
@@ -12652,26 +12342,26 @@ __export(rules_tools_exports, {
12652
12342
  rulesStatusText: () => rulesStatusText,
12653
12343
  templateSourceDir: () => templateSourceDir
12654
12344
  });
12655
- import { existsSync as existsSync20, mkdirSync as mkdirSync9, readdirSync as readdirSync7, readFileSync as readFileSync19, writeFileSync as writeFileSync9 } from "fs";
12656
- 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";
12657
12347
  import { fileURLToPath as fileURLToPath12 } from "url";
12658
12348
  function initRulesDir(targetDir, opts = {}) {
12659
12349
  mkdirSync9(targetDir, { recursive: true });
12660
12350
  const written = [];
12661
- const extraPath = join16(targetDir, EXTRA_FILENAME);
12662
- if (opts.force || !existsSync20(extraPath)) {
12351
+ const extraPath = join20(targetDir, EXTRA_FILENAME);
12352
+ if (opts.force || !existsSync21(extraPath)) {
12663
12353
  writeFileSync9(extraPath, JSON.stringify(EXTRA_TEMPLATE, null, 2), "utf-8");
12664
12354
  written.push(extraPath);
12665
12355
  }
12666
- const samplePath = join16(targetDir, OVERRIDE_FILENAME);
12667
- if (opts.force || !existsSync20(samplePath)) {
12356
+ const samplePath = join20(targetDir, OVERRIDE_FILENAME);
12357
+ if (opts.force || !existsSync21(samplePath)) {
12668
12358
  let payload = OVERRIDE_TEMPLATE;
12669
12359
  if (opts.withBuiltin) {
12670
12360
  let data = {};
12671
12361
  const builtin = resolveRulesFile("l1_rules.json");
12672
- if (builtin && existsSync20(builtin)) {
12362
+ if (builtin && existsSync21(builtin)) {
12673
12363
  try {
12674
- data = JSON.parse(readFileSync19(builtin, "utf-8")) || {};
12364
+ data = JSON.parse(readFileSync20(builtin, "utf-8")) || {};
12675
12365
  } catch {
12676
12366
  data = {};
12677
12367
  }
@@ -12682,17 +12372,17 @@ function initRulesDir(targetDir, opts = {}) {
12682
12372
  writeFileSync9(samplePath, JSON.stringify(payload, null, 2), "utf-8");
12683
12373
  written.push(samplePath);
12684
12374
  }
12685
- const readmeSrc = join16(templateSourceDir(), "README.md");
12686
- const readmeDst = join16(targetDir, "README.md");
12687
- if (existsSync20(readmeSrc) && (opts.force || !existsSync20(readmeDst))) {
12688
- 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");
12689
12379
  written.push(readmeDst);
12690
12380
  }
12691
12381
  return written;
12692
12382
  }
12693
12383
  function templateSourceDir() {
12694
- const cands = [join16(__dirname11, "data", "rules_template"), join16(__dirname11, "..", "..", "agent_moss", "data", "rules_template")];
12695
- 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;
12696
12386
  return cands[0];
12697
12387
  }
12698
12388
  function templateFiles(dirs) {
@@ -12700,7 +12390,7 @@ function templateFiles(dirs) {
12700
12390
  for (const d of dirs) {
12701
12391
  try {
12702
12392
  for (const name of readdirSync7(d)) {
12703
- if (name.endsWith(".json.\u793A\u4F8B")) out.push(join16(d, name));
12393
+ if (name.endsWith(".json.\u793A\u4F8B")) out.push(join20(d, name));
12704
12394
  }
12705
12395
  } catch {
12706
12396
  }
@@ -12709,7 +12399,7 @@ function templateFiles(dirs) {
12709
12399
  }
12710
12400
  function rulesStatusText() {
12711
12401
  const lines = [];
12712
- const existing = rulesDirs().filter((d) => existsSync20(d));
12402
+ const existing = rulesDirs().filter((d) => existsSync21(d));
12713
12403
  lines.push("\u89C4\u5219\u76EE\u5F55: " + (existing.length > 0 ? existing.join(", ") : "\uFF08\u672A\u8BBE\u7F6E / \u4E0D\u5B58\u5728\uFF09"));
12714
12404
  if (existing.length > 0) {
12715
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`);
@@ -12861,6 +12551,17 @@ async function analyzeRawCommand(command, useJson = false) {
12861
12551
  async function main() {
12862
12552
  const args = process.argv.slice(2);
12863
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
+ }
12864
12565
  if (command === "--version" || command === "-V") {
12865
12566
  console.log(`agent-moss ${version}`);
12866
12567
  process.exit(0);