@kenhkl/agent-moss 0.16.12 → 0.16.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,6 +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");
109
+ ENV_RULES_ROOT = "AGENT_MOSS_RULES_ROOT";
105
110
  }
106
111
  });
107
112
 
@@ -149,235 +154,49 @@ var init_shell_split = __esm({
149
154
  }
150
155
  });
151
156
 
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;
157
+ // src/infra/rules-mode.ts
158
+ function truthy(name) {
159
+ return TRUTHY.has((process.env[name] || "").trim().toLowerCase());
160
+ }
161
+ function getRulesMode() {
162
+ if (cached !== null) return cached;
163
+ const raw = (process.env[ENV_RULES_MODE] || "").trim().toLowerCase();
164
+ if (raw) {
165
+ if (VALID_MODES.has(raw)) {
166
+ cached = raw;
167
+ } else {
168
+ console.error(
169
+ `[AgentMoss] ${ENV_RULES_MODE}=${JSON.stringify(raw)} \u975E\u6CD5\uFF08\u53EA\u652F\u6301 hot / cold / builtin\uFF09\uFF1B\u4E3A\u5B89\u5168\u8D77\u89C1\u6309 builtin\uFF08\u4EC5\u5185\u7F6E\u89C4\u5219\uFF09\u5904\u7406`
170
+ );
171
+ cached = "builtin";
163
172
  }
173
+ return cached;
164
174
  }
165
- if (/\|\s*\b(?:tee|tac|install)\b/i.test(command)) {
166
- return false;
167
- }
168
- return true;
175
+ if (truthy(ENV_RULES_BUILTIN_ONLY)) cached = "builtin";
176
+ else if (truthy(ENV_DISABLE_RULE_HOT_RELOAD)) cached = "cold";
177
+ else cached = "hot";
178
+ return cached;
169
179
  }
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");
180
+ function isHotReloadEnabled() {
181
+ return getRulesMode() === "hot";
184
182
  }
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));
183
+ function isRuntimeConfigDisabled() {
184
+ return getRulesMode() !== "hot";
194
185
  }
195
- function hasReadWriteKeyword(code) {
196
- const lower = code.toLowerCase();
197
- return READ_WRITE_KEYWORDS.some((kw) => lower.includes(kw));
186
+ function isBuiltinOnly() {
187
+ return getRulesMode() === "builtin";
198
188
  }
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"() {
189
+ var ENV_RULES_MODE, ENV_RULES_BUILTIN_ONLY, ENV_DISABLE_RULE_HOT_RELOAD, TRUTHY, VALID_MODES, cached;
190
+ var init_rules_mode = __esm({
191
+ "src/infra/rules-mode.ts"() {
248
192
  "use strict";
249
193
  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
- ];
194
+ ENV_RULES_MODE = "AGENT_MOSS_RULES_MODE";
195
+ ENV_RULES_BUILTIN_ONLY = "AGENT_MOSS_RULES_BUILTIN_ONLY";
196
+ ENV_DISABLE_RULE_HOT_RELOAD = "AGENT_MOSS_DISABLE_RULE_HOT_RELOAD";
197
+ TRUTHY = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
198
+ VALID_MODES = /* @__PURE__ */ new Set(["hot", "cold", "builtin"]);
199
+ cached = null;
381
200
  }
382
201
  });
383
202
 
@@ -407,23 +226,48 @@ function parseDirEnv(envName) {
407
226
  return raw.split(/[,;]/).map((s) => s.trim()).filter(Boolean).map((s) => resolve(s));
408
227
  }
409
228
  function rulesDirs() {
229
+ if (isBuiltinOnly()) return [];
410
230
  const dirs = [];
411
231
  for (const env of RULES_DIR_ENVS) dirs.push(...parseDirEnv(env));
412
232
  dirs.push(resolve(dataRootDir(), "rules"));
413
233
  return [...new Set(dirs)];
414
234
  }
235
+ function rulesDirCands() {
236
+ const root = getRulesRoot();
237
+ if (root) return [join2(root, "rules")];
238
+ const cands = [
239
+ // 注:`<data_root>/rules/` 不再是"按内置文件名覆盖"的查找位 —— 2026-10 起
240
+ // 规则目录只认 override.json / extra.json(见 rulesDirs())。
241
+ // dist: __dirname=dist/ → dist/rules;__dirname=dist/engine/ → dist/engine/../rules=dist/rules
242
+ resolve(__dirname2, "../rules"),
243
+ resolve(__dirname2, "rules"),
244
+ // dev src: __dirname=ts/src/infra/ → ts/src/infra/../../../agent_moss/rules = 仓库根 agent_moss/rules
245
+ resolve(__dirname2, "../../../agent_moss/rules"),
246
+ // dist 但 __dirname 指向 dist/engine 再往上两级的仓库根 agent_moss/rules
247
+ resolve(__dirname2, "../../../../agent_moss/rules"),
248
+ // 相对当前工作目录的仓库根(兜底,兼容以仓库根为 cwd 运行)
249
+ resolve(process.cwd(), "agent_moss/rules")
250
+ ];
251
+ return cands.filter((c) => existsSync(c));
252
+ }
415
253
  function findRulesFile(name) {
416
- for (const dir of RULES_DIR_CANDS) {
254
+ for (const dir of rulesDirCands()) {
417
255
  const p = resolve(dir, name);
418
256
  if (existsSync(p)) return p;
419
257
  }
420
258
  return null;
421
259
  }
422
260
  function loadJson(name) {
261
+ if (!isHotReloadEnabled()) {
262
+ const cached2 = frozenJsonCache.get(name);
263
+ if (cached2 !== void 0) return cached2;
264
+ }
423
265
  const p = findRulesFile(name);
424
266
  if (!p) return {};
425
267
  try {
426
- return JSON.parse(readFileSync2(p, "utf-8")) || {};
268
+ const parsed = JSON.parse(readFileSync2(p, "utf-8")) || {};
269
+ if (!isHotReloadEnabled()) frozenJsonCache.set(name, parsed);
270
+ return parsed;
427
271
  } catch {
428
272
  return {};
429
273
  }
@@ -463,6 +307,74 @@ function loadL1() {
463
307
  function loadL2() {
464
308
  return loadJson("l2_rules.json");
465
309
  }
310
+ function regexFlags(flags) {
311
+ let out = "";
312
+ for (const ch of String(flags ?? "")) if (RE_FLAG_MAP[ch]) out += RE_FLAG_MAP[ch];
313
+ return out;
314
+ }
315
+ function compileRuleRegex(rule, extraFlags = "") {
316
+ return new RegExp(String(rule.pattern), regexFlags(rule.flags) + extraFlags);
317
+ }
318
+ function resolveKeywordSet(group, keywordSets) {
319
+ if (typeof group === "string") return new Set(keywordSets[group.slice(1)] ?? []);
320
+ return new Set(Array.isArray(group) ? group : []);
321
+ }
322
+ function loadFormalRules() {
323
+ return loadJson("formal_rules.json");
324
+ }
325
+ function loadLogicRules() {
326
+ return loadJson("logic_rules.json");
327
+ }
328
+ function loadHeuristicRules() {
329
+ return loadJson("heuristic_rules.json");
330
+ }
331
+ function loadScriptRules() {
332
+ return loadJson("script_rules.json");
333
+ }
334
+ function loadInline() {
335
+ return loadJson("inline_rules.json");
336
+ }
337
+ function loadPlatformTraits(osName) {
338
+ const data = loadJson("platform_traits.json");
339
+ if (!data || Object.keys(data).length === 0) {
340
+ console.error(
341
+ "[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"
342
+ );
343
+ return {};
344
+ }
345
+ const name = resolveTraitsOs(osName);
346
+ const entry = data[name];
347
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
348
+ console.error(`[AgentMoss] platform_traits.json \u91CC\u6CA1\u6709\u53EF\u7528\u7684 ${name} \u6761\u76EE \u2014\u2014 \u5E73\u53F0\u7279\u5F81\u4E3A\u7A7A\u3002`);
349
+ return {};
350
+ }
351
+ const out = {};
352
+ for (const f of TRAITS_FIELDS) if (f in entry) out[f] = entry[f];
353
+ return out;
354
+ }
355
+ function resolveTraitsOs(osName) {
356
+ const data = loadJson("platform_traits.json");
357
+ let name = String(osName || "").trim().toLowerCase() || TRAITS_OS_FALLBACK;
358
+ const seen = /* @__PURE__ */ new Set();
359
+ for (; ; ) {
360
+ if (seen.has(name)) {
361
+ console.error(`[AgentMoss] platform_traits.json \u7684 inherits \u6210\u73AF\uFF08${name}\uFF09\u2014\u2014 \u56DE\u843D linux\u3002`);
362
+ return TRAITS_OS_FALLBACK;
363
+ }
364
+ seen.add(name);
365
+ const entry = data[name];
366
+ if (entry && typeof entry === "object" && !Array.isArray(entry)) {
367
+ const nxt = entry.inherits;
368
+ if (typeof nxt === "string" && nxt.trim().toLowerCase() !== name) {
369
+ name = nxt.trim().toLowerCase();
370
+ continue;
371
+ }
372
+ return name;
373
+ }
374
+ if (name === TRAITS_OS_FALLBACK) return name;
375
+ name = TRAITS_OS_FALLBACK;
376
+ }
377
+ }
466
378
  function loadL3() {
467
379
  const data = { ...loadJson("l3_rules.json") };
468
380
  try {
@@ -748,6 +660,7 @@ function scanRuleDirs() {
748
660
  return { override, extra, loaded, ignored };
749
661
  }
750
662
  function rulesCached() {
663
+ if (!isHotReloadEnabled() && _RULES_CACHE) return _RULES_CACHE;
751
664
  const dirs = rulesDirs();
752
665
  const sig = RULES_DIR_ENVS.map((e) => `${e}=${process.env[e] || ""}`).join("|") + "|" + dirs.join("|");
753
666
  const files = [];
@@ -783,40 +696,40 @@ function identicalToBuiltin(override) {
783
696
  return Object.keys(override).length > 0;
784
697
  }
785
698
  function loadOverrideRules() {
699
+ if (isBuiltinOnly()) return {};
786
700
  return rulesCached().override;
787
701
  }
788
702
  function loadExtraRules() {
703
+ if (isBuiltinOnly()) return {};
789
704
  return rulesCached().extra;
790
705
  }
791
- var __filename2, __dirname2, OVERRIDE_FILENAME, EXTRA_FILENAME, RULES_DIR_ENVS, RULES_DIR_CANDS, SCENARIO_LIST_KEYS, EXTRA_CATEGORIES, STRUCT_KEYS, EXTRA_FLAT_FIELD, _RULES_CACHE, warnedIgnored;
706
+ 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;
792
707
  var init_rules_loader = __esm({
793
708
  "src/infra/rules-loader.ts"() {
794
709
  "use strict";
795
710
  init_esm_shims();
796
711
  init_paths();
712
+ init_rules_mode();
797
713
  init_runtime_config_bridge();
798
714
  __filename2 = fileURLToPath2(import.meta.url);
799
715
  __dirname2 = dirname(__filename2);
800
716
  OVERRIDE_FILENAME = "override.json";
801
717
  EXTRA_FILENAME = "extra.json";
802
718
  RULES_DIR_ENVS = ["AGENT_MOSS_EXTRA_RULES_DIR", "AGENT_MOSS_OVERRIDE_RULES_DIR"];
803
- RULES_DIR_CANDS = (() => {
804
- const cands = [
805
- // 注:`<data_root>/rules/` 不再是"按内置文件名覆盖"的查找位 —— 2026-10 起
806
- // 规则目录只认 override.json / extra.json(见 rulesDirs())。
807
- // dist: __dirname=dist/ → dist/rules;__dirname=dist/engine/ → dist/engine/../rules=dist/rules
808
- resolve(__dirname2, "../rules"),
809
- resolve(__dirname2, "rules"),
810
- // dev src: __dirname=ts/src/infra/ → ts/src/infra/../../../agent_moss/rules = 仓库根 agent_moss/rules
811
- resolve(__dirname2, "../../../agent_moss/rules"),
812
- // dist 但 __dirname 指向 dist/engine 再往上两级的仓库根 agent_moss/rules
813
- resolve(__dirname2, "../../../../agent_moss/rules"),
814
- // 相对当前工作目录的仓库根(兜底,兼容以仓库根为 cwd 运行)
815
- resolve(process.cwd(), "agent_moss/rules")
816
- ];
817
- return cands.filter((c) => existsSync(c));
818
- })();
719
+ frozenJsonCache = /* @__PURE__ */ new Map();
819
720
  SCENARIO_LIST_KEYS = ["extra_command_patterns", "extra_injection_keywords", "extra_sensitive_paths"];
721
+ RE_FLAG_MAP = { i: "i", s: "s", m: "m" };
722
+ TRAITS_FIELDS = [
723
+ "write_keywords",
724
+ "read_keywords",
725
+ "write_command_res",
726
+ "read_only_commands",
727
+ "fully_safe_commands",
728
+ "readonly_sensitive_commands",
729
+ "safe_action_types",
730
+ "readonly_sensitive_action_types"
731
+ ];
732
+ TRAITS_OS_FALLBACK = "linux";
820
733
  EXTRA_CATEGORIES = {
821
734
  dangerous_commands: ["pattern"],
822
735
  injection_detection: ["keyword", "lang"],
@@ -838,7 +751,155 @@ var init_rules_loader = __esm({
838
751
  }
839
752
  });
840
753
 
754
+ // src/engine/inline-analyzer.ts
755
+ function buildChainRegex(pattern, flags) {
756
+ return new RegExp(pattern, (flags || "") + "g");
757
+ }
758
+ function isLiteralTextCommand(command) {
759
+ const segments = splitCommandSegments(command);
760
+ if (segments.length === 0 || segments.some((s) => !ECHO_SEG_RE.test(s))) return false;
761
+ if (command.includes(">")) {
762
+ let d = command.replace(/<=>|>=|<=|<>|=>/g, " ");
763
+ d = d.replace(/(?:2|&|1)?\s*>\s*\/dev\/null\b/g, "");
764
+ d = d.replace(new RegExp(`>{1,2}\\s*(['"])([~./][^'"]+)\\1`, "g"), (_m, _q, p) => ">" + p);
765
+ d = d.replace(/'[^']*'|"[^"]*"/g, "");
766
+ if (/>{1,2}\s*(?!&)\S/.test(d)) {
767
+ return false;
768
+ }
769
+ }
770
+ if (/\|\s*\b(?:tee|tac|install)\b/i.test(command)) {
771
+ return false;
772
+ }
773
+ return true;
774
+ }
775
+ function extractInlineCode(command) {
776
+ const parts = [];
777
+ let m;
778
+ INLINE_CODE_RE.lastIndex = 0;
779
+ while ((m = INLINE_CODE_RE.exec(command)) !== null) {
780
+ const code = m[1] ?? m[2];
781
+ if (code) parts.push(code);
782
+ }
783
+ ECHO_STRING_RE.lastIndex = 0;
784
+ while ((m = ECHO_STRING_RE.exec(command)) !== null) {
785
+ const code = m[1] ?? m[2];
786
+ if (code) parts.push(code);
787
+ }
788
+ return parts.join("\n");
789
+ }
790
+ function hasWriteExecChain(command) {
791
+ return WRITE_EXEC_CHAIN_PATTERNS.some((p) => {
792
+ p.lastIndex = 0;
793
+ return p.test(command);
794
+ });
795
+ }
796
+ function hasDirTraversal(code) {
797
+ const lower = code.toLowerCase();
798
+ return DIR_TRAVERSAL_KEYWORDS.some((kw) => lower.includes(kw));
799
+ }
800
+ function hasReadWriteKeyword(code) {
801
+ const lower = code.toLowerCase();
802
+ return READ_WRITE_KEYWORDS.some((kw) => lower.includes(kw));
803
+ }
804
+ function analyzeInlineScript(command) {
805
+ const code = extractInlineCode(command);
806
+ if (!code) {
807
+ return { actionable: false, allowed: true, reason: "", riskLevel: "low", riskType: "" };
808
+ }
809
+ if (hasWriteExecChain(command)) {
810
+ return {
811
+ actionable: true,
812
+ allowed: false,
813
+ 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",
814
+ riskLevel: "critical",
815
+ riskType: "script_execution"
816
+ };
817
+ }
818
+ const redirMatch = command.match(REDIRECT_TO_SENSITIVE_RE);
819
+ if (redirMatch) {
820
+ return {
821
+ actionable: true,
822
+ allowed: false,
823
+ reason: `\u91CD\u5B9A\u5411\u5199\u5165\u654F\u611F\u8DEF\u5F84: ${redirMatch[1]}`,
824
+ riskLevel: "critical",
825
+ riskType: "file_access"
826
+ };
827
+ }
828
+ const isEchoOrPrintf1 = isLiteralTextCommand(command);
829
+ if (!isEchoOrPrintf1 && hasDirTraversal(code) && hasReadWriteKeyword(code)) {
830
+ return { actionable: false, allowed: true, reason: "", riskLevel: "low", riskType: "" };
831
+ }
832
+ const isEchoOrPrintf = isLiteralTextCommand(command);
833
+ if (!isEchoOrPrintf && hasReadWriteKeyword(code)) {
834
+ return {
835
+ actionable: true,
836
+ allowed: false,
837
+ 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",
838
+ riskLevel: "critical",
839
+ riskType: "file_access"
840
+ };
841
+ }
842
+ return {
843
+ actionable: true,
844
+ allowed: true,
845
+ reason: "\u654F\u611F\u8DEF\u5F84\u4EC5\u4F5C\u4E3A\u6587\u672C\u5B57\u9762\u91CF\u51FA\u73B0\uFF0C\u672A\u4F34\u968F\u8BFB\u5199\u64CD\u4F5C",
846
+ riskLevel: "low",
847
+ riskType: "file_access"
848
+ };
849
+ }
850
+ 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;
851
+ var init_inline_analyzer = __esm({
852
+ "src/engine/inline-analyzer.ts"() {
853
+ "use strict";
854
+ init_esm_shims();
855
+ init_shell_split();
856
+ init_rules_loader();
857
+ INLINE_RULES = loadInline();
858
+ if (!INLINE_RULES || Object.keys(INLINE_RULES).length === 0) {
859
+ console.error(
860
+ "[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"
861
+ );
862
+ }
863
+ INLINE_CODE_RE = /(?:python|python3|perl|perl5|ruby|jruby|mruby|node|php|lua)\s+(?:-[a-zA-Z]*[cera][a-zA-Z]*\s+)"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
864
+ ECHO_STRING_RE = /\b(?:echo|printf)\s+"([^"\\]*(?:\\.[^"\\]*)*)"|\b(?:echo|printf)\s+'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
865
+ ECHO_SEG_RE = /^\s*(?:echo|printf)(?:\s|$)/i;
866
+ READ_WRITE_KEYWORDS = INLINE_RULES.read_write_keywords ?? [];
867
+ WRITE_EXEC_CHAIN_PATTERNS = (INLINE_RULES.write_exec_chain_patterns ?? []).filter((p) => p && typeof p.pattern === "string").map((p) => buildChainRegex(p.pattern, p.flags ?? ""));
868
+ _REDIRECT_PATHS = INLINE_RULES.redirect_sensitive_paths ?? {};
869
+ SHELL_RC_FRAGMENTS = _REDIRECT_PATHS.shell_rc_fragments ?? [];
870
+ SENSITIVE_PATH_FRAGMENTS = [
871
+ ..._REDIRECT_PATHS.ts_static ?? [],
872
+ ...SHELL_RC_FRAGMENTS
873
+ ];
874
+ REDIRECT_TO_SENSITIVE_RE = new RegExp(`>{1,2}\\s*(?:\\S*/)?(${SENSITIVE_PATH_FRAGMENTS.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})(?:\\s|$)`, "i");
875
+ DIR_TRAVERSAL_KEYWORDS = INLINE_RULES.dir_traversal_keywords ?? [];
876
+ }
877
+ });
878
+
841
879
  // src/engine/platform-traits.ts
880
+ function buildTraits(osName) {
881
+ const t = loadPlatformTraits(osName);
882
+ const strs = (k) => Array.isArray(t[k]) ? t[k] : [];
883
+ return {
884
+ writeKeywords: strs("write_keywords"),
885
+ readKeywords: strs("read_keywords"),
886
+ writeCommandRes: strs("write_command_res").map((p) => new RegExp(p)),
887
+ readOnlyCommands: new Set(strs("read_only_commands")),
888
+ fullySafeCommands: strs("fully_safe_commands").map((p) => new RegExp(p, "i")),
889
+ readonlySensitiveCommands: strs("readonly_sensitive_commands").map((p) => new RegExp(p, "i")),
890
+ safeActionTypes: new Set(strs("safe_action_types")),
891
+ readonlySensitiveActionTypes: new Set(strs("readonly_sensitive_action_types"))
892
+ };
893
+ }
894
+ function traitsFor(key) {
895
+ const resolved = resolveTraitsOs(key);
896
+ let t = traitsCache.get(resolved);
897
+ if (!t) {
898
+ t = buildTraits(resolved);
899
+ traitsCache.set(resolved, t);
900
+ }
901
+ return t;
902
+ }
842
903
  function getPlatformTraits(osType) {
843
904
  const accepted = acceptedOsValues(osType);
844
905
  if (accepted.includes("windows")) return WINDOWS_TRAITS;
@@ -846,368 +907,18 @@ function getPlatformTraits(osType) {
846
907
  if (accepted.includes("harmony")) return HARMONY_TRAITS;
847
908
  return LINUX_TRAITS;
848
909
  }
849
- var LINUX_TRAITS, WINDOWS_TRAITS, MACOS_TRAITS, HARMONY_TRAITS;
910
+ var traitsCache, LINUX_TRAITS, WINDOWS_TRAITS, MACOS_TRAITS, HARMONY_TRAITS;
850
911
  var init_platform_traits = __esm({
851
912
  "src/engine/platform-traits.ts"() {
852
913
  "use strict";
853
914
  init_esm_shims();
854
915
  init_logic_rules();
855
- LINUX_TRAITS = {
856
- writeKeywords: [
857
- "write",
858
- "\u5199\u5165",
859
- "save",
860
- "\u4FDD\u5B58",
861
- "modify",
862
- "\u4FEE\u6539",
863
- "update",
864
- "\u66F4\u65B0",
865
- "delete",
866
- "\u5220\u9664",
867
- "remove",
868
- "\u79FB\u9664",
869
- "create",
870
- "\u521B\u5EFA",
871
- "append",
872
- "\u8FFD\u52A0",
873
- "truncate",
874
- "\u622A\u65AD",
875
- "overwrite",
876
- "\u8986\u76D6",
877
- "replace",
878
- "\u66FF\u6362",
879
- "edit"
880
- ],
881
- readKeywords: [
882
- "read",
883
- "\u8BFB\u53D6",
884
- "cat",
885
- "head",
886
- "tail",
887
- "less",
888
- "more",
889
- "view",
890
- "\u67E5\u770B",
891
- "open",
892
- "\u6253\u5F00",
893
- "load",
894
- "\u52A0\u8F7D",
895
- "grep",
896
- "search",
897
- "\u641C\u7D22",
898
- "find",
899
- "\u67E5\u627E"
900
- ],
901
- writeCommandRes: [
902
- /\brm\b/,
903
- /\bunlink\b/,
904
- /\brmdir\b/,
905
- /\bshred\b/,
906
- /\bcp\b/,
907
- /\bmv\b/,
908
- /\binstall\b/,
909
- /\btee\b/,
910
- /\bdd\b/,
911
- /\bmkfs\b/,
912
- /\b(fdisk|parted|cfdisk|sfdisk)\b/,
913
- /\bchmod\b/,
914
- /\bchown\b/,
915
- /\btruncate\b/
916
- ],
917
- readOnlyCommands: /* @__PURE__ */ new Set([
918
- "cat",
919
- "head",
920
- "tail",
921
- "less",
922
- "more",
923
- "grep",
924
- "find",
925
- "awk",
926
- "sed",
927
- "sort",
928
- "uniq",
929
- "wc",
930
- "cut",
931
- "strings",
932
- "od",
933
- "xxd",
934
- "hexdump",
935
- "tr",
936
- "file",
937
- "stat",
938
- "du",
939
- "df",
940
- "ls",
941
- "dir",
942
- "tree",
943
- "nl",
944
- "tac",
945
- "rev",
946
- "echo",
947
- "printf",
948
- "lsblk",
949
- "blockdev",
950
- "smartctl",
951
- "udevadm",
952
- "dmidecode",
953
- "lscpu",
954
- "lspci",
955
- "lsusb",
956
- "lsmem",
957
- "lsns",
958
- "lsof",
959
- "hwinfo",
960
- "inxi",
961
- "fdisk",
962
- "parted",
963
- "dmesg",
964
- "journalctl",
965
- "systemctl",
966
- "hostnamectl",
967
- "localectl",
968
- "timedatectl",
969
- "ps",
970
- "top",
971
- "free",
972
- "vmstat",
973
- "iostat",
974
- "mpstat",
975
- "pidof",
976
- "pgrep",
977
- "ip",
978
- "ifconfig",
979
- "route",
980
- "ss",
981
- "netstat",
982
- "arp",
983
- "ethtool",
984
- "nmcli",
985
- "uname",
986
- "arch",
987
- "nproc",
988
- "getconf",
989
- "getent",
990
- "id",
991
- "whoami",
992
- "who",
993
- "w",
994
- "last",
995
- "uptime",
996
- "lsmod",
997
- "modinfo",
998
- "sysctl"
999
- ]),
1000
- fullySafeCommands: [
1001
- /^ls(?:\s|$)/i,
1002
- /^dir(?:\s|$)/i,
1003
- /^pwd(?:\s|$)/i,
1004
- /^which\s+/i,
1005
- /^whereis\s+/i,
1006
- /^realpath\s+/i,
1007
- /^basename\s+/i,
1008
- /^dirname\s+/i,
1009
- /^file\s+/i,
1010
- /^stat\s+/i,
1011
- /^du(?:\s|$)/i,
1012
- /^echo\s+/i,
1013
- /^printf\s+/i,
1014
- /^type\s+/i,
1015
- /^command\s+/i,
1016
- // 系统信息查询(只读,不涉及文件/网络/权限)
1017
- /^whoami(?:\s|$)/i,
1018
- /^id(?:\s|$)/i,
1019
- /^hostname(?:\s|$)/i,
1020
- /^uname(?:\s|$)/i,
1021
- /^date(?:\s|$)/i,
1022
- /^env(?:\s|$)/i,
1023
- /^printenv(?:\s|$)/i,
1024
- /^tty(?:\s|$)/i,
1025
- /^arch(?:\s|$)/i,
1026
- /^uptime(?:\s|$)/i,
1027
- /^groups(?:\s|$)/i,
1028
- /^logname(?:\s|$)/i,
1029
- // 系统资源/硬件信息(只读,不读文件内容、无状态变更选项)
1030
- // 与上面 uptime/uname/id 同性质;缺了它们时开 L3 的部署每查一次磁盘/内存
1031
- // 都要走 LLM(实测 df -h 从 5ms 变成十几秒)。
1032
- /^df(?:\s|$)/i,
1033
- /^free(?:\s|$)/i,
1034
- /^vmstat(?:\s|$)/i,
1035
- /^nproc(?:\s|$)/i,
1036
- /^lscpu(?:\s|$)/i,
1037
- /^lsblk(?:\s|$)/i,
1038
- /^lspci(?:\s|$)/i,
1039
- /^lsusb(?:\s|$)/i,
1040
- /^lsmod(?:\s|$)/i
1041
- ],
1042
- readonlySensitiveCommands: [
1043
- /^cat\s+/i,
1044
- /^head(?:\s|$)/i,
1045
- /^tail(?:\s|$)/i,
1046
- /^less(?:\s|$)/i,
1047
- /^more(?:\s|$)/i,
1048
- /^wc(?:\s|$)/i,
1049
- /^grep(?:\s|$)/i,
1050
- /^find\s+/i,
1051
- /^ag(?:\s|$)/i,
1052
- /^rg(?:\s|$)/i,
1053
- /^awk\s+/i,
1054
- // NEW-FN-P(M03):sed -i/--in-place 是原地写,挡出白名单走 L2 read_before_write。
1055
- // 前瞻放最前(外层 ^sed\s+ 会消费 -i 前的空格)。与 Python profiles/linux.py 对齐。
1056
- /^(?!.*(?:\s|['"])-[a-zA-Z]*i[a-zA-Z]*\b)(?!.*(?:\s|['"])--in-place)sed\s+/i,
1057
- /^cut\s+/i,
1058
- /^sort(?:\s|$)/i,
1059
- /^uniq(?:\s|$)/i,
1060
- // 进程/网络/打开文件信息:只读,但会暴露进程命令行、连接对端、打开的文件
1061
- // 路径,故只跳 L3、保留 L2(L1 敏感路径检测照常生效)。
1062
- /^ps(?:\s|$)/i,
1063
- /^ss(?:\s|$)/i,
1064
- /^netstat(?:\s|$)/i,
1065
- /^lsof(?:\s|$)/i,
1066
- // 只读子命令级白名单:匹配的是「第一段命令」而非首词(extractFirstCommand
1067
- // 按 | && || 切分),所以能精确到子命令。systemctl stop/disable 等不在此列。
1068
- /^systemctl\s+(?:status|is-active|is-enabled|is-failed|list-units|list-unit-files|list-dependencies|show|cat)\b/i,
1069
- /^service\s+\S+\s+status\b/i,
1070
- // journalctl 整族是只读的,但 --vacuum-*/--rotate/--flush/--sync 会删改日志,
1071
- // 用否定前瞻挡掉后再放行。注意前瞻里必须用 .* 扫描**整段**:只写 (?!\s+--...)
1072
- // 的话只看紧跟 journalctl 的第一个参数,
1073
- // `journalctl -u sshd --vacuum-size=100M` 就漏过去了。
1074
- /^journalctl\b(?!.*--(?:vacuum-size|vacuum-time|vacuum-files|rotate|flush|sync)\b)/i
1075
- ]
1076
- };
1077
- WINDOWS_TRAITS = {
1078
- writeKeywords: [
1079
- "write",
1080
- "\u5199\u5165",
1081
- "save",
1082
- "\u4FDD\u5B58",
1083
- "modify",
1084
- "\u4FEE\u6539",
1085
- "update",
1086
- "\u66F4\u65B0",
1087
- "delete",
1088
- "\u5220\u9664",
1089
- "remove",
1090
- "\u79FB\u9664",
1091
- "create",
1092
- "\u521B\u5EFA",
1093
- "append",
1094
- "\u8FFD\u52A0",
1095
- "overwrite",
1096
- "\u8986\u76D6",
1097
- "replace",
1098
- "\u66FF\u6362",
1099
- "set",
1100
- "\u8BBE\u7F6E"
1101
- // 注意:这里**不能放 `reg` / `注册表`**。关键词是子串包含匹配,放进去等于
1102
- // 「只要命令里出现 reg 字样就算写操作」——`reg query`(读)会被判成写,
1103
- // 进而误拦对 deny_write 路径的只读查询。注册表的写判定交给下面的
1104
- // writeCommandRes(只认写子命令)。两端语义必须一致。
1105
- ],
1106
- readKeywords: [
1107
- "read",
1108
- "\u8BFB\u53D6",
1109
- "type",
1110
- "findstr",
1111
- "dir",
1112
- "view",
1113
- "\u67E5\u770B",
1114
- "open",
1115
- "\u6253\u5F00",
1116
- "load",
1117
- "\u52A0\u8F7D",
1118
- "search",
1119
- "\u641C\u7D22",
1120
- "find",
1121
- "\u67E5\u627E",
1122
- "get",
1123
- "\u83B7\u53D6"
1124
- ],
1125
- // Windows 写/删命令:del/copy/move/xcopy/replace/rmdir/icacls/reg delete 等
1126
- writeCommandRes: [
1127
- /\b(del|erase)\b/,
1128
- /\brmdir\b/,
1129
- /\b(rd)\b/,
1130
- /\bcopy\b/,
1131
- /\b(xcopy|robocopy)\b/,
1132
- /\b(move|movefile)\b/,
1133
- /\breplace\b/,
1134
- /\bicacls\b/,
1135
- /\battrib\b/,
1136
- // 注册表:**只认写子命令**(对齐 Python WindowsProfile.write_command_res)。
1137
- // 此前是 /\breg\b/,把 reg query(读)也判成写 → 误拦对 deny_write 路径的只读查询。
1138
- // 写子命令 add/delete/copy/save/restore/import/load/unload;
1139
- // 读子命令 query/compare/export/flags/help 不在此列。
1140
- /\breg(?:\.exe)?\s+(?:add|delete|copy|save|restore|import|load|unload)\b/,
1141
- /\bformat\b/,
1142
- /\bdiskpart\b/,
1143
- /\bmkdir\b/,
1144
- /\bmd\b/,
1145
- /\bsetx\b/,
1146
- /\bwmic\b/
1147
- ],
1148
- // Windows 只读命令(重定向写判定用):type/findstr/more/tree/dir 等
1149
- readOnlyCommands: /* @__PURE__ */ new Set([
1150
- "type",
1151
- "findstr",
1152
- "more",
1153
- "tree",
1154
- "dir",
1155
- "where",
1156
- "ver",
1157
- "cls",
1158
- "echo",
1159
- "help",
1160
- "fc",
1161
- "comp",
1162
- "find",
1163
- "sort",
1164
- "powershell",
1165
- "cmd",
1166
- "netstat",
1167
- "ipconfig",
1168
- "tasklist",
1169
- "systeminfo",
1170
- "driverquery",
1171
- "reg",
1172
- "wmic",
1173
- "whoami",
1174
- "hostname",
1175
- "getmac",
1176
- "query",
1177
- "w32tm",
1178
- "diskpart"
1179
- ]),
1180
- // Windows 完全安全命令(对齐 WindowsProfile.fully_safe_commands)
1181
- fullySafeCommands: [
1182
- /^dir(?:\s|$)/,
1183
- /^where\s+/,
1184
- /^echo\s+/,
1185
- /^cd(?:\s|$)/,
1186
- /^tree(?:\s|$)/,
1187
- /^help(?:\s|$)/,
1188
- /^cls(?:\s|$)/,
1189
- /^ver(?:\s|$)/,
1190
- // 系统信息查询(只读,不读文件内容、无状态变更选项),与 Linux 侧 df/free 对齐
1191
- /^whoami(?:\s|$)/,
1192
- /^hostname(?:\s|$)/,
1193
- /^systeminfo(?:\s|$)/,
1194
- /^tasklist(?:\s|$)/,
1195
- /^getmac(?:\s|$)/,
1196
- /^driverquery(?:\s|$)/,
1197
- /^netstat(?:\s|$)/
1198
- ],
1199
- // Windows 安全但可能访问敏感路径的命令(对齐 WindowsProfile.readonly_sensitive_commands)
1200
- readonlySensitiveCommands: [
1201
- /^type\s+/,
1202
- /^findstr\s+/,
1203
- // 只读子命令级白名单(匹配「第一段命令」而非首词);
1204
- // sc create/delete/start/stop、reg add/delete/set 均不在此列。
1205
- /^sc\s+query\b/,
1206
- /^reg\s+query\b/
1207
- ]
1208
- };
1209
- MACOS_TRAITS = LINUX_TRAITS;
1210
- HARMONY_TRAITS = LINUX_TRAITS;
916
+ init_rules_loader();
917
+ traitsCache = /* @__PURE__ */ new Map();
918
+ LINUX_TRAITS = traitsFor("linux");
919
+ WINDOWS_TRAITS = traitsFor("windows");
920
+ MACOS_TRAITS = traitsFor("macos");
921
+ HARMONY_TRAITS = traitsFor("harmony");
1211
922
  }
1212
923
  });
1213
924
 
@@ -1366,7 +1077,6 @@ function checkIntentConsistency(input) {
1366
1077
  const { aNext, promptSession, promptHistory } = input;
1367
1078
  const actionDetail = aNext.actionDetail.toLowerCase();
1368
1079
  const actionType = aNext.actionType.toLowerCase();
1369
- const EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
1370
1080
  if (EXCLUDED_PREFIXES.some((p) => actionType.startsWith(p))) return null;
1371
1081
  if (INLINE_SCRIPT_RE.test(actionDetail)) return null;
1372
1082
  const intentTexts = collectIntentTexts(promptSession, promptHistory);
@@ -1394,7 +1104,6 @@ function runLogicCheck(input, opts) {
1394
1104
  const actionDetail = aNext.actionDetail.toLowerCase();
1395
1105
  const actionType = aNext.actionType.toLowerCase();
1396
1106
  const resolvedCwd = cwd || "/";
1397
- const EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
1398
1107
  const isExcludedType = EXCLUDED_PREFIXES.some((p) => actionType.startsWith(p));
1399
1108
  if (!opts?.skipReadBeforeWrite) {
1400
1109
  const rbwResult = checkReadBeforeWrite(actionType, actionDetail, actionHistory, input.reason, resolvedCwd, input.osType);
@@ -1506,7 +1215,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
1506
1215
  const isBashLike = actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash";
1507
1216
  const isWrite = isBashLike ? isWriteOperation(actionType, actionDetail, void 0, osType) : getPlatformTraits(osType).writeKeywords.some((kw) => actionType.includes(kw));
1508
1217
  if (!isWrite) return null;
1509
- const WRITE_CMDS = /* @__PURE__ */ new Set(["cp", "mv", "install", "tee", "dd", "truncate", "mkfs", "fdisk", "parted"]);
1218
+ const WRITE_CMDS = new Set(_LOGIC.write_commands ?? []);
1510
1219
  if (isBashLike) {
1511
1220
  const detailNoCompare = actionDetail.replace(/<=>|>=|<=|<>|=>/g, " ");
1512
1221
  const detailNoDevnull = detailNoCompare.replace(DEVNULL_REDIRECT_RE, "");
@@ -1538,7 +1247,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
1538
1247
  }
1539
1248
  if (!hasWriteSeg) return null;
1540
1249
  }
1541
- const SOURCE_ARG_CMDS = /* @__PURE__ */ new Set(["cp", "mv", "install", "rsync"]);
1250
+ const SOURCE_ARG_CMDS = new Set(_LOGIC.source_arg_commands ?? []);
1542
1251
  const REDIRECT_TARGET_RE = new RegExp(`>{1,2}\\s*(?!&)(?:'(/[^']+)'|"(/[^\\"]+)"|(/[^\\s;|&><'\\"]+))`);
1543
1252
  const targetPaths = [];
1544
1253
  for (const seg of splitCommandSegments(actionDetail)) {
@@ -1586,7 +1295,6 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
1586
1295
  }
1587
1296
  }
1588
1297
  }
1589
- const TEMP_DIRS = ["/tmp/", "/var/tmp/", "/dev/shm/", "/run/"];
1590
1298
  let filteredPaths = uniqueTargetPaths;
1591
1299
  if (/\bdd\b/i.test(actionDetail) && /\bif=\/dev\/zero\b/i.test(actionDetail)) {
1592
1300
  filteredPaths = uniqueTargetPaths.filter(
@@ -1687,10 +1395,10 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd, osType) {
1687
1395
  if (!hasTraversal) {
1688
1396
  return null;
1689
1397
  }
1690
- const READ_KEYWORDS = ["read(", "readline", "readlines", "fread", "fgets", "load(", "getpass", "file_get_contents"];
1691
- const WRITE_KEYWORDS2 = ["write(", "writelines", "fwrite", "file_put_contents"];
1692
- 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"];
1693
- const OPEN_KEYWORDS = ["open(", "fopen("];
1398
+ const READ_KEYWORDS = IFA_READ_KEYWORDS;
1399
+ const WRITE_KEYWORDS2 = IFA_WRITE_KEYWORDS;
1400
+ const EXEC_KEYWORDS = IFA_EXEC_KEYWORDS;
1401
+ const OPEN_KEYWORDS = IFA_OPEN_KEYWORDS;
1694
1402
  const code = extractInlineCode(actionDetail);
1695
1403
  if (!code) return null;
1696
1404
  const codeLower = code.toLowerCase();
@@ -1753,7 +1461,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd, osType) {
1753
1461
  violatedLayers: ["L2.1"]
1754
1462
  };
1755
1463
  }
1756
- 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;
1464
+ 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;
1757
1465
  var init_logic_rules = __esm({
1758
1466
  "src/engine/logic-rules.ts"() {
1759
1467
  "use strict";
@@ -1763,6 +1471,10 @@ var init_logic_rules = __esm({
1763
1471
  init_rules_loader();
1764
1472
  init_platform_traits();
1765
1473
  init_shell_split();
1474
+ _LOGIC = loadLogicRules();
1475
+ _HEURISTIC = loadHeuristicRules();
1476
+ EXCLUDED_PREFIXES = _HEURISTIC.sensitive_excluded_prefixes ?? [];
1477
+ TEMP_DIRS = (_HEURISTIC.temp_dirs ?? []).map((d) => d + "/");
1766
1478
  ROOT_SENSITIVE_PATHS = _loadSensitivePathScope("root");
1767
1479
  HOME_SENSITIVE_PATHS = _loadSensitivePathScope("home");
1768
1480
  CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path5, riskLevel, desc, dirDelOnly }) => ({ path: path5, riskLevel, desc, credential: true, dirDelOnly }));
@@ -1783,7 +1495,7 @@ var init_logic_rules = __esm({
1783
1495
  "list_dir",
1784
1496
  "count_text_length",
1785
1497
  "search",
1786
- // filemgr 只读三件套(globfiles 在 coordinator FULLY_SAFE_TOOLS 提前放行,此处兜底防御非 coordinator 入口)
1498
+ // filemgr 只读三件套(globfiles 在 platform-traits 的 safeActionTypes 提前放行,此处兜底防御非 coordinator 入口)
1787
1499
  "filemgr-globfiles",
1788
1500
  "filemgr-readfile",
1789
1501
  "filemgr-grepfiles",
@@ -1795,7 +1507,7 @@ var init_logic_rules = __esm({
1795
1507
  "tail",
1796
1508
  "cat"
1797
1509
  ]);
1798
- NON_WRITE_TOOL_PREFIXES = ["memory-", "skill-manager-", "mailbox-", "app-"];
1510
+ NON_WRITE_TOOL_PREFIXES = _LOGIC.non_write_tool_prefixes ?? [];
1799
1511
  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"];
1800
1512
  SED_INPLACE_RE = /\bsed\s+(?:-\S+\s+)*(?:-[a-zA-Z]*i[a-zA-Z]*|--in-place)\b/i;
1801
1513
  DEVNULL_REDIRECT_RE = /(?:2|&|1)?\s*>\s*\/dev\/null\b/g;
@@ -1836,25 +1548,24 @@ var init_logic_rules = __esm({
1836
1548
  }));
1837
1549
  })();
1838
1550
  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;
1839
- DIR_TRAVERSAL_KW = [
1840
- "os.listdir",
1841
- "os.walk",
1842
- "os.scandir",
1843
- "glob.glob",
1844
- "glob.iglob",
1845
- "pathlib.",
1846
- "iterdir",
1847
- "rglob",
1848
- "scandir(",
1849
- "listdir("
1850
- ];
1551
+ DIR_TRAVERSAL_KW = loadInline().dir_traversal_keywords ?? [];
1552
+ _IFA = loadLogicRules().indirect_file_access ?? {};
1553
+ IFA_READ_KEYWORDS = _IFA.read_keywords ?? [];
1554
+ IFA_WRITE_KEYWORDS = _IFA.write_keywords ?? [];
1555
+ IFA_EXEC_KEYWORDS = _IFA.exec_keywords ?? [];
1556
+ IFA_OPEN_KEYWORDS = _IFA.open_keywords ?? [];
1851
1557
  }
1852
1558
  });
1853
1559
 
1854
1560
  // src/engine/asset-dir.ts
1855
1561
  import { existsSync as existsSync3 } from "fs";
1856
- import { resolve as resolve2 } from "path";
1562
+ import { join as join3, resolve as resolve2 } from "path";
1857
1563
  function assetDirCandidates(moduleDir, name) {
1564
+ const root = getRulesRoot();
1565
+ if (root) {
1566
+ const p = join3(root, name);
1567
+ return [p, p];
1568
+ }
1858
1569
  return [
1859
1570
  resolve2(moduleDir, name),
1860
1571
  resolve2(moduleDir, "../../..", "agent_moss", name)
@@ -1870,6 +1581,7 @@ var init_asset_dir = __esm({
1870
1581
  "src/engine/asset-dir.ts"() {
1871
1582
  "use strict";
1872
1583
  init_esm_shims();
1584
+ init_paths();
1873
1585
  }
1874
1586
  });
1875
1587
 
@@ -1898,6 +1610,7 @@ function userSkillsDir() {
1898
1610
  return resolve3(getDataRoot(), "agent_moss_skills");
1899
1611
  }
1900
1612
  function runtimeCustomSkillKeywords() {
1613
+ if (isRuntimeConfigDisabled()) return {};
1901
1614
  try {
1902
1615
  const rt = runtimeConfigBridge.current?.loadRuntimeConfig?.();
1903
1616
  if (!rt) return {};
@@ -1915,6 +1628,7 @@ function runtimeCustomSkillKeywords() {
1915
1628
  }
1916
1629
  }
1917
1630
  function loadUserSkill(name) {
1631
+ if (isRuntimeConfigDisabled()) return "";
1918
1632
  if (userSkillCache[name] !== void 0) return userSkillCache[name];
1919
1633
  try {
1920
1634
  const p = resolve3(userSkillsDir(), `${name}.md`);
@@ -1983,6 +1697,7 @@ var init_skill_loader = __esm({
1983
1697
  init_esm_shims();
1984
1698
  init_rules_loader();
1985
1699
  init_paths();
1700
+ init_rules_mode();
1986
1701
  init_asset_dir();
1987
1702
  init_runtime_config_bridge();
1988
1703
  __filename3 = fileURLToPath3(import.meta.url);
@@ -2043,6 +1758,7 @@ __export(runtime_config_exports, {
2043
1758
  getEnabledScenarioExtensions: () => getEnabledScenarioExtensions,
2044
1759
  getEnvOverrides: () => getEnvOverrides,
2045
1760
  getProfileOrDetect: () => getProfileOrDetect,
1761
+ getRuleSourceFingerprint: () => getRuleSourceFingerprint,
2046
1762
  getRuntimeConfigPath: () => getRuntimeConfigPath,
2047
1763
  getScenarioRules: () => getScenarioRules,
2048
1764
  hasRuntimeConfig: () => hasRuntimeConfig,
@@ -2066,7 +1782,7 @@ __export(runtime_config_exports, {
2066
1782
  updateSkillEnabled: () => updateSkillEnabled
2067
1783
  });
2068
1784
  import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync5, mkdirSync, unlinkSync, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
2069
- import { join as join3 } from "path";
1785
+ import { join as join4 } from "path";
2070
1786
  import { fileURLToPath as fileURLToPath4 } from "url";
2071
1787
  import { dirname as dirname3 } from "path";
2072
1788
  function runtimeDir() {
@@ -2077,7 +1793,22 @@ function getRuntimeConfigPath() {
2077
1793
  if (envPath) return envPath;
2078
1794
  return RUNTIME_CONFIG_PATH;
2079
1795
  }
1796
+ function getRuleSourceFingerprint() {
1797
+ if (isRuntimeConfigDisabled()) return `mode:${getRulesMode()}`;
1798
+ const p = getRuntimeConfigPath();
1799
+ try {
1800
+ const st = statSync2(p);
1801
+ return `${p}|${st.mtimeMs}|${st.size}`;
1802
+ } catch {
1803
+ return `${p}|absent`;
1804
+ }
1805
+ }
2080
1806
  function saveRuntimeConfig(config) {
1807
+ if (isRuntimeConfigDisabled()) {
1808
+ throw new Error(
1809
+ `\u89C4\u5219\u6765\u6E90\u6A21\u5F0F\u4E3A ${getRulesMode()}\uFF08runtime \u914D\u7F6E\u5DF2\u7981\u7528\uFF09\uFF1A\u4FEE\u6539\u89C4\u5219\u4E0D\u4F1A\u751F\u6548\uFF0C\u8BF7\u5207\u6362\u89C4\u5219\u6765\u6E90\u6A21\u5F0F\u5E76\u91CD\u542F\u670D\u52A1\u3002`
1810
+ );
1811
+ }
2081
1812
  const p = getRuntimeConfigPath();
2082
1813
  const dir = p.substring(0, p.lastIndexOf("/"));
2083
1814
  if (dir && !existsSync5(dir)) mkdirSync(dir, { recursive: true });
@@ -2101,6 +1832,18 @@ function invalidateRuntimeConfigCache() {
2101
1832
  hasRuntimeConfigCache.clear();
2102
1833
  }
2103
1834
  function loadRuntimeConfig() {
1835
+ if (isRuntimeConfigDisabled()) {
1836
+ return {
1837
+ version: 1,
1838
+ layers: { ...DEFAULT_LAYERS },
1839
+ L1_rules: {},
1840
+ L2_rules: {},
1841
+ L3_skills: {},
1842
+ L2_2: {},
1843
+ agent_rules: {},
1844
+ scenario_rules: {}
1845
+ };
1846
+ }
2104
1847
  const p = getRuntimeConfigPath();
2105
1848
  if (!existsSync5(p)) {
2106
1849
  const seeded = mergeConfig({}, generateSourceDefaults());
@@ -2147,6 +1890,7 @@ function loadRuntimeConfig() {
2147
1890
  function isLayerEnabled(layerKey) {
2148
1891
  const disableKey = LAYER_DISABLE_ENV[layerKey];
2149
1892
  if (disableKey && process.env[disableKey] === "1") return false;
1893
+ if (isRuntimeConfigDisabled()) return true;
2150
1894
  const runtime = loadRuntimeConfig();
2151
1895
  const layers = runtime.layers || {};
2152
1896
  if (layerKey in layers) return !!layers[layerKey];
@@ -2173,7 +1917,10 @@ function filterEnabledRules(rules, categoryEnabled = true) {
2173
1917
  }
2174
1918
  function _filterRulesByOs(rules, osType) {
2175
1919
  const accepted = acceptedOsValues(osType);
2176
- return rules.filter((r) => accepted.includes(String(r.os || "linux").toLowerCase()));
1920
+ return rules.filter((r) => {
1921
+ const os3 = String(r.os || "linux").toLowerCase();
1922
+ return os3 === "all" || accepted.includes(os3);
1923
+ });
2177
1924
  }
2178
1925
  function _userCustomRules(runtime, layers, requiredKey) {
2179
1926
  const list = Array.isArray(layers) ? layers : [layers];
@@ -2247,6 +1994,7 @@ function getEnabledL2SensitivePaths(runtime, osType) {
2247
1994
  });
2248
1995
  }
2249
1996
  function hasRuntimeConfig() {
1997
+ if (isRuntimeConfigDisabled()) return false;
2250
1998
  const p = getRuntimeConfigPath();
2251
1999
  let key;
2252
2000
  try {
@@ -2255,8 +2003,8 @@ function hasRuntimeConfig() {
2255
2003
  } catch {
2256
2004
  return false;
2257
2005
  }
2258
- const cached = hasRuntimeConfigCache.get(key);
2259
- if (cached !== void 0) return cached;
2006
+ const cached2 = hasRuntimeConfigCache.get(key);
2007
+ if (cached2 !== void 0) return cached2;
2260
2008
  let ok = false;
2261
2009
  try {
2262
2010
  JSON.parse(readFileSync4(p, "utf-8"));
@@ -2359,6 +2107,9 @@ function _l1InjectionKeywordsToRules() {
2359
2107
  keyword: kw.keyword,
2360
2108
  risk_level: kw.riskLevel,
2361
2109
  lang: kw.lang || "en",
2110
+ // 带上条目自己的 os(`all` = 全平台通用),别丢 —— 丢了 Windows 会拿到
2111
+ // 本不该给它的条目。与 Python `_l1_injection_keywords_to_rules` 对齐。
2112
+ os: kw.os,
2362
2113
  enabled: true,
2363
2114
  builtin: true
2364
2115
  }));
@@ -2476,7 +2227,8 @@ function _l3SkillsToRules() {
2476
2227
  }
2477
2228
  function _scenarioRulesToDefaults() {
2478
2229
  const out = {};
2479
- const scenDir = existsSync5(_SCENARIOS_DIR_PROD) ? _SCENARIOS_DIR_PROD : existsSync5(_SCENARIOS_DIR_DEV) ? _SCENARIOS_DIR_DEV : _SCENARIOS_DIR_PROD;
2230
+ const _root = getRulesRoot();
2231
+ const scenDir = _root ? join4(_root, "scenarios") : existsSync5(_SCENARIOS_DIR_PROD) ? _SCENARIOS_DIR_PROD : existsSync5(_SCENARIOS_DIR_DEV) ? _SCENARIOS_DIR_DEV : _SCENARIOS_DIR_PROD;
2480
2232
  if (!existsSync5(scenDir)) return out;
2481
2233
  let entries;
2482
2234
  try {
@@ -2485,7 +2237,7 @@ function _scenarioRulesToDefaults() {
2485
2237
  return out;
2486
2238
  }
2487
2239
  for (const scenarioId of entries) {
2488
- const rulesFile = resolveScenarioRulesFile(scenarioId) ?? join3(scenDir, scenarioId, "rules.json");
2240
+ const rulesFile = resolveScenarioRulesFile(scenarioId) ?? join4(scenDir, scenarioId, "rules.json");
2489
2241
  if (!existsSync5(rulesFile)) continue;
2490
2242
  let data;
2491
2243
  try {
@@ -2525,7 +2277,8 @@ function _scenarioRulesToDefaults() {
2525
2277
  }
2526
2278
  function _agentRulesToDefaults() {
2527
2279
  const out = {};
2528
- const agentsDir = existsSync5(_AGENTS_DIR_PROD) ? _AGENTS_DIR_PROD : existsSync5(_AGENTS_DIR_DEV) ? _AGENTS_DIR_DEV : _AGENTS_DIR_PROD;
2280
+ const _root = getRulesRoot();
2281
+ const agentsDir = _root ? join4(_root, "agents") : existsSync5(_AGENTS_DIR_PROD) ? _AGENTS_DIR_PROD : existsSync5(_AGENTS_DIR_DEV) ? _AGENTS_DIR_DEV : _AGENTS_DIR_PROD;
2529
2282
  if (!existsSync5(agentsDir)) return out;
2530
2283
  let entries;
2531
2284
  try {
@@ -2534,7 +2287,7 @@ function _agentRulesToDefaults() {
2534
2287
  return out;
2535
2288
  }
2536
2289
  for (const agentId of entries) {
2537
- const rulesFile = join3(agentsDir, agentId, "rules.json");
2290
+ const rulesFile = join4(agentsDir, agentId, "rules.json");
2538
2291
  if (!existsSync5(rulesFile)) continue;
2539
2292
  let data;
2540
2293
  try {
@@ -2913,7 +2666,11 @@ function getDisabledSkillIds(runtime) {
2913
2666
  }
2914
2667
  function _filterDefaultsByOs(rules, osType) {
2915
2668
  const accepted = acceptedOsValues(osType);
2916
- return rules.filter((r) => !r.os || accepted.includes(String(r.os).toLowerCase()));
2669
+ return rules.filter((r) => {
2670
+ if (!r.os) return true;
2671
+ const os3 = String(r.os).toLowerCase();
2672
+ return os3 === "all" || accepted.includes(os3);
2673
+ });
2917
2674
  }
2918
2675
  function _hasRulesForOs(rules, osType) {
2919
2676
  return _filterRulesByOs(rules || [], osType || "linux").length > 0;
@@ -3136,13 +2893,18 @@ function deleteCustomRule(layer, category, ruleId) {
3136
2893
  }
3137
2894
  function addCustomSkill(skillId, category, keywords, content) {
3138
2895
  _validateSkillId(skillId);
2896
+ if (isRuntimeConfigDisabled()) {
2897
+ throw new Error(
2898
+ `\u89C4\u5219\u6765\u6E90\u6A21\u5F0F\u4E3A ${getRulesMode()}\uFF08runtime \u914D\u7F6E\u5DF2\u7981\u7528\uFF09\uFF0C\u4E0D\u5141\u8BB8\u65B0\u589E\u81EA\u5B9A\u4E49 skill\u3002`
2899
+ );
2900
+ }
3139
2901
  const runtime = loadRuntimeConfig();
3140
- const skillsDir = join3(getRuntimeConfigDir(), "agent_moss_skills");
2902
+ const skillsDir = join4(getRuntimeConfigDir(), "agent_moss_skills");
3141
2903
  try {
3142
2904
  mkdirSync(skillsDir, { recursive: true });
3143
2905
  } catch {
3144
2906
  }
3145
- writeFileSync(join3(skillsDir, `${skillId}.md`), content, "utf-8");
2907
+ writeFileSync(join4(skillsDir, `${skillId}.md`), content, "utf-8");
3146
2908
  const l3 = runtime.L3_skills || (runtime.L3_skills = {});
3147
2909
  const cat = l3[category] || (l3[category] = { category_enabled: true, category_desc: "", skills: [] });
3148
2910
  (cat.skills || (cat.skills = [])).push({ id: skillId, keywords, enabled: true, builtin: false });
@@ -3157,7 +2919,7 @@ function deleteCustomSkill(skillId, category) {
3157
2919
  if (idx >= 0) {
3158
2920
  if (skills[idx].builtin) return null;
3159
2921
  skills.splice(idx, 1);
3160
- const skillPath = join3(getRuntimeConfigDir(), "agent_moss_skills", `${skillId}.md`);
2922
+ const skillPath = join4(getRuntimeConfigDir(), "agent_moss_skills", `${skillId}.md`);
3161
2923
  try {
3162
2924
  if (existsSync5(skillPath)) unlinkSync(skillPath);
3163
2925
  } catch {
@@ -3362,10 +3124,11 @@ var init_runtime_config = __esm({
3362
3124
  init_skill_loader();
3363
3125
  init_rules_loader();
3364
3126
  init_runtime_config_bridge();
3127
+ init_rules_mode();
3365
3128
  __dirname4 = dirname3(fileURLToPath4(import.meta.url));
3366
3129
  ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
3367
3130
  RUNTIME_CONFIG_DIR = runtimeDir();
3368
- RUNTIME_CONFIG_PATH = join3(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
3131
+ RUNTIME_CONFIG_PATH = join4(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
3369
3132
  LAYER_DISABLE_ENV = {
3370
3133
  L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
3371
3134
  L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
@@ -3393,10 +3156,10 @@ var init_runtime_config = __esm({
3393
3156
  intent_consistency: ["intent_keywords", "reason"]
3394
3157
  };
3395
3158
  hasRuntimeConfigCache = /* @__PURE__ */ new Map();
3396
- _AGENTS_DIR_PROD = join3(__dirname4, "agents");
3397
- _AGENTS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "agents");
3398
- _SCENARIOS_DIR_PROD = join3(__dirname4, "scenarios");
3399
- _SCENARIOS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
3159
+ _AGENTS_DIR_PROD = join4(__dirname4, "agents");
3160
+ _AGENTS_DIR_DEV = join4(__dirname4, "..", "..", "..", "agent_moss", "agents");
3161
+ _SCENARIOS_DIR_PROD = join4(__dirname4, "scenarios");
3162
+ _SCENARIOS_DIR_DEV = join4(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
3400
3163
  _effectiveCache = /* @__PURE__ */ new WeakMap();
3401
3164
  SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
3402
3165
  registerRuntimeConfigBridge({
@@ -3433,6 +3196,7 @@ __export(config_exports, {
3433
3196
  isPolicyGenEnabled: () => isPolicyGenEnabled,
3434
3197
  isPolicySaveEnabled: () => isPolicySaveEnabled,
3435
3198
  probeLLMConnection: () => probeLLMConnection,
3199
+ reloadCustomRules: () => reloadCustomRules,
3436
3200
  updateLLMConfig: () => updateLLMConfig,
3437
3201
  userRuleVariantPattern: () => userRuleVariantPattern,
3438
3202
  wildcardToRegExp: () => wildcardToRegExp
@@ -3513,17 +3277,19 @@ function wildcardToRegExp(pattern) {
3513
3277
  }
3514
3278
  function loadCustomRules() {
3515
3279
  const rules = [];
3516
- try {
3517
- const raw = process.env.AGENT_MOSS_CUSTOM_RULES;
3518
- if (raw) {
3519
- const parsed = JSON.parse(raw);
3520
- if (Array.isArray(parsed)) {
3521
- for (const r of parsed) {
3522
- if (r && r.pattern && r.action) rules.push({ pattern: r.pattern, action: r.action, severity: r.severity || "high" });
3280
+ if (!isBuiltinOnly()) {
3281
+ try {
3282
+ const raw = process.env.AGENT_MOSS_CUSTOM_RULES;
3283
+ if (raw) {
3284
+ const parsed = JSON.parse(raw);
3285
+ if (Array.isArray(parsed)) {
3286
+ for (const r of parsed) {
3287
+ if (r && r.pattern && r.action) rules.push({ pattern: r.pattern, action: r.action, severity: r.severity || "high" });
3288
+ }
3523
3289
  }
3524
3290
  }
3291
+ } catch {
3525
3292
  }
3526
- } catch {
3527
3293
  }
3528
3294
  for (const rulesPath of USER_RULES_PATHS) {
3529
3295
  try {
@@ -3552,6 +3318,9 @@ function userRuleVariantPattern(pattern) {
3552
3318
  }
3553
3319
  return null;
3554
3320
  }
3321
+ function reloadCustomRules() {
3322
+ customRules = loadCustomRules();
3323
+ }
3555
3324
  function getLLMConfig() {
3556
3325
  return llmConfig;
3557
3326
  }
@@ -3639,6 +3408,7 @@ function layerEnabledOrDefault(key) {
3639
3408
  }
3640
3409
  }
3641
3410
  function isBrainEnabled() {
3411
+ if (isRuntimeConfigDisabled()) return false;
3642
3412
  return getAgentMossSetting("AGENT_MOSS_BRAIN_ENABLED", "") === "1";
3643
3413
  }
3644
3414
  function getAgentMossSetting(key, def = "") {
@@ -3680,6 +3450,7 @@ var init_config = __esm({
3680
3450
  init_esm_shims();
3681
3451
  init_paths();
3682
3452
  init_runtime_config();
3453
+ init_rules_mode();
3683
3454
  __configModuleDir = (() => {
3684
3455
  try {
3685
3456
  return path2.dirname(fileURLToPath5(import.meta.url));
@@ -3727,16 +3498,20 @@ var init_config = __esm({
3727
3498
 
3728
3499
  // src/agents/index.ts
3729
3500
  import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
3730
- import { resolve as resolve4, dirname as dirname4 } from "path";
3501
+ import { resolve as resolve4, dirname as dirname4, join as join5 } from "path";
3731
3502
  import { fileURLToPath as fileURLToPath6 } from "url";
3732
3503
  function resolveAgentsDir() {
3504
+ const root = getRulesRoot();
3505
+ if (root) return join5(root, "agents");
3733
3506
  if (existsSync6(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
3734
3507
  if (existsSync6(AGENTS_DIR_DEV_FALLBACK)) return AGENTS_DIR_DEV_FALLBACK;
3735
3508
  return AGENTS_DIR_PROD;
3736
3509
  }
3737
3510
  function getAgentExtensions(agentId) {
3738
3511
  if (!agentId) return null;
3739
- if (cache.has(agentId)) return cache.get(agentId) ?? null;
3512
+ const fp = getRuleSourceFingerprint();
3513
+ const cached2 = cache.get(agentId);
3514
+ if (cached2 && cached2.fp === fp) return cached2.ext;
3740
3515
  let ext = null;
3741
3516
  try {
3742
3517
  if (hasRuntimeConfig()) {
@@ -3748,7 +3523,7 @@ function getAgentExtensions(agentId) {
3748
3523
  const agentsDir = resolveAgentsDir();
3749
3524
  const rulesFile = resolve4(agentsDir, agentId, "rules.json");
3750
3525
  if (!existsSync6(rulesFile)) {
3751
- cache.set(agentId, null);
3526
+ cache.set(agentId, { fp, ext: null });
3752
3527
  return null;
3753
3528
  }
3754
3529
  try {
@@ -3756,16 +3531,16 @@ function getAgentExtensions(agentId) {
3756
3531
  ext = JSON.parse(raw);
3757
3532
  if (!ext.agent_id) {
3758
3533
  console.warn(`[AgentMoss agents] rules.json \u7F3A\u5C11 agent_id \u5B57\u6BB5: ${rulesFile}`);
3759
- cache.set(agentId, null);
3534
+ cache.set(agentId, { fp, ext: null });
3760
3535
  return null;
3761
3536
  }
3762
3537
  } catch (e) {
3763
3538
  console.error(`[AgentMoss agents] \u52A0\u8F7D agent ${agentId} rules.json \u5931\u8D25:`, e.message);
3764
- cache.set(agentId, null);
3539
+ cache.set(agentId, { fp, ext: null });
3765
3540
  return null;
3766
3541
  }
3767
3542
  }
3768
- cache.set(agentId, ext);
3543
+ cache.set(agentId, { fp, ext });
3769
3544
  console.error(
3770
3545
  `[AgentMoss agents] \u52A0\u8F7D agent ${agentId} \u5B9A\u5236\u89C4\u5219: ${ext.extra_command_patterns?.length ?? 0} cmd patterns, ${ext.extra_injection_keywords?.length ?? 0} injection keywords, ${ext.extra_sensitive_paths?.length ?? 0} sensitive paths, ${ext.builtin_safe_skills?.length ?? 0} builtin safe skills, ${ext.builtin_safe_action_types?.length ?? 0} builtin safe action types`
3771
3546
  );
@@ -3791,6 +3566,7 @@ var init_agents = __esm({
3791
3566
  "src/agents/index.ts"() {
3792
3567
  "use strict";
3793
3568
  init_esm_shims();
3569
+ init_paths();
3794
3570
  init_runtime_config();
3795
3571
  __filename4 = fileURLToPath6(import.meta.url);
3796
3572
  __dirname5 = dirname4(__filename4);
@@ -3802,16 +3578,20 @@ var init_agents = __esm({
3802
3578
 
3803
3579
  // src/scenarios/index.ts
3804
3580
  import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
3805
- import { resolve as resolve5, dirname as dirname5 } from "path";
3581
+ import { resolve as resolve5, dirname as dirname5, join as join6 } from "path";
3806
3582
  import { fileURLToPath as fileURLToPath7 } from "url";
3807
3583
  function resolveScenariosDir() {
3584
+ const root = getRulesRoot();
3585
+ if (root) return join6(root, "scenarios");
3808
3586
  if (existsSync7(SCENARIOS_DIR_PROD)) return SCENARIOS_DIR_PROD;
3809
3587
  if (existsSync7(SCENARIOS_DIR_DEV_FALLBACK)) return SCENARIOS_DIR_DEV_FALLBACK;
3810
3588
  return SCENARIOS_DIR_PROD;
3811
3589
  }
3812
3590
  function getScenarioExtensions(scenarioId) {
3813
3591
  if (!scenarioId) return null;
3814
- if (cache2.has(scenarioId)) return cache2.get(scenarioId) ?? null;
3592
+ const fp = getRuleSourceFingerprint();
3593
+ const cached2 = cache2.get(scenarioId);
3594
+ if (cached2 && cached2.fp === fp) return cached2.ext;
3815
3595
  let ext = null;
3816
3596
  let overrideHit = null;
3817
3597
  try {
@@ -3835,7 +3615,7 @@ function getScenarioExtensions(scenarioId) {
3835
3615
  );
3836
3616
  }
3837
3617
  if (!existsSync7(rulesFile)) {
3838
- cache2.set(scenarioId, null);
3618
+ cache2.set(scenarioId, { fp, ext: null });
3839
3619
  return null;
3840
3620
  }
3841
3621
  try {
@@ -3844,7 +3624,7 @@ function getScenarioExtensions(scenarioId) {
3844
3624
  const id = data.scenario_id || data.agent_id || "";
3845
3625
  if (!id) {
3846
3626
  console.warn(`[AgentMoss scenarios] rules.json \u7F3A\u5C11 scenario_id \u5B57\u6BB5: ${rulesFile}`);
3847
- cache2.set(scenarioId, null);
3627
+ cache2.set(scenarioId, { fp, ext: null });
3848
3628
  return null;
3849
3629
  }
3850
3630
  ext = {
@@ -3859,11 +3639,11 @@ function getScenarioExtensions(scenarioId) {
3859
3639
  };
3860
3640
  } catch (e) {
3861
3641
  console.error(`[AgentMoss scenarios] \u52A0\u8F7D\u573A\u666F ${scenarioId} rules.json \u5931\u8D25:`, e.message);
3862
- cache2.set(scenarioId, null);
3642
+ cache2.set(scenarioId, { fp, ext: null });
3863
3643
  return null;
3864
3644
  }
3865
3645
  }
3866
- cache2.set(scenarioId, ext);
3646
+ cache2.set(scenarioId, { fp, ext });
3867
3647
  console.error(
3868
3648
  `[AgentMoss scenarios] \u52A0\u8F7D\u573A\u666F ${scenarioId} \u5B9A\u5236\u89C4\u5219: ${ext.extra_command_patterns?.length ?? 0} cmd patterns, ${ext.extra_injection_keywords?.length ?? 0} injection keywords, ${ext.extra_sensitive_paths?.length ?? 0} sensitive paths`
3869
3649
  );
@@ -3883,6 +3663,7 @@ var init_scenarios = __esm({
3883
3663
  "src/scenarios/index.ts"() {
3884
3664
  "use strict";
3885
3665
  init_esm_shims();
3666
+ init_paths();
3886
3667
  init_agents();
3887
3668
  init_runtime_config();
3888
3669
  init_rules_loader();
@@ -4289,7 +4070,7 @@ function severityRank(s) {
4289
4070
  }
4290
4071
  function isSearchActionType(actionType) {
4291
4072
  const at = actionType.toLowerCase();
4292
- return SEARCH_ACTION_PREFIXES.some((p) => at.startsWith(p)) || ["websearch", "web_search", "search", "query"].includes(at);
4073
+ return SEARCH_ACTION_PREFIXES.some((p) => at.startsWith(p)) || SEARCH_ACTION_EXACT.includes(at);
4293
4074
  }
4294
4075
  function checkSensitivePath(command, cwd, agentExt, osType) {
4295
4076
  const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
@@ -4579,8 +4360,7 @@ function isSshKeygenGenerate(command) {
4579
4360
  function checkDangerousPatterns(actionType, actionDetail) {
4580
4361
  if (actionType === "file_write" || actionType === "file_edit") return null;
4581
4362
  const scan = stripHeredocContent(actionDetail);
4582
- const TEMP_DIRS = ["/tmp", "/var/tmp", "/run", "/dev/shm"];
4583
- const isTempPath = TEMP_DIRS.some(
4363
+ const isTempPath = TEMP_DIRS2.some(
4584
4364
  (td) => new RegExp(`(?:^|[\\s/])${td.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[\\s/]|$)`).test(actionDetail)
4585
4365
  ) || /(?:^|[\s;]|&&\s*|\|\|\s*)var\/tmp(?:[/\s;]|$)/.test(actionDetail);
4586
4366
  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;
@@ -4726,7 +4506,7 @@ function checkLateralMovement(actionDetail) {
4726
4506
  }
4727
4507
  return null;
4728
4508
  }
4729
- 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;
4509
+ 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;
4730
4510
  var init_heuristic = __esm({
4731
4511
  "src/engine/heuristic.ts"() {
4732
4512
  "use strict";
@@ -4743,7 +4523,8 @@ var init_heuristic = __esm({
4743
4523
  return kws.map((k) => ({
4744
4524
  keyword: k.keyword,
4745
4525
  riskLevel: k.risk_level,
4746
- lang: k.lang || "en"
4526
+ lang: k.lang || "en",
4527
+ os: String(k.os || "all").toLowerCase()
4747
4528
  }));
4748
4529
  })();
4749
4530
  DANGEROUS_PATTERNS2 = _loadDangerousPatterns("linux");
@@ -4751,7 +4532,9 @@ var init_heuristic = __esm({
4751
4532
  DANGEROUS_PATTERNS_MACOS = _loadDangerousPatterns("macos");
4752
4533
  DANGEROUS_PATTERNS_HARMONY = _loadDangerousPatterns("harmony");
4753
4534
  LOOPBACK_URL_RE = /https?:\/\/([^\/\s;'"|&)]+)/i;
4754
- LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "::1", "0.0.0.0", "[::1]"]);
4535
+ HEURISTIC_RULES = loadHeuristicRules();
4536
+ LOOPBACK_HOSTS = new Set(HEURISTIC_RULES.loopback_hosts ?? []);
4537
+ TEMP_DIRS2 = HEURISTIC_RULES.temp_dirs ?? [];
4755
4538
  PATH_CONTEXT_TRIGGER_RE = /(?:^|[;&|]\s*)(?:rm|rmdir|unlink|ln|mv|cp|dd|truncate|shred|chmod|chown)\b/i;
4756
4539
  HOME_PREFIX_RE = /(?:~|~(?=\/)|\$HOME\b|\$\{HOME\}|\/home\/[a-z0-9_.-]+)/gi;
4757
4540
  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;
@@ -4759,8 +4542,9 @@ var init_heuristic = __esm({
4759
4542
  PY_BIGNUM_RE = /\d{6,}/;
4760
4543
  PY_INLINE_C_RE = /(python(?:3(?:\.\d+)?)?|perl5?|ruby|node|php|lua)\s+(?:-[a-zA-Z]+\s+)*-c\s+(?:"([^"]*)"|'([^']*)')/i;
4761
4544
  PY_CMD_SUBST_C_RE = /\$\(\s*(python(?:3(?:\.\d+)?)?|perl5?|ruby|node|php|lua)\s+(?:-[a-zA-Z]+\s+)*-c\s+(?:"([^"]*)"'|'([^']*)')\s*\)/i;
4762
- SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
4763
- SEARCH_ACTION_PREFIXES = ["search-", "web-", "browser-", "query-", "websearch", "web_search"];
4545
+ SENSITIVE_EXCLUDED_PREFIXES = HEURISTIC_RULES.sensitive_excluded_prefixes ?? [];
4546
+ SEARCH_ACTION_PREFIXES = HEURISTIC_RULES.search_action_prefixes ?? [];
4547
+ SEARCH_ACTION_EXACT = HEURISTIC_RULES.search_action_exact ?? [];
4764
4548
  HOME_TARGET_RE = /(?:^|[\s="'($])(?:~|(?:\$home|\$\{home\}))(?:\/(?:\.\*|\*|\.)?)?(?=[\s"';}|&)]|$)/i;
4765
4549
  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;
4766
4550
  }
@@ -4986,87 +4770,25 @@ function loadTemplates() {
4986
4770
 
4987
4771
  // src/engine/script-analyzer.ts
4988
4772
  init_esm_shims();
4773
+ init_rules_loader();
4989
4774
  import { readFileSync as readFileSync8 } from "fs";
4990
- import { resolve as resolve7, join as join4 } from "path";
4991
- var SUSPICIOUS_PATTERNS = [
4992
- // Network exfiltration (curl/wget POST in scripts is almost never legitimate)
4993
- { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
4994
- { name: "curl --data", pattern: /curl\s+(.*\s)?--data/i },
4995
- { name: "curl -d", pattern: /curl\s+(.*\s)?-d\s/i },
4996
- { name: "wget --post-data", pattern: /wget\s+(.*\s)?--post-data/i },
4997
- { name: "wget --post-file", pattern: /wget\s+(.*\s)?--post-file/i },
4998
- // Reverse shell
4999
- { name: "nc -e", pattern: /\bnc\s+.*-e\b/ },
5000
- { name: "ncat -e", pattern: /\bncat\s+.*-e\b/ },
5001
- { name: "/dev/tcp", pattern: /\/dev\/tcp\// },
5002
- { name: "/dev/udp", pattern: /\/dev\/udp\// },
5003
- // Base64 obfuscation
5004
- { name: "$(base64", pattern: /\$\(\s*base64/ },
5005
- { name: "`base64", pattern: /`\s*base64/ },
5006
- { name: "base64 decode pipe", pattern: /base64\s+(-d|--decode)\s*\|/ },
5007
- // Sensitive file access
5008
- { name: "/etc/shadow", pattern: /\/etc\/shadow/ },
5009
- { name: "id_rsa", pattern: /\bid_rsa\b/ },
5010
- { name: "id_ed25519", pattern: /\bid_ed25519\b/ },
5011
- // Persistence backdoor
5012
- { name: "crontab install", pattern: /crontab\s+-/ },
5013
- { name: "authorized_keys", pattern: /authorized_keys/ },
5014
- { name: "ssh no host key check", pattern: /ssh\s+.*StrictHostKeyChecking\s*=\s*no/ }
5015
- ];
5016
- var SCRIPT_EXEC_PATTERNS = [
5017
- /bash\s+(\S+\.sh)/,
5018
- /sh\s+(\S+\.sh)/,
5019
- /\.\/(\S+\.sh)/,
5020
- /source\s+(\S+\.sh)/,
5021
- /\.\s+(\S+\.sh)/,
5022
- /zsh\s+(\S+\.sh)/
5023
- ];
5024
- var KEYWORD_COMBINATION_RISKS = [
5025
- {
5026
- group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
5027
- group2: /* @__PURE__ */ new Set(["$(base64", "`base64", "base64 decode pipe"]),
5028
- level: "critical",
5029
- 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"
5030
- },
5031
- {
5032
- group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
5033
- group2: /* @__PURE__ */ new Set(["/etc/shadow", "id_rsa", "id_ed25519"]),
5034
- level: "critical",
5035
- 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"
5036
- },
5037
- {
5038
- group1: /* @__PURE__ */ new Set(["nc -e", "ncat -e", "/dev/tcp", "/dev/udp"]),
5039
- group2: /* @__PURE__ */ new Set(),
5040
- level: "high",
5041
- reason: "\u68C0\u6D4B\u5230\u53CD\u5F39 Shell \u7279\u5F81\uFF0C\u653B\u51FB\u8005\u53EF\u80FD\u5EFA\u7ACB\u8FDC\u7A0B\u63A7\u5236\u901A\u9053\u3002"
5042
- },
5043
- {
5044
- group1: /* @__PURE__ */ new Set(["crontab install", "authorized_keys", "ssh no host key check"]),
5045
- group2: /* @__PURE__ */ new Set(),
5046
- level: "high",
5047
- 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"
5048
- }
5049
- ];
5050
- var SINGLE_KEYWORD_RISKS = {
5051
- "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" },
5052
- "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" },
5053
- "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" },
5054
- "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" },
5055
- "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" },
5056
- "nc -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 nc -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
5057
- "ncat -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 ncat -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
5058
- "/dev/tcp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/tcp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
5059
- "/dev/udp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/udp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
5060
- "$(base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
5061
- "`base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
5062
- "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" },
5063
- "/etc/shadow": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6\u3002" },
5064
- "id_rsa": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
5065
- "id_ed25519": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
5066
- "crontab install": { level: "high", reason: "\u68C0\u6D4B\u5230\u4FEE\u6539\u5B9A\u65F6\u4EFB\u52A1\uFF0C\u53EF\u80FD\u662F\u6301\u4E45\u5316\u540E\u95E8\u3002" },
5067
- "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" },
5068
- "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" }
5069
- };
4775
+ import { resolve as resolve7, join as join7 } from "path";
4776
+ var SCRIPT_RULES = loadScriptRules();
4777
+ if (!SCRIPT_RULES || Object.keys(SCRIPT_RULES).length === 0) {
4778
+ console.error(
4779
+ "[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"
4780
+ );
4781
+ }
4782
+ var SUSPICIOUS_PATTERNS = (SCRIPT_RULES.suspicious_patterns ?? []).map((r) => ({ name: r.name, pattern: compileRuleRegex(r) }));
4783
+ var SCRIPT_EXEC_PATTERNS = ((SCRIPT_RULES.script_exec_patterns ?? {}).legacy ?? []).map((r) => compileRuleRegex(r));
4784
+ var KEYWORD_SETS = SCRIPT_RULES.keyword_sets ?? {};
4785
+ var KEYWORD_COMBINATION_RISKS = (SCRIPT_RULES.keyword_combination_risks ?? []).map((r) => ({
4786
+ group1: resolveKeywordSet(r.group1, KEYWORD_SETS),
4787
+ group2: resolveKeywordSet(r.group2, KEYWORD_SETS),
4788
+ level: String(r.level),
4789
+ reason: String(r.reason)
4790
+ }));
4791
+ var SINGLE_KEYWORD_RISKS = SCRIPT_RULES.single_keyword_risks ?? {};
5070
4792
  var LEVEL_ORDER = { low: 1, medium: 2, high: 3, critical: 4 };
5071
4793
  function emptyResult() {
5072
4794
  return { scriptPath: "", scriptContent: "", matchedKeywords: [], lineCount: 0, isSuspicious: false, error: "", riskLevel: "", riskReason: "" };
@@ -5093,10 +4815,10 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
5093
4815
  const hitGroup2 = rule.group2.size === 0 ? true : [...rule.group2].some((k) => keywordSet.has(k));
5094
4816
  if (hitGroup1 && hitGroup2) return { level: rule.level, reason: rule.reason };
5095
4817
  }
5096
- const networkPostKeywords = /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]);
4818
+ const networkPostKeywords = new Set(KEYWORD_SETS.network_post ?? []);
5097
4819
  const hasNetworkPost = [...networkPostKeywords].some((k) => keywordSet.has(k));
5098
4820
  if (hasNetworkPost) {
5099
- const envPatterns = ["$env", "$(env)", "$USER", "$(hostname)", "$(pwd)", "$HOME"];
4821
+ const envPatterns = SCRIPT_RULES.env_exfil_patterns ?? [];
5100
4822
  const contentLower = scriptContent.toLowerCase();
5101
4823
  for (const p of envPatterns) {
5102
4824
  if (contentLower.includes(p.toLowerCase())) {
@@ -5120,7 +4842,7 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
5120
4842
  }
5121
4843
  function readScriptContent(scriptPath, maxLines = 500) {
5122
4844
  try {
5123
- const expandedPath = scriptPath === "~" || scriptPath.startsWith("~/") ? join4(process.env.HOME || "/home/user", scriptPath.slice(1)) : resolve7(scriptPath);
4845
+ const expandedPath = scriptPath === "~" || scriptPath.startsWith("~/") ? join7(process.env.HOME || "/home/user", scriptPath.slice(1)) : resolve7(scriptPath);
5124
4846
  const raw = readFileSync8(expandedPath, "utf-8");
5125
4847
  const lines = raw.split("\n").slice(0, maxLines);
5126
4848
  return { content: lines.join("\n"), lineCount: lines.length, error: "" };
@@ -5178,7 +4900,7 @@ init_config();
5178
4900
  init_logic_rules();
5179
4901
  init_inline_analyzer();
5180
4902
  import { statSync as statSync4, existsSync as existsSync10 } from "fs";
5181
- import { resolve as resolve9, join as join6, isAbsolute as isAbsolute2 } from "path";
4903
+ import { resolve as resolve9, join as join9, isAbsolute as isAbsolute2 } from "path";
5182
4904
  import { homedir as homedir3 } from "os";
5183
4905
 
5184
4906
  // src/engine/script-engine/tracker.ts
@@ -6689,6 +6411,7 @@ var RecursiveTracker = class {
6689
6411
  };
6690
6412
 
6691
6413
  // src/engine/script-content-analyzer.ts
6414
+ init_rules_loader();
6692
6415
  function emptyChain() {
6693
6416
  return {
6694
6417
  nodes: [],
@@ -6705,87 +6428,22 @@ function emptyChain() {
6705
6428
  error: ""
6706
6429
  };
6707
6430
  }
6708
- var SUSPICIOUS_PATTERNS2 = [
6709
- { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
6710
- { name: "curl --data", pattern: /curl\s+(.*\s)?--data/i },
6711
- { name: "curl -d", pattern: /curl\s+(.*\s)?-d\s/i },
6712
- { name: "wget --post-data", pattern: /wget\s+(.*\s)?--post-data/i },
6713
- { name: "wget --post-file", pattern: /wget\s+(.*\s)?--post-file/i },
6714
- { name: "nc -e", pattern: /\bnc\s+.*-e\b/ },
6715
- { name: "ncat -e", pattern: /\bncat\s+.*-e\b/ },
6716
- { name: "/dev/tcp", pattern: /\/dev\/tcp\// },
6717
- { name: "/dev/udp", pattern: /\/dev\/udp\// },
6718
- { name: "$(base64", pattern: /\$\(\s*base64/ },
6719
- { name: "`base64", pattern: /`\s*base64/ },
6720
- { name: "base64 decode pipe", pattern: /base64\s+(-d|--decode)\s*\|/ },
6721
- { name: "/etc/shadow", pattern: /\/etc\/shadow/ },
6722
- { name: "id_rsa", pattern: /\bid_rsa\b/ },
6723
- { name: "id_ed25519", pattern: /\bid_ed25519\b/ },
6724
- { name: "crontab install", pattern: /crontab\s+-/ },
6725
- { name: "authorized_keys", pattern: /authorized_keys/ },
6726
- { name: "ssh no host key check", pattern: /ssh\s+.*StrictHostKeyChecking\s*=\s*no/ }
6727
- ];
6728
- var SCRIPT_EXEC_PATTERNS2 = [
6729
- /(?:^|\s|&&|\|\|)\s*bash\s+(\S+)/i,
6730
- /(?:^|\s|&&|\|\|)\s*\/bin\/bash\s+(\S+)/i,
6731
- /(?:^|\s|&&|\|\|)\s*sh\s+(\S+)/i,
6732
- /(?:^|\s|&&|\|\|)\s*zsh\s+(\S+)/i,
6733
- /\.\/(\S+)/,
6734
- /(?:^|\s|&&|\|\|)\s*source\s+(\S+)/i,
6735
- /(?:^|\s|&&|\|\|)\s*\.\s+(\S+)/,
6736
- /(?:^|\s|&&|\|\|)\s*python3?\s+(\S+\.py)/i,
6737
- /(?:^|\s|&&|\|\|)\s*node\s+(\S+\.js)/i,
6738
- /(?:^|\s|&&|\|\|)\s*ruby\s+(\S+\.rb)/i,
6739
- /(?:^|\s|&&|\|\|)\s*perl\s+(\S+\.pl)/i,
6740
- /(?:^|\s|&&|\|\|)\s*php\s+(\S+\.php)/i,
6741
- /(?:^|\s|&&|\|\|)\s*lua\s+(\S+\.lua)/i
6742
- ];
6743
- var KEYWORD_COMBINATION_RISKS2 = [
6744
- {
6745
- group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
6746
- group2: /* @__PURE__ */ new Set(["$(base64", "`base64", "base64 decode pipe"]),
6747
- level: "critical",
6748
- 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"
6749
- },
6750
- {
6751
- group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
6752
- group2: /* @__PURE__ */ new Set(["/etc/shadow", "id_rsa", "id_ed25519"]),
6753
- level: "critical",
6754
- 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"
6755
- },
6756
- {
6757
- group1: /* @__PURE__ */ new Set(["nc -e", "ncat -e", "/dev/tcp", "/dev/udp"]),
6758
- group2: /* @__PURE__ */ new Set(),
6759
- level: "high",
6760
- reason: "\u68C0\u6D4B\u5230\u53CD\u5F39 Shell \u7279\u5F81\uFF0C\u653B\u51FB\u8005\u53EF\u80FD\u5EFA\u7ACB\u8FDC\u7A0B\u63A7\u5236\u901A\u9053\u3002"
6761
- },
6762
- {
6763
- group1: /* @__PURE__ */ new Set(["crontab install", "authorized_keys", "ssh no host key check"]),
6764
- group2: /* @__PURE__ */ new Set(),
6765
- level: "high",
6766
- 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"
6767
- }
6768
- ];
6769
- var SINGLE_KEYWORD_RISKS2 = {
6770
- "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" },
6771
- "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" },
6772
- "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" },
6773
- "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" },
6774
- "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" },
6775
- "nc -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 nc -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
6776
- "ncat -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 ncat -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
6777
- "/dev/tcp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/tcp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
6778
- "/dev/udp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/udp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
6779
- "$(base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
6780
- "`base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
6781
- "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" },
6782
- "/etc/shadow": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6\u3002" },
6783
- "id_rsa": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
6784
- "id_ed25519": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
6785
- "crontab install": { level: "high", reason: "\u68C0\u6D4B\u5230\u4FEE\u6539\u5B9A\u65F6\u4EFB\u52A1\uFF0C\u53EF\u80FD\u662F\u6301\u4E45\u5316\u540E\u95E8\u3002" },
6786
- "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" },
6787
- "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" }
6788
- };
6431
+ var SCRIPT_RULES2 = loadScriptRules();
6432
+ if (!SCRIPT_RULES2 || Object.keys(SCRIPT_RULES2).length === 0) {
6433
+ console.error(
6434
+ "[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"
6435
+ );
6436
+ }
6437
+ var SUSPICIOUS_PATTERNS2 = (SCRIPT_RULES2.suspicious_patterns ?? []).map((r) => ({ name: r.name, pattern: compileRuleRegex(r) }));
6438
+ var SCRIPT_EXEC_PATTERNS2 = ((SCRIPT_RULES2.script_exec_patterns ?? {}).chain ?? []).map((r) => compileRuleRegex(r));
6439
+ var KEYWORD_SETS2 = SCRIPT_RULES2.keyword_sets ?? {};
6440
+ var KEYWORD_COMBINATION_RISKS2 = (SCRIPT_RULES2.keyword_combination_risks ?? []).map((r) => ({
6441
+ group1: resolveKeywordSet(r.group1, KEYWORD_SETS2),
6442
+ group2: resolveKeywordSet(r.group2, KEYWORD_SETS2),
6443
+ level: String(r.level),
6444
+ reason: String(r.reason)
6445
+ }));
6446
+ var SINGLE_KEYWORD_RISKS2 = SCRIPT_RULES2.single_keyword_risks ?? {};
6789
6447
  var LEVEL_ORDER2 = { low: 1, medium: 2, high: 3, critical: 4 };
6790
6448
  function scanScriptForKeywords2(content) {
6791
6449
  const matched = [];
@@ -6802,10 +6460,10 @@ function assessKeywordCombinationRisk2(matchedKeywords, scriptContent) {
6802
6460
  const hitGroup2 = rule.group2.size === 0 ? true : [...rule.group2].some((k) => keywordSet.has(k));
6803
6461
  if (hitGroup1 && hitGroup2) return { level: rule.level, reason: rule.reason };
6804
6462
  }
6805
- const networkPostKeywords = /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]);
6463
+ const networkPostKeywords = new Set(KEYWORD_SETS2.network_post ?? []);
6806
6464
  const hasNetworkPost = [...networkPostKeywords].some((k) => keywordSet.has(k));
6807
6465
  if (hasNetworkPost) {
6808
- const envPatterns = ["$env", "$(env)", "$USER", "$(hostname)", "$(pwd)", "$HOME"];
6466
+ const envPatterns = SCRIPT_RULES2.env_exfil_patterns ?? [];
6809
6467
  const contentLower = scriptContent.toLowerCase();
6810
6468
  for (const p of envPatterns) {
6811
6469
  if (contentLower.includes(p.toLowerCase())) {
@@ -6952,7 +6610,7 @@ function shlexSplit(s) {
6952
6610
  function resolvePath2(p) {
6953
6611
  let expanded = p;
6954
6612
  if (expanded.startsWith("~/") || expanded === "~") {
6955
- expanded = join6(homedir3(), expanded.slice(1));
6613
+ expanded = join9(homedir3(), expanded.slice(1));
6956
6614
  }
6957
6615
  return resolve9(expanded);
6958
6616
  }
@@ -7076,7 +6734,7 @@ function analyzeScriptContent2(input, ext) {
7076
6734
  isInline = true;
7077
6735
  }
7078
6736
  if (resolvedPath && !isAbsolute2(resolvedPath) && !resolvedPath.startsWith("~/")) {
7079
- resolvedPath = join6(input.cwd || process.cwd(), resolvedPath);
6737
+ resolvedPath = join9(input.cwd || process.cwd(), resolvedPath);
7080
6738
  }
7081
6739
  const l1Adapter = (content) => {
7082
6740
  const j = scanDangerousCommandPatterns("bash", content, input.osType);
@@ -7184,10 +6842,10 @@ async function runLLMAnalysis(input, previous) {
7184
6842
  const config = input.llmConfig?.apiKey ? { ...globalConfig, ...input.llmConfig } : globalConfig;
7185
6843
  const rawDetail = input.aNext.actionDetail.replace(/^terminal-\w+:\s*command=([^,]+).*/, "$1").trim();
7186
6844
  const cacheKey = `${input.aNext.actionType}:${rawDetail || input.aNext.actionDetail}`;
7187
- const cached = llmCache.get(cacheKey);
7188
- if (cached) {
6845
+ const cached2 = llmCache.get(cacheKey);
6846
+ if (cached2) {
7189
6847
  console.log("[AgentMoss] LLM cache hit");
7190
- return cached;
6848
+ return cached2;
7191
6849
  }
7192
6850
  const matchedSkills = matchSkills(input.aNext.actionType, input.aNext.actionDetail, input.reason);
7193
6851
  const templates = loadTemplates();
@@ -7400,9 +7058,9 @@ init_esm_shims();
7400
7058
  // src/brain/rule-store.ts
7401
7059
  init_esm_shims();
7402
7060
  import { existsSync as existsSync12, readFileSync as readFileSync12, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
7403
- import { join as join7 } from "path";
7061
+ import { join as join10 } from "path";
7404
7062
  import { homedir as homedir4 } from "os";
7405
- var DEFAULT_RULES_PATH = join7(homedir4(), ".agentmoss", "brain", "rules.json");
7063
+ var DEFAULT_RULES_PATH = join10(homedir4(), ".agentmoss", "brain", "rules.json");
7406
7064
  var ENV_RULES_PATH = "AGENT_MOSS_BRAIN_RULES_PATH";
7407
7065
  var STATUS_DRAFT = "draft";
7408
7066
  var STATUS_APPROVED = "approved";
@@ -8017,9 +7675,9 @@ function parseReview(content) {
8017
7675
  init_esm_shims();
8018
7676
  import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync13, renameSync, writeFileSync as writeFileSync4 } from "fs";
8019
7677
  import { homedir as homedir5 } from "os";
8020
- import { join as join8 } from "path";
7678
+ import { join as join11 } from "path";
8021
7679
  import { randomUUID } from "crypto";
8022
- var DEFAULT_SKILL_META_PATH = join8(homedir5(), ".agentmoss", "brain", "skill_meta.json");
7680
+ var DEFAULT_SKILL_META_PATH = join11(homedir5(), ".agentmoss", "brain", "skill_meta.json");
8023
7681
  var ENV_SKILL_META_PATH = "AGENT_MOSS_BRAIN_SKILL_META_PATH";
8024
7682
  function getMetaPath(override) {
8025
7683
  return override || process.env[ENV_SKILL_META_PATH] || DEFAULT_SKILL_META_PATH;
@@ -8029,7 +7687,7 @@ var BrainSkillManager = class {
8029
7687
  this.skillsDir = skillsDir;
8030
7688
  this.metaPath = getMetaPath(metaPath);
8031
7689
  try {
8032
- mkdirSync4(join8(this.metaPath, ".."), { recursive: true });
7690
+ mkdirSync4(join11(this.metaPath, ".."), { recursive: true });
8033
7691
  } catch {
8034
7692
  }
8035
7693
  this.load();
@@ -8064,7 +7722,7 @@ var BrainSkillManager = class {
8064
7722
  let changed = false;
8065
7723
  for (const sk of this.skills) {
8066
7724
  if (sk.status !== "active") continue;
8067
- if (!existsSync13(join8(this.skillsDir, `${sk.name}.md`))) {
7725
+ if (!existsSync13(join11(this.skillsDir, `${sk.name}.md`))) {
8068
7726
  sk.status = "stale";
8069
7727
  changed = true;
8070
7728
  }
@@ -8076,7 +7734,7 @@ var BrainSkillManager = class {
8076
7734
  const id = skill.id || randomUUID();
8077
7735
  const name = skill.name || `brain_${id.slice(0, 8)}`;
8078
7736
  if (!existsSync13(this.skillsDir)) return "";
8079
- const mdPath = join8(this.skillsDir, `${name}.md`);
7737
+ const mdPath = join11(this.skillsDir, `${name}.md`);
8080
7738
  if (existsSync13(mdPath)) return "";
8081
7739
  try {
8082
7740
  writeFileSync4(mdPath, skill.content, "utf-8");
@@ -8126,7 +7784,7 @@ var BrainSkillManager = class {
8126
7784
  const out = [];
8127
7785
  for (const sk of this.skills) {
8128
7786
  if (sk.origin_node !== "local" || sk.status !== "active") continue;
8129
- const mdPath = join8(this.skillsDir, `${sk.name}.md`);
7787
+ const mdPath = join11(this.skillsDir, `${sk.name}.md`);
8130
7788
  if (!existsSync13(mdPath)) continue;
8131
7789
  out.push({
8132
7790
  name: sk.name,
@@ -8278,6 +7936,7 @@ init_rules_loader();
8278
7936
 
8279
7937
  // src/engine/formal-engine/decode.ts
8280
7938
  init_esm_shims();
7939
+ init_rules_loader();
8281
7940
  var B64_TOKEN_RE = /[A-Za-z0-9+/]+={0,2}/g;
8282
7941
  function isValidBase64Token(tok) {
8283
7942
  if (tok.length < 8) return false;
@@ -8312,7 +7971,7 @@ function decodeBase64Literals(text) {
8312
7971
  return out;
8313
7972
  }
8314
7973
  var QUOTED_STR_RE = /('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`)/g;
8315
- var OUTPUT_CMDS = /* @__PURE__ */ new Set(["echo", "printf", "print"]);
7974
+ var OUTPUT_CMDS = new Set(loadFormalRules().output_commands ?? []);
8316
7975
  function stripStringLiterals(text) {
8317
7976
  const m = text.match(/\s*([A-Za-z_][\w-]*|\/?[\w./-]+)\b/);
8318
7977
  if (!m) return text;
@@ -8797,8 +8456,9 @@ function extractInlineShell(s) {
8797
8456
  }
8798
8457
  return out;
8799
8458
  }
8800
- var PKG_MGR = /* @__PURE__ */ new Set(["apt", "apt-get", "aptitude", "yum", "dnf", "pacman", "zypper", "apk", "brew"]);
8801
- var SHELLS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "fish", "ksh", "csh", "tcsh", "nologin"]);
8459
+ var FORMAL_RULES = loadFormalRules();
8460
+ var PKG_MGR = new Set(FORMAL_RULES.privesc?.package_managers ?? []);
8461
+ var SHELLS = new Set(FORMAL_RULES.privesc?.bare_shells ?? []);
8802
8462
  function containsPrivesc(shell) {
8803
8463
  if (!shell) return false;
8804
8464
  for (const seg of shell.split(/\&\&|\|\||;|\n/)) {
@@ -8971,7 +8631,7 @@ function lookupToolClass(actionType) {
8971
8631
  return entry.actionClass;
8972
8632
  }
8973
8633
  var 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;
8974
- var STRONG_COMPUTE_TYPES = /* @__PURE__ */ new Set(["python", "node", "exec", "terminal", "cmd"]);
8634
+ var STRONG_COMPUTE_TYPES = new Set(FORMAL_RULES.compute_action_types ?? []);
8975
8635
  function isComputeNode(n) {
8976
8636
  if (STRONG_COMPUTE_TYPES.has(n.actionType.toLowerCase())) return true;
8977
8637
  const cls = lookupToolClass(n.actionType);
@@ -9197,25 +8857,7 @@ import * as path4 from "path";
9197
8857
  import { createHash } from "crypto";
9198
8858
 
9199
8859
  // src/engine/coordinator.ts
9200
- var FULLY_SAFE_TOOLS = /* @__PURE__ */ new Set([
9201
- "ask_user_question",
9202
- "glob",
9203
- "list_dir",
9204
- "ls",
9205
- "count_text_length",
9206
- "filemgr-globfiles"
9207
- ]);
9208
8860
  var TRUSTED_SKILLS_BASE = /* @__PURE__ */ new Set([]);
9209
- var READONLY_SENSITIVE_TOOLS = /* @__PURE__ */ new Set([
9210
- "read",
9211
- "file_read",
9212
- "read_file",
9213
- "head",
9214
- "tail",
9215
- "grep",
9216
- "filemgr-readfile",
9217
- "filemgr-grepfiles"
9218
- ]);
9219
8861
  function whitelistTier(command, fullySafe, readonlySensitive) {
9220
8862
  const segments = splitCommandSegments(command);
9221
8863
  if (segments.length === 0) return "";
@@ -9372,7 +9014,7 @@ async function runAnalysis(input) {
9372
9014
  const actionType = input.aNext.actionType.toLowerCase();
9373
9015
  const rawDetail = extractRawCommand(input.aNext.actionDetail);
9374
9016
  const lowerDetail = rawDetail.toLowerCase();
9375
- if (FULLY_SAFE_TOOLS.has(actionType)) {
9017
+ if (getPlatformTraits(input.osType).safeActionTypes.has(actionType)) {
9376
9018
  return {
9377
9019
  allowed: true,
9378
9020
  reason: `\u5B8C\u5168\u5B89\u5168\u5DE5\u5177\uFF0C\u8DF3\u8FC7\u6DF1\u5EA6\u5206\u6790: ${actionType}`,
@@ -9415,7 +9057,7 @@ async function runAnalysis(input) {
9415
9057
  violatedLayers: []
9416
9058
  };
9417
9059
  }
9418
- let skipLLM = READONLY_SENSITIVE_TOOLS.has(actionType);
9060
+ let skipLLM = getPlatformTraits(input.osType).readonlySensitiveActionTypes.has(actionType);
9419
9061
  if (actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash") {
9420
9062
  const firstCmd = extractFirstCommand(lowerDetail);
9421
9063
  const { fullySafeCommands, readonlySensitiveCommands } = getPlatformTraits(input.osType);