@kenhkl/agent-moss 0.16.13 → 0.16.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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,295 +106,7 @@ 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");
105
- }
106
- });
107
-
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));
138
- }
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;
163
- }
164
- }
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");
184
- }
185
- function hasWriteExecChain(command) {
186
- return WRITE_EXEC_CHAIN_PATTERNS.some((p) => {
187
- p.lastIndex = 0;
188
- return p.test(command);
189
- });
190
- }
191
- function hasDirTraversal(code) {
192
- const lower = code.toLowerCase();
193
- return DIR_TRAVERSAL_KEYWORDS.some((kw) => lower.includes(kw));
194
- }
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
- };
244
- }
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"() {
248
- "use strict";
249
- 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
- ];
381
- }
382
- });
383
-
384
- // src/infra/runtime-config-bridge.ts
385
- function registerRuntimeConfigBridge(bridge) {
386
- runtimeConfigBridge.current = bridge;
387
- }
388
- var runtimeConfigBridge;
389
- var init_runtime_config_bridge = __esm({
390
- "src/infra/runtime-config-bridge.ts"() {
391
- "use strict";
392
- init_esm_shims();
393
- runtimeConfigBridge = { current: null };
109
+ ENV_RULES_ROOT = "AGENT_MOSS_RULES_ROOT";
394
110
  }
395
111
  });
396
112
 
@@ -440,6 +156,19 @@ var init_rules_mode = __esm({
440
156
  }
441
157
  });
442
158
 
159
+ // src/infra/runtime-config-bridge.ts
160
+ function registerRuntimeConfigBridge(bridge) {
161
+ runtimeConfigBridge.current = bridge;
162
+ }
163
+ var runtimeConfigBridge;
164
+ var init_runtime_config_bridge = __esm({
165
+ "src/infra/runtime-config-bridge.ts"() {
166
+ "use strict";
167
+ init_esm_shims();
168
+ runtimeConfigBridge = { current: null };
169
+ }
170
+ });
171
+
443
172
  // src/infra/rules-loader.ts
444
173
  import { readFileSync as readFileSync2, existsSync, readdirSync, statSync } from "fs";
445
174
  import { resolve, dirname, join as join2 } from "path";
@@ -459,13 +188,34 @@ function rulesDirs() {
459
188
  dirs.push(resolve(dataRootDir(), "rules"));
460
189
  return [...new Set(dirs)];
461
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
+ }
462
209
  function findRulesFile(name) {
463
- for (const dir of RULES_DIR_CANDS) {
210
+ for (const dir of rulesDirCands()) {
464
211
  const p = resolve(dir, name);
465
212
  if (existsSync(p)) return p;
466
213
  }
467
214
  return null;
468
215
  }
216
+ function resolveRulesFile(name) {
217
+ return findRulesFile(name);
218
+ }
469
219
  function loadJson(name) {
470
220
  if (!isHotReloadEnabled()) {
471
221
  const cached2 = frozenJsonCache.get(name);
@@ -516,6 +266,74 @@ function loadL1() {
516
266
  function loadL2() {
517
267
  return loadJson("l2_rules.json");
518
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
+ }
519
337
  function loadL3() {
520
338
  const data = { ...loadJson("l3_rules.json") };
521
339
  try {
@@ -850,7 +668,7 @@ function ruleFilesLoaded() {
850
668
  function ignoredRuleFiles() {
851
669
  return rulesCached().ignored;
852
670
  }
853
- var __filename2, __dirname2, OVERRIDE_FILENAME, EXTRA_FILENAME, RULES_DIR_ENVS, RULES_DIR_CANDS, frozenJsonCache, SCENARIO_LIST_KEYS, EXTRA_CATEGORIES, STRUCT_KEYS, EXTRA_FLAT_FIELD, _RULES_CACHE, warnedIgnored;
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;
854
672
  var init_rules_loader = __esm({
855
673
  "src/infra/rules-loader.ts"() {
856
674
  "use strict";
@@ -863,24 +681,20 @@ var init_rules_loader = __esm({
863
681
  OVERRIDE_FILENAME = "override.json";
864
682
  EXTRA_FILENAME = "extra.json";
865
683
  RULES_DIR_ENVS = ["AGENT_MOSS_EXTRA_RULES_DIR", "AGENT_MOSS_OVERRIDE_RULES_DIR"];
866
- RULES_DIR_CANDS = (() => {
867
- const cands = [
868
- // 注:`<data_root>/rules/` 不再是"按内置文件名覆盖"的查找位 —— 2026-10 起
869
- // 规则目录只认 override.json / extra.json(见 rulesDirs())。
870
- // dist: __dirname=dist/ → dist/rules;__dirname=dist/engine/ → dist/engine/../rules=dist/rules
871
- resolve(__dirname2, "../rules"),
872
- resolve(__dirname2, "rules"),
873
- // dev src: __dirname=ts/src/infra/ → ts/src/infra/../../../agent_moss/rules = 仓库根 agent_moss/rules
874
- resolve(__dirname2, "../../../agent_moss/rules"),
875
- // dist 但 __dirname 指向 dist/engine 再往上两级的仓库根 agent_moss/rules
876
- resolve(__dirname2, "../../../../agent_moss/rules"),
877
- // 相对当前工作目录的仓库根(兜底,兼容以仓库根为 cwd 运行)
878
- resolve(process.cwd(), "agent_moss/rules")
879
- ];
880
- return cands.filter((c) => existsSync(c));
881
- })();
882
684
  frozenJsonCache = /* @__PURE__ */ new Map();
883
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";
884
698
  EXTRA_CATEGORIES = {
885
699
  dangerous_commands: ["pattern"],
886
700
  injection_detection: ["keyword", "lang"],
@@ -902,7 +716,199 @@ var init_rules_loader = __esm({
902
716
  }
903
717
  });
904
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
+
905
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
+ }
906
912
  function getPlatformTraits(osType) {
907
913
  const accepted = acceptedOsValues(osType);
908
914
  if (accepted.includes("windows")) return WINDOWS_TRAITS;
@@ -910,368 +916,18 @@ function getPlatformTraits(osType) {
910
916
  if (accepted.includes("harmony")) return HARMONY_TRAITS;
911
917
  return LINUX_TRAITS;
912
918
  }
913
- var LINUX_TRAITS, WINDOWS_TRAITS, MACOS_TRAITS, HARMONY_TRAITS;
919
+ var traitsCache, LINUX_TRAITS, WINDOWS_TRAITS, MACOS_TRAITS, HARMONY_TRAITS;
914
920
  var init_platform_traits = __esm({
915
921
  "src/engine/platform-traits.ts"() {
916
922
  "use strict";
917
923
  init_esm_shims();
918
924
  init_logic_rules();
919
- LINUX_TRAITS = {
920
- writeKeywords: [
921
- "write",
922
- "\u5199\u5165",
923
- "save",
924
- "\u4FDD\u5B58",
925
- "modify",
926
- "\u4FEE\u6539",
927
- "update",
928
- "\u66F4\u65B0",
929
- "delete",
930
- "\u5220\u9664",
931
- "remove",
932
- "\u79FB\u9664",
933
- "create",
934
- "\u521B\u5EFA",
935
- "append",
936
- "\u8FFD\u52A0",
937
- "truncate",
938
- "\u622A\u65AD",
939
- "overwrite",
940
- "\u8986\u76D6",
941
- "replace",
942
- "\u66FF\u6362",
943
- "edit"
944
- ],
945
- readKeywords: [
946
- "read",
947
- "\u8BFB\u53D6",
948
- "cat",
949
- "head",
950
- "tail",
951
- "less",
952
- "more",
953
- "view",
954
- "\u67E5\u770B",
955
- "open",
956
- "\u6253\u5F00",
957
- "load",
958
- "\u52A0\u8F7D",
959
- "grep",
960
- "search",
961
- "\u641C\u7D22",
962
- "find",
963
- "\u67E5\u627E"
964
- ],
965
- writeCommandRes: [
966
- /\brm\b/,
967
- /\bunlink\b/,
968
- /\brmdir\b/,
969
- /\bshred\b/,
970
- /\bcp\b/,
971
- /\bmv\b/,
972
- /\binstall\b/,
973
- /\btee\b/,
974
- /\bdd\b/,
975
- /\bmkfs\b/,
976
- /\b(fdisk|parted|cfdisk|sfdisk)\b/,
977
- /\bchmod\b/,
978
- /\bchown\b/,
979
- /\btruncate\b/
980
- ],
981
- readOnlyCommands: /* @__PURE__ */ new Set([
982
- "cat",
983
- "head",
984
- "tail",
985
- "less",
986
- "more",
987
- "grep",
988
- "find",
989
- "awk",
990
- "sed",
991
- "sort",
992
- "uniq",
993
- "wc",
994
- "cut",
995
- "strings",
996
- "od",
997
- "xxd",
998
- "hexdump",
999
- "tr",
1000
- "file",
1001
- "stat",
1002
- "du",
1003
- "df",
1004
- "ls",
1005
- "dir",
1006
- "tree",
1007
- "nl",
1008
- "tac",
1009
- "rev",
1010
- "echo",
1011
- "printf",
1012
- "lsblk",
1013
- "blockdev",
1014
- "smartctl",
1015
- "udevadm",
1016
- "dmidecode",
1017
- "lscpu",
1018
- "lspci",
1019
- "lsusb",
1020
- "lsmem",
1021
- "lsns",
1022
- "lsof",
1023
- "hwinfo",
1024
- "inxi",
1025
- "fdisk",
1026
- "parted",
1027
- "dmesg",
1028
- "journalctl",
1029
- "systemctl",
1030
- "hostnamectl",
1031
- "localectl",
1032
- "timedatectl",
1033
- "ps",
1034
- "top",
1035
- "free",
1036
- "vmstat",
1037
- "iostat",
1038
- "mpstat",
1039
- "pidof",
1040
- "pgrep",
1041
- "ip",
1042
- "ifconfig",
1043
- "route",
1044
- "ss",
1045
- "netstat",
1046
- "arp",
1047
- "ethtool",
1048
- "nmcli",
1049
- "uname",
1050
- "arch",
1051
- "nproc",
1052
- "getconf",
1053
- "getent",
1054
- "id",
1055
- "whoami",
1056
- "who",
1057
- "w",
1058
- "last",
1059
- "uptime",
1060
- "lsmod",
1061
- "modinfo",
1062
- "sysctl"
1063
- ]),
1064
- fullySafeCommands: [
1065
- /^ls(?:\s|$)/i,
1066
- /^dir(?:\s|$)/i,
1067
- /^pwd(?:\s|$)/i,
1068
- /^which\s+/i,
1069
- /^whereis\s+/i,
1070
- /^realpath\s+/i,
1071
- /^basename\s+/i,
1072
- /^dirname\s+/i,
1073
- /^file\s+/i,
1074
- /^stat\s+/i,
1075
- /^du(?:\s|$)/i,
1076
- /^echo\s+/i,
1077
- /^printf\s+/i,
1078
- /^type\s+/i,
1079
- /^command\s+/i,
1080
- // 系统信息查询(只读,不涉及文件/网络/权限)
1081
- /^whoami(?:\s|$)/i,
1082
- /^id(?:\s|$)/i,
1083
- /^hostname(?:\s|$)/i,
1084
- /^uname(?:\s|$)/i,
1085
- /^date(?:\s|$)/i,
1086
- /^env(?:\s|$)/i,
1087
- /^printenv(?:\s|$)/i,
1088
- /^tty(?:\s|$)/i,
1089
- /^arch(?:\s|$)/i,
1090
- /^uptime(?:\s|$)/i,
1091
- /^groups(?:\s|$)/i,
1092
- /^logname(?:\s|$)/i,
1093
- // 系统资源/硬件信息(只读,不读文件内容、无状态变更选项)
1094
- // 与上面 uptime/uname/id 同性质;缺了它们时开 L3 的部署每查一次磁盘/内存
1095
- // 都要走 LLM(实测 df -h 从 5ms 变成十几秒)。
1096
- /^df(?:\s|$)/i,
1097
- /^free(?:\s|$)/i,
1098
- /^vmstat(?:\s|$)/i,
1099
- /^nproc(?:\s|$)/i,
1100
- /^lscpu(?:\s|$)/i,
1101
- /^lsblk(?:\s|$)/i,
1102
- /^lspci(?:\s|$)/i,
1103
- /^lsusb(?:\s|$)/i,
1104
- /^lsmod(?:\s|$)/i
1105
- ],
1106
- readonlySensitiveCommands: [
1107
- /^cat\s+/i,
1108
- /^head(?:\s|$)/i,
1109
- /^tail(?:\s|$)/i,
1110
- /^less(?:\s|$)/i,
1111
- /^more(?:\s|$)/i,
1112
- /^wc(?:\s|$)/i,
1113
- /^grep(?:\s|$)/i,
1114
- /^find\s+/i,
1115
- /^ag(?:\s|$)/i,
1116
- /^rg(?:\s|$)/i,
1117
- /^awk\s+/i,
1118
- // NEW-FN-P(M03):sed -i/--in-place 是原地写,挡出白名单走 L2 read_before_write。
1119
- // 前瞻放最前(外层 ^sed\s+ 会消费 -i 前的空格)。与 Python profiles/linux.py 对齐。
1120
- /^(?!.*(?:\s|['"])-[a-zA-Z]*i[a-zA-Z]*\b)(?!.*(?:\s|['"])--in-place)sed\s+/i,
1121
- /^cut\s+/i,
1122
- /^sort(?:\s|$)/i,
1123
- /^uniq(?:\s|$)/i,
1124
- // 进程/网络/打开文件信息:只读,但会暴露进程命令行、连接对端、打开的文件
1125
- // 路径,故只跳 L3、保留 L2(L1 敏感路径检测照常生效)。
1126
- /^ps(?:\s|$)/i,
1127
- /^ss(?:\s|$)/i,
1128
- /^netstat(?:\s|$)/i,
1129
- /^lsof(?:\s|$)/i,
1130
- // 只读子命令级白名单:匹配的是「第一段命令」而非首词(extractFirstCommand
1131
- // 按 | && || 切分),所以能精确到子命令。systemctl stop/disable 等不在此列。
1132
- /^systemctl\s+(?:status|is-active|is-enabled|is-failed|list-units|list-unit-files|list-dependencies|show|cat)\b/i,
1133
- /^service\s+\S+\s+status\b/i,
1134
- // journalctl 整族是只读的,但 --vacuum-*/--rotate/--flush/--sync 会删改日志,
1135
- // 用否定前瞻挡掉后再放行。注意前瞻里必须用 .* 扫描**整段**:只写 (?!\s+--...)
1136
- // 的话只看紧跟 journalctl 的第一个参数,
1137
- // `journalctl -u sshd --vacuum-size=100M` 就漏过去了。
1138
- /^journalctl\b(?!.*--(?:vacuum-size|vacuum-time|vacuum-files|rotate|flush|sync)\b)/i
1139
- ]
1140
- };
1141
- WINDOWS_TRAITS = {
1142
- writeKeywords: [
1143
- "write",
1144
- "\u5199\u5165",
1145
- "save",
1146
- "\u4FDD\u5B58",
1147
- "modify",
1148
- "\u4FEE\u6539",
1149
- "update",
1150
- "\u66F4\u65B0",
1151
- "delete",
1152
- "\u5220\u9664",
1153
- "remove",
1154
- "\u79FB\u9664",
1155
- "create",
1156
- "\u521B\u5EFA",
1157
- "append",
1158
- "\u8FFD\u52A0",
1159
- "overwrite",
1160
- "\u8986\u76D6",
1161
- "replace",
1162
- "\u66FF\u6362",
1163
- "set",
1164
- "\u8BBE\u7F6E"
1165
- // 注意:这里**不能放 `reg` / `注册表`**。关键词是子串包含匹配,放进去等于
1166
- // 「只要命令里出现 reg 字样就算写操作」——`reg query`(读)会被判成写,
1167
- // 进而误拦对 deny_write 路径的只读查询。注册表的写判定交给下面的
1168
- // writeCommandRes(只认写子命令)。两端语义必须一致。
1169
- ],
1170
- readKeywords: [
1171
- "read",
1172
- "\u8BFB\u53D6",
1173
- "type",
1174
- "findstr",
1175
- "dir",
1176
- "view",
1177
- "\u67E5\u770B",
1178
- "open",
1179
- "\u6253\u5F00",
1180
- "load",
1181
- "\u52A0\u8F7D",
1182
- "search",
1183
- "\u641C\u7D22",
1184
- "find",
1185
- "\u67E5\u627E",
1186
- "get",
1187
- "\u83B7\u53D6"
1188
- ],
1189
- // Windows 写/删命令:del/copy/move/xcopy/replace/rmdir/icacls/reg delete 等
1190
- writeCommandRes: [
1191
- /\b(del|erase)\b/,
1192
- /\brmdir\b/,
1193
- /\b(rd)\b/,
1194
- /\bcopy\b/,
1195
- /\b(xcopy|robocopy)\b/,
1196
- /\b(move|movefile)\b/,
1197
- /\breplace\b/,
1198
- /\bicacls\b/,
1199
- /\battrib\b/,
1200
- // 注册表:**只认写子命令**(对齐 Python WindowsProfile.write_command_res)。
1201
- // 此前是 /\breg\b/,把 reg query(读)也判成写 → 误拦对 deny_write 路径的只读查询。
1202
- // 写子命令 add/delete/copy/save/restore/import/load/unload;
1203
- // 读子命令 query/compare/export/flags/help 不在此列。
1204
- /\breg(?:\.exe)?\s+(?:add|delete|copy|save|restore|import|load|unload)\b/,
1205
- /\bformat\b/,
1206
- /\bdiskpart\b/,
1207
- /\bmkdir\b/,
1208
- /\bmd\b/,
1209
- /\bsetx\b/,
1210
- /\bwmic\b/
1211
- ],
1212
- // Windows 只读命令(重定向写判定用):type/findstr/more/tree/dir 等
1213
- readOnlyCommands: /* @__PURE__ */ new Set([
1214
- "type",
1215
- "findstr",
1216
- "more",
1217
- "tree",
1218
- "dir",
1219
- "where",
1220
- "ver",
1221
- "cls",
1222
- "echo",
1223
- "help",
1224
- "fc",
1225
- "comp",
1226
- "find",
1227
- "sort",
1228
- "powershell",
1229
- "cmd",
1230
- "netstat",
1231
- "ipconfig",
1232
- "tasklist",
1233
- "systeminfo",
1234
- "driverquery",
1235
- "reg",
1236
- "wmic",
1237
- "whoami",
1238
- "hostname",
1239
- "getmac",
1240
- "query",
1241
- "w32tm",
1242
- "diskpart"
1243
- ]),
1244
- // Windows 完全安全命令(对齐 WindowsProfile.fully_safe_commands)
1245
- fullySafeCommands: [
1246
- /^dir(?:\s|$)/,
1247
- /^where\s+/,
1248
- /^echo\s+/,
1249
- /^cd(?:\s|$)/,
1250
- /^tree(?:\s|$)/,
1251
- /^help(?:\s|$)/,
1252
- /^cls(?:\s|$)/,
1253
- /^ver(?:\s|$)/,
1254
- // 系统信息查询(只读,不读文件内容、无状态变更选项),与 Linux 侧 df/free 对齐
1255
- /^whoami(?:\s|$)/,
1256
- /^hostname(?:\s|$)/,
1257
- /^systeminfo(?:\s|$)/,
1258
- /^tasklist(?:\s|$)/,
1259
- /^getmac(?:\s|$)/,
1260
- /^driverquery(?:\s|$)/,
1261
- /^netstat(?:\s|$)/
1262
- ],
1263
- // Windows 安全但可能访问敏感路径的命令(对齐 WindowsProfile.readonly_sensitive_commands)
1264
- readonlySensitiveCommands: [
1265
- /^type\s+/,
1266
- /^findstr\s+/,
1267
- // 只读子命令级白名单(匹配「第一段命令」而非首词);
1268
- // sc create/delete/start/stop、reg add/delete/set 均不在此列。
1269
- /^sc\s+query\b/,
1270
- /^reg\s+query\b/
1271
- ]
1272
- };
1273
- MACOS_TRAITS = LINUX_TRAITS;
1274
- 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");
1275
931
  }
1276
932
  });
1277
933
 
@@ -1430,7 +1086,6 @@ function checkIntentConsistency(input) {
1430
1086
  const { aNext, promptSession, promptHistory } = input;
1431
1087
  const actionDetail = aNext.actionDetail.toLowerCase();
1432
1088
  const actionType = aNext.actionType.toLowerCase();
1433
- const EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
1434
1089
  if (EXCLUDED_PREFIXES.some((p) => actionType.startsWith(p))) return null;
1435
1090
  if (INLINE_SCRIPT_RE.test(actionDetail)) return null;
1436
1091
  const intentTexts = collectIntentTexts(promptSession, promptHistory);
@@ -1458,7 +1113,6 @@ function runLogicCheck(input, opts) {
1458
1113
  const actionDetail = aNext.actionDetail.toLowerCase();
1459
1114
  const actionType = aNext.actionType.toLowerCase();
1460
1115
  const resolvedCwd = cwd || "/";
1461
- const EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
1462
1116
  const isExcludedType = EXCLUDED_PREFIXES.some((p) => actionType.startsWith(p));
1463
1117
  if (!opts?.skipReadBeforeWrite) {
1464
1118
  const rbwResult = checkReadBeforeWrite(actionType, actionDetail, actionHistory, input.reason, resolvedCwd, input.osType);
@@ -1570,7 +1224,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
1570
1224
  const isBashLike = actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash";
1571
1225
  const isWrite = isBashLike ? isWriteOperation(actionType, actionDetail, void 0, osType) : getPlatformTraits(osType).writeKeywords.some((kw) => actionType.includes(kw));
1572
1226
  if (!isWrite) return null;
1573
- const WRITE_CMDS = /* @__PURE__ */ new Set(["cp", "mv", "install", "tee", "dd", "truncate", "mkfs", "fdisk", "parted"]);
1227
+ const WRITE_CMDS = new Set(_LOGIC.write_commands ?? []);
1574
1228
  if (isBashLike) {
1575
1229
  const detailNoCompare = actionDetail.replace(/<=>|>=|<=|<>|=>/g, " ");
1576
1230
  const detailNoDevnull = detailNoCompare.replace(DEVNULL_REDIRECT_RE, "");
@@ -1602,7 +1256,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
1602
1256
  }
1603
1257
  if (!hasWriteSeg) return null;
1604
1258
  }
1605
- const SOURCE_ARG_CMDS = /* @__PURE__ */ new Set(["cp", "mv", "install", "rsync"]);
1259
+ const SOURCE_ARG_CMDS = new Set(_LOGIC.source_arg_commands ?? []);
1606
1260
  const REDIRECT_TARGET_RE = new RegExp(`>{1,2}\\s*(?!&)(?:'(/[^']+)'|"(/[^\\"]+)"|(/[^\\s;|&><'\\"]+))`);
1607
1261
  const targetPaths = [];
1608
1262
  for (const seg of splitCommandSegments(actionDetail)) {
@@ -1650,7 +1304,6 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
1650
1304
  }
1651
1305
  }
1652
1306
  }
1653
- const TEMP_DIRS = ["/tmp/", "/var/tmp/", "/dev/shm/", "/run/"];
1654
1307
  let filteredPaths = uniqueTargetPaths;
1655
1308
  if (/\bdd\b/i.test(actionDetail) && /\bif=\/dev\/zero\b/i.test(actionDetail)) {
1656
1309
  filteredPaths = uniqueTargetPaths.filter(
@@ -1751,10 +1404,10 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd, osType) {
1751
1404
  if (!hasTraversal) {
1752
1405
  return null;
1753
1406
  }
1754
- const READ_KEYWORDS = ["read(", "readline", "readlines", "fread", "fgets", "load(", "getpass", "file_get_contents"];
1755
- const WRITE_KEYWORDS2 = ["write(", "writelines", "fwrite", "file_put_contents"];
1756
- 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"];
1757
- 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;
1758
1411
  const code = extractInlineCode(actionDetail);
1759
1412
  if (!code) return null;
1760
1413
  const codeLower = code.toLowerCase();
@@ -1817,7 +1470,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd, osType) {
1817
1470
  violatedLayers: ["L2.1"]
1818
1471
  };
1819
1472
  }
1820
- 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;
1821
1474
  var init_logic_rules = __esm({
1822
1475
  "src/engine/logic-rules.ts"() {
1823
1476
  "use strict";
@@ -1827,6 +1480,10 @@ var init_logic_rules = __esm({
1827
1480
  init_rules_loader();
1828
1481
  init_platform_traits();
1829
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 + "/");
1830
1487
  ROOT_SENSITIVE_PATHS = _loadSensitivePathScope("root");
1831
1488
  HOME_SENSITIVE_PATHS = _loadSensitivePathScope("home");
1832
1489
  CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path6, riskLevel, desc, dirDelOnly }) => ({ path: path6, riskLevel, desc, credential: true, dirDelOnly }));
@@ -1847,7 +1504,7 @@ var init_logic_rules = __esm({
1847
1504
  "list_dir",
1848
1505
  "count_text_length",
1849
1506
  "search",
1850
- // filemgr 只读三件套(globfiles 在 coordinator FULLY_SAFE_TOOLS 提前放行,此处兜底防御非 coordinator 入口)
1507
+ // filemgr 只读三件套(globfiles 在 platform-traits 的 safeActionTypes 提前放行,此处兜底防御非 coordinator 入口)
1851
1508
  "filemgr-globfiles",
1852
1509
  "filemgr-readfile",
1853
1510
  "filemgr-grepfiles",
@@ -1859,7 +1516,7 @@ var init_logic_rules = __esm({
1859
1516
  "tail",
1860
1517
  "cat"
1861
1518
  ]);
1862
- NON_WRITE_TOOL_PREFIXES = ["memory-", "skill-manager-", "mailbox-", "app-"];
1519
+ NON_WRITE_TOOL_PREFIXES = _LOGIC.non_write_tool_prefixes ?? [];
1863
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"];
1864
1521
  SED_INPLACE_RE = /\bsed\s+(?:-\S+\s+)*(?:-[a-zA-Z]*i[a-zA-Z]*|--in-place)\b/i;
1865
1522
  DEVNULL_REDIRECT_RE = /(?:2|&|1)?\s*>\s*\/dev\/null\b/g;
@@ -1900,25 +1557,24 @@ var init_logic_rules = __esm({
1900
1557
  }));
1901
1558
  })();
1902
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;
1903
- DIR_TRAVERSAL_KW = [
1904
- "os.listdir",
1905
- "os.walk",
1906
- "os.scandir",
1907
- "glob.glob",
1908
- "glob.iglob",
1909
- "pathlib.",
1910
- "iterdir",
1911
- "rglob",
1912
- "scandir(",
1913
- "listdir("
1914
- ];
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 ?? [];
1915
1566
  }
1916
1567
  });
1917
1568
 
1918
1569
  // src/engine/asset-dir.ts
1919
- import { existsSync as existsSync3 } from "fs";
1920
- import { resolve as resolve2 } from "path";
1570
+ import { existsSync as existsSync4 } from "fs";
1571
+ import { join as join4, resolve as resolve2 } from "path";
1921
1572
  function assetDirCandidates(moduleDir, name) {
1573
+ const root = getRulesRoot();
1574
+ if (root) {
1575
+ const p = join4(root, name);
1576
+ return [p, p];
1577
+ }
1922
1578
  return [
1923
1579
  resolve2(moduleDir, name),
1924
1580
  resolve2(moduleDir, "../../..", "agent_moss", name)
@@ -1926,7 +1582,7 @@ function assetDirCandidates(moduleDir, name) {
1926
1582
  }
1927
1583
  function resolveAssetDir(moduleDir, name) {
1928
1584
  for (const dir of assetDirCandidates(moduleDir, name)) {
1929
- if (existsSync3(dir)) return dir;
1585
+ if (existsSync4(dir)) return dir;
1930
1586
  }
1931
1587
  return null;
1932
1588
  }
@@ -1934,11 +1590,12 @@ var init_asset_dir = __esm({
1934
1590
  "src/engine/asset-dir.ts"() {
1935
1591
  "use strict";
1936
1592
  init_esm_shims();
1593
+ init_paths();
1937
1594
  }
1938
1595
  });
1939
1596
 
1940
1597
  // src/engine/skill-loader.ts
1941
- 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";
1942
1599
  import { resolve as resolve3, dirname as dirname2 } from "path";
1943
1600
  import { fileURLToPath as fileURLToPath3 } from "url";
1944
1601
  function loadAllSkills() {
@@ -1953,7 +1610,7 @@ function loadAllSkills() {
1953
1610
  const files = readdirSync3(dir).filter((f) => f.endsWith(".md"));
1954
1611
  for (const f of files) {
1955
1612
  const name = f.replace(".md", "");
1956
- cachedSkills[name] = readFileSync3(resolve3(dir, f), "utf-8");
1613
+ cachedSkills[name] = readFileSync4(resolve3(dir, f), "utf-8");
1957
1614
  }
1958
1615
  console.log(`[AgentMoss] Loaded ${Object.keys(cachedSkills).length} skills from ${dir}`);
1959
1616
  return cachedSkills;
@@ -1984,7 +1641,7 @@ function loadUserSkill(name) {
1984
1641
  if (userSkillCache[name] !== void 0) return userSkillCache[name];
1985
1642
  try {
1986
1643
  const p = resolve3(userSkillsDir(), `${name}.md`);
1987
- userSkillCache[name] = existsSync4(p) ? readFileSync3(p, "utf-8") : "";
1644
+ userSkillCache[name] = existsSync5(p) ? readFileSync4(p, "utf-8") : "";
1988
1645
  } catch {
1989
1646
  userSkillCache[name] = "";
1990
1647
  }
@@ -2133,8 +1790,8 @@ __export(runtime_config_exports, {
2133
1790
  updateSkillCategoryEnabled: () => updateSkillCategoryEnabled,
2134
1791
  updateSkillEnabled: () => updateSkillEnabled
2135
1792
  });
2136
- import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync5, mkdirSync, unlinkSync, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
2137
- 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";
2138
1795
  import { fileURLToPath as fileURLToPath4 } from "url";
2139
1796
  import { dirname as dirname3 } from "path";
2140
1797
  function runtimeDir() {
@@ -2163,7 +1820,7 @@ function saveRuntimeConfig(config) {
2163
1820
  }
2164
1821
  const p = getRuntimeConfigPath();
2165
1822
  const dir = p.substring(0, p.lastIndexOf("/"));
2166
- if (dir && !existsSync5(dir)) mkdirSync(dir, { recursive: true });
1823
+ if (dir && !existsSync6(dir)) mkdirSync(dir, { recursive: true });
2167
1824
  writeFileSync(p, JSON.stringify(config, null, 2), "utf-8");
2168
1825
  invalidateRuntimeConfigCache();
2169
1826
  }
@@ -2197,7 +1854,7 @@ function loadRuntimeConfig() {
2197
1854
  };
2198
1855
  }
2199
1856
  const p = getRuntimeConfigPath();
2200
- if (!existsSync5(p)) {
1857
+ if (!existsSync6(p)) {
2201
1858
  const seeded = mergeConfig({}, generateSourceDefaults());
2202
1859
  try {
2203
1860
  saveRuntimeConfig(seeded);
@@ -2211,7 +1868,7 @@ function loadRuntimeConfig() {
2211
1868
  if (runtimeConfigCache && runtimeConfigCache.key === cacheKey) {
2212
1869
  return runtimeConfigCache.value;
2213
1870
  }
2214
- const data = JSON.parse(readFileSync4(p, "utf-8"));
1871
+ const data = JSON.parse(readFileSync5(p, "utf-8"));
2215
1872
  const merged = mergeConfig(data, generateSourceDefaults());
2216
1873
  const result = {
2217
1874
  version: data.version || 1,
@@ -2269,7 +1926,10 @@ function filterEnabledRules(rules, categoryEnabled = true) {
2269
1926
  }
2270
1927
  function _filterRulesByOs(rules, osType) {
2271
1928
  const accepted = acceptedOsValues(osType);
2272
- 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
+ });
2273
1933
  }
2274
1934
  function _userCustomRules(runtime, layers, requiredKey) {
2275
1935
  const list = Array.isArray(layers) ? layers : [layers];
@@ -2356,7 +2016,7 @@ function hasRuntimeConfig() {
2356
2016
  if (cached2 !== void 0) return cached2;
2357
2017
  let ok = false;
2358
2018
  try {
2359
- JSON.parse(readFileSync4(p, "utf-8"));
2019
+ JSON.parse(readFileSync5(p, "utf-8"));
2360
2020
  ok = true;
2361
2021
  } catch {
2362
2022
  ok = false;
@@ -2456,6 +2116,9 @@ function _l1InjectionKeywordsToRules() {
2456
2116
  keyword: kw.keyword,
2457
2117
  risk_level: kw.riskLevel,
2458
2118
  lang: kw.lang || "en",
2119
+ // 带上条目自己的 os(`all` = 全平台通用),别丢 —— 丢了 Windows 会拿到
2120
+ // 本不该给它的条目。与 Python `_l1_injection_keywords_to_rules` 对齐。
2121
+ os: kw.os,
2459
2122
  enabled: true,
2460
2123
  builtin: true
2461
2124
  }));
@@ -2573,8 +2236,9 @@ function _l3SkillsToRules() {
2573
2236
  }
2574
2237
  function _scenarioRulesToDefaults() {
2575
2238
  const out = {};
2576
- const scenDir = existsSync5(_SCENARIOS_DIR_PROD) ? _SCENARIOS_DIR_PROD : existsSync5(_SCENARIOS_DIR_DEV) ? _SCENARIOS_DIR_DEV : _SCENARIOS_DIR_PROD;
2577
- 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;
2578
2242
  let entries;
2579
2243
  try {
2580
2244
  entries = readdirSync4(scenDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
@@ -2582,11 +2246,11 @@ function _scenarioRulesToDefaults() {
2582
2246
  return out;
2583
2247
  }
2584
2248
  for (const scenarioId of entries) {
2585
- const rulesFile = resolveScenarioRulesFile(scenarioId) ?? join3(scenDir, scenarioId, "rules.json");
2586
- if (!existsSync5(rulesFile)) continue;
2249
+ const rulesFile = resolveScenarioRulesFile(scenarioId) ?? join5(scenDir, scenarioId, "rules.json");
2250
+ if (!existsSync6(rulesFile)) continue;
2587
2251
  let data;
2588
2252
  try {
2589
- data = JSON.parse(readFileSync4(rulesFile, "utf-8"));
2253
+ data = JSON.parse(readFileSync5(rulesFile, "utf-8"));
2590
2254
  } catch {
2591
2255
  continue;
2592
2256
  }
@@ -2622,8 +2286,9 @@ function _scenarioRulesToDefaults() {
2622
2286
  }
2623
2287
  function _agentRulesToDefaults() {
2624
2288
  const out = {};
2625
- const agentsDir = existsSync5(_AGENTS_DIR_PROD) ? _AGENTS_DIR_PROD : existsSync5(_AGENTS_DIR_DEV) ? _AGENTS_DIR_DEV : _AGENTS_DIR_PROD;
2626
- 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;
2627
2292
  let entries;
2628
2293
  try {
2629
2294
  entries = readdirSync4(agentsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
@@ -2631,11 +2296,11 @@ function _agentRulesToDefaults() {
2631
2296
  return out;
2632
2297
  }
2633
2298
  for (const agentId of entries) {
2634
- const rulesFile = join3(agentsDir, agentId, "rules.json");
2635
- if (!existsSync5(rulesFile)) continue;
2299
+ const rulesFile = join5(agentsDir, agentId, "rules.json");
2300
+ if (!existsSync6(rulesFile)) continue;
2636
2301
  let data;
2637
2302
  try {
2638
- data = JSON.parse(readFileSync4(rulesFile, "utf-8"));
2303
+ data = JSON.parse(readFileSync5(rulesFile, "utf-8"));
2639
2304
  } catch {
2640
2305
  continue;
2641
2306
  }
@@ -3010,7 +2675,11 @@ function getDisabledSkillIds(runtime) {
3010
2675
  }
3011
2676
  function _filterDefaultsByOs(rules, osType) {
3012
2677
  const accepted = acceptedOsValues(osType);
3013
- 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
+ });
3014
2683
  }
3015
2684
  function _hasRulesForOs(rules, osType) {
3016
2685
  return _filterRulesByOs(rules || [], osType || "linux").length > 0;
@@ -3239,12 +2908,12 @@ function addCustomSkill(skillId, category, keywords, content) {
3239
2908
  );
3240
2909
  }
3241
2910
  const runtime = loadRuntimeConfig();
3242
- const skillsDir = join3(getRuntimeConfigDir(), "agent_moss_skills");
2911
+ const skillsDir = join5(getRuntimeConfigDir(), "agent_moss_skills");
3243
2912
  try {
3244
2913
  mkdirSync(skillsDir, { recursive: true });
3245
2914
  } catch {
3246
2915
  }
3247
- writeFileSync(join3(skillsDir, `${skillId}.md`), content, "utf-8");
2916
+ writeFileSync(join5(skillsDir, `${skillId}.md`), content, "utf-8");
3248
2917
  const l3 = runtime.L3_skills || (runtime.L3_skills = {});
3249
2918
  const cat = l3[category] || (l3[category] = { category_enabled: true, category_desc: "", skills: [] });
3250
2919
  (cat.skills || (cat.skills = [])).push({ id: skillId, keywords, enabled: true, builtin: false });
@@ -3259,9 +2928,9 @@ function deleteCustomSkill(skillId, category) {
3259
2928
  if (idx >= 0) {
3260
2929
  if (skills[idx].builtin) return null;
3261
2930
  skills.splice(idx, 1);
3262
- const skillPath = join3(getRuntimeConfigDir(), "agent_moss_skills", `${skillId}.md`);
2931
+ const skillPath = join5(getRuntimeConfigDir(), "agent_moss_skills", `${skillId}.md`);
3263
2932
  try {
3264
- if (existsSync5(skillPath)) unlinkSync(skillPath);
2933
+ if (existsSync6(skillPath)) unlinkSync(skillPath);
3265
2934
  } catch {
3266
2935
  }
3267
2936
  saveRuntimeConfig(runtime);
@@ -3468,7 +3137,7 @@ var init_runtime_config = __esm({
3468
3137
  __dirname4 = dirname3(fileURLToPath4(import.meta.url));
3469
3138
  ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
3470
3139
  RUNTIME_CONFIG_DIR = runtimeDir();
3471
- RUNTIME_CONFIG_PATH = join3(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
3140
+ RUNTIME_CONFIG_PATH = join5(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
3472
3141
  LAYER_DISABLE_ENV = {
3473
3142
  L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
3474
3143
  L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
@@ -3496,10 +3165,10 @@ var init_runtime_config = __esm({
3496
3165
  intent_consistency: ["intent_keywords", "reason"]
3497
3166
  };
3498
3167
  hasRuntimeConfigCache = /* @__PURE__ */ new Map();
3499
- _AGENTS_DIR_PROD = join3(__dirname4, "agents");
3500
- _AGENTS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "agents");
3501
- _SCENARIOS_DIR_PROD = join3(__dirname4, "scenarios");
3502
- _SCENARIOS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
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");
3503
3172
  _effectiveCache = /* @__PURE__ */ new WeakMap();
3504
3173
  SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
3505
3174
  registerRuntimeConfigBridge({
@@ -3837,12 +3506,14 @@ var init_config = __esm({
3837
3506
  });
3838
3507
 
3839
3508
  // src/agents/index.ts
3840
- import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
3841
- 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";
3842
3511
  import { fileURLToPath as fileURLToPath6 } from "url";
3843
3512
  function resolveAgentsDir() {
3844
- if (existsSync6(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
3845
- 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;
3846
3517
  return AGENTS_DIR_PROD;
3847
3518
  }
3848
3519
  function getAgentExtensions(agentId) {
@@ -3860,12 +3531,12 @@ function getAgentExtensions(agentId) {
3860
3531
  if (ext === null) {
3861
3532
  const agentsDir = resolveAgentsDir();
3862
3533
  const rulesFile = resolve4(agentsDir, agentId, "rules.json");
3863
- if (!existsSync6(rulesFile)) {
3534
+ if (!existsSync7(rulesFile)) {
3864
3535
  cache.set(agentId, { fp, ext: null });
3865
3536
  return null;
3866
3537
  }
3867
3538
  try {
3868
- const raw = readFileSync5(rulesFile, "utf-8");
3539
+ const raw = readFileSync6(rulesFile, "utf-8");
3869
3540
  ext = JSON.parse(raw);
3870
3541
  if (!ext.agent_id) {
3871
3542
  console.warn(`[AgentMoss agents] rules.json \u7F3A\u5C11 agent_id \u5B57\u6BB5: ${rulesFile}`);
@@ -3904,6 +3575,7 @@ var init_agents = __esm({
3904
3575
  "src/agents/index.ts"() {
3905
3576
  "use strict";
3906
3577
  init_esm_shims();
3578
+ init_paths();
3907
3579
  init_runtime_config();
3908
3580
  __filename4 = fileURLToPath6(import.meta.url);
3909
3581
  __dirname5 = dirname4(__filename4);
@@ -3914,12 +3586,14 @@ var init_agents = __esm({
3914
3586
  });
3915
3587
 
3916
3588
  // src/scenarios/index.ts
3917
- import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
3918
- 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";
3919
3591
  import { fileURLToPath as fileURLToPath7 } from "url";
3920
3592
  function resolveScenariosDir() {
3921
- if (existsSync7(SCENARIOS_DIR_PROD)) return SCENARIOS_DIR_PROD;
3922
- 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;
3923
3597
  return SCENARIOS_DIR_PROD;
3924
3598
  }
3925
3599
  function getScenarioExtensions(scenarioId) {
@@ -3949,12 +3623,12 @@ function getScenarioExtensions(scenarioId) {
3949
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`
3950
3624
  );
3951
3625
  }
3952
- if (!existsSync7(rulesFile)) {
3626
+ if (!existsSync8(rulesFile)) {
3953
3627
  cache2.set(scenarioId, { fp, ext: null });
3954
3628
  return null;
3955
3629
  }
3956
3630
  try {
3957
- const raw = readFileSync6(rulesFile, "utf-8");
3631
+ const raw = readFileSync7(rulesFile, "utf-8");
3958
3632
  const data = JSON.parse(raw);
3959
3633
  const id = data.scenario_id || data.agent_id || "";
3960
3634
  if (!id) {
@@ -3998,6 +3672,7 @@ var init_scenarios = __esm({
3998
3672
  "src/scenarios/index.ts"() {
3999
3673
  "use strict";
4000
3674
  init_esm_shims();
3675
+ init_paths();
4001
3676
  init_agents();
4002
3677
  init_runtime_config();
4003
3678
  init_rules_loader();
@@ -4404,7 +4079,7 @@ function severityRank(s) {
4404
4079
  }
4405
4080
  function isSearchActionType(actionType) {
4406
4081
  const at = actionType.toLowerCase();
4407
- 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);
4408
4083
  }
4409
4084
  function checkSensitivePath(command, cwd, agentExt, osType) {
4410
4085
  const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
@@ -4694,8 +4369,7 @@ function isSshKeygenGenerate(command) {
4694
4369
  function checkDangerousPatterns(actionType, actionDetail) {
4695
4370
  if (actionType === "file_write" || actionType === "file_edit") return null;
4696
4371
  const scan = stripHeredocContent(actionDetail);
4697
- const TEMP_DIRS = ["/tmp", "/var/tmp", "/run", "/dev/shm"];
4698
- const isTempPath = TEMP_DIRS.some(
4372
+ const isTempPath = TEMP_DIRS2.some(
4699
4373
  (td) => new RegExp(`(?:^|[\\s/])${td.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[\\s/]|$)`).test(actionDetail)
4700
4374
  ) || /(?:^|[\s;]|&&\s*|\|\|\s*)var\/tmp(?:[/\s;]|$)/.test(actionDetail);
4701
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;
@@ -4841,7 +4515,7 @@ function checkLateralMovement(actionDetail) {
4841
4515
  }
4842
4516
  return null;
4843
4517
  }
4844
- 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;
4845
4519
  var init_heuristic = __esm({
4846
4520
  "src/engine/heuristic.ts"() {
4847
4521
  "use strict";
@@ -4858,7 +4532,8 @@ var init_heuristic = __esm({
4858
4532
  return kws.map((k) => ({
4859
4533
  keyword: k.keyword,
4860
4534
  riskLevel: k.risk_level,
4861
- lang: k.lang || "en"
4535
+ lang: k.lang || "en",
4536
+ os: String(k.os || "all").toLowerCase()
4862
4537
  }));
4863
4538
  })();
4864
4539
  DANGEROUS_PATTERNS2 = _loadDangerousPatterns("linux");
@@ -4866,7 +4541,9 @@ var init_heuristic = __esm({
4866
4541
  DANGEROUS_PATTERNS_MACOS = _loadDangerousPatterns("macos");
4867
4542
  DANGEROUS_PATTERNS_HARMONY = _loadDangerousPatterns("harmony");
4868
4543
  LOOPBACK_URL_RE = /https?:\/\/([^\/\s;'"|&)]+)/i;
4869
- 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 ?? [];
4870
4547
  PATH_CONTEXT_TRIGGER_RE = /(?:^|[;&|]\s*)(?:rm|rmdir|unlink|ln|mv|cp|dd|truncate|shred|chmod|chown)\b/i;
4871
4548
  HOME_PREFIX_RE = /(?:~|~(?=\/)|\$HOME\b|\$\{HOME\}|\/home\/[a-z0-9_.-]+)/gi;
4872
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;
@@ -4874,15 +4551,16 @@ var init_heuristic = __esm({
4874
4551
  PY_BIGNUM_RE = /\d{6,}/;
4875
4552
  PY_INLINE_C_RE = /(python(?:3(?:\.\d+)?)?|perl5?|ruby|node|php|lua)\s+(?:-[a-zA-Z]+\s+)*-c\s+(?:"([^"]*)"|'([^']*)')/i;
4876
4553
  PY_CMD_SUBST_C_RE = /\$\(\s*(python(?:3(?:\.\d+)?)?|perl5?|ruby|node|php|lua)\s+(?:-[a-zA-Z]+\s+)*-c\s+(?:"([^"]*)"'|'([^']*)')\s*\)/i;
4877
- SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
4878
- 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 ?? [];
4879
4557
  HOME_TARGET_RE = /(?:^|[\s="'($])(?:~|(?:\$home|\$\{home\}))(?:\/(?:\.\*|\*|\.)?)?(?=[\s"';}|&)]|$)/i;
4880
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;
4881
4559
  }
4882
4560
  });
4883
4561
 
4884
4562
  // src/engine/template-loader.ts
4885
- import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
4563
+ import { readFileSync as readFileSync8, existsSync as existsSync9 } from "fs";
4886
4564
  import { resolve as resolve6, dirname as dirname6 } from "path";
4887
4565
  import { fileURLToPath as fileURLToPath8 } from "url";
4888
4566
  function loadTemplates() {
@@ -4896,9 +4574,9 @@ function loadTemplates() {
4896
4574
  }
4897
4575
  for (const file of TEMPLATE_FILES) {
4898
4576
  const filePath = resolve6(dir, file);
4899
- if (existsSync8(filePath)) {
4577
+ if (existsSync9(filePath)) {
4900
4578
  const key = file.replace(".txt", "").replace("_template", "");
4901
- cachedTemplates[key] = readFileSync7(filePath, "utf-8");
4579
+ cachedTemplates[key] = readFileSync8(filePath, "utf-8");
4902
4580
  }
4903
4581
  }
4904
4582
  console.log(`[AgentMoss] Loaded ${Object.keys(cachedTemplates).length} templates from ${dir}`);
@@ -4918,8 +4596,8 @@ var init_template_loader = __esm({
4918
4596
  });
4919
4597
 
4920
4598
  // src/engine/script-analyzer.ts
4921
- import { readFileSync as readFileSync8 } from "fs";
4922
- 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";
4923
4601
  function emptyResult() {
4924
4602
  return { scriptPath: "", scriptContent: "", matchedKeywords: [], lineCount: 0, isSuspicious: false, error: "", riskLevel: "", riskReason: "" };
4925
4603
  }
@@ -4945,10 +4623,10 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
4945
4623
  const hitGroup2 = rule.group2.size === 0 ? true : [...rule.group2].some((k) => keywordSet.has(k));
4946
4624
  if (hitGroup1 && hitGroup2) return { level: rule.level, reason: rule.reason };
4947
4625
  }
4948
- 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 ?? []);
4949
4627
  const hasNetworkPost = [...networkPostKeywords].some((k) => keywordSet.has(k));
4950
4628
  if (hasNetworkPost) {
4951
- const envPatterns = ["$env", "$(env)", "$USER", "$(hostname)", "$(pwd)", "$HOME"];
4629
+ const envPatterns = SCRIPT_RULES.env_exfil_patterns ?? [];
4952
4630
  const contentLower = scriptContent.toLowerCase();
4953
4631
  for (const p of envPatterns) {
4954
4632
  if (contentLower.includes(p.toLowerCase())) {
@@ -4972,8 +4650,8 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
4972
4650
  }
4973
4651
  function readScriptContent(scriptPath, maxLines = 500) {
4974
4652
  try {
4975
- const expandedPath = scriptPath === "~" || scriptPath.startsWith("~/") ? join4(process.env.HOME || "/home/user", scriptPath.slice(1)) : resolve7(scriptPath);
4976
- 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");
4977
4655
  const lines = raw.split("\n").slice(0, maxLines);
4978
4656
  return { content: lines.join("\n"), lineCount: lines.length, error: "" };
4979
4657
  } catch (e) {
@@ -5022,90 +4700,28 @@ ${result.scriptContent}
5022
4700
  \`\`\`
5023
4701
  `;
5024
4702
  }
5025
- 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;
5026
4704
  var init_script_analyzer = __esm({
5027
4705
  "src/engine/script-analyzer.ts"() {
5028
4706
  "use strict";
5029
4707
  init_esm_shims();
5030
- SUSPICIOUS_PATTERNS = [
5031
- // Network exfiltration (curl/wget POST in scripts is almost never legitimate)
5032
- { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
5033
- { name: "curl --data", pattern: /curl\s+(.*\s)?--data/i },
5034
- { name: "curl -d", pattern: /curl\s+(.*\s)?-d\s/i },
5035
- { name: "wget --post-data", pattern: /wget\s+(.*\s)?--post-data/i },
5036
- { name: "wget --post-file", pattern: /wget\s+(.*\s)?--post-file/i },
5037
- // Reverse shell
5038
- { name: "nc -e", pattern: /\bnc\s+.*-e\b/ },
5039
- { name: "ncat -e", pattern: /\bncat\s+.*-e\b/ },
5040
- { name: "/dev/tcp", pattern: /\/dev\/tcp\// },
5041
- { name: "/dev/udp", pattern: /\/dev\/udp\// },
5042
- // Base64 obfuscation
5043
- { name: "$(base64", pattern: /\$\(\s*base64/ },
5044
- { name: "`base64", pattern: /`\s*base64/ },
5045
- { name: "base64 decode pipe", pattern: /base64\s+(-d|--decode)\s*\|/ },
5046
- // Sensitive file access
5047
- { name: "/etc/shadow", pattern: /\/etc\/shadow/ },
5048
- { name: "id_rsa", pattern: /\bid_rsa\b/ },
5049
- { name: "id_ed25519", pattern: /\bid_ed25519\b/ },
5050
- // Persistence backdoor
5051
- { name: "crontab install", pattern: /crontab\s+-/ },
5052
- { name: "authorized_keys", pattern: /authorized_keys/ },
5053
- { name: "ssh no host key check", pattern: /ssh\s+.*StrictHostKeyChecking\s*=\s*no/ }
5054
- ];
5055
- SCRIPT_EXEC_PATTERNS = [
5056
- /bash\s+(\S+\.sh)/,
5057
- /sh\s+(\S+\.sh)/,
5058
- /\.\/(\S+\.sh)/,
5059
- /source\s+(\S+\.sh)/,
5060
- /\.\s+(\S+\.sh)/,
5061
- /zsh\s+(\S+\.sh)/
5062
- ];
5063
- KEYWORD_COMBINATION_RISKS = [
5064
- {
5065
- group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
5066
- group2: /* @__PURE__ */ new Set(["$(base64", "`base64", "base64 decode pipe"]),
5067
- level: "critical",
5068
- 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"
5069
- },
5070
- {
5071
- group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
5072
- group2: /* @__PURE__ */ new Set(["/etc/shadow", "id_rsa", "id_ed25519"]),
5073
- level: "critical",
5074
- 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"
5075
- },
5076
- {
5077
- group1: /* @__PURE__ */ new Set(["nc -e", "ncat -e", "/dev/tcp", "/dev/udp"]),
5078
- group2: /* @__PURE__ */ new Set(),
5079
- level: "high",
5080
- reason: "\u68C0\u6D4B\u5230\u53CD\u5F39 Shell \u7279\u5F81\uFF0C\u653B\u51FB\u8005\u53EF\u80FD\u5EFA\u7ACB\u8FDC\u7A0B\u63A7\u5236\u901A\u9053\u3002"
5081
- },
5082
- {
5083
- group1: /* @__PURE__ */ new Set(["crontab install", "authorized_keys", "ssh no host key check"]),
5084
- group2: /* @__PURE__ */ new Set(),
5085
- level: "high",
5086
- 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"
5087
- }
5088
- ];
5089
- SINGLE_KEYWORD_RISKS = {
5090
- "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" },
5091
- "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" },
5092
- "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" },
5093
- "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" },
5094
- "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" },
5095
- "nc -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 nc -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
5096
- "ncat -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 ncat -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
5097
- "/dev/tcp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/tcp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
5098
- "/dev/udp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/udp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
5099
- "$(base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
5100
- "`base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
5101
- "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" },
5102
- "/etc/shadow": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6\u3002" },
5103
- "id_rsa": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
5104
- "id_ed25519": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
5105
- "crontab install": { level: "high", reason: "\u68C0\u6D4B\u5230\u4FEE\u6539\u5B9A\u65F6\u4EFB\u52A1\uFF0C\u53EF\u80FD\u662F\u6301\u4E45\u5316\u540E\u95E8\u3002" },
5106
- "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" },
5107
- "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" }
5108
- };
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 ?? {};
5109
4725
  LEVEL_ORDER = { low: 1, medium: 2, high: 3, critical: 4 };
5110
4726
  }
5111
4727
  });
@@ -6649,8 +6265,8 @@ var init_tracker = __esm({
6649
6265
  });
6650
6266
 
6651
6267
  // src/engine/script-content-analyzer.ts
6652
- import { statSync as statSync4, existsSync as existsSync10 } from "fs";
6653
- 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";
6654
6270
  import { homedir as homedir3 } from "os";
6655
6271
  function emptyChain() {
6656
6272
  return {
@@ -6683,10 +6299,10 @@ function assessKeywordCombinationRisk2(matchedKeywords, scriptContent) {
6683
6299
  const hitGroup2 = rule.group2.size === 0 ? true : [...rule.group2].some((k) => keywordSet.has(k));
6684
6300
  if (hitGroup1 && hitGroup2) return { level: rule.level, reason: rule.reason };
6685
6301
  }
6686
- 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 ?? []);
6687
6303
  const hasNetworkPost = [...networkPostKeywords].some((k) => keywordSet.has(k));
6688
6304
  if (hasNetworkPost) {
6689
- const envPatterns = ["$env", "$(env)", "$USER", "$(hostname)", "$(pwd)", "$HOME"];
6305
+ const envPatterns = SCRIPT_RULES2.env_exfil_patterns ?? [];
6690
6306
  const contentLower = scriptContent.toLowerCase();
6691
6307
  for (const p of envPatterns) {
6692
6308
  if (contentLower.includes(p.toLowerCase())) {
@@ -6762,7 +6378,7 @@ function extractInlineScriptPath(actionDetail) {
6762
6378
  if (looksLikeScriptPath(tok)) {
6763
6379
  try {
6764
6380
  const resolved = resolvePath2(tok.replace(/^['"]|['"]$/g, ""));
6765
- if (existsSync10(resolved)) {
6381
+ if (existsSync11(resolved)) {
6766
6382
  const stat = statSync4(resolved);
6767
6383
  if (stat.isFile()) return resolved;
6768
6384
  }
@@ -6776,7 +6392,7 @@ function extractInlineScriptPath(actionDetail) {
6776
6392
  const p = m[1];
6777
6393
  try {
6778
6394
  const resolved = resolvePath2(p);
6779
- if (existsSync10(resolved)) {
6395
+ if (existsSync11(resolved)) {
6780
6396
  const stat = statSync4(resolved);
6781
6397
  if (stat.isFile()) return resolved;
6782
6398
  }
@@ -6792,7 +6408,7 @@ function extractInlineScriptPath(actionDetail) {
6792
6408
  let resolved;
6793
6409
  try {
6794
6410
  resolved = resolvePath2(p);
6795
- if (existsSync10(resolved)) {
6411
+ if (existsSync11(resolved)) {
6796
6412
  const stat = statSync4(resolved);
6797
6413
  if (stat.isFile()) return resolved;
6798
6414
  }
@@ -6833,7 +6449,7 @@ function shlexSplit(s) {
6833
6449
  function resolvePath2(p) {
6834
6450
  let expanded = p;
6835
6451
  if (expanded.startsWith("~/") || expanded === "~") {
6836
- expanded = join6(homedir3(), expanded.slice(1));
6452
+ expanded = join10(homedir3(), expanded.slice(1));
6837
6453
  }
6838
6454
  return resolve9(expanded);
6839
6455
  }
@@ -6957,7 +6573,7 @@ function analyzeScriptContent2(input, ext) {
6957
6573
  isInline = true;
6958
6574
  }
6959
6575
  if (resolvedPath && !isAbsolute2(resolvedPath) && !resolvedPath.startsWith("~/")) {
6960
- resolvedPath = join6(input.cwd || process.cwd(), resolvedPath);
6576
+ resolvedPath = join10(input.cwd || process.cwd(), resolvedPath);
6961
6577
  }
6962
6578
  const l1Adapter = (content) => {
6963
6579
  const j = scanDangerousCommandPatterns("bash", content, input.osType);
@@ -7042,7 +6658,7 @@ ${contentPreview}
7042
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";
7043
6659
  return header + parts.join("\n---\n\n");
7044
6660
  }
7045
- 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;
7046
6662
  var init_script_content_analyzer = __esm({
7047
6663
  "src/engine/script-content-analyzer.ts"() {
7048
6664
  "use strict";
@@ -7052,87 +6668,23 @@ var init_script_content_analyzer = __esm({
7052
6668
  init_logic_rules();
7053
6669
  init_inline_analyzer();
7054
6670
  init_tracker();
7055
- SUSPICIOUS_PATTERNS2 = [
7056
- { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
7057
- { name: "curl --data", pattern: /curl\s+(.*\s)?--data/i },
7058
- { name: "curl -d", pattern: /curl\s+(.*\s)?-d\s/i },
7059
- { name: "wget --post-data", pattern: /wget\s+(.*\s)?--post-data/i },
7060
- { name: "wget --post-file", pattern: /wget\s+(.*\s)?--post-file/i },
7061
- { name: "nc -e", pattern: /\bnc\s+.*-e\b/ },
7062
- { name: "ncat -e", pattern: /\bncat\s+.*-e\b/ },
7063
- { name: "/dev/tcp", pattern: /\/dev\/tcp\// },
7064
- { name: "/dev/udp", pattern: /\/dev\/udp\// },
7065
- { name: "$(base64", pattern: /\$\(\s*base64/ },
7066
- { name: "`base64", pattern: /`\s*base64/ },
7067
- { name: "base64 decode pipe", pattern: /base64\s+(-d|--decode)\s*\|/ },
7068
- { name: "/etc/shadow", pattern: /\/etc\/shadow/ },
7069
- { name: "id_rsa", pattern: /\bid_rsa\b/ },
7070
- { name: "id_ed25519", pattern: /\bid_ed25519\b/ },
7071
- { name: "crontab install", pattern: /crontab\s+-/ },
7072
- { name: "authorized_keys", pattern: /authorized_keys/ },
7073
- { name: "ssh no host key check", pattern: /ssh\s+.*StrictHostKeyChecking\s*=\s*no/ }
7074
- ];
7075
- SCRIPT_EXEC_PATTERNS2 = [
7076
- /(?:^|\s|&&|\|\|)\s*bash\s+(\S+)/i,
7077
- /(?:^|\s|&&|\|\|)\s*\/bin\/bash\s+(\S+)/i,
7078
- /(?:^|\s|&&|\|\|)\s*sh\s+(\S+)/i,
7079
- /(?:^|\s|&&|\|\|)\s*zsh\s+(\S+)/i,
7080
- /\.\/(\S+)/,
7081
- /(?:^|\s|&&|\|\|)\s*source\s+(\S+)/i,
7082
- /(?:^|\s|&&|\|\|)\s*\.\s+(\S+)/,
7083
- /(?:^|\s|&&|\|\|)\s*python3?\s+(\S+\.py)/i,
7084
- /(?:^|\s|&&|\|\|)\s*node\s+(\S+\.js)/i,
7085
- /(?:^|\s|&&|\|\|)\s*ruby\s+(\S+\.rb)/i,
7086
- /(?:^|\s|&&|\|\|)\s*perl\s+(\S+\.pl)/i,
7087
- /(?:^|\s|&&|\|\|)\s*php\s+(\S+\.php)/i,
7088
- /(?:^|\s|&&|\|\|)\s*lua\s+(\S+\.lua)/i
7089
- ];
7090
- KEYWORD_COMBINATION_RISKS2 = [
7091
- {
7092
- group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
7093
- group2: /* @__PURE__ */ new Set(["$(base64", "`base64", "base64 decode pipe"]),
7094
- level: "critical",
7095
- 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"
7096
- },
7097
- {
7098
- group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
7099
- group2: /* @__PURE__ */ new Set(["/etc/shadow", "id_rsa", "id_ed25519"]),
7100
- level: "critical",
7101
- 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"
7102
- },
7103
- {
7104
- group1: /* @__PURE__ */ new Set(["nc -e", "ncat -e", "/dev/tcp", "/dev/udp"]),
7105
- group2: /* @__PURE__ */ new Set(),
7106
- level: "high",
7107
- reason: "\u68C0\u6D4B\u5230\u53CD\u5F39 Shell \u7279\u5F81\uFF0C\u653B\u51FB\u8005\u53EF\u80FD\u5EFA\u7ACB\u8FDC\u7A0B\u63A7\u5236\u901A\u9053\u3002"
7108
- },
7109
- {
7110
- group1: /* @__PURE__ */ new Set(["crontab install", "authorized_keys", "ssh no host key check"]),
7111
- group2: /* @__PURE__ */ new Set(),
7112
- level: "high",
7113
- 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"
7114
- }
7115
- ];
7116
- SINGLE_KEYWORD_RISKS2 = {
7117
- "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" },
7118
- "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" },
7119
- "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" },
7120
- "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" },
7121
- "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" },
7122
- "nc -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 nc -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
7123
- "ncat -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 ncat -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
7124
- "/dev/tcp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/tcp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
7125
- "/dev/udp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/udp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
7126
- "$(base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
7127
- "`base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
7128
- "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" },
7129
- "/etc/shadow": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6\u3002" },
7130
- "id_rsa": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
7131
- "id_ed25519": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
7132
- "crontab install": { level: "high", reason: "\u68C0\u6D4B\u5230\u4FEE\u6539\u5B9A\u65F6\u4EFB\u52A1\uFF0C\u53EF\u80FD\u662F\u6301\u4E45\u5316\u540E\u95E8\u3002" },
7133
- "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" },
7134
- "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" }
7135
- };
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 ?? {};
7136
6688
  LEVEL_ORDER2 = { low: 1, medium: 2, high: 3, critical: 4 };
7137
6689
  }
7138
6690
  });
@@ -7147,16 +6699,16 @@ __export(token_stats_exports, {
7147
6699
  recordTokenUsage: () => recordTokenUsage,
7148
6700
  resetTokenStats: () => resetTokenStats
7149
6701
  });
7150
- 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";
7151
6703
  function getStatsPath() {
7152
6704
  const envPath = process.env[ENV_STATS_PATH];
7153
6705
  if (envPath) return envPath;
7154
6706
  return getTokenStatsPath();
7155
6707
  }
7156
6708
  function loadAllRecords(statsPath) {
7157
- if (!existsSync11(statsPath)) return [];
6709
+ if (!existsSync12(statsPath)) return [];
7158
6710
  try {
7159
- const data = JSON.parse(readFileSync11(statsPath, "utf-8"));
6711
+ const data = JSON.parse(readFileSync12(statsPath, "utf-8"));
7160
6712
  if (Array.isArray(data)) return data;
7161
6713
  if (data && Array.isArray(data.records)) return data.records;
7162
6714
  return [];
@@ -7167,7 +6719,7 @@ function loadAllRecords(statsPath) {
7167
6719
  function saveRecords(statsPath, records) {
7168
6720
  try {
7169
6721
  const dir = statsPath.substring(0, statsPath.lastIndexOf("/"));
7170
- if (dir && !existsSync11(dir)) mkdirSync2(dir, { recursive: true });
6722
+ if (dir && !existsSync12(dir)) mkdirSync2(dir, { recursive: true });
7171
6723
  writeFileSync2(statsPath, JSON.stringify(records, null, 2), "utf-8");
7172
6724
  } catch (e) {
7173
6725
  }
@@ -7294,7 +6846,7 @@ function getTokenTrend(days = 0, startDate, endDate) {
7294
6846
  }
7295
6847
  function resetTokenStats() {
7296
6848
  const statsPath = getStatsPath();
7297
- if (existsSync11(statsPath)) {
6849
+ if (existsSync12(statsPath)) {
7298
6850
  try {
7299
6851
  unlinkSync2(statsPath);
7300
6852
  } catch {
@@ -7551,8 +7103,8 @@ var init_llm_analyzer = __esm({
7551
7103
  });
7552
7104
 
7553
7105
  // src/brain/rule-store.ts
7554
- import { existsSync as existsSync12, readFileSync as readFileSync12, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
7555
- 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";
7556
7108
  import { homedir as homedir4 } from "os";
7557
7109
  function getRulesPath() {
7558
7110
  const envPath = process.env[ENV_RULES_PATH];
@@ -7571,7 +7123,7 @@ var init_rule_store = __esm({
7571
7123
  "src/brain/rule-store.ts"() {
7572
7124
  "use strict";
7573
7125
  init_esm_shims();
7574
- DEFAULT_RULES_PATH = join7(homedir4(), ".agentmoss", "brain", "rules.json");
7126
+ DEFAULT_RULES_PATH = join11(homedir4(), ".agentmoss", "brain", "rules.json");
7575
7127
  ENV_RULES_PATH = "AGENT_MOSS_BRAIN_RULES_PATH";
7576
7128
  STATUS_DRAFT = "draft";
7577
7129
  STATUS_APPROVED = "approved";
@@ -7588,11 +7140,11 @@ var init_rule_store = __esm({
7588
7140
  this.data = this.load();
7589
7141
  }
7590
7142
  load() {
7591
- if (!existsSync12(this.path)) {
7143
+ if (!existsSync13(this.path)) {
7592
7144
  return { version: 1, rules: [], pattern_candidates: [] };
7593
7145
  }
7594
7146
  try {
7595
- const raw = JSON.parse(readFileSync12(this.path, "utf-8"));
7147
+ const raw = JSON.parse(readFileSync13(this.path, "utf-8"));
7596
7148
  return {
7597
7149
  version: raw.version || 1,
7598
7150
  rules: raw.rules || [],
@@ -8186,9 +7738,9 @@ var init_reviewer = __esm({
8186
7738
  });
8187
7739
 
8188
7740
  // src/brain/skill-manager.ts
8189
- 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";
8190
7742
  import { homedir as homedir5 } from "os";
8191
- import { join as join8 } from "path";
7743
+ import { join as join12 } from "path";
8192
7744
  import { randomUUID } from "crypto";
8193
7745
  function getMetaPath(override) {
8194
7746
  return override || process.env[ENV_SKILL_META_PATH] || DEFAULT_SKILL_META_PATH;
@@ -8198,14 +7750,14 @@ var init_skill_manager = __esm({
8198
7750
  "src/brain/skill-manager.ts"() {
8199
7751
  "use strict";
8200
7752
  init_esm_shims();
8201
- DEFAULT_SKILL_META_PATH = join8(homedir5(), ".agentmoss", "brain", "skill_meta.json");
7753
+ DEFAULT_SKILL_META_PATH = join12(homedir5(), ".agentmoss", "brain", "skill_meta.json");
8202
7754
  ENV_SKILL_META_PATH = "AGENT_MOSS_BRAIN_SKILL_META_PATH";
8203
7755
  BrainSkillManager = class {
8204
7756
  constructor(skillsDir, metaPath) {
8205
7757
  this.skillsDir = skillsDir;
8206
7758
  this.metaPath = getMetaPath(metaPath);
8207
7759
  try {
8208
- mkdirSync4(join8(this.metaPath, ".."), { recursive: true });
7760
+ mkdirSync4(join12(this.metaPath, ".."), { recursive: true });
8209
7761
  } catch {
8210
7762
  }
8211
7763
  this.load();
@@ -8215,9 +7767,9 @@ var init_skill_manager = __esm({
8215
7767
  skills = [];
8216
7768
  metaPath;
8217
7769
  load() {
8218
- if (!existsSync13(this.metaPath)) return;
7770
+ if (!existsSync14(this.metaPath)) return;
8219
7771
  try {
8220
- const data = JSON.parse(readFileSync13(this.metaPath, "utf-8"));
7772
+ const data = JSON.parse(readFileSync14(this.metaPath, "utf-8"));
8221
7773
  this.skills = data.skills || [];
8222
7774
  } catch {
8223
7775
  this.skills = [];
@@ -8240,7 +7792,7 @@ var init_skill_manager = __esm({
8240
7792
  let changed = false;
8241
7793
  for (const sk of this.skills) {
8242
7794
  if (sk.status !== "active") continue;
8243
- if (!existsSync13(join8(this.skillsDir, `${sk.name}.md`))) {
7795
+ if (!existsSync14(join12(this.skillsDir, `${sk.name}.md`))) {
8244
7796
  sk.status = "stale";
8245
7797
  changed = true;
8246
7798
  }
@@ -8251,9 +7803,9 @@ var init_skill_manager = __esm({
8251
7803
  addSkill(skill) {
8252
7804
  const id = skill.id || randomUUID();
8253
7805
  const name = skill.name || `brain_${id.slice(0, 8)}`;
8254
- if (!existsSync13(this.skillsDir)) return "";
8255
- const mdPath = join8(this.skillsDir, `${name}.md`);
8256
- if (existsSync13(mdPath)) return "";
7806
+ if (!existsSync14(this.skillsDir)) return "";
7807
+ const mdPath = join12(this.skillsDir, `${name}.md`);
7808
+ if (existsSync14(mdPath)) return "";
8257
7809
  try {
8258
7810
  writeFileSync4(mdPath, skill.content, "utf-8");
8259
7811
  } catch {
@@ -8302,12 +7854,12 @@ var init_skill_manager = __esm({
8302
7854
  const out = [];
8303
7855
  for (const sk of this.skills) {
8304
7856
  if (sk.origin_node !== "local" || sk.status !== "active") continue;
8305
- const mdPath = join8(this.skillsDir, `${sk.name}.md`);
8306
- if (!existsSync13(mdPath)) continue;
7857
+ const mdPath = join12(this.skillsDir, `${sk.name}.md`);
7858
+ if (!existsSync14(mdPath)) continue;
8307
7859
  out.push({
8308
7860
  name: sk.name,
8309
7861
  category: sk.category || "",
8310
- content: readFileSync13(mdPath, "utf-8"),
7862
+ content: readFileSync14(mdPath, "utf-8"),
8311
7863
  keywords: sk.keywords || []
8312
7864
  });
8313
7865
  }
@@ -8595,9 +8147,10 @@ var init_decode = __esm({
8595
8147
  "src/engine/formal-engine/decode.ts"() {
8596
8148
  "use strict";
8597
8149
  init_esm_shims();
8150
+ init_rules_loader();
8598
8151
  B64_TOKEN_RE = /[A-Za-z0-9+/]+={0,2}/g;
8599
8152
  QUOTED_STR_RE = /('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`)/g;
8600
- OUTPUT_CMDS = /* @__PURE__ */ new Set(["echo", "printf", "print"]);
8153
+ OUTPUT_CMDS = new Set(loadFormalRules().output_commands ?? []);
8601
8154
  STAGE_PRIORITY = ["EXFIL", "PRIVESC", "PERSIST", "SCAN", "LISTEN"];
8602
8155
  PURPOSE_EXEC_RE = /subprocess\.\w+\s*\(|os\.system\s*\(|shell\s*=\s*True|\beval\s*\(|\bexec\s*\(|os\.popen\s*\(|\/exec\b/i;
8603
8156
  LISTEN_LEAD_RE = /http\.server|flask\s+run|nohup\b|nc\s+-l|socat\s+TCP-LISTEN/i;
@@ -9315,7 +8868,7 @@ function checkSensitiveDataAggregation(graph) {
9315
8868
  }
9316
8869
  return violations;
9317
8870
  }
9318
- 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;
9319
8872
  var init_properties = __esm({
9320
8873
  "src/engine/formal-engine/properties.ts"() {
9321
8874
  "use strict";
@@ -9332,11 +8885,12 @@ var init_properties = __esm({
9332
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;
9333
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;
9334
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;
9335
- PKG_MGR = /* @__PURE__ */ new Set(["apt", "apt-get", "aptitude", "yum", "dnf", "pacman", "zypper", "apk", "brew"]);
9336
- 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 ?? []);
9337
8891
  TOOL_REGISTRY = null;
9338
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;
9339
- STRONG_COMPUTE_TYPES = /* @__PURE__ */ new Set(["python", "node", "exec", "terminal", "cmd"]);
8893
+ STRONG_COMPUTE_TYPES = new Set(FORMAL_RULES.compute_action_types ?? []);
9340
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;
9341
8895
  ALL_PROPERTIES = [
9342
8896
  { id: "cross-session-chain", check: checkCrossSessionChain, shortName: "P1" },
@@ -9860,10 +9414,10 @@ var init_stateful = __esm({
9860
9414
  import * as fs4 from "fs";
9861
9415
  import * as os2 from "os";
9862
9416
  import * as path4 from "path";
9863
- import { createHash } from "crypto";
9417
+ import { createHash as createHash2 } from "crypto";
9864
9418
  function defaultStateDbPath() {
9865
9419
  const dir = process.env.AGENT_MOSS_STATE_DIR || path4.join(os2.homedir(), ".agentmoss");
9866
- 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);
9867
9421
  return path4.join(dir, `stateful_chains_${tag}.json`);
9868
9422
  }
9869
9423
  var ChainStateStore;
@@ -10123,7 +9677,7 @@ async function runAnalysis(input) {
10123
9677
  const actionType = input.aNext.actionType.toLowerCase();
10124
9678
  const rawDetail = extractRawCommand(input.aNext.actionDetail);
10125
9679
  const lowerDetail = rawDetail.toLowerCase();
10126
- if (FULLY_SAFE_TOOLS.has(actionType)) {
9680
+ if (getPlatformTraits(input.osType).safeActionTypes.has(actionType)) {
10127
9681
  return {
10128
9682
  allowed: true,
10129
9683
  reason: `\u5B8C\u5168\u5B89\u5168\u5DE5\u5177\uFF0C\u8DF3\u8FC7\u6DF1\u5EA6\u5206\u6790: ${actionType}`,
@@ -10166,7 +9720,7 @@ async function runAnalysis(input) {
10166
9720
  violatedLayers: []
10167
9721
  };
10168
9722
  }
10169
- let skipLLM = READONLY_SENSITIVE_TOOLS.has(actionType);
9723
+ let skipLLM = getPlatformTraits(input.osType).readonlySensitiveActionTypes.has(actionType);
10170
9724
  if (actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash") {
10171
9725
  const firstCmd = extractFirstCommand(lowerDetail);
10172
9726
  const { fullySafeCommands, readonlySensitiveCommands } = getPlatformTraits(input.osType);
@@ -10398,7 +9952,7 @@ async function runAnalysis(input) {
10398
9952
  }
10399
9953
  return llmResult;
10400
9954
  }
10401
- 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;
10402
9956
  var init_coordinator = __esm({
10403
9957
  "src/engine/coordinator.ts"() {
10404
9958
  "use strict";
@@ -10421,25 +9975,7 @@ var init_coordinator = __esm({
10421
9975
  init_asset_dir();
10422
9976
  init_runtime_config_bridge();
10423
9977
  init_formal_engine();
10424
- FULLY_SAFE_TOOLS = /* @__PURE__ */ new Set([
10425
- "ask_user_question",
10426
- "glob",
10427
- "list_dir",
10428
- "ls",
10429
- "count_text_length",
10430
- "filemgr-globfiles"
10431
- ]);
10432
9978
  TRUSTED_SKILLS_BASE = /* @__PURE__ */ new Set([]);
10433
- READONLY_SENSITIVE_TOOLS = /* @__PURE__ */ new Set([
10434
- "read",
10435
- "file_read",
10436
- "read_file",
10437
- "head",
10438
- "tail",
10439
- "grep",
10440
- "filemgr-readfile",
10441
- "filemgr-grepfiles"
10442
- ]);
10443
9979
  _brainStore = null;
10444
9980
  _brainExtractor = null;
10445
9981
  _brainSkillManager = null;
@@ -10454,10 +9990,150 @@ var init_coordinator = __esm({
10454
9990
  // src/server.ts
10455
9991
  init_esm_shims();
10456
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
10457
10133
  import { cors } from "hono/cors";
10458
10134
  import { logger } from "hono/logger";
10459
10135
  import net from "net";
10460
- import { existsSync as existsSync19 } from "fs";
10136
+ import { existsSync as existsSync20 } from "fs";
10461
10137
 
10462
10138
  // src/routes.ts
10463
10139
  init_esm_shims();
@@ -10547,18 +10223,18 @@ var ReviewResponseSchema = z.object({
10547
10223
  // src/engine/analyzer.ts
10548
10224
  init_esm_shims();
10549
10225
  init_coordinator();
10550
- 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";
10551
10227
  import { tmpdir as tmpdir2 } from "os";
10552
- import { join as join11, resolve as resolve10 } from "path";
10228
+ import { join as join15, resolve as resolve10 } from "path";
10553
10229
 
10554
10230
  // src/infra/policy-cache.ts
10555
10231
  init_esm_shims();
10556
10232
  init_config();
10557
- import { createHash as createHash2 } from "crypto";
10558
- 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";
10559
10235
  import { tmpdir } from "os";
10560
- import { join as join10 } from "path";
10561
- 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");
10562
10238
  var cacheDirOverride = null;
10563
10239
  function cacheDir() {
10564
10240
  return cacheDirOverride ?? CACHE_DIR;
@@ -10574,9 +10250,9 @@ var PolicyCache = class {
10574
10250
  return `${sessionId}::${promptSession}`;
10575
10251
  }
10576
10252
  cacheFilePath(sessionId, promptSession) {
10577
- const promptHash = createHash2("md5").update(promptSession).digest("hex").slice(0, 12);
10253
+ const promptHash = createHash3("md5").update(promptSession).digest("hex").slice(0, 12);
10578
10254
  const safeSessionId = sessionId.replace(/\//g, "_").replace(/\\/g, "_");
10579
- return join10(cacheDir(), safeSessionId, `${promptHash}.toml`);
10255
+ return join14(cacheDir(), safeSessionId, `${promptHash}.toml`);
10580
10256
  }
10581
10257
  /** 取缓存:内存 → 文件;未命中返回 null。 */
10582
10258
  get(sessionId, promptSession) {
@@ -10589,9 +10265,9 @@ var PolicyCache = class {
10589
10265
  return hit;
10590
10266
  }
10591
10267
  const file = this.cacheFilePath(sessionId, promptSession);
10592
- if (existsSync15(file)) {
10268
+ if (existsSync16(file)) {
10593
10269
  try {
10594
- const policy = readFileSync15(file, "utf-8");
10270
+ const policy = readFileSync16(file, "utf-8");
10595
10271
  this.remember(key, policy);
10596
10272
  return policy;
10597
10273
  } catch {
@@ -10605,7 +10281,7 @@ var PolicyCache = class {
10605
10281
  this.remember(this.cacheKey(sessionId, promptSession), policy);
10606
10282
  const file = this.cacheFilePath(sessionId, promptSession);
10607
10283
  try {
10608
- mkdirSync6(join10(cacheDir(), sessionId.replace(/\//g, "_").replace(/\\/g, "_")), { recursive: true });
10284
+ mkdirSync6(join14(cacheDir(), sessionId.replace(/\//g, "_").replace(/\\/g, "_")), { recursive: true });
10609
10285
  writeFileSync6(file, policy, "utf-8");
10610
10286
  } catch {
10611
10287
  }
@@ -10631,12 +10307,12 @@ var PolicyCache = class {
10631
10307
  if (k.startsWith(`${sessionId}::`)) this.memory.delete(k);
10632
10308
  }
10633
10309
  const safeSessionId = sessionId.replace(/\//g, "_").replace(/\\/g, "_");
10634
- const dir = join10(cacheDir(), safeSessionId);
10310
+ const dir = join14(cacheDir(), safeSessionId);
10635
10311
  try {
10636
- if (existsSync15(dir)) {
10312
+ if (existsSync16(dir)) {
10637
10313
  for (const f of readdirSync5(dir)) {
10638
10314
  try {
10639
- unlinkSync3(join10(dir, f));
10315
+ unlinkSync3(join14(dir, f));
10640
10316
  } catch {
10641
10317
  }
10642
10318
  }
@@ -10648,11 +10324,11 @@ var PolicyCache = class {
10648
10324
  clear() {
10649
10325
  this.memory.clear();
10650
10326
  try {
10651
- if (existsSync15(cacheDir())) {
10327
+ if (existsSync16(cacheDir())) {
10652
10328
  for (const sessionDir of readdirSync5(cacheDir())) {
10653
- const dir = join10(cacheDir(), sessionDir);
10329
+ const dir = join14(cacheDir(), sessionDir);
10654
10330
  try {
10655
- for (const f of readdirSync5(dir)) unlinkSync3(join10(dir, f));
10331
+ for (const f of readdirSync5(dir)) unlinkSync3(join14(dir, f));
10656
10332
  rmdirSync(dir);
10657
10333
  } catch {
10658
10334
  }
@@ -10739,7 +10415,7 @@ ${safe(policy, 2e3)}`);
10739
10415
 
10740
10416
  // src/engine/analyzer.ts
10741
10417
  import { parse as parseToml } from "smol-toml";
10742
- var POLICY_OUTPUT_DIR = join11(tmpdir2(), "agent_moss");
10418
+ var POLICY_OUTPUT_DIR = join15(tmpdir2(), "agent_moss");
10743
10419
  var policyOutputDirOverride = null;
10744
10420
  function policyOutputDir() {
10745
10421
  return policyOutputDirOverride ?? POLICY_OUTPUT_DIR;
@@ -10775,7 +10451,7 @@ function savePolicyToFile(sessionId, policyToml) {
10775
10451
  const outDir = policyOutputDir();
10776
10452
  mkdirSync7(outDir, { recursive: true });
10777
10453
  const safeName = sessionId.replace(/\//g, "_").replace(/\\/g, "_");
10778
- const filepath = join11(outDir, `${safeName}.toml`);
10454
+ const filepath = join15(outDir, `${safeName}.toml`);
10779
10455
  writeFileSync7(filepath, policyToml, "utf-8");
10780
10456
  return filepath;
10781
10457
  } catch (err) {
@@ -10814,11 +10490,11 @@ function loadPolicyMappingDoc() {
10814
10490
  const dir = resolveAssetDir(__dirname, "templates");
10815
10491
  if (dir) {
10816
10492
  const p = resolve10(dir, "policy_mapping.md");
10817
- if (existsSync16(p)) return readFileSync16(p, "utf-8");
10493
+ if (existsSync17(p)) return readFileSync17(p, "utf-8");
10818
10494
  }
10819
10495
  for (const cand of assetDirCandidates(__dirname, "templates")) {
10820
10496
  const p = resolve10(cand, "policy_mapping.md");
10821
- if (existsSync16(p)) return readFileSync16(p, "utf-8");
10497
+ if (existsSync17(p)) return readFileSync17(p, "utf-8");
10822
10498
  }
10823
10499
  return "";
10824
10500
  }
@@ -11093,9 +10769,11 @@ init_formal_engine();
11093
10769
 
11094
10770
  // src/engine/formal-engine/pattern_miner.ts
11095
10771
  init_esm_shims();
10772
+ init_rules_loader();
11096
10773
  init_builder();
11097
- var DANGER_STAGES = /* @__PURE__ */ new Set(["PRIVESC", "EXFIL", "LISTEN"]);
11098
- 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("|")));
11099
10777
  function projectStages(actionHistory) {
11100
10778
  const stages = [];
11101
10779
  let prev = null;
@@ -11335,7 +11013,7 @@ function normalizeObservablePayload(body) {
11335
11013
  init_rule_store();
11336
11014
  init_skill_manager();
11337
11015
  init_asset_dir();
11338
- import { join as join13, dirname as dirname11 } from "path";
11016
+ import { join as join17, dirname as dirname11 } from "path";
11339
11017
  import { fileURLToPath as fileURLToPath10 } from "url";
11340
11018
  var __dirname9 = dirname11(fileURLToPath10(import.meta.url));
11341
11019
  async function healthRoute(c) {
@@ -11586,7 +11264,7 @@ function getBrainRuleStore() {
11586
11264
  return new BrainRuleStore();
11587
11265
  }
11588
11266
  function getBrainSkillManager() {
11589
- const dir = resolveAssetDir(__dirname9, "skills") ?? join13(__dirname9, "..", "..", "agent_moss", "skills");
11267
+ const dir = resolveAssetDir(__dirname9, "skills") ?? join17(__dirname9, "..", "..", "agent_moss", "skills");
11590
11268
  return new BrainSkillManager(dir);
11591
11269
  }
11592
11270
  async function brainRulesRoute(c) {
@@ -11689,11 +11367,11 @@ init_rules_loader();
11689
11367
  init_asset_dir();
11690
11368
  init_rules_mode();
11691
11369
  import { Hono } from "hono";
11692
- import { readFileSync as readFileSync18, existsSync as existsSync18, unlinkSync as unlinkSync4 } from "fs";
11693
- 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";
11694
11372
  import { fileURLToPath as fileURLToPath11 } from "url";
11695
11373
  var __dirname10 = dirname12(fileURLToPath11(import.meta.url));
11696
- var STATIC_DIR = join14(__dirname10, "static");
11374
+ var STATIC_DIR = join18(__dirname10, "static");
11697
11375
  var CONSOLE_TOKEN_ENV = "AGENT_MOSS_CONSOLE_TOKEN";
11698
11376
  function getConsoleToken() {
11699
11377
  return process.env[CONSOLE_TOKEN_ENV] || "";
@@ -11731,9 +11409,9 @@ function createConsoleRouter() {
11731
11409
  console2.use("/api/*", authMiddleware());
11732
11410
  console2.use("/api/*", builtinOnlyReadOnlyMiddleware());
11733
11411
  const serveIndex = (c) => {
11734
- const indexFile = join14(STATIC_DIR, "index.html");
11735
- if (existsSync18(indexFile)) {
11736
- 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"));
11737
11415
  }
11738
11416
  return c.html("<h1>AgentMoss Policy Console</h1><p>Frontend not built. Use /console/api/* endpoints.</p>");
11739
11417
  };
@@ -11928,15 +11606,15 @@ function createConsoleRouter() {
11928
11606
  if (!/^[A-Za-z0-9_-]+$/.test(skillId)) {
11929
11607
  return c.json({ detail: "\u975E\u6CD5 skill id" }, 400);
11930
11608
  }
11931
- const userPath = join14(dirname12(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
11609
+ const userPath = join18(dirname12(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
11932
11610
  const builtinDir = resolveAssetDir(__dirname10, "skills");
11933
11611
  const candidates = [
11934
11612
  ...isRuntimeConfigDisabled() ? [] : [userPath],
11935
- builtinDir ? join14(builtinDir, `${skillId}.md`) : ""
11613
+ builtinDir ? join18(builtinDir, `${skillId}.md`) : ""
11936
11614
  ].filter(Boolean);
11937
11615
  for (const p of candidates) {
11938
- if (existsSync18(p)) {
11939
- 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") });
11940
11618
  }
11941
11619
  }
11942
11620
  return c.json({ detail: `skill ${skillId} \u4E0D\u5B58\u5728` }, 404);
@@ -12033,7 +11711,7 @@ function createConsoleRouter() {
12033
11711
  const defaults = generateSourceDefaults();
12034
11712
  try {
12035
11713
  const p = getRuntimeConfigPath();
12036
- if (existsSync18(p)) unlinkSync4(p);
11714
+ if (existsSync19(p)) unlinkSync4(p);
12037
11715
  } catch {
12038
11716
  }
12039
11717
  return c.json(withL22View(defaults));
@@ -12090,7 +11768,7 @@ function logDeploymentSelfCheck() {
12090
11768
  console.log("\u5C42\u7EA7\u5F00\u5173: " + layerKeys.map((k) => `${k}=${isLayerEnabled(k) ? "on" : "OFF"}`).join(" "));
12091
11769
  if (!builtinOnly) {
12092
11770
  try {
12093
- const dirs = rulesDirs().filter((d) => existsSync19(d));
11771
+ const dirs = rulesDirs().filter((d) => existsSync20(d));
12094
11772
  if (dirs.length > 0) {
12095
11773
  console.log(`\u89C4\u5219\u76EE\u5F55: ${dirs.join(", ")}\uFF08\u53EA\u8BFB\u5176\u4E2D ${OVERRIDE_FILENAME} / ${EXTRA_FILENAME}\uFF09`);
12096
11774
  }
@@ -12129,6 +11807,7 @@ function logDeploymentSelfCheck() {
12129
11807
  }
12130
11808
  }
12131
11809
  function createApp() {
11810
+ enforceRulesContract();
12132
11811
  const app = new Hono2();
12133
11812
  app.use("*", cors({ origin: "*", allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], allowHeaders: ["*"] }));
12134
11813
  app.use("*", logger());
@@ -12223,7 +11902,7 @@ async function startUnixSocketServer(socketPath = "/var/run/agent_moss/agent_mos
12223
11902
  server = null;
12224
11903
  },
12225
11904
  serve: async () => {
12226
- const { mkdirSync: mkdirSync8, existsSync: existsSync20, unlinkSync: unlinkSync5 } = await import("fs");
11905
+ const { mkdirSync: mkdirSync8, existsSync: existsSync21, unlinkSync: unlinkSync5 } = await import("fs");
12227
11906
  const { dirname: dirname13 } = await import("path");
12228
11907
  const dir = dirname13(socketPath);
12229
11908
  if (dir) {
@@ -12232,7 +11911,7 @@ async function startUnixSocketServer(socketPath = "/var/run/agent_moss/agent_mos
12232
11911
  } catch {
12233
11912
  }
12234
11913
  }
12235
- if (existsSync20(socketPath)) {
11914
+ if (existsSync21(socketPath)) {
12236
11915
  const { connect } = await import("net");
12237
11916
  const inUse = await new Promise((resolve11) => {
12238
11917
  const probe = connect(socketPath);