@kenhkl/agent-moss 0.16.13 → 0.17.0

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,14 @@ 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 isSafeId(value) {
29
+ if (!value || value.length > ID_MAX_LEN) return false;
30
+ return ID_PATTERN.test(value);
31
+ }
32
+ function getRulesRoot() {
33
+ const raw = (process.env[ENV_RULES_ROOT] ?? "").trim();
34
+ return raw ? raw : null;
35
+ }
28
36
  function isRoot() {
29
37
  const getuid = process.getuid;
30
38
  return typeof getuid === "function" && getuid() === 0;
@@ -94,7 +102,7 @@ function getTokenStatsPath() {
94
102
  }
95
103
  return join(getDataRoot(), "agent_moss_token_stats.json");
96
104
  }
97
- var ENV_DATA_ROOT, ENV_INSTANCE, DEFAULT_DATA_ROOT;
105
+ var ENV_DATA_ROOT, ENV_INSTANCE, DEFAULT_DATA_ROOT, ENV_RULES_ROOT, ID_MAX_LEN, ID_PATTERN;
98
106
  var init_paths = __esm({
99
107
  "src/infra/paths.ts"() {
100
108
  "use strict";
@@ -102,6 +110,9 @@ var init_paths = __esm({
102
110
  ENV_DATA_ROOT = "AGENT_MOSS_DATA_DIR";
103
111
  ENV_INSTANCE = "AGENT_MOSS_INSTANCE";
104
112
  DEFAULT_DATA_ROOT = join(homedir(), ".config", "agentmoss");
113
+ ENV_RULES_ROOT = "AGENT_MOSS_RULES_ROOT";
114
+ ID_MAX_LEN = 64;
115
+ ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
105
116
  }
106
117
  });
107
118
 
@@ -149,251 +160,6 @@ var init_shell_split = __esm({
149
160
  }
150
161
  });
151
162
 
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
163
  // src/infra/rules-mode.ts
398
164
  function truthy(name) {
399
165
  return TRUTHY.has((process.env[name] || "").trim().toLowerCase());
@@ -440,6 +206,19 @@ var init_rules_mode = __esm({
440
206
  }
441
207
  });
442
208
 
209
+ // src/infra/runtime-config-bridge.ts
210
+ function registerRuntimeConfigBridge(bridge) {
211
+ runtimeConfigBridge.current = bridge;
212
+ }
213
+ var runtimeConfigBridge;
214
+ var init_runtime_config_bridge = __esm({
215
+ "src/infra/runtime-config-bridge.ts"() {
216
+ "use strict";
217
+ init_esm_shims();
218
+ runtimeConfigBridge = { current: null };
219
+ }
220
+ });
221
+
443
222
  // src/infra/rules-loader.ts
444
223
  import { readFileSync as readFileSync2, existsSync, readdirSync, statSync } from "fs";
445
224
  import { resolve, dirname, join as join2 } from "path";
@@ -455,12 +234,30 @@ function parseDirEnv(envName) {
455
234
  function rulesDirs() {
456
235
  if (isBuiltinOnly()) return [];
457
236
  const dirs = [];
458
- for (const env of RULES_DIR_ENVS) dirs.push(...parseDirEnv(env));
237
+ for (const env2 of RULES_DIR_ENVS) dirs.push(...parseDirEnv(env2));
459
238
  dirs.push(resolve(dataRootDir(), "rules"));
460
239
  return [...new Set(dirs)];
461
240
  }
241
+ function rulesDirCands() {
242
+ const root = getRulesRoot();
243
+ if (root) return [join2(root, "rules")];
244
+ const cands = [
245
+ // 注:`<data_root>/rules/` 不再是"按内置文件名覆盖"的查找位 —— 2026-10 起
246
+ // 规则目录只认 override.json / extra.json(见 rulesDirs())。
247
+ // dist: __dirname=dist/ → dist/rules;__dirname=dist/engine/ → dist/engine/../rules=dist/rules
248
+ resolve(__dirname2, "../rules"),
249
+ resolve(__dirname2, "rules"),
250
+ // dev src: __dirname=ts/src/infra/ → ts/src/infra/../../../agent_moss/rules = 仓库根 agent_moss/rules
251
+ resolve(__dirname2, "../../../agent_moss/rules"),
252
+ // dist 但 __dirname 指向 dist/engine 再往上两级的仓库根 agent_moss/rules
253
+ resolve(__dirname2, "../../../../agent_moss/rules"),
254
+ // 相对当前工作目录的仓库根(兜底,兼容以仓库根为 cwd 运行)
255
+ resolve(process.cwd(), "agent_moss/rules")
256
+ ];
257
+ return cands.filter((c) => existsSync(c));
258
+ }
462
259
  function findRulesFile(name) {
463
- for (const dir of RULES_DIR_CANDS) {
260
+ for (const dir of rulesDirCands()) {
464
261
  const p = resolve(dir, name);
465
262
  if (existsSync(p)) return p;
466
263
  }
@@ -482,6 +279,7 @@ function loadJson(name) {
482
279
  }
483
280
  }
484
281
  function resolveScenarioRulesFile(scenarioId) {
282
+ if (!isSafeId(scenarioId)) return null;
485
283
  const rel = join2("scenarios", scenarioId, "rules.json");
486
284
  for (const dir of rulesDirs()) {
487
285
  const p = resolve(dir, rel);
@@ -516,6 +314,74 @@ function loadL1() {
516
314
  function loadL2() {
517
315
  return loadJson("l2_rules.json");
518
316
  }
317
+ function regexFlags(flags) {
318
+ let out = "";
319
+ for (const ch of String(flags ?? "")) if (RE_FLAG_MAP[ch]) out += RE_FLAG_MAP[ch];
320
+ return out;
321
+ }
322
+ function compileRuleRegex(rule, extraFlags = "") {
323
+ return new RegExp(String(rule.pattern), regexFlags(rule.flags) + extraFlags);
324
+ }
325
+ function resolveKeywordSet(group, keywordSets) {
326
+ if (typeof group === "string") return new Set(keywordSets[group.slice(1)] ?? []);
327
+ return new Set(Array.isArray(group) ? group : []);
328
+ }
329
+ function loadFormalRules() {
330
+ return loadJson("formal_rules.json");
331
+ }
332
+ function loadLogicRules() {
333
+ return loadJson("logic_rules.json");
334
+ }
335
+ function loadHeuristicRules() {
336
+ return loadJson("heuristic_rules.json");
337
+ }
338
+ function loadScriptRules() {
339
+ return loadJson("script_rules.json");
340
+ }
341
+ function loadInline() {
342
+ return loadJson("inline_rules.json");
343
+ }
344
+ function loadPlatformTraits(osName) {
345
+ const data = loadJson("platform_traits.json");
346
+ if (!data || Object.keys(data).length === 0) {
347
+ console.error(
348
+ "[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"
349
+ );
350
+ return {};
351
+ }
352
+ const name = resolveTraitsOs(osName);
353
+ const entry = data[name];
354
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
355
+ console.error(`[AgentMoss] platform_traits.json \u91CC\u6CA1\u6709\u53EF\u7528\u7684 ${name} \u6761\u76EE \u2014\u2014 \u5E73\u53F0\u7279\u5F81\u4E3A\u7A7A\u3002`);
356
+ return {};
357
+ }
358
+ const out = {};
359
+ for (const f of TRAITS_FIELDS) if (f in entry) out[f] = entry[f];
360
+ return out;
361
+ }
362
+ function resolveTraitsOs(osName) {
363
+ const data = loadJson("platform_traits.json");
364
+ let name = String(osName || "").trim().toLowerCase() || TRAITS_OS_FALLBACK;
365
+ const seen = /* @__PURE__ */ new Set();
366
+ for (; ; ) {
367
+ if (seen.has(name)) {
368
+ console.error(`[AgentMoss] platform_traits.json \u7684 inherits \u6210\u73AF\uFF08${name}\uFF09\u2014\u2014 \u56DE\u843D linux\u3002`);
369
+ return TRAITS_OS_FALLBACK;
370
+ }
371
+ seen.add(name);
372
+ const entry = data[name];
373
+ if (entry && typeof entry === "object" && !Array.isArray(entry)) {
374
+ const nxt = entry.inherits;
375
+ if (typeof nxt === "string" && nxt.trim().toLowerCase() !== name) {
376
+ name = nxt.trim().toLowerCase();
377
+ continue;
378
+ }
379
+ return name;
380
+ }
381
+ if (name === TRAITS_OS_FALLBACK) return name;
382
+ name = TRAITS_OS_FALLBACK;
383
+ }
384
+ }
519
385
  function loadL3() {
520
386
  const data = { ...loadJson("l3_rules.json") };
521
387
  try {
@@ -844,7 +710,7 @@ function loadExtraRules() {
844
710
  if (isBuiltinOnly()) return {};
845
711
  return rulesCached().extra;
846
712
  }
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;
713
+ 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
714
  var init_rules_loader = __esm({
849
715
  "src/infra/rules-loader.ts"() {
850
716
  "use strict";
@@ -857,24 +723,20 @@ var init_rules_loader = __esm({
857
723
  OVERRIDE_FILENAME = "override.json";
858
724
  EXTRA_FILENAME = "extra.json";
859
725
  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
726
  frozenJsonCache = /* @__PURE__ */ new Map();
877
727
  SCENARIO_LIST_KEYS = ["extra_command_patterns", "extra_injection_keywords", "extra_sensitive_paths"];
728
+ RE_FLAG_MAP = { i: "i", s: "s", m: "m" };
729
+ TRAITS_FIELDS = [
730
+ "write_keywords",
731
+ "read_keywords",
732
+ "write_command_res",
733
+ "read_only_commands",
734
+ "fully_safe_commands",
735
+ "readonly_sensitive_commands",
736
+ "safe_action_types",
737
+ "readonly_sensitive_action_types"
738
+ ];
739
+ TRAITS_OS_FALLBACK = "linux";
878
740
  EXTRA_CATEGORIES = {
879
741
  dangerous_commands: ["pattern"],
880
742
  injection_detection: ["keyword", "lang"],
@@ -896,7 +758,155 @@ var init_rules_loader = __esm({
896
758
  }
897
759
  });
898
760
 
761
+ // src/engine/inline-analyzer.ts
762
+ function buildChainRegex(pattern, flags) {
763
+ return new RegExp(pattern, (flags || "") + "g");
764
+ }
765
+ function isLiteralTextCommand(command) {
766
+ const segments = splitCommandSegments(command);
767
+ if (segments.length === 0 || segments.some((s) => !ECHO_SEG_RE.test(s))) return false;
768
+ if (command.includes(">")) {
769
+ let d = command.replace(/<=>|>=|<=|<>|=>/g, " ");
770
+ d = d.replace(/(?:2|&|1)?\s*>\s*\/dev\/null\b/g, "");
771
+ d = d.replace(new RegExp(`>{1,2}\\s*(['"])([~./][^'"]+)\\1`, "g"), (_m, _q, p) => ">" + p);
772
+ d = d.replace(/'[^']*'|"[^"]*"/g, "");
773
+ if (/>{1,2}\s*(?!&)\S/.test(d)) {
774
+ return false;
775
+ }
776
+ }
777
+ if (/\|\s*\b(?:tee|tac|install)\b/i.test(command)) {
778
+ return false;
779
+ }
780
+ return true;
781
+ }
782
+ function extractInlineCode(command) {
783
+ const parts = [];
784
+ let m;
785
+ INLINE_CODE_RE.lastIndex = 0;
786
+ while ((m = INLINE_CODE_RE.exec(command)) !== null) {
787
+ const code = m[1] ?? m[2];
788
+ if (code) parts.push(code);
789
+ }
790
+ ECHO_STRING_RE.lastIndex = 0;
791
+ while ((m = ECHO_STRING_RE.exec(command)) !== null) {
792
+ const code = m[1] ?? m[2];
793
+ if (code) parts.push(code);
794
+ }
795
+ return parts.join("\n");
796
+ }
797
+ function hasWriteExecChain(command) {
798
+ return WRITE_EXEC_CHAIN_PATTERNS.some((p) => {
799
+ p.lastIndex = 0;
800
+ return p.test(command);
801
+ });
802
+ }
803
+ function hasDirTraversal(code) {
804
+ const lower = code.toLowerCase();
805
+ return DIR_TRAVERSAL_KEYWORDS.some((kw) => lower.includes(kw));
806
+ }
807
+ function hasReadWriteKeyword(code) {
808
+ const lower = code.toLowerCase();
809
+ return READ_WRITE_KEYWORDS.some((kw) => lower.includes(kw));
810
+ }
811
+ function analyzeInlineScript(command) {
812
+ const code = extractInlineCode(command);
813
+ if (!code) {
814
+ return { actionable: false, allowed: true, reason: "", riskLevel: "low", riskType: "" };
815
+ }
816
+ if (hasWriteExecChain(command)) {
817
+ return {
818
+ actionable: true,
819
+ allowed: false,
820
+ 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",
821
+ riskLevel: "critical",
822
+ riskType: "script_execution"
823
+ };
824
+ }
825
+ const redirMatch = command.match(REDIRECT_TO_SENSITIVE_RE);
826
+ if (redirMatch) {
827
+ return {
828
+ actionable: true,
829
+ allowed: false,
830
+ reason: `\u91CD\u5B9A\u5411\u5199\u5165\u654F\u611F\u8DEF\u5F84: ${redirMatch[1]}`,
831
+ riskLevel: "critical",
832
+ riskType: "file_access"
833
+ };
834
+ }
835
+ const isEchoOrPrintf1 = isLiteralTextCommand(command);
836
+ if (!isEchoOrPrintf1 && hasDirTraversal(code) && hasReadWriteKeyword(code)) {
837
+ return { actionable: false, allowed: true, reason: "", riskLevel: "low", riskType: "" };
838
+ }
839
+ const isEchoOrPrintf = isLiteralTextCommand(command);
840
+ if (!isEchoOrPrintf && hasReadWriteKeyword(code)) {
841
+ return {
842
+ actionable: true,
843
+ allowed: false,
844
+ 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",
845
+ riskLevel: "critical",
846
+ riskType: "file_access"
847
+ };
848
+ }
849
+ return {
850
+ actionable: true,
851
+ allowed: true,
852
+ reason: "\u654F\u611F\u8DEF\u5F84\u4EC5\u4F5C\u4E3A\u6587\u672C\u5B57\u9762\u91CF\u51FA\u73B0\uFF0C\u672A\u4F34\u968F\u8BFB\u5199\u64CD\u4F5C",
853
+ riskLevel: "low",
854
+ riskType: "file_access"
855
+ };
856
+ }
857
+ 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;
858
+ var init_inline_analyzer = __esm({
859
+ "src/engine/inline-analyzer.ts"() {
860
+ "use strict";
861
+ init_esm_shims();
862
+ init_shell_split();
863
+ init_rules_loader();
864
+ INLINE_RULES = loadInline();
865
+ if (!INLINE_RULES || Object.keys(INLINE_RULES).length === 0) {
866
+ console.error(
867
+ "[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"
868
+ );
869
+ }
870
+ INLINE_CODE_RE = /(?:python|python3|perl|perl5|ruby|jruby|mruby|node|php|lua)\s+(?:-[a-zA-Z]*[cera][a-zA-Z]*\s+)"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
871
+ ECHO_STRING_RE = /\b(?:echo|printf)\s+"([^"\\]*(?:\\.[^"\\]*)*)"|\b(?:echo|printf)\s+'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
872
+ ECHO_SEG_RE = /^\s*(?:echo|printf)(?:\s|$)/i;
873
+ READ_WRITE_KEYWORDS = INLINE_RULES.read_write_keywords ?? [];
874
+ WRITE_EXEC_CHAIN_PATTERNS = (INLINE_RULES.write_exec_chain_patterns ?? []).filter((p) => p && typeof p.pattern === "string").map((p) => buildChainRegex(p.pattern, p.flags ?? ""));
875
+ _REDIRECT_PATHS = INLINE_RULES.redirect_sensitive_paths ?? {};
876
+ SHELL_RC_FRAGMENTS = _REDIRECT_PATHS.shell_rc_fragments ?? [];
877
+ SENSITIVE_PATH_FRAGMENTS = [
878
+ ..._REDIRECT_PATHS.ts_static ?? [],
879
+ ...SHELL_RC_FRAGMENTS
880
+ ];
881
+ REDIRECT_TO_SENSITIVE_RE = new RegExp(`>{1,2}\\s*(?:\\S*/)?(${SENSITIVE_PATH_FRAGMENTS.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})(?:\\s|$)`, "i");
882
+ DIR_TRAVERSAL_KEYWORDS = INLINE_RULES.dir_traversal_keywords ?? [];
883
+ }
884
+ });
885
+
899
886
  // src/engine/platform-traits.ts
887
+ function buildTraits(osName) {
888
+ const t = loadPlatformTraits(osName);
889
+ const strs = (k) => Array.isArray(t[k]) ? t[k] : [];
890
+ return {
891
+ writeKeywords: strs("write_keywords"),
892
+ readKeywords: strs("read_keywords"),
893
+ writeCommandRes: strs("write_command_res").map((p) => new RegExp(p)),
894
+ readOnlyCommands: new Set(strs("read_only_commands")),
895
+ fullySafeCommands: strs("fully_safe_commands").map((p) => new RegExp(p, "i")),
896
+ readonlySensitiveCommands: strs("readonly_sensitive_commands").map((p) => new RegExp(p, "i")),
897
+ safeActionTypes: new Set(strs("safe_action_types")),
898
+ readonlySensitiveActionTypes: new Set(strs("readonly_sensitive_action_types"))
899
+ };
900
+ }
901
+ function traitsFor(key) {
902
+ const resolved = resolveTraitsOs(key);
903
+ let t = traitsCache.get(resolved);
904
+ if (!t) {
905
+ t = buildTraits(resolved);
906
+ traitsCache.set(resolved, t);
907
+ }
908
+ return t;
909
+ }
900
910
  function getPlatformTraits(osType) {
901
911
  const accepted = acceptedOsValues(osType);
902
912
  if (accepted.includes("windows")) return WINDOWS_TRAITS;
@@ -904,368 +914,18 @@ function getPlatformTraits(osType) {
904
914
  if (accepted.includes("harmony")) return HARMONY_TRAITS;
905
915
  return LINUX_TRAITS;
906
916
  }
907
- var LINUX_TRAITS, WINDOWS_TRAITS, MACOS_TRAITS, HARMONY_TRAITS;
917
+ var traitsCache, LINUX_TRAITS, WINDOWS_TRAITS, MACOS_TRAITS, HARMONY_TRAITS;
908
918
  var init_platform_traits = __esm({
909
919
  "src/engine/platform-traits.ts"() {
910
920
  "use strict";
911
921
  init_esm_shims();
912
922
  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;
923
+ init_rules_loader();
924
+ traitsCache = /* @__PURE__ */ new Map();
925
+ LINUX_TRAITS = traitsFor("linux");
926
+ WINDOWS_TRAITS = traitsFor("windows");
927
+ MACOS_TRAITS = traitsFor("macos");
928
+ HARMONY_TRAITS = traitsFor("harmony");
1269
929
  }
1270
930
  });
1271
931
 
@@ -1424,7 +1084,6 @@ function checkIntentConsistency(input) {
1424
1084
  const { aNext, promptSession, promptHistory } = input;
1425
1085
  const actionDetail = aNext.actionDetail.toLowerCase();
1426
1086
  const actionType = aNext.actionType.toLowerCase();
1427
- const EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
1428
1087
  if (EXCLUDED_PREFIXES.some((p) => actionType.startsWith(p))) return null;
1429
1088
  if (INLINE_SCRIPT_RE.test(actionDetail)) return null;
1430
1089
  const intentTexts = collectIntentTexts(promptSession, promptHistory);
@@ -1452,7 +1111,6 @@ function runLogicCheck(input, opts) {
1452
1111
  const actionDetail = aNext.actionDetail.toLowerCase();
1453
1112
  const actionType = aNext.actionType.toLowerCase();
1454
1113
  const resolvedCwd = cwd || "/";
1455
- const EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
1456
1114
  const isExcludedType = EXCLUDED_PREFIXES.some((p) => actionType.startsWith(p));
1457
1115
  if (!opts?.skipReadBeforeWrite) {
1458
1116
  const rbwResult = checkReadBeforeWrite(actionType, actionDetail, actionHistory, input.reason, resolvedCwd, input.osType);
@@ -1564,7 +1222,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
1564
1222
  const isBashLike = actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash";
1565
1223
  const isWrite = isBashLike ? isWriteOperation(actionType, actionDetail, void 0, osType) : getPlatformTraits(osType).writeKeywords.some((kw) => actionType.includes(kw));
1566
1224
  if (!isWrite) return null;
1567
- const WRITE_CMDS = /* @__PURE__ */ new Set(["cp", "mv", "install", "tee", "dd", "truncate", "mkfs", "fdisk", "parted"]);
1225
+ const WRITE_CMDS = new Set(_LOGIC.write_commands ?? []);
1568
1226
  if (isBashLike) {
1569
1227
  const detailNoCompare = actionDetail.replace(/<=>|>=|<=|<>|=>/g, " ");
1570
1228
  const detailNoDevnull = detailNoCompare.replace(DEVNULL_REDIRECT_RE, "");
@@ -1596,7 +1254,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
1596
1254
  }
1597
1255
  if (!hasWriteSeg) return null;
1598
1256
  }
1599
- const SOURCE_ARG_CMDS = /* @__PURE__ */ new Set(["cp", "mv", "install", "rsync"]);
1257
+ const SOURCE_ARG_CMDS = new Set(_LOGIC.source_arg_commands ?? []);
1600
1258
  const REDIRECT_TARGET_RE = new RegExp(`>{1,2}\\s*(?!&)(?:'(/[^']+)'|"(/[^\\"]+)"|(/[^\\s;|&><'\\"]+))`);
1601
1259
  const targetPaths = [];
1602
1260
  for (const seg of splitCommandSegments(actionDetail)) {
@@ -1631,7 +1289,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
1631
1289
  targetPaths.push(...segPathsFiltered);
1632
1290
  }
1633
1291
  }
1634
- const uniqueTargetPaths = [...new Set(targetPaths)];
1292
+ const uniqueTargetPaths = [...new Set(targetPaths)].filter((p) => !PSEUDO_FS_PREFIXES.some((pfx) => p.startsWith(pfx)));
1635
1293
  if (uniqueTargetPaths.length === 0) return null;
1636
1294
  const historyReadPaths = /* @__PURE__ */ new Set();
1637
1295
  for (const action of actionHistory) {
@@ -1644,7 +1302,6 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
1644
1302
  }
1645
1303
  }
1646
1304
  }
1647
- const TEMP_DIRS = ["/tmp/", "/var/tmp/", "/dev/shm/", "/run/"];
1648
1305
  let filteredPaths = uniqueTargetPaths;
1649
1306
  if (/\bdd\b/i.test(actionDetail) && /\bif=\/dev\/zero\b/i.test(actionDetail)) {
1650
1307
  filteredPaths = uniqueTargetPaths.filter(
@@ -1745,10 +1402,10 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd, osType) {
1745
1402
  if (!hasTraversal) {
1746
1403
  return null;
1747
1404
  }
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("];
1405
+ const READ_KEYWORDS = IFA_READ_KEYWORDS;
1406
+ const WRITE_KEYWORDS2 = IFA_WRITE_KEYWORDS;
1407
+ const EXEC_KEYWORDS = IFA_EXEC_KEYWORDS;
1408
+ const OPEN_KEYWORDS = IFA_OPEN_KEYWORDS;
1752
1409
  const code = extractInlineCode(actionDetail);
1753
1410
  if (!code) return null;
1754
1411
  const codeLower = code.toLowerCase();
@@ -1811,7 +1468,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd, osType) {
1811
1468
  violatedLayers: ["L2.1"]
1812
1469
  };
1813
1470
  }
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;
1471
+ 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, PSEUDO_FS_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
1472
  var init_logic_rules = __esm({
1816
1473
  "src/engine/logic-rules.ts"() {
1817
1474
  "use strict";
@@ -1821,6 +1478,10 @@ var init_logic_rules = __esm({
1821
1478
  init_rules_loader();
1822
1479
  init_platform_traits();
1823
1480
  init_shell_split();
1481
+ _LOGIC = loadLogicRules();
1482
+ _HEURISTIC = loadHeuristicRules();
1483
+ EXCLUDED_PREFIXES = _HEURISTIC.sensitive_excluded_prefixes ?? [];
1484
+ TEMP_DIRS = (_HEURISTIC.temp_dirs ?? []).map((d) => d + "/");
1824
1485
  ROOT_SENSITIVE_PATHS = _loadSensitivePathScope("root");
1825
1486
  HOME_SENSITIVE_PATHS = _loadSensitivePathScope("home");
1826
1487
  CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path5, riskLevel, desc, dirDelOnly }) => ({ path: path5, riskLevel, desc, credential: true, dirDelOnly }));
@@ -1841,7 +1502,7 @@ var init_logic_rules = __esm({
1841
1502
  "list_dir",
1842
1503
  "count_text_length",
1843
1504
  "search",
1844
- // filemgr 只读三件套(globfiles 在 coordinator FULLY_SAFE_TOOLS 提前放行,此处兜底防御非 coordinator 入口)
1505
+ // filemgr 只读三件套(globfiles 在 platform-traits 的 safeActionTypes 提前放行,此处兜底防御非 coordinator 入口)
1845
1506
  "filemgr-globfiles",
1846
1507
  "filemgr-readfile",
1847
1508
  "filemgr-grepfiles",
@@ -1853,7 +1514,8 @@ var init_logic_rules = __esm({
1853
1514
  "tail",
1854
1515
  "cat"
1855
1516
  ]);
1856
- NON_WRITE_TOOL_PREFIXES = ["memory-", "skill-manager-", "mailbox-", "app-"];
1517
+ NON_WRITE_TOOL_PREFIXES = _LOGIC.non_write_tool_prefixes ?? [];
1518
+ PSEUDO_FS_PREFIXES = ["/sys/", "/proc/", "/dev/", "/run/"];
1857
1519
  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
1520
  SED_INPLACE_RE = /\bsed\s+(?:-\S+\s+)*(?:-[a-zA-Z]*i[a-zA-Z]*|--in-place)\b/i;
1859
1521
  DEVNULL_REDIRECT_RE = /(?:2|&|1)?\s*>\s*\/dev\/null\b/g;
@@ -1894,25 +1556,24 @@ var init_logic_rules = __esm({
1894
1556
  }));
1895
1557
  })();
1896
1558
  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
- ];
1559
+ DIR_TRAVERSAL_KW = loadInline().dir_traversal_keywords ?? [];
1560
+ _IFA = loadLogicRules().indirect_file_access ?? {};
1561
+ IFA_READ_KEYWORDS = _IFA.read_keywords ?? [];
1562
+ IFA_WRITE_KEYWORDS = _IFA.write_keywords ?? [];
1563
+ IFA_EXEC_KEYWORDS = _IFA.exec_keywords ?? [];
1564
+ IFA_OPEN_KEYWORDS = _IFA.open_keywords ?? [];
1909
1565
  }
1910
1566
  });
1911
1567
 
1912
1568
  // src/engine/asset-dir.ts
1913
1569
  import { existsSync as existsSync3 } from "fs";
1914
- import { resolve as resolve2 } from "path";
1570
+ import { join as join3, resolve as resolve2 } from "path";
1915
1571
  function assetDirCandidates(moduleDir, name) {
1572
+ const root = getRulesRoot();
1573
+ if (root) {
1574
+ const p = join3(root, name);
1575
+ return [p, p];
1576
+ }
1916
1577
  return [
1917
1578
  resolve2(moduleDir, name),
1918
1579
  resolve2(moduleDir, "../../..", "agent_moss", name)
@@ -1928,6 +1589,7 @@ var init_asset_dir = __esm({
1928
1589
  "src/engine/asset-dir.ts"() {
1929
1590
  "use strict";
1930
1591
  init_esm_shims();
1592
+ init_paths();
1931
1593
  }
1932
1594
  });
1933
1595
 
@@ -2128,7 +1790,7 @@ __export(runtime_config_exports, {
2128
1790
  updateSkillEnabled: () => updateSkillEnabled
2129
1791
  });
2130
1792
  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";
1793
+ import { join as join4 } from "path";
2132
1794
  import { fileURLToPath as fileURLToPath4 } from "url";
2133
1795
  import { dirname as dirname3 } from "path";
2134
1796
  function runtimeDir() {
@@ -2263,7 +1925,10 @@ function filterEnabledRules(rules, categoryEnabled = true) {
2263
1925
  }
2264
1926
  function _filterRulesByOs(rules, osType) {
2265
1927
  const accepted = acceptedOsValues(osType);
2266
- return rules.filter((r) => accepted.includes(String(r.os || "linux").toLowerCase()));
1928
+ return rules.filter((r) => {
1929
+ const os3 = String(r.os || "linux").toLowerCase();
1930
+ return os3 === "all" || accepted.includes(os3);
1931
+ });
2267
1932
  }
2268
1933
  function _userCustomRules(runtime, layers, requiredKey) {
2269
1934
  const list = Array.isArray(layers) ? layers : [layers];
@@ -2450,6 +2115,9 @@ function _l1InjectionKeywordsToRules() {
2450
2115
  keyword: kw.keyword,
2451
2116
  risk_level: kw.riskLevel,
2452
2117
  lang: kw.lang || "en",
2118
+ // 带上条目自己的 os(`all` = 全平台通用),别丢 —— 丢了 Windows 会拿到
2119
+ // 本不该给它的条目。与 Python `_l1_injection_keywords_to_rules` 对齐。
2120
+ os: kw.os,
2453
2121
  enabled: true,
2454
2122
  builtin: true
2455
2123
  }));
@@ -2462,7 +2130,7 @@ function _l2SensitivePathsToRules() {
2462
2130
  const all = [...root, ...home, ...credential];
2463
2131
  for (const sp of all) {
2464
2132
  const os3 = String(sp.os || "linux").toLowerCase();
2465
- const denyMode = _deriveDenyMode({
2133
+ const denyMode = sp.denyMode || _deriveDenyMode({
2466
2134
  deny_mode: sp.deny_mode,
2467
2135
  credential: sp.credential,
2468
2136
  read_only: sp.read_only
@@ -2567,7 +2235,8 @@ function _l3SkillsToRules() {
2567
2235
  }
2568
2236
  function _scenarioRulesToDefaults() {
2569
2237
  const out = {};
2570
- const scenDir = existsSync5(_SCENARIOS_DIR_PROD) ? _SCENARIOS_DIR_PROD : existsSync5(_SCENARIOS_DIR_DEV) ? _SCENARIOS_DIR_DEV : _SCENARIOS_DIR_PROD;
2238
+ const _root = getRulesRoot();
2239
+ const scenDir = _root ? join4(_root, "scenarios") : existsSync5(_SCENARIOS_DIR_PROD) ? _SCENARIOS_DIR_PROD : existsSync5(_SCENARIOS_DIR_DEV) ? _SCENARIOS_DIR_DEV : _SCENARIOS_DIR_PROD;
2571
2240
  if (!existsSync5(scenDir)) return out;
2572
2241
  let entries;
2573
2242
  try {
@@ -2576,7 +2245,7 @@ function _scenarioRulesToDefaults() {
2576
2245
  return out;
2577
2246
  }
2578
2247
  for (const scenarioId of entries) {
2579
- const rulesFile = resolveScenarioRulesFile(scenarioId) ?? join3(scenDir, scenarioId, "rules.json");
2248
+ const rulesFile = resolveScenarioRulesFile(scenarioId) ?? join4(scenDir, scenarioId, "rules.json");
2580
2249
  if (!existsSync5(rulesFile)) continue;
2581
2250
  let data;
2582
2251
  try {
@@ -2616,7 +2285,8 @@ function _scenarioRulesToDefaults() {
2616
2285
  }
2617
2286
  function _agentRulesToDefaults() {
2618
2287
  const out = {};
2619
- const agentsDir = existsSync5(_AGENTS_DIR_PROD) ? _AGENTS_DIR_PROD : existsSync5(_AGENTS_DIR_DEV) ? _AGENTS_DIR_DEV : _AGENTS_DIR_PROD;
2288
+ const _root = getRulesRoot();
2289
+ const agentsDir = _root ? join4(_root, "agents") : existsSync5(_AGENTS_DIR_PROD) ? _AGENTS_DIR_PROD : existsSync5(_AGENTS_DIR_DEV) ? _AGENTS_DIR_DEV : _AGENTS_DIR_PROD;
2620
2290
  if (!existsSync5(agentsDir)) return out;
2621
2291
  let entries;
2622
2292
  try {
@@ -2625,7 +2295,7 @@ function _agentRulesToDefaults() {
2625
2295
  return out;
2626
2296
  }
2627
2297
  for (const agentId of entries) {
2628
- const rulesFile = join3(agentsDir, agentId, "rules.json");
2298
+ const rulesFile = join4(agentsDir, agentId, "rules.json");
2629
2299
  if (!existsSync5(rulesFile)) continue;
2630
2300
  let data;
2631
2301
  try {
@@ -3004,7 +2674,11 @@ function getDisabledSkillIds(runtime) {
3004
2674
  }
3005
2675
  function _filterDefaultsByOs(rules, osType) {
3006
2676
  const accepted = acceptedOsValues(osType);
3007
- return rules.filter((r) => !r.os || accepted.includes(String(r.os).toLowerCase()));
2677
+ return rules.filter((r) => {
2678
+ if (!r.os) return true;
2679
+ const os3 = String(r.os).toLowerCase();
2680
+ return os3 === "all" || accepted.includes(os3);
2681
+ });
3008
2682
  }
3009
2683
  function _hasRulesForOs(rules, osType) {
3010
2684
  return _filterRulesByOs(rules || [], osType || "linux").length > 0;
@@ -3233,12 +2907,12 @@ function addCustomSkill(skillId, category, keywords, content) {
3233
2907
  );
3234
2908
  }
3235
2909
  const runtime = loadRuntimeConfig();
3236
- const skillsDir = join3(getRuntimeConfigDir(), "agent_moss_skills");
2910
+ const skillsDir = join4(getRuntimeConfigDir(), "agent_moss_skills");
3237
2911
  try {
3238
2912
  mkdirSync(skillsDir, { recursive: true });
3239
2913
  } catch {
3240
2914
  }
3241
- writeFileSync(join3(skillsDir, `${skillId}.md`), content, "utf-8");
2915
+ writeFileSync(join4(skillsDir, `${skillId}.md`), content, "utf-8");
3242
2916
  const l3 = runtime.L3_skills || (runtime.L3_skills = {});
3243
2917
  const cat = l3[category] || (l3[category] = { category_enabled: true, category_desc: "", skills: [] });
3244
2918
  (cat.skills || (cat.skills = [])).push({ id: skillId, keywords, enabled: true, builtin: false });
@@ -3253,7 +2927,7 @@ function deleteCustomSkill(skillId, category) {
3253
2927
  if (idx >= 0) {
3254
2928
  if (skills[idx].builtin) return null;
3255
2929
  skills.splice(idx, 1);
3256
- const skillPath = join3(getRuntimeConfigDir(), "agent_moss_skills", `${skillId}.md`);
2930
+ const skillPath = join4(getRuntimeConfigDir(), "agent_moss_skills", `${skillId}.md`);
3257
2931
  try {
3258
2932
  if (existsSync5(skillPath)) unlinkSync(skillPath);
3259
2933
  } catch {
@@ -3462,7 +3136,7 @@ var init_runtime_config = __esm({
3462
3136
  __dirname4 = dirname3(fileURLToPath4(import.meta.url));
3463
3137
  ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
3464
3138
  RUNTIME_CONFIG_DIR = runtimeDir();
3465
- RUNTIME_CONFIG_PATH = join3(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
3139
+ RUNTIME_CONFIG_PATH = join4(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
3466
3140
  LAYER_DISABLE_ENV = {
3467
3141
  L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
3468
3142
  L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
@@ -3490,10 +3164,10 @@ var init_runtime_config = __esm({
3490
3164
  intent_consistency: ["intent_keywords", "reason"]
3491
3165
  };
3492
3166
  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");
3167
+ _AGENTS_DIR_PROD = join4(__dirname4, "agents");
3168
+ _AGENTS_DIR_DEV = join4(__dirname4, "..", "..", "..", "agent_moss", "agents");
3169
+ _SCENARIOS_DIR_PROD = join4(__dirname4, "scenarios");
3170
+ _SCENARIOS_DIR_DEV = join4(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
3497
3171
  _effectiveCache = /* @__PURE__ */ new WeakMap();
3498
3172
  SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
3499
3173
  registerRuntimeConfigBridge({
@@ -3832,15 +3506,22 @@ var init_config = __esm({
3832
3506
 
3833
3507
  // src/agents/index.ts
3834
3508
  import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
3835
- import { resolve as resolve4, dirname as dirname4 } from "path";
3509
+ import { resolve as resolve4, dirname as dirname4, join as join5 } from "path";
3836
3510
  import { fileURLToPath as fileURLToPath6 } from "url";
3837
3511
  function resolveAgentsDir() {
3512
+ const root = getRulesRoot();
3513
+ if (root) return join5(root, "agents");
3838
3514
  if (existsSync6(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
3839
3515
  if (existsSync6(AGENTS_DIR_DEV_FALLBACK)) return AGENTS_DIR_DEV_FALLBACK;
3840
3516
  return AGENTS_DIR_PROD;
3841
3517
  }
3842
3518
  function getAgentExtensions(agentId) {
3843
3519
  if (!agentId) return null;
3520
+ if (!isSafeId(agentId)) {
3521
+ console.warn(`[AgentMoss agents] \u975E\u6CD5 agent_id \u5DF2\u5FFD\u7565\uFF08\u542B\u8DEF\u5F84\u5B57\u7B26\u6216\u8D85\u957F\uFF09: ${agentId.slice(0, 120)}`);
3522
+ return null;
3523
+ }
3524
+ if (cache.size >= CACHE_MAX) cache.clear();
3844
3525
  const fp = getRuleSourceFingerprint();
3845
3526
  const cached2 = cache.get(agentId);
3846
3527
  if (cached2 && cached2.fp === fp) return cached2.ext;
@@ -3893,31 +3574,40 @@ function mergeAgentExtensions(...exts) {
3893
3574
  extra_skills: nonNull.flatMap((e) => e.extra_skills || [])
3894
3575
  };
3895
3576
  }
3896
- var __filename4, __dirname5, AGENTS_DIR_PROD, AGENTS_DIR_DEV_FALLBACK, cache;
3577
+ var __filename4, __dirname5, AGENTS_DIR_PROD, AGENTS_DIR_DEV_FALLBACK, cache, CACHE_MAX;
3897
3578
  var init_agents = __esm({
3898
3579
  "src/agents/index.ts"() {
3899
3580
  "use strict";
3900
3581
  init_esm_shims();
3582
+ init_paths();
3901
3583
  init_runtime_config();
3902
3584
  __filename4 = fileURLToPath6(import.meta.url);
3903
3585
  __dirname5 = dirname4(__filename4);
3904
3586
  AGENTS_DIR_PROD = resolve4(__dirname5, "agents");
3905
3587
  AGENTS_DIR_DEV_FALLBACK = resolve4(__dirname5, "../../../agent_moss/agents");
3906
3588
  cache = /* @__PURE__ */ new Map();
3589
+ CACHE_MAX = 512;
3907
3590
  }
3908
3591
  });
3909
3592
 
3910
3593
  // src/scenarios/index.ts
3911
3594
  import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
3912
- import { resolve as resolve5, dirname as dirname5 } from "path";
3595
+ import { resolve as resolve5, dirname as dirname5, join as join6 } from "path";
3913
3596
  import { fileURLToPath as fileURLToPath7 } from "url";
3914
3597
  function resolveScenariosDir() {
3598
+ const root = getRulesRoot();
3599
+ if (root) return join6(root, "scenarios");
3915
3600
  if (existsSync7(SCENARIOS_DIR_PROD)) return SCENARIOS_DIR_PROD;
3916
3601
  if (existsSync7(SCENARIOS_DIR_DEV_FALLBACK)) return SCENARIOS_DIR_DEV_FALLBACK;
3917
3602
  return SCENARIOS_DIR_PROD;
3918
3603
  }
3919
3604
  function getScenarioExtensions(scenarioId) {
3920
3605
  if (!scenarioId) return null;
3606
+ if (!isSafeId(scenarioId)) {
3607
+ console.warn(`[AgentMoss scenarios] \u975E\u6CD5 scenario_id \u5DF2\u5FFD\u7565\uFF08\u542B\u8DEF\u5F84\u5B57\u7B26\u6216\u8D85\u957F\uFF09: ${scenarioId.slice(0, 120)}`);
3608
+ return null;
3609
+ }
3610
+ if (cache2.size >= CACHE_MAX2) cache2.clear();
3921
3611
  const fp = getRuleSourceFingerprint();
3922
3612
  const cached2 = cache2.get(scenarioId);
3923
3613
  if (cached2 && cached2.fp === fp) return cached2.ext;
@@ -3987,11 +3677,12 @@ function getScenarioExtensionsMulti(scenarioSpec) {
3987
3677
  if (nonNull.length === 0) return null;
3988
3678
  return mergeAgentExtensions(...nonNull);
3989
3679
  }
3990
- var __filename5, __dirname6, SCENARIOS_DIR_PROD, SCENARIOS_DIR_DEV_FALLBACK, cache2;
3680
+ var __filename5, __dirname6, SCENARIOS_DIR_PROD, SCENARIOS_DIR_DEV_FALLBACK, cache2, CACHE_MAX2;
3991
3681
  var init_scenarios = __esm({
3992
3682
  "src/scenarios/index.ts"() {
3993
3683
  "use strict";
3994
3684
  init_esm_shims();
3685
+ init_paths();
3995
3686
  init_agents();
3996
3687
  init_runtime_config();
3997
3688
  init_rules_loader();
@@ -4000,6 +3691,7 @@ var init_scenarios = __esm({
4000
3691
  SCENARIOS_DIR_PROD = resolve5(__dirname6, "scenarios");
4001
3692
  SCENARIOS_DIR_DEV_FALLBACK = resolve5(__dirname6, "../../../agent_moss/scenarios");
4002
3693
  cache2 = /* @__PURE__ */ new Map();
3694
+ CACHE_MAX2 = 512;
4003
3695
  }
4004
3696
  });
4005
3697
 
@@ -4398,7 +4090,7 @@ function severityRank(s) {
4398
4090
  }
4399
4091
  function isSearchActionType(actionType) {
4400
4092
  const at = actionType.toLowerCase();
4401
- return SEARCH_ACTION_PREFIXES.some((p) => at.startsWith(p)) || ["websearch", "web_search", "search", "query"].includes(at);
4093
+ return SEARCH_ACTION_PREFIXES.some((p) => at.startsWith(p)) || SEARCH_ACTION_EXACT.includes(at);
4402
4094
  }
4403
4095
  function checkSensitivePath(command, cwd, agentExt, osType) {
4404
4096
  const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
@@ -4688,8 +4380,7 @@ function isSshKeygenGenerate(command) {
4688
4380
  function checkDangerousPatterns(actionType, actionDetail) {
4689
4381
  if (actionType === "file_write" || actionType === "file_edit") return null;
4690
4382
  const scan = stripHeredocContent(actionDetail);
4691
- const TEMP_DIRS = ["/tmp", "/var/tmp", "/run", "/dev/shm"];
4692
- const isTempPath = TEMP_DIRS.some(
4383
+ const isTempPath = TEMP_DIRS2.some(
4693
4384
  (td) => new RegExp(`(?:^|[\\s/])${td.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[\\s/]|$)`).test(actionDetail)
4694
4385
  ) || /(?:^|[\s;]|&&\s*|\|\|\s*)var\/tmp(?:[/\s;]|$)/.test(actionDetail);
4695
4386
  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 +4526,7 @@ function checkLateralMovement(actionDetail) {
4835
4526
  }
4836
4527
  return null;
4837
4528
  }
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;
4529
+ 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
4530
  var init_heuristic = __esm({
4840
4531
  "src/engine/heuristic.ts"() {
4841
4532
  "use strict";
@@ -4852,7 +4543,8 @@ var init_heuristic = __esm({
4852
4543
  return kws.map((k) => ({
4853
4544
  keyword: k.keyword,
4854
4545
  riskLevel: k.risk_level,
4855
- lang: k.lang || "en"
4546
+ lang: k.lang || "en",
4547
+ os: String(k.os || "all").toLowerCase()
4856
4548
  }));
4857
4549
  })();
4858
4550
  DANGEROUS_PATTERNS2 = _loadDangerousPatterns("linux");
@@ -4860,7 +4552,9 @@ var init_heuristic = __esm({
4860
4552
  DANGEROUS_PATTERNS_MACOS = _loadDangerousPatterns("macos");
4861
4553
  DANGEROUS_PATTERNS_HARMONY = _loadDangerousPatterns("harmony");
4862
4554
  LOOPBACK_URL_RE = /https?:\/\/([^\/\s;'"|&)]+)/i;
4863
- LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "::1", "0.0.0.0", "[::1]"]);
4555
+ HEURISTIC_RULES = loadHeuristicRules();
4556
+ LOOPBACK_HOSTS = new Set(HEURISTIC_RULES.loopback_hosts ?? []);
4557
+ TEMP_DIRS2 = HEURISTIC_RULES.temp_dirs ?? [];
4864
4558
  PATH_CONTEXT_TRIGGER_RE = /(?:^|[;&|]\s*)(?:rm|rmdir|unlink|ln|mv|cp|dd|truncate|shred|chmod|chown)\b/i;
4865
4559
  HOME_PREFIX_RE = /(?:~|~(?=\/)|\$HOME\b|\$\{HOME\}|\/home\/[a-z0-9_.-]+)/gi;
4866
4560
  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 +4562,9 @@ var init_heuristic = __esm({
4868
4562
  PY_BIGNUM_RE = /\d{6,}/;
4869
4563
  PY_INLINE_C_RE = /(python(?:3(?:\.\d+)?)?|perl5?|ruby|node|php|lua)\s+(?:-[a-zA-Z]+\s+)*-c\s+(?:"([^"]*)"|'([^']*)')/i;
4870
4564
  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"];
4565
+ SENSITIVE_EXCLUDED_PREFIXES = HEURISTIC_RULES.sensitive_excluded_prefixes ?? [];
4566
+ SEARCH_ACTION_PREFIXES = HEURISTIC_RULES.search_action_prefixes ?? [];
4567
+ SEARCH_ACTION_EXACT = HEURISTIC_RULES.search_action_exact ?? [];
4873
4568
  HOME_TARGET_RE = /(?:^|[\s="'($])(?:~|(?:\$home|\$\{home\}))(?:\/(?:\.\*|\*|\.)?)?(?=[\s"';}|&)]|$)/i;
4874
4569
  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
4570
  }
@@ -4885,16 +4580,16 @@ __export(token_stats_exports, {
4885
4580
  recordTokenUsage: () => recordTokenUsage,
4886
4581
  resetTokenStats: () => resetTokenStats
4887
4582
  });
4888
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync2, existsSync as existsSync11, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
4583
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync2, existsSync as existsSync12, mkdirSync as mkdirSync3, unlinkSync as unlinkSync2 } from "fs";
4889
4584
  function getStatsPath() {
4890
4585
  const envPath = process.env[ENV_STATS_PATH];
4891
4586
  if (envPath) return envPath;
4892
4587
  return getTokenStatsPath();
4893
4588
  }
4894
4589
  function loadAllRecords(statsPath) {
4895
- if (!existsSync11(statsPath)) return [];
4590
+ if (!existsSync12(statsPath)) return [];
4896
4591
  try {
4897
- const data = JSON.parse(readFileSync11(statsPath, "utf-8"));
4592
+ const data = JSON.parse(readFileSync12(statsPath, "utf-8"));
4898
4593
  if (Array.isArray(data)) return data;
4899
4594
  if (data && Array.isArray(data.records)) return data.records;
4900
4595
  return [];
@@ -4905,7 +4600,7 @@ function loadAllRecords(statsPath) {
4905
4600
  function saveRecords(statsPath, records) {
4906
4601
  try {
4907
4602
  const dir = statsPath.substring(0, statsPath.lastIndexOf("/"));
4908
- if (dir && !existsSync11(dir)) mkdirSync2(dir, { recursive: true });
4603
+ if (dir && !existsSync12(dir)) mkdirSync3(dir, { recursive: true });
4909
4604
  writeFileSync2(statsPath, JSON.stringify(records, null, 2), "utf-8");
4910
4605
  } catch (e) {
4911
4606
  }
@@ -5032,7 +4727,7 @@ function getTokenTrend(days = 0, startDate, endDate) {
5032
4727
  }
5033
4728
  function resetTokenStats() {
5034
4729
  const statsPath = getStatsPath();
5035
- if (existsSync11(statsPath)) {
4730
+ if (existsSync12(statsPath)) {
5036
4731
  try {
5037
4732
  unlinkSync2(statsPath);
5038
4733
  } catch {
@@ -5060,17 +4755,113 @@ init_inline_analyzer();
5060
4755
 
5061
4756
  // src/engine/llm-analyzer.ts
5062
4757
  init_esm_shims();
4758
+
4759
+ // src/infra/security-policy.ts
4760
+ init_esm_shims();
4761
+ init_paths();
4762
+ import { chmodSync, closeSync, existsSync as existsSync8, lstatSync, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync7, readSync, realpathSync, writeSync } from "fs";
4763
+ import { homedir as homedir2, tmpdir } from "os";
4764
+ import { basename, isAbsolute, join as join7, relative, resolve as resolve6, dirname as dirname6 } from "path";
4765
+ var TRUTHY2 = ["1", "true", "yes", "on"];
4766
+ function env(name, dflt = "") {
4767
+ return (process.env[name] ?? dflt).trim();
4768
+ }
4769
+ function splitList(raw) {
4770
+ return raw.replace(/;/g, ",").split(",").map((x) => x.trim()).filter(Boolean);
4771
+ }
4772
+ function homeDir() {
4773
+ try {
4774
+ return homedir2();
4775
+ } catch {
4776
+ return tmpdir();
4777
+ }
4778
+ }
4779
+ function logRoots() {
4780
+ const raw = env("AGENT_MOSS_LOG_ROOTS");
4781
+ if (raw) return splitList(raw);
4782
+ return [.../* @__PURE__ */ new Set([homeDir(), "/tmp", tmpdir()])];
4783
+ }
4784
+ function realpathLoose(p) {
4785
+ const abs = resolve6(p.replace(/^~(?=$|\/)/, homeDir()));
4786
+ try {
4787
+ return realpathSync(abs);
4788
+ } catch {
4789
+ }
4790
+ const parts = [];
4791
+ let cur = abs;
4792
+ for (; ; ) {
4793
+ const parent = dirname6(cur);
4794
+ if (parent === cur) return null;
4795
+ parts.unshift(basename(cur));
4796
+ cur = parent;
4797
+ try {
4798
+ return join7(realpathSync(cur), ...parts);
4799
+ } catch {
4800
+ }
4801
+ }
4802
+ }
4803
+ function pathWithin(p, roots) {
4804
+ if (!p) return true;
4805
+ const target = realpathLoose(p);
4806
+ if (target === null) return false;
4807
+ for (const root of roots) {
4808
+ const rootReal = realpathLoose(root);
4809
+ if (rootReal === null) continue;
4810
+ const rel = relative(rootReal, target);
4811
+ if (rel === "" || !rel.startsWith("..") && !isAbsolute(rel)) return true;
4812
+ }
4813
+ return false;
4814
+ }
4815
+ var BLOCKED_HOSTS = ["169.254.169.254", "metadata.google.internal", "metadata.tencentyun.com"];
4816
+ function validateLlmBaseUrl(url, allowPrivate) {
4817
+ if (!url) return [true, ""];
4818
+ let u;
4819
+ try {
4820
+ u = new URL(url);
4821
+ } catch {
4822
+ return [false, "baseUrl \u4E0D\u662F\u5408\u6CD5 URL"];
4823
+ }
4824
+ if (u.protocol !== "http:" && u.protocol !== "https:") {
4825
+ return [false, `baseUrl \u534F\u8BAE\u4E0D\u53D7\u652F\u6301: ${u.protocol}`];
4826
+ }
4827
+ const host = (u.hostname || "").toLowerCase();
4828
+ if (!host) return [false, "baseUrl \u7F3A\u5C11\u4E3B\u673A\u540D"];
4829
+ if (BLOCKED_HOSTS.includes(host)) return [false, `baseUrl \u6307\u5411\u4E91\u5143\u6570\u636E\u5730\u5740\uFF0C\u5DF2\u62D2\u7EDD: ${host}`];
4830
+ if (/^169\.254\.\d{1,3}\.\d{1,3}$/.test(host)) {
4831
+ return [false, `baseUrl \u6307\u5411\u94FE\u8DEF\u672C\u5730\u5730\u5740\uFF0C\u5DF2\u62D2\u7EDD: ${host}`];
4832
+ }
4833
+ const allowlist = splitList(env("AGENT_MOSS_LLM_HOST_ALLOWLIST")).map((h) => h.toLowerCase());
4834
+ if (allowlist.length && !allowlist.includes(host)) {
4835
+ return [false, `baseUrl \u4E3B\u673A\u4E0D\u5728\u5141\u8BB8\u5217\u8868: ${host}`];
4836
+ }
4837
+ if (allowPrivate === void 0) {
4838
+ allowPrivate = TRUTHY2.includes(env("AGENT_MOSS_TRUST_PRIVATE_LLM", "1").toLowerCase());
4839
+ }
4840
+ if (!allowPrivate) {
4841
+ if (host === "localhost" || host === "::1") return [false, "baseUrl \u6307\u5411\u672C\u673A\uFF0C\u5DF2\u62D2\u7EDD"];
4842
+ const m = host.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/);
4843
+ if (m) {
4844
+ const [a, b] = [Number(m[1]), Number(m[2])];
4845
+ if (a === 10 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 127 || a === 169 && b === 254) {
4846
+ return [false, `baseUrl \u6307\u5411\u5185\u7F51\u5730\u5740\uFF0C\u5DF2\u62D2\u7EDD: ${host}`];
4847
+ }
4848
+ }
4849
+ }
4850
+ return [true, ""];
4851
+ }
4852
+
4853
+ // src/engine/llm-analyzer.ts
5063
4854
  init_config();
5064
4855
  init_skill_loader();
5065
4856
 
5066
4857
  // src/engine/template-loader.ts
5067
4858
  init_esm_shims();
5068
4859
  init_asset_dir();
5069
- import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
5070
- import { resolve as resolve6, dirname as dirname6 } from "path";
4860
+ import { readFileSync as readFileSync8, existsSync as existsSync9 } from "fs";
4861
+ import { resolve as resolve7, dirname as dirname7 } from "path";
5071
4862
  import { fileURLToPath as fileURLToPath8 } from "url";
5072
4863
  var __filename6 = fileURLToPath8(import.meta.url);
5073
- var __dirname7 = dirname6(__filename6);
4864
+ var __dirname7 = dirname7(__filename6);
5074
4865
  var TEMPLATE_FILES = ["prompt1_template.txt", "prompt2_template.txt", "security_judge_template.txt"];
5075
4866
  var cachedTemplates = null;
5076
4867
  function loadTemplates() {
@@ -5083,10 +4874,10 @@ function loadTemplates() {
5083
4874
  return cachedTemplates;
5084
4875
  }
5085
4876
  for (const file of TEMPLATE_FILES) {
5086
- const filePath = resolve6(dir, file);
5087
- if (existsSync8(filePath)) {
4877
+ const filePath = resolve7(dir, file);
4878
+ if (existsSync9(filePath)) {
5088
4879
  const key = file.replace(".txt", "").replace("_template", "");
5089
- cachedTemplates[key] = readFileSync7(filePath, "utf-8");
4880
+ cachedTemplates[key] = readFileSync8(filePath, "utf-8");
5090
4881
  }
5091
4882
  }
5092
4883
  console.log(`[AgentMoss] Loaded ${Object.keys(cachedTemplates).length} templates from ${dir}`);
@@ -5095,87 +4886,25 @@ function loadTemplates() {
5095
4886
 
5096
4887
  // src/engine/script-analyzer.ts
5097
4888
  init_esm_shims();
5098
- 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
- };
4889
+ init_rules_loader();
4890
+ import { readFileSync as readFileSync9 } from "fs";
4891
+ import { resolve as resolve8, join as join8 } from "path";
4892
+ var SCRIPT_RULES = loadScriptRules();
4893
+ if (!SCRIPT_RULES || Object.keys(SCRIPT_RULES).length === 0) {
4894
+ console.error(
4895
+ "[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"
4896
+ );
4897
+ }
4898
+ var SUSPICIOUS_PATTERNS = (SCRIPT_RULES.suspicious_patterns ?? []).map((r) => ({ name: r.name, pattern: compileRuleRegex(r) }));
4899
+ var SCRIPT_EXEC_PATTERNS = ((SCRIPT_RULES.script_exec_patterns ?? {}).legacy ?? []).map((r) => compileRuleRegex(r));
4900
+ var KEYWORD_SETS = SCRIPT_RULES.keyword_sets ?? {};
4901
+ var KEYWORD_COMBINATION_RISKS = (SCRIPT_RULES.keyword_combination_risks ?? []).map((r) => ({
4902
+ group1: resolveKeywordSet(r.group1, KEYWORD_SETS),
4903
+ group2: resolveKeywordSet(r.group2, KEYWORD_SETS),
4904
+ level: String(r.level),
4905
+ reason: String(r.reason)
4906
+ }));
4907
+ var SINGLE_KEYWORD_RISKS = SCRIPT_RULES.single_keyword_risks ?? {};
5179
4908
  var LEVEL_ORDER = { low: 1, medium: 2, high: 3, critical: 4 };
5180
4909
  function emptyResult() {
5181
4910
  return { scriptPath: "", scriptContent: "", matchedKeywords: [], lineCount: 0, isSuspicious: false, error: "", riskLevel: "", riskReason: "" };
@@ -5202,10 +4931,10 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
5202
4931
  const hitGroup2 = rule.group2.size === 0 ? true : [...rule.group2].some((k) => keywordSet.has(k));
5203
4932
  if (hitGroup1 && hitGroup2) return { level: rule.level, reason: rule.reason };
5204
4933
  }
5205
- const networkPostKeywords = /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]);
4934
+ const networkPostKeywords = new Set(KEYWORD_SETS.network_post ?? []);
5206
4935
  const hasNetworkPost = [...networkPostKeywords].some((k) => keywordSet.has(k));
5207
4936
  if (hasNetworkPost) {
5208
- const envPatterns = ["$env", "$(env)", "$USER", "$(hostname)", "$(pwd)", "$HOME"];
4937
+ const envPatterns = SCRIPT_RULES.env_exfil_patterns ?? [];
5209
4938
  const contentLower = scriptContent.toLowerCase();
5210
4939
  for (const p of envPatterns) {
5211
4940
  if (contentLower.includes(p.toLowerCase())) {
@@ -5229,8 +4958,8 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
5229
4958
  }
5230
4959
  function readScriptContent(scriptPath, maxLines = 500) {
5231
4960
  try {
5232
- const expandedPath = scriptPath === "~" || scriptPath.startsWith("~/") ? join4(process.env.HOME || "/home/user", scriptPath.slice(1)) : resolve7(scriptPath);
5233
- const raw = readFileSync8(expandedPath, "utf-8");
4961
+ const expandedPath = scriptPath === "~" || scriptPath.startsWith("~/") ? join8(process.env.HOME || "/home/user", scriptPath.slice(1)) : resolve8(scriptPath);
4962
+ const raw = readFileSync9(expandedPath, "utf-8");
5234
4963
  const lines = raw.split("\n").slice(0, maxLines);
5235
4964
  return { content: lines.join("\n"), lineCount: lines.length, error: "" };
5236
4965
  } catch (e) {
@@ -5286,9 +5015,9 @@ init_heuristic();
5286
5015
  init_config();
5287
5016
  init_logic_rules();
5288
5017
  init_inline_analyzer();
5289
- import { statSync as statSync4, existsSync as existsSync10 } from "fs";
5290
- import { resolve as resolve9, join as join6, isAbsolute as isAbsolute2 } from "path";
5291
- import { homedir as homedir3 } from "os";
5018
+ import { statSync as statSync4, existsSync as existsSync11 } from "fs";
5019
+ import { resolve as resolve10, join as join10, isAbsolute as isAbsolute3 } from "path";
5020
+ import { homedir as homedir4 } from "os";
5292
5021
 
5293
5022
  // src/engine/script-engine/tracker.ts
5294
5023
  init_esm_shims();
@@ -5398,14 +5127,14 @@ function isSysArgv(node) {
5398
5127
  const prop = node.getChild("PropertyName");
5399
5128
  return prop !== null && srcSlice(prop) === "argv";
5400
5129
  }
5401
- function evalExpr(node, env) {
5130
+ function evalExpr(node, env2) {
5402
5131
  if (!node) return null;
5403
5132
  const name = node.name;
5404
5133
  if (name === "String") return stringValue(srcSlice(node));
5405
5134
  if (name === "Number") return srcSlice(node);
5406
5135
  if (name === "VariableName") {
5407
5136
  const id = srcSlice(node);
5408
- return id in env.vars ? env.vars[id] : null;
5137
+ return id in env2.vars ? env2.vars[id] : null;
5409
5138
  }
5410
5139
  if (name === "MemberExpression") {
5411
5140
  const bracket = node.getChild("[");
@@ -5415,13 +5144,13 @@ function evalExpr(node, env) {
5415
5144
  const idxNode = bracket.nextSibling;
5416
5145
  if (idxNode && idxNode.name === "Number") {
5417
5146
  const idx = parseInt(srcSlice(idxNode), 10);
5418
- if (idx >= 1 && idx <= env.argv.length) return env.argv[idx - 1];
5147
+ if (idx >= 1 && idx <= env2.argv.length) return env2.argv[idx - 1];
5419
5148
  return null;
5420
5149
  }
5421
5150
  }
5422
5151
  return null;
5423
5152
  }
5424
- if (isSysArgv(node)) return env.argv[0] ?? null;
5153
+ if (isSysArgv(node)) return env2.argv[0] ?? null;
5425
5154
  return null;
5426
5155
  }
5427
5156
  if (name === "CallExpression") {
@@ -5429,7 +5158,7 @@ function evalExpr(node, env) {
5429
5158
  if (func && func.name === "VariableName" && srcSlice(func) === "len") {
5430
5159
  const argList = node.getChild("ArgList");
5431
5160
  const inner = argList ? firstNamedChild(argList) : null;
5432
- if (inner && isSysArgv(inner)) return String(env.argv.length + 1);
5161
+ if (inner && isSysArgv(inner)) return String(env2.argv.length + 1);
5433
5162
  }
5434
5163
  return null;
5435
5164
  }
@@ -5441,16 +5170,16 @@ function evalExpr(node, env) {
5441
5170
  const condNode = ifKw.nextSibling;
5442
5171
  const orelseNode = elseKw.nextSibling;
5443
5172
  if (!thenNode || !condNode || !orelseNode) return null;
5444
- const cond = evalCondition(condNode, env);
5445
- if (cond === true) return evalExpr(thenNode, env);
5446
- if (cond === false) return evalExpr(orelseNode, env);
5173
+ const cond = evalCondition(condNode, env2);
5174
+ if (cond === true) return evalExpr(thenNode, env2);
5175
+ if (cond === false) return evalExpr(orelseNode, env2);
5447
5176
  return null;
5448
5177
  }
5449
5178
  if (name === "BinaryExpression") {
5450
5179
  const op = findOp(node);
5451
5180
  if (op === "+") {
5452
- const left = evalExpr(node.firstChild, env);
5453
- const right = evalExpr(lastNamedChild(node), env);
5181
+ const left = evalExpr(node.firstChild, env2);
5182
+ const right = evalExpr(lastNamedChild(node), env2);
5454
5183
  if (left !== null && right !== null) return left + right;
5455
5184
  }
5456
5185
  return null;
@@ -5483,7 +5212,7 @@ function findOp(node) {
5483
5212
  }
5484
5213
  return null;
5485
5214
  }
5486
- function evalCondition(node, env) {
5215
+ function evalCondition(node, env2) {
5487
5216
  if (!node) return null;
5488
5217
  const name = node.name;
5489
5218
  if (name === "Boolean") {
@@ -5494,7 +5223,7 @@ function evalCondition(node, env) {
5494
5223
  const op = findOp(node);
5495
5224
  if (op === "and" || op === "or") {
5496
5225
  const parts2 = namedChildren(node);
5497
- const vals = parts2.map((p) => evalCondition(p, env));
5226
+ const vals = parts2.map((p) => evalCondition(p, env2));
5498
5227
  if (op === "and") {
5499
5228
  if (vals.some((v) => v === false)) return false;
5500
5229
  if (vals.every((v) => v === true)) return true;
@@ -5508,13 +5237,13 @@ function evalCondition(node, env) {
5508
5237
  if (parts.length !== 3) return null;
5509
5238
  const [left, opNode, right] = parts;
5510
5239
  const opText = srcSlice(opNode).trim();
5511
- return evalCompare(left, opText, right, env);
5240
+ return evalCompare(left, opText, right, env2);
5512
5241
  }
5513
5242
  if (name === "UnaryExpression") {
5514
5243
  const kw = node.getChild("not");
5515
5244
  if (kw) {
5516
5245
  const operand = kw.nextSibling;
5517
- const v = evalCondition(operand, env);
5246
+ const v = evalCondition(operand, env2);
5518
5247
  return v === null ? null : !v;
5519
5248
  }
5520
5249
  return null;
@@ -5535,39 +5264,39 @@ function namedChildren(node) {
5535
5264
  }
5536
5265
  return out;
5537
5266
  }
5538
- function evalCompare(left, opText, right, env) {
5267
+ function evalCompare(left, opText, right, env2) {
5539
5268
  if (opText === "==" && left.name === "VariableName" && srcSlice(left) === "__name__") {
5540
- const rv = evalExpr(right, env);
5269
+ const rv = evalExpr(right, env2);
5541
5270
  if (rv !== null) return rv === "__main__";
5542
5271
  return null;
5543
5272
  }
5544
5273
  if (opText === "==" || opText === "!=") {
5545
- const lv = evalExpr(left, env);
5546
- const rv = evalExpr(right, env);
5274
+ const lv = evalExpr(left, env2);
5275
+ const rv = evalExpr(right, env2);
5547
5276
  if (lv !== null && rv !== null) {
5548
5277
  return opText === "==" ? lv === rv : lv !== rv;
5549
5278
  }
5550
5279
  return null;
5551
5280
  }
5552
5281
  if (opText === "in") {
5553
- const lv = evalExpr(left, env);
5282
+ const lv = evalExpr(left, env2);
5554
5283
  if (lv === null) return null;
5555
5284
  const rightName = right.name;
5556
5285
  if (rightName === "TupleExpression" || rightName === "ArrayExpression" || rightName === "SetExpression" || rightName === "ParenthesizedExpression") {
5557
5286
  const container = rightName === "ParenthesizedExpression" && right.firstChild && right.firstChild.name === "TupleExpression" ? right.firstChild : right;
5558
5287
  for (const elt of namedChildren(container)) {
5559
- const ev = evalExpr(elt, env);
5288
+ const ev = evalExpr(elt, env2);
5560
5289
  if (ev !== null && ev === lv) return true;
5561
5290
  }
5562
5291
  return false;
5563
5292
  }
5564
- const rv = evalExpr(right, env);
5293
+ const rv = evalExpr(right, env2);
5565
5294
  if (rv !== null) return rv.includes(lv);
5566
5295
  return null;
5567
5296
  }
5568
5297
  if (opText === ">" || opText === ">=" || opText === "<" || opText === "<=") {
5569
- const lv = evalExpr(left, env);
5570
- const rv = evalExpr(right, env);
5298
+ const lv = evalExpr(left, env2);
5299
+ const rv = evalExpr(right, env2);
5571
5300
  if (lv === null || rv === null) return null;
5572
5301
  const ln = parseInt(lv, 10);
5573
5302
  const rn = parseInt(rv, 10);
@@ -5579,71 +5308,71 @@ function evalCompare(left, opText, right, env) {
5579
5308
  }
5580
5309
  return null;
5581
5310
  }
5582
- function processStmts(stmts, env, lineStarts, lines, branches) {
5583
- for (const s of stmts) processStmt(s, env, lineStarts, lines, branches);
5311
+ function processStmts(stmts, env2, lineStarts, lines, branches) {
5312
+ for (const s of stmts) processStmt(s, env2, lineStarts, lines, branches);
5584
5313
  }
5585
- function invalidateEnvOnCalls(node, env) {
5314
+ function invalidateEnvOnCalls(node, env2) {
5586
5315
  const cursor = node.cursor();
5587
5316
  cursor.iterate((n) => {
5588
5317
  const nd = n.node;
5589
5318
  if (nd.name !== "CallExpression") return true;
5590
5319
  const func = nd.firstChild;
5591
5320
  if (func && func.name === "VariableName" && !PURE_BUILTINS.has(srcSlice(func))) {
5592
- for (const k of Object.keys(env.vars)) env.vars[k] = null;
5321
+ for (const k of Object.keys(env2.vars)) env2.vars[k] = null;
5593
5322
  return false;
5594
5323
  }
5595
5324
  return true;
5596
5325
  });
5597
5326
  }
5598
- function processStmt(stmt, env, lineStarts, lines, branches) {
5327
+ function processStmt(stmt, env2, lineStarts, lines, branches) {
5599
5328
  const name = stmt.name;
5600
5329
  if (name === "AssignStatement") {
5601
5330
  const target = stmt.firstChild;
5602
5331
  if (target && target.name === "VariableName") {
5603
5332
  const valNode = target.nextSibling ? target.nextSibling.nextSibling : null;
5604
- invalidateEnvOnCalls(stmt, env);
5605
- const val = evalExpr(valNode, env);
5606
- env.vars[srcSlice(target)] = val;
5333
+ invalidateEnvOnCalls(stmt, env2);
5334
+ const val = evalExpr(valNode, env2);
5335
+ env2.vars[srcSlice(target)] = val;
5607
5336
  }
5608
5337
  markNodeLines(stmt, lines, lineStarts);
5609
5338
  return;
5610
5339
  }
5611
5340
  if (name === "UpdateStatement") {
5612
5341
  const target = stmt.firstChild;
5613
- if (target && target.name === "VariableName") env.vars[srcSlice(target)] = null;
5614
- invalidateEnvOnCalls(stmt, env);
5342
+ if (target && target.name === "VariableName") env2.vars[srcSlice(target)] = null;
5343
+ invalidateEnvOnCalls(stmt, env2);
5615
5344
  markNodeLines(stmt, lines, lineStarts);
5616
5345
  return;
5617
5346
  }
5618
5347
  if (name === "IfStatement") {
5619
- processIf(stmt, env, lineStarts, lines, branches);
5348
+ processIf(stmt, env2, lineStarts, lines, branches);
5620
5349
  return;
5621
5350
  }
5622
5351
  if (name === "ForStatement" || name === "WhileStatement") {
5623
5352
  markNodeLines(stmt, lines, lineStarts);
5624
5353
  for (const body of stmt.getChildren("Body")) {
5625
- processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
5354
+ processStmts(bodyStmts(body), { argv: env2.argv, vars: { ...env2.vars } }, lineStarts, lines, branches);
5626
5355
  }
5627
5356
  return;
5628
5357
  }
5629
5358
  if (name === "TryStatement") {
5630
5359
  markNodeLines(stmt, lines, lineStarts);
5631
5360
  for (const body of stmt.getChildren("Body")) {
5632
- processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
5361
+ processStmts(bodyStmts(body), { argv: env2.argv, vars: { ...env2.vars } }, lineStarts, lines, branches);
5633
5362
  }
5634
5363
  return;
5635
5364
  }
5636
5365
  if (name === "WithStatement") {
5637
5366
  markNodeLines(stmt, lines, lineStarts);
5638
5367
  const body = stmt.getChild("Body");
5639
- if (body) processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
5368
+ if (body) processStmts(bodyStmts(body), { argv: env2.argv, vars: { ...env2.vars } }, lineStarts, lines, branches);
5640
5369
  return;
5641
5370
  }
5642
5371
  if (name === "MatchStatement") {
5643
5372
  markNodeLines(stmt, lines, lineStarts);
5644
5373
  for (const clause of stmt.getChildren("MatchClause")) {
5645
5374
  const body = clause.getChild("Body");
5646
- if (body) processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
5375
+ if (body) processStmts(bodyStmts(body), { argv: env2.argv, vars: { ...env2.vars } }, lineStarts, lines, branches);
5647
5376
  }
5648
5377
  return;
5649
5378
  }
@@ -5652,7 +5381,7 @@ function processStmt(stmt, env, lineStarts, lines, branches) {
5652
5381
  return;
5653
5382
  }
5654
5383
  if (name === "ExpressionStatement") {
5655
- invalidateEnvOnCalls(stmt, env);
5384
+ invalidateEnvOnCalls(stmt, env2);
5656
5385
  }
5657
5386
  markNodeLines(stmt, lines, lineStarts);
5658
5387
  }
@@ -5672,7 +5401,7 @@ function bodyStmts(body) {
5672
5401
  }
5673
5402
  return out;
5674
5403
  }
5675
- function processIf(stmt, env, lineStarts, lines, branches) {
5404
+ function processIf(stmt, env2, lineStarts, lines, branches) {
5676
5405
  const sections = [];
5677
5406
  let c = stmt.firstChild;
5678
5407
  while (c) {
@@ -5687,17 +5416,17 @@ function processIf(stmt, env, lineStarts, lines, branches) {
5687
5416
  }
5688
5417
  c = c.nextSibling;
5689
5418
  }
5690
- processIfSections(stmt, sections, 0, env, lineStarts, lines, branches);
5419
+ processIfSections(stmt, sections, 0, env2, lineStarts, lines, branches);
5691
5420
  }
5692
- function processIfSections(_stmt, sections, idx, env, lineStarts, lines, branches) {
5421
+ function processIfSections(_stmt, sections, idx, env2, lineStarts, lines, branches) {
5693
5422
  if (idx >= sections.length) return;
5694
5423
  const s = sections[idx];
5695
5424
  if (s.cond) markNodeLines(s.cond, lines, lineStarts);
5696
5425
  if (s.isElse) {
5697
- if (s.body) processStmts(bodyStmts(s.body), env, lineStarts, lines, branches);
5426
+ if (s.body) processStmts(bodyStmts(s.body), env2, lineStarts, lines, branches);
5698
5427
  return;
5699
5428
  }
5700
- const cond = s.cond ? evalCondition(s.cond, env) : null;
5429
+ const cond = s.cond ? evalCondition(s.cond, env2) : null;
5701
5430
  branches.push({
5702
5431
  kind: "if",
5703
5432
  label: s.cond ? srcSlice(s.cond) : "",
@@ -5705,18 +5434,18 @@ function processIfSections(_stmt, sections, idx, env, lineStarts, lines, branche
5705
5434
  reachable: cond !== false
5706
5435
  });
5707
5436
  if (cond === true) {
5708
- if (s.body) processStmts(bodyStmts(s.body), env, lineStarts, lines, branches);
5437
+ if (s.body) processStmts(bodyStmts(s.body), env2, lineStarts, lines, branches);
5709
5438
  return;
5710
5439
  }
5711
5440
  if (cond === false) {
5712
- processIfSections(_stmt, sections, idx + 1, env, lineStarts, lines, branches);
5441
+ processIfSections(_stmt, sections, idx + 1, env2, lineStarts, lines, branches);
5713
5442
  return;
5714
5443
  }
5715
- if (s.body) processStmts(bodyStmts(s.body), env, lineStarts, lines, branches);
5444
+ if (s.body) processStmts(bodyStmts(s.body), env2, lineStarts, lines, branches);
5716
5445
  for (let j = idx + 1; j < sections.length; j++) {
5717
5446
  const sj = sections[j];
5718
5447
  if (sj.cond) markNodeLines(sj.cond, lines, lineStarts);
5719
- if (sj.body) processStmts(bodyStmts(sj.body), env, lineStarts, lines, branches);
5448
+ if (sj.body) processStmts(bodyStmts(sj.body), env2, lineStarts, lines, branches);
5720
5449
  }
5721
5450
  }
5722
5451
  function parseShellString(s) {
@@ -5839,7 +5568,7 @@ var PythonBackend = class {
5839
5568
  SOURCE = c;
5840
5569
  const sourceLines = c.split("\n");
5841
5570
  const lineStarts = buildLineStarts(c);
5842
- const env = makeEnv(entryArgs || []);
5571
+ const env2 = makeEnv(entryArgs || []);
5843
5572
  const lines = /* @__PURE__ */ new Set();
5844
5573
  const branches = [];
5845
5574
  if (sourceLines.length > MAX_LINES) {
@@ -5866,7 +5595,7 @@ var PythonBackend = class {
5866
5595
  };
5867
5596
  }
5868
5597
  const stmts = bodyStmts(tree);
5869
- processStmts(stmts, env, lineStarts, lines, branches);
5598
+ processStmts(stmts, env2, lineStarts, lines, branches);
5870
5599
  const reachable = reconstruct(sourceLines, lines);
5871
5600
  let calls = extractCallsFromTree(tree, lines);
5872
5601
  if (calls.length === 0) calls = extractCallsRegex(c);
@@ -6116,7 +5845,7 @@ function tokenize(content) {
6116
5845
  return stmts;
6117
5846
  }
6118
5847
  var EXPR_SPLIT_RE = /(\$\{[^}]*\}|\$[A-Za-z_][A-Za-z0-9_]*|\$\d+)/;
6119
- function evalExpr2(text, env) {
5848
+ function evalExpr2(text, env2) {
6120
5849
  const isSingleQuoted = text.length >= 2 && text[0] === "'" && text[text.length - 1] === "'";
6121
5850
  if (text.length >= 2 && (text[0] === '"' || text[0] === "'") && text[text.length - 1] === text[0]) {
6122
5851
  text = text.slice(1, -1);
@@ -6132,19 +5861,19 @@ function evalExpr2(text, env) {
6132
5861
  const dm = inner.match(/^([A-Za-z_][A-Za-z0-9_]*|\d+):?-(.*)$/s);
6133
5862
  if (dm) {
6134
5863
  const key = /^\d+$/.test(dm[1]) ? `$${dm[1]}` : dm[1];
6135
- const bound = env.get(key);
5864
+ const bound = env2.get(key);
6136
5865
  if (bound && !(bound.kind === "concrete" && !bound.value)) {
6137
5866
  result.push(bound);
6138
5867
  } else {
6139
- result.push(evalExpr2(dm[2], env));
5868
+ result.push(evalExpr2(dm[2], env2));
6140
5869
  }
6141
5870
  continue;
6142
5871
  }
6143
5872
  if (/^\d+$/.test(inner)) {
6144
- const v = env.get(`$${inner}`);
5873
+ const v = env2.get(`$${inner}`);
6145
5874
  result.push(v || svalSym(`$${inner}`));
6146
5875
  } else {
6147
- const v = env.get(inner);
5876
+ const v = env2.get(inner);
6148
5877
  result.push(v || svalSym(inner));
6149
5878
  }
6150
5879
  continue;
@@ -6153,7 +5882,7 @@ function evalExpr2(text, env) {
6153
5882
  if (bare) {
6154
5883
  const varName = bare[1];
6155
5884
  const key = /^\d+$/.test(varName) ? `$${varName}` : varName;
6156
- const v = env.get(key);
5885
+ const v = env2.get(key);
6157
5886
  result.push(v || svalSym(p));
6158
5887
  continue;
6159
5888
  }
@@ -6163,13 +5892,13 @@ function evalExpr2(text, env) {
6163
5892
  return fold(svalExpr(result));
6164
5893
  }
6165
5894
  function initEnv(entryArgs) {
6166
- const env = /* @__PURE__ */ new Map();
5895
+ const env2 = /* @__PURE__ */ new Map();
6167
5896
  if (entryArgs && entryArgs.length) {
6168
- entryArgs.forEach((a, idx) => env.set(`$${idx + 1}`, svalConc(a)));
6169
- env.set("$@", svalConc(entryArgs.join(" ")));
6170
- env.set("$*", svalConc(entryArgs.join(" ")));
5897
+ entryArgs.forEach((a, idx) => env2.set(`$${idx + 1}`, svalConc(a)));
5898
+ env2.set("$@", svalConc(entryArgs.join(" ")));
5899
+ env2.set("$*", svalConc(entryArgs.join(" ")));
6171
5900
  }
6172
- return env;
5901
+ return env2;
6173
5902
  }
6174
5903
  var HAS_GLOB = /[*?[]/;
6175
5904
  function globToRegex(pattern) {
@@ -6228,12 +5957,12 @@ function evalNumericCompare(op, left, right) {
6228
5957
  return null;
6229
5958
  }
6230
5959
  }
6231
- function resolveVarSide(tok, env) {
5960
+ function resolveVarSide(tok, env2) {
6232
5961
  if (!tok.startsWith("$")) return tok;
6233
- const ev = evalExpr2(tok, env);
5962
+ const ev = evalExpr2(tok, env2);
6234
5963
  return ev.kind === "concrete" ? ev.value : null;
6235
5964
  }
6236
- function evalCondition2(cond, env) {
5965
+ function evalCondition2(cond, env2) {
6237
5966
  let inner = cond.trim().replace(/^\[\[?|\]\]?$/g, "").trim();
6238
5967
  if (/^-[A-Za-z]\s/.test(inner) || inner.startsWith("-d") || inner.startsWith("-f")) return null;
6239
5968
  const innerQ = inner.replace(/"/g, "").replace(/'/g, "");
@@ -6243,8 +5972,8 @@ function evalCondition2(cond, env) {
6243
5972
  if (!opm) continue;
6244
5973
  const left2 = innerQ.slice(0, opm.index).trim();
6245
5974
  const right2 = innerQ.slice(opm.index + opm[0].length).trim();
6246
- const lv2 = resolveVarSide(left2, env);
6247
- const rv2 = resolveVarSide(right2, env);
5975
+ const lv2 = resolveVarSide(left2, env2);
5976
+ const rv2 = resolveVarSide(right2, env2);
6248
5977
  if (lv2 === null || rv2 === null) return null;
6249
5978
  return evalNumericCompare(op2, lv2, rv2);
6250
5979
  }
@@ -6255,7 +5984,7 @@ function evalCondition2(cond, env) {
6255
5984
  let right = sm[3].trim();
6256
5985
  if (op === "=~") {
6257
5986
  if (right.startsWith("/") && right.endsWith("/")) right = right.slice(1, -1);
6258
- const lv2 = resolveVarSide(left, env);
5987
+ const lv2 = resolveVarSide(left, env2);
6259
5988
  if (lv2 === null) return null;
6260
5989
  try {
6261
5990
  return new RegExp(right).test(lv2);
@@ -6263,8 +5992,8 @@ function evalCondition2(cond, env) {
6263
5992
  return null;
6264
5993
  }
6265
5994
  }
6266
- const lv = resolveVarSide(left, env);
6267
- const rv = resolveVarSide(right, env);
5995
+ const lv = resolveVarSide(left, env2);
5996
+ const rv = resolveVarSide(right, env2);
6268
5997
  if (lv === null || rv === null) return null;
6269
5998
  if (op === "!=") return lv !== rv;
6270
5999
  return lv === rv;
@@ -6274,7 +6003,7 @@ function symbolicProcess(content, entryArgs) {
6274
6003
  const stmts = tokenize(content);
6275
6004
  if (!stmts.some((s) => s.kind === "case" || s.kind === "if" || s.kind === "assign")) return null;
6276
6005
  if (stmts.some((s) => !s.modelable)) return null;
6277
- const env = initEnv(entryArgs);
6006
+ const env2 = initEnv(entryArgs);
6278
6007
  let linesBudget = 0;
6279
6008
  let branchesBudget = 0;
6280
6009
  let modelable = false;
@@ -6284,9 +6013,9 @@ function symbolicProcess(content, entryArgs) {
6284
6013
  if (linesBudget > MAX_LINES2) return null;
6285
6014
  if (s.kind === "assign") {
6286
6015
  modelable = true;
6287
- if (env.size >= MAX_VARS) return null;
6288
- const v = evalExpr2(s.rhs, env);
6289
- env.set(s.name, fold(v));
6016
+ if (env2.size >= MAX_VARS) return null;
6017
+ const v = evalExpr2(s.rhs, env2);
6018
+ env2.set(s.name, fold(v));
6290
6019
  out.push(s.line);
6291
6020
  continue;
6292
6021
  }
@@ -6294,7 +6023,7 @@ function symbolicProcess(content, entryArgs) {
6294
6023
  modelable = true;
6295
6024
  branchesBudget += s.branches.length;
6296
6025
  if (branchesBudget > MAX_BRANCHES) return null;
6297
- const selVal = fold(evalExpr2(s.selector, env));
6026
+ const selVal = fold(evalExpr2(s.selector, env2));
6298
6027
  if (selVal.kind !== "concrete") return null;
6299
6028
  const concrete = selVal.value || "";
6300
6029
  let selected = null;
@@ -6321,7 +6050,7 @@ esac`);
6321
6050
  modelable = true;
6322
6051
  branchesBudget++;
6323
6052
  if (branchesBudget > MAX_BRANCHES) return null;
6324
- const truth = evalCondition2(s.cond, env);
6053
+ const truth = evalCondition2(s.cond, env2);
6325
6054
  if (truth === null) return null;
6326
6055
  const chosen = truth ? s.thenBody : s.elseBody;
6327
6056
  if (chosen === null) return null;
@@ -6798,6 +6527,7 @@ var RecursiveTracker = class {
6798
6527
  };
6799
6528
 
6800
6529
  // src/engine/script-content-analyzer.ts
6530
+ init_rules_loader();
6801
6531
  function emptyChain() {
6802
6532
  return {
6803
6533
  nodes: [],
@@ -6814,87 +6544,22 @@ function emptyChain() {
6814
6544
  error: ""
6815
6545
  };
6816
6546
  }
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
- };
6547
+ var SCRIPT_RULES2 = loadScriptRules();
6548
+ if (!SCRIPT_RULES2 || Object.keys(SCRIPT_RULES2).length === 0) {
6549
+ console.error(
6550
+ "[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"
6551
+ );
6552
+ }
6553
+ var SUSPICIOUS_PATTERNS2 = (SCRIPT_RULES2.suspicious_patterns ?? []).map((r) => ({ name: r.name, pattern: compileRuleRegex(r) }));
6554
+ var SCRIPT_EXEC_PATTERNS2 = ((SCRIPT_RULES2.script_exec_patterns ?? {}).chain ?? []).map((r) => compileRuleRegex(r));
6555
+ var KEYWORD_SETS2 = SCRIPT_RULES2.keyword_sets ?? {};
6556
+ var KEYWORD_COMBINATION_RISKS2 = (SCRIPT_RULES2.keyword_combination_risks ?? []).map((r) => ({
6557
+ group1: resolveKeywordSet(r.group1, KEYWORD_SETS2),
6558
+ group2: resolveKeywordSet(r.group2, KEYWORD_SETS2),
6559
+ level: String(r.level),
6560
+ reason: String(r.reason)
6561
+ }));
6562
+ var SINGLE_KEYWORD_RISKS2 = SCRIPT_RULES2.single_keyword_risks ?? {};
6898
6563
  var LEVEL_ORDER2 = { low: 1, medium: 2, high: 3, critical: 4 };
6899
6564
  function scanScriptForKeywords2(content) {
6900
6565
  const matched = [];
@@ -6911,10 +6576,10 @@ function assessKeywordCombinationRisk2(matchedKeywords, scriptContent) {
6911
6576
  const hitGroup2 = rule.group2.size === 0 ? true : [...rule.group2].some((k) => keywordSet.has(k));
6912
6577
  if (hitGroup1 && hitGroup2) return { level: rule.level, reason: rule.reason };
6913
6578
  }
6914
- const networkPostKeywords = /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]);
6579
+ const networkPostKeywords = new Set(KEYWORD_SETS2.network_post ?? []);
6915
6580
  const hasNetworkPost = [...networkPostKeywords].some((k) => keywordSet.has(k));
6916
6581
  if (hasNetworkPost) {
6917
- const envPatterns = ["$env", "$(env)", "$USER", "$(hostname)", "$(pwd)", "$HOME"];
6582
+ const envPatterns = SCRIPT_RULES2.env_exfil_patterns ?? [];
6918
6583
  const contentLower = scriptContent.toLowerCase();
6919
6584
  for (const p of envPatterns) {
6920
6585
  if (contentLower.includes(p.toLowerCase())) {
@@ -6990,7 +6655,7 @@ function extractInlineScriptPath(actionDetail) {
6990
6655
  if (looksLikeScriptPath(tok)) {
6991
6656
  try {
6992
6657
  const resolved = resolvePath2(tok.replace(/^['"]|['"]$/g, ""));
6993
- if (existsSync10(resolved)) {
6658
+ if (existsSync11(resolved)) {
6994
6659
  const stat = statSync4(resolved);
6995
6660
  if (stat.isFile()) return resolved;
6996
6661
  }
@@ -7004,7 +6669,7 @@ function extractInlineScriptPath(actionDetail) {
7004
6669
  const p = m[1];
7005
6670
  try {
7006
6671
  const resolved = resolvePath2(p);
7007
- if (existsSync10(resolved)) {
6672
+ if (existsSync11(resolved)) {
7008
6673
  const stat = statSync4(resolved);
7009
6674
  if (stat.isFile()) return resolved;
7010
6675
  }
@@ -7020,7 +6685,7 @@ function extractInlineScriptPath(actionDetail) {
7020
6685
  let resolved;
7021
6686
  try {
7022
6687
  resolved = resolvePath2(p);
7023
- if (existsSync10(resolved)) {
6688
+ if (existsSync11(resolved)) {
7024
6689
  const stat = statSync4(resolved);
7025
6690
  if (stat.isFile()) return resolved;
7026
6691
  }
@@ -7061,9 +6726,9 @@ function shlexSplit(s) {
7061
6726
  function resolvePath2(p) {
7062
6727
  let expanded = p;
7063
6728
  if (expanded.startsWith("~/") || expanded === "~") {
7064
- expanded = join6(homedir3(), expanded.slice(1));
6729
+ expanded = join10(homedir4(), expanded.slice(1));
7065
6730
  }
7066
- return resolve9(expanded);
6731
+ return resolve10(expanded);
7067
6732
  }
7068
6733
  function scanExtensionPatterns(ext, content) {
7069
6734
  const pats = ext?.extra_command_patterns;
@@ -7184,8 +6849,8 @@ function analyzeScriptContent2(input, ext) {
7184
6849
  resolvedPath = inline;
7185
6850
  isInline = true;
7186
6851
  }
7187
- if (resolvedPath && !isAbsolute2(resolvedPath) && !resolvedPath.startsWith("~/")) {
7188
- resolvedPath = join6(input.cwd || process.cwd(), resolvedPath);
6852
+ if (resolvedPath && !isAbsolute3(resolvedPath) && !resolvedPath.startsWith("~/")) {
6853
+ resolvedPath = join10(input.cwd || process.cwd(), resolvedPath);
7189
6854
  }
7190
6855
  const l1Adapter = (content) => {
7191
6856
  const j = scanDangerousCommandPatterns("bash", content, input.osType);
@@ -7272,7 +6937,7 @@ ${contentPreview}
7272
6937
  }
7273
6938
 
7274
6939
  // src/engine/llm-analyzer.ts
7275
- import { appendFileSync } from "fs";
6940
+ import { openSync as openSync2, writeSync as writeSync2, closeSync as closeSync2, constants as fsConstants } from "fs";
7276
6941
  import OpenAI from "openai";
7277
6942
  var llmCache = /* @__PURE__ */ new Map();
7278
6943
  var LLM_CACHE_MAX = 200;
@@ -7290,7 +6955,15 @@ async function runLLMAnalysis(input, previous) {
7290
6955
  };
7291
6956
  }
7292
6957
  const globalConfig = getLLMConfig();
7293
- const config = input.llmConfig?.apiKey ? { ...globalConfig, ...input.llmConfig } : globalConfig;
6958
+ let perRequest = input.llmConfig;
6959
+ if (perRequest?.apiKey) {
6960
+ const [okUrl, why] = validateLlmBaseUrl(String(perRequest.baseUrl || ""));
6961
+ if (!okUrl) {
6962
+ console.warn("[AgentMoss] \u5FFD\u7565\u4E0D\u88AB\u5141\u8BB8\u7684 per-request llmConfig.baseUrl:", why);
6963
+ perRequest = { ...perRequest, baseUrl: void 0 };
6964
+ }
6965
+ }
6966
+ const config = perRequest?.apiKey ? { ...globalConfig, ...perRequest } : globalConfig;
7294
6967
  const rawDetail = input.aNext.actionDetail.replace(/^terminal-\w+:\s*command=([^,]+).*/, "$1").trim();
7295
6968
  const cacheKey = `${input.aNext.actionType}:${rawDetail || input.aNext.actionDetail}`;
7296
6969
  const cached2 = llmCache.get(cacheKey);
@@ -7319,6 +6992,10 @@ async function runLLMAnalysis(input, previous) {
7319
6992
  }
7320
6993
  function logPrompt(prompt, aNext, logPath) {
7321
6994
  if (!logPath) return;
6995
+ if (!pathWithin(logPath, logRoots())) {
6996
+ console.warn("[AgentMoss] \u5FFD\u7565\u8D8A\u754C\u7684 llm_log_path\uFF08\u4E0D\u5728\u5141\u8BB8\u6839\u76EE\u5F55\u5185\uFF09:", logPath);
6997
+ return;
6998
+ }
7322
6999
  try {
7323
7000
  const actionType = aNext.actionType || "unknown";
7324
7001
  const actionDetail = (aNext.actionDetail || "").slice(0, 100);
@@ -7326,7 +7003,14 @@ function logPrompt(prompt, aNext, logPath) {
7326
7003
  ${prompt}
7327
7004
  --- END PROMPT ---
7328
7005
  `;
7329
- appendFileSync(logPath, line, "utf-8");
7006
+ let flags = fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_APPEND;
7007
+ if (fsConstants.O_NOFOLLOW) flags |= fsConstants.O_NOFOLLOW;
7008
+ const fd = openSync2(logPath, flags, 384);
7009
+ try {
7010
+ writeSync2(fd, line);
7011
+ } finally {
7012
+ closeSync2(fd);
7013
+ }
7330
7014
  } catch (e) {
7331
7015
  console.warn("[AgentMoss] Failed to write LLM prompt to log:", e);
7332
7016
  }
@@ -7485,7 +7169,7 @@ async function analyzeWithLLM(prompt, config) {
7485
7169
  }
7486
7170
  if (attempt < maxRetries) {
7487
7171
  console.error(`[AgentMoss] LLM call failed (attempt ${attempt + 1}/${maxRetries + 1}), retrying in ${retryCfg.retryInterval}s:`, err.message);
7488
- await new Promise((resolve10) => setTimeout(resolve10, retryCfg.retryInterval * 1e3));
7172
+ await new Promise((resolve11) => setTimeout(resolve11, retryCfg.retryInterval * 1e3));
7489
7173
  } else {
7490
7174
  clearTimeout(totalTimeoutTimer);
7491
7175
  console.error("[AgentMoss] LLM call failed after all retries:", err.message);
@@ -7508,10 +7192,10 @@ init_esm_shims();
7508
7192
 
7509
7193
  // src/brain/rule-store.ts
7510
7194
  init_esm_shims();
7511
- import { existsSync as existsSync12, readFileSync as readFileSync12, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
7512
- import { join as join7 } from "path";
7513
- import { homedir as homedir4 } from "os";
7514
- var DEFAULT_RULES_PATH = join7(homedir4(), ".agentmoss", "brain", "rules.json");
7195
+ import { existsSync as existsSync13, readFileSync as readFileSync13, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "fs";
7196
+ import { join as join11 } from "path";
7197
+ import { homedir as homedir5 } from "os";
7198
+ var DEFAULT_RULES_PATH = join11(homedir5(), ".agentmoss", "brain", "rules.json");
7515
7199
  var ENV_RULES_PATH = "AGENT_MOSS_BRAIN_RULES_PATH";
7516
7200
  var STATUS_DRAFT = "draft";
7517
7201
  var STATUS_APPROVED = "approved";
@@ -7540,11 +7224,11 @@ var BrainRuleStore = class {
7540
7224
  this.data = this.load();
7541
7225
  }
7542
7226
  load() {
7543
- if (!existsSync12(this.path)) {
7227
+ if (!existsSync13(this.path)) {
7544
7228
  return { version: 1, rules: [], pattern_candidates: [] };
7545
7229
  }
7546
7230
  try {
7547
- const raw = JSON.parse(readFileSync12(this.path, "utf-8"));
7231
+ const raw = JSON.parse(readFileSync13(this.path, "utf-8"));
7548
7232
  return {
7549
7233
  version: raw.version || 1,
7550
7234
  rules: raw.rules || [],
@@ -7556,7 +7240,7 @@ var BrainRuleStore = class {
7556
7240
  }
7557
7241
  save() {
7558
7242
  const dir = this.path.substring(0, this.path.lastIndexOf("/"));
7559
- if (dir) mkdirSync3(dir, { recursive: true });
7243
+ if (dir) mkdirSync4(dir, { recursive: true });
7560
7244
  writeFileSync3(this.path, JSON.stringify(this.data, null, 2), "utf-8");
7561
7245
  }
7562
7246
  addRule(rule) {
@@ -8124,11 +7808,11 @@ function parseReview(content) {
8124
7808
 
8125
7809
  // src/brain/skill-manager.ts
8126
7810
  init_esm_shims();
8127
- import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync13, renameSync, writeFileSync as writeFileSync4 } from "fs";
8128
- import { homedir as homedir5 } from "os";
8129
- import { join as join8 } from "path";
7811
+ import { existsSync as existsSync14, mkdirSync as mkdirSync5, readFileSync as readFileSync14, renameSync, writeFileSync as writeFileSync4 } from "fs";
7812
+ import { homedir as homedir6 } from "os";
7813
+ import { join as join12 } from "path";
8130
7814
  import { randomUUID } from "crypto";
8131
- var DEFAULT_SKILL_META_PATH = join8(homedir5(), ".agentmoss", "brain", "skill_meta.json");
7815
+ var DEFAULT_SKILL_META_PATH = join12(homedir6(), ".agentmoss", "brain", "skill_meta.json");
8132
7816
  var ENV_SKILL_META_PATH = "AGENT_MOSS_BRAIN_SKILL_META_PATH";
8133
7817
  function getMetaPath(override) {
8134
7818
  return override || process.env[ENV_SKILL_META_PATH] || DEFAULT_SKILL_META_PATH;
@@ -8138,7 +7822,7 @@ var BrainSkillManager = class {
8138
7822
  this.skillsDir = skillsDir;
8139
7823
  this.metaPath = getMetaPath(metaPath);
8140
7824
  try {
8141
- mkdirSync4(join8(this.metaPath, ".."), { recursive: true });
7825
+ mkdirSync5(join12(this.metaPath, ".."), { recursive: true });
8142
7826
  } catch {
8143
7827
  }
8144
7828
  this.load();
@@ -8148,9 +7832,9 @@ var BrainSkillManager = class {
8148
7832
  skills = [];
8149
7833
  metaPath;
8150
7834
  load() {
8151
- if (!existsSync13(this.metaPath)) return;
7835
+ if (!existsSync14(this.metaPath)) return;
8152
7836
  try {
8153
- const data = JSON.parse(readFileSync13(this.metaPath, "utf-8"));
7837
+ const data = JSON.parse(readFileSync14(this.metaPath, "utf-8"));
8154
7838
  this.skills = data.skills || [];
8155
7839
  } catch {
8156
7840
  this.skills = [];
@@ -8173,7 +7857,7 @@ var BrainSkillManager = class {
8173
7857
  let changed = false;
8174
7858
  for (const sk of this.skills) {
8175
7859
  if (sk.status !== "active") continue;
8176
- if (!existsSync13(join8(this.skillsDir, `${sk.name}.md`))) {
7860
+ if (!existsSync14(join12(this.skillsDir, `${sk.name}.md`))) {
8177
7861
  sk.status = "stale";
8178
7862
  changed = true;
8179
7863
  }
@@ -8184,9 +7868,9 @@ var BrainSkillManager = class {
8184
7868
  addSkill(skill) {
8185
7869
  const id = skill.id || randomUUID();
8186
7870
  const name = skill.name || `brain_${id.slice(0, 8)}`;
8187
- if (!existsSync13(this.skillsDir)) return "";
8188
- const mdPath = join8(this.skillsDir, `${name}.md`);
8189
- if (existsSync13(mdPath)) return "";
7871
+ if (!existsSync14(this.skillsDir)) return "";
7872
+ const mdPath = join12(this.skillsDir, `${name}.md`);
7873
+ if (existsSync14(mdPath)) return "";
8190
7874
  try {
8191
7875
  writeFileSync4(mdPath, skill.content, "utf-8");
8192
7876
  } catch {
@@ -8235,12 +7919,12 @@ var BrainSkillManager = class {
8235
7919
  const out = [];
8236
7920
  for (const sk of this.skills) {
8237
7921
  if (sk.origin_node !== "local" || sk.status !== "active") continue;
8238
- const mdPath = join8(this.skillsDir, `${sk.name}.md`);
8239
- if (!existsSync13(mdPath)) continue;
7922
+ const mdPath = join12(this.skillsDir, `${sk.name}.md`);
7923
+ if (!existsSync14(mdPath)) continue;
8240
7924
  out.push({
8241
7925
  name: sk.name,
8242
7926
  category: sk.category || "",
8243
- content: readFileSync13(mdPath, "utf-8"),
7927
+ content: readFileSync14(mdPath, "utf-8"),
8244
7928
  keywords: sk.keywords || []
8245
7929
  });
8246
7930
  }
@@ -8321,9 +8005,9 @@ var BrainCurator = class {
8321
8005
  // src/brain/contributor.ts
8322
8006
  init_esm_shims();
8323
8007
  init_asset_dir();
8324
- import { dirname as dirname9 } from "path";
8008
+ import { dirname as dirname10 } from "path";
8325
8009
  import { fileURLToPath as fileURLToPath9 } from "url";
8326
- var __dirname8 = dirname9(fileURLToPath9(import.meta.url));
8010
+ var __dirname8 = dirname10(fileURLToPath9(import.meta.url));
8327
8011
 
8328
8012
  // src/brain/index.ts
8329
8013
  function checkBrainRules(store, opts) {
@@ -8387,6 +8071,7 @@ init_rules_loader();
8387
8071
 
8388
8072
  // src/engine/formal-engine/decode.ts
8389
8073
  init_esm_shims();
8074
+ init_rules_loader();
8390
8075
  var B64_TOKEN_RE = /[A-Za-z0-9+/]+={0,2}/g;
8391
8076
  function isValidBase64Token(tok) {
8392
8077
  if (tok.length < 8) return false;
@@ -8421,7 +8106,7 @@ function decodeBase64Literals(text) {
8421
8106
  return out;
8422
8107
  }
8423
8108
  var QUOTED_STR_RE = /('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`)/g;
8424
- var OUTPUT_CMDS = /* @__PURE__ */ new Set(["echo", "printf", "print"]);
8109
+ var OUTPUT_CMDS = new Set(loadFormalRules().output_commands ?? []);
8425
8110
  function stripStringLiterals(text) {
8426
8111
  const m = text.match(/\s*([A-Za-z_][\w-]*|\/?[\w./-]+)\b/);
8427
8112
  if (!m) return text;
@@ -8675,7 +8360,7 @@ function markIntegrity(actionDetail) {
8675
8360
  }
8676
8361
  return { integrity: "medium", isLowSource: false };
8677
8362
  }
8678
- function initNode(sessionId, seq, actionType, actionDetail) {
8363
+ function initNode(sessionId, seq, actionType, actionDetail, timestamp) {
8679
8364
  const { integrity, isLowSource } = markIntegrity(actionDetail);
8680
8365
  return {
8681
8366
  sessionId,
@@ -8685,7 +8370,8 @@ function initNode(sessionId, seq, actionType, actionDetail) {
8685
8370
  stage: markStage(actionDetail),
8686
8371
  resources: extractResources(actionDetail),
8687
8372
  integrity,
8688
- isLowSource
8373
+ isLowSource,
8374
+ timestamp: timestamp || void 0
8689
8375
  };
8690
8376
  }
8691
8377
  function buildSessionGraph(actionHistory, aNext, sessionId = "current") {
@@ -8695,13 +8381,21 @@ function buildSessionGraph(actionHistory, aNext, sessionId = "current") {
8695
8381
  const details = [];
8696
8382
  for (const a of actionHistory) {
8697
8383
  const ad = String(a.actionDetail || "");
8698
- if (ad) details.push({ actionType: String(a.actionType || ""), actionDetail: ad });
8384
+ if (ad) details.push({
8385
+ actionType: String(a.actionType || ""),
8386
+ actionDetail: ad,
8387
+ calledAt: a.calledAt || void 0
8388
+ });
8699
8389
  }
8700
8390
  if (aNext) {
8701
- details.push({ actionType: String(aNext.actionType || ""), actionDetail: String(aNext.actionDetail || "") });
8391
+ details.push({
8392
+ actionType: String(aNext.actionType || ""),
8393
+ actionDetail: String(aNext.actionDetail || ""),
8394
+ calledAt: aNext.calledAt || void 0
8395
+ });
8702
8396
  }
8703
8397
  details.forEach((d, seq) => {
8704
- const node = initNode(sessionId, seq, d.actionType, d.actionDetail);
8398
+ const node = initNode(sessionId, seq, d.actionType, d.actionDetail, d.calledAt);
8705
8399
  nodes.push(node);
8706
8400
  if (node.stage) stageFlags.add(node.stage);
8707
8401
  });
@@ -8727,7 +8421,8 @@ function buildMultiSessionGraph(sessions, aNext) {
8727
8421
  last.sessionId,
8728
8422
  last.nodes.length,
8729
8423
  String(aNext.actionType || ""),
8730
- String(aNext.actionDetail || "")
8424
+ String(aNext.actionDetail || ""),
8425
+ aNext.calledAt || void 0
8731
8426
  ));
8732
8427
  if (last.nodes.length > 1) {
8733
8428
  last.edges.push({ src: last.nodes.length - 2, dst: last.nodes.length - 1, kind: "next" });
@@ -8775,6 +8470,27 @@ function allNodesFlat(mg) {
8775
8470
  // src/engine/formal-engine/properties.ts
8776
8471
  init_esm_shims();
8777
8472
  init_rules_loader();
8473
+ function parseTs(ts) {
8474
+ if (!ts) return null;
8475
+ const t = Date.parse(String(ts).trim());
8476
+ return Number.isFinite(t) ? t : null;
8477
+ }
8478
+ function privescExfilWindowSec() {
8479
+ const raw = (process.env.AGENT_MOSS_PRIVESC_EXFIL_WINDOW_SEC || "").trim();
8480
+ if (!raw) return 1800;
8481
+ const n = parseInt(raw, 10);
8482
+ return Number.isFinite(n) ? Math.max(0, n) : 1800;
8483
+ }
8484
+ function privescInWindow(nodes, indexes, exfilIdx, windowSec) {
8485
+ const before = indexes.filter((i) => i < exfilIdx);
8486
+ if (windowSec <= 0) return before;
8487
+ const exfilTs = parseTs(nodes[exfilIdx].timestamp);
8488
+ if (exfilTs === null) return before;
8489
+ return before.filter((i) => {
8490
+ const ts = parseTs(nodes[i].timestamp);
8491
+ return ts === null || exfilTs - ts <= windowSec * 1e3;
8492
+ });
8493
+ }
8778
8494
  var CROSS_SESSION_CHAINS = null;
8779
8495
  function loadCrossSessionChains() {
8780
8496
  if (CROSS_SESSION_CHAINS) return CROSS_SESSION_CHAINS;
@@ -8906,8 +8622,9 @@ function extractInlineShell(s) {
8906
8622
  }
8907
8623
  return out;
8908
8624
  }
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"]);
8625
+ var FORMAL_RULES = loadFormalRules();
8626
+ var PKG_MGR = new Set(FORMAL_RULES.privesc?.package_managers ?? []);
8627
+ var SHELLS = new Set(FORMAL_RULES.privesc?.bare_shells ?? []);
8911
8628
  function containsPrivesc(shell) {
8912
8629
  if (!shell) return false;
8913
8630
  for (const seg of shell.split(/\&\&|\|\||;|\n/)) {
@@ -8976,6 +8693,7 @@ function checkNeverExfilAfterPrivesc(graph) {
8976
8693
  const nodes = allNodes(graph);
8977
8694
  if (nodes.length < 2) return violations;
8978
8695
  const witness = graph.witnessIdx;
8696
+ const windowSec = privescExfilWindowSec();
8979
8697
  if (witness !== void 0) {
8980
8698
  const evIdx = [];
8981
8699
  nodes.forEach((n, i) => {
@@ -8983,15 +8701,17 @@ function checkNeverExfilAfterPrivesc(graph) {
8983
8701
  });
8984
8702
  if (evIdx.length === 0) return violations;
8985
8703
  const firstEv = Math.min(...evIdx);
8986
- let pre = null;
8987
- for (let i = 0; i < firstEv; i++) {
8988
- if (isPrivescNode(nodes[i])) pre = nodes[i];
8989
- }
8990
- if (pre) {
8704
+ const privescIdx = [];
8705
+ nodes.forEach((n, i) => {
8706
+ if (isPrivescNode(n)) privescIdx.push(i);
8707
+ });
8708
+ const candidates = privescInWindow(nodes, privescIdx, firstEv, windowSec);
8709
+ if (candidates.length > 0) {
8710
+ const pre = nodes[candidates[candidates.length - 1]];
8991
8711
  const post = nodes[firstEv];
8992
8712
  return [{
8993
8713
  propertyId: "no-exfil-after-privesc",
8994
- reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
8714
+ reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\u25C7EXFIL) \u7A97\u53E3\u7EA6\u675F",
8995
8715
  riskLevel: "critical",
8996
8716
  path: [
8997
8717
  { sessionId: pre.sessionId, seq: pre.seq, detail: pre.actionDetail.slice(0, 60) },
@@ -9004,18 +8724,18 @@ function checkNeverExfilAfterPrivesc(graph) {
9004
8724
  for (let i = 0; i < nodes.length; i++) {
9005
8725
  if (!isPrivescNode(nodes[i])) continue;
9006
8726
  for (let j = i + 1; j < nodes.length; j++) {
9007
- if (isExfilNode(nodes[j])) {
9008
- violations.push({
9009
- propertyId: "no-exfil-after-privesc",
9010
- reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
9011
- riskLevel: "critical",
9012
- path: [
9013
- { sessionId: nodes[i].sessionId, seq: nodes[i].seq, detail: nodes[i].actionDetail.slice(0, 60) },
9014
- { sessionId: nodes[j].sessionId, seq: nodes[j].seq, detail: nodes[j].actionDetail.slice(0, 60) }
9015
- ]
9016
- });
9017
- return violations;
9018
- }
8727
+ if (!isExfilNode(nodes[j])) continue;
8728
+ if (privescInWindow(nodes, [i], j, windowSec).length === 0) continue;
8729
+ violations.push({
8730
+ propertyId: "no-exfil-after-privesc",
8731
+ reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\u25C7EXFIL) \u7A97\u53E3\u7EA6\u675F",
8732
+ riskLevel: "critical",
8733
+ path: [
8734
+ { sessionId: nodes[i].sessionId, seq: nodes[i].seq, detail: nodes[i].actionDetail.slice(0, 60) },
8735
+ { sessionId: nodes[j].sessionId, seq: nodes[j].seq, detail: nodes[j].actionDetail.slice(0, 60) }
8736
+ ]
8737
+ });
8738
+ return violations;
9019
8739
  }
9020
8740
  }
9021
8741
  return violations;
@@ -9080,7 +8800,7 @@ function lookupToolClass(actionType) {
9080
8800
  return entry.actionClass;
9081
8801
  }
9082
8802
  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"]);
8803
+ var STRONG_COMPUTE_TYPES = new Set(FORMAL_RULES.compute_action_types ?? []);
9084
8804
  function isComputeNode(n) {
9085
8805
  if (STRONG_COMPUTE_TYPES.has(n.actionType.toLowerCase())) return true;
9086
8806
  const cls = lookupToolClass(n.actionType);
@@ -9182,6 +8902,11 @@ function checkCrossSessionReachability(graph) {
9182
8902
  }
9183
8903
  return violations;
9184
8904
  }
8905
+ var DATA_SOURCE_READ_RE = /\b(?:cat|head|tail|less|more|sed|awk|grep|strings|base64|xxd|od|dd)\b|\b(?:open|read_text|read_bytes)\s*\(/i;
8906
+ function isDataSourceNode(n) {
8907
+ if (n.stage === "CRED" || n.isLowSource) return true;
8908
+ return DATA_SOURCE_READ_RE.test(n.actionDetail);
8909
+ }
9185
8910
  function checkSensitiveDataAggregation(graph) {
9186
8911
  const violations = [];
9187
8912
  const nodes = allNodes(graph);
@@ -9201,6 +8926,7 @@ function checkSensitiveDataAggregation(graph) {
9201
8926
  for (const r of n.resources) {
9202
8927
  for (const j of resIndex.get(r) || []) {
9203
8928
  if (j === exfilIdx) continue;
8929
+ if (!isDataSourceNode(nodes[j])) continue;
9204
8930
  srcs.add(`${nodes[j].sessionId}#${nodes[j].seq}`);
9205
8931
  }
9206
8932
  }
@@ -9306,25 +9032,7 @@ import * as path4 from "path";
9306
9032
  import { createHash } from "crypto";
9307
9033
 
9308
9034
  // 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
9035
  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
9036
  function whitelistTier(command, fullySafe, readonlySensitive) {
9329
9037
  const segments = splitCommandSegments(command);
9330
9038
  if (segments.length === 0) return "";
@@ -9481,7 +9189,7 @@ async function runAnalysis(input) {
9481
9189
  const actionType = input.aNext.actionType.toLowerCase();
9482
9190
  const rawDetail = extractRawCommand(input.aNext.actionDetail);
9483
9191
  const lowerDetail = rawDetail.toLowerCase();
9484
- if (FULLY_SAFE_TOOLS.has(actionType)) {
9192
+ if (getPlatformTraits(input.osType).safeActionTypes.has(actionType)) {
9485
9193
  return {
9486
9194
  allowed: true,
9487
9195
  reason: `\u5B8C\u5168\u5B89\u5168\u5DE5\u5177\uFF0C\u8DF3\u8FC7\u6DF1\u5EA6\u5206\u6790: ${actionType}`,
@@ -9524,7 +9232,7 @@ async function runAnalysis(input) {
9524
9232
  violatedLayers: []
9525
9233
  };
9526
9234
  }
9527
- let skipLLM = READONLY_SENSITIVE_TOOLS.has(actionType);
9235
+ let skipLLM = getPlatformTraits(input.osType).readonlySensitiveActionTypes.has(actionType);
9528
9236
  if (actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash") {
9529
9237
  const firstCmd = extractFirstCommand(lowerDetail);
9530
9238
  const { fullySafeCommands, readonlySensitiveCommands } = getPlatformTraits(input.osType);