@kenhkl/agent-moss 0.16.13 → 0.16.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,251 +154,6 @@ 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;
163
- }
164
- }
165
- if (/\|\s*\b(?:tee|tac|install)\b/i.test(command)) {
166
- return false;
167
- }
168
- return true;
169
- }
170
- function extractInlineCode(command) {
171
- const parts = [];
172
- let m;
173
- INLINE_CODE_RE.lastIndex = 0;
174
- while ((m = INLINE_CODE_RE.exec(command)) !== null) {
175
- const code = m[1] ?? m[2];
176
- if (code) parts.push(code);
177
- }
178
- ECHO_STRING_RE.lastIndex = 0;
179
- while ((m = ECHO_STRING_RE.exec(command)) !== null) {
180
- const code = m[1] ?? m[2];
181
- if (code) parts.push(code);
182
- }
183
- return parts.join("\n");
184
- }
185
- function hasWriteExecChain(command) {
186
- return WRITE_EXEC_CHAIN_PATTERNS.some((p) => {
187
- p.lastIndex = 0;
188
- return p.test(command);
189
- });
190
- }
191
- function hasDirTraversal(code) {
192
- const lower = code.toLowerCase();
193
- return DIR_TRAVERSAL_KEYWORDS.some((kw) => lower.includes(kw));
194
- }
195
- function hasReadWriteKeyword(code) {
196
- const lower = code.toLowerCase();
197
- return READ_WRITE_KEYWORDS.some((kw) => lower.includes(kw));
198
- }
199
- function analyzeInlineScript(command) {
200
- const code = extractInlineCode(command);
201
- if (!code) {
202
- return { actionable: false, allowed: true, reason: "", riskLevel: "low", riskType: "" };
203
- }
204
- if (hasWriteExecChain(command)) {
205
- return {
206
- actionable: true,
207
- allowed: false,
208
- reason: "\u5185\u8054\u811A\u672C\u542B\u5199\u5165\u6587\u4EF6\u540E\u6267\u884C\u7684\u6A21\u5F0F\uFF0C\u7591\u4F3C\u7ED5\u8FC7\u654F\u611F\u8DEF\u5F84\u68C0\u6D4B",
209
- riskLevel: "critical",
210
- riskType: "script_execution"
211
- };
212
- }
213
- const redirMatch = command.match(REDIRECT_TO_SENSITIVE_RE);
214
- if (redirMatch) {
215
- return {
216
- actionable: true,
217
- allowed: false,
218
- reason: `\u91CD\u5B9A\u5411\u5199\u5165\u654F\u611F\u8DEF\u5F84: ${redirMatch[1]}`,
219
- riskLevel: "critical",
220
- riskType: "file_access"
221
- };
222
- }
223
- const isEchoOrPrintf1 = isLiteralTextCommand(command);
224
- if (!isEchoOrPrintf1 && hasDirTraversal(code) && hasReadWriteKeyword(code)) {
225
- return { actionable: false, allowed: true, reason: "", riskLevel: "low", riskType: "" };
226
- }
227
- const isEchoOrPrintf = isLiteralTextCommand(command);
228
- if (!isEchoOrPrintf && hasReadWriteKeyword(code)) {
229
- return {
230
- actionable: true,
231
- allowed: false,
232
- reason: "\u5185\u8054\u811A\u672C\u542B\u654F\u611F\u8DEF\u5F84\u4E14\u542B\u6587\u4EF6\u8BFB\u5199/\u547D\u4EE4\u6267\u884C\u8C03\u7528\uFF0C\u5224\u4E3A\u771F\u5B9E\u8BBF\u95EE",
233
- riskLevel: "critical",
234
- riskType: "file_access"
235
- };
236
- }
237
- return {
238
- actionable: true,
239
- allowed: true,
240
- reason: "\u654F\u611F\u8DEF\u5F84\u4EC5\u4F5C\u4E3A\u6587\u672C\u5B57\u9762\u91CF\u51FA\u73B0\uFF0C\u672A\u4F34\u968F\u8BFB\u5199\u64CD\u4F5C",
241
- riskLevel: "low",
242
- riskType: "file_access"
243
- };
244
- }
245
- var INLINE_CODE_RE, ECHO_STRING_RE, ECHO_SEG_RE, READ_WRITE_KEYWORDS, WRITE_EXEC_CHAIN_PATTERNS, SHELL_RC_FRAGMENTS, SENSITIVE_PATH_FRAGMENTS, REDIRECT_TO_SENSITIVE_RE, DIR_TRAVERSAL_KEYWORDS;
246
- var init_inline_analyzer = __esm({
247
- "src/engine/inline-analyzer.ts"() {
248
- "use strict";
249
- init_esm_shims();
250
- init_shell_split();
251
- INLINE_CODE_RE = /(?:python|python3|perl|perl5|ruby|jruby|mruby|node|php|lua)\s+(?:-[a-zA-Z]*[cera][a-zA-Z]*\s+)"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
252
- ECHO_STRING_RE = /\b(?:echo|printf)\s+"([^"\\]*(?:\\.[^"\\]*)*)"|\b(?:echo|printf)\s+'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
253
- ECHO_SEG_RE = /^\s*(?:echo|printf)(?:\s|$)/i;
254
- READ_WRITE_KEYWORDS = [
255
- // 函数调用形态(关键词后跟 '(')
256
- "open(",
257
- "read(",
258
- "write(",
259
- "fopen(",
260
- "system(",
261
- "exec(",
262
- "eval(",
263
- "popen(",
264
- "load(",
265
- "dump(",
266
- // 文件读写(组合形态)
267
- "readline",
268
- "readlines",
269
- "writelines",
270
- "getpass",
271
- "pickle.load",
272
- "yaml.load",
273
- "json.load",
274
- "json.read",
275
- "configparser",
276
- "fread",
277
- "fwrite",
278
- "fgets",
279
- // 子进程执行(组合形态)
280
- "os.system",
281
- "os.popen",
282
- "subprocess",
283
- "os.exec",
284
- "os.spawn",
285
- "import os",
286
- "import subprocess",
287
- "shell=true",
288
- "check_output",
289
- "check_call",
290
- "run(",
291
- // 绕过变体检测
292
- "getattr(",
293
- "__builtins__",
294
- "globals(",
295
- "locals(",
296
- // PHP
297
- "file_get_contents",
298
- "file_put_contents",
299
- "passthru",
300
- "shell_exec",
301
- "proc_open",
302
- // Perl/Ruby
303
- "<stdin>",
304
- "open my",
305
- // Node/JS(2026-09 补,与 Python _READ_WRITE_KEYWORDS 同步):历史缺口 ——
306
- // 识别表里一条 Node API 都没有,于是 node -e "require('fs').readFileSync('/etc/shadow')"
307
- // 判为纯文本字面量 → 放行(真漏报)。
308
- "readfilesync(",
309
- "writefilesync(",
310
- "appendfilesync(",
311
- "unlinksync(",
312
- "rmsync(",
313
- "mkdirsync(",
314
- "execsync(",
315
- "spawnsync(",
316
- "execfile",
317
- "createreadstream",
318
- "createwritestream",
319
- "fs.read",
320
- "fs.write",
321
- "fs.promises",
322
- "require('fs')",
323
- 'require("fs")',
324
- "require('node:fs')",
325
- 'require("node:fs")',
326
- "require('child_process')",
327
- 'require("child_process")',
328
- "require('https')",
329
- 'require("https")'
330
- ];
331
- WRITE_EXEC_CHAIN_PATTERNS = [
332
- // echo/cat/printf "..." > file && bash/sh/python file
333
- /(?:echo|cat|printf)\b.*?>\s*\S+.*(?:&&|;|\|)\s*(?:bash|sh|zsh|python\d?|perl|ruby|node|php)\s/gis,
334
- // ... | bash / | sh / | python(管道喂解释器执行)
335
- /\|\s*(?:bash|sh|zsh|python\d?|perl|ruby|node|php)\b/gi,
336
- // eval "$(cat ...)" / eval "$(echo ...)"
337
- /eval\s+"\$\(/gi,
338
- // xargs bash / xargs sh
339
- /\|\s*xargs\s+(?:bash|sh|zsh)/gi
340
- ];
341
- SHELL_RC_FRAGMENTS = [
342
- ".bashrc",
343
- ".zshrc",
344
- ".profile",
345
- ".bash_profile",
346
- ".zprofile"
347
- ];
348
- SENSITIVE_PATH_FRAGMENTS = [
349
- "/etc/shadow",
350
- "/etc/gshadow",
351
- "/etc/passwd",
352
- "/etc/sudoers",
353
- "/etc/hosts",
354
- "/etc/crontab",
355
- "/etc/systemd/",
356
- "/etc/ssh/sshd_config",
357
- "/boot/",
358
- "/proc/sys/",
359
- "/sys/",
360
- "/dev/mem",
361
- "/dev/kmsg",
362
- ".ssh/id_rsa",
363
- ".ssh/id_ed25519",
364
- ".ssh/authorized_keys",
365
- ...SHELL_RC_FRAGMENTS
366
- ];
367
- REDIRECT_TO_SENSITIVE_RE = new RegExp(`>{1,2}\\s*(?:\\S*/)?(${SENSITIVE_PATH_FRAGMENTS.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})(?:\\s|$)`, "i");
368
- DIR_TRAVERSAL_KEYWORDS = [
369
- // Python 目录遍历
370
- "os.listdir",
371
- "os.walk",
372
- "os.scandir",
373
- "glob.glob",
374
- "glob.iglob",
375
- "pathlib.",
376
- "iterdir",
377
- "rglob",
378
- "scandir(",
379
- "listdir("
380
- ];
381
- }
382
- });
383
-
384
- // src/infra/runtime-config-bridge.ts
385
- function registerRuntimeConfigBridge(bridge) {
386
- runtimeConfigBridge.current = bridge;
387
- }
388
- var runtimeConfigBridge;
389
- var init_runtime_config_bridge = __esm({
390
- "src/infra/runtime-config-bridge.ts"() {
391
- "use strict";
392
- init_esm_shims();
393
- runtimeConfigBridge = { current: null };
394
- }
395
- });
396
-
397
157
  // src/infra/rules-mode.ts
398
158
  function truthy(name) {
399
159
  return TRUTHY.has((process.env[name] || "").trim().toLowerCase());
@@ -440,6 +200,19 @@ var init_rules_mode = __esm({
440
200
  }
441
201
  });
442
202
 
203
+ // src/infra/runtime-config-bridge.ts
204
+ function registerRuntimeConfigBridge(bridge) {
205
+ runtimeConfigBridge.current = bridge;
206
+ }
207
+ var runtimeConfigBridge;
208
+ var init_runtime_config_bridge = __esm({
209
+ "src/infra/runtime-config-bridge.ts"() {
210
+ "use strict";
211
+ init_esm_shims();
212
+ runtimeConfigBridge = { current: null };
213
+ }
214
+ });
215
+
443
216
  // src/infra/rules-loader.ts
444
217
  import { readFileSync as readFileSync2, existsSync, readdirSync, statSync } from "fs";
445
218
  import { resolve, dirname, join as join2 } from "path";
@@ -459,8 +232,26 @@ function rulesDirs() {
459
232
  dirs.push(resolve(dataRootDir(), "rules"));
460
233
  return [...new Set(dirs)];
461
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
+ }
462
253
  function findRulesFile(name) {
463
- for (const dir of RULES_DIR_CANDS) {
254
+ for (const dir of rulesDirCands()) {
464
255
  const p = resolve(dir, name);
465
256
  if (existsSync(p)) return p;
466
257
  }
@@ -516,6 +307,74 @@ function loadL1() {
516
307
  function loadL2() {
517
308
  return loadJson("l2_rules.json");
518
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
+ }
519
378
  function loadL3() {
520
379
  const data = { ...loadJson("l3_rules.json") };
521
380
  try {
@@ -844,7 +703,7 @@ function loadExtraRules() {
844
703
  if (isBuiltinOnly()) return {};
845
704
  return rulesCached().extra;
846
705
  }
847
- var __filename2, __dirname2, OVERRIDE_FILENAME, EXTRA_FILENAME, RULES_DIR_ENVS, RULES_DIR_CANDS, frozenJsonCache, SCENARIO_LIST_KEYS, EXTRA_CATEGORIES, STRUCT_KEYS, EXTRA_FLAT_FIELD, _RULES_CACHE, warnedIgnored;
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;
848
707
  var init_rules_loader = __esm({
849
708
  "src/infra/rules-loader.ts"() {
850
709
  "use strict";
@@ -857,24 +716,20 @@ var init_rules_loader = __esm({
857
716
  OVERRIDE_FILENAME = "override.json";
858
717
  EXTRA_FILENAME = "extra.json";
859
718
  RULES_DIR_ENVS = ["AGENT_MOSS_EXTRA_RULES_DIR", "AGENT_MOSS_OVERRIDE_RULES_DIR"];
860
- RULES_DIR_CANDS = (() => {
861
- const cands = [
862
- // 注:`<data_root>/rules/` 不再是"按内置文件名覆盖"的查找位 —— 2026-10 起
863
- // 规则目录只认 override.json / extra.json(见 rulesDirs())。
864
- // dist: __dirname=dist/ → dist/rules;__dirname=dist/engine/ → dist/engine/../rules=dist/rules
865
- resolve(__dirname2, "../rules"),
866
- resolve(__dirname2, "rules"),
867
- // dev src: __dirname=ts/src/infra/ → ts/src/infra/../../../agent_moss/rules = 仓库根 agent_moss/rules
868
- resolve(__dirname2, "../../../agent_moss/rules"),
869
- // dist 但 __dirname 指向 dist/engine 再往上两级的仓库根 agent_moss/rules
870
- resolve(__dirname2, "../../../../agent_moss/rules"),
871
- // 相对当前工作目录的仓库根(兜底,兼容以仓库根为 cwd 运行)
872
- resolve(process.cwd(), "agent_moss/rules")
873
- ];
874
- return cands.filter((c) => existsSync(c));
875
- })();
876
719
  frozenJsonCache = /* @__PURE__ */ new Map();
877
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";
878
733
  EXTRA_CATEGORIES = {
879
734
  dangerous_commands: ["pattern"],
880
735
  injection_detection: ["keyword", "lang"],
@@ -896,7 +751,155 @@ var init_rules_loader = __esm({
896
751
  }
897
752
  });
898
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
+
899
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
+ }
900
903
  function getPlatformTraits(osType) {
901
904
  const accepted = acceptedOsValues(osType);
902
905
  if (accepted.includes("windows")) return WINDOWS_TRAITS;
@@ -904,368 +907,18 @@ function getPlatformTraits(osType) {
904
907
  if (accepted.includes("harmony")) return HARMONY_TRAITS;
905
908
  return LINUX_TRAITS;
906
909
  }
907
- var LINUX_TRAITS, WINDOWS_TRAITS, MACOS_TRAITS, HARMONY_TRAITS;
910
+ var traitsCache, LINUX_TRAITS, WINDOWS_TRAITS, MACOS_TRAITS, HARMONY_TRAITS;
908
911
  var init_platform_traits = __esm({
909
912
  "src/engine/platform-traits.ts"() {
910
913
  "use strict";
911
914
  init_esm_shims();
912
915
  init_logic_rules();
913
- LINUX_TRAITS = {
914
- writeKeywords: [
915
- "write",
916
- "\u5199\u5165",
917
- "save",
918
- "\u4FDD\u5B58",
919
- "modify",
920
- "\u4FEE\u6539",
921
- "update",
922
- "\u66F4\u65B0",
923
- "delete",
924
- "\u5220\u9664",
925
- "remove",
926
- "\u79FB\u9664",
927
- "create",
928
- "\u521B\u5EFA",
929
- "append",
930
- "\u8FFD\u52A0",
931
- "truncate",
932
- "\u622A\u65AD",
933
- "overwrite",
934
- "\u8986\u76D6",
935
- "replace",
936
- "\u66FF\u6362",
937
- "edit"
938
- ],
939
- readKeywords: [
940
- "read",
941
- "\u8BFB\u53D6",
942
- "cat",
943
- "head",
944
- "tail",
945
- "less",
946
- "more",
947
- "view",
948
- "\u67E5\u770B",
949
- "open",
950
- "\u6253\u5F00",
951
- "load",
952
- "\u52A0\u8F7D",
953
- "grep",
954
- "search",
955
- "\u641C\u7D22",
956
- "find",
957
- "\u67E5\u627E"
958
- ],
959
- writeCommandRes: [
960
- /\brm\b/,
961
- /\bunlink\b/,
962
- /\brmdir\b/,
963
- /\bshred\b/,
964
- /\bcp\b/,
965
- /\bmv\b/,
966
- /\binstall\b/,
967
- /\btee\b/,
968
- /\bdd\b/,
969
- /\bmkfs\b/,
970
- /\b(fdisk|parted|cfdisk|sfdisk)\b/,
971
- /\bchmod\b/,
972
- /\bchown\b/,
973
- /\btruncate\b/
974
- ],
975
- readOnlyCommands: /* @__PURE__ */ new Set([
976
- "cat",
977
- "head",
978
- "tail",
979
- "less",
980
- "more",
981
- "grep",
982
- "find",
983
- "awk",
984
- "sed",
985
- "sort",
986
- "uniq",
987
- "wc",
988
- "cut",
989
- "strings",
990
- "od",
991
- "xxd",
992
- "hexdump",
993
- "tr",
994
- "file",
995
- "stat",
996
- "du",
997
- "df",
998
- "ls",
999
- "dir",
1000
- "tree",
1001
- "nl",
1002
- "tac",
1003
- "rev",
1004
- "echo",
1005
- "printf",
1006
- "lsblk",
1007
- "blockdev",
1008
- "smartctl",
1009
- "udevadm",
1010
- "dmidecode",
1011
- "lscpu",
1012
- "lspci",
1013
- "lsusb",
1014
- "lsmem",
1015
- "lsns",
1016
- "lsof",
1017
- "hwinfo",
1018
- "inxi",
1019
- "fdisk",
1020
- "parted",
1021
- "dmesg",
1022
- "journalctl",
1023
- "systemctl",
1024
- "hostnamectl",
1025
- "localectl",
1026
- "timedatectl",
1027
- "ps",
1028
- "top",
1029
- "free",
1030
- "vmstat",
1031
- "iostat",
1032
- "mpstat",
1033
- "pidof",
1034
- "pgrep",
1035
- "ip",
1036
- "ifconfig",
1037
- "route",
1038
- "ss",
1039
- "netstat",
1040
- "arp",
1041
- "ethtool",
1042
- "nmcli",
1043
- "uname",
1044
- "arch",
1045
- "nproc",
1046
- "getconf",
1047
- "getent",
1048
- "id",
1049
- "whoami",
1050
- "who",
1051
- "w",
1052
- "last",
1053
- "uptime",
1054
- "lsmod",
1055
- "modinfo",
1056
- "sysctl"
1057
- ]),
1058
- fullySafeCommands: [
1059
- /^ls(?:\s|$)/i,
1060
- /^dir(?:\s|$)/i,
1061
- /^pwd(?:\s|$)/i,
1062
- /^which\s+/i,
1063
- /^whereis\s+/i,
1064
- /^realpath\s+/i,
1065
- /^basename\s+/i,
1066
- /^dirname\s+/i,
1067
- /^file\s+/i,
1068
- /^stat\s+/i,
1069
- /^du(?:\s|$)/i,
1070
- /^echo\s+/i,
1071
- /^printf\s+/i,
1072
- /^type\s+/i,
1073
- /^command\s+/i,
1074
- // 系统信息查询(只读,不涉及文件/网络/权限)
1075
- /^whoami(?:\s|$)/i,
1076
- /^id(?:\s|$)/i,
1077
- /^hostname(?:\s|$)/i,
1078
- /^uname(?:\s|$)/i,
1079
- /^date(?:\s|$)/i,
1080
- /^env(?:\s|$)/i,
1081
- /^printenv(?:\s|$)/i,
1082
- /^tty(?:\s|$)/i,
1083
- /^arch(?:\s|$)/i,
1084
- /^uptime(?:\s|$)/i,
1085
- /^groups(?:\s|$)/i,
1086
- /^logname(?:\s|$)/i,
1087
- // 系统资源/硬件信息(只读,不读文件内容、无状态变更选项)
1088
- // 与上面 uptime/uname/id 同性质;缺了它们时开 L3 的部署每查一次磁盘/内存
1089
- // 都要走 LLM(实测 df -h 从 5ms 变成十几秒)。
1090
- /^df(?:\s|$)/i,
1091
- /^free(?:\s|$)/i,
1092
- /^vmstat(?:\s|$)/i,
1093
- /^nproc(?:\s|$)/i,
1094
- /^lscpu(?:\s|$)/i,
1095
- /^lsblk(?:\s|$)/i,
1096
- /^lspci(?:\s|$)/i,
1097
- /^lsusb(?:\s|$)/i,
1098
- /^lsmod(?:\s|$)/i
1099
- ],
1100
- readonlySensitiveCommands: [
1101
- /^cat\s+/i,
1102
- /^head(?:\s|$)/i,
1103
- /^tail(?:\s|$)/i,
1104
- /^less(?:\s|$)/i,
1105
- /^more(?:\s|$)/i,
1106
- /^wc(?:\s|$)/i,
1107
- /^grep(?:\s|$)/i,
1108
- /^find\s+/i,
1109
- /^ag(?:\s|$)/i,
1110
- /^rg(?:\s|$)/i,
1111
- /^awk\s+/i,
1112
- // NEW-FN-P(M03):sed -i/--in-place 是原地写,挡出白名单走 L2 read_before_write。
1113
- // 前瞻放最前(外层 ^sed\s+ 会消费 -i 前的空格)。与 Python profiles/linux.py 对齐。
1114
- /^(?!.*(?:\s|['"])-[a-zA-Z]*i[a-zA-Z]*\b)(?!.*(?:\s|['"])--in-place)sed\s+/i,
1115
- /^cut\s+/i,
1116
- /^sort(?:\s|$)/i,
1117
- /^uniq(?:\s|$)/i,
1118
- // 进程/网络/打开文件信息:只读,但会暴露进程命令行、连接对端、打开的文件
1119
- // 路径,故只跳 L3、保留 L2(L1 敏感路径检测照常生效)。
1120
- /^ps(?:\s|$)/i,
1121
- /^ss(?:\s|$)/i,
1122
- /^netstat(?:\s|$)/i,
1123
- /^lsof(?:\s|$)/i,
1124
- // 只读子命令级白名单:匹配的是「第一段命令」而非首词(extractFirstCommand
1125
- // 按 | && || 切分),所以能精确到子命令。systemctl stop/disable 等不在此列。
1126
- /^systemctl\s+(?:status|is-active|is-enabled|is-failed|list-units|list-unit-files|list-dependencies|show|cat)\b/i,
1127
- /^service\s+\S+\s+status\b/i,
1128
- // journalctl 整族是只读的,但 --vacuum-*/--rotate/--flush/--sync 会删改日志,
1129
- // 用否定前瞻挡掉后再放行。注意前瞻里必须用 .* 扫描**整段**:只写 (?!\s+--...)
1130
- // 的话只看紧跟 journalctl 的第一个参数,
1131
- // `journalctl -u sshd --vacuum-size=100M` 就漏过去了。
1132
- /^journalctl\b(?!.*--(?:vacuum-size|vacuum-time|vacuum-files|rotate|flush|sync)\b)/i
1133
- ]
1134
- };
1135
- WINDOWS_TRAITS = {
1136
- writeKeywords: [
1137
- "write",
1138
- "\u5199\u5165",
1139
- "save",
1140
- "\u4FDD\u5B58",
1141
- "modify",
1142
- "\u4FEE\u6539",
1143
- "update",
1144
- "\u66F4\u65B0",
1145
- "delete",
1146
- "\u5220\u9664",
1147
- "remove",
1148
- "\u79FB\u9664",
1149
- "create",
1150
- "\u521B\u5EFA",
1151
- "append",
1152
- "\u8FFD\u52A0",
1153
- "overwrite",
1154
- "\u8986\u76D6",
1155
- "replace",
1156
- "\u66FF\u6362",
1157
- "set",
1158
- "\u8BBE\u7F6E"
1159
- // 注意:这里**不能放 `reg` / `注册表`**。关键词是子串包含匹配,放进去等于
1160
- // 「只要命令里出现 reg 字样就算写操作」——`reg query`(读)会被判成写,
1161
- // 进而误拦对 deny_write 路径的只读查询。注册表的写判定交给下面的
1162
- // writeCommandRes(只认写子命令)。两端语义必须一致。
1163
- ],
1164
- readKeywords: [
1165
- "read",
1166
- "\u8BFB\u53D6",
1167
- "type",
1168
- "findstr",
1169
- "dir",
1170
- "view",
1171
- "\u67E5\u770B",
1172
- "open",
1173
- "\u6253\u5F00",
1174
- "load",
1175
- "\u52A0\u8F7D",
1176
- "search",
1177
- "\u641C\u7D22",
1178
- "find",
1179
- "\u67E5\u627E",
1180
- "get",
1181
- "\u83B7\u53D6"
1182
- ],
1183
- // Windows 写/删命令:del/copy/move/xcopy/replace/rmdir/icacls/reg delete 等
1184
- writeCommandRes: [
1185
- /\b(del|erase)\b/,
1186
- /\brmdir\b/,
1187
- /\b(rd)\b/,
1188
- /\bcopy\b/,
1189
- /\b(xcopy|robocopy)\b/,
1190
- /\b(move|movefile)\b/,
1191
- /\breplace\b/,
1192
- /\bicacls\b/,
1193
- /\battrib\b/,
1194
- // 注册表:**只认写子命令**(对齐 Python WindowsProfile.write_command_res)。
1195
- // 此前是 /\breg\b/,把 reg query(读)也判成写 → 误拦对 deny_write 路径的只读查询。
1196
- // 写子命令 add/delete/copy/save/restore/import/load/unload;
1197
- // 读子命令 query/compare/export/flags/help 不在此列。
1198
- /\breg(?:\.exe)?\s+(?:add|delete|copy|save|restore|import|load|unload)\b/,
1199
- /\bformat\b/,
1200
- /\bdiskpart\b/,
1201
- /\bmkdir\b/,
1202
- /\bmd\b/,
1203
- /\bsetx\b/,
1204
- /\bwmic\b/
1205
- ],
1206
- // Windows 只读命令(重定向写判定用):type/findstr/more/tree/dir 等
1207
- readOnlyCommands: /* @__PURE__ */ new Set([
1208
- "type",
1209
- "findstr",
1210
- "more",
1211
- "tree",
1212
- "dir",
1213
- "where",
1214
- "ver",
1215
- "cls",
1216
- "echo",
1217
- "help",
1218
- "fc",
1219
- "comp",
1220
- "find",
1221
- "sort",
1222
- "powershell",
1223
- "cmd",
1224
- "netstat",
1225
- "ipconfig",
1226
- "tasklist",
1227
- "systeminfo",
1228
- "driverquery",
1229
- "reg",
1230
- "wmic",
1231
- "whoami",
1232
- "hostname",
1233
- "getmac",
1234
- "query",
1235
- "w32tm",
1236
- "diskpart"
1237
- ]),
1238
- // Windows 完全安全命令(对齐 WindowsProfile.fully_safe_commands)
1239
- fullySafeCommands: [
1240
- /^dir(?:\s|$)/,
1241
- /^where\s+/,
1242
- /^echo\s+/,
1243
- /^cd(?:\s|$)/,
1244
- /^tree(?:\s|$)/,
1245
- /^help(?:\s|$)/,
1246
- /^cls(?:\s|$)/,
1247
- /^ver(?:\s|$)/,
1248
- // 系统信息查询(只读,不读文件内容、无状态变更选项),与 Linux 侧 df/free 对齐
1249
- /^whoami(?:\s|$)/,
1250
- /^hostname(?:\s|$)/,
1251
- /^systeminfo(?:\s|$)/,
1252
- /^tasklist(?:\s|$)/,
1253
- /^getmac(?:\s|$)/,
1254
- /^driverquery(?:\s|$)/,
1255
- /^netstat(?:\s|$)/
1256
- ],
1257
- // Windows 安全但可能访问敏感路径的命令(对齐 WindowsProfile.readonly_sensitive_commands)
1258
- readonlySensitiveCommands: [
1259
- /^type\s+/,
1260
- /^findstr\s+/,
1261
- // 只读子命令级白名单(匹配「第一段命令」而非首词);
1262
- // sc create/delete/start/stop、reg add/delete/set 均不在此列。
1263
- /^sc\s+query\b/,
1264
- /^reg\s+query\b/
1265
- ]
1266
- };
1267
- MACOS_TRAITS = LINUX_TRAITS;
1268
- 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");
1269
922
  }
1270
923
  });
1271
924
 
@@ -1424,7 +1077,6 @@ function checkIntentConsistency(input) {
1424
1077
  const { aNext, promptSession, promptHistory } = input;
1425
1078
  const actionDetail = aNext.actionDetail.toLowerCase();
1426
1079
  const actionType = aNext.actionType.toLowerCase();
1427
- const EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
1428
1080
  if (EXCLUDED_PREFIXES.some((p) => actionType.startsWith(p))) return null;
1429
1081
  if (INLINE_SCRIPT_RE.test(actionDetail)) return null;
1430
1082
  const intentTexts = collectIntentTexts(promptSession, promptHistory);
@@ -1452,7 +1104,6 @@ function runLogicCheck(input, opts) {
1452
1104
  const actionDetail = aNext.actionDetail.toLowerCase();
1453
1105
  const actionType = aNext.actionType.toLowerCase();
1454
1106
  const resolvedCwd = cwd || "/";
1455
- const EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
1456
1107
  const isExcludedType = EXCLUDED_PREFIXES.some((p) => actionType.startsWith(p));
1457
1108
  if (!opts?.skipReadBeforeWrite) {
1458
1109
  const rbwResult = checkReadBeforeWrite(actionType, actionDetail, actionHistory, input.reason, resolvedCwd, input.osType);
@@ -1564,7 +1215,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
1564
1215
  const isBashLike = actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash";
1565
1216
  const isWrite = isBashLike ? isWriteOperation(actionType, actionDetail, void 0, osType) : getPlatformTraits(osType).writeKeywords.some((kw) => actionType.includes(kw));
1566
1217
  if (!isWrite) return null;
1567
- const WRITE_CMDS = /* @__PURE__ */ new Set(["cp", "mv", "install", "tee", "dd", "truncate", "mkfs", "fdisk", "parted"]);
1218
+ const WRITE_CMDS = new Set(_LOGIC.write_commands ?? []);
1568
1219
  if (isBashLike) {
1569
1220
  const detailNoCompare = actionDetail.replace(/<=>|>=|<=|<>|=>/g, " ");
1570
1221
  const detailNoDevnull = detailNoCompare.replace(DEVNULL_REDIRECT_RE, "");
@@ -1596,7 +1247,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
1596
1247
  }
1597
1248
  if (!hasWriteSeg) return null;
1598
1249
  }
1599
- const SOURCE_ARG_CMDS = /* @__PURE__ */ new Set(["cp", "mv", "install", "rsync"]);
1250
+ const SOURCE_ARG_CMDS = new Set(_LOGIC.source_arg_commands ?? []);
1600
1251
  const REDIRECT_TARGET_RE = new RegExp(`>{1,2}\\s*(?!&)(?:'(/[^']+)'|"(/[^\\"]+)"|(/[^\\s;|&><'\\"]+))`);
1601
1252
  const targetPaths = [];
1602
1253
  for (const seg of splitCommandSegments(actionDetail)) {
@@ -1644,7 +1295,6 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
1644
1295
  }
1645
1296
  }
1646
1297
  }
1647
- const TEMP_DIRS = ["/tmp/", "/var/tmp/", "/dev/shm/", "/run/"];
1648
1298
  let filteredPaths = uniqueTargetPaths;
1649
1299
  if (/\bdd\b/i.test(actionDetail) && /\bif=\/dev\/zero\b/i.test(actionDetail)) {
1650
1300
  filteredPaths = uniqueTargetPaths.filter(
@@ -1745,10 +1395,10 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd, osType) {
1745
1395
  if (!hasTraversal) {
1746
1396
  return null;
1747
1397
  }
1748
- const READ_KEYWORDS = ["read(", "readline", "readlines", "fread", "fgets", "load(", "getpass", "file_get_contents"];
1749
- const WRITE_KEYWORDS2 = ["write(", "writelines", "fwrite", "file_put_contents"];
1750
- 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"];
1751
- 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;
1752
1402
  const code = extractInlineCode(actionDetail);
1753
1403
  if (!code) return null;
1754
1404
  const codeLower = code.toLowerCase();
@@ -1811,7 +1461,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd, osType) {
1811
1461
  violatedLayers: ["L2.1"]
1812
1462
  };
1813
1463
  }
1814
- 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;
1815
1465
  var init_logic_rules = __esm({
1816
1466
  "src/engine/logic-rules.ts"() {
1817
1467
  "use strict";
@@ -1821,6 +1471,10 @@ var init_logic_rules = __esm({
1821
1471
  init_rules_loader();
1822
1472
  init_platform_traits();
1823
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 + "/");
1824
1478
  ROOT_SENSITIVE_PATHS = _loadSensitivePathScope("root");
1825
1479
  HOME_SENSITIVE_PATHS = _loadSensitivePathScope("home");
1826
1480
  CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path5, riskLevel, desc, dirDelOnly }) => ({ path: path5, riskLevel, desc, credential: true, dirDelOnly }));
@@ -1841,7 +1495,7 @@ var init_logic_rules = __esm({
1841
1495
  "list_dir",
1842
1496
  "count_text_length",
1843
1497
  "search",
1844
- // filemgr 只读三件套(globfiles 在 coordinator FULLY_SAFE_TOOLS 提前放行,此处兜底防御非 coordinator 入口)
1498
+ // filemgr 只读三件套(globfiles 在 platform-traits 的 safeActionTypes 提前放行,此处兜底防御非 coordinator 入口)
1845
1499
  "filemgr-globfiles",
1846
1500
  "filemgr-readfile",
1847
1501
  "filemgr-grepfiles",
@@ -1853,7 +1507,7 @@ var init_logic_rules = __esm({
1853
1507
  "tail",
1854
1508
  "cat"
1855
1509
  ]);
1856
- NON_WRITE_TOOL_PREFIXES = ["memory-", "skill-manager-", "mailbox-", "app-"];
1510
+ NON_WRITE_TOOL_PREFIXES = _LOGIC.non_write_tool_prefixes ?? [];
1857
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"];
1858
1512
  SED_INPLACE_RE = /\bsed\s+(?:-\S+\s+)*(?:-[a-zA-Z]*i[a-zA-Z]*|--in-place)\b/i;
1859
1513
  DEVNULL_REDIRECT_RE = /(?:2|&|1)?\s*>\s*\/dev\/null\b/g;
@@ -1894,25 +1548,24 @@ var init_logic_rules = __esm({
1894
1548
  }));
1895
1549
  })();
1896
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;
1897
- DIR_TRAVERSAL_KW = [
1898
- "os.listdir",
1899
- "os.walk",
1900
- "os.scandir",
1901
- "glob.glob",
1902
- "glob.iglob",
1903
- "pathlib.",
1904
- "iterdir",
1905
- "rglob",
1906
- "scandir(",
1907
- "listdir("
1908
- ];
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 ?? [];
1909
1557
  }
1910
1558
  });
1911
1559
 
1912
1560
  // src/engine/asset-dir.ts
1913
1561
  import { existsSync as existsSync3 } from "fs";
1914
- import { resolve as resolve2 } from "path";
1562
+ import { join as join3, resolve as resolve2 } from "path";
1915
1563
  function assetDirCandidates(moduleDir, name) {
1564
+ const root = getRulesRoot();
1565
+ if (root) {
1566
+ const p = join3(root, name);
1567
+ return [p, p];
1568
+ }
1916
1569
  return [
1917
1570
  resolve2(moduleDir, name),
1918
1571
  resolve2(moduleDir, "../../..", "agent_moss", name)
@@ -1928,6 +1581,7 @@ var init_asset_dir = __esm({
1928
1581
  "src/engine/asset-dir.ts"() {
1929
1582
  "use strict";
1930
1583
  init_esm_shims();
1584
+ init_paths();
1931
1585
  }
1932
1586
  });
1933
1587
 
@@ -2128,7 +1782,7 @@ __export(runtime_config_exports, {
2128
1782
  updateSkillEnabled: () => updateSkillEnabled
2129
1783
  });
2130
1784
  import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync5, mkdirSync, unlinkSync, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
2131
- import { join as join3 } from "path";
1785
+ import { join as join4 } from "path";
2132
1786
  import { fileURLToPath as fileURLToPath4 } from "url";
2133
1787
  import { dirname as dirname3 } from "path";
2134
1788
  function runtimeDir() {
@@ -2263,7 +1917,10 @@ function filterEnabledRules(rules, categoryEnabled = true) {
2263
1917
  }
2264
1918
  function _filterRulesByOs(rules, osType) {
2265
1919
  const accepted = acceptedOsValues(osType);
2266
- 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
+ });
2267
1924
  }
2268
1925
  function _userCustomRules(runtime, layers, requiredKey) {
2269
1926
  const list = Array.isArray(layers) ? layers : [layers];
@@ -2450,6 +2107,9 @@ function _l1InjectionKeywordsToRules() {
2450
2107
  keyword: kw.keyword,
2451
2108
  risk_level: kw.riskLevel,
2452
2109
  lang: kw.lang || "en",
2110
+ // 带上条目自己的 os(`all` = 全平台通用),别丢 —— 丢了 Windows 会拿到
2111
+ // 本不该给它的条目。与 Python `_l1_injection_keywords_to_rules` 对齐。
2112
+ os: kw.os,
2453
2113
  enabled: true,
2454
2114
  builtin: true
2455
2115
  }));
@@ -2567,7 +2227,8 @@ function _l3SkillsToRules() {
2567
2227
  }
2568
2228
  function _scenarioRulesToDefaults() {
2569
2229
  const out = {};
2570
- 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;
2571
2232
  if (!existsSync5(scenDir)) return out;
2572
2233
  let entries;
2573
2234
  try {
@@ -2576,7 +2237,7 @@ function _scenarioRulesToDefaults() {
2576
2237
  return out;
2577
2238
  }
2578
2239
  for (const scenarioId of entries) {
2579
- const rulesFile = resolveScenarioRulesFile(scenarioId) ?? join3(scenDir, scenarioId, "rules.json");
2240
+ const rulesFile = resolveScenarioRulesFile(scenarioId) ?? join4(scenDir, scenarioId, "rules.json");
2580
2241
  if (!existsSync5(rulesFile)) continue;
2581
2242
  let data;
2582
2243
  try {
@@ -2616,7 +2277,8 @@ function _scenarioRulesToDefaults() {
2616
2277
  }
2617
2278
  function _agentRulesToDefaults() {
2618
2279
  const out = {};
2619
- 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;
2620
2282
  if (!existsSync5(agentsDir)) return out;
2621
2283
  let entries;
2622
2284
  try {
@@ -2625,7 +2287,7 @@ function _agentRulesToDefaults() {
2625
2287
  return out;
2626
2288
  }
2627
2289
  for (const agentId of entries) {
2628
- const rulesFile = join3(agentsDir, agentId, "rules.json");
2290
+ const rulesFile = join4(agentsDir, agentId, "rules.json");
2629
2291
  if (!existsSync5(rulesFile)) continue;
2630
2292
  let data;
2631
2293
  try {
@@ -3004,7 +2666,11 @@ function getDisabledSkillIds(runtime) {
3004
2666
  }
3005
2667
  function _filterDefaultsByOs(rules, osType) {
3006
2668
  const accepted = acceptedOsValues(osType);
3007
- 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
+ });
3008
2674
  }
3009
2675
  function _hasRulesForOs(rules, osType) {
3010
2676
  return _filterRulesByOs(rules || [], osType || "linux").length > 0;
@@ -3233,12 +2899,12 @@ function addCustomSkill(skillId, category, keywords, content) {
3233
2899
  );
3234
2900
  }
3235
2901
  const runtime = loadRuntimeConfig();
3236
- const skillsDir = join3(getRuntimeConfigDir(), "agent_moss_skills");
2902
+ const skillsDir = join4(getRuntimeConfigDir(), "agent_moss_skills");
3237
2903
  try {
3238
2904
  mkdirSync(skillsDir, { recursive: true });
3239
2905
  } catch {
3240
2906
  }
3241
- writeFileSync(join3(skillsDir, `${skillId}.md`), content, "utf-8");
2907
+ writeFileSync(join4(skillsDir, `${skillId}.md`), content, "utf-8");
3242
2908
  const l3 = runtime.L3_skills || (runtime.L3_skills = {});
3243
2909
  const cat = l3[category] || (l3[category] = { category_enabled: true, category_desc: "", skills: [] });
3244
2910
  (cat.skills || (cat.skills = [])).push({ id: skillId, keywords, enabled: true, builtin: false });
@@ -3253,7 +2919,7 @@ function deleteCustomSkill(skillId, category) {
3253
2919
  if (idx >= 0) {
3254
2920
  if (skills[idx].builtin) return null;
3255
2921
  skills.splice(idx, 1);
3256
- const skillPath = join3(getRuntimeConfigDir(), "agent_moss_skills", `${skillId}.md`);
2922
+ const skillPath = join4(getRuntimeConfigDir(), "agent_moss_skills", `${skillId}.md`);
3257
2923
  try {
3258
2924
  if (existsSync5(skillPath)) unlinkSync(skillPath);
3259
2925
  } catch {
@@ -3462,7 +3128,7 @@ var init_runtime_config = __esm({
3462
3128
  __dirname4 = dirname3(fileURLToPath4(import.meta.url));
3463
3129
  ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
3464
3130
  RUNTIME_CONFIG_DIR = runtimeDir();
3465
- RUNTIME_CONFIG_PATH = join3(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
3131
+ RUNTIME_CONFIG_PATH = join4(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
3466
3132
  LAYER_DISABLE_ENV = {
3467
3133
  L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
3468
3134
  L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
@@ -3490,10 +3156,10 @@ var init_runtime_config = __esm({
3490
3156
  intent_consistency: ["intent_keywords", "reason"]
3491
3157
  };
3492
3158
  hasRuntimeConfigCache = /* @__PURE__ */ new Map();
3493
- _AGENTS_DIR_PROD = join3(__dirname4, "agents");
3494
- _AGENTS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "agents");
3495
- _SCENARIOS_DIR_PROD = join3(__dirname4, "scenarios");
3496
- _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");
3497
3163
  _effectiveCache = /* @__PURE__ */ new WeakMap();
3498
3164
  SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
3499
3165
  registerRuntimeConfigBridge({
@@ -3832,9 +3498,11 @@ var init_config = __esm({
3832
3498
 
3833
3499
  // src/agents/index.ts
3834
3500
  import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
3835
- import { resolve as resolve4, dirname as dirname4 } from "path";
3501
+ import { resolve as resolve4, dirname as dirname4, join as join5 } from "path";
3836
3502
  import { fileURLToPath as fileURLToPath6 } from "url";
3837
3503
  function resolveAgentsDir() {
3504
+ const root = getRulesRoot();
3505
+ if (root) return join5(root, "agents");
3838
3506
  if (existsSync6(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
3839
3507
  if (existsSync6(AGENTS_DIR_DEV_FALLBACK)) return AGENTS_DIR_DEV_FALLBACK;
3840
3508
  return AGENTS_DIR_PROD;
@@ -3898,6 +3566,7 @@ var init_agents = __esm({
3898
3566
  "src/agents/index.ts"() {
3899
3567
  "use strict";
3900
3568
  init_esm_shims();
3569
+ init_paths();
3901
3570
  init_runtime_config();
3902
3571
  __filename4 = fileURLToPath6(import.meta.url);
3903
3572
  __dirname5 = dirname4(__filename4);
@@ -3909,9 +3578,11 @@ var init_agents = __esm({
3909
3578
 
3910
3579
  // src/scenarios/index.ts
3911
3580
  import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
3912
- import { resolve as resolve5, dirname as dirname5 } from "path";
3581
+ import { resolve as resolve5, dirname as dirname5, join as join6 } from "path";
3913
3582
  import { fileURLToPath as fileURLToPath7 } from "url";
3914
3583
  function resolveScenariosDir() {
3584
+ const root = getRulesRoot();
3585
+ if (root) return join6(root, "scenarios");
3915
3586
  if (existsSync7(SCENARIOS_DIR_PROD)) return SCENARIOS_DIR_PROD;
3916
3587
  if (existsSync7(SCENARIOS_DIR_DEV_FALLBACK)) return SCENARIOS_DIR_DEV_FALLBACK;
3917
3588
  return SCENARIOS_DIR_PROD;
@@ -3992,6 +3663,7 @@ var init_scenarios = __esm({
3992
3663
  "src/scenarios/index.ts"() {
3993
3664
  "use strict";
3994
3665
  init_esm_shims();
3666
+ init_paths();
3995
3667
  init_agents();
3996
3668
  init_runtime_config();
3997
3669
  init_rules_loader();
@@ -4398,7 +4070,7 @@ function severityRank(s) {
4398
4070
  }
4399
4071
  function isSearchActionType(actionType) {
4400
4072
  const at = actionType.toLowerCase();
4401
- 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);
4402
4074
  }
4403
4075
  function checkSensitivePath(command, cwd, agentExt, osType) {
4404
4076
  const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
@@ -4688,8 +4360,7 @@ function isSshKeygenGenerate(command) {
4688
4360
  function checkDangerousPatterns(actionType, actionDetail) {
4689
4361
  if (actionType === "file_write" || actionType === "file_edit") return null;
4690
4362
  const scan = stripHeredocContent(actionDetail);
4691
- const TEMP_DIRS = ["/tmp", "/var/tmp", "/run", "/dev/shm"];
4692
- const isTempPath = TEMP_DIRS.some(
4363
+ const isTempPath = TEMP_DIRS2.some(
4693
4364
  (td) => new RegExp(`(?:^|[\\s/])${td.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[\\s/]|$)`).test(actionDetail)
4694
4365
  ) || /(?:^|[\s;]|&&\s*|\|\|\s*)var\/tmp(?:[/\s;]|$)/.test(actionDetail);
4695
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;
@@ -4835,7 +4506,7 @@ function checkLateralMovement(actionDetail) {
4835
4506
  }
4836
4507
  return null;
4837
4508
  }
4838
- 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;
4839
4510
  var init_heuristic = __esm({
4840
4511
  "src/engine/heuristic.ts"() {
4841
4512
  "use strict";
@@ -4852,7 +4523,8 @@ var init_heuristic = __esm({
4852
4523
  return kws.map((k) => ({
4853
4524
  keyword: k.keyword,
4854
4525
  riskLevel: k.risk_level,
4855
- lang: k.lang || "en"
4526
+ lang: k.lang || "en",
4527
+ os: String(k.os || "all").toLowerCase()
4856
4528
  }));
4857
4529
  })();
4858
4530
  DANGEROUS_PATTERNS2 = _loadDangerousPatterns("linux");
@@ -4860,7 +4532,9 @@ var init_heuristic = __esm({
4860
4532
  DANGEROUS_PATTERNS_MACOS = _loadDangerousPatterns("macos");
4861
4533
  DANGEROUS_PATTERNS_HARMONY = _loadDangerousPatterns("harmony");
4862
4534
  LOOPBACK_URL_RE = /https?:\/\/([^\/\s;'"|&)]+)/i;
4863
- 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 ?? [];
4864
4538
  PATH_CONTEXT_TRIGGER_RE = /(?:^|[;&|]\s*)(?:rm|rmdir|unlink|ln|mv|cp|dd|truncate|shred|chmod|chown)\b/i;
4865
4539
  HOME_PREFIX_RE = /(?:~|~(?=\/)|\$HOME\b|\$\{HOME\}|\/home\/[a-z0-9_.-]+)/gi;
4866
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;
@@ -4868,8 +4542,9 @@ var init_heuristic = __esm({
4868
4542
  PY_BIGNUM_RE = /\d{6,}/;
4869
4543
  PY_INLINE_C_RE = /(python(?:3(?:\.\d+)?)?|perl5?|ruby|node|php|lua)\s+(?:-[a-zA-Z]+\s+)*-c\s+(?:"([^"]*)"|'([^']*)')/i;
4870
4544
  PY_CMD_SUBST_C_RE = /\$\(\s*(python(?:3(?:\.\d+)?)?|perl5?|ruby|node|php|lua)\s+(?:-[a-zA-Z]+\s+)*-c\s+(?:"([^"]*)"'|'([^']*)')\s*\)/i;
4871
- SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
4872
- 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 ?? [];
4873
4548
  HOME_TARGET_RE = /(?:^|[\s="'($])(?:~|(?:\$home|\$\{home\}))(?:\/(?:\.\*|\*|\.)?)?(?=[\s"';}|&)]|$)/i;
4874
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;
4875
4550
  }
@@ -5095,87 +4770,25 @@ function loadTemplates() {
5095
4770
 
5096
4771
  // src/engine/script-analyzer.ts
5097
4772
  init_esm_shims();
4773
+ init_rules_loader();
5098
4774
  import { readFileSync as readFileSync8 } from "fs";
5099
- import { resolve as resolve7, join as join4 } from "path";
5100
- var SUSPICIOUS_PATTERNS = [
5101
- // Network exfiltration (curl/wget POST in scripts is almost never legitimate)
5102
- { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
5103
- { name: "curl --data", pattern: /curl\s+(.*\s)?--data/i },
5104
- { name: "curl -d", pattern: /curl\s+(.*\s)?-d\s/i },
5105
- { name: "wget --post-data", pattern: /wget\s+(.*\s)?--post-data/i },
5106
- { name: "wget --post-file", pattern: /wget\s+(.*\s)?--post-file/i },
5107
- // Reverse shell
5108
- { name: "nc -e", pattern: /\bnc\s+.*-e\b/ },
5109
- { name: "ncat -e", pattern: /\bncat\s+.*-e\b/ },
5110
- { name: "/dev/tcp", pattern: /\/dev\/tcp\// },
5111
- { name: "/dev/udp", pattern: /\/dev\/udp\// },
5112
- // Base64 obfuscation
5113
- { name: "$(base64", pattern: /\$\(\s*base64/ },
5114
- { name: "`base64", pattern: /`\s*base64/ },
5115
- { name: "base64 decode pipe", pattern: /base64\s+(-d|--decode)\s*\|/ },
5116
- // Sensitive file access
5117
- { name: "/etc/shadow", pattern: /\/etc\/shadow/ },
5118
- { name: "id_rsa", pattern: /\bid_rsa\b/ },
5119
- { name: "id_ed25519", pattern: /\bid_ed25519\b/ },
5120
- // Persistence backdoor
5121
- { name: "crontab install", pattern: /crontab\s+-/ },
5122
- { name: "authorized_keys", pattern: /authorized_keys/ },
5123
- { name: "ssh no host key check", pattern: /ssh\s+.*StrictHostKeyChecking\s*=\s*no/ }
5124
- ];
5125
- var SCRIPT_EXEC_PATTERNS = [
5126
- /bash\s+(\S+\.sh)/,
5127
- /sh\s+(\S+\.sh)/,
5128
- /\.\/(\S+\.sh)/,
5129
- /source\s+(\S+\.sh)/,
5130
- /\.\s+(\S+\.sh)/,
5131
- /zsh\s+(\S+\.sh)/
5132
- ];
5133
- var KEYWORD_COMBINATION_RISKS = [
5134
- {
5135
- group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
5136
- group2: /* @__PURE__ */ new Set(["$(base64", "`base64", "base64 decode pipe"]),
5137
- level: "critical",
5138
- 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"
5139
- },
5140
- {
5141
- group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
5142
- group2: /* @__PURE__ */ new Set(["/etc/shadow", "id_rsa", "id_ed25519"]),
5143
- level: "critical",
5144
- 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"
5145
- },
5146
- {
5147
- group1: /* @__PURE__ */ new Set(["nc -e", "ncat -e", "/dev/tcp", "/dev/udp"]),
5148
- group2: /* @__PURE__ */ new Set(),
5149
- level: "high",
5150
- reason: "\u68C0\u6D4B\u5230\u53CD\u5F39 Shell \u7279\u5F81\uFF0C\u653B\u51FB\u8005\u53EF\u80FD\u5EFA\u7ACB\u8FDC\u7A0B\u63A7\u5236\u901A\u9053\u3002"
5151
- },
5152
- {
5153
- group1: /* @__PURE__ */ new Set(["crontab install", "authorized_keys", "ssh no host key check"]),
5154
- group2: /* @__PURE__ */ new Set(),
5155
- level: "high",
5156
- 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"
5157
- }
5158
- ];
5159
- var SINGLE_KEYWORD_RISKS = {
5160
- "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" },
5161
- "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" },
5162
- "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" },
5163
- "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" },
5164
- "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" },
5165
- "nc -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 nc -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
5166
- "ncat -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 ncat -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
5167
- "/dev/tcp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/tcp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
5168
- "/dev/udp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/udp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
5169
- "$(base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
5170
- "`base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
5171
- "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" },
5172
- "/etc/shadow": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6\u3002" },
5173
- "id_rsa": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
5174
- "id_ed25519": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
5175
- "crontab install": { level: "high", reason: "\u68C0\u6D4B\u5230\u4FEE\u6539\u5B9A\u65F6\u4EFB\u52A1\uFF0C\u53EF\u80FD\u662F\u6301\u4E45\u5316\u540E\u95E8\u3002" },
5176
- "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" },
5177
- "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" }
5178
- };
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 ?? {};
5179
4792
  var LEVEL_ORDER = { low: 1, medium: 2, high: 3, critical: 4 };
5180
4793
  function emptyResult() {
5181
4794
  return { scriptPath: "", scriptContent: "", matchedKeywords: [], lineCount: 0, isSuspicious: false, error: "", riskLevel: "", riskReason: "" };
@@ -5202,10 +4815,10 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
5202
4815
  const hitGroup2 = rule.group2.size === 0 ? true : [...rule.group2].some((k) => keywordSet.has(k));
5203
4816
  if (hitGroup1 && hitGroup2) return { level: rule.level, reason: rule.reason };
5204
4817
  }
5205
- 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 ?? []);
5206
4819
  const hasNetworkPost = [...networkPostKeywords].some((k) => keywordSet.has(k));
5207
4820
  if (hasNetworkPost) {
5208
- const envPatterns = ["$env", "$(env)", "$USER", "$(hostname)", "$(pwd)", "$HOME"];
4821
+ const envPatterns = SCRIPT_RULES.env_exfil_patterns ?? [];
5209
4822
  const contentLower = scriptContent.toLowerCase();
5210
4823
  for (const p of envPatterns) {
5211
4824
  if (contentLower.includes(p.toLowerCase())) {
@@ -5229,7 +4842,7 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
5229
4842
  }
5230
4843
  function readScriptContent(scriptPath, maxLines = 500) {
5231
4844
  try {
5232
- 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);
5233
4846
  const raw = readFileSync8(expandedPath, "utf-8");
5234
4847
  const lines = raw.split("\n").slice(0, maxLines);
5235
4848
  return { content: lines.join("\n"), lineCount: lines.length, error: "" };
@@ -5287,7 +4900,7 @@ init_config();
5287
4900
  init_logic_rules();
5288
4901
  init_inline_analyzer();
5289
4902
  import { statSync as statSync4, existsSync as existsSync10 } from "fs";
5290
- 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";
5291
4904
  import { homedir as homedir3 } from "os";
5292
4905
 
5293
4906
  // src/engine/script-engine/tracker.ts
@@ -6798,6 +6411,7 @@ var RecursiveTracker = class {
6798
6411
  };
6799
6412
 
6800
6413
  // src/engine/script-content-analyzer.ts
6414
+ init_rules_loader();
6801
6415
  function emptyChain() {
6802
6416
  return {
6803
6417
  nodes: [],
@@ -6814,87 +6428,22 @@ function emptyChain() {
6814
6428
  error: ""
6815
6429
  };
6816
6430
  }
6817
- var SUSPICIOUS_PATTERNS2 = [
6818
- { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
6819
- { name: "curl --data", pattern: /curl\s+(.*\s)?--data/i },
6820
- { name: "curl -d", pattern: /curl\s+(.*\s)?-d\s/i },
6821
- { name: "wget --post-data", pattern: /wget\s+(.*\s)?--post-data/i },
6822
- { name: "wget --post-file", pattern: /wget\s+(.*\s)?--post-file/i },
6823
- { name: "nc -e", pattern: /\bnc\s+.*-e\b/ },
6824
- { name: "ncat -e", pattern: /\bncat\s+.*-e\b/ },
6825
- { name: "/dev/tcp", pattern: /\/dev\/tcp\// },
6826
- { name: "/dev/udp", pattern: /\/dev\/udp\// },
6827
- { name: "$(base64", pattern: /\$\(\s*base64/ },
6828
- { name: "`base64", pattern: /`\s*base64/ },
6829
- { name: "base64 decode pipe", pattern: /base64\s+(-d|--decode)\s*\|/ },
6830
- { name: "/etc/shadow", pattern: /\/etc\/shadow/ },
6831
- { name: "id_rsa", pattern: /\bid_rsa\b/ },
6832
- { name: "id_ed25519", pattern: /\bid_ed25519\b/ },
6833
- { name: "crontab install", pattern: /crontab\s+-/ },
6834
- { name: "authorized_keys", pattern: /authorized_keys/ },
6835
- { name: "ssh no host key check", pattern: /ssh\s+.*StrictHostKeyChecking\s*=\s*no/ }
6836
- ];
6837
- var SCRIPT_EXEC_PATTERNS2 = [
6838
- /(?:^|\s|&&|\|\|)\s*bash\s+(\S+)/i,
6839
- /(?:^|\s|&&|\|\|)\s*\/bin\/bash\s+(\S+)/i,
6840
- /(?:^|\s|&&|\|\|)\s*sh\s+(\S+)/i,
6841
- /(?:^|\s|&&|\|\|)\s*zsh\s+(\S+)/i,
6842
- /\.\/(\S+)/,
6843
- /(?:^|\s|&&|\|\|)\s*source\s+(\S+)/i,
6844
- /(?:^|\s|&&|\|\|)\s*\.\s+(\S+)/,
6845
- /(?:^|\s|&&|\|\|)\s*python3?\s+(\S+\.py)/i,
6846
- /(?:^|\s|&&|\|\|)\s*node\s+(\S+\.js)/i,
6847
- /(?:^|\s|&&|\|\|)\s*ruby\s+(\S+\.rb)/i,
6848
- /(?:^|\s|&&|\|\|)\s*perl\s+(\S+\.pl)/i,
6849
- /(?:^|\s|&&|\|\|)\s*php\s+(\S+\.php)/i,
6850
- /(?:^|\s|&&|\|\|)\s*lua\s+(\S+\.lua)/i
6851
- ];
6852
- var KEYWORD_COMBINATION_RISKS2 = [
6853
- {
6854
- group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
6855
- group2: /* @__PURE__ */ new Set(["$(base64", "`base64", "base64 decode pipe"]),
6856
- level: "critical",
6857
- 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"
6858
- },
6859
- {
6860
- group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
6861
- group2: /* @__PURE__ */ new Set(["/etc/shadow", "id_rsa", "id_ed25519"]),
6862
- level: "critical",
6863
- 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"
6864
- },
6865
- {
6866
- group1: /* @__PURE__ */ new Set(["nc -e", "ncat -e", "/dev/tcp", "/dev/udp"]),
6867
- group2: /* @__PURE__ */ new Set(),
6868
- level: "high",
6869
- reason: "\u68C0\u6D4B\u5230\u53CD\u5F39 Shell \u7279\u5F81\uFF0C\u653B\u51FB\u8005\u53EF\u80FD\u5EFA\u7ACB\u8FDC\u7A0B\u63A7\u5236\u901A\u9053\u3002"
6870
- },
6871
- {
6872
- group1: /* @__PURE__ */ new Set(["crontab install", "authorized_keys", "ssh no host key check"]),
6873
- group2: /* @__PURE__ */ new Set(),
6874
- level: "high",
6875
- 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"
6876
- }
6877
- ];
6878
- var SINGLE_KEYWORD_RISKS2 = {
6879
- "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" },
6880
- "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" },
6881
- "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" },
6882
- "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" },
6883
- "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" },
6884
- "nc -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 nc -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
6885
- "ncat -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 ncat -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
6886
- "/dev/tcp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/tcp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
6887
- "/dev/udp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/udp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
6888
- "$(base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
6889
- "`base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
6890
- "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" },
6891
- "/etc/shadow": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6\u3002" },
6892
- "id_rsa": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
6893
- "id_ed25519": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
6894
- "crontab install": { level: "high", reason: "\u68C0\u6D4B\u5230\u4FEE\u6539\u5B9A\u65F6\u4EFB\u52A1\uFF0C\u53EF\u80FD\u662F\u6301\u4E45\u5316\u540E\u95E8\u3002" },
6895
- "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" },
6896
- "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" }
6897
- };
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 ?? {};
6898
6447
  var LEVEL_ORDER2 = { low: 1, medium: 2, high: 3, critical: 4 };
6899
6448
  function scanScriptForKeywords2(content) {
6900
6449
  const matched = [];
@@ -6911,10 +6460,10 @@ function assessKeywordCombinationRisk2(matchedKeywords, scriptContent) {
6911
6460
  const hitGroup2 = rule.group2.size === 0 ? true : [...rule.group2].some((k) => keywordSet.has(k));
6912
6461
  if (hitGroup1 && hitGroup2) return { level: rule.level, reason: rule.reason };
6913
6462
  }
6914
- 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 ?? []);
6915
6464
  const hasNetworkPost = [...networkPostKeywords].some((k) => keywordSet.has(k));
6916
6465
  if (hasNetworkPost) {
6917
- const envPatterns = ["$env", "$(env)", "$USER", "$(hostname)", "$(pwd)", "$HOME"];
6466
+ const envPatterns = SCRIPT_RULES2.env_exfil_patterns ?? [];
6918
6467
  const contentLower = scriptContent.toLowerCase();
6919
6468
  for (const p of envPatterns) {
6920
6469
  if (contentLower.includes(p.toLowerCase())) {
@@ -7061,7 +6610,7 @@ function shlexSplit(s) {
7061
6610
  function resolvePath2(p) {
7062
6611
  let expanded = p;
7063
6612
  if (expanded.startsWith("~/") || expanded === "~") {
7064
- expanded = join6(homedir3(), expanded.slice(1));
6613
+ expanded = join9(homedir3(), expanded.slice(1));
7065
6614
  }
7066
6615
  return resolve9(expanded);
7067
6616
  }
@@ -7185,7 +6734,7 @@ function analyzeScriptContent2(input, ext) {
7185
6734
  isInline = true;
7186
6735
  }
7187
6736
  if (resolvedPath && !isAbsolute2(resolvedPath) && !resolvedPath.startsWith("~/")) {
7188
- resolvedPath = join6(input.cwd || process.cwd(), resolvedPath);
6737
+ resolvedPath = join9(input.cwd || process.cwd(), resolvedPath);
7189
6738
  }
7190
6739
  const l1Adapter = (content) => {
7191
6740
  const j = scanDangerousCommandPatterns("bash", content, input.osType);
@@ -7509,9 +7058,9 @@ init_esm_shims();
7509
7058
  // src/brain/rule-store.ts
7510
7059
  init_esm_shims();
7511
7060
  import { existsSync as existsSync12, readFileSync as readFileSync12, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
7512
- import { join as join7 } from "path";
7061
+ import { join as join10 } from "path";
7513
7062
  import { homedir as homedir4 } from "os";
7514
- var DEFAULT_RULES_PATH = join7(homedir4(), ".agentmoss", "brain", "rules.json");
7063
+ var DEFAULT_RULES_PATH = join10(homedir4(), ".agentmoss", "brain", "rules.json");
7515
7064
  var ENV_RULES_PATH = "AGENT_MOSS_BRAIN_RULES_PATH";
7516
7065
  var STATUS_DRAFT = "draft";
7517
7066
  var STATUS_APPROVED = "approved";
@@ -8126,9 +7675,9 @@ function parseReview(content) {
8126
7675
  init_esm_shims();
8127
7676
  import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync13, renameSync, writeFileSync as writeFileSync4 } from "fs";
8128
7677
  import { homedir as homedir5 } from "os";
8129
- import { join as join8 } from "path";
7678
+ import { join as join11 } from "path";
8130
7679
  import { randomUUID } from "crypto";
8131
- 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");
8132
7681
  var ENV_SKILL_META_PATH = "AGENT_MOSS_BRAIN_SKILL_META_PATH";
8133
7682
  function getMetaPath(override) {
8134
7683
  return override || process.env[ENV_SKILL_META_PATH] || DEFAULT_SKILL_META_PATH;
@@ -8138,7 +7687,7 @@ var BrainSkillManager = class {
8138
7687
  this.skillsDir = skillsDir;
8139
7688
  this.metaPath = getMetaPath(metaPath);
8140
7689
  try {
8141
- mkdirSync4(join8(this.metaPath, ".."), { recursive: true });
7690
+ mkdirSync4(join11(this.metaPath, ".."), { recursive: true });
8142
7691
  } catch {
8143
7692
  }
8144
7693
  this.load();
@@ -8173,7 +7722,7 @@ var BrainSkillManager = class {
8173
7722
  let changed = false;
8174
7723
  for (const sk of this.skills) {
8175
7724
  if (sk.status !== "active") continue;
8176
- if (!existsSync13(join8(this.skillsDir, `${sk.name}.md`))) {
7725
+ if (!existsSync13(join11(this.skillsDir, `${sk.name}.md`))) {
8177
7726
  sk.status = "stale";
8178
7727
  changed = true;
8179
7728
  }
@@ -8185,7 +7734,7 @@ var BrainSkillManager = class {
8185
7734
  const id = skill.id || randomUUID();
8186
7735
  const name = skill.name || `brain_${id.slice(0, 8)}`;
8187
7736
  if (!existsSync13(this.skillsDir)) return "";
8188
- const mdPath = join8(this.skillsDir, `${name}.md`);
7737
+ const mdPath = join11(this.skillsDir, `${name}.md`);
8189
7738
  if (existsSync13(mdPath)) return "";
8190
7739
  try {
8191
7740
  writeFileSync4(mdPath, skill.content, "utf-8");
@@ -8235,7 +7784,7 @@ var BrainSkillManager = class {
8235
7784
  const out = [];
8236
7785
  for (const sk of this.skills) {
8237
7786
  if (sk.origin_node !== "local" || sk.status !== "active") continue;
8238
- const mdPath = join8(this.skillsDir, `${sk.name}.md`);
7787
+ const mdPath = join11(this.skillsDir, `${sk.name}.md`);
8239
7788
  if (!existsSync13(mdPath)) continue;
8240
7789
  out.push({
8241
7790
  name: sk.name,
@@ -8387,6 +7936,7 @@ init_rules_loader();
8387
7936
 
8388
7937
  // src/engine/formal-engine/decode.ts
8389
7938
  init_esm_shims();
7939
+ init_rules_loader();
8390
7940
  var B64_TOKEN_RE = /[A-Za-z0-9+/]+={0,2}/g;
8391
7941
  function isValidBase64Token(tok) {
8392
7942
  if (tok.length < 8) return false;
@@ -8421,7 +7971,7 @@ function decodeBase64Literals(text) {
8421
7971
  return out;
8422
7972
  }
8423
7973
  var QUOTED_STR_RE = /('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`)/g;
8424
- var OUTPUT_CMDS = /* @__PURE__ */ new Set(["echo", "printf", "print"]);
7974
+ var OUTPUT_CMDS = new Set(loadFormalRules().output_commands ?? []);
8425
7975
  function stripStringLiterals(text) {
8426
7976
  const m = text.match(/\s*([A-Za-z_][\w-]*|\/?[\w./-]+)\b/);
8427
7977
  if (!m) return text;
@@ -8906,8 +8456,9 @@ function extractInlineShell(s) {
8906
8456
  }
8907
8457
  return out;
8908
8458
  }
8909
- var PKG_MGR = /* @__PURE__ */ new Set(["apt", "apt-get", "aptitude", "yum", "dnf", "pacman", "zypper", "apk", "brew"]);
8910
- 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 ?? []);
8911
8462
  function containsPrivesc(shell) {
8912
8463
  if (!shell) return false;
8913
8464
  for (const seg of shell.split(/\&\&|\|\||;|\n/)) {
@@ -9080,7 +8631,7 @@ function lookupToolClass(actionType) {
9080
8631
  return entry.actionClass;
9081
8632
  }
9082
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;
9083
- var STRONG_COMPUTE_TYPES = /* @__PURE__ */ new Set(["python", "node", "exec", "terminal", "cmd"]);
8634
+ var STRONG_COMPUTE_TYPES = new Set(FORMAL_RULES.compute_action_types ?? []);
9084
8635
  function isComputeNode(n) {
9085
8636
  if (STRONG_COMPUTE_TYPES.has(n.actionType.toLowerCase())) return true;
9086
8637
  const cls = lookupToolClass(n.actionType);
@@ -9306,25 +8857,7 @@ import * as path4 from "path";
9306
8857
  import { createHash } from "crypto";
9307
8858
 
9308
8859
  // src/engine/coordinator.ts
9309
- var FULLY_SAFE_TOOLS = /* @__PURE__ */ new Set([
9310
- "ask_user_question",
9311
- "glob",
9312
- "list_dir",
9313
- "ls",
9314
- "count_text_length",
9315
- "filemgr-globfiles"
9316
- ]);
9317
8860
  var TRUSTED_SKILLS_BASE = /* @__PURE__ */ new Set([]);
9318
- var READONLY_SENSITIVE_TOOLS = /* @__PURE__ */ new Set([
9319
- "read",
9320
- "file_read",
9321
- "read_file",
9322
- "head",
9323
- "tail",
9324
- "grep",
9325
- "filemgr-readfile",
9326
- "filemgr-grepfiles"
9327
- ]);
9328
8861
  function whitelistTier(command, fullySafe, readonlySensitive) {
9329
8862
  const segments = splitCommandSegments(command);
9330
8863
  if (segments.length === 0) return "";
@@ -9481,7 +9014,7 @@ async function runAnalysis(input) {
9481
9014
  const actionType = input.aNext.actionType.toLowerCase();
9482
9015
  const rawDetail = extractRawCommand(input.aNext.actionDetail);
9483
9016
  const lowerDetail = rawDetail.toLowerCase();
9484
- if (FULLY_SAFE_TOOLS.has(actionType)) {
9017
+ if (getPlatformTraits(input.osType).safeActionTypes.has(actionType)) {
9485
9018
  return {
9486
9019
  allowed: true,
9487
9020
  reason: `\u5B8C\u5168\u5B89\u5168\u5DE5\u5177\uFF0C\u8DF3\u8FC7\u6DF1\u5EA6\u5206\u6790: ${actionType}`,
@@ -9524,7 +9057,7 @@ async function runAnalysis(input) {
9524
9057
  violatedLayers: []
9525
9058
  };
9526
9059
  }
9527
- let skipLLM = READONLY_SENSITIVE_TOOLS.has(actionType);
9060
+ let skipLLM = getPlatformTraits(input.osType).readonlySensitiveActionTypes.has(actionType);
9528
9061
  if (actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash") {
9529
9062
  const firstCmd = extractFirstCommand(lowerDetail);
9530
9063
  const { fullySafeCommands, readonlySensitiveCommands } = getPlatformTraits(input.osType);