@kenhkl/agent-moss 0.16.12 → 0.16.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -25,6 +25,10 @@ var init_esm_shims = __esm({
25
25
  import { join } from "path";
26
26
  import { homedir } from "os";
27
27
  import { readFileSync } from "fs";
28
+ function getRulesRoot() {
29
+ const raw = (process.env[ENV_RULES_ROOT] ?? "").trim();
30
+ return raw ? raw : null;
31
+ }
28
32
  function isRoot() {
29
33
  const getuid = process.getuid;
30
34
  return typeof getuid === "function" && getuid() === 0;
@@ -94,7 +98,7 @@ function getTokenStatsPath() {
94
98
  }
95
99
  return join(getDataRoot(), "agent_moss_token_stats.json");
96
100
  }
97
- var ENV_DATA_ROOT, ENV_INSTANCE, DEFAULT_DATA_ROOT;
101
+ var ENV_DATA_ROOT, ENV_INSTANCE, DEFAULT_DATA_ROOT, ENV_RULES_ROOT;
98
102
  var init_paths = __esm({
99
103
  "src/infra/paths.ts"() {
100
104
  "use strict";
@@ -102,282 +106,53 @@ var init_paths = __esm({
102
106
  ENV_DATA_ROOT = "AGENT_MOSS_DATA_DIR";
103
107
  ENV_INSTANCE = "AGENT_MOSS_INSTANCE";
104
108
  DEFAULT_DATA_ROOT = join(homedir(), ".config", "agentmoss");
109
+ ENV_RULES_ROOT = "AGENT_MOSS_RULES_ROOT";
105
110
  }
106
111
  });
107
112
 
108
- // src/engine/shell-split.ts
109
- function hasCommandSubstitution(text) {
110
- return CMD_SUBSTITUTION.test(text);
111
- }
112
- function splitCommandSegments(command) {
113
- const segments = [];
114
- let cur = "";
115
- let quote = null;
116
- for (let i = 0; i < command.length; i++) {
117
- const ch = command[i];
118
- if (quote) {
119
- cur += ch;
120
- if (ch === quote) quote = null;
121
- continue;
122
- }
123
- if (ch === "'" || ch === '"') {
124
- quote = ch;
125
- cur += ch;
126
- continue;
127
- }
128
- if (/\||&|;|\n/.test(ch)) {
129
- segments.push(cur);
130
- segments.push(ch);
131
- cur = "";
132
- continue;
133
- }
134
- cur += ch;
135
- }
136
- if (cur) segments.push(cur);
137
- return segments.map((s) => s.trim()).filter((s) => s.length > 0 && !/^(?:\|\||&&|[|;&])$/.test(s));
113
+ // src/infra/rules-mode.ts
114
+ function truthy(name) {
115
+ return TRUTHY.has((process.env[name] || "").trim().toLowerCase());
138
116
  }
139
- function extractFirstCommand(command) {
140
- const segments = splitCommandSegments(command);
141
- return segments.length ? segments[0] : "";
142
- }
143
- var CMD_SUBSTITUTION;
144
- var init_shell_split = __esm({
145
- "src/engine/shell-split.ts"() {
146
- "use strict";
147
- init_esm_shims();
148
- CMD_SUBSTITUTION = /\$\(|`|</;
149
- }
150
- });
151
-
152
- // src/engine/inline-analyzer.ts
153
- function isLiteralTextCommand(command) {
154
- const segments = splitCommandSegments(command);
155
- if (segments.length === 0 || segments.some((s) => !ECHO_SEG_RE.test(s))) return false;
156
- if (command.includes(">")) {
157
- let d = command.replace(/<=>|>=|<=|<>|=>/g, " ");
158
- d = d.replace(/(?:2|&|1)?\s*>\s*\/dev\/null\b/g, "");
159
- d = d.replace(new RegExp(`>{1,2}\\s*(['"])([~./][^'"]+)\\1`, "g"), (_m, _q, p) => ">" + p);
160
- d = d.replace(/'[^']*'|"[^"]*"/g, "");
161
- if (/>{1,2}\s*(?!&)\S/.test(d)) {
162
- return false;
117
+ function getRulesMode() {
118
+ if (cached !== null) return cached;
119
+ const raw = (process.env[ENV_RULES_MODE] || "").trim().toLowerCase();
120
+ if (raw) {
121
+ if (VALID_MODES.has(raw)) {
122
+ cached = raw;
123
+ } else {
124
+ console.error(
125
+ `[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`
126
+ );
127
+ cached = "builtin";
163
128
  }
129
+ return cached;
164
130
  }
165
- if (/\|\s*\b(?:tee|tac|install)\b/i.test(command)) {
166
- return false;
167
- }
168
- return true;
169
- }
170
- function extractInlineCode(command) {
171
- const parts = [];
172
- let m;
173
- INLINE_CODE_RE.lastIndex = 0;
174
- while ((m = INLINE_CODE_RE.exec(command)) !== null) {
175
- const code = m[1] ?? m[2];
176
- if (code) parts.push(code);
177
- }
178
- ECHO_STRING_RE.lastIndex = 0;
179
- while ((m = ECHO_STRING_RE.exec(command)) !== null) {
180
- const code = m[1] ?? m[2];
181
- if (code) parts.push(code);
182
- }
183
- return parts.join("\n");
131
+ if (truthy(ENV_RULES_BUILTIN_ONLY)) cached = "builtin";
132
+ else if (truthy(ENV_DISABLE_RULE_HOT_RELOAD)) cached = "cold";
133
+ else cached = "hot";
134
+ return cached;
184
135
  }
185
- function hasWriteExecChain(command) {
186
- return WRITE_EXEC_CHAIN_PATTERNS.some((p) => {
187
- p.lastIndex = 0;
188
- return p.test(command);
189
- });
136
+ function isHotReloadEnabled() {
137
+ return getRulesMode() === "hot";
190
138
  }
191
- function hasDirTraversal(code) {
192
- const lower = code.toLowerCase();
193
- return DIR_TRAVERSAL_KEYWORDS.some((kw) => lower.includes(kw));
139
+ function isRuntimeConfigDisabled() {
140
+ return getRulesMode() !== "hot";
194
141
  }
195
- function hasReadWriteKeyword(code) {
196
- const lower = code.toLowerCase();
197
- return READ_WRITE_KEYWORDS.some((kw) => lower.includes(kw));
198
- }
199
- function analyzeInlineScript(command) {
200
- const code = extractInlineCode(command);
201
- if (!code) {
202
- return { actionable: false, allowed: true, reason: "", riskLevel: "low", riskType: "" };
203
- }
204
- if (hasWriteExecChain(command)) {
205
- return {
206
- actionable: true,
207
- allowed: false,
208
- 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",
209
- riskLevel: "critical",
210
- riskType: "script_execution"
211
- };
212
- }
213
- const redirMatch = command.match(REDIRECT_TO_SENSITIVE_RE);
214
- if (redirMatch) {
215
- return {
216
- actionable: true,
217
- allowed: false,
218
- reason: `\u91CD\u5B9A\u5411\u5199\u5165\u654F\u611F\u8DEF\u5F84: ${redirMatch[1]}`,
219
- riskLevel: "critical",
220
- riskType: "file_access"
221
- };
222
- }
223
- const isEchoOrPrintf1 = isLiteralTextCommand(command);
224
- if (!isEchoOrPrintf1 && hasDirTraversal(code) && hasReadWriteKeyword(code)) {
225
- return { actionable: false, allowed: true, reason: "", riskLevel: "low", riskType: "" };
226
- }
227
- const isEchoOrPrintf = isLiteralTextCommand(command);
228
- if (!isEchoOrPrintf && hasReadWriteKeyword(code)) {
229
- return {
230
- actionable: true,
231
- allowed: false,
232
- 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",
233
- riskLevel: "critical",
234
- riskType: "file_access"
235
- };
236
- }
237
- return {
238
- actionable: true,
239
- allowed: true,
240
- reason: "\u654F\u611F\u8DEF\u5F84\u4EC5\u4F5C\u4E3A\u6587\u672C\u5B57\u9762\u91CF\u51FA\u73B0\uFF0C\u672A\u4F34\u968F\u8BFB\u5199\u64CD\u4F5C",
241
- riskLevel: "low",
242
- riskType: "file_access"
243
- };
142
+ function isBuiltinOnly() {
143
+ return getRulesMode() === "builtin";
244
144
  }
245
- 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;
246
- var init_inline_analyzer = __esm({
247
- "src/engine/inline-analyzer.ts"() {
145
+ var ENV_RULES_MODE, ENV_RULES_BUILTIN_ONLY, ENV_DISABLE_RULE_HOT_RELOAD, TRUTHY, VALID_MODES, cached;
146
+ var init_rules_mode = __esm({
147
+ "src/infra/rules-mode.ts"() {
248
148
  "use strict";
249
149
  init_esm_shims();
250
- init_shell_split();
251
- INLINE_CODE_RE = /(?:python|python3|perl|perl5|ruby|jruby|mruby|node|php|lua)\s+(?:-[a-zA-Z]*[cera][a-zA-Z]*\s+)"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
252
- ECHO_STRING_RE = /\b(?:echo|printf)\s+"([^"\\]*(?:\\.[^"\\]*)*)"|\b(?:echo|printf)\s+'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
253
- ECHO_SEG_RE = /^\s*(?:echo|printf)(?:\s|$)/i;
254
- READ_WRITE_KEYWORDS = [
255
- // 函数调用形态(关键词后跟 '(')
256
- "open(",
257
- "read(",
258
- "write(",
259
- "fopen(",
260
- "system(",
261
- "exec(",
262
- "eval(",
263
- "popen(",
264
- "load(",
265
- "dump(",
266
- // 文件读写(组合形态)
267
- "readline",
268
- "readlines",
269
- "writelines",
270
- "getpass",
271
- "pickle.load",
272
- "yaml.load",
273
- "json.load",
274
- "json.read",
275
- "configparser",
276
- "fread",
277
- "fwrite",
278
- "fgets",
279
- // 子进程执行(组合形态)
280
- "os.system",
281
- "os.popen",
282
- "subprocess",
283
- "os.exec",
284
- "os.spawn",
285
- "import os",
286
- "import subprocess",
287
- "shell=true",
288
- "check_output",
289
- "check_call",
290
- "run(",
291
- // 绕过变体检测
292
- "getattr(",
293
- "__builtins__",
294
- "globals(",
295
- "locals(",
296
- // PHP
297
- "file_get_contents",
298
- "file_put_contents",
299
- "passthru",
300
- "shell_exec",
301
- "proc_open",
302
- // Perl/Ruby
303
- "<stdin>",
304
- "open my",
305
- // Node/JS(2026-09 补,与 Python _READ_WRITE_KEYWORDS 同步):历史缺口 ——
306
- // 识别表里一条 Node API 都没有,于是 node -e "require('fs').readFileSync('/etc/shadow')"
307
- // 判为纯文本字面量 → 放行(真漏报)。
308
- "readfilesync(",
309
- "writefilesync(",
310
- "appendfilesync(",
311
- "unlinksync(",
312
- "rmsync(",
313
- "mkdirsync(",
314
- "execsync(",
315
- "spawnsync(",
316
- "execfile",
317
- "createreadstream",
318
- "createwritestream",
319
- "fs.read",
320
- "fs.write",
321
- "fs.promises",
322
- "require('fs')",
323
- 'require("fs")',
324
- "require('node:fs')",
325
- 'require("node:fs")',
326
- "require('child_process')",
327
- 'require("child_process")',
328
- "require('https')",
329
- 'require("https")'
330
- ];
331
- WRITE_EXEC_CHAIN_PATTERNS = [
332
- // echo/cat/printf "..." > file && bash/sh/python file
333
- /(?:echo|cat|printf)\b.*?>\s*\S+.*(?:&&|;|\|)\s*(?:bash|sh|zsh|python\d?|perl|ruby|node|php)\s/gis,
334
- // ... | bash / | sh / | python(管道喂解释器执行)
335
- /\|\s*(?:bash|sh|zsh|python\d?|perl|ruby|node|php)\b/gi,
336
- // eval "$(cat ...)" / eval "$(echo ...)"
337
- /eval\s+"\$\(/gi,
338
- // xargs bash / xargs sh
339
- /\|\s*xargs\s+(?:bash|sh|zsh)/gi
340
- ];
341
- SHELL_RC_FRAGMENTS = [
342
- ".bashrc",
343
- ".zshrc",
344
- ".profile",
345
- ".bash_profile",
346
- ".zprofile"
347
- ];
348
- SENSITIVE_PATH_FRAGMENTS = [
349
- "/etc/shadow",
350
- "/etc/gshadow",
351
- "/etc/passwd",
352
- "/etc/sudoers",
353
- "/etc/hosts",
354
- "/etc/crontab",
355
- "/etc/systemd/",
356
- "/etc/ssh/sshd_config",
357
- "/boot/",
358
- "/proc/sys/",
359
- "/sys/",
360
- "/dev/mem",
361
- "/dev/kmsg",
362
- ".ssh/id_rsa",
363
- ".ssh/id_ed25519",
364
- ".ssh/authorized_keys",
365
- ...SHELL_RC_FRAGMENTS
366
- ];
367
- REDIRECT_TO_SENSITIVE_RE = new RegExp(`>{1,2}\\s*(?:\\S*/)?(${SENSITIVE_PATH_FRAGMENTS.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})(?:\\s|$)`, "i");
368
- DIR_TRAVERSAL_KEYWORDS = [
369
- // Python 目录遍历
370
- "os.listdir",
371
- "os.walk",
372
- "os.scandir",
373
- "glob.glob",
374
- "glob.iglob",
375
- "pathlib.",
376
- "iterdir",
377
- "rglob",
378
- "scandir(",
379
- "listdir("
380
- ];
150
+ ENV_RULES_MODE = "AGENT_MOSS_RULES_MODE";
151
+ ENV_RULES_BUILTIN_ONLY = "AGENT_MOSS_RULES_BUILTIN_ONLY";
152
+ ENV_DISABLE_RULE_HOT_RELOAD = "AGENT_MOSS_DISABLE_RULE_HOT_RELOAD";
153
+ TRUTHY = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
154
+ VALID_MODES = /* @__PURE__ */ new Set(["hot", "cold", "builtin"]);
155
+ cached = null;
381
156
  }
382
157
  });
383
158
 
@@ -407,23 +182,51 @@ function parseDirEnv(envName) {
407
182
  return raw.split(/[,;]/).map((s) => s.trim()).filter(Boolean).map((s) => resolve(s));
408
183
  }
409
184
  function rulesDirs() {
185
+ if (isBuiltinOnly()) return [];
410
186
  const dirs = [];
411
187
  for (const env of RULES_DIR_ENVS) dirs.push(...parseDirEnv(env));
412
188
  dirs.push(resolve(dataRootDir(), "rules"));
413
189
  return [...new Set(dirs)];
414
190
  }
191
+ function rulesDirCands() {
192
+ const root = getRulesRoot();
193
+ if (root) return [join2(root, "rules")];
194
+ const cands = [
195
+ // 注:`<data_root>/rules/` 不再是"按内置文件名覆盖"的查找位 —— 2026-10 起
196
+ // 规则目录只认 override.json / extra.json(见 rulesDirs())。
197
+ // dist: __dirname=dist/ → dist/rules;__dirname=dist/engine/ → dist/engine/../rules=dist/rules
198
+ resolve(__dirname2, "../rules"),
199
+ resolve(__dirname2, "rules"),
200
+ // dev src: __dirname=ts/src/infra/ → ts/src/infra/../../../agent_moss/rules = 仓库根 agent_moss/rules
201
+ resolve(__dirname2, "../../../agent_moss/rules"),
202
+ // dist 但 __dirname 指向 dist/engine 再往上两级的仓库根 agent_moss/rules
203
+ resolve(__dirname2, "../../../../agent_moss/rules"),
204
+ // 相对当前工作目录的仓库根(兜底,兼容以仓库根为 cwd 运行)
205
+ resolve(process.cwd(), "agent_moss/rules")
206
+ ];
207
+ return cands.filter((c) => existsSync(c));
208
+ }
415
209
  function findRulesFile(name) {
416
- for (const dir of RULES_DIR_CANDS) {
210
+ for (const dir of rulesDirCands()) {
417
211
  const p = resolve(dir, name);
418
212
  if (existsSync(p)) return p;
419
213
  }
420
214
  return null;
421
215
  }
216
+ function resolveRulesFile(name) {
217
+ return findRulesFile(name);
218
+ }
422
219
  function loadJson(name) {
220
+ if (!isHotReloadEnabled()) {
221
+ const cached2 = frozenJsonCache.get(name);
222
+ if (cached2 !== void 0) return cached2;
223
+ }
423
224
  const p = findRulesFile(name);
424
225
  if (!p) return {};
425
226
  try {
426
- return JSON.parse(readFileSync2(p, "utf-8")) || {};
227
+ const parsed = JSON.parse(readFileSync2(p, "utf-8")) || {};
228
+ if (!isHotReloadEnabled()) frozenJsonCache.set(name, parsed);
229
+ return parsed;
427
230
  } catch {
428
231
  return {};
429
232
  }
@@ -463,6 +266,74 @@ function loadL1() {
463
266
  function loadL2() {
464
267
  return loadJson("l2_rules.json");
465
268
  }
269
+ function regexFlags(flags) {
270
+ let out = "";
271
+ for (const ch of String(flags ?? "")) if (RE_FLAG_MAP[ch]) out += RE_FLAG_MAP[ch];
272
+ return out;
273
+ }
274
+ function compileRuleRegex(rule, extraFlags = "") {
275
+ return new RegExp(String(rule.pattern), regexFlags(rule.flags) + extraFlags);
276
+ }
277
+ function resolveKeywordSet(group, keywordSets) {
278
+ if (typeof group === "string") return new Set(keywordSets[group.slice(1)] ?? []);
279
+ return new Set(Array.isArray(group) ? group : []);
280
+ }
281
+ function loadFormalRules() {
282
+ return loadJson("formal_rules.json");
283
+ }
284
+ function loadLogicRules() {
285
+ return loadJson("logic_rules.json");
286
+ }
287
+ function loadHeuristicRules() {
288
+ return loadJson("heuristic_rules.json");
289
+ }
290
+ function loadScriptRules() {
291
+ return loadJson("script_rules.json");
292
+ }
293
+ function loadInline() {
294
+ return loadJson("inline_rules.json");
295
+ }
296
+ function loadPlatformTraits(osName) {
297
+ const data = loadJson("platform_traits.json");
298
+ if (!data || Object.keys(data).length === 0) {
299
+ console.error(
300
+ "[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"
301
+ );
302
+ return {};
303
+ }
304
+ const name = resolveTraitsOs(osName);
305
+ const entry = data[name];
306
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
307
+ console.error(`[AgentMoss] platform_traits.json \u91CC\u6CA1\u6709\u53EF\u7528\u7684 ${name} \u6761\u76EE \u2014\u2014 \u5E73\u53F0\u7279\u5F81\u4E3A\u7A7A\u3002`);
308
+ return {};
309
+ }
310
+ const out = {};
311
+ for (const f of TRAITS_FIELDS) if (f in entry) out[f] = entry[f];
312
+ return out;
313
+ }
314
+ function resolveTraitsOs(osName) {
315
+ const data = loadJson("platform_traits.json");
316
+ let name = String(osName || "").trim().toLowerCase() || TRAITS_OS_FALLBACK;
317
+ const seen = /* @__PURE__ */ new Set();
318
+ for (; ; ) {
319
+ if (seen.has(name)) {
320
+ console.error(`[AgentMoss] platform_traits.json \u7684 inherits \u6210\u73AF\uFF08${name}\uFF09\u2014\u2014 \u56DE\u843D linux\u3002`);
321
+ return TRAITS_OS_FALLBACK;
322
+ }
323
+ seen.add(name);
324
+ const entry = data[name];
325
+ if (entry && typeof entry === "object" && !Array.isArray(entry)) {
326
+ const nxt = entry.inherits;
327
+ if (typeof nxt === "string" && nxt.trim().toLowerCase() !== name) {
328
+ name = nxt.trim().toLowerCase();
329
+ continue;
330
+ }
331
+ return name;
332
+ }
333
+ if (name === TRAITS_OS_FALLBACK) return name;
334
+ name = TRAITS_OS_FALLBACK;
335
+ }
336
+ }
466
337
  function loadL3() {
467
338
  const data = { ...loadJson("l3_rules.json") };
468
339
  try {
@@ -748,6 +619,7 @@ function scanRuleDirs() {
748
619
  return { override, extra, loaded, ignored };
749
620
  }
750
621
  function rulesCached() {
622
+ if (!isHotReloadEnabled() && _RULES_CACHE) return _RULES_CACHE;
751
623
  const dirs = rulesDirs();
752
624
  const sig = RULES_DIR_ENVS.map((e) => `${e}=${process.env[e] || ""}`).join("|") + "|" + dirs.join("|");
753
625
  const files = [];
@@ -783,9 +655,11 @@ function identicalToBuiltin(override) {
783
655
  return Object.keys(override).length > 0;
784
656
  }
785
657
  function loadOverrideRules() {
658
+ if (isBuiltinOnly()) return {};
786
659
  return rulesCached().override;
787
660
  }
788
661
  function loadExtraRules() {
662
+ if (isBuiltinOnly()) return {};
789
663
  return rulesCached().extra;
790
664
  }
791
665
  function ruleFilesLoaded() {
@@ -794,35 +668,33 @@ function ruleFilesLoaded() {
794
668
  function ignoredRuleFiles() {
795
669
  return rulesCached().ignored;
796
670
  }
797
- var __filename2, __dirname2, OVERRIDE_FILENAME, EXTRA_FILENAME, RULES_DIR_ENVS, RULES_DIR_CANDS, SCENARIO_LIST_KEYS, EXTRA_CATEGORIES, STRUCT_KEYS, EXTRA_FLAT_FIELD, _RULES_CACHE, warnedIgnored;
671
+ 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;
798
672
  var init_rules_loader = __esm({
799
673
  "src/infra/rules-loader.ts"() {
800
674
  "use strict";
801
675
  init_esm_shims();
802
676
  init_paths();
677
+ init_rules_mode();
803
678
  init_runtime_config_bridge();
804
679
  __filename2 = fileURLToPath2(import.meta.url);
805
680
  __dirname2 = dirname(__filename2);
806
681
  OVERRIDE_FILENAME = "override.json";
807
682
  EXTRA_FILENAME = "extra.json";
808
683
  RULES_DIR_ENVS = ["AGENT_MOSS_EXTRA_RULES_DIR", "AGENT_MOSS_OVERRIDE_RULES_DIR"];
809
- RULES_DIR_CANDS = (() => {
810
- const cands = [
811
- // 注:`<data_root>/rules/` 不再是"按内置文件名覆盖"的查找位 —— 2026-10 起
812
- // 规则目录只认 override.json / extra.json(见 rulesDirs())。
813
- // dist: __dirname=dist/ → dist/rules;__dirname=dist/engine/ → dist/engine/../rules=dist/rules
814
- resolve(__dirname2, "../rules"),
815
- resolve(__dirname2, "rules"),
816
- // dev src: __dirname=ts/src/infra/ → ts/src/infra/../../../agent_moss/rules = 仓库根 agent_moss/rules
817
- resolve(__dirname2, "../../../agent_moss/rules"),
818
- // dist 但 __dirname 指向 dist/engine 再往上两级的仓库根 agent_moss/rules
819
- resolve(__dirname2, "../../../../agent_moss/rules"),
820
- // 相对当前工作目录的仓库根(兜底,兼容以仓库根为 cwd 运行)
821
- resolve(process.cwd(), "agent_moss/rules")
822
- ];
823
- return cands.filter((c) => existsSync(c));
824
- })();
684
+ frozenJsonCache = /* @__PURE__ */ new Map();
825
685
  SCENARIO_LIST_KEYS = ["extra_command_patterns", "extra_injection_keywords", "extra_sensitive_paths"];
686
+ RE_FLAG_MAP = { i: "i", s: "s", m: "m" };
687
+ TRAITS_FIELDS = [
688
+ "write_keywords",
689
+ "read_keywords",
690
+ "write_command_res",
691
+ "read_only_commands",
692
+ "fully_safe_commands",
693
+ "readonly_sensitive_commands",
694
+ "safe_action_types",
695
+ "readonly_sensitive_action_types"
696
+ ];
697
+ TRAITS_OS_FALLBACK = "linux";
826
698
  EXTRA_CATEGORIES = {
827
699
  dangerous_commands: ["pattern"],
828
700
  injection_detection: ["keyword", "lang"],
@@ -844,7 +716,199 @@ var init_rules_loader = __esm({
844
716
  }
845
717
  });
846
718
 
719
+ // src/engine/shell-split.ts
720
+ function hasCommandSubstitution(text) {
721
+ return CMD_SUBSTITUTION.test(text);
722
+ }
723
+ function splitCommandSegments(command) {
724
+ const segments = [];
725
+ let cur = "";
726
+ let quote = null;
727
+ for (let i = 0; i < command.length; i++) {
728
+ const ch = command[i];
729
+ if (quote) {
730
+ cur += ch;
731
+ if (ch === quote) quote = null;
732
+ continue;
733
+ }
734
+ if (ch === "'" || ch === '"') {
735
+ quote = ch;
736
+ cur += ch;
737
+ continue;
738
+ }
739
+ if (/\||&|;|\n/.test(ch)) {
740
+ segments.push(cur);
741
+ segments.push(ch);
742
+ cur = "";
743
+ continue;
744
+ }
745
+ cur += ch;
746
+ }
747
+ if (cur) segments.push(cur);
748
+ return segments.map((s) => s.trim()).filter((s) => s.length > 0 && !/^(?:\|\||&&|[|;&])$/.test(s));
749
+ }
750
+ function extractFirstCommand(command) {
751
+ const segments = splitCommandSegments(command);
752
+ return segments.length ? segments[0] : "";
753
+ }
754
+ var CMD_SUBSTITUTION;
755
+ var init_shell_split = __esm({
756
+ "src/engine/shell-split.ts"() {
757
+ "use strict";
758
+ init_esm_shims();
759
+ CMD_SUBSTITUTION = /\$\(|`|</;
760
+ }
761
+ });
762
+
763
+ // src/engine/inline-analyzer.ts
764
+ function buildChainRegex(pattern, flags) {
765
+ return new RegExp(pattern, (flags || "") + "g");
766
+ }
767
+ function isLiteralTextCommand(command) {
768
+ const segments = splitCommandSegments(command);
769
+ if (segments.length === 0 || segments.some((s) => !ECHO_SEG_RE.test(s))) return false;
770
+ if (command.includes(">")) {
771
+ let d = command.replace(/<=>|>=|<=|<>|=>/g, " ");
772
+ d = d.replace(/(?:2|&|1)?\s*>\s*\/dev\/null\b/g, "");
773
+ d = d.replace(new RegExp(`>{1,2}\\s*(['"])([~./][^'"]+)\\1`, "g"), (_m, _q, p) => ">" + p);
774
+ d = d.replace(/'[^']*'|"[^"]*"/g, "");
775
+ if (/>{1,2}\s*(?!&)\S/.test(d)) {
776
+ return false;
777
+ }
778
+ }
779
+ if (/\|\s*\b(?:tee|tac|install)\b/i.test(command)) {
780
+ return false;
781
+ }
782
+ return true;
783
+ }
784
+ function extractInlineCode(command) {
785
+ const parts = [];
786
+ let m;
787
+ INLINE_CODE_RE.lastIndex = 0;
788
+ while ((m = INLINE_CODE_RE.exec(command)) !== null) {
789
+ const code = m[1] ?? m[2];
790
+ if (code) parts.push(code);
791
+ }
792
+ ECHO_STRING_RE.lastIndex = 0;
793
+ while ((m = ECHO_STRING_RE.exec(command)) !== null) {
794
+ const code = m[1] ?? m[2];
795
+ if (code) parts.push(code);
796
+ }
797
+ return parts.join("\n");
798
+ }
799
+ function hasWriteExecChain(command) {
800
+ return WRITE_EXEC_CHAIN_PATTERNS.some((p) => {
801
+ p.lastIndex = 0;
802
+ return p.test(command);
803
+ });
804
+ }
805
+ function hasDirTraversal(code) {
806
+ const lower = code.toLowerCase();
807
+ return DIR_TRAVERSAL_KEYWORDS.some((kw) => lower.includes(kw));
808
+ }
809
+ function hasReadWriteKeyword(code) {
810
+ const lower = code.toLowerCase();
811
+ return READ_WRITE_KEYWORDS.some((kw) => lower.includes(kw));
812
+ }
813
+ function analyzeInlineScript(command) {
814
+ const code = extractInlineCode(command);
815
+ if (!code) {
816
+ return { actionable: false, allowed: true, reason: "", riskLevel: "low", riskType: "" };
817
+ }
818
+ if (hasWriteExecChain(command)) {
819
+ return {
820
+ actionable: true,
821
+ allowed: false,
822
+ 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",
823
+ riskLevel: "critical",
824
+ riskType: "script_execution"
825
+ };
826
+ }
827
+ const redirMatch = command.match(REDIRECT_TO_SENSITIVE_RE);
828
+ if (redirMatch) {
829
+ return {
830
+ actionable: true,
831
+ allowed: false,
832
+ reason: `\u91CD\u5B9A\u5411\u5199\u5165\u654F\u611F\u8DEF\u5F84: ${redirMatch[1]}`,
833
+ riskLevel: "critical",
834
+ riskType: "file_access"
835
+ };
836
+ }
837
+ const isEchoOrPrintf1 = isLiteralTextCommand(command);
838
+ if (!isEchoOrPrintf1 && hasDirTraversal(code) && hasReadWriteKeyword(code)) {
839
+ return { actionable: false, allowed: true, reason: "", riskLevel: "low", riskType: "" };
840
+ }
841
+ const isEchoOrPrintf = isLiteralTextCommand(command);
842
+ if (!isEchoOrPrintf && hasReadWriteKeyword(code)) {
843
+ return {
844
+ actionable: true,
845
+ allowed: false,
846
+ 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",
847
+ riskLevel: "critical",
848
+ riskType: "file_access"
849
+ };
850
+ }
851
+ return {
852
+ actionable: true,
853
+ allowed: true,
854
+ reason: "\u654F\u611F\u8DEF\u5F84\u4EC5\u4F5C\u4E3A\u6587\u672C\u5B57\u9762\u91CF\u51FA\u73B0\uFF0C\u672A\u4F34\u968F\u8BFB\u5199\u64CD\u4F5C",
855
+ riskLevel: "low",
856
+ riskType: "file_access"
857
+ };
858
+ }
859
+ 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;
860
+ var init_inline_analyzer = __esm({
861
+ "src/engine/inline-analyzer.ts"() {
862
+ "use strict";
863
+ init_esm_shims();
864
+ init_shell_split();
865
+ init_rules_loader();
866
+ INLINE_RULES = loadInline();
867
+ if (!INLINE_RULES || Object.keys(INLINE_RULES).length === 0) {
868
+ console.error(
869
+ "[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"
870
+ );
871
+ }
872
+ INLINE_CODE_RE = /(?:python|python3|perl|perl5|ruby|jruby|mruby|node|php|lua)\s+(?:-[a-zA-Z]*[cera][a-zA-Z]*\s+)"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
873
+ ECHO_STRING_RE = /\b(?:echo|printf)\s+"([^"\\]*(?:\\.[^"\\]*)*)"|\b(?:echo|printf)\s+'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
874
+ ECHO_SEG_RE = /^\s*(?:echo|printf)(?:\s|$)/i;
875
+ READ_WRITE_KEYWORDS = INLINE_RULES.read_write_keywords ?? [];
876
+ WRITE_EXEC_CHAIN_PATTERNS = (INLINE_RULES.write_exec_chain_patterns ?? []).filter((p) => p && typeof p.pattern === "string").map((p) => buildChainRegex(p.pattern, p.flags ?? ""));
877
+ _REDIRECT_PATHS = INLINE_RULES.redirect_sensitive_paths ?? {};
878
+ SHELL_RC_FRAGMENTS = _REDIRECT_PATHS.shell_rc_fragments ?? [];
879
+ SENSITIVE_PATH_FRAGMENTS = [
880
+ ..._REDIRECT_PATHS.ts_static ?? [],
881
+ ...SHELL_RC_FRAGMENTS
882
+ ];
883
+ REDIRECT_TO_SENSITIVE_RE = new RegExp(`>{1,2}\\s*(?:\\S*/)?(${SENSITIVE_PATH_FRAGMENTS.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})(?:\\s|$)`, "i");
884
+ DIR_TRAVERSAL_KEYWORDS = INLINE_RULES.dir_traversal_keywords ?? [];
885
+ }
886
+ });
887
+
847
888
  // src/engine/platform-traits.ts
889
+ function buildTraits(osName) {
890
+ const t = loadPlatformTraits(osName);
891
+ const strs = (k) => Array.isArray(t[k]) ? t[k] : [];
892
+ return {
893
+ writeKeywords: strs("write_keywords"),
894
+ readKeywords: strs("read_keywords"),
895
+ writeCommandRes: strs("write_command_res").map((p) => new RegExp(p)),
896
+ readOnlyCommands: new Set(strs("read_only_commands")),
897
+ fullySafeCommands: strs("fully_safe_commands").map((p) => new RegExp(p, "i")),
898
+ readonlySensitiveCommands: strs("readonly_sensitive_commands").map((p) => new RegExp(p, "i")),
899
+ safeActionTypes: new Set(strs("safe_action_types")),
900
+ readonlySensitiveActionTypes: new Set(strs("readonly_sensitive_action_types"))
901
+ };
902
+ }
903
+ function traitsFor(key) {
904
+ const resolved = resolveTraitsOs(key);
905
+ let t = traitsCache.get(resolved);
906
+ if (!t) {
907
+ t = buildTraits(resolved);
908
+ traitsCache.set(resolved, t);
909
+ }
910
+ return t;
911
+ }
848
912
  function getPlatformTraits(osType) {
849
913
  const accepted = acceptedOsValues(osType);
850
914
  if (accepted.includes("windows")) return WINDOWS_TRAITS;
@@ -852,368 +916,18 @@ function getPlatformTraits(osType) {
852
916
  if (accepted.includes("harmony")) return HARMONY_TRAITS;
853
917
  return LINUX_TRAITS;
854
918
  }
855
- var LINUX_TRAITS, WINDOWS_TRAITS, MACOS_TRAITS, HARMONY_TRAITS;
919
+ var traitsCache, LINUX_TRAITS, WINDOWS_TRAITS, MACOS_TRAITS, HARMONY_TRAITS;
856
920
  var init_platform_traits = __esm({
857
921
  "src/engine/platform-traits.ts"() {
858
922
  "use strict";
859
923
  init_esm_shims();
860
924
  init_logic_rules();
861
- LINUX_TRAITS = {
862
- writeKeywords: [
863
- "write",
864
- "\u5199\u5165",
865
- "save",
866
- "\u4FDD\u5B58",
867
- "modify",
868
- "\u4FEE\u6539",
869
- "update",
870
- "\u66F4\u65B0",
871
- "delete",
872
- "\u5220\u9664",
873
- "remove",
874
- "\u79FB\u9664",
875
- "create",
876
- "\u521B\u5EFA",
877
- "append",
878
- "\u8FFD\u52A0",
879
- "truncate",
880
- "\u622A\u65AD",
881
- "overwrite",
882
- "\u8986\u76D6",
883
- "replace",
884
- "\u66FF\u6362",
885
- "edit"
886
- ],
887
- readKeywords: [
888
- "read",
889
- "\u8BFB\u53D6",
890
- "cat",
891
- "head",
892
- "tail",
893
- "less",
894
- "more",
895
- "view",
896
- "\u67E5\u770B",
897
- "open",
898
- "\u6253\u5F00",
899
- "load",
900
- "\u52A0\u8F7D",
901
- "grep",
902
- "search",
903
- "\u641C\u7D22",
904
- "find",
905
- "\u67E5\u627E"
906
- ],
907
- writeCommandRes: [
908
- /\brm\b/,
909
- /\bunlink\b/,
910
- /\brmdir\b/,
911
- /\bshred\b/,
912
- /\bcp\b/,
913
- /\bmv\b/,
914
- /\binstall\b/,
915
- /\btee\b/,
916
- /\bdd\b/,
917
- /\bmkfs\b/,
918
- /\b(fdisk|parted|cfdisk|sfdisk)\b/,
919
- /\bchmod\b/,
920
- /\bchown\b/,
921
- /\btruncate\b/
922
- ],
923
- readOnlyCommands: /* @__PURE__ */ new Set([
924
- "cat",
925
- "head",
926
- "tail",
927
- "less",
928
- "more",
929
- "grep",
930
- "find",
931
- "awk",
932
- "sed",
933
- "sort",
934
- "uniq",
935
- "wc",
936
- "cut",
937
- "strings",
938
- "od",
939
- "xxd",
940
- "hexdump",
941
- "tr",
942
- "file",
943
- "stat",
944
- "du",
945
- "df",
946
- "ls",
947
- "dir",
948
- "tree",
949
- "nl",
950
- "tac",
951
- "rev",
952
- "echo",
953
- "printf",
954
- "lsblk",
955
- "blockdev",
956
- "smartctl",
957
- "udevadm",
958
- "dmidecode",
959
- "lscpu",
960
- "lspci",
961
- "lsusb",
962
- "lsmem",
963
- "lsns",
964
- "lsof",
965
- "hwinfo",
966
- "inxi",
967
- "fdisk",
968
- "parted",
969
- "dmesg",
970
- "journalctl",
971
- "systemctl",
972
- "hostnamectl",
973
- "localectl",
974
- "timedatectl",
975
- "ps",
976
- "top",
977
- "free",
978
- "vmstat",
979
- "iostat",
980
- "mpstat",
981
- "pidof",
982
- "pgrep",
983
- "ip",
984
- "ifconfig",
985
- "route",
986
- "ss",
987
- "netstat",
988
- "arp",
989
- "ethtool",
990
- "nmcli",
991
- "uname",
992
- "arch",
993
- "nproc",
994
- "getconf",
995
- "getent",
996
- "id",
997
- "whoami",
998
- "who",
999
- "w",
1000
- "last",
1001
- "uptime",
1002
- "lsmod",
1003
- "modinfo",
1004
- "sysctl"
1005
- ]),
1006
- fullySafeCommands: [
1007
- /^ls(?:\s|$)/i,
1008
- /^dir(?:\s|$)/i,
1009
- /^pwd(?:\s|$)/i,
1010
- /^which\s+/i,
1011
- /^whereis\s+/i,
1012
- /^realpath\s+/i,
1013
- /^basename\s+/i,
1014
- /^dirname\s+/i,
1015
- /^file\s+/i,
1016
- /^stat\s+/i,
1017
- /^du(?:\s|$)/i,
1018
- /^echo\s+/i,
1019
- /^printf\s+/i,
1020
- /^type\s+/i,
1021
- /^command\s+/i,
1022
- // 系统信息查询(只读,不涉及文件/网络/权限)
1023
- /^whoami(?:\s|$)/i,
1024
- /^id(?:\s|$)/i,
1025
- /^hostname(?:\s|$)/i,
1026
- /^uname(?:\s|$)/i,
1027
- /^date(?:\s|$)/i,
1028
- /^env(?:\s|$)/i,
1029
- /^printenv(?:\s|$)/i,
1030
- /^tty(?:\s|$)/i,
1031
- /^arch(?:\s|$)/i,
1032
- /^uptime(?:\s|$)/i,
1033
- /^groups(?:\s|$)/i,
1034
- /^logname(?:\s|$)/i,
1035
- // 系统资源/硬件信息(只读,不读文件内容、无状态变更选项)
1036
- // 与上面 uptime/uname/id 同性质;缺了它们时开 L3 的部署每查一次磁盘/内存
1037
- // 都要走 LLM(实测 df -h 从 5ms 变成十几秒)。
1038
- /^df(?:\s|$)/i,
1039
- /^free(?:\s|$)/i,
1040
- /^vmstat(?:\s|$)/i,
1041
- /^nproc(?:\s|$)/i,
1042
- /^lscpu(?:\s|$)/i,
1043
- /^lsblk(?:\s|$)/i,
1044
- /^lspci(?:\s|$)/i,
1045
- /^lsusb(?:\s|$)/i,
1046
- /^lsmod(?:\s|$)/i
1047
- ],
1048
- readonlySensitiveCommands: [
1049
- /^cat\s+/i,
1050
- /^head(?:\s|$)/i,
1051
- /^tail(?:\s|$)/i,
1052
- /^less(?:\s|$)/i,
1053
- /^more(?:\s|$)/i,
1054
- /^wc(?:\s|$)/i,
1055
- /^grep(?:\s|$)/i,
1056
- /^find\s+/i,
1057
- /^ag(?:\s|$)/i,
1058
- /^rg(?:\s|$)/i,
1059
- /^awk\s+/i,
1060
- // NEW-FN-P(M03):sed -i/--in-place 是原地写,挡出白名单走 L2 read_before_write。
1061
- // 前瞻放最前(外层 ^sed\s+ 会消费 -i 前的空格)。与 Python profiles/linux.py 对齐。
1062
- /^(?!.*(?:\s|['"])-[a-zA-Z]*i[a-zA-Z]*\b)(?!.*(?:\s|['"])--in-place)sed\s+/i,
1063
- /^cut\s+/i,
1064
- /^sort(?:\s|$)/i,
1065
- /^uniq(?:\s|$)/i,
1066
- // 进程/网络/打开文件信息:只读,但会暴露进程命令行、连接对端、打开的文件
1067
- // 路径,故只跳 L3、保留 L2(L1 敏感路径检测照常生效)。
1068
- /^ps(?:\s|$)/i,
1069
- /^ss(?:\s|$)/i,
1070
- /^netstat(?:\s|$)/i,
1071
- /^lsof(?:\s|$)/i,
1072
- // 只读子命令级白名单:匹配的是「第一段命令」而非首词(extractFirstCommand
1073
- // 按 | && || 切分),所以能精确到子命令。systemctl stop/disable 等不在此列。
1074
- /^systemctl\s+(?:status|is-active|is-enabled|is-failed|list-units|list-unit-files|list-dependencies|show|cat)\b/i,
1075
- /^service\s+\S+\s+status\b/i,
1076
- // journalctl 整族是只读的,但 --vacuum-*/--rotate/--flush/--sync 会删改日志,
1077
- // 用否定前瞻挡掉后再放行。注意前瞻里必须用 .* 扫描**整段**:只写 (?!\s+--...)
1078
- // 的话只看紧跟 journalctl 的第一个参数,
1079
- // `journalctl -u sshd --vacuum-size=100M` 就漏过去了。
1080
- /^journalctl\b(?!.*--(?:vacuum-size|vacuum-time|vacuum-files|rotate|flush|sync)\b)/i
1081
- ]
1082
- };
1083
- WINDOWS_TRAITS = {
1084
- writeKeywords: [
1085
- "write",
1086
- "\u5199\u5165",
1087
- "save",
1088
- "\u4FDD\u5B58",
1089
- "modify",
1090
- "\u4FEE\u6539",
1091
- "update",
1092
- "\u66F4\u65B0",
1093
- "delete",
1094
- "\u5220\u9664",
1095
- "remove",
1096
- "\u79FB\u9664",
1097
- "create",
1098
- "\u521B\u5EFA",
1099
- "append",
1100
- "\u8FFD\u52A0",
1101
- "overwrite",
1102
- "\u8986\u76D6",
1103
- "replace",
1104
- "\u66FF\u6362",
1105
- "set",
1106
- "\u8BBE\u7F6E"
1107
- // 注意:这里**不能放 `reg` / `注册表`**。关键词是子串包含匹配,放进去等于
1108
- // 「只要命令里出现 reg 字样就算写操作」——`reg query`(读)会被判成写,
1109
- // 进而误拦对 deny_write 路径的只读查询。注册表的写判定交给下面的
1110
- // writeCommandRes(只认写子命令)。两端语义必须一致。
1111
- ],
1112
- readKeywords: [
1113
- "read",
1114
- "\u8BFB\u53D6",
1115
- "type",
1116
- "findstr",
1117
- "dir",
1118
- "view",
1119
- "\u67E5\u770B",
1120
- "open",
1121
- "\u6253\u5F00",
1122
- "load",
1123
- "\u52A0\u8F7D",
1124
- "search",
1125
- "\u641C\u7D22",
1126
- "find",
1127
- "\u67E5\u627E",
1128
- "get",
1129
- "\u83B7\u53D6"
1130
- ],
1131
- // Windows 写/删命令:del/copy/move/xcopy/replace/rmdir/icacls/reg delete 等
1132
- writeCommandRes: [
1133
- /\b(del|erase)\b/,
1134
- /\brmdir\b/,
1135
- /\b(rd)\b/,
1136
- /\bcopy\b/,
1137
- /\b(xcopy|robocopy)\b/,
1138
- /\b(move|movefile)\b/,
1139
- /\breplace\b/,
1140
- /\bicacls\b/,
1141
- /\battrib\b/,
1142
- // 注册表:**只认写子命令**(对齐 Python WindowsProfile.write_command_res)。
1143
- // 此前是 /\breg\b/,把 reg query(读)也判成写 → 误拦对 deny_write 路径的只读查询。
1144
- // 写子命令 add/delete/copy/save/restore/import/load/unload;
1145
- // 读子命令 query/compare/export/flags/help 不在此列。
1146
- /\breg(?:\.exe)?\s+(?:add|delete|copy|save|restore|import|load|unload)\b/,
1147
- /\bformat\b/,
1148
- /\bdiskpart\b/,
1149
- /\bmkdir\b/,
1150
- /\bmd\b/,
1151
- /\bsetx\b/,
1152
- /\bwmic\b/
1153
- ],
1154
- // Windows 只读命令(重定向写判定用):type/findstr/more/tree/dir 等
1155
- readOnlyCommands: /* @__PURE__ */ new Set([
1156
- "type",
1157
- "findstr",
1158
- "more",
1159
- "tree",
1160
- "dir",
1161
- "where",
1162
- "ver",
1163
- "cls",
1164
- "echo",
1165
- "help",
1166
- "fc",
1167
- "comp",
1168
- "find",
1169
- "sort",
1170
- "powershell",
1171
- "cmd",
1172
- "netstat",
1173
- "ipconfig",
1174
- "tasklist",
1175
- "systeminfo",
1176
- "driverquery",
1177
- "reg",
1178
- "wmic",
1179
- "whoami",
1180
- "hostname",
1181
- "getmac",
1182
- "query",
1183
- "w32tm",
1184
- "diskpart"
1185
- ]),
1186
- // Windows 完全安全命令(对齐 WindowsProfile.fully_safe_commands)
1187
- fullySafeCommands: [
1188
- /^dir(?:\s|$)/,
1189
- /^where\s+/,
1190
- /^echo\s+/,
1191
- /^cd(?:\s|$)/,
1192
- /^tree(?:\s|$)/,
1193
- /^help(?:\s|$)/,
1194
- /^cls(?:\s|$)/,
1195
- /^ver(?:\s|$)/,
1196
- // 系统信息查询(只读,不读文件内容、无状态变更选项),与 Linux 侧 df/free 对齐
1197
- /^whoami(?:\s|$)/,
1198
- /^hostname(?:\s|$)/,
1199
- /^systeminfo(?:\s|$)/,
1200
- /^tasklist(?:\s|$)/,
1201
- /^getmac(?:\s|$)/,
1202
- /^driverquery(?:\s|$)/,
1203
- /^netstat(?:\s|$)/
1204
- ],
1205
- // Windows 安全但可能访问敏感路径的命令(对齐 WindowsProfile.readonly_sensitive_commands)
1206
- readonlySensitiveCommands: [
1207
- /^type\s+/,
1208
- /^findstr\s+/,
1209
- // 只读子命令级白名单(匹配「第一段命令」而非首词);
1210
- // sc create/delete/start/stop、reg add/delete/set 均不在此列。
1211
- /^sc\s+query\b/,
1212
- /^reg\s+query\b/
1213
- ]
1214
- };
1215
- MACOS_TRAITS = LINUX_TRAITS;
1216
- HARMONY_TRAITS = LINUX_TRAITS;
925
+ init_rules_loader();
926
+ traitsCache = /* @__PURE__ */ new Map();
927
+ LINUX_TRAITS = traitsFor("linux");
928
+ WINDOWS_TRAITS = traitsFor("windows");
929
+ MACOS_TRAITS = traitsFor("macos");
930
+ HARMONY_TRAITS = traitsFor("harmony");
1217
931
  }
1218
932
  });
1219
933
 
@@ -1372,7 +1086,6 @@ function checkIntentConsistency(input) {
1372
1086
  const { aNext, promptSession, promptHistory } = input;
1373
1087
  const actionDetail = aNext.actionDetail.toLowerCase();
1374
1088
  const actionType = aNext.actionType.toLowerCase();
1375
- const EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
1376
1089
  if (EXCLUDED_PREFIXES.some((p) => actionType.startsWith(p))) return null;
1377
1090
  if (INLINE_SCRIPT_RE.test(actionDetail)) return null;
1378
1091
  const intentTexts = collectIntentTexts(promptSession, promptHistory);
@@ -1400,7 +1113,6 @@ function runLogicCheck(input, opts) {
1400
1113
  const actionDetail = aNext.actionDetail.toLowerCase();
1401
1114
  const actionType = aNext.actionType.toLowerCase();
1402
1115
  const resolvedCwd = cwd || "/";
1403
- const EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
1404
1116
  const isExcludedType = EXCLUDED_PREFIXES.some((p) => actionType.startsWith(p));
1405
1117
  if (!opts?.skipReadBeforeWrite) {
1406
1118
  const rbwResult = checkReadBeforeWrite(actionType, actionDetail, actionHistory, input.reason, resolvedCwd, input.osType);
@@ -1512,7 +1224,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
1512
1224
  const isBashLike = actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash";
1513
1225
  const isWrite = isBashLike ? isWriteOperation(actionType, actionDetail, void 0, osType) : getPlatformTraits(osType).writeKeywords.some((kw) => actionType.includes(kw));
1514
1226
  if (!isWrite) return null;
1515
- const WRITE_CMDS = /* @__PURE__ */ new Set(["cp", "mv", "install", "tee", "dd", "truncate", "mkfs", "fdisk", "parted"]);
1227
+ const WRITE_CMDS = new Set(_LOGIC.write_commands ?? []);
1516
1228
  if (isBashLike) {
1517
1229
  const detailNoCompare = actionDetail.replace(/<=>|>=|<=|<>|=>/g, " ");
1518
1230
  const detailNoDevnull = detailNoCompare.replace(DEVNULL_REDIRECT_RE, "");
@@ -1544,7 +1256,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
1544
1256
  }
1545
1257
  if (!hasWriteSeg) return null;
1546
1258
  }
1547
- const SOURCE_ARG_CMDS = /* @__PURE__ */ new Set(["cp", "mv", "install", "rsync"]);
1259
+ const SOURCE_ARG_CMDS = new Set(_LOGIC.source_arg_commands ?? []);
1548
1260
  const REDIRECT_TARGET_RE = new RegExp(`>{1,2}\\s*(?!&)(?:'(/[^']+)'|"(/[^\\"]+)"|(/[^\\s;|&><'\\"]+))`);
1549
1261
  const targetPaths = [];
1550
1262
  for (const seg of splitCommandSegments(actionDetail)) {
@@ -1592,7 +1304,6 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
1592
1304
  }
1593
1305
  }
1594
1306
  }
1595
- const TEMP_DIRS = ["/tmp/", "/var/tmp/", "/dev/shm/", "/run/"];
1596
1307
  let filteredPaths = uniqueTargetPaths;
1597
1308
  if (/\bdd\b/i.test(actionDetail) && /\bif=\/dev\/zero\b/i.test(actionDetail)) {
1598
1309
  filteredPaths = uniqueTargetPaths.filter(
@@ -1693,10 +1404,10 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd, osType) {
1693
1404
  if (!hasTraversal) {
1694
1405
  return null;
1695
1406
  }
1696
- const READ_KEYWORDS = ["read(", "readline", "readlines", "fread", "fgets", "load(", "getpass", "file_get_contents"];
1697
- const WRITE_KEYWORDS2 = ["write(", "writelines", "fwrite", "file_put_contents"];
1698
- 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"];
1699
- const OPEN_KEYWORDS = ["open(", "fopen("];
1407
+ const READ_KEYWORDS = IFA_READ_KEYWORDS;
1408
+ const WRITE_KEYWORDS2 = IFA_WRITE_KEYWORDS;
1409
+ const EXEC_KEYWORDS = IFA_EXEC_KEYWORDS;
1410
+ const OPEN_KEYWORDS = IFA_OPEN_KEYWORDS;
1700
1411
  const code = extractInlineCode(actionDetail);
1701
1412
  if (!code) return null;
1702
1413
  const codeLower = code.toLowerCase();
@@ -1759,7 +1470,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd, osType) {
1759
1470
  violatedLayers: ["L2.1"]
1760
1471
  };
1761
1472
  }
1762
- 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;
1473
+ 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;
1763
1474
  var init_logic_rules = __esm({
1764
1475
  "src/engine/logic-rules.ts"() {
1765
1476
  "use strict";
@@ -1769,6 +1480,10 @@ var init_logic_rules = __esm({
1769
1480
  init_rules_loader();
1770
1481
  init_platform_traits();
1771
1482
  init_shell_split();
1483
+ _LOGIC = loadLogicRules();
1484
+ _HEURISTIC = loadHeuristicRules();
1485
+ EXCLUDED_PREFIXES = _HEURISTIC.sensitive_excluded_prefixes ?? [];
1486
+ TEMP_DIRS = (_HEURISTIC.temp_dirs ?? []).map((d) => d + "/");
1772
1487
  ROOT_SENSITIVE_PATHS = _loadSensitivePathScope("root");
1773
1488
  HOME_SENSITIVE_PATHS = _loadSensitivePathScope("home");
1774
1489
  CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path6, riskLevel, desc, dirDelOnly }) => ({ path: path6, riskLevel, desc, credential: true, dirDelOnly }));
@@ -1789,7 +1504,7 @@ var init_logic_rules = __esm({
1789
1504
  "list_dir",
1790
1505
  "count_text_length",
1791
1506
  "search",
1792
- // filemgr 只读三件套(globfiles 在 coordinator FULLY_SAFE_TOOLS 提前放行,此处兜底防御非 coordinator 入口)
1507
+ // filemgr 只读三件套(globfiles 在 platform-traits 的 safeActionTypes 提前放行,此处兜底防御非 coordinator 入口)
1793
1508
  "filemgr-globfiles",
1794
1509
  "filemgr-readfile",
1795
1510
  "filemgr-grepfiles",
@@ -1801,7 +1516,7 @@ var init_logic_rules = __esm({
1801
1516
  "tail",
1802
1517
  "cat"
1803
1518
  ]);
1804
- NON_WRITE_TOOL_PREFIXES = ["memory-", "skill-manager-", "mailbox-", "app-"];
1519
+ NON_WRITE_TOOL_PREFIXES = _LOGIC.non_write_tool_prefixes ?? [];
1805
1520
  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"];
1806
1521
  SED_INPLACE_RE = /\bsed\s+(?:-\S+\s+)*(?:-[a-zA-Z]*i[a-zA-Z]*|--in-place)\b/i;
1807
1522
  DEVNULL_REDIRECT_RE = /(?:2|&|1)?\s*>\s*\/dev\/null\b/g;
@@ -1842,25 +1557,24 @@ var init_logic_rules = __esm({
1842
1557
  }));
1843
1558
  })();
1844
1559
  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;
1845
- DIR_TRAVERSAL_KW = [
1846
- "os.listdir",
1847
- "os.walk",
1848
- "os.scandir",
1849
- "glob.glob",
1850
- "glob.iglob",
1851
- "pathlib.",
1852
- "iterdir",
1853
- "rglob",
1854
- "scandir(",
1855
- "listdir("
1856
- ];
1560
+ DIR_TRAVERSAL_KW = loadInline().dir_traversal_keywords ?? [];
1561
+ _IFA = loadLogicRules().indirect_file_access ?? {};
1562
+ IFA_READ_KEYWORDS = _IFA.read_keywords ?? [];
1563
+ IFA_WRITE_KEYWORDS = _IFA.write_keywords ?? [];
1564
+ IFA_EXEC_KEYWORDS = _IFA.exec_keywords ?? [];
1565
+ IFA_OPEN_KEYWORDS = _IFA.open_keywords ?? [];
1857
1566
  }
1858
1567
  });
1859
1568
 
1860
1569
  // src/engine/asset-dir.ts
1861
- import { existsSync as existsSync3 } from "fs";
1862
- import { resolve as resolve2 } from "path";
1570
+ import { existsSync as existsSync4 } from "fs";
1571
+ import { join as join4, resolve as resolve2 } from "path";
1863
1572
  function assetDirCandidates(moduleDir, name) {
1573
+ const root = getRulesRoot();
1574
+ if (root) {
1575
+ const p = join4(root, name);
1576
+ return [p, p];
1577
+ }
1864
1578
  return [
1865
1579
  resolve2(moduleDir, name),
1866
1580
  resolve2(moduleDir, "../../..", "agent_moss", name)
@@ -1868,7 +1582,7 @@ function assetDirCandidates(moduleDir, name) {
1868
1582
  }
1869
1583
  function resolveAssetDir(moduleDir, name) {
1870
1584
  for (const dir of assetDirCandidates(moduleDir, name)) {
1871
- if (existsSync3(dir)) return dir;
1585
+ if (existsSync4(dir)) return dir;
1872
1586
  }
1873
1587
  return null;
1874
1588
  }
@@ -1876,11 +1590,12 @@ var init_asset_dir = __esm({
1876
1590
  "src/engine/asset-dir.ts"() {
1877
1591
  "use strict";
1878
1592
  init_esm_shims();
1593
+ init_paths();
1879
1594
  }
1880
1595
  });
1881
1596
 
1882
1597
  // src/engine/skill-loader.ts
1883
- import { readFileSync as readFileSync3, readdirSync as readdirSync3, existsSync as existsSync4 } from "fs";
1598
+ import { readFileSync as readFileSync4, readdirSync as readdirSync3, existsSync as existsSync5 } from "fs";
1884
1599
  import { resolve as resolve3, dirname as dirname2 } from "path";
1885
1600
  import { fileURLToPath as fileURLToPath3 } from "url";
1886
1601
  function loadAllSkills() {
@@ -1895,7 +1610,7 @@ function loadAllSkills() {
1895
1610
  const files = readdirSync3(dir).filter((f) => f.endsWith(".md"));
1896
1611
  for (const f of files) {
1897
1612
  const name = f.replace(".md", "");
1898
- cachedSkills[name] = readFileSync3(resolve3(dir, f), "utf-8");
1613
+ cachedSkills[name] = readFileSync4(resolve3(dir, f), "utf-8");
1899
1614
  }
1900
1615
  console.log(`[AgentMoss] Loaded ${Object.keys(cachedSkills).length} skills from ${dir}`);
1901
1616
  return cachedSkills;
@@ -1904,6 +1619,7 @@ function userSkillsDir() {
1904
1619
  return resolve3(getDataRoot(), "agent_moss_skills");
1905
1620
  }
1906
1621
  function runtimeCustomSkillKeywords() {
1622
+ if (isRuntimeConfigDisabled()) return {};
1907
1623
  try {
1908
1624
  const rt = runtimeConfigBridge.current?.loadRuntimeConfig?.();
1909
1625
  if (!rt) return {};
@@ -1921,10 +1637,11 @@ function runtimeCustomSkillKeywords() {
1921
1637
  }
1922
1638
  }
1923
1639
  function loadUserSkill(name) {
1640
+ if (isRuntimeConfigDisabled()) return "";
1924
1641
  if (userSkillCache[name] !== void 0) return userSkillCache[name];
1925
1642
  try {
1926
1643
  const p = resolve3(userSkillsDir(), `${name}.md`);
1927
- userSkillCache[name] = existsSync4(p) ? readFileSync3(p, "utf-8") : "";
1644
+ userSkillCache[name] = existsSync5(p) ? readFileSync4(p, "utf-8") : "";
1928
1645
  } catch {
1929
1646
  userSkillCache[name] = "";
1930
1647
  }
@@ -1989,6 +1706,7 @@ var init_skill_loader = __esm({
1989
1706
  init_esm_shims();
1990
1707
  init_rules_loader();
1991
1708
  init_paths();
1709
+ init_rules_mode();
1992
1710
  init_asset_dir();
1993
1711
  init_runtime_config_bridge();
1994
1712
  __filename3 = fileURLToPath3(import.meta.url);
@@ -2049,6 +1767,7 @@ __export(runtime_config_exports, {
2049
1767
  getEnabledScenarioExtensions: () => getEnabledScenarioExtensions,
2050
1768
  getEnvOverrides: () => getEnvOverrides,
2051
1769
  getProfileOrDetect: () => getProfileOrDetect,
1770
+ getRuleSourceFingerprint: () => getRuleSourceFingerprint,
2052
1771
  getRuntimeConfigPath: () => getRuntimeConfigPath,
2053
1772
  getScenarioRules: () => getScenarioRules,
2054
1773
  hasRuntimeConfig: () => hasRuntimeConfig,
@@ -2071,8 +1790,8 @@ __export(runtime_config_exports, {
2071
1790
  updateSkillCategoryEnabled: () => updateSkillCategoryEnabled,
2072
1791
  updateSkillEnabled: () => updateSkillEnabled
2073
1792
  });
2074
- import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync5, mkdirSync, unlinkSync, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
2075
- import { join as join3 } from "path";
1793
+ import { readFileSync as readFileSync5, writeFileSync, existsSync as existsSync6, mkdirSync, unlinkSync, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
1794
+ import { join as join5 } from "path";
2076
1795
  import { fileURLToPath as fileURLToPath4 } from "url";
2077
1796
  import { dirname as dirname3 } from "path";
2078
1797
  function runtimeDir() {
@@ -2083,10 +1802,25 @@ function getRuntimeConfigPath() {
2083
1802
  if (envPath) return envPath;
2084
1803
  return RUNTIME_CONFIG_PATH;
2085
1804
  }
1805
+ function getRuleSourceFingerprint() {
1806
+ if (isRuntimeConfigDisabled()) return `mode:${getRulesMode()}`;
1807
+ const p = getRuntimeConfigPath();
1808
+ try {
1809
+ const st = statSync2(p);
1810
+ return `${p}|${st.mtimeMs}|${st.size}`;
1811
+ } catch {
1812
+ return `${p}|absent`;
1813
+ }
1814
+ }
2086
1815
  function saveRuntimeConfig(config) {
1816
+ if (isRuntimeConfigDisabled()) {
1817
+ throw new Error(
1818
+ `\u89C4\u5219\u6765\u6E90\u6A21\u5F0F\u4E3A ${getRulesMode()}\uFF08runtime \u914D\u7F6E\u5DF2\u7981\u7528\uFF09\uFF1A\u4FEE\u6539\u89C4\u5219\u4E0D\u4F1A\u751F\u6548\uFF0C\u8BF7\u5207\u6362\u89C4\u5219\u6765\u6E90\u6A21\u5F0F\u5E76\u91CD\u542F\u670D\u52A1\u3002`
1819
+ );
1820
+ }
2087
1821
  const p = getRuntimeConfigPath();
2088
1822
  const dir = p.substring(0, p.lastIndexOf("/"));
2089
- if (dir && !existsSync5(dir)) mkdirSync(dir, { recursive: true });
1823
+ if (dir && !existsSync6(dir)) mkdirSync(dir, { recursive: true });
2090
1824
  writeFileSync(p, JSON.stringify(config, null, 2), "utf-8");
2091
1825
  invalidateRuntimeConfigCache();
2092
1826
  }
@@ -2107,8 +1841,20 @@ function invalidateRuntimeConfigCache() {
2107
1841
  hasRuntimeConfigCache.clear();
2108
1842
  }
2109
1843
  function loadRuntimeConfig() {
1844
+ if (isRuntimeConfigDisabled()) {
1845
+ return {
1846
+ version: 1,
1847
+ layers: { ...DEFAULT_LAYERS },
1848
+ L1_rules: {},
1849
+ L2_rules: {},
1850
+ L3_skills: {},
1851
+ L2_2: {},
1852
+ agent_rules: {},
1853
+ scenario_rules: {}
1854
+ };
1855
+ }
2110
1856
  const p = getRuntimeConfigPath();
2111
- if (!existsSync5(p)) {
1857
+ if (!existsSync6(p)) {
2112
1858
  const seeded = mergeConfig({}, generateSourceDefaults());
2113
1859
  try {
2114
1860
  saveRuntimeConfig(seeded);
@@ -2122,7 +1868,7 @@ function loadRuntimeConfig() {
2122
1868
  if (runtimeConfigCache && runtimeConfigCache.key === cacheKey) {
2123
1869
  return runtimeConfigCache.value;
2124
1870
  }
2125
- const data = JSON.parse(readFileSync4(p, "utf-8"));
1871
+ const data = JSON.parse(readFileSync5(p, "utf-8"));
2126
1872
  const merged = mergeConfig(data, generateSourceDefaults());
2127
1873
  const result = {
2128
1874
  version: data.version || 1,
@@ -2153,6 +1899,7 @@ function loadRuntimeConfig() {
2153
1899
  function isLayerEnabled(layerKey) {
2154
1900
  const disableKey = LAYER_DISABLE_ENV[layerKey];
2155
1901
  if (disableKey && process.env[disableKey] === "1") return false;
1902
+ if (isRuntimeConfigDisabled()) return true;
2156
1903
  const runtime = loadRuntimeConfig();
2157
1904
  const layers = runtime.layers || {};
2158
1905
  if (layerKey in layers) return !!layers[layerKey];
@@ -2179,7 +1926,10 @@ function filterEnabledRules(rules, categoryEnabled = true) {
2179
1926
  }
2180
1927
  function _filterRulesByOs(rules, osType) {
2181
1928
  const accepted = acceptedOsValues(osType);
2182
- return rules.filter((r) => accepted.includes(String(r.os || "linux").toLowerCase()));
1929
+ return rules.filter((r) => {
1930
+ const os4 = String(r.os || "linux").toLowerCase();
1931
+ return os4 === "all" || accepted.includes(os4);
1932
+ });
2183
1933
  }
2184
1934
  function _userCustomRules(runtime, layers, requiredKey) {
2185
1935
  const list = Array.isArray(layers) ? layers : [layers];
@@ -2253,6 +2003,7 @@ function getEnabledL2SensitivePaths(runtime, osType) {
2253
2003
  });
2254
2004
  }
2255
2005
  function hasRuntimeConfig() {
2006
+ if (isRuntimeConfigDisabled()) return false;
2256
2007
  const p = getRuntimeConfigPath();
2257
2008
  let key;
2258
2009
  try {
@@ -2261,11 +2012,11 @@ function hasRuntimeConfig() {
2261
2012
  } catch {
2262
2013
  return false;
2263
2014
  }
2264
- const cached = hasRuntimeConfigCache.get(key);
2265
- if (cached !== void 0) return cached;
2015
+ const cached2 = hasRuntimeConfigCache.get(key);
2016
+ if (cached2 !== void 0) return cached2;
2266
2017
  let ok = false;
2267
2018
  try {
2268
- JSON.parse(readFileSync4(p, "utf-8"));
2019
+ JSON.parse(readFileSync5(p, "utf-8"));
2269
2020
  ok = true;
2270
2021
  } catch {
2271
2022
  ok = false;
@@ -2365,6 +2116,9 @@ function _l1InjectionKeywordsToRules() {
2365
2116
  keyword: kw.keyword,
2366
2117
  risk_level: kw.riskLevel,
2367
2118
  lang: kw.lang || "en",
2119
+ // 带上条目自己的 os(`all` = 全平台通用),别丢 —— 丢了 Windows 会拿到
2120
+ // 本不该给它的条目。与 Python `_l1_injection_keywords_to_rules` 对齐。
2121
+ os: kw.os,
2368
2122
  enabled: true,
2369
2123
  builtin: true
2370
2124
  }));
@@ -2482,8 +2236,9 @@ function _l3SkillsToRules() {
2482
2236
  }
2483
2237
  function _scenarioRulesToDefaults() {
2484
2238
  const out = {};
2485
- const scenDir = existsSync5(_SCENARIOS_DIR_PROD) ? _SCENARIOS_DIR_PROD : existsSync5(_SCENARIOS_DIR_DEV) ? _SCENARIOS_DIR_DEV : _SCENARIOS_DIR_PROD;
2486
- if (!existsSync5(scenDir)) return out;
2239
+ const _root = getRulesRoot();
2240
+ const scenDir = _root ? join5(_root, "scenarios") : existsSync6(_SCENARIOS_DIR_PROD) ? _SCENARIOS_DIR_PROD : existsSync6(_SCENARIOS_DIR_DEV) ? _SCENARIOS_DIR_DEV : _SCENARIOS_DIR_PROD;
2241
+ if (!existsSync6(scenDir)) return out;
2487
2242
  let entries;
2488
2243
  try {
2489
2244
  entries = readdirSync4(scenDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
@@ -2491,11 +2246,11 @@ function _scenarioRulesToDefaults() {
2491
2246
  return out;
2492
2247
  }
2493
2248
  for (const scenarioId of entries) {
2494
- const rulesFile = resolveScenarioRulesFile(scenarioId) ?? join3(scenDir, scenarioId, "rules.json");
2495
- if (!existsSync5(rulesFile)) continue;
2249
+ const rulesFile = resolveScenarioRulesFile(scenarioId) ?? join5(scenDir, scenarioId, "rules.json");
2250
+ if (!existsSync6(rulesFile)) continue;
2496
2251
  let data;
2497
2252
  try {
2498
- data = JSON.parse(readFileSync4(rulesFile, "utf-8"));
2253
+ data = JSON.parse(readFileSync5(rulesFile, "utf-8"));
2499
2254
  } catch {
2500
2255
  continue;
2501
2256
  }
@@ -2531,8 +2286,9 @@ function _scenarioRulesToDefaults() {
2531
2286
  }
2532
2287
  function _agentRulesToDefaults() {
2533
2288
  const out = {};
2534
- const agentsDir = existsSync5(_AGENTS_DIR_PROD) ? _AGENTS_DIR_PROD : existsSync5(_AGENTS_DIR_DEV) ? _AGENTS_DIR_DEV : _AGENTS_DIR_PROD;
2535
- if (!existsSync5(agentsDir)) return out;
2289
+ const _root = getRulesRoot();
2290
+ const agentsDir = _root ? join5(_root, "agents") : existsSync6(_AGENTS_DIR_PROD) ? _AGENTS_DIR_PROD : existsSync6(_AGENTS_DIR_DEV) ? _AGENTS_DIR_DEV : _AGENTS_DIR_PROD;
2291
+ if (!existsSync6(agentsDir)) return out;
2536
2292
  let entries;
2537
2293
  try {
2538
2294
  entries = readdirSync4(agentsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
@@ -2540,11 +2296,11 @@ function _agentRulesToDefaults() {
2540
2296
  return out;
2541
2297
  }
2542
2298
  for (const agentId of entries) {
2543
- const rulesFile = join3(agentsDir, agentId, "rules.json");
2544
- if (!existsSync5(rulesFile)) continue;
2299
+ const rulesFile = join5(agentsDir, agentId, "rules.json");
2300
+ if (!existsSync6(rulesFile)) continue;
2545
2301
  let data;
2546
2302
  try {
2547
- data = JSON.parse(readFileSync4(rulesFile, "utf-8"));
2303
+ data = JSON.parse(readFileSync5(rulesFile, "utf-8"));
2548
2304
  } catch {
2549
2305
  continue;
2550
2306
  }
@@ -2919,7 +2675,11 @@ function getDisabledSkillIds(runtime) {
2919
2675
  }
2920
2676
  function _filterDefaultsByOs(rules, osType) {
2921
2677
  const accepted = acceptedOsValues(osType);
2922
- return rules.filter((r) => !r.os || accepted.includes(String(r.os).toLowerCase()));
2678
+ return rules.filter((r) => {
2679
+ if (!r.os) return true;
2680
+ const os4 = String(r.os).toLowerCase();
2681
+ return os4 === "all" || accepted.includes(os4);
2682
+ });
2923
2683
  }
2924
2684
  function _hasRulesForOs(rules, osType) {
2925
2685
  return _filterRulesByOs(rules || [], osType || "linux").length > 0;
@@ -3142,13 +2902,18 @@ function deleteCustomRule(layer, category, ruleId) {
3142
2902
  }
3143
2903
  function addCustomSkill(skillId, category, keywords, content) {
3144
2904
  _validateSkillId(skillId);
2905
+ if (isRuntimeConfigDisabled()) {
2906
+ throw new Error(
2907
+ `\u89C4\u5219\u6765\u6E90\u6A21\u5F0F\u4E3A ${getRulesMode()}\uFF08runtime \u914D\u7F6E\u5DF2\u7981\u7528\uFF09\uFF0C\u4E0D\u5141\u8BB8\u65B0\u589E\u81EA\u5B9A\u4E49 skill\u3002`
2908
+ );
2909
+ }
3145
2910
  const runtime = loadRuntimeConfig();
3146
- const skillsDir = join3(getRuntimeConfigDir(), "agent_moss_skills");
2911
+ const skillsDir = join5(getRuntimeConfigDir(), "agent_moss_skills");
3147
2912
  try {
3148
2913
  mkdirSync(skillsDir, { recursive: true });
3149
2914
  } catch {
3150
2915
  }
3151
- writeFileSync(join3(skillsDir, `${skillId}.md`), content, "utf-8");
2916
+ writeFileSync(join5(skillsDir, `${skillId}.md`), content, "utf-8");
3152
2917
  const l3 = runtime.L3_skills || (runtime.L3_skills = {});
3153
2918
  const cat = l3[category] || (l3[category] = { category_enabled: true, category_desc: "", skills: [] });
3154
2919
  (cat.skills || (cat.skills = [])).push({ id: skillId, keywords, enabled: true, builtin: false });
@@ -3163,9 +2928,9 @@ function deleteCustomSkill(skillId, category) {
3163
2928
  if (idx >= 0) {
3164
2929
  if (skills[idx].builtin) return null;
3165
2930
  skills.splice(idx, 1);
3166
- const skillPath = join3(getRuntimeConfigDir(), "agent_moss_skills", `${skillId}.md`);
2931
+ const skillPath = join5(getRuntimeConfigDir(), "agent_moss_skills", `${skillId}.md`);
3167
2932
  try {
3168
- if (existsSync5(skillPath)) unlinkSync(skillPath);
2933
+ if (existsSync6(skillPath)) unlinkSync(skillPath);
3169
2934
  } catch {
3170
2935
  }
3171
2936
  saveRuntimeConfig(runtime);
@@ -3368,10 +3133,11 @@ var init_runtime_config = __esm({
3368
3133
  init_skill_loader();
3369
3134
  init_rules_loader();
3370
3135
  init_runtime_config_bridge();
3136
+ init_rules_mode();
3371
3137
  __dirname4 = dirname3(fileURLToPath4(import.meta.url));
3372
3138
  ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
3373
3139
  RUNTIME_CONFIG_DIR = runtimeDir();
3374
- RUNTIME_CONFIG_PATH = join3(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
3140
+ RUNTIME_CONFIG_PATH = join5(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
3375
3141
  LAYER_DISABLE_ENV = {
3376
3142
  L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
3377
3143
  L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
@@ -3399,10 +3165,10 @@ var init_runtime_config = __esm({
3399
3165
  intent_consistency: ["intent_keywords", "reason"]
3400
3166
  };
3401
3167
  hasRuntimeConfigCache = /* @__PURE__ */ new Map();
3402
- _AGENTS_DIR_PROD = join3(__dirname4, "agents");
3403
- _AGENTS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "agents");
3404
- _SCENARIOS_DIR_PROD = join3(__dirname4, "scenarios");
3405
- _SCENARIOS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
3168
+ _AGENTS_DIR_PROD = join5(__dirname4, "agents");
3169
+ _AGENTS_DIR_DEV = join5(__dirname4, "..", "..", "..", "agent_moss", "agents");
3170
+ _SCENARIOS_DIR_PROD = join5(__dirname4, "scenarios");
3171
+ _SCENARIOS_DIR_DEV = join5(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
3406
3172
  _effectiveCache = /* @__PURE__ */ new WeakMap();
3407
3173
  SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
3408
3174
  registerRuntimeConfigBridge({
@@ -3439,6 +3205,7 @@ __export(config_exports, {
3439
3205
  isPolicyGenEnabled: () => isPolicyGenEnabled,
3440
3206
  isPolicySaveEnabled: () => isPolicySaveEnabled,
3441
3207
  probeLLMConnection: () => probeLLMConnection,
3208
+ reloadCustomRules: () => reloadCustomRules,
3442
3209
  updateLLMConfig: () => updateLLMConfig,
3443
3210
  userRuleVariantPattern: () => userRuleVariantPattern,
3444
3211
  wildcardToRegExp: () => wildcardToRegExp
@@ -3519,17 +3286,19 @@ function wildcardToRegExp(pattern) {
3519
3286
  }
3520
3287
  function loadCustomRules() {
3521
3288
  const rules = [];
3522
- try {
3523
- const raw = process.env.AGENT_MOSS_CUSTOM_RULES;
3524
- if (raw) {
3525
- const parsed = JSON.parse(raw);
3526
- if (Array.isArray(parsed)) {
3527
- for (const r of parsed) {
3528
- if (r && r.pattern && r.action) rules.push({ pattern: r.pattern, action: r.action, severity: r.severity || "high" });
3289
+ if (!isBuiltinOnly()) {
3290
+ try {
3291
+ const raw = process.env.AGENT_MOSS_CUSTOM_RULES;
3292
+ if (raw) {
3293
+ const parsed = JSON.parse(raw);
3294
+ if (Array.isArray(parsed)) {
3295
+ for (const r of parsed) {
3296
+ if (r && r.pattern && r.action) rules.push({ pattern: r.pattern, action: r.action, severity: r.severity || "high" });
3297
+ }
3529
3298
  }
3530
3299
  }
3300
+ } catch {
3531
3301
  }
3532
- } catch {
3533
3302
  }
3534
3303
  for (const rulesPath of USER_RULES_PATHS) {
3535
3304
  try {
@@ -3558,6 +3327,9 @@ function userRuleVariantPattern(pattern) {
3558
3327
  }
3559
3328
  return null;
3560
3329
  }
3330
+ function reloadCustomRules() {
3331
+ customRules = loadCustomRules();
3332
+ }
3561
3333
  function getLLMConfig() {
3562
3334
  return llmConfig;
3563
3335
  }
@@ -3645,6 +3417,7 @@ function layerEnabledOrDefault(key) {
3645
3417
  }
3646
3418
  }
3647
3419
  function isBrainEnabled() {
3420
+ if (isRuntimeConfigDisabled()) return false;
3648
3421
  return getAgentMossSetting("AGENT_MOSS_BRAIN_ENABLED", "") === "1";
3649
3422
  }
3650
3423
  function getAgentMossSetting(key, def = "") {
@@ -3686,6 +3459,7 @@ var init_config = __esm({
3686
3459
  init_esm_shims();
3687
3460
  init_paths();
3688
3461
  init_runtime_config();
3462
+ init_rules_mode();
3689
3463
  __configModuleDir = (() => {
3690
3464
  try {
3691
3465
  return path2.dirname(fileURLToPath5(import.meta.url));
@@ -3732,17 +3506,21 @@ var init_config = __esm({
3732
3506
  });
3733
3507
 
3734
3508
  // src/agents/index.ts
3735
- import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
3736
- import { resolve as resolve4, dirname as dirname4 } from "path";
3509
+ import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
3510
+ import { resolve as resolve4, dirname as dirname4, join as join6 } from "path";
3737
3511
  import { fileURLToPath as fileURLToPath6 } from "url";
3738
3512
  function resolveAgentsDir() {
3739
- if (existsSync6(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
3740
- if (existsSync6(AGENTS_DIR_DEV_FALLBACK)) return AGENTS_DIR_DEV_FALLBACK;
3513
+ const root = getRulesRoot();
3514
+ if (root) return join6(root, "agents");
3515
+ if (existsSync7(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
3516
+ if (existsSync7(AGENTS_DIR_DEV_FALLBACK)) return AGENTS_DIR_DEV_FALLBACK;
3741
3517
  return AGENTS_DIR_PROD;
3742
3518
  }
3743
3519
  function getAgentExtensions(agentId) {
3744
3520
  if (!agentId) return null;
3745
- if (cache.has(agentId)) return cache.get(agentId) ?? null;
3521
+ const fp = getRuleSourceFingerprint();
3522
+ const cached2 = cache.get(agentId);
3523
+ if (cached2 && cached2.fp === fp) return cached2.ext;
3746
3524
  let ext = null;
3747
3525
  try {
3748
3526
  if (hasRuntimeConfig()) {
@@ -3753,25 +3531,25 @@ function getAgentExtensions(agentId) {
3753
3531
  if (ext === null) {
3754
3532
  const agentsDir = resolveAgentsDir();
3755
3533
  const rulesFile = resolve4(agentsDir, agentId, "rules.json");
3756
- if (!existsSync6(rulesFile)) {
3757
- cache.set(agentId, null);
3534
+ if (!existsSync7(rulesFile)) {
3535
+ cache.set(agentId, { fp, ext: null });
3758
3536
  return null;
3759
3537
  }
3760
3538
  try {
3761
- const raw = readFileSync5(rulesFile, "utf-8");
3539
+ const raw = readFileSync6(rulesFile, "utf-8");
3762
3540
  ext = JSON.parse(raw);
3763
3541
  if (!ext.agent_id) {
3764
3542
  console.warn(`[AgentMoss agents] rules.json \u7F3A\u5C11 agent_id \u5B57\u6BB5: ${rulesFile}`);
3765
- cache.set(agentId, null);
3543
+ cache.set(agentId, { fp, ext: null });
3766
3544
  return null;
3767
3545
  }
3768
3546
  } catch (e) {
3769
3547
  console.error(`[AgentMoss agents] \u52A0\u8F7D agent ${agentId} rules.json \u5931\u8D25:`, e.message);
3770
- cache.set(agentId, null);
3548
+ cache.set(agentId, { fp, ext: null });
3771
3549
  return null;
3772
3550
  }
3773
3551
  }
3774
- cache.set(agentId, ext);
3552
+ cache.set(agentId, { fp, ext });
3775
3553
  console.error(
3776
3554
  `[AgentMoss agents] \u52A0\u8F7D agent ${agentId} \u5B9A\u5236\u89C4\u5219: ${ext.extra_command_patterns?.length ?? 0} cmd patterns, ${ext.extra_injection_keywords?.length ?? 0} injection keywords, ${ext.extra_sensitive_paths?.length ?? 0} sensitive paths, ${ext.builtin_safe_skills?.length ?? 0} builtin safe skills, ${ext.builtin_safe_action_types?.length ?? 0} builtin safe action types`
3777
3555
  );
@@ -3797,6 +3575,7 @@ var init_agents = __esm({
3797
3575
  "src/agents/index.ts"() {
3798
3576
  "use strict";
3799
3577
  init_esm_shims();
3578
+ init_paths();
3800
3579
  init_runtime_config();
3801
3580
  __filename4 = fileURLToPath6(import.meta.url);
3802
3581
  __dirname5 = dirname4(__filename4);
@@ -3807,17 +3586,21 @@ var init_agents = __esm({
3807
3586
  });
3808
3587
 
3809
3588
  // src/scenarios/index.ts
3810
- import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
3811
- import { resolve as resolve5, dirname as dirname5 } from "path";
3589
+ import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
3590
+ import { resolve as resolve5, dirname as dirname5, join as join7 } from "path";
3812
3591
  import { fileURLToPath as fileURLToPath7 } from "url";
3813
3592
  function resolveScenariosDir() {
3814
- if (existsSync7(SCENARIOS_DIR_PROD)) return SCENARIOS_DIR_PROD;
3815
- if (existsSync7(SCENARIOS_DIR_DEV_FALLBACK)) return SCENARIOS_DIR_DEV_FALLBACK;
3593
+ const root = getRulesRoot();
3594
+ if (root) return join7(root, "scenarios");
3595
+ if (existsSync8(SCENARIOS_DIR_PROD)) return SCENARIOS_DIR_PROD;
3596
+ if (existsSync8(SCENARIOS_DIR_DEV_FALLBACK)) return SCENARIOS_DIR_DEV_FALLBACK;
3816
3597
  return SCENARIOS_DIR_PROD;
3817
3598
  }
3818
3599
  function getScenarioExtensions(scenarioId) {
3819
3600
  if (!scenarioId) return null;
3820
- if (cache2.has(scenarioId)) return cache2.get(scenarioId) ?? null;
3601
+ const fp = getRuleSourceFingerprint();
3602
+ const cached2 = cache2.get(scenarioId);
3603
+ if (cached2 && cached2.fp === fp) return cached2.ext;
3821
3604
  let ext = null;
3822
3605
  let overrideHit = null;
3823
3606
  try {
@@ -3840,17 +3623,17 @@ function getScenarioExtensions(scenarioId) {
3840
3623
  `[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`
3841
3624
  );
3842
3625
  }
3843
- if (!existsSync7(rulesFile)) {
3844
- cache2.set(scenarioId, null);
3626
+ if (!existsSync8(rulesFile)) {
3627
+ cache2.set(scenarioId, { fp, ext: null });
3845
3628
  return null;
3846
3629
  }
3847
3630
  try {
3848
- const raw = readFileSync6(rulesFile, "utf-8");
3631
+ const raw = readFileSync7(rulesFile, "utf-8");
3849
3632
  const data = JSON.parse(raw);
3850
3633
  const id = data.scenario_id || data.agent_id || "";
3851
3634
  if (!id) {
3852
3635
  console.warn(`[AgentMoss scenarios] rules.json \u7F3A\u5C11 scenario_id \u5B57\u6BB5: ${rulesFile}`);
3853
- cache2.set(scenarioId, null);
3636
+ cache2.set(scenarioId, { fp, ext: null });
3854
3637
  return null;
3855
3638
  }
3856
3639
  ext = {
@@ -3865,11 +3648,11 @@ function getScenarioExtensions(scenarioId) {
3865
3648
  };
3866
3649
  } catch (e) {
3867
3650
  console.error(`[AgentMoss scenarios] \u52A0\u8F7D\u573A\u666F ${scenarioId} rules.json \u5931\u8D25:`, e.message);
3868
- cache2.set(scenarioId, null);
3651
+ cache2.set(scenarioId, { fp, ext: null });
3869
3652
  return null;
3870
3653
  }
3871
3654
  }
3872
- cache2.set(scenarioId, ext);
3655
+ cache2.set(scenarioId, { fp, ext });
3873
3656
  console.error(
3874
3657
  `[AgentMoss scenarios] \u52A0\u8F7D\u573A\u666F ${scenarioId} \u5B9A\u5236\u89C4\u5219: ${ext.extra_command_patterns?.length ?? 0} cmd patterns, ${ext.extra_injection_keywords?.length ?? 0} injection keywords, ${ext.extra_sensitive_paths?.length ?? 0} sensitive paths`
3875
3658
  );
@@ -3889,6 +3672,7 @@ var init_scenarios = __esm({
3889
3672
  "src/scenarios/index.ts"() {
3890
3673
  "use strict";
3891
3674
  init_esm_shims();
3675
+ init_paths();
3892
3676
  init_agents();
3893
3677
  init_runtime_config();
3894
3678
  init_rules_loader();
@@ -4295,7 +4079,7 @@ function severityRank(s) {
4295
4079
  }
4296
4080
  function isSearchActionType(actionType) {
4297
4081
  const at = actionType.toLowerCase();
4298
- return SEARCH_ACTION_PREFIXES.some((p) => at.startsWith(p)) || ["websearch", "web_search", "search", "query"].includes(at);
4082
+ return SEARCH_ACTION_PREFIXES.some((p) => at.startsWith(p)) || SEARCH_ACTION_EXACT.includes(at);
4299
4083
  }
4300
4084
  function checkSensitivePath(command, cwd, agentExt, osType) {
4301
4085
  const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
@@ -4585,8 +4369,7 @@ function isSshKeygenGenerate(command) {
4585
4369
  function checkDangerousPatterns(actionType, actionDetail) {
4586
4370
  if (actionType === "file_write" || actionType === "file_edit") return null;
4587
4371
  const scan = stripHeredocContent(actionDetail);
4588
- const TEMP_DIRS = ["/tmp", "/var/tmp", "/run", "/dev/shm"];
4589
- const isTempPath = TEMP_DIRS.some(
4372
+ const isTempPath = TEMP_DIRS2.some(
4590
4373
  (td) => new RegExp(`(?:^|[\\s/])${td.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[\\s/]|$)`).test(actionDetail)
4591
4374
  ) || /(?:^|[\s;]|&&\s*|\|\|\s*)var\/tmp(?:[/\s;]|$)/.test(actionDetail);
4592
4375
  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;
@@ -4732,7 +4515,7 @@ function checkLateralMovement(actionDetail) {
4732
4515
  }
4733
4516
  return null;
4734
4517
  }
4735
- 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;
4518
+ 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;
4736
4519
  var init_heuristic = __esm({
4737
4520
  "src/engine/heuristic.ts"() {
4738
4521
  "use strict";
@@ -4749,7 +4532,8 @@ var init_heuristic = __esm({
4749
4532
  return kws.map((k) => ({
4750
4533
  keyword: k.keyword,
4751
4534
  riskLevel: k.risk_level,
4752
- lang: k.lang || "en"
4535
+ lang: k.lang || "en",
4536
+ os: String(k.os || "all").toLowerCase()
4753
4537
  }));
4754
4538
  })();
4755
4539
  DANGEROUS_PATTERNS2 = _loadDangerousPatterns("linux");
@@ -4757,7 +4541,9 @@ var init_heuristic = __esm({
4757
4541
  DANGEROUS_PATTERNS_MACOS = _loadDangerousPatterns("macos");
4758
4542
  DANGEROUS_PATTERNS_HARMONY = _loadDangerousPatterns("harmony");
4759
4543
  LOOPBACK_URL_RE = /https?:\/\/([^\/\s;'"|&)]+)/i;
4760
- LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "::1", "0.0.0.0", "[::1]"]);
4544
+ HEURISTIC_RULES = loadHeuristicRules();
4545
+ LOOPBACK_HOSTS = new Set(HEURISTIC_RULES.loopback_hosts ?? []);
4546
+ TEMP_DIRS2 = HEURISTIC_RULES.temp_dirs ?? [];
4761
4547
  PATH_CONTEXT_TRIGGER_RE = /(?:^|[;&|]\s*)(?:rm|rmdir|unlink|ln|mv|cp|dd|truncate|shred|chmod|chown)\b/i;
4762
4548
  HOME_PREFIX_RE = /(?:~|~(?=\/)|\$HOME\b|\$\{HOME\}|\/home\/[a-z0-9_.-]+)/gi;
4763
4549
  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;
@@ -4765,15 +4551,16 @@ var init_heuristic = __esm({
4765
4551
  PY_BIGNUM_RE = /\d{6,}/;
4766
4552
  PY_INLINE_C_RE = /(python(?:3(?:\.\d+)?)?|perl5?|ruby|node|php|lua)\s+(?:-[a-zA-Z]+\s+)*-c\s+(?:"([^"]*)"|'([^']*)')/i;
4767
4553
  PY_CMD_SUBST_C_RE = /\$\(\s*(python(?:3(?:\.\d+)?)?|perl5?|ruby|node|php|lua)\s+(?:-[a-zA-Z]+\s+)*-c\s+(?:"([^"]*)"'|'([^']*)')\s*\)/i;
4768
- SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
4769
- SEARCH_ACTION_PREFIXES = ["search-", "web-", "browser-", "query-", "websearch", "web_search"];
4554
+ SENSITIVE_EXCLUDED_PREFIXES = HEURISTIC_RULES.sensitive_excluded_prefixes ?? [];
4555
+ SEARCH_ACTION_PREFIXES = HEURISTIC_RULES.search_action_prefixes ?? [];
4556
+ SEARCH_ACTION_EXACT = HEURISTIC_RULES.search_action_exact ?? [];
4770
4557
  HOME_TARGET_RE = /(?:^|[\s="'($])(?:~|(?:\$home|\$\{home\}))(?:\/(?:\.\*|\*|\.)?)?(?=[\s"';}|&)]|$)/i;
4771
4558
  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;
4772
4559
  }
4773
4560
  });
4774
4561
 
4775
4562
  // src/engine/template-loader.ts
4776
- import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
4563
+ import { readFileSync as readFileSync8, existsSync as existsSync9 } from "fs";
4777
4564
  import { resolve as resolve6, dirname as dirname6 } from "path";
4778
4565
  import { fileURLToPath as fileURLToPath8 } from "url";
4779
4566
  function loadTemplates() {
@@ -4787,9 +4574,9 @@ function loadTemplates() {
4787
4574
  }
4788
4575
  for (const file of TEMPLATE_FILES) {
4789
4576
  const filePath = resolve6(dir, file);
4790
- if (existsSync8(filePath)) {
4577
+ if (existsSync9(filePath)) {
4791
4578
  const key = file.replace(".txt", "").replace("_template", "");
4792
- cachedTemplates[key] = readFileSync7(filePath, "utf-8");
4579
+ cachedTemplates[key] = readFileSync8(filePath, "utf-8");
4793
4580
  }
4794
4581
  }
4795
4582
  console.log(`[AgentMoss] Loaded ${Object.keys(cachedTemplates).length} templates from ${dir}`);
@@ -4809,8 +4596,8 @@ var init_template_loader = __esm({
4809
4596
  });
4810
4597
 
4811
4598
  // src/engine/script-analyzer.ts
4812
- import { readFileSync as readFileSync8 } from "fs";
4813
- import { resolve as resolve7, join as join4 } from "path";
4599
+ import { readFileSync as readFileSync9 } from "fs";
4600
+ import { resolve as resolve7, join as join8 } from "path";
4814
4601
  function emptyResult() {
4815
4602
  return { scriptPath: "", scriptContent: "", matchedKeywords: [], lineCount: 0, isSuspicious: false, error: "", riskLevel: "", riskReason: "" };
4816
4603
  }
@@ -4836,10 +4623,10 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
4836
4623
  const hitGroup2 = rule.group2.size === 0 ? true : [...rule.group2].some((k) => keywordSet.has(k));
4837
4624
  if (hitGroup1 && hitGroup2) return { level: rule.level, reason: rule.reason };
4838
4625
  }
4839
- const networkPostKeywords = /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]);
4626
+ const networkPostKeywords = new Set(KEYWORD_SETS.network_post ?? []);
4840
4627
  const hasNetworkPost = [...networkPostKeywords].some((k) => keywordSet.has(k));
4841
4628
  if (hasNetworkPost) {
4842
- const envPatterns = ["$env", "$(env)", "$USER", "$(hostname)", "$(pwd)", "$HOME"];
4629
+ const envPatterns = SCRIPT_RULES.env_exfil_patterns ?? [];
4843
4630
  const contentLower = scriptContent.toLowerCase();
4844
4631
  for (const p of envPatterns) {
4845
4632
  if (contentLower.includes(p.toLowerCase())) {
@@ -4863,8 +4650,8 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
4863
4650
  }
4864
4651
  function readScriptContent(scriptPath, maxLines = 500) {
4865
4652
  try {
4866
- const expandedPath = scriptPath === "~" || scriptPath.startsWith("~/") ? join4(process.env.HOME || "/home/user", scriptPath.slice(1)) : resolve7(scriptPath);
4867
- const raw = readFileSync8(expandedPath, "utf-8");
4653
+ const expandedPath = scriptPath === "~" || scriptPath.startsWith("~/") ? join8(process.env.HOME || "/home/user", scriptPath.slice(1)) : resolve7(scriptPath);
4654
+ const raw = readFileSync9(expandedPath, "utf-8");
4868
4655
  const lines = raw.split("\n").slice(0, maxLines);
4869
4656
  return { content: lines.join("\n"), lineCount: lines.length, error: "" };
4870
4657
  } catch (e) {
@@ -4913,90 +4700,28 @@ ${result.scriptContent}
4913
4700
  \`\`\`
4914
4701
  `;
4915
4702
  }
4916
- var SUSPICIOUS_PATTERNS, SCRIPT_EXEC_PATTERNS, KEYWORD_COMBINATION_RISKS, SINGLE_KEYWORD_RISKS, LEVEL_ORDER;
4703
+ var SCRIPT_RULES, SUSPICIOUS_PATTERNS, SCRIPT_EXEC_PATTERNS, KEYWORD_SETS, KEYWORD_COMBINATION_RISKS, SINGLE_KEYWORD_RISKS, LEVEL_ORDER;
4917
4704
  var init_script_analyzer = __esm({
4918
4705
  "src/engine/script-analyzer.ts"() {
4919
4706
  "use strict";
4920
4707
  init_esm_shims();
4921
- SUSPICIOUS_PATTERNS = [
4922
- // Network exfiltration (curl/wget POST in scripts is almost never legitimate)
4923
- { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
4924
- { name: "curl --data", pattern: /curl\s+(.*\s)?--data/i },
4925
- { name: "curl -d", pattern: /curl\s+(.*\s)?-d\s/i },
4926
- { name: "wget --post-data", pattern: /wget\s+(.*\s)?--post-data/i },
4927
- { name: "wget --post-file", pattern: /wget\s+(.*\s)?--post-file/i },
4928
- // Reverse shell
4929
- { name: "nc -e", pattern: /\bnc\s+.*-e\b/ },
4930
- { name: "ncat -e", pattern: /\bncat\s+.*-e\b/ },
4931
- { name: "/dev/tcp", pattern: /\/dev\/tcp\// },
4932
- { name: "/dev/udp", pattern: /\/dev\/udp\// },
4933
- // Base64 obfuscation
4934
- { name: "$(base64", pattern: /\$\(\s*base64/ },
4935
- { name: "`base64", pattern: /`\s*base64/ },
4936
- { name: "base64 decode pipe", pattern: /base64\s+(-d|--decode)\s*\|/ },
4937
- // Sensitive file access
4938
- { name: "/etc/shadow", pattern: /\/etc\/shadow/ },
4939
- { name: "id_rsa", pattern: /\bid_rsa\b/ },
4940
- { name: "id_ed25519", pattern: /\bid_ed25519\b/ },
4941
- // Persistence backdoor
4942
- { name: "crontab install", pattern: /crontab\s+-/ },
4943
- { name: "authorized_keys", pattern: /authorized_keys/ },
4944
- { name: "ssh no host key check", pattern: /ssh\s+.*StrictHostKeyChecking\s*=\s*no/ }
4945
- ];
4946
- SCRIPT_EXEC_PATTERNS = [
4947
- /bash\s+(\S+\.sh)/,
4948
- /sh\s+(\S+\.sh)/,
4949
- /\.\/(\S+\.sh)/,
4950
- /source\s+(\S+\.sh)/,
4951
- /\.\s+(\S+\.sh)/,
4952
- /zsh\s+(\S+\.sh)/
4953
- ];
4954
- KEYWORD_COMBINATION_RISKS = [
4955
- {
4956
- group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
4957
- group2: /* @__PURE__ */ new Set(["$(base64", "`base64", "base64 decode pipe"]),
4958
- level: "critical",
4959
- 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"
4960
- },
4961
- {
4962
- group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
4963
- group2: /* @__PURE__ */ new Set(["/etc/shadow", "id_rsa", "id_ed25519"]),
4964
- level: "critical",
4965
- 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"
4966
- },
4967
- {
4968
- group1: /* @__PURE__ */ new Set(["nc -e", "ncat -e", "/dev/tcp", "/dev/udp"]),
4969
- group2: /* @__PURE__ */ new Set(),
4970
- level: "high",
4971
- reason: "\u68C0\u6D4B\u5230\u53CD\u5F39 Shell \u7279\u5F81\uFF0C\u653B\u51FB\u8005\u53EF\u80FD\u5EFA\u7ACB\u8FDC\u7A0B\u63A7\u5236\u901A\u9053\u3002"
4972
- },
4973
- {
4974
- group1: /* @__PURE__ */ new Set(["crontab install", "authorized_keys", "ssh no host key check"]),
4975
- group2: /* @__PURE__ */ new Set(),
4976
- level: "high",
4977
- 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"
4978
- }
4979
- ];
4980
- SINGLE_KEYWORD_RISKS = {
4981
- "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" },
4982
- "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" },
4983
- "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" },
4984
- "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" },
4985
- "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" },
4986
- "nc -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 nc -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
4987
- "ncat -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 ncat -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
4988
- "/dev/tcp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/tcp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
4989
- "/dev/udp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/udp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
4990
- "$(base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
4991
- "`base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
4992
- "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" },
4993
- "/etc/shadow": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6\u3002" },
4994
- "id_rsa": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
4995
- "id_ed25519": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
4996
- "crontab install": { level: "high", reason: "\u68C0\u6D4B\u5230\u4FEE\u6539\u5B9A\u65F6\u4EFB\u52A1\uFF0C\u53EF\u80FD\u662F\u6301\u4E45\u5316\u540E\u95E8\u3002" },
4997
- "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" },
4998
- "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" }
4999
- };
4708
+ init_rules_loader();
4709
+ SCRIPT_RULES = loadScriptRules();
4710
+ if (!SCRIPT_RULES || Object.keys(SCRIPT_RULES).length === 0) {
4711
+ console.error(
4712
+ "[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"
4713
+ );
4714
+ }
4715
+ SUSPICIOUS_PATTERNS = (SCRIPT_RULES.suspicious_patterns ?? []).map((r) => ({ name: r.name, pattern: compileRuleRegex(r) }));
4716
+ SCRIPT_EXEC_PATTERNS = ((SCRIPT_RULES.script_exec_patterns ?? {}).legacy ?? []).map((r) => compileRuleRegex(r));
4717
+ KEYWORD_SETS = SCRIPT_RULES.keyword_sets ?? {};
4718
+ KEYWORD_COMBINATION_RISKS = (SCRIPT_RULES.keyword_combination_risks ?? []).map((r) => ({
4719
+ group1: resolveKeywordSet(r.group1, KEYWORD_SETS),
4720
+ group2: resolveKeywordSet(r.group2, KEYWORD_SETS),
4721
+ level: String(r.level),
4722
+ reason: String(r.reason)
4723
+ }));
4724
+ SINGLE_KEYWORD_RISKS = SCRIPT_RULES.single_keyword_risks ?? {};
5000
4725
  LEVEL_ORDER = { low: 1, medium: 2, high: 3, critical: 4 };
5001
4726
  }
5002
4727
  });
@@ -6540,8 +6265,8 @@ var init_tracker = __esm({
6540
6265
  });
6541
6266
 
6542
6267
  // src/engine/script-content-analyzer.ts
6543
- import { statSync as statSync4, existsSync as existsSync10 } from "fs";
6544
- import { resolve as resolve9, join as join6, isAbsolute as isAbsolute2 } from "path";
6268
+ import { statSync as statSync4, existsSync as existsSync11 } from "fs";
6269
+ import { resolve as resolve9, join as join10, isAbsolute as isAbsolute2 } from "path";
6545
6270
  import { homedir as homedir3 } from "os";
6546
6271
  function emptyChain() {
6547
6272
  return {
@@ -6574,10 +6299,10 @@ function assessKeywordCombinationRisk2(matchedKeywords, scriptContent) {
6574
6299
  const hitGroup2 = rule.group2.size === 0 ? true : [...rule.group2].some((k) => keywordSet.has(k));
6575
6300
  if (hitGroup1 && hitGroup2) return { level: rule.level, reason: rule.reason };
6576
6301
  }
6577
- const networkPostKeywords = /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]);
6302
+ const networkPostKeywords = new Set(KEYWORD_SETS2.network_post ?? []);
6578
6303
  const hasNetworkPost = [...networkPostKeywords].some((k) => keywordSet.has(k));
6579
6304
  if (hasNetworkPost) {
6580
- const envPatterns = ["$env", "$(env)", "$USER", "$(hostname)", "$(pwd)", "$HOME"];
6305
+ const envPatterns = SCRIPT_RULES2.env_exfil_patterns ?? [];
6581
6306
  const contentLower = scriptContent.toLowerCase();
6582
6307
  for (const p of envPatterns) {
6583
6308
  if (contentLower.includes(p.toLowerCase())) {
@@ -6653,7 +6378,7 @@ function extractInlineScriptPath(actionDetail) {
6653
6378
  if (looksLikeScriptPath(tok)) {
6654
6379
  try {
6655
6380
  const resolved = resolvePath2(tok.replace(/^['"]|['"]$/g, ""));
6656
- if (existsSync10(resolved)) {
6381
+ if (existsSync11(resolved)) {
6657
6382
  const stat = statSync4(resolved);
6658
6383
  if (stat.isFile()) return resolved;
6659
6384
  }
@@ -6667,7 +6392,7 @@ function extractInlineScriptPath(actionDetail) {
6667
6392
  const p = m[1];
6668
6393
  try {
6669
6394
  const resolved = resolvePath2(p);
6670
- if (existsSync10(resolved)) {
6395
+ if (existsSync11(resolved)) {
6671
6396
  const stat = statSync4(resolved);
6672
6397
  if (stat.isFile()) return resolved;
6673
6398
  }
@@ -6683,7 +6408,7 @@ function extractInlineScriptPath(actionDetail) {
6683
6408
  let resolved;
6684
6409
  try {
6685
6410
  resolved = resolvePath2(p);
6686
- if (existsSync10(resolved)) {
6411
+ if (existsSync11(resolved)) {
6687
6412
  const stat = statSync4(resolved);
6688
6413
  if (stat.isFile()) return resolved;
6689
6414
  }
@@ -6724,7 +6449,7 @@ function shlexSplit(s) {
6724
6449
  function resolvePath2(p) {
6725
6450
  let expanded = p;
6726
6451
  if (expanded.startsWith("~/") || expanded === "~") {
6727
- expanded = join6(homedir3(), expanded.slice(1));
6452
+ expanded = join10(homedir3(), expanded.slice(1));
6728
6453
  }
6729
6454
  return resolve9(expanded);
6730
6455
  }
@@ -6848,7 +6573,7 @@ function analyzeScriptContent2(input, ext) {
6848
6573
  isInline = true;
6849
6574
  }
6850
6575
  if (resolvedPath && !isAbsolute2(resolvedPath) && !resolvedPath.startsWith("~/")) {
6851
- resolvedPath = join6(input.cwd || process.cwd(), resolvedPath);
6576
+ resolvedPath = join10(input.cwd || process.cwd(), resolvedPath);
6852
6577
  }
6853
6578
  const l1Adapter = (content) => {
6854
6579
  const j = scanDangerousCommandPatterns("bash", content, input.osType);
@@ -6933,7 +6658,7 @@ ${contentPreview}
6933
6658
  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";
6934
6659
  return header + parts.join("\n---\n\n");
6935
6660
  }
6936
- var SUSPICIOUS_PATTERNS2, SCRIPT_EXEC_PATTERNS2, KEYWORD_COMBINATION_RISKS2, SINGLE_KEYWORD_RISKS2, LEVEL_ORDER2;
6661
+ var SCRIPT_RULES2, SUSPICIOUS_PATTERNS2, SCRIPT_EXEC_PATTERNS2, KEYWORD_SETS2, KEYWORD_COMBINATION_RISKS2, SINGLE_KEYWORD_RISKS2, LEVEL_ORDER2;
6937
6662
  var init_script_content_analyzer = __esm({
6938
6663
  "src/engine/script-content-analyzer.ts"() {
6939
6664
  "use strict";
@@ -6943,87 +6668,23 @@ var init_script_content_analyzer = __esm({
6943
6668
  init_logic_rules();
6944
6669
  init_inline_analyzer();
6945
6670
  init_tracker();
6946
- SUSPICIOUS_PATTERNS2 = [
6947
- { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
6948
- { name: "curl --data", pattern: /curl\s+(.*\s)?--data/i },
6949
- { name: "curl -d", pattern: /curl\s+(.*\s)?-d\s/i },
6950
- { name: "wget --post-data", pattern: /wget\s+(.*\s)?--post-data/i },
6951
- { name: "wget --post-file", pattern: /wget\s+(.*\s)?--post-file/i },
6952
- { name: "nc -e", pattern: /\bnc\s+.*-e\b/ },
6953
- { name: "ncat -e", pattern: /\bncat\s+.*-e\b/ },
6954
- { name: "/dev/tcp", pattern: /\/dev\/tcp\// },
6955
- { name: "/dev/udp", pattern: /\/dev\/udp\// },
6956
- { name: "$(base64", pattern: /\$\(\s*base64/ },
6957
- { name: "`base64", pattern: /`\s*base64/ },
6958
- { name: "base64 decode pipe", pattern: /base64\s+(-d|--decode)\s*\|/ },
6959
- { name: "/etc/shadow", pattern: /\/etc\/shadow/ },
6960
- { name: "id_rsa", pattern: /\bid_rsa\b/ },
6961
- { name: "id_ed25519", pattern: /\bid_ed25519\b/ },
6962
- { name: "crontab install", pattern: /crontab\s+-/ },
6963
- { name: "authorized_keys", pattern: /authorized_keys/ },
6964
- { name: "ssh no host key check", pattern: /ssh\s+.*StrictHostKeyChecking\s*=\s*no/ }
6965
- ];
6966
- SCRIPT_EXEC_PATTERNS2 = [
6967
- /(?:^|\s|&&|\|\|)\s*bash\s+(\S+)/i,
6968
- /(?:^|\s|&&|\|\|)\s*\/bin\/bash\s+(\S+)/i,
6969
- /(?:^|\s|&&|\|\|)\s*sh\s+(\S+)/i,
6970
- /(?:^|\s|&&|\|\|)\s*zsh\s+(\S+)/i,
6971
- /\.\/(\S+)/,
6972
- /(?:^|\s|&&|\|\|)\s*source\s+(\S+)/i,
6973
- /(?:^|\s|&&|\|\|)\s*\.\s+(\S+)/,
6974
- /(?:^|\s|&&|\|\|)\s*python3?\s+(\S+\.py)/i,
6975
- /(?:^|\s|&&|\|\|)\s*node\s+(\S+\.js)/i,
6976
- /(?:^|\s|&&|\|\|)\s*ruby\s+(\S+\.rb)/i,
6977
- /(?:^|\s|&&|\|\|)\s*perl\s+(\S+\.pl)/i,
6978
- /(?:^|\s|&&|\|\|)\s*php\s+(\S+\.php)/i,
6979
- /(?:^|\s|&&|\|\|)\s*lua\s+(\S+\.lua)/i
6980
- ];
6981
- KEYWORD_COMBINATION_RISKS2 = [
6982
- {
6983
- group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
6984
- group2: /* @__PURE__ */ new Set(["$(base64", "`base64", "base64 decode pipe"]),
6985
- level: "critical",
6986
- 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"
6987
- },
6988
- {
6989
- group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
6990
- group2: /* @__PURE__ */ new Set(["/etc/shadow", "id_rsa", "id_ed25519"]),
6991
- level: "critical",
6992
- 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"
6993
- },
6994
- {
6995
- group1: /* @__PURE__ */ new Set(["nc -e", "ncat -e", "/dev/tcp", "/dev/udp"]),
6996
- group2: /* @__PURE__ */ new Set(),
6997
- level: "high",
6998
- reason: "\u68C0\u6D4B\u5230\u53CD\u5F39 Shell \u7279\u5F81\uFF0C\u653B\u51FB\u8005\u53EF\u80FD\u5EFA\u7ACB\u8FDC\u7A0B\u63A7\u5236\u901A\u9053\u3002"
6999
- },
7000
- {
7001
- group1: /* @__PURE__ */ new Set(["crontab install", "authorized_keys", "ssh no host key check"]),
7002
- group2: /* @__PURE__ */ new Set(),
7003
- level: "high",
7004
- 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"
7005
- }
7006
- ];
7007
- SINGLE_KEYWORD_RISKS2 = {
7008
- "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" },
7009
- "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" },
7010
- "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" },
7011
- "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" },
7012
- "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" },
7013
- "nc -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 nc -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
7014
- "ncat -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 ncat -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
7015
- "/dev/tcp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/tcp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
7016
- "/dev/udp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/udp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
7017
- "$(base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
7018
- "`base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
7019
- "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" },
7020
- "/etc/shadow": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6\u3002" },
7021
- "id_rsa": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
7022
- "id_ed25519": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
7023
- "crontab install": { level: "high", reason: "\u68C0\u6D4B\u5230\u4FEE\u6539\u5B9A\u65F6\u4EFB\u52A1\uFF0C\u53EF\u80FD\u662F\u6301\u4E45\u5316\u540E\u95E8\u3002" },
7024
- "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" },
7025
- "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" }
7026
- };
6671
+ init_rules_loader();
6672
+ SCRIPT_RULES2 = loadScriptRules();
6673
+ if (!SCRIPT_RULES2 || Object.keys(SCRIPT_RULES2).length === 0) {
6674
+ console.error(
6675
+ "[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"
6676
+ );
6677
+ }
6678
+ SUSPICIOUS_PATTERNS2 = (SCRIPT_RULES2.suspicious_patterns ?? []).map((r) => ({ name: r.name, pattern: compileRuleRegex(r) }));
6679
+ SCRIPT_EXEC_PATTERNS2 = ((SCRIPT_RULES2.script_exec_patterns ?? {}).chain ?? []).map((r) => compileRuleRegex(r));
6680
+ KEYWORD_SETS2 = SCRIPT_RULES2.keyword_sets ?? {};
6681
+ KEYWORD_COMBINATION_RISKS2 = (SCRIPT_RULES2.keyword_combination_risks ?? []).map((r) => ({
6682
+ group1: resolveKeywordSet(r.group1, KEYWORD_SETS2),
6683
+ group2: resolveKeywordSet(r.group2, KEYWORD_SETS2),
6684
+ level: String(r.level),
6685
+ reason: String(r.reason)
6686
+ }));
6687
+ SINGLE_KEYWORD_RISKS2 = SCRIPT_RULES2.single_keyword_risks ?? {};
7027
6688
  LEVEL_ORDER2 = { low: 1, medium: 2, high: 3, critical: 4 };
7028
6689
  }
7029
6690
  });
@@ -7038,16 +6699,16 @@ __export(token_stats_exports, {
7038
6699
  recordTokenUsage: () => recordTokenUsage,
7039
6700
  resetTokenStats: () => resetTokenStats
7040
6701
  });
7041
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync2, existsSync as existsSync11, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
6702
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync2, existsSync as existsSync12, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
7042
6703
  function getStatsPath() {
7043
6704
  const envPath = process.env[ENV_STATS_PATH];
7044
6705
  if (envPath) return envPath;
7045
6706
  return getTokenStatsPath();
7046
6707
  }
7047
6708
  function loadAllRecords(statsPath) {
7048
- if (!existsSync11(statsPath)) return [];
6709
+ if (!existsSync12(statsPath)) return [];
7049
6710
  try {
7050
- const data = JSON.parse(readFileSync11(statsPath, "utf-8"));
6711
+ const data = JSON.parse(readFileSync12(statsPath, "utf-8"));
7051
6712
  if (Array.isArray(data)) return data;
7052
6713
  if (data && Array.isArray(data.records)) return data.records;
7053
6714
  return [];
@@ -7058,7 +6719,7 @@ function loadAllRecords(statsPath) {
7058
6719
  function saveRecords(statsPath, records) {
7059
6720
  try {
7060
6721
  const dir = statsPath.substring(0, statsPath.lastIndexOf("/"));
7061
- if (dir && !existsSync11(dir)) mkdirSync2(dir, { recursive: true });
6722
+ if (dir && !existsSync12(dir)) mkdirSync2(dir, { recursive: true });
7062
6723
  writeFileSync2(statsPath, JSON.stringify(records, null, 2), "utf-8");
7063
6724
  } catch (e) {
7064
6725
  }
@@ -7185,7 +6846,7 @@ function getTokenTrend(days = 0, startDate, endDate) {
7185
6846
  }
7186
6847
  function resetTokenStats() {
7187
6848
  const statsPath = getStatsPath();
7188
- if (existsSync11(statsPath)) {
6849
+ if (existsSync12(statsPath)) {
7189
6850
  try {
7190
6851
  unlinkSync2(statsPath);
7191
6852
  } catch {
@@ -7223,10 +6884,10 @@ async function runLLMAnalysis(input, previous) {
7223
6884
  const config = input.llmConfig?.apiKey ? { ...globalConfig, ...input.llmConfig } : globalConfig;
7224
6885
  const rawDetail = input.aNext.actionDetail.replace(/^terminal-\w+:\s*command=([^,]+).*/, "$1").trim();
7225
6886
  const cacheKey = `${input.aNext.actionType}:${rawDetail || input.aNext.actionDetail}`;
7226
- const cached = llmCache.get(cacheKey);
7227
- if (cached) {
6887
+ const cached2 = llmCache.get(cacheKey);
6888
+ if (cached2) {
7228
6889
  console.log("[AgentMoss] LLM cache hit");
7229
- return cached;
6890
+ return cached2;
7230
6891
  }
7231
6892
  const matchedSkills = matchSkills(input.aNext.actionType, input.aNext.actionDetail, input.reason);
7232
6893
  const templates = loadTemplates();
@@ -7442,8 +7103,8 @@ var init_llm_analyzer = __esm({
7442
7103
  });
7443
7104
 
7444
7105
  // src/brain/rule-store.ts
7445
- import { existsSync as existsSync12, readFileSync as readFileSync12, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
7446
- import { join as join7 } from "path";
7106
+ import { existsSync as existsSync13, readFileSync as readFileSync13, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
7107
+ import { join as join11 } from "path";
7447
7108
  import { homedir as homedir4 } from "os";
7448
7109
  function getRulesPath() {
7449
7110
  const envPath = process.env[ENV_RULES_PATH];
@@ -7462,7 +7123,7 @@ var init_rule_store = __esm({
7462
7123
  "src/brain/rule-store.ts"() {
7463
7124
  "use strict";
7464
7125
  init_esm_shims();
7465
- DEFAULT_RULES_PATH = join7(homedir4(), ".agentmoss", "brain", "rules.json");
7126
+ DEFAULT_RULES_PATH = join11(homedir4(), ".agentmoss", "brain", "rules.json");
7466
7127
  ENV_RULES_PATH = "AGENT_MOSS_BRAIN_RULES_PATH";
7467
7128
  STATUS_DRAFT = "draft";
7468
7129
  STATUS_APPROVED = "approved";
@@ -7479,11 +7140,11 @@ var init_rule_store = __esm({
7479
7140
  this.data = this.load();
7480
7141
  }
7481
7142
  load() {
7482
- if (!existsSync12(this.path)) {
7143
+ if (!existsSync13(this.path)) {
7483
7144
  return { version: 1, rules: [], pattern_candidates: [] };
7484
7145
  }
7485
7146
  try {
7486
- const raw = JSON.parse(readFileSync12(this.path, "utf-8"));
7147
+ const raw = JSON.parse(readFileSync13(this.path, "utf-8"));
7487
7148
  return {
7488
7149
  version: raw.version || 1,
7489
7150
  rules: raw.rules || [],
@@ -8077,9 +7738,9 @@ var init_reviewer = __esm({
8077
7738
  });
8078
7739
 
8079
7740
  // src/brain/skill-manager.ts
8080
- import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync13, renameSync, writeFileSync as writeFileSync4 } from "fs";
7741
+ import { existsSync as existsSync14, mkdirSync as mkdirSync4, readFileSync as readFileSync14, renameSync, writeFileSync as writeFileSync4 } from "fs";
8081
7742
  import { homedir as homedir5 } from "os";
8082
- import { join as join8 } from "path";
7743
+ import { join as join12 } from "path";
8083
7744
  import { randomUUID } from "crypto";
8084
7745
  function getMetaPath(override) {
8085
7746
  return override || process.env[ENV_SKILL_META_PATH] || DEFAULT_SKILL_META_PATH;
@@ -8089,14 +7750,14 @@ var init_skill_manager = __esm({
8089
7750
  "src/brain/skill-manager.ts"() {
8090
7751
  "use strict";
8091
7752
  init_esm_shims();
8092
- DEFAULT_SKILL_META_PATH = join8(homedir5(), ".agentmoss", "brain", "skill_meta.json");
7753
+ DEFAULT_SKILL_META_PATH = join12(homedir5(), ".agentmoss", "brain", "skill_meta.json");
8093
7754
  ENV_SKILL_META_PATH = "AGENT_MOSS_BRAIN_SKILL_META_PATH";
8094
7755
  BrainSkillManager = class {
8095
7756
  constructor(skillsDir, metaPath) {
8096
7757
  this.skillsDir = skillsDir;
8097
7758
  this.metaPath = getMetaPath(metaPath);
8098
7759
  try {
8099
- mkdirSync4(join8(this.metaPath, ".."), { recursive: true });
7760
+ mkdirSync4(join12(this.metaPath, ".."), { recursive: true });
8100
7761
  } catch {
8101
7762
  }
8102
7763
  this.load();
@@ -8106,9 +7767,9 @@ var init_skill_manager = __esm({
8106
7767
  skills = [];
8107
7768
  metaPath;
8108
7769
  load() {
8109
- if (!existsSync13(this.metaPath)) return;
7770
+ if (!existsSync14(this.metaPath)) return;
8110
7771
  try {
8111
- const data = JSON.parse(readFileSync13(this.metaPath, "utf-8"));
7772
+ const data = JSON.parse(readFileSync14(this.metaPath, "utf-8"));
8112
7773
  this.skills = data.skills || [];
8113
7774
  } catch {
8114
7775
  this.skills = [];
@@ -8131,7 +7792,7 @@ var init_skill_manager = __esm({
8131
7792
  let changed = false;
8132
7793
  for (const sk of this.skills) {
8133
7794
  if (sk.status !== "active") continue;
8134
- if (!existsSync13(join8(this.skillsDir, `${sk.name}.md`))) {
7795
+ if (!existsSync14(join12(this.skillsDir, `${sk.name}.md`))) {
8135
7796
  sk.status = "stale";
8136
7797
  changed = true;
8137
7798
  }
@@ -8142,9 +7803,9 @@ var init_skill_manager = __esm({
8142
7803
  addSkill(skill) {
8143
7804
  const id = skill.id || randomUUID();
8144
7805
  const name = skill.name || `brain_${id.slice(0, 8)}`;
8145
- if (!existsSync13(this.skillsDir)) return "";
8146
- const mdPath = join8(this.skillsDir, `${name}.md`);
8147
- if (existsSync13(mdPath)) return "";
7806
+ if (!existsSync14(this.skillsDir)) return "";
7807
+ const mdPath = join12(this.skillsDir, `${name}.md`);
7808
+ if (existsSync14(mdPath)) return "";
8148
7809
  try {
8149
7810
  writeFileSync4(mdPath, skill.content, "utf-8");
8150
7811
  } catch {
@@ -8193,12 +7854,12 @@ var init_skill_manager = __esm({
8193
7854
  const out = [];
8194
7855
  for (const sk of this.skills) {
8195
7856
  if (sk.origin_node !== "local" || sk.status !== "active") continue;
8196
- const mdPath = join8(this.skillsDir, `${sk.name}.md`);
8197
- if (!existsSync13(mdPath)) continue;
7857
+ const mdPath = join12(this.skillsDir, `${sk.name}.md`);
7858
+ if (!existsSync14(mdPath)) continue;
8198
7859
  out.push({
8199
7860
  name: sk.name,
8200
7861
  category: sk.category || "",
8201
- content: readFileSync13(mdPath, "utf-8"),
7862
+ content: readFileSync14(mdPath, "utf-8"),
8202
7863
  keywords: sk.keywords || []
8203
7864
  });
8204
7865
  }
@@ -8486,9 +8147,10 @@ var init_decode = __esm({
8486
8147
  "src/engine/formal-engine/decode.ts"() {
8487
8148
  "use strict";
8488
8149
  init_esm_shims();
8150
+ init_rules_loader();
8489
8151
  B64_TOKEN_RE = /[A-Za-z0-9+/]+={0,2}/g;
8490
8152
  QUOTED_STR_RE = /('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`)/g;
8491
- OUTPUT_CMDS = /* @__PURE__ */ new Set(["echo", "printf", "print"]);
8153
+ OUTPUT_CMDS = new Set(loadFormalRules().output_commands ?? []);
8492
8154
  STAGE_PRIORITY = ["EXFIL", "PRIVESC", "PERSIST", "SCAN", "LISTEN"];
8493
8155
  PURPOSE_EXEC_RE = /subprocess\.\w+\s*\(|os\.system\s*\(|shell\s*=\s*True|\beval\s*\(|\bexec\s*\(|os\.popen\s*\(|\/exec\b/i;
8494
8156
  LISTEN_LEAD_RE = /http\.server|flask\s+run|nohup\b|nc\s+-l|socat\s+TCP-LISTEN/i;
@@ -9206,7 +8868,7 @@ function checkSensitiveDataAggregation(graph) {
9206
8868
  }
9207
8869
  return violations;
9208
8870
  }
9209
- 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;
8871
+ 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;
9210
8872
  var init_properties = __esm({
9211
8873
  "src/engine/formal-engine/properties.ts"() {
9212
8874
  "use strict";
@@ -9223,11 +8885,12 @@ var init_properties = __esm({
9223
8885
  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;
9224
8886
  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;
9225
8887
  INLINE_SHELL_RE = /(?:os\.system|os\.popen)\s*\(\s*['"]([^'"]+)['"]|subprocess\.[a-z_]+\(\s*\[([^\]]+)\]|subprocess\.[a-z_]+\(\s*['"]([^'"]+)['"]|(?:bash|sh|zsh)\s+-c\s*['"]([^'"]+)['"]/gi;
9226
- PKG_MGR = /* @__PURE__ */ new Set(["apt", "apt-get", "aptitude", "yum", "dnf", "pacman", "zypper", "apk", "brew"]);
9227
- SHELLS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "fish", "ksh", "csh", "tcsh", "nologin"]);
8888
+ FORMAL_RULES = loadFormalRules();
8889
+ PKG_MGR = new Set(FORMAL_RULES.privesc?.package_managers ?? []);
8890
+ SHELLS = new Set(FORMAL_RULES.privesc?.bare_shells ?? []);
9228
8891
  TOOL_REGISTRY = null;
9229
8892
  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;
9230
- STRONG_COMPUTE_TYPES = /* @__PURE__ */ new Set(["python", "node", "exec", "terminal", "cmd"]);
8893
+ STRONG_COMPUTE_TYPES = new Set(FORMAL_RULES.compute_action_types ?? []);
9231
8894
  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;
9232
8895
  ALL_PROPERTIES = [
9233
8896
  { id: "cross-session-chain", check: checkCrossSessionChain, shortName: "P1" },
@@ -9751,10 +9414,10 @@ var init_stateful = __esm({
9751
9414
  import * as fs4 from "fs";
9752
9415
  import * as os2 from "os";
9753
9416
  import * as path4 from "path";
9754
- import { createHash } from "crypto";
9417
+ import { createHash as createHash2 } from "crypto";
9755
9418
  function defaultStateDbPath() {
9756
9419
  const dir = process.env.AGENT_MOSS_STATE_DIR || path4.join(os2.homedir(), ".agentmoss");
9757
- const tag = createHash("sha1").update("agentmoss-stateful").digest("hex").slice(0, 8);
9420
+ const tag = createHash2("sha1").update("agentmoss-stateful").digest("hex").slice(0, 8);
9758
9421
  return path4.join(dir, `stateful_chains_${tag}.json`);
9759
9422
  }
9760
9423
  var ChainStateStore;
@@ -10014,7 +9677,7 @@ async function runAnalysis(input) {
10014
9677
  const actionType = input.aNext.actionType.toLowerCase();
10015
9678
  const rawDetail = extractRawCommand(input.aNext.actionDetail);
10016
9679
  const lowerDetail = rawDetail.toLowerCase();
10017
- if (FULLY_SAFE_TOOLS.has(actionType)) {
9680
+ if (getPlatformTraits(input.osType).safeActionTypes.has(actionType)) {
10018
9681
  return {
10019
9682
  allowed: true,
10020
9683
  reason: `\u5B8C\u5168\u5B89\u5168\u5DE5\u5177\uFF0C\u8DF3\u8FC7\u6DF1\u5EA6\u5206\u6790: ${actionType}`,
@@ -10057,7 +9720,7 @@ async function runAnalysis(input) {
10057
9720
  violatedLayers: []
10058
9721
  };
10059
9722
  }
10060
- let skipLLM = READONLY_SENSITIVE_TOOLS.has(actionType);
9723
+ let skipLLM = getPlatformTraits(input.osType).readonlySensitiveActionTypes.has(actionType);
10061
9724
  if (actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash") {
10062
9725
  const firstCmd = extractFirstCommand(lowerDetail);
10063
9726
  const { fullySafeCommands, readonlySensitiveCommands } = getPlatformTraits(input.osType);
@@ -10289,7 +9952,7 @@ async function runAnalysis(input) {
10289
9952
  }
10290
9953
  return llmResult;
10291
9954
  }
10292
- var FULLY_SAFE_TOOLS, TRUSTED_SKILLS_BASE, READONLY_SENSITIVE_TOOLS, _brainStore, _brainExtractor, _brainSkillManager, _brainCurator, _brainInitialized, _brainLastMaintenance, BRAIN_MAINT_INTERVAL_MS, formalEngine;
9955
+ var TRUSTED_SKILLS_BASE, _brainStore, _brainExtractor, _brainSkillManager, _brainCurator, _brainInitialized, _brainLastMaintenance, BRAIN_MAINT_INTERVAL_MS, formalEngine;
10293
9956
  var init_coordinator = __esm({
10294
9957
  "src/engine/coordinator.ts"() {
10295
9958
  "use strict";
@@ -10312,25 +9975,7 @@ var init_coordinator = __esm({
10312
9975
  init_asset_dir();
10313
9976
  init_runtime_config_bridge();
10314
9977
  init_formal_engine();
10315
- FULLY_SAFE_TOOLS = /* @__PURE__ */ new Set([
10316
- "ask_user_question",
10317
- "glob",
10318
- "list_dir",
10319
- "ls",
10320
- "count_text_length",
10321
- "filemgr-globfiles"
10322
- ]);
10323
9978
  TRUSTED_SKILLS_BASE = /* @__PURE__ */ new Set([]);
10324
- READONLY_SENSITIVE_TOOLS = /* @__PURE__ */ new Set([
10325
- "read",
10326
- "file_read",
10327
- "read_file",
10328
- "head",
10329
- "tail",
10330
- "grep",
10331
- "filemgr-readfile",
10332
- "filemgr-grepfiles"
10333
- ]);
10334
9979
  _brainStore = null;
10335
9980
  _brainExtractor = null;
10336
9981
  _brainSkillManager = null;
@@ -10345,10 +9990,150 @@ var init_coordinator = __esm({
10345
9990
  // src/server.ts
10346
9991
  init_esm_shims();
10347
9992
  import { Hono as Hono2 } from "hono";
9993
+
9994
+ // src/infra/rules-contract.ts
9995
+ init_esm_shims();
9996
+ init_paths();
9997
+ init_rules_loader();
9998
+ import { createHash } from "crypto";
9999
+ import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
10000
+ import { join as join3 } from "path";
10001
+ var RULES_SCHEMA_VERSION = 1;
10002
+ var REQUIRED_RULE_FILES = {
10003
+ "l1_rules.json": ["dangerous_commands", "injection_detection", "sensitive_path_access"],
10004
+ "l2_rules.json": ["intent_consistency", "password_modify_consent", "user_deletion_consent"],
10005
+ "l3_rules.json": [],
10006
+ "mc_rules.json": ["rules", "ap_patterns"],
10007
+ "tool_registry.json": ["tools"],
10008
+ "platform_traits.json": ["linux", "windows"],
10009
+ "inline_rules.json": ["read_write_keywords", "dir_traversal_keywords", "write_exec_chain_patterns"],
10010
+ "script_rules.json": [
10011
+ "suspicious_patterns",
10012
+ "keyword_combination_risks",
10013
+ "single_keyword_risks",
10014
+ "script_exec_patterns"
10015
+ ],
10016
+ "heuristic_rules.json": [
10017
+ "sensitive_excluded_prefixes",
10018
+ "search_action_prefixes",
10019
+ "loopback_hosts",
10020
+ "temp_dirs"
10021
+ ],
10022
+ "logic_rules.json": ["indirect_file_access", "write_commands", "source_arg_commands"],
10023
+ "formal_rules.json": ["privesc", "compute_action_types", "pattern_miner", "output_commands"]
10024
+ };
10025
+ var MANIFEST_FILE = "manifest.json";
10026
+ function sha256Short(data, n = 12) {
10027
+ return createHash("sha256").update(data).digest("hex").slice(0, n);
10028
+ }
10029
+ function rulesDir() {
10030
+ if (getRulesRoot()) return join3(getRulesRoot(), "rules");
10031
+ const probe = resolveRulesFile("l1_rules.json");
10032
+ return probe ? probe.slice(0, probe.length - "l1_rules.json".length - 1) : null;
10033
+ }
10034
+ function checkRulesAssets() {
10035
+ const rep = {
10036
+ ok: true,
10037
+ schemaVersion: 0,
10038
+ fingerprint: "",
10039
+ fileHashes: {},
10040
+ errors: [],
10041
+ external: getRulesRoot() !== null
10042
+ };
10043
+ const dir = rulesDir();
10044
+ if (!dir) {
10045
+ 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");
10046
+ rep.ok = false;
10047
+ return rep;
10048
+ }
10049
+ const manifestPath = join3(dir, MANIFEST_FILE);
10050
+ if (!existsSync2(manifestPath)) {
10051
+ rep.errors.push(`\u7F3A\u5C11 ${MANIFEST_FILE}\uFF08\u89C4\u5219\u5305\u5FC5\u987B\u58F0\u660E schema \u7248\u672C\uFF09`);
10052
+ } else {
10053
+ try {
10054
+ const m = JSON.parse(readFileSync3(manifestPath, "utf-8")) || {};
10055
+ const ver = m.schema_version;
10056
+ if (typeof ver !== "number" || !Number.isInteger(ver)) {
10057
+ rep.errors.push(`${MANIFEST_FILE} \u7F3A\u5C11\u6574\u6570 schema_version`);
10058
+ } else {
10059
+ rep.schemaVersion = ver;
10060
+ if (ver !== RULES_SCHEMA_VERSION) {
10061
+ rep.errors.push(
10062
+ `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`
10063
+ );
10064
+ }
10065
+ }
10066
+ } catch (e) {
10067
+ rep.errors.push(`${MANIFEST_FILE} \u8BFB\u53D6/\u89E3\u6790\u5931\u8D25: ${String(e)}`);
10068
+ }
10069
+ }
10070
+ const fpParts = [];
10071
+ for (const [name, requiredKeys] of Object.entries(REQUIRED_RULE_FILES)) {
10072
+ const p = join3(dir, name);
10073
+ if (!existsSync2(p)) {
10074
+ rep.errors.push(`\u7F3A\u5C11\u89C4\u5219\u6587\u4EF6 ${name}`);
10075
+ rep.fileHashes[name] = "MISSING";
10076
+ fpParts.push(`${name}:MISSING`);
10077
+ continue;
10078
+ }
10079
+ let raw = null;
10080
+ try {
10081
+ raw = readFileSync3(p);
10082
+ } catch (e) {
10083
+ rep.errors.push(`${name} \u8BFB\u53D6\u5931\u8D25: ${String(e)}`);
10084
+ rep.fileHashes[name] = "UNREADABLE";
10085
+ fpParts.push(`${name}:UNREADABLE`);
10086
+ continue;
10087
+ }
10088
+ rep.fileHashes[name] = sha256Short(raw);
10089
+ fpParts.push(`${name}:${rep.fileHashes[name]}`);
10090
+ let data = null;
10091
+ try {
10092
+ data = JSON.parse(raw.toString("utf-8")) || {};
10093
+ } catch (e) {
10094
+ rep.errors.push(`${name} \u89E3\u6790\u5931\u8D25: ${String(e)}`);
10095
+ rep.fileHashes[name] = "BADJSON";
10096
+ continue;
10097
+ }
10098
+ if (typeof data !== "object" || Array.isArray(data)) {
10099
+ rep.errors.push(`${name} \u9876\u5C42\u5FC5\u987B\u662F JSON \u5BF9\u8C61`);
10100
+ continue;
10101
+ }
10102
+ const missing = requiredKeys.filter((k) => !(k in data));
10103
+ if (missing.length) rep.errors.push(`${name} \u7F3A\u5C11\u9876\u5C42\u952E: ${missing.join(", ")}`);
10104
+ }
10105
+ rep.fingerprint = sha256Short([...fpParts].sort().join("\n"));
10106
+ rep.ok = rep.errors.length === 0;
10107
+ return rep;
10108
+ }
10109
+ function formatRulesIntegrity(rep) {
10110
+ const src = rep.external ? `\u5916\u90E8\u89C4\u5219\u5305 ${getRulesRoot()}` : "\u5305\u5185\u89C4\u5219";
10111
+ const head = `\u89C4\u5219\u5305: ${src} | schema v${rep.schemaVersion} | \u6307\u7EB9 ${rep.fingerprint || "-"}`;
10112
+ if (rep.ok) return head;
10113
+ return `${head} | \u274C ${rep.errors.length} \u4E2A\u95EE\u9898: ${rep.errors.slice(0, 5).join("\uFF1B")}`;
10114
+ }
10115
+ function enforceRulesContract() {
10116
+ const rep = checkRulesAssets();
10117
+ if (rep.ok) {
10118
+ console.log(`[AgentMoss] ${formatRulesIntegrity(rep)}`);
10119
+ return rep;
10120
+ }
10121
+ const msg = formatRulesIntegrity(rep);
10122
+ if (rep.external) {
10123
+ console.error(`[AgentMoss] \u274C ${msg}`);
10124
+ throw new Error(
10125
+ `\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")}`
10126
+ );
10127
+ }
10128
+ 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`);
10129
+ return rep;
10130
+ }
10131
+
10132
+ // src/server.ts
10348
10133
  import { cors } from "hono/cors";
10349
10134
  import { logger } from "hono/logger";
10350
10135
  import net from "net";
10351
- import { existsSync as existsSync19 } from "fs";
10136
+ import { existsSync as existsSync20 } from "fs";
10352
10137
 
10353
10138
  // src/routes.ts
10354
10139
  init_esm_shims();
@@ -10438,18 +10223,18 @@ var ReviewResponseSchema = z.object({
10438
10223
  // src/engine/analyzer.ts
10439
10224
  init_esm_shims();
10440
10225
  init_coordinator();
10441
- import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync16, writeFileSync as writeFileSync7 } from "fs";
10226
+ import { existsSync as existsSync17, mkdirSync as mkdirSync7, readFileSync as readFileSync17, writeFileSync as writeFileSync7 } from "fs";
10442
10227
  import { tmpdir as tmpdir2 } from "os";
10443
- import { join as join11, resolve as resolve10 } from "path";
10228
+ import { join as join15, resolve as resolve10 } from "path";
10444
10229
 
10445
10230
  // src/infra/policy-cache.ts
10446
10231
  init_esm_shims();
10447
10232
  init_config();
10448
- import { createHash as createHash2 } from "crypto";
10449
- import { existsSync as existsSync15, mkdirSync as mkdirSync6, readdirSync as readdirSync5, readFileSync as readFileSync15, rmdirSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync6 } from "fs";
10233
+ import { createHash as createHash3 } from "crypto";
10234
+ import { existsSync as existsSync16, mkdirSync as mkdirSync6, readdirSync as readdirSync5, readFileSync as readFileSync16, rmdirSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync6 } from "fs";
10450
10235
  import { tmpdir } from "os";
10451
- import { join as join10 } from "path";
10452
- var CACHE_DIR = join10(tmpdir(), "agent_moss_policy_cache");
10236
+ import { join as join14 } from "path";
10237
+ var CACHE_DIR = join14(tmpdir(), "agent_moss_policy_cache");
10453
10238
  var cacheDirOverride = null;
10454
10239
  function cacheDir() {
10455
10240
  return cacheDirOverride ?? CACHE_DIR;
@@ -10465,9 +10250,9 @@ var PolicyCache = class {
10465
10250
  return `${sessionId}::${promptSession}`;
10466
10251
  }
10467
10252
  cacheFilePath(sessionId, promptSession) {
10468
- const promptHash = createHash2("md5").update(promptSession).digest("hex").slice(0, 12);
10253
+ const promptHash = createHash3("md5").update(promptSession).digest("hex").slice(0, 12);
10469
10254
  const safeSessionId = sessionId.replace(/\//g, "_").replace(/\\/g, "_");
10470
- return join10(cacheDir(), safeSessionId, `${promptHash}.toml`);
10255
+ return join14(cacheDir(), safeSessionId, `${promptHash}.toml`);
10471
10256
  }
10472
10257
  /** 取缓存:内存 → 文件;未命中返回 null。 */
10473
10258
  get(sessionId, promptSession) {
@@ -10480,9 +10265,9 @@ var PolicyCache = class {
10480
10265
  return hit;
10481
10266
  }
10482
10267
  const file = this.cacheFilePath(sessionId, promptSession);
10483
- if (existsSync15(file)) {
10268
+ if (existsSync16(file)) {
10484
10269
  try {
10485
- const policy = readFileSync15(file, "utf-8");
10270
+ const policy = readFileSync16(file, "utf-8");
10486
10271
  this.remember(key, policy);
10487
10272
  return policy;
10488
10273
  } catch {
@@ -10496,7 +10281,7 @@ var PolicyCache = class {
10496
10281
  this.remember(this.cacheKey(sessionId, promptSession), policy);
10497
10282
  const file = this.cacheFilePath(sessionId, promptSession);
10498
10283
  try {
10499
- mkdirSync6(join10(cacheDir(), sessionId.replace(/\//g, "_").replace(/\\/g, "_")), { recursive: true });
10284
+ mkdirSync6(join14(cacheDir(), sessionId.replace(/\//g, "_").replace(/\\/g, "_")), { recursive: true });
10500
10285
  writeFileSync6(file, policy, "utf-8");
10501
10286
  } catch {
10502
10287
  }
@@ -10522,12 +10307,12 @@ var PolicyCache = class {
10522
10307
  if (k.startsWith(`${sessionId}::`)) this.memory.delete(k);
10523
10308
  }
10524
10309
  const safeSessionId = sessionId.replace(/\//g, "_").replace(/\\/g, "_");
10525
- const dir = join10(cacheDir(), safeSessionId);
10310
+ const dir = join14(cacheDir(), safeSessionId);
10526
10311
  try {
10527
- if (existsSync15(dir)) {
10312
+ if (existsSync16(dir)) {
10528
10313
  for (const f of readdirSync5(dir)) {
10529
10314
  try {
10530
- unlinkSync3(join10(dir, f));
10315
+ unlinkSync3(join14(dir, f));
10531
10316
  } catch {
10532
10317
  }
10533
10318
  }
@@ -10539,11 +10324,11 @@ var PolicyCache = class {
10539
10324
  clear() {
10540
10325
  this.memory.clear();
10541
10326
  try {
10542
- if (existsSync15(cacheDir())) {
10327
+ if (existsSync16(cacheDir())) {
10543
10328
  for (const sessionDir of readdirSync5(cacheDir())) {
10544
- const dir = join10(cacheDir(), sessionDir);
10329
+ const dir = join14(cacheDir(), sessionDir);
10545
10330
  try {
10546
- for (const f of readdirSync5(dir)) unlinkSync3(join10(dir, f));
10331
+ for (const f of readdirSync5(dir)) unlinkSync3(join14(dir, f));
10547
10332
  rmdirSync(dir);
10548
10333
  } catch {
10549
10334
  }
@@ -10630,7 +10415,7 @@ ${safe(policy, 2e3)}`);
10630
10415
 
10631
10416
  // src/engine/analyzer.ts
10632
10417
  import { parse as parseToml } from "smol-toml";
10633
- var POLICY_OUTPUT_DIR = join11(tmpdir2(), "agent_moss");
10418
+ var POLICY_OUTPUT_DIR = join15(tmpdir2(), "agent_moss");
10634
10419
  var policyOutputDirOverride = null;
10635
10420
  function policyOutputDir() {
10636
10421
  return policyOutputDirOverride ?? POLICY_OUTPUT_DIR;
@@ -10666,7 +10451,7 @@ function savePolicyToFile(sessionId, policyToml) {
10666
10451
  const outDir = policyOutputDir();
10667
10452
  mkdirSync7(outDir, { recursive: true });
10668
10453
  const safeName = sessionId.replace(/\//g, "_").replace(/\\/g, "_");
10669
- const filepath = join11(outDir, `${safeName}.toml`);
10454
+ const filepath = join15(outDir, `${safeName}.toml`);
10670
10455
  writeFileSync7(filepath, policyToml, "utf-8");
10671
10456
  return filepath;
10672
10457
  } catch (err) {
@@ -10705,11 +10490,11 @@ function loadPolicyMappingDoc() {
10705
10490
  const dir = resolveAssetDir(__dirname, "templates");
10706
10491
  if (dir) {
10707
10492
  const p = resolve10(dir, "policy_mapping.md");
10708
- if (existsSync16(p)) return readFileSync16(p, "utf-8");
10493
+ if (existsSync17(p)) return readFileSync17(p, "utf-8");
10709
10494
  }
10710
10495
  for (const cand of assetDirCandidates(__dirname, "templates")) {
10711
10496
  const p = resolve10(cand, "policy_mapping.md");
10712
- if (existsSync16(p)) return readFileSync16(p, "utf-8");
10497
+ if (existsSync17(p)) return readFileSync17(p, "utf-8");
10713
10498
  }
10714
10499
  return "";
10715
10500
  }
@@ -10814,12 +10599,12 @@ async function analyzeInner(input, mossLog) {
10814
10599
  }
10815
10600
  const cache3 = getDefaultCache();
10816
10601
  mossLog.startStep("step2_cache_lookup");
10817
- const cached = cache3.get(input.sessionId, input.promptSession);
10818
- if (cached !== null) {
10602
+ const cached2 = cache3.get(input.sessionId, input.promptSession);
10603
+ if (cached2 !== null) {
10819
10604
  mossLog.endStep("step2_cache_lookup", "\u7F13\u5B58\u547D\u4E2D");
10820
- savePolicyToFile(input.sessionId, cached);
10821
- const res = allowResult(cached, judgment);
10822
- mossLog.logResult("Allow", res.reason, cached);
10605
+ savePolicyToFile(input.sessionId, cached2);
10606
+ const res = allowResult(cached2, judgment);
10607
+ mossLog.logResult("Allow", res.reason, cached2);
10823
10608
  return res;
10824
10609
  }
10825
10610
  if (!isPolicyGenEnabled()) {
@@ -10984,9 +10769,11 @@ init_formal_engine();
10984
10769
 
10985
10770
  // src/engine/formal-engine/pattern_miner.ts
10986
10771
  init_esm_shims();
10772
+ init_rules_loader();
10987
10773
  init_builder();
10988
- var DANGER_STAGES = /* @__PURE__ */ new Set(["PRIVESC", "EXFIL", "LISTEN"]);
10989
- var SENSITIVE_PAIRS = /* @__PURE__ */ new Set(["CRED|LISTEN", "LISTEN|CRED"]);
10774
+ var _PM = loadFormalRules().pattern_miner ?? {};
10775
+ var DANGER_STAGES = new Set(_PM.danger_stages ?? []);
10776
+ var SENSITIVE_PAIRS = new Set((_PM.sensitive_pairs ?? []).map((p) => p.join("|")));
10990
10777
  function projectStages(actionHistory) {
10991
10778
  const stages = [];
10992
10779
  let prev = null;
@@ -11226,7 +11013,7 @@ function normalizeObservablePayload(body) {
11226
11013
  init_rule_store();
11227
11014
  init_skill_manager();
11228
11015
  init_asset_dir();
11229
- import { join as join13, dirname as dirname11 } from "path";
11016
+ import { join as join17, dirname as dirname11 } from "path";
11230
11017
  import { fileURLToPath as fileURLToPath10 } from "url";
11231
11018
  var __dirname9 = dirname11(fileURLToPath10(import.meta.url));
11232
11019
  async function healthRoute(c) {
@@ -11477,7 +11264,7 @@ function getBrainRuleStore() {
11477
11264
  return new BrainRuleStore();
11478
11265
  }
11479
11266
  function getBrainSkillManager() {
11480
- const dir = resolveAssetDir(__dirname9, "skills") ?? join13(__dirname9, "..", "..", "agent_moss", "skills");
11267
+ const dir = resolveAssetDir(__dirname9, "skills") ?? join17(__dirname9, "..", "..", "agent_moss", "skills");
11481
11268
  return new BrainSkillManager(dir);
11482
11269
  }
11483
11270
  async function brainRulesRoute(c) {
@@ -11578,12 +11365,13 @@ init_runtime_config();
11578
11365
  init_token_stats();
11579
11366
  init_rules_loader();
11580
11367
  init_asset_dir();
11368
+ init_rules_mode();
11581
11369
  import { Hono } from "hono";
11582
- import { readFileSync as readFileSync18, existsSync as existsSync18, unlinkSync as unlinkSync4 } from "fs";
11583
- import { join as join14, dirname as dirname12 } from "path";
11370
+ import { readFileSync as readFileSync19, existsSync as existsSync19, unlinkSync as unlinkSync4 } from "fs";
11371
+ import { join as join18, dirname as dirname12 } from "path";
11584
11372
  import { fileURLToPath as fileURLToPath11 } from "url";
11585
11373
  var __dirname10 = dirname12(fileURLToPath11(import.meta.url));
11586
- var STATIC_DIR = join14(__dirname10, "static");
11374
+ var STATIC_DIR = join18(__dirname10, "static");
11587
11375
  var CONSOLE_TOKEN_ENV = "AGENT_MOSS_CONSOLE_TOKEN";
11588
11376
  function getConsoleToken() {
11589
11377
  return process.env[CONSOLE_TOKEN_ENV] || "";
@@ -11603,21 +11391,35 @@ function authMiddleware() {
11603
11391
  await next();
11604
11392
  };
11605
11393
  }
11394
+ function displayRuntimeConfig() {
11395
+ return isRuntimeConfigDisabled() ? generateSourceDefaults() : loadRuntimeConfig();
11396
+ }
11397
+ function builtinOnlyReadOnlyMiddleware() {
11398
+ return async (c, next) => {
11399
+ if (isRuntimeConfigDisabled() && !["GET", "HEAD", "OPTIONS"].includes(c.req.method)) {
11400
+ return c.json({
11401
+ detail: `\u89C4\u5219\u6765\u6E90\u6A21\u5F0F\u4E3A ${getRulesMode()}\uFF08runtime \u914D\u7F6E\u5DF2\u7981\u7528\uFF09\uFF1AConsole \u53EA\u8BFB\uFF0C\u4FEE\u6539\u4E0D\u4F1A\u4FDD\u5B58\u3002\u8BF7\u5207\u6362\u89C4\u5219\u6765\u6E90\u6A21\u5F0F\u5E76\u91CD\u542F\u670D\u52A1\u540E\u518D\u6539\u89C4\u5219\u3002`
11402
+ }, 403);
11403
+ }
11404
+ await next();
11405
+ };
11406
+ }
11606
11407
  function createConsoleRouter() {
11607
11408
  const console2 = new Hono();
11608
11409
  console2.use("/api/*", authMiddleware());
11410
+ console2.use("/api/*", builtinOnlyReadOnlyMiddleware());
11609
11411
  const serveIndex = (c) => {
11610
- const indexFile = join14(STATIC_DIR, "index.html");
11611
- if (existsSync18(indexFile)) {
11612
- return c.html(readFileSync18(indexFile, "utf-8"));
11412
+ const indexFile = join18(STATIC_DIR, "index.html");
11413
+ if (existsSync19(indexFile)) {
11414
+ return c.html(readFileSync19(indexFile, "utf-8"));
11613
11415
  }
11614
11416
  return c.html("<h1>AgentMoss Policy Console</h1><p>Frontend not built. Use /console/api/* endpoints.</p>");
11615
11417
  };
11616
11418
  console2.get("/", serveIndex);
11617
11419
  console2.get("", serveIndex);
11618
11420
  console2.get("/api/layers", (c) => {
11619
- const rc = loadRuntimeConfig();
11620
- return c.json({ layers: rc.layers, env_overrides: getEnvOverrides() });
11421
+ const rc = displayRuntimeConfig();
11422
+ return c.json({ layers: rc.layers, env_overrides: getEnvOverrides(), builtin_only: isBuiltinOnly(), rules_mode: getRulesMode() });
11621
11423
  });
11622
11424
  console2.put("/api/layers", async (c) => {
11623
11425
  const body = await c.req.json();
@@ -11652,7 +11454,7 @@ function createConsoleRouter() {
11652
11454
  const rules = mc.rules || [];
11653
11455
  let catEnabled = true;
11654
11456
  try {
11655
- const rt = loadRuntimeConfig();
11457
+ const rt = displayRuntimeConfig();
11656
11458
  catEnabled = rt.L2_2?.ltl_rules?.category_enabled !== false;
11657
11459
  } catch {
11658
11460
  }
@@ -11708,7 +11510,7 @@ function createConsoleRouter() {
11708
11510
  return rc;
11709
11511
  }
11710
11512
  console2.get("/api/rules", (c) => {
11711
- const rc = loadRuntimeConfig();
11513
+ const rc = displayRuntimeConfig();
11712
11514
  const layer = c.req.query("layer");
11713
11515
  const category = c.req.query("category");
11714
11516
  const out = {};
@@ -11773,8 +11575,8 @@ function createConsoleRouter() {
11773
11575
  return c.json(withL22View(rc));
11774
11576
  });
11775
11577
  console2.get("/api/skills", (c) => {
11776
- const rc = loadRuntimeConfig();
11777
- return c.json(rc.L3_skills);
11578
+ const rc = displayRuntimeConfig();
11579
+ return c.json(rc.L3_skills || {});
11778
11580
  });
11779
11581
  console2.put("/api/skills/enabled", async (c) => {
11780
11582
  const b = await c.req.json();
@@ -11804,18 +11606,21 @@ function createConsoleRouter() {
11804
11606
  if (!/^[A-Za-z0-9_-]+$/.test(skillId)) {
11805
11607
  return c.json({ detail: "\u975E\u6CD5 skill id" }, 400);
11806
11608
  }
11807
- const userPath = join14(dirname12(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
11609
+ const userPath = join18(dirname12(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
11808
11610
  const builtinDir = resolveAssetDir(__dirname10, "skills");
11809
- const candidates = [userPath, builtinDir ? join14(builtinDir, `${skillId}.md`) : ""].filter(Boolean);
11611
+ const candidates = [
11612
+ ...isRuntimeConfigDisabled() ? [] : [userPath],
11613
+ builtinDir ? join18(builtinDir, `${skillId}.md`) : ""
11614
+ ].filter(Boolean);
11810
11615
  for (const p of candidates) {
11811
- if (existsSync18(p)) {
11812
- return c.json({ skill_id: skillId, path: p, content: readFileSync18(p, "utf-8") });
11616
+ if (existsSync19(p)) {
11617
+ return c.json({ skill_id: skillId, path: p, content: readFileSync19(p, "utf-8") });
11813
11618
  }
11814
11619
  }
11815
11620
  return c.json({ detail: `skill ${skillId} \u4E0D\u5B58\u5728` }, 404);
11816
11621
  });
11817
11622
  console2.get("/api/agents", (c) => {
11818
- const rc = loadRuntimeConfig();
11623
+ const rc = displayRuntimeConfig();
11819
11624
  return c.json({ agents: getAgentRules(rc) });
11820
11625
  });
11821
11626
  console2.put("/api/agents/rules/enabled", async (c) => {
@@ -11854,7 +11659,7 @@ function createConsoleRouter() {
11854
11659
  });
11855
11660
  const enabledScenarioIds = () => (process.env.AGENT_MOSS_SCENARIO || "").split(",").map((s) => s.trim()).filter(Boolean);
11856
11661
  console2.get("/api/scenarios", (c) => {
11857
- const all = getScenarioRules(loadRuntimeConfig());
11662
+ const all = getScenarioRules(displayRuntimeConfig());
11858
11663
  const enabled = enabledScenarioIds();
11859
11664
  if (!enabled.length) return c.json({ scenarios: {}, enabled: [] });
11860
11665
  const shown = {};
@@ -11896,8 +11701,8 @@ function createConsoleRouter() {
11896
11701
  return c.json({ scenarios: getScenarioRules(rc), enabled: enabledScenarioIds() });
11897
11702
  });
11898
11703
  console2.get("/api/config", (c) => {
11899
- const rc = loadRuntimeConfig();
11900
- return c.json({ ...rc, L2_2: getL22View() });
11704
+ const rc = displayRuntimeConfig();
11705
+ return c.json({ ...rc, L2_2: getL22View(), builtin_only: isBuiltinOnly(), rules_mode: getRulesMode() });
11901
11706
  });
11902
11707
  console2.get("/api/env-overrides", (c) => {
11903
11708
  return c.json({ overrides: getEnvOverrides() });
@@ -11906,7 +11711,7 @@ function createConsoleRouter() {
11906
11711
  const defaults = generateSourceDefaults();
11907
11712
  try {
11908
11713
  const p = getRuntimeConfigPath();
11909
- if (existsSync18(p)) unlinkSync4(p);
11714
+ if (existsSync19(p)) unlinkSync4(p);
11910
11715
  } catch {
11911
11716
  }
11912
11717
  return c.json(withL22View(defaults));
@@ -11935,37 +11740,58 @@ function createConsoleRouter() {
11935
11740
 
11936
11741
  // src/server.ts
11937
11742
  init_runtime_config();
11743
+ init_rules_mode();
11938
11744
  init_rules_loader();
11939
11745
  function logDeploymentSelfCheck() {
11940
11746
  try {
11747
+ const mode = getRulesMode();
11748
+ const builtinOnly = isBuiltinOnly();
11749
+ const runtimeDisabled = isRuntimeConfigDisabled();
11941
11750
  console.log("================ AgentMoss \u90E8\u7F72\u81EA\u68C0 ================");
11942
- console.log(`runtime \u914D\u7F6E: ${getRuntimeConfigPath()}`);
11943
- const layerKeys = ["L1_heuristic", "L2_logic_rules", "L3_llm_analysis", "agent_rules_enabled", "scenario_rules_enabled"];
11944
- console.log("\u5C42\u7EA7\u5F00\u5173: " + layerKeys.map((k) => `${k}=${isLayerEnabled(k) ? "on" : "OFF"}`).join(" "));
11945
- try {
11946
- const dirs = rulesDirs().filter((d) => existsSync19(d));
11947
- if (dirs.length > 0) {
11948
- console.log(`\u89C4\u5219\u76EE\u5F55: ${dirs.join(", ")}\uFF08\u53EA\u8BFB\u5176\u4E2D ${OVERRIDE_FILENAME} / ${EXTRA_FILENAME}\uFF09`);
11949
- }
11950
- const loaded = ruleFilesLoaded();
11951
- if (loaded.length > 0) {
11952
- const ov = loadOverrideRules();
11953
- const ex = loadExtraRules();
11954
- console.error(`\u26A0\uFE0F \u5DF2\u4ECE\u89C4\u5219\u76EE\u5F55\u52A0\u8F7D ${loaded.length} \u4E2A\u6587\u4EF6\uFF08\u6574\u4EFD\u66FF\u6362 / \u8FFD\u52A0\uFF0C\u5185\u7F6E\u66F4\u65B0\u4E0D\u4F1A\u53CD\u6620\u5230\u5B83\u4EEC\u4E0A\u9762\uFF09:`);
11955
- for (const f of loaded) console.error(` ${f}`);
11956
- const ovs = Object.entries(ov).sort().map(([k, v]) => `${k}\xD7${v.length}`).join(", ");
11957
- if (ovs) console.error(` \u8986\u76D6\u7C7B\u522B: ${ovs}\uFF08**\u6574\u4EFD\u66FF\u6362**\uFF1A\u672A\u5217\u51FA\u7684\u5185\u7F6E\u89C4\u5219\u4E0D\u518D\u751F\u6548\uFF09`);
11958
- const exs = Object.entries(ex).sort().map(([k, v]) => `${k}\xD7${v.length}`).join(", ");
11959
- if (exs) console.log(` \u8FFD\u52A0\u7C7B\u522B: ${exs}\uFF08\u5185\u7F6E\u4FDD\u7559\uFF0C\u8FFD\u52A0\u5728\u5176\u540E\u4E00\u8D77\u5224\u5B9A\uFF09`);
11960
- }
11961
- const ignored = ignoredRuleFiles();
11962
- if (ignored.length > 0) {
11751
+ if (runtimeDisabled) {
11752
+ console.log(`runtime \u914D\u7F6E: ${getRuntimeConfigPath()}\uFF08**\u5DF2\u5FFD\u7565**\uFF1A\u89C4\u5219\u6765\u6E90\u6A21\u5F0F=${mode}\uFF09`);
11753
+ if (builtinOnly) {
11754
+ console.error(
11755
+ "\u89C4\u5219\u6765\u6E90: **\u4EC5\u5185\u7F6E** \u2014\u2014 \u4E0D\u8BFB runtime \u914D\u7F6E / Console \u81EA\u5B9A\u4E49 / \u89C4\u5219\u76EE\u5F55 / AGENT_MOSS_CUSTOM_RULES\uFF1B\u4E0D seed\u3001\u4E0D merge\u3001\u4E0D\u5199\u56DE\u3002"
11756
+ );
11757
+ } else {
11963
11758
  console.error(
11964
- `\u26A0\uFE0F \u89C4\u5219\u76EE\u5F55\u91CC\u6709 ${ignored.length} \u4E2A json **\u4E0D\u4F1A\u88AB\u8BFB\u53D6**\uFF08\u53EA\u8BA4 ${OVERRIDE_FILENAME} / ${EXTRA_FILENAME}\uFF09:`
11759
+ "\u89C4\u5219\u6765\u6E90: **\u51B7\u542F\u52A8\uFF08cold\uFF09** \u2014\u2014 \u542F\u52A8\u65F6\u8BFB\u53D6\u5185\u7F6E\u89C4\u5219 + \u89C4\u5219\u76EE\u5F55 / AGENT_MOSS_CUSTOM_RULES / \u573A\u666F\uFF0C\u4E4B\u540E\u51BB\u7ED3\uFF1B\u4E0D\u8BFB runtime \u914D\u7F6E / Console\uFF0C\u4E0D seed\u3001\u4E0D merge\u3001\u4E0D\u5199\u56DE\u3002"
11965
11760
  );
11966
- for (const f of ignored) console.error(` ${f}`);
11761
+ console.error("\u89C4\u5219\u76EE\u5F55\u5185\u5BB9\u6539\u52A8\u9700\u91CD\u542F\u670D\u52A1\u624D\u751F\u6548\u3002");
11762
+ }
11763
+ console.error("Console: \u53EA\u8BFB\uFF08\u89C4\u5219\u5199\u63A5\u53E3\u8FD4\u56DE 403\uFF09\uFF1B\u4FEE\u6539\u89C4\u5219\u9700\u91CD\u542F\u670D\u52A1\u5E76\u5207\u6362\u89C4\u5219\u6765\u6E90\u6A21\u5F0F\u3002");
11764
+ } else {
11765
+ console.log(`runtime \u914D\u7F6E: ${getRuntimeConfigPath()}`);
11766
+ }
11767
+ const layerKeys = ["L1_heuristic", "L2_logic_rules", "L3_llm_analysis", "agent_rules_enabled", "scenario_rules_enabled"];
11768
+ console.log("\u5C42\u7EA7\u5F00\u5173: " + layerKeys.map((k) => `${k}=${isLayerEnabled(k) ? "on" : "OFF"}`).join(" "));
11769
+ if (!builtinOnly) {
11770
+ try {
11771
+ const dirs = rulesDirs().filter((d) => existsSync20(d));
11772
+ if (dirs.length > 0) {
11773
+ console.log(`\u89C4\u5219\u76EE\u5F55: ${dirs.join(", ")}\uFF08\u53EA\u8BFB\u5176\u4E2D ${OVERRIDE_FILENAME} / ${EXTRA_FILENAME}\uFF09`);
11774
+ }
11775
+ const loaded = ruleFilesLoaded();
11776
+ if (loaded.length > 0) {
11777
+ const ov = loadOverrideRules();
11778
+ const ex = loadExtraRules();
11779
+ console.error(`\u26A0\uFE0F \u5DF2\u4ECE\u89C4\u5219\u76EE\u5F55\u52A0\u8F7D ${loaded.length} \u4E2A\u6587\u4EF6\uFF08\u6574\u4EFD\u66FF\u6362 / \u8FFD\u52A0\uFF0C\u5185\u7F6E\u66F4\u65B0\u4E0D\u4F1A\u53CD\u6620\u5230\u5B83\u4EEC\u4E0A\u9762\uFF09:`);
11780
+ for (const f of loaded) console.error(` ${f}`);
11781
+ const ovs = Object.entries(ov).sort().map(([k, v]) => `${k}\xD7${v.length}`).join(", ");
11782
+ if (ovs) console.error(` \u8986\u76D6\u7C7B\u522B: ${ovs}\uFF08**\u6574\u4EFD\u66FF\u6362**\uFF1A\u672A\u5217\u51FA\u7684\u5185\u7F6E\u89C4\u5219\u4E0D\u518D\u751F\u6548\uFF09`);
11783
+ const exs = Object.entries(ex).sort().map(([k, v]) => `${k}\xD7${v.length}`).join(", ");
11784
+ if (exs) console.log(` \u8FFD\u52A0\u7C7B\u522B: ${exs}\uFF08\u5185\u7F6E\u4FDD\u7559\uFF0C\u8FFD\u52A0\u5728\u5176\u540E\u4E00\u8D77\u5224\u5B9A\uFF09`);
11785
+ }
11786
+ const ignored = ignoredRuleFiles();
11787
+ if (ignored.length > 0) {
11788
+ console.error(
11789
+ `\u26A0\uFE0F \u89C4\u5219\u76EE\u5F55\u91CC\u6709 ${ignored.length} \u4E2A json **\u4E0D\u4F1A\u88AB\u8BFB\u53D6**\uFF08\u53EA\u8BA4 ${OVERRIDE_FILENAME} / ${EXTRA_FILENAME}\uFF09:`
11790
+ );
11791
+ for (const f of ignored) console.error(` ${f}`);
11792
+ }
11793
+ } catch {
11967
11794
  }
11968
- } catch {
11969
11795
  }
11970
11796
  const scenarioRaw = (process.env.AGENT_MOSS_SCENARIO || "").trim();
11971
11797
  if (scenarioRaw) {
@@ -11981,6 +11807,7 @@ function logDeploymentSelfCheck() {
11981
11807
  }
11982
11808
  }
11983
11809
  function createApp() {
11810
+ enforceRulesContract();
11984
11811
  const app = new Hono2();
11985
11812
  app.use("*", cors({ origin: "*", allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], allowHeaders: ["*"] }));
11986
11813
  app.use("*", logger());
@@ -12075,7 +11902,7 @@ async function startUnixSocketServer(socketPath = "/var/run/agent_moss/agent_mos
12075
11902
  server = null;
12076
11903
  },
12077
11904
  serve: async () => {
12078
- const { mkdirSync: mkdirSync8, existsSync: existsSync20, unlinkSync: unlinkSync5 } = await import("fs");
11905
+ const { mkdirSync: mkdirSync8, existsSync: existsSync21, unlinkSync: unlinkSync5 } = await import("fs");
12079
11906
  const { dirname: dirname13 } = await import("path");
12080
11907
  const dir = dirname13(socketPath);
12081
11908
  if (dir) {
@@ -12084,7 +11911,7 @@ async function startUnixSocketServer(socketPath = "/var/run/agent_moss/agent_mos
12084
11911
  } catch {
12085
11912
  }
12086
11913
  }
12087
- if (existsSync20(socketPath)) {
11914
+ if (existsSync21(socketPath)) {
12088
11915
  const { connect } = await import("net");
12089
11916
  const inUse = await new Promise((resolve11) => {
12090
11917
  const probe = connect(socketPath);