@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.
package/dist/server.js CHANGED
@@ -1,5 +1,11 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
+ }) : x)(function(x) {
6
+ if (typeof require !== "undefined") return require.apply(this, arguments);
7
+ throw Error('Dynamic require of "' + x + '" is not supported');
8
+ });
3
9
  var __esm = (fn, res) => function __init() {
4
10
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
11
  };
@@ -25,6 +31,14 @@ var init_esm_shims = __esm({
25
31
  import { join } from "path";
26
32
  import { homedir } from "os";
27
33
  import { readFileSync } from "fs";
34
+ function isSafeId(value) {
35
+ if (!value || value.length > ID_MAX_LEN) return false;
36
+ return ID_PATTERN.test(value);
37
+ }
38
+ function getRulesRoot() {
39
+ const raw = (process.env[ENV_RULES_ROOT] ?? "").trim();
40
+ return raw ? raw : null;
41
+ }
28
42
  function isRoot() {
29
43
  const getuid = process.getuid;
30
44
  return typeof getuid === "function" && getuid() === 0;
@@ -94,7 +108,7 @@ function getTokenStatsPath() {
94
108
  }
95
109
  return join(getDataRoot(), "agent_moss_token_stats.json");
96
110
  }
97
- var ENV_DATA_ROOT, ENV_INSTANCE, DEFAULT_DATA_ROOT;
111
+ var ENV_DATA_ROOT, ENV_INSTANCE, DEFAULT_DATA_ROOT, ENV_RULES_ROOT, ID_MAX_LEN, ID_PATTERN;
98
112
  var init_paths = __esm({
99
113
  "src/infra/paths.ts"() {
100
114
  "use strict";
@@ -102,295 +116,9 @@ var init_paths = __esm({
102
116
  ENV_DATA_ROOT = "AGENT_MOSS_DATA_DIR";
103
117
  ENV_INSTANCE = "AGENT_MOSS_INSTANCE";
104
118
  DEFAULT_DATA_ROOT = join(homedir(), ".config", "agentmoss");
105
- }
106
- });
107
-
108
- // src/engine/shell-split.ts
109
- function hasCommandSubstitution(text) {
110
- return CMD_SUBSTITUTION.test(text);
111
- }
112
- function splitCommandSegments(command) {
113
- const segments = [];
114
- let cur = "";
115
- let quote = null;
116
- for (let i = 0; i < command.length; i++) {
117
- const ch = command[i];
118
- if (quote) {
119
- cur += ch;
120
- if (ch === quote) quote = null;
121
- continue;
122
- }
123
- if (ch === "'" || ch === '"') {
124
- quote = ch;
125
- cur += ch;
126
- continue;
127
- }
128
- if (/\||&|;|\n/.test(ch)) {
129
- segments.push(cur);
130
- segments.push(ch);
131
- cur = "";
132
- continue;
133
- }
134
- cur += ch;
135
- }
136
- if (cur) segments.push(cur);
137
- return segments.map((s) => s.trim()).filter((s) => s.length > 0 && !/^(?:\|\||&&|[|;&])$/.test(s));
138
- }
139
- function extractFirstCommand(command) {
140
- const segments = splitCommandSegments(command);
141
- return segments.length ? segments[0] : "";
142
- }
143
- var CMD_SUBSTITUTION;
144
- var init_shell_split = __esm({
145
- "src/engine/shell-split.ts"() {
146
- "use strict";
147
- init_esm_shims();
148
- CMD_SUBSTITUTION = /\$\(|`|</;
149
- }
150
- });
151
-
152
- // src/engine/inline-analyzer.ts
153
- function isLiteralTextCommand(command) {
154
- const segments = splitCommandSegments(command);
155
- if (segments.length === 0 || segments.some((s) => !ECHO_SEG_RE.test(s))) return false;
156
- if (command.includes(">")) {
157
- let d = command.replace(/<=>|>=|<=|<>|=>/g, " ");
158
- d = d.replace(/(?:2|&|1)?\s*>\s*\/dev\/null\b/g, "");
159
- d = d.replace(new RegExp(`>{1,2}\\s*(['"])([~./][^'"]+)\\1`, "g"), (_m, _q, p) => ">" + p);
160
- d = d.replace(/'[^']*'|"[^"]*"/g, "");
161
- if (/>{1,2}\s*(?!&)\S/.test(d)) {
162
- return false;
163
- }
164
- }
165
- if (/\|\s*\b(?:tee|tac|install)\b/i.test(command)) {
166
- return false;
167
- }
168
- return true;
169
- }
170
- function extractInlineCode(command) {
171
- const parts = [];
172
- let m;
173
- INLINE_CODE_RE.lastIndex = 0;
174
- while ((m = INLINE_CODE_RE.exec(command)) !== null) {
175
- const code = m[1] ?? m[2];
176
- if (code) parts.push(code);
177
- }
178
- ECHO_STRING_RE.lastIndex = 0;
179
- while ((m = ECHO_STRING_RE.exec(command)) !== null) {
180
- const code = m[1] ?? m[2];
181
- if (code) parts.push(code);
182
- }
183
- return parts.join("\n");
184
- }
185
- function hasWriteExecChain(command) {
186
- return WRITE_EXEC_CHAIN_PATTERNS.some((p) => {
187
- p.lastIndex = 0;
188
- return p.test(command);
189
- });
190
- }
191
- function hasDirTraversal(code) {
192
- const lower = code.toLowerCase();
193
- return DIR_TRAVERSAL_KEYWORDS.some((kw) => lower.includes(kw));
194
- }
195
- function hasReadWriteKeyword(code) {
196
- const lower = code.toLowerCase();
197
- return READ_WRITE_KEYWORDS.some((kw) => lower.includes(kw));
198
- }
199
- function analyzeInlineScript(command) {
200
- const code = extractInlineCode(command);
201
- if (!code) {
202
- return { actionable: false, allowed: true, reason: "", riskLevel: "low", riskType: "" };
203
- }
204
- if (hasWriteExecChain(command)) {
205
- return {
206
- actionable: true,
207
- allowed: false,
208
- reason: "\u5185\u8054\u811A\u672C\u542B\u5199\u5165\u6587\u4EF6\u540E\u6267\u884C\u7684\u6A21\u5F0F\uFF0C\u7591\u4F3C\u7ED5\u8FC7\u654F\u611F\u8DEF\u5F84\u68C0\u6D4B",
209
- riskLevel: "critical",
210
- riskType: "script_execution"
211
- };
212
- }
213
- const redirMatch = command.match(REDIRECT_TO_SENSITIVE_RE);
214
- if (redirMatch) {
215
- return {
216
- actionable: true,
217
- allowed: false,
218
- reason: `\u91CD\u5B9A\u5411\u5199\u5165\u654F\u611F\u8DEF\u5F84: ${redirMatch[1]}`,
219
- riskLevel: "critical",
220
- riskType: "file_access"
221
- };
222
- }
223
- const isEchoOrPrintf1 = isLiteralTextCommand(command);
224
- if (!isEchoOrPrintf1 && hasDirTraversal(code) && hasReadWriteKeyword(code)) {
225
- return { actionable: false, allowed: true, reason: "", riskLevel: "low", riskType: "" };
226
- }
227
- const isEchoOrPrintf = isLiteralTextCommand(command);
228
- if (!isEchoOrPrintf && hasReadWriteKeyword(code)) {
229
- return {
230
- actionable: true,
231
- allowed: false,
232
- reason: "\u5185\u8054\u811A\u672C\u542B\u654F\u611F\u8DEF\u5F84\u4E14\u542B\u6587\u4EF6\u8BFB\u5199/\u547D\u4EE4\u6267\u884C\u8C03\u7528\uFF0C\u5224\u4E3A\u771F\u5B9E\u8BBF\u95EE",
233
- riskLevel: "critical",
234
- riskType: "file_access"
235
- };
236
- }
237
- return {
238
- actionable: true,
239
- allowed: true,
240
- reason: "\u654F\u611F\u8DEF\u5F84\u4EC5\u4F5C\u4E3A\u6587\u672C\u5B57\u9762\u91CF\u51FA\u73B0\uFF0C\u672A\u4F34\u968F\u8BFB\u5199\u64CD\u4F5C",
241
- riskLevel: "low",
242
- riskType: "file_access"
243
- };
244
- }
245
- var INLINE_CODE_RE, ECHO_STRING_RE, ECHO_SEG_RE, READ_WRITE_KEYWORDS, WRITE_EXEC_CHAIN_PATTERNS, SHELL_RC_FRAGMENTS, SENSITIVE_PATH_FRAGMENTS, REDIRECT_TO_SENSITIVE_RE, DIR_TRAVERSAL_KEYWORDS;
246
- var init_inline_analyzer = __esm({
247
- "src/engine/inline-analyzer.ts"() {
248
- "use strict";
249
- init_esm_shims();
250
- init_shell_split();
251
- INLINE_CODE_RE = /(?:python|python3|perl|perl5|ruby|jruby|mruby|node|php|lua)\s+(?:-[a-zA-Z]*[cera][a-zA-Z]*\s+)"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
252
- ECHO_STRING_RE = /\b(?:echo|printf)\s+"([^"\\]*(?:\\.[^"\\]*)*)"|\b(?:echo|printf)\s+'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
253
- ECHO_SEG_RE = /^\s*(?:echo|printf)(?:\s|$)/i;
254
- READ_WRITE_KEYWORDS = [
255
- // 函数调用形态(关键词后跟 '(')
256
- "open(",
257
- "read(",
258
- "write(",
259
- "fopen(",
260
- "system(",
261
- "exec(",
262
- "eval(",
263
- "popen(",
264
- "load(",
265
- "dump(",
266
- // 文件读写(组合形态)
267
- "readline",
268
- "readlines",
269
- "writelines",
270
- "getpass",
271
- "pickle.load",
272
- "yaml.load",
273
- "json.load",
274
- "json.read",
275
- "configparser",
276
- "fread",
277
- "fwrite",
278
- "fgets",
279
- // 子进程执行(组合形态)
280
- "os.system",
281
- "os.popen",
282
- "subprocess",
283
- "os.exec",
284
- "os.spawn",
285
- "import os",
286
- "import subprocess",
287
- "shell=true",
288
- "check_output",
289
- "check_call",
290
- "run(",
291
- // 绕过变体检测
292
- "getattr(",
293
- "__builtins__",
294
- "globals(",
295
- "locals(",
296
- // PHP
297
- "file_get_contents",
298
- "file_put_contents",
299
- "passthru",
300
- "shell_exec",
301
- "proc_open",
302
- // Perl/Ruby
303
- "<stdin>",
304
- "open my",
305
- // Node/JS(2026-09 补,与 Python _READ_WRITE_KEYWORDS 同步):历史缺口 ——
306
- // 识别表里一条 Node API 都没有,于是 node -e "require('fs').readFileSync('/etc/shadow')"
307
- // 判为纯文本字面量 → 放行(真漏报)。
308
- "readfilesync(",
309
- "writefilesync(",
310
- "appendfilesync(",
311
- "unlinksync(",
312
- "rmsync(",
313
- "mkdirsync(",
314
- "execsync(",
315
- "spawnsync(",
316
- "execfile",
317
- "createreadstream",
318
- "createwritestream",
319
- "fs.read",
320
- "fs.write",
321
- "fs.promises",
322
- "require('fs')",
323
- 'require("fs")',
324
- "require('node:fs')",
325
- 'require("node:fs")',
326
- "require('child_process')",
327
- 'require("child_process")',
328
- "require('https')",
329
- 'require("https")'
330
- ];
331
- WRITE_EXEC_CHAIN_PATTERNS = [
332
- // echo/cat/printf "..." > file && bash/sh/python file
333
- /(?:echo|cat|printf)\b.*?>\s*\S+.*(?:&&|;|\|)\s*(?:bash|sh|zsh|python\d?|perl|ruby|node|php)\s/gis,
334
- // ... | bash / | sh / | python(管道喂解释器执行)
335
- /\|\s*(?:bash|sh|zsh|python\d?|perl|ruby|node|php)\b/gi,
336
- // eval "$(cat ...)" / eval "$(echo ...)"
337
- /eval\s+"\$\(/gi,
338
- // xargs bash / xargs sh
339
- /\|\s*xargs\s+(?:bash|sh|zsh)/gi
340
- ];
341
- SHELL_RC_FRAGMENTS = [
342
- ".bashrc",
343
- ".zshrc",
344
- ".profile",
345
- ".bash_profile",
346
- ".zprofile"
347
- ];
348
- SENSITIVE_PATH_FRAGMENTS = [
349
- "/etc/shadow",
350
- "/etc/gshadow",
351
- "/etc/passwd",
352
- "/etc/sudoers",
353
- "/etc/hosts",
354
- "/etc/crontab",
355
- "/etc/systemd/",
356
- "/etc/ssh/sshd_config",
357
- "/boot/",
358
- "/proc/sys/",
359
- "/sys/",
360
- "/dev/mem",
361
- "/dev/kmsg",
362
- ".ssh/id_rsa",
363
- ".ssh/id_ed25519",
364
- ".ssh/authorized_keys",
365
- ...SHELL_RC_FRAGMENTS
366
- ];
367
- REDIRECT_TO_SENSITIVE_RE = new RegExp(`>{1,2}\\s*(?:\\S*/)?(${SENSITIVE_PATH_FRAGMENTS.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})(?:\\s|$)`, "i");
368
- DIR_TRAVERSAL_KEYWORDS = [
369
- // Python 目录遍历
370
- "os.listdir",
371
- "os.walk",
372
- "os.scandir",
373
- "glob.glob",
374
- "glob.iglob",
375
- "pathlib.",
376
- "iterdir",
377
- "rglob",
378
- "scandir(",
379
- "listdir("
380
- ];
381
- }
382
- });
383
-
384
- // src/infra/runtime-config-bridge.ts
385
- function registerRuntimeConfigBridge(bridge) {
386
- runtimeConfigBridge.current = bridge;
387
- }
388
- var runtimeConfigBridge;
389
- var init_runtime_config_bridge = __esm({
390
- "src/infra/runtime-config-bridge.ts"() {
391
- "use strict";
392
- init_esm_shims();
393
- runtimeConfigBridge = { current: null };
119
+ ENV_RULES_ROOT = "AGENT_MOSS_RULES_ROOT";
120
+ ID_MAX_LEN = 64;
121
+ ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
394
122
  }
395
123
  });
396
124
 
@@ -440,6 +168,19 @@ var init_rules_mode = __esm({
440
168
  }
441
169
  });
442
170
 
171
+ // src/infra/runtime-config-bridge.ts
172
+ function registerRuntimeConfigBridge(bridge) {
173
+ runtimeConfigBridge.current = bridge;
174
+ }
175
+ var runtimeConfigBridge;
176
+ var init_runtime_config_bridge = __esm({
177
+ "src/infra/runtime-config-bridge.ts"() {
178
+ "use strict";
179
+ init_esm_shims();
180
+ runtimeConfigBridge = { current: null };
181
+ }
182
+ });
183
+
443
184
  // src/infra/rules-loader.ts
444
185
  import { readFileSync as readFileSync2, existsSync, readdirSync, statSync } from "fs";
445
186
  import { resolve, dirname, join as join2 } from "path";
@@ -455,17 +196,38 @@ function parseDirEnv(envName) {
455
196
  function rulesDirs() {
456
197
  if (isBuiltinOnly()) return [];
457
198
  const dirs = [];
458
- for (const env of RULES_DIR_ENVS) dirs.push(...parseDirEnv(env));
199
+ for (const env2 of RULES_DIR_ENVS) dirs.push(...parseDirEnv(env2));
459
200
  dirs.push(resolve(dataRootDir(), "rules"));
460
201
  return [...new Set(dirs)];
461
202
  }
203
+ function rulesDirCands() {
204
+ const root = getRulesRoot();
205
+ if (root) return [join2(root, "rules")];
206
+ const cands = [
207
+ // 注:`<data_root>/rules/` 不再是"按内置文件名覆盖"的查找位 —— 2026-10 起
208
+ // 规则目录只认 override.json / extra.json(见 rulesDirs())。
209
+ // dist: __dirname=dist/ → dist/rules;__dirname=dist/engine/ → dist/engine/../rules=dist/rules
210
+ resolve(__dirname2, "../rules"),
211
+ resolve(__dirname2, "rules"),
212
+ // dev src: __dirname=ts/src/infra/ → ts/src/infra/../../../agent_moss/rules = 仓库根 agent_moss/rules
213
+ resolve(__dirname2, "../../../agent_moss/rules"),
214
+ // dist 但 __dirname 指向 dist/engine 再往上两级的仓库根 agent_moss/rules
215
+ resolve(__dirname2, "../../../../agent_moss/rules"),
216
+ // 相对当前工作目录的仓库根(兜底,兼容以仓库根为 cwd 运行)
217
+ resolve(process.cwd(), "agent_moss/rules")
218
+ ];
219
+ return cands.filter((c) => existsSync(c));
220
+ }
462
221
  function findRulesFile(name) {
463
- for (const dir of RULES_DIR_CANDS) {
222
+ for (const dir of rulesDirCands()) {
464
223
  const p = resolve(dir, name);
465
224
  if (existsSync(p)) return p;
466
225
  }
467
226
  return null;
468
227
  }
228
+ function resolveRulesFile(name) {
229
+ return findRulesFile(name);
230
+ }
469
231
  function loadJson(name) {
470
232
  if (!isHotReloadEnabled()) {
471
233
  const cached2 = frozenJsonCache.get(name);
@@ -482,6 +244,7 @@ function loadJson(name) {
482
244
  }
483
245
  }
484
246
  function resolveScenarioRulesFile(scenarioId) {
247
+ if (!isSafeId(scenarioId)) return null;
485
248
  const rel = join2("scenarios", scenarioId, "rules.json");
486
249
  for (const dir of rulesDirs()) {
487
250
  const p = resolve(dir, rel);
@@ -516,6 +279,74 @@ function loadL1() {
516
279
  function loadL2() {
517
280
  return loadJson("l2_rules.json");
518
281
  }
282
+ function regexFlags(flags) {
283
+ let out = "";
284
+ for (const ch of String(flags ?? "")) if (RE_FLAG_MAP[ch]) out += RE_FLAG_MAP[ch];
285
+ return out;
286
+ }
287
+ function compileRuleRegex(rule, extraFlags = "") {
288
+ return new RegExp(String(rule.pattern), regexFlags(rule.flags) + extraFlags);
289
+ }
290
+ function resolveKeywordSet(group, keywordSets) {
291
+ if (typeof group === "string") return new Set(keywordSets[group.slice(1)] ?? []);
292
+ return new Set(Array.isArray(group) ? group : []);
293
+ }
294
+ function loadFormalRules() {
295
+ return loadJson("formal_rules.json");
296
+ }
297
+ function loadLogicRules() {
298
+ return loadJson("logic_rules.json");
299
+ }
300
+ function loadHeuristicRules() {
301
+ return loadJson("heuristic_rules.json");
302
+ }
303
+ function loadScriptRules() {
304
+ return loadJson("script_rules.json");
305
+ }
306
+ function loadInline() {
307
+ return loadJson("inline_rules.json");
308
+ }
309
+ function loadPlatformTraits(osName) {
310
+ const data = loadJson("platform_traits.json");
311
+ if (!data || Object.keys(data).length === 0) {
312
+ console.error(
313
+ "[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"
314
+ );
315
+ return {};
316
+ }
317
+ const name = resolveTraitsOs(osName);
318
+ const entry = data[name];
319
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
320
+ console.error(`[AgentMoss] platform_traits.json \u91CC\u6CA1\u6709\u53EF\u7528\u7684 ${name} \u6761\u76EE \u2014\u2014 \u5E73\u53F0\u7279\u5F81\u4E3A\u7A7A\u3002`);
321
+ return {};
322
+ }
323
+ const out = {};
324
+ for (const f of TRAITS_FIELDS) if (f in entry) out[f] = entry[f];
325
+ return out;
326
+ }
327
+ function resolveTraitsOs(osName) {
328
+ const data = loadJson("platform_traits.json");
329
+ let name = String(osName || "").trim().toLowerCase() || TRAITS_OS_FALLBACK;
330
+ const seen = /* @__PURE__ */ new Set();
331
+ for (; ; ) {
332
+ if (seen.has(name)) {
333
+ console.error(`[AgentMoss] platform_traits.json \u7684 inherits \u6210\u73AF\uFF08${name}\uFF09\u2014\u2014 \u56DE\u843D linux\u3002`);
334
+ return TRAITS_OS_FALLBACK;
335
+ }
336
+ seen.add(name);
337
+ const entry = data[name];
338
+ if (entry && typeof entry === "object" && !Array.isArray(entry)) {
339
+ const nxt = entry.inherits;
340
+ if (typeof nxt === "string" && nxt.trim().toLowerCase() !== name) {
341
+ name = nxt.trim().toLowerCase();
342
+ continue;
343
+ }
344
+ return name;
345
+ }
346
+ if (name === TRAITS_OS_FALLBACK) return name;
347
+ name = TRAITS_OS_FALLBACK;
348
+ }
349
+ }
519
350
  function loadL3() {
520
351
  const data = { ...loadJson("l3_rules.json") };
521
352
  try {
@@ -850,59 +681,732 @@ function ruleFilesLoaded() {
850
681
  function ignoredRuleFiles() {
851
682
  return rulesCached().ignored;
852
683
  }
853
- var __filename2, __dirname2, OVERRIDE_FILENAME, EXTRA_FILENAME, RULES_DIR_ENVS, RULES_DIR_CANDS, frozenJsonCache, SCENARIO_LIST_KEYS, EXTRA_CATEGORIES, STRUCT_KEYS, EXTRA_FLAT_FIELD, _RULES_CACHE, warnedIgnored;
854
- var init_rules_loader = __esm({
855
- "src/infra/rules-loader.ts"() {
684
+ 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;
685
+ var init_rules_loader = __esm({
686
+ "src/infra/rules-loader.ts"() {
687
+ "use strict";
688
+ init_esm_shims();
689
+ init_paths();
690
+ init_rules_mode();
691
+ init_runtime_config_bridge();
692
+ __filename2 = fileURLToPath2(import.meta.url);
693
+ __dirname2 = dirname(__filename2);
694
+ OVERRIDE_FILENAME = "override.json";
695
+ EXTRA_FILENAME = "extra.json";
696
+ RULES_DIR_ENVS = ["AGENT_MOSS_EXTRA_RULES_DIR", "AGENT_MOSS_OVERRIDE_RULES_DIR"];
697
+ frozenJsonCache = /* @__PURE__ */ new Map();
698
+ SCENARIO_LIST_KEYS = ["extra_command_patterns", "extra_injection_keywords", "extra_sensitive_paths"];
699
+ RE_FLAG_MAP = { i: "i", s: "s", m: "m" };
700
+ TRAITS_FIELDS = [
701
+ "write_keywords",
702
+ "read_keywords",
703
+ "write_command_res",
704
+ "read_only_commands",
705
+ "fully_safe_commands",
706
+ "readonly_sensitive_commands",
707
+ "safe_action_types",
708
+ "readonly_sensitive_action_types"
709
+ ];
710
+ TRAITS_OS_FALLBACK = "linux";
711
+ EXTRA_CATEGORIES = {
712
+ dangerous_commands: ["pattern"],
713
+ injection_detection: ["keyword", "lang"],
714
+ sensitive_path_access: ["path"],
715
+ intent_consistency: ["intent_keywords"],
716
+ password_modify_consent: ["pattern"],
717
+ user_deletion_consent: ["pattern"]
718
+ };
719
+ STRUCT_KEYS = ["skills", "ltl_rules", "tools"];
720
+ EXTRA_FLAT_FIELD = [
721
+ [["path"], "sensitive_path_access"],
722
+ [["keyword", "lang"], "injection_detection"],
723
+ [["intent_keywords"], "intent_consistency"],
724
+ [["pattern", "risk_level"], "dangerous_commands"],
725
+ [["pattern", "risk_type"], "dangerous_commands"]
726
+ ];
727
+ _RULES_CACHE = null;
728
+ warnedIgnored = /* @__PURE__ */ new Set();
729
+ }
730
+ });
731
+
732
+ // src/infra/security-policy.ts
733
+ import { createHash as createHash2, randomBytes, timingSafeEqual } from "crypto";
734
+ import { chmodSync, closeSync, existsSync as existsSync3, lstatSync, mkdirSync, openSync, readFileSync as readFileSync4, readSync, realpathSync, writeSync } from "fs";
735
+ import { homedir as homedir2, tmpdir } from "os";
736
+ import { basename, isAbsolute, join as join4, relative, resolve as resolve2, dirname as dirname2 } from "path";
737
+ function env(name, dflt = "") {
738
+ return (process.env[name] ?? dflt).trim();
739
+ }
740
+ function envInt(name, dflt) {
741
+ const raw = env(name);
742
+ if (!raw) return dflt;
743
+ const n = Number.parseInt(raw, 10);
744
+ return Number.isFinite(n) ? n : dflt;
745
+ }
746
+ function splitList(raw) {
747
+ return raw.replace(/;/g, ",").split(",").map((x) => x.trim()).filter(Boolean);
748
+ }
749
+ function originCheckEnabled() {
750
+ return TRUTHY2.includes(env("AGENT_MOSS_ORIGIN_CHECK", "1").toLowerCase());
751
+ }
752
+ function allowedOrigins() {
753
+ return splitList(env("AGENT_MOSS_CORS_ORIGINS"));
754
+ }
755
+ function normOrigin(o) {
756
+ return (o || "").trim().replace(/\/+$/, "");
757
+ }
758
+ function originAllowed(origin, hostHeader, extra) {
759
+ const o = normOrigin(origin);
760
+ if (!o) return true;
761
+ const entries = (extra ?? allowedOrigins()).map(normOrigin);
762
+ if (entries.includes(o)) return true;
763
+ const host = (hostHeader || "").trim();
764
+ if (!host) return false;
765
+ try {
766
+ const u = new URL(o);
767
+ return u.host === host;
768
+ } catch {
769
+ return false;
770
+ }
771
+ }
772
+ function authMode() {
773
+ return (env("AGENT_MOSS_AUTH_MODE", AUTH_MODE_AUTO) || AUTH_MODE_AUTO).toLowerCase();
774
+ }
775
+ function autoTokenPath() {
776
+ let root;
777
+ try {
778
+ root = getDataRoot();
779
+ } catch {
780
+ root = join4(homedir2(), ".config", "agentmoss");
781
+ }
782
+ return join4(root, "auth_token");
783
+ }
784
+ function readFdToken() {
785
+ const raw = env("AGENT_MOSS_AUTH_TOKEN_FD");
786
+ if (!raw) return "";
787
+ const fd = Number.parseInt(raw, 10);
788
+ if (!Number.isFinite(fd)) return "";
789
+ if (fdTokenCache.has(fd)) return fdTokenCache.get(fd);
790
+ const buf = Buffer.alloc(8192);
791
+ let n = 0;
792
+ try {
793
+ n = readSync(fd, buf, 0, buf.length, null);
794
+ } catch (e) {
795
+ console.error(`[AgentMoss] \u4ECE fd ${fd} \u8BFB\u53D6\u8BBF\u95EE token \u5931\u8D25:`, e);
796
+ }
797
+ try {
798
+ closeSync(fd);
799
+ } catch {
800
+ }
801
+ const tok = n > 0 ? buf.subarray(0, n).toString("utf-8").trim() : "";
802
+ fdTokenCache.set(fd, tok);
803
+ return tok;
804
+ }
805
+ function readExplicitToken() {
806
+ const tok = env("AGENT_MOSS_AUTH_TOKEN");
807
+ if (tok) return tok;
808
+ const fdTok = readFdToken();
809
+ if (fdTok) return fdTok;
810
+ const path6 = env("AGENT_MOSS_AUTH_TOKEN_FILE");
811
+ if (path6) {
812
+ try {
813
+ return readFileSync4(path6, "utf-8").trim();
814
+ } catch {
815
+ return "";
816
+ }
817
+ }
818
+ return "";
819
+ }
820
+ function ensureAutoToken() {
821
+ const path6 = autoTokenPath();
822
+ try {
823
+ if (existsSync3(path6) && !lstatSync(path6).isSymbolicLink()) {
824
+ const tok2 = readFileSync4(path6, "utf-8").trim();
825
+ if (tok2) {
826
+ try {
827
+ chmodSync(path6, 384);
828
+ } catch {
829
+ }
830
+ return tok2;
831
+ }
832
+ }
833
+ const tok = randomBytes(32).toString("base64url");
834
+ ensurePrivateDir(dirname2(path6));
835
+ if (!writePrivateText(path6, tok + "\n")) return "";
836
+ console.warn(
837
+ `[AgentMoss] \u5DF2\u751F\u6210\u8BBF\u95EE token \u5E76\u5199\u5165 ${path6}\uFF080600\uFF09\u3002\u5BA2\u6237\u7AEF\u9700\u5E26 \`Authorization: Bearer <token>\`\uFF1B\u67E5\u770B\uFF1Acat ${path6}\uFF1B\u5982\u9700\u5173\u95ED\u9274\u6743\uFF1AAGENT_MOSS_AUTH_MODE=off`
838
+ );
839
+ return tok;
840
+ } catch (e) {
841
+ console.error("[AgentMoss] \u751F\u6210\u8BBF\u95EE token \u5931\u8D25\uFF08\u672C\u6B21\u8FD0\u884C\u5C06\u4E0D\u9274\u6743\uFF09:", e);
842
+ return "";
843
+ }
844
+ }
845
+ function parseAgentTokens(raw) {
846
+ const out = {};
847
+ for (const pair of splitList(raw)) {
848
+ const i = pair.indexOf("=");
849
+ if (i > 0) {
850
+ const a = pair.slice(0, i).trim();
851
+ const t = pair.slice(i + 1).trim();
852
+ if (a && t) out[a] = t;
853
+ }
854
+ }
855
+ return out;
856
+ }
857
+ function resolveAuth() {
858
+ const rawAgents = env("AGENT_MOSS_AGENT_TOKENS");
859
+ const key = [
860
+ authMode(),
861
+ env("AGENT_MOSS_AUTH_TOKEN"),
862
+ env("AGENT_MOSS_AUTH_TOKEN_FILE"),
863
+ env("AGENT_MOSS_AUTH_TOKEN_FD"),
864
+ rawAgents,
865
+ env("AGENT_MOSS_DATA_DIR")
866
+ ].join("\0");
867
+ if (tokenCache && tokenCache.key === key) {
868
+ return [tokenCache.token, tokenCache.agents, tokenCache.error];
869
+ }
870
+ let error = "";
871
+ let token = readExplicitToken();
872
+ if (!token && env("AGENT_MOSS_AUTH_TOKEN_FD") && authMode() !== AUTH_MODE_OFF) {
873
+ error = "AGENT_MOSS_AUTH_TOKEN_FD \u5DF2\u8BBE\u7F6E\uFF0C\u4F46\u6CA1\u80FD\u4ECE\u8BE5\u6587\u4EF6\u63CF\u8FF0\u7B26\u8BFB\u5230 token\u3002\u62D2\u7EDD\u964D\u7EA7\u5230\u78C1\u76D8 token\uFF08\u90A3\u4F1A\u5728\u672C\u673A\u7559\u4E0B\u4E00\u4EFD\u540C\u7528\u6237\u53EF\u8BFB\u7684\u51ED\u8BC1\uFF09\u3002";
874
+ console.error(`[agent-moss] ${error}`);
875
+ }
876
+ if (!token && !error && authMode() !== AUTH_MODE_OFF) token = ensureAutoToken();
877
+ const agents = parseAgentTokens(rawAgents);
878
+ tokenCache = { key, token, agents, error };
879
+ return [token, agents, error];
880
+ }
881
+ function agentTokens() {
882
+ return resolveAuth()[1];
883
+ }
884
+ function authConfigError() {
885
+ return resolveAuth()[2];
886
+ }
887
+ function failFastOnAuthError() {
888
+ const err = authConfigError();
889
+ if (err) {
890
+ console.error(`[agent-moss] \u9274\u6743\u914D\u7F6E\u9519\u8BEF\uFF0C\u62D2\u7EDD\u542F\u52A8: ${err}`);
891
+ process.exit(1);
892
+ }
893
+ }
894
+ function authEnabled() {
895
+ if (authMode() === AUTH_MODE_OFF) return false;
896
+ const [tok, agents] = resolveAuth();
897
+ return Boolean(tok) || Object.keys(agents).length > 0;
898
+ }
899
+ function bearer(headers) {
900
+ const get = (k) => headers instanceof Headers ? headers.get(k) ?? "" : headers[k] ?? "";
901
+ const v = get("authorization") || get("Authorization");
902
+ return v.toLowerCase().startsWith("bearer ") ? v.slice(7).trim() : "";
903
+ }
904
+ function constEq(a, b) {
905
+ const ba = Buffer.from(a, "utf-8");
906
+ const bb = Buffer.from(b, "utf-8");
907
+ if (ba.length !== bb.length) return false;
908
+ return timingSafeEqual(ba, bb);
909
+ }
910
+ function checkRequestAuth(headers, path6) {
911
+ if (authMode() === AUTH_MODE_OFF) return [true, ""];
912
+ const isHealth = HEALTH_PATHS.includes(path6.replace(/\/+$/, ""));
913
+ const [gt0, agents, err] = resolveAuth();
914
+ if (err) return isHealth ? [true, ""] : [false, ""];
915
+ if (!gt0 && Object.keys(agents).length === 0) return [true, ""];
916
+ if (isHealth) return [true, ""];
917
+ const tok = bearer(headers);
918
+ if (!tok) return [false, ""];
919
+ if (path6.startsWith("/console/api")) {
920
+ const consoleTok = env("AGENT_MOSS_CONSOLE_TOKEN");
921
+ if (consoleTok && constEq(tok, consoleTok)) return [true, "console"];
922
+ }
923
+ const gt = gt0;
924
+ if (gt && constEq(tok, gt)) return [true, "global"];
925
+ for (const [agent, expected] of Object.entries(agents)) {
926
+ if (constEq(tok, expected)) return [true, `agent:${agent}`];
927
+ }
928
+ return [false, ""];
929
+ }
930
+ function bindAgent(principal, agentId) {
931
+ const tokens = agentTokens();
932
+ if (Object.keys(tokens).length === 0) return [true, agentId];
933
+ if (!principal.startsWith("agent:")) return [true, agentId];
934
+ const bound = principal.slice(6);
935
+ if (!agentId) return [true, bound];
936
+ return [bound === agentId, bound];
937
+ }
938
+ function principalOf(headers, path6 = "") {
939
+ const [ok, principal] = checkRequestAuth(headers, path6);
940
+ return ok ? principal : "";
941
+ }
942
+ function principalHash(principal) {
943
+ if (!principal) return "";
944
+ return createHash2("sha256").update(principal, "utf-8").digest("hex").slice(0, 12);
945
+ }
946
+ function homeDir() {
947
+ try {
948
+ return homedir2();
949
+ } catch {
950
+ return tmpdir();
951
+ }
952
+ }
953
+ function logRoots() {
954
+ const raw = env("AGENT_MOSS_LOG_ROOTS");
955
+ if (raw) return splitList(raw);
956
+ return [.../* @__PURE__ */ new Set([homeDir(), "/tmp", tmpdir()])];
957
+ }
958
+ function reviewRoots() {
959
+ const roots = [join4(homeDir(), ".local", "share", "agent_gate")];
960
+ for (const name of ["AGENT_GATE_DB", "AGENT_GATE_JSON_PATH"]) {
961
+ const v = env(name);
962
+ if (v) roots.push(existsSync3(v) && lstatSync(v).isDirectory() ? v : dirname2(v));
963
+ }
964
+ roots.push(...splitList(env("AGENT_MOSS_REVIEW_PATHS")));
965
+ return [...new Set(roots)];
966
+ }
967
+ function realpathLoose(p) {
968
+ const abs = resolve2(p.replace(/^~(?=$|\/)/, homeDir()));
969
+ try {
970
+ return realpathSync(abs);
971
+ } catch {
972
+ }
973
+ const parts = [];
974
+ let cur = abs;
975
+ for (; ; ) {
976
+ const parent = dirname2(cur);
977
+ if (parent === cur) return null;
978
+ parts.unshift(basename(cur));
979
+ cur = parent;
980
+ try {
981
+ return join4(realpathSync(cur), ...parts);
982
+ } catch {
983
+ }
984
+ }
985
+ }
986
+ function pathWithin(p, roots) {
987
+ if (!p) return true;
988
+ const target = realpathLoose(p);
989
+ if (target === null) return false;
990
+ for (const root of roots) {
991
+ const rootReal = realpathLoose(root);
992
+ if (rootReal === null) continue;
993
+ const rel = relative(rootReal, target);
994
+ if (rel === "" || !rel.startsWith("..") && !isAbsolute(rel)) return true;
995
+ }
996
+ return false;
997
+ }
998
+ function ensurePrivateDir(dir) {
999
+ try {
1000
+ mkdirSync(dir, { recursive: true });
1001
+ if (lstatSync(dir).isSymbolicLink()) return false;
1002
+ if (typeof process.getuid === "function" && lstatSync(dir).uid !== process.getuid()) return false;
1003
+ chmodSync(dir, 448);
1004
+ return true;
1005
+ } catch {
1006
+ return false;
1007
+ }
1008
+ }
1009
+ function writePrivateText(file, text) {
1010
+ try {
1011
+ const flags = __require("fs").constants;
1012
+ let openFlags = flags.O_WRONLY | flags.O_CREAT | flags.O_TRUNC;
1013
+ if (flags.O_NOFOLLOW) openFlags |= flags.O_NOFOLLOW;
1014
+ const fd = openSync(file, openFlags, 384);
1015
+ try {
1016
+ writeSync(fd, text);
1017
+ } finally {
1018
+ closeSync(fd);
1019
+ }
1020
+ return true;
1021
+ } catch {
1022
+ return false;
1023
+ }
1024
+ }
1025
+ function perRequestLlmEnabled() {
1026
+ return TRUTHY2.includes(env("AGENT_MOSS_ALLOW_PER_REQUEST_LLM", "1").toLowerCase());
1027
+ }
1028
+ function validateLlmBaseUrl(url, allowPrivate) {
1029
+ if (!url) return [true, ""];
1030
+ let u;
1031
+ try {
1032
+ u = new URL(url);
1033
+ } catch {
1034
+ return [false, "baseUrl \u4E0D\u662F\u5408\u6CD5 URL"];
1035
+ }
1036
+ if (u.protocol !== "http:" && u.protocol !== "https:") {
1037
+ return [false, `baseUrl \u534F\u8BAE\u4E0D\u53D7\u652F\u6301: ${u.protocol}`];
1038
+ }
1039
+ const host = (u.hostname || "").toLowerCase();
1040
+ if (!host) return [false, "baseUrl \u7F3A\u5C11\u4E3B\u673A\u540D"];
1041
+ if (BLOCKED_HOSTS.includes(host)) return [false, `baseUrl \u6307\u5411\u4E91\u5143\u6570\u636E\u5730\u5740\uFF0C\u5DF2\u62D2\u7EDD: ${host}`];
1042
+ if (/^169\.254\.\d{1,3}\.\d{1,3}$/.test(host)) {
1043
+ return [false, `baseUrl \u6307\u5411\u94FE\u8DEF\u672C\u5730\u5730\u5740\uFF0C\u5DF2\u62D2\u7EDD: ${host}`];
1044
+ }
1045
+ const allowlist = splitList(env("AGENT_MOSS_LLM_HOST_ALLOWLIST")).map((h) => h.toLowerCase());
1046
+ if (allowlist.length && !allowlist.includes(host)) {
1047
+ return [false, `baseUrl \u4E3B\u673A\u4E0D\u5728\u5141\u8BB8\u5217\u8868: ${host}`];
1048
+ }
1049
+ if (allowPrivate === void 0) {
1050
+ allowPrivate = TRUTHY2.includes(env("AGENT_MOSS_TRUST_PRIVATE_LLM", "1").toLowerCase());
1051
+ }
1052
+ if (!allowPrivate) {
1053
+ if (host === "localhost" || host === "::1") return [false, "baseUrl \u6307\u5411\u672C\u673A\uFF0C\u5DF2\u62D2\u7EDD"];
1054
+ const m = host.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/);
1055
+ if (m) {
1056
+ const [a, b] = [Number(m[1]), Number(m[2])];
1057
+ if (a === 10 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 127 || a === 169 && b === 254) {
1058
+ return [false, `baseUrl \u6307\u5411\u5185\u7F51\u5730\u5740\uFF0C\u5DF2\u62D2\u7EDD: ${host}`];
1059
+ }
1060
+ }
1061
+ }
1062
+ return [true, ""];
1063
+ }
1064
+ function hardenExistingPaths() {
1065
+ try {
1066
+ const roots = [join4(homeDir(), ".config", "agentmoss"), join4(homeDir(), ".agentmoss")];
1067
+ const names = [
1068
+ "agent_moss_runtime.json",
1069
+ "config.json",
1070
+ "agent_moss_settings.json",
1071
+ "agent_moss_token_stats.json",
1072
+ "stateful_chains.db",
1073
+ "stateful_chains.db-wal",
1074
+ "stateful_chains.db-shm"
1075
+ ];
1076
+ for (const root of roots) {
1077
+ try {
1078
+ if (existsSync3(root) && !lstatSync(root).isSymbolicLink()) chmodSync(root, 448);
1079
+ } catch {
1080
+ }
1081
+ for (const n of names) {
1082
+ const p = join4(root, n);
1083
+ try {
1084
+ if (existsSync3(p) && !lstatSync(p).isSymbolicLink()) chmodSync(p, 384);
1085
+ } catch {
1086
+ }
1087
+ }
1088
+ }
1089
+ } catch {
1090
+ }
1091
+ }
1092
+ function chainDetailMode() {
1093
+ return (env("AGENT_MOSS_CHAIN_DETAIL", "redacted") || "redacted").toLowerCase();
1094
+ }
1095
+ function redactChain(chain, currentSessionId) {
1096
+ if (!chain || chain.length === 0) return [];
1097
+ if (chainDetailMode() === "full") return [...chain];
1098
+ return chain.map((step) => {
1099
+ if (Array.isArray(step) && step.length >= 3) {
1100
+ const [sid, seq, , ts] = step;
1101
+ if (String(sid) === String(currentSessionId)) return [...step];
1102
+ return ["<other-session>", seq, "<\u5DF2\u9690\u85CF>", ts ?? ""];
1103
+ }
1104
+ return step;
1105
+ });
1106
+ }
1107
+ function sessionSweepThreshold() {
1108
+ return envInt("AGENT_MOSS_SESSION_SWEEP_THRESHOLD", 50);
1109
+ }
1110
+ function sessionSweepWindow() {
1111
+ return envInt("AGENT_MOSS_SESSION_SWEEP_WINDOW_SEC", 300);
1112
+ }
1113
+ function sessionSweepAction() {
1114
+ return (env("AGENT_MOSS_SESSION_SWEEP_ACTION", "log") || "log").toLowerCase();
1115
+ }
1116
+ function noteSessionAccess(key, sessionId) {
1117
+ const limit = sessionSweepThreshold();
1118
+ if (limit <= 0 || !sessionId) return [0, false];
1119
+ const now = Date.now() / 1e3;
1120
+ const cutoff = now - sessionSweepWindow();
1121
+ let entries = (sessionWindow.get(key) ?? []).filter(([t]) => t >= cutoff);
1122
+ entries.push([now, sessionId]);
1123
+ if (entries.length > limit * 4) entries = entries.slice(-limit * 4);
1124
+ sessionWindow.set(key, entries);
1125
+ if (sessionWindow.size > 1e4) sessionWindow.clear();
1126
+ const distinct = new Set(entries.map(([, s]) => s)).size;
1127
+ return [distinct, distinct > limit];
1128
+ }
1129
+ function queueAbuseThreshold() {
1130
+ return envInt("AGENT_MOSS_QUEUE_ABUSE_THRESHOLD", 10);
1131
+ }
1132
+ function queueAbuseWindow() {
1133
+ return envInt("AGENT_MOSS_QUEUE_ABUSE_WINDOW_SEC", 60);
1134
+ }
1135
+ function queueAbuseAction() {
1136
+ return (env("AGENT_MOSS_QUEUE_ABUSE_ACTION", "log") || "log").toLowerCase();
1137
+ }
1138
+ function noteQueueReject(key) {
1139
+ const limit = queueAbuseThreshold();
1140
+ if (limit <= 0 || !key) return [0, false];
1141
+ const now = Date.now() / 1e3;
1142
+ const cutoff = now - queueAbuseWindow();
1143
+ let entries = (queueRejects.get(key) ?? []).filter((t) => t >= cutoff);
1144
+ entries.push(now);
1145
+ if (entries.length > limit * 4) entries = entries.slice(-limit * 4);
1146
+ queueRejects.set(key, entries);
1147
+ if (queueRejects.size > 1e4) queueRejects.clear();
1148
+ return [entries.length, entries.length > limit];
1149
+ }
1150
+ function socketMode() {
1151
+ const raw = env("AGENT_MOSS_SOCKET_MODE", "600");
1152
+ const mode = parseInt(raw, 8);
1153
+ if (!Number.isFinite(mode) || mode < 0 || mode > 511) {
1154
+ console.warn(`[agent-moss] AGENT_MOSS_SOCKET_MODE=${raw} \u975E\u6CD5\uFF0C\u56DE\u9000 600`);
1155
+ return 384;
1156
+ }
1157
+ return mode;
1158
+ }
1159
+ function errorDetailMode() {
1160
+ return (env("AGENT_MOSS_ERROR_DETAIL", "generic") || "generic").toLowerCase();
1161
+ }
1162
+ function maxBodyBytes() {
1163
+ return envInt("AGENT_MOSS_MAX_BODY_BYTES", 16 * 1024 * 1024);
1164
+ }
1165
+ function poolOf(path6) {
1166
+ if (path6 === "/api/v1/analyze" || path6.startsWith("/api/v1/analyze/")) return "biz";
1167
+ if (path6 === "/api/v1/review" || path6.startsWith("/api/v1/review/")) return "review";
1168
+ if (path6 === "/console" || path6.startsWith("/console/") || path6.startsWith("/api/v1/brain")) return "ops";
1169
+ if (path6 === "/api/v1/health") return "health";
1170
+ return "other";
1171
+ }
1172
+ function poolLimit(name) {
1173
+ let dflt = POOL_DEFAULTS[name] ?? 4;
1174
+ if (name === "biz") dflt = envInt("AGENT_MOSS_MAX_INFLIGHT", dflt);
1175
+ return envInt(`AGENT_MOSS_POOL_${name.toUpperCase()}`, dflt);
1176
+ }
1177
+ function unauthInflight() {
1178
+ return envInt("AGENT_MOSS_MAX_UNAUTH_INFLIGHT", 4);
1179
+ }
1180
+ function unauthPerIp() {
1181
+ return envInt("AGENT_MOSS_MAX_UNAUTH_PER_IP", 2);
1182
+ }
1183
+ function agentInflight() {
1184
+ return envInt("AGENT_MOSS_MAX_AGENT_INFLIGHT", 8);
1185
+ }
1186
+ function boundAgentOf(principal) {
1187
+ return principal.startsWith("agent:") ? principal.slice("agent:".length) : "";
1188
+ }
1189
+ function rateLimitQps() {
1190
+ return envInt("AGENT_MOSS_RATE_LIMIT_QPS", 100);
1191
+ }
1192
+ function maxConnections() {
1193
+ return envInt("AGENT_MOSS_MAX_CONNECTIONS", 128);
1194
+ }
1195
+ function keepAliveSec() {
1196
+ return envInt("AGENT_MOSS_KEEP_ALIVE_SEC", 5);
1197
+ }
1198
+ var TRUTHY2, HEALTH_PATHS, AUTH_MODE_AUTO, AUTH_MODE_OFF, tokenCache, fdTokenCache, BLOCKED_HOSTS, sessionWindow, queueRejects, POOL_DEFAULTS;
1199
+ var init_security_policy = __esm({
1200
+ "src/infra/security-policy.ts"() {
1201
+ "use strict";
1202
+ init_esm_shims();
1203
+ init_paths();
1204
+ TRUTHY2 = ["1", "true", "yes", "on"];
1205
+ HEALTH_PATHS = ["/api/v1/health"];
1206
+ AUTH_MODE_AUTO = "auto";
1207
+ AUTH_MODE_OFF = "off";
1208
+ tokenCache = null;
1209
+ fdTokenCache = /* @__PURE__ */ new Map();
1210
+ BLOCKED_HOSTS = ["169.254.169.254", "metadata.google.internal", "metadata.tencentyun.com"];
1211
+ sessionWindow = /* @__PURE__ */ new Map();
1212
+ queueRejects = /* @__PURE__ */ new Map();
1213
+ POOL_DEFAULTS = { biz: 16, review: 1, ops: 4, health: 4, other: 4 };
1214
+ }
1215
+ });
1216
+
1217
+ // src/engine/shell-split.ts
1218
+ function hasCommandSubstitution(text) {
1219
+ return CMD_SUBSTITUTION.test(text);
1220
+ }
1221
+ function splitCommandSegments(command) {
1222
+ const segments = [];
1223
+ let cur = "";
1224
+ let quote = null;
1225
+ for (let i = 0; i < command.length; i++) {
1226
+ const ch = command[i];
1227
+ if (quote) {
1228
+ cur += ch;
1229
+ if (ch === quote) quote = null;
1230
+ continue;
1231
+ }
1232
+ if (ch === "'" || ch === '"') {
1233
+ quote = ch;
1234
+ cur += ch;
1235
+ continue;
1236
+ }
1237
+ if (/\||&|;|\n/.test(ch)) {
1238
+ segments.push(cur);
1239
+ segments.push(ch);
1240
+ cur = "";
1241
+ continue;
1242
+ }
1243
+ cur += ch;
1244
+ }
1245
+ if (cur) segments.push(cur);
1246
+ return segments.map((s) => s.trim()).filter((s) => s.length > 0 && !/^(?:\|\||&&|[|;&])$/.test(s));
1247
+ }
1248
+ function extractFirstCommand(command) {
1249
+ const segments = splitCommandSegments(command);
1250
+ return segments.length ? segments[0] : "";
1251
+ }
1252
+ var CMD_SUBSTITUTION;
1253
+ var init_shell_split = __esm({
1254
+ "src/engine/shell-split.ts"() {
1255
+ "use strict";
1256
+ init_esm_shims();
1257
+ CMD_SUBSTITUTION = /\$\(|`|</;
1258
+ }
1259
+ });
1260
+
1261
+ // src/engine/inline-analyzer.ts
1262
+ function buildChainRegex(pattern, flags) {
1263
+ return new RegExp(pattern, (flags || "") + "g");
1264
+ }
1265
+ function isLiteralTextCommand(command) {
1266
+ const segments = splitCommandSegments(command);
1267
+ if (segments.length === 0 || segments.some((s) => !ECHO_SEG_RE.test(s))) return false;
1268
+ if (command.includes(">")) {
1269
+ let d = command.replace(/<=>|>=|<=|<>|=>/g, " ");
1270
+ d = d.replace(/(?:2|&|1)?\s*>\s*\/dev\/null\b/g, "");
1271
+ d = d.replace(new RegExp(`>{1,2}\\s*(['"])([~./][^'"]+)\\1`, "g"), (_m, _q, p) => ">" + p);
1272
+ d = d.replace(/'[^']*'|"[^"]*"/g, "");
1273
+ if (/>{1,2}\s*(?!&)\S/.test(d)) {
1274
+ return false;
1275
+ }
1276
+ }
1277
+ if (/\|\s*\b(?:tee|tac|install)\b/i.test(command)) {
1278
+ return false;
1279
+ }
1280
+ return true;
1281
+ }
1282
+ function extractInlineCode(command) {
1283
+ const parts = [];
1284
+ let m;
1285
+ INLINE_CODE_RE.lastIndex = 0;
1286
+ while ((m = INLINE_CODE_RE.exec(command)) !== null) {
1287
+ const code = m[1] ?? m[2];
1288
+ if (code) parts.push(code);
1289
+ }
1290
+ ECHO_STRING_RE.lastIndex = 0;
1291
+ while ((m = ECHO_STRING_RE.exec(command)) !== null) {
1292
+ const code = m[1] ?? m[2];
1293
+ if (code) parts.push(code);
1294
+ }
1295
+ return parts.join("\n");
1296
+ }
1297
+ function hasWriteExecChain(command) {
1298
+ return WRITE_EXEC_CHAIN_PATTERNS.some((p) => {
1299
+ p.lastIndex = 0;
1300
+ return p.test(command);
1301
+ });
1302
+ }
1303
+ function hasDirTraversal(code) {
1304
+ const lower = code.toLowerCase();
1305
+ return DIR_TRAVERSAL_KEYWORDS.some((kw) => lower.includes(kw));
1306
+ }
1307
+ function hasReadWriteKeyword(code) {
1308
+ const lower = code.toLowerCase();
1309
+ return READ_WRITE_KEYWORDS.some((kw) => lower.includes(kw));
1310
+ }
1311
+ function analyzeInlineScript(command) {
1312
+ const code = extractInlineCode(command);
1313
+ if (!code) {
1314
+ return { actionable: false, allowed: true, reason: "", riskLevel: "low", riskType: "" };
1315
+ }
1316
+ if (hasWriteExecChain(command)) {
1317
+ return {
1318
+ actionable: true,
1319
+ allowed: false,
1320
+ 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",
1321
+ riskLevel: "critical",
1322
+ riskType: "script_execution"
1323
+ };
1324
+ }
1325
+ const redirMatch = command.match(REDIRECT_TO_SENSITIVE_RE);
1326
+ if (redirMatch) {
1327
+ return {
1328
+ actionable: true,
1329
+ allowed: false,
1330
+ reason: `\u91CD\u5B9A\u5411\u5199\u5165\u654F\u611F\u8DEF\u5F84: ${redirMatch[1]}`,
1331
+ riskLevel: "critical",
1332
+ riskType: "file_access"
1333
+ };
1334
+ }
1335
+ const isEchoOrPrintf1 = isLiteralTextCommand(command);
1336
+ if (!isEchoOrPrintf1 && hasDirTraversal(code) && hasReadWriteKeyword(code)) {
1337
+ return { actionable: false, allowed: true, reason: "", riskLevel: "low", riskType: "" };
1338
+ }
1339
+ const isEchoOrPrintf = isLiteralTextCommand(command);
1340
+ if (!isEchoOrPrintf && hasReadWriteKeyword(code)) {
1341
+ return {
1342
+ actionable: true,
1343
+ allowed: false,
1344
+ 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",
1345
+ riskLevel: "critical",
1346
+ riskType: "file_access"
1347
+ };
1348
+ }
1349
+ return {
1350
+ actionable: true,
1351
+ allowed: true,
1352
+ reason: "\u654F\u611F\u8DEF\u5F84\u4EC5\u4F5C\u4E3A\u6587\u672C\u5B57\u9762\u91CF\u51FA\u73B0\uFF0C\u672A\u4F34\u968F\u8BFB\u5199\u64CD\u4F5C",
1353
+ riskLevel: "low",
1354
+ riskType: "file_access"
1355
+ };
1356
+ }
1357
+ 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;
1358
+ var init_inline_analyzer = __esm({
1359
+ "src/engine/inline-analyzer.ts"() {
856
1360
  "use strict";
857
1361
  init_esm_shims();
858
- init_paths();
859
- init_rules_mode();
860
- init_runtime_config_bridge();
861
- __filename2 = fileURLToPath2(import.meta.url);
862
- __dirname2 = dirname(__filename2);
863
- OVERRIDE_FILENAME = "override.json";
864
- EXTRA_FILENAME = "extra.json";
865
- RULES_DIR_ENVS = ["AGENT_MOSS_EXTRA_RULES_DIR", "AGENT_MOSS_OVERRIDE_RULES_DIR"];
866
- RULES_DIR_CANDS = (() => {
867
- const cands = [
868
- // 注:`<data_root>/rules/` 不再是"按内置文件名覆盖"的查找位 —— 2026-10 起
869
- // 规则目录只认 override.json / extra.json(见 rulesDirs())。
870
- // dist: __dirname=dist/ → dist/rules;__dirname=dist/engine/ → dist/engine/../rules=dist/rules
871
- resolve(__dirname2, "../rules"),
872
- resolve(__dirname2, "rules"),
873
- // dev src: __dirname=ts/src/infra/ → ts/src/infra/../../../agent_moss/rules = 仓库根 agent_moss/rules
874
- resolve(__dirname2, "../../../agent_moss/rules"),
875
- // dist 但 __dirname 指向 dist/engine 再往上两级的仓库根 agent_moss/rules
876
- resolve(__dirname2, "../../../../agent_moss/rules"),
877
- // 相对当前工作目录的仓库根(兜底,兼容以仓库根为 cwd 运行)
878
- resolve(process.cwd(), "agent_moss/rules")
879
- ];
880
- return cands.filter((c) => existsSync(c));
881
- })();
882
- frozenJsonCache = /* @__PURE__ */ new Map();
883
- SCENARIO_LIST_KEYS = ["extra_command_patterns", "extra_injection_keywords", "extra_sensitive_paths"];
884
- EXTRA_CATEGORIES = {
885
- dangerous_commands: ["pattern"],
886
- injection_detection: ["keyword", "lang"],
887
- sensitive_path_access: ["path"],
888
- intent_consistency: ["intent_keywords"],
889
- password_modify_consent: ["pattern"],
890
- user_deletion_consent: ["pattern"]
891
- };
892
- STRUCT_KEYS = ["skills", "ltl_rules", "tools"];
893
- EXTRA_FLAT_FIELD = [
894
- [["path"], "sensitive_path_access"],
895
- [["keyword", "lang"], "injection_detection"],
896
- [["intent_keywords"], "intent_consistency"],
897
- [["pattern", "risk_level"], "dangerous_commands"],
898
- [["pattern", "risk_type"], "dangerous_commands"]
1362
+ init_shell_split();
1363
+ init_rules_loader();
1364
+ INLINE_RULES = loadInline();
1365
+ if (!INLINE_RULES || Object.keys(INLINE_RULES).length === 0) {
1366
+ console.error(
1367
+ "[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"
1368
+ );
1369
+ }
1370
+ INLINE_CODE_RE = /(?:python|python3|perl|perl5|ruby|jruby|mruby|node|php|lua)\s+(?:-[a-zA-Z]*[cera][a-zA-Z]*\s+)"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
1371
+ ECHO_STRING_RE = /\b(?:echo|printf)\s+"([^"\\]*(?:\\.[^"\\]*)*)"|\b(?:echo|printf)\s+'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
1372
+ ECHO_SEG_RE = /^\s*(?:echo|printf)(?:\s|$)/i;
1373
+ READ_WRITE_KEYWORDS = INLINE_RULES.read_write_keywords ?? [];
1374
+ WRITE_EXEC_CHAIN_PATTERNS = (INLINE_RULES.write_exec_chain_patterns ?? []).filter((p) => p && typeof p.pattern === "string").map((p) => buildChainRegex(p.pattern, p.flags ?? ""));
1375
+ _REDIRECT_PATHS = INLINE_RULES.redirect_sensitive_paths ?? {};
1376
+ SHELL_RC_FRAGMENTS = _REDIRECT_PATHS.shell_rc_fragments ?? [];
1377
+ SENSITIVE_PATH_FRAGMENTS = [
1378
+ ..._REDIRECT_PATHS.ts_static ?? [],
1379
+ ...SHELL_RC_FRAGMENTS
899
1380
  ];
900
- _RULES_CACHE = null;
901
- warnedIgnored = /* @__PURE__ */ new Set();
1381
+ REDIRECT_TO_SENSITIVE_RE = new RegExp(`>{1,2}\\s*(?:\\S*/)?(${SENSITIVE_PATH_FRAGMENTS.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})(?:\\s|$)`, "i");
1382
+ DIR_TRAVERSAL_KEYWORDS = INLINE_RULES.dir_traversal_keywords ?? [];
902
1383
  }
903
1384
  });
904
1385
 
905
1386
  // src/engine/platform-traits.ts
1387
+ function buildTraits(osName) {
1388
+ const t = loadPlatformTraits(osName);
1389
+ const strs = (k) => Array.isArray(t[k]) ? t[k] : [];
1390
+ return {
1391
+ writeKeywords: strs("write_keywords"),
1392
+ readKeywords: strs("read_keywords"),
1393
+ writeCommandRes: strs("write_command_res").map((p) => new RegExp(p)),
1394
+ readOnlyCommands: new Set(strs("read_only_commands")),
1395
+ fullySafeCommands: strs("fully_safe_commands").map((p) => new RegExp(p, "i")),
1396
+ readonlySensitiveCommands: strs("readonly_sensitive_commands").map((p) => new RegExp(p, "i")),
1397
+ safeActionTypes: new Set(strs("safe_action_types")),
1398
+ readonlySensitiveActionTypes: new Set(strs("readonly_sensitive_action_types"))
1399
+ };
1400
+ }
1401
+ function traitsFor(key) {
1402
+ const resolved = resolveTraitsOs(key);
1403
+ let t = traitsCache.get(resolved);
1404
+ if (!t) {
1405
+ t = buildTraits(resolved);
1406
+ traitsCache.set(resolved, t);
1407
+ }
1408
+ return t;
1409
+ }
906
1410
  function getPlatformTraits(osType) {
907
1411
  const accepted = acceptedOsValues(osType);
908
1412
  if (accepted.includes("windows")) return WINDOWS_TRAITS;
@@ -910,368 +1414,18 @@ function getPlatformTraits(osType) {
910
1414
  if (accepted.includes("harmony")) return HARMONY_TRAITS;
911
1415
  return LINUX_TRAITS;
912
1416
  }
913
- var LINUX_TRAITS, WINDOWS_TRAITS, MACOS_TRAITS, HARMONY_TRAITS;
1417
+ var traitsCache, LINUX_TRAITS, WINDOWS_TRAITS, MACOS_TRAITS, HARMONY_TRAITS;
914
1418
  var init_platform_traits = __esm({
915
1419
  "src/engine/platform-traits.ts"() {
916
1420
  "use strict";
917
1421
  init_esm_shims();
918
1422
  init_logic_rules();
919
- LINUX_TRAITS = {
920
- writeKeywords: [
921
- "write",
922
- "\u5199\u5165",
923
- "save",
924
- "\u4FDD\u5B58",
925
- "modify",
926
- "\u4FEE\u6539",
927
- "update",
928
- "\u66F4\u65B0",
929
- "delete",
930
- "\u5220\u9664",
931
- "remove",
932
- "\u79FB\u9664",
933
- "create",
934
- "\u521B\u5EFA",
935
- "append",
936
- "\u8FFD\u52A0",
937
- "truncate",
938
- "\u622A\u65AD",
939
- "overwrite",
940
- "\u8986\u76D6",
941
- "replace",
942
- "\u66FF\u6362",
943
- "edit"
944
- ],
945
- readKeywords: [
946
- "read",
947
- "\u8BFB\u53D6",
948
- "cat",
949
- "head",
950
- "tail",
951
- "less",
952
- "more",
953
- "view",
954
- "\u67E5\u770B",
955
- "open",
956
- "\u6253\u5F00",
957
- "load",
958
- "\u52A0\u8F7D",
959
- "grep",
960
- "search",
961
- "\u641C\u7D22",
962
- "find",
963
- "\u67E5\u627E"
964
- ],
965
- writeCommandRes: [
966
- /\brm\b/,
967
- /\bunlink\b/,
968
- /\brmdir\b/,
969
- /\bshred\b/,
970
- /\bcp\b/,
971
- /\bmv\b/,
972
- /\binstall\b/,
973
- /\btee\b/,
974
- /\bdd\b/,
975
- /\bmkfs\b/,
976
- /\b(fdisk|parted|cfdisk|sfdisk)\b/,
977
- /\bchmod\b/,
978
- /\bchown\b/,
979
- /\btruncate\b/
980
- ],
981
- readOnlyCommands: /* @__PURE__ */ new Set([
982
- "cat",
983
- "head",
984
- "tail",
985
- "less",
986
- "more",
987
- "grep",
988
- "find",
989
- "awk",
990
- "sed",
991
- "sort",
992
- "uniq",
993
- "wc",
994
- "cut",
995
- "strings",
996
- "od",
997
- "xxd",
998
- "hexdump",
999
- "tr",
1000
- "file",
1001
- "stat",
1002
- "du",
1003
- "df",
1004
- "ls",
1005
- "dir",
1006
- "tree",
1007
- "nl",
1008
- "tac",
1009
- "rev",
1010
- "echo",
1011
- "printf",
1012
- "lsblk",
1013
- "blockdev",
1014
- "smartctl",
1015
- "udevadm",
1016
- "dmidecode",
1017
- "lscpu",
1018
- "lspci",
1019
- "lsusb",
1020
- "lsmem",
1021
- "lsns",
1022
- "lsof",
1023
- "hwinfo",
1024
- "inxi",
1025
- "fdisk",
1026
- "parted",
1027
- "dmesg",
1028
- "journalctl",
1029
- "systemctl",
1030
- "hostnamectl",
1031
- "localectl",
1032
- "timedatectl",
1033
- "ps",
1034
- "top",
1035
- "free",
1036
- "vmstat",
1037
- "iostat",
1038
- "mpstat",
1039
- "pidof",
1040
- "pgrep",
1041
- "ip",
1042
- "ifconfig",
1043
- "route",
1044
- "ss",
1045
- "netstat",
1046
- "arp",
1047
- "ethtool",
1048
- "nmcli",
1049
- "uname",
1050
- "arch",
1051
- "nproc",
1052
- "getconf",
1053
- "getent",
1054
- "id",
1055
- "whoami",
1056
- "who",
1057
- "w",
1058
- "last",
1059
- "uptime",
1060
- "lsmod",
1061
- "modinfo",
1062
- "sysctl"
1063
- ]),
1064
- fullySafeCommands: [
1065
- /^ls(?:\s|$)/i,
1066
- /^dir(?:\s|$)/i,
1067
- /^pwd(?:\s|$)/i,
1068
- /^which\s+/i,
1069
- /^whereis\s+/i,
1070
- /^realpath\s+/i,
1071
- /^basename\s+/i,
1072
- /^dirname\s+/i,
1073
- /^file\s+/i,
1074
- /^stat\s+/i,
1075
- /^du(?:\s|$)/i,
1076
- /^echo\s+/i,
1077
- /^printf\s+/i,
1078
- /^type\s+/i,
1079
- /^command\s+/i,
1080
- // 系统信息查询(只读,不涉及文件/网络/权限)
1081
- /^whoami(?:\s|$)/i,
1082
- /^id(?:\s|$)/i,
1083
- /^hostname(?:\s|$)/i,
1084
- /^uname(?:\s|$)/i,
1085
- /^date(?:\s|$)/i,
1086
- /^env(?:\s|$)/i,
1087
- /^printenv(?:\s|$)/i,
1088
- /^tty(?:\s|$)/i,
1089
- /^arch(?:\s|$)/i,
1090
- /^uptime(?:\s|$)/i,
1091
- /^groups(?:\s|$)/i,
1092
- /^logname(?:\s|$)/i,
1093
- // 系统资源/硬件信息(只读,不读文件内容、无状态变更选项)
1094
- // 与上面 uptime/uname/id 同性质;缺了它们时开 L3 的部署每查一次磁盘/内存
1095
- // 都要走 LLM(实测 df -h 从 5ms 变成十几秒)。
1096
- /^df(?:\s|$)/i,
1097
- /^free(?:\s|$)/i,
1098
- /^vmstat(?:\s|$)/i,
1099
- /^nproc(?:\s|$)/i,
1100
- /^lscpu(?:\s|$)/i,
1101
- /^lsblk(?:\s|$)/i,
1102
- /^lspci(?:\s|$)/i,
1103
- /^lsusb(?:\s|$)/i,
1104
- /^lsmod(?:\s|$)/i
1105
- ],
1106
- readonlySensitiveCommands: [
1107
- /^cat\s+/i,
1108
- /^head(?:\s|$)/i,
1109
- /^tail(?:\s|$)/i,
1110
- /^less(?:\s|$)/i,
1111
- /^more(?:\s|$)/i,
1112
- /^wc(?:\s|$)/i,
1113
- /^grep(?:\s|$)/i,
1114
- /^find\s+/i,
1115
- /^ag(?:\s|$)/i,
1116
- /^rg(?:\s|$)/i,
1117
- /^awk\s+/i,
1118
- // NEW-FN-P(M03):sed -i/--in-place 是原地写,挡出白名单走 L2 read_before_write。
1119
- // 前瞻放最前(外层 ^sed\s+ 会消费 -i 前的空格)。与 Python profiles/linux.py 对齐。
1120
- /^(?!.*(?:\s|['"])-[a-zA-Z]*i[a-zA-Z]*\b)(?!.*(?:\s|['"])--in-place)sed\s+/i,
1121
- /^cut\s+/i,
1122
- /^sort(?:\s|$)/i,
1123
- /^uniq(?:\s|$)/i,
1124
- // 进程/网络/打开文件信息:只读,但会暴露进程命令行、连接对端、打开的文件
1125
- // 路径,故只跳 L3、保留 L2(L1 敏感路径检测照常生效)。
1126
- /^ps(?:\s|$)/i,
1127
- /^ss(?:\s|$)/i,
1128
- /^netstat(?:\s|$)/i,
1129
- /^lsof(?:\s|$)/i,
1130
- // 只读子命令级白名单:匹配的是「第一段命令」而非首词(extractFirstCommand
1131
- // 按 | && || 切分),所以能精确到子命令。systemctl stop/disable 等不在此列。
1132
- /^systemctl\s+(?:status|is-active|is-enabled|is-failed|list-units|list-unit-files|list-dependencies|show|cat)\b/i,
1133
- /^service\s+\S+\s+status\b/i,
1134
- // journalctl 整族是只读的,但 --vacuum-*/--rotate/--flush/--sync 会删改日志,
1135
- // 用否定前瞻挡掉后再放行。注意前瞻里必须用 .* 扫描**整段**:只写 (?!\s+--...)
1136
- // 的话只看紧跟 journalctl 的第一个参数,
1137
- // `journalctl -u sshd --vacuum-size=100M` 就漏过去了。
1138
- /^journalctl\b(?!.*--(?:vacuum-size|vacuum-time|vacuum-files|rotate|flush|sync)\b)/i
1139
- ]
1140
- };
1141
- WINDOWS_TRAITS = {
1142
- writeKeywords: [
1143
- "write",
1144
- "\u5199\u5165",
1145
- "save",
1146
- "\u4FDD\u5B58",
1147
- "modify",
1148
- "\u4FEE\u6539",
1149
- "update",
1150
- "\u66F4\u65B0",
1151
- "delete",
1152
- "\u5220\u9664",
1153
- "remove",
1154
- "\u79FB\u9664",
1155
- "create",
1156
- "\u521B\u5EFA",
1157
- "append",
1158
- "\u8FFD\u52A0",
1159
- "overwrite",
1160
- "\u8986\u76D6",
1161
- "replace",
1162
- "\u66FF\u6362",
1163
- "set",
1164
- "\u8BBE\u7F6E"
1165
- // 注意:这里**不能放 `reg` / `注册表`**。关键词是子串包含匹配,放进去等于
1166
- // 「只要命令里出现 reg 字样就算写操作」——`reg query`(读)会被判成写,
1167
- // 进而误拦对 deny_write 路径的只读查询。注册表的写判定交给下面的
1168
- // writeCommandRes(只认写子命令)。两端语义必须一致。
1169
- ],
1170
- readKeywords: [
1171
- "read",
1172
- "\u8BFB\u53D6",
1173
- "type",
1174
- "findstr",
1175
- "dir",
1176
- "view",
1177
- "\u67E5\u770B",
1178
- "open",
1179
- "\u6253\u5F00",
1180
- "load",
1181
- "\u52A0\u8F7D",
1182
- "search",
1183
- "\u641C\u7D22",
1184
- "find",
1185
- "\u67E5\u627E",
1186
- "get",
1187
- "\u83B7\u53D6"
1188
- ],
1189
- // Windows 写/删命令:del/copy/move/xcopy/replace/rmdir/icacls/reg delete 等
1190
- writeCommandRes: [
1191
- /\b(del|erase)\b/,
1192
- /\brmdir\b/,
1193
- /\b(rd)\b/,
1194
- /\bcopy\b/,
1195
- /\b(xcopy|robocopy)\b/,
1196
- /\b(move|movefile)\b/,
1197
- /\breplace\b/,
1198
- /\bicacls\b/,
1199
- /\battrib\b/,
1200
- // 注册表:**只认写子命令**(对齐 Python WindowsProfile.write_command_res)。
1201
- // 此前是 /\breg\b/,把 reg query(读)也判成写 → 误拦对 deny_write 路径的只读查询。
1202
- // 写子命令 add/delete/copy/save/restore/import/load/unload;
1203
- // 读子命令 query/compare/export/flags/help 不在此列。
1204
- /\breg(?:\.exe)?\s+(?:add|delete|copy|save|restore|import|load|unload)\b/,
1205
- /\bformat\b/,
1206
- /\bdiskpart\b/,
1207
- /\bmkdir\b/,
1208
- /\bmd\b/,
1209
- /\bsetx\b/,
1210
- /\bwmic\b/
1211
- ],
1212
- // Windows 只读命令(重定向写判定用):type/findstr/more/tree/dir 等
1213
- readOnlyCommands: /* @__PURE__ */ new Set([
1214
- "type",
1215
- "findstr",
1216
- "more",
1217
- "tree",
1218
- "dir",
1219
- "where",
1220
- "ver",
1221
- "cls",
1222
- "echo",
1223
- "help",
1224
- "fc",
1225
- "comp",
1226
- "find",
1227
- "sort",
1228
- "powershell",
1229
- "cmd",
1230
- "netstat",
1231
- "ipconfig",
1232
- "tasklist",
1233
- "systeminfo",
1234
- "driverquery",
1235
- "reg",
1236
- "wmic",
1237
- "whoami",
1238
- "hostname",
1239
- "getmac",
1240
- "query",
1241
- "w32tm",
1242
- "diskpart"
1243
- ]),
1244
- // Windows 完全安全命令(对齐 WindowsProfile.fully_safe_commands)
1245
- fullySafeCommands: [
1246
- /^dir(?:\s|$)/,
1247
- /^where\s+/,
1248
- /^echo\s+/,
1249
- /^cd(?:\s|$)/,
1250
- /^tree(?:\s|$)/,
1251
- /^help(?:\s|$)/,
1252
- /^cls(?:\s|$)/,
1253
- /^ver(?:\s|$)/,
1254
- // 系统信息查询(只读,不读文件内容、无状态变更选项),与 Linux 侧 df/free 对齐
1255
- /^whoami(?:\s|$)/,
1256
- /^hostname(?:\s|$)/,
1257
- /^systeminfo(?:\s|$)/,
1258
- /^tasklist(?:\s|$)/,
1259
- /^getmac(?:\s|$)/,
1260
- /^driverquery(?:\s|$)/,
1261
- /^netstat(?:\s|$)/
1262
- ],
1263
- // Windows 安全但可能访问敏感路径的命令(对齐 WindowsProfile.readonly_sensitive_commands)
1264
- readonlySensitiveCommands: [
1265
- /^type\s+/,
1266
- /^findstr\s+/,
1267
- // 只读子命令级白名单(匹配「第一段命令」而非首词);
1268
- // sc create/delete/start/stop、reg add/delete/set 均不在此列。
1269
- /^sc\s+query\b/,
1270
- /^reg\s+query\b/
1271
- ]
1272
- };
1273
- MACOS_TRAITS = LINUX_TRAITS;
1274
- HARMONY_TRAITS = LINUX_TRAITS;
1423
+ init_rules_loader();
1424
+ traitsCache = /* @__PURE__ */ new Map();
1425
+ LINUX_TRAITS = traitsFor("linux");
1426
+ WINDOWS_TRAITS = traitsFor("windows");
1427
+ MACOS_TRAITS = traitsFor("macos");
1428
+ HARMONY_TRAITS = traitsFor("harmony");
1275
1429
  }
1276
1430
  });
1277
1431
 
@@ -1430,7 +1584,6 @@ function checkIntentConsistency(input) {
1430
1584
  const { aNext, promptSession, promptHistory } = input;
1431
1585
  const actionDetail = aNext.actionDetail.toLowerCase();
1432
1586
  const actionType = aNext.actionType.toLowerCase();
1433
- const EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
1434
1587
  if (EXCLUDED_PREFIXES.some((p) => actionType.startsWith(p))) return null;
1435
1588
  if (INLINE_SCRIPT_RE.test(actionDetail)) return null;
1436
1589
  const intentTexts = collectIntentTexts(promptSession, promptHistory);
@@ -1458,7 +1611,6 @@ function runLogicCheck(input, opts) {
1458
1611
  const actionDetail = aNext.actionDetail.toLowerCase();
1459
1612
  const actionType = aNext.actionType.toLowerCase();
1460
1613
  const resolvedCwd = cwd || "/";
1461
- const EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
1462
1614
  const isExcludedType = EXCLUDED_PREFIXES.some((p) => actionType.startsWith(p));
1463
1615
  if (!opts?.skipReadBeforeWrite) {
1464
1616
  const rbwResult = checkReadBeforeWrite(actionType, actionDetail, actionHistory, input.reason, resolvedCwd, input.osType);
@@ -1570,7 +1722,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
1570
1722
  const isBashLike = actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash";
1571
1723
  const isWrite = isBashLike ? isWriteOperation(actionType, actionDetail, void 0, osType) : getPlatformTraits(osType).writeKeywords.some((kw) => actionType.includes(kw));
1572
1724
  if (!isWrite) return null;
1573
- const WRITE_CMDS = /* @__PURE__ */ new Set(["cp", "mv", "install", "tee", "dd", "truncate", "mkfs", "fdisk", "parted"]);
1725
+ const WRITE_CMDS = new Set(_LOGIC.write_commands ?? []);
1574
1726
  if (isBashLike) {
1575
1727
  const detailNoCompare = actionDetail.replace(/<=>|>=|<=|<>|=>/g, " ");
1576
1728
  const detailNoDevnull = detailNoCompare.replace(DEVNULL_REDIRECT_RE, "");
@@ -1602,7 +1754,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
1602
1754
  }
1603
1755
  if (!hasWriteSeg) return null;
1604
1756
  }
1605
- const SOURCE_ARG_CMDS = /* @__PURE__ */ new Set(["cp", "mv", "install", "rsync"]);
1757
+ const SOURCE_ARG_CMDS = new Set(_LOGIC.source_arg_commands ?? []);
1606
1758
  const REDIRECT_TARGET_RE = new RegExp(`>{1,2}\\s*(?!&)(?:'(/[^']+)'|"(/[^\\"]+)"|(/[^\\s;|&><'\\"]+))`);
1607
1759
  const targetPaths = [];
1608
1760
  for (const seg of splitCommandSegments(actionDetail)) {
@@ -1637,7 +1789,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
1637
1789
  targetPaths.push(...segPathsFiltered);
1638
1790
  }
1639
1791
  }
1640
- const uniqueTargetPaths = [...new Set(targetPaths)];
1792
+ const uniqueTargetPaths = [...new Set(targetPaths)].filter((p) => !PSEUDO_FS_PREFIXES.some((pfx) => p.startsWith(pfx)));
1641
1793
  if (uniqueTargetPaths.length === 0) return null;
1642
1794
  const historyReadPaths = /* @__PURE__ */ new Set();
1643
1795
  for (const action of actionHistory) {
@@ -1650,7 +1802,6 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
1650
1802
  }
1651
1803
  }
1652
1804
  }
1653
- const TEMP_DIRS = ["/tmp/", "/var/tmp/", "/dev/shm/", "/run/"];
1654
1805
  let filteredPaths = uniqueTargetPaths;
1655
1806
  if (/\bdd\b/i.test(actionDetail) && /\bif=\/dev\/zero\b/i.test(actionDetail)) {
1656
1807
  filteredPaths = uniqueTargetPaths.filter(
@@ -1751,10 +1902,10 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd, osType) {
1751
1902
  if (!hasTraversal) {
1752
1903
  return null;
1753
1904
  }
1754
- const READ_KEYWORDS = ["read(", "readline", "readlines", "fread", "fgets", "load(", "getpass", "file_get_contents"];
1755
- const WRITE_KEYWORDS2 = ["write(", "writelines", "fwrite", "file_put_contents"];
1756
- const EXEC_KEYWORDS = ["system(", "exec(", "eval(", "popen(", "os.system", "os.popen", "subprocess", "os.exec", "os.spawn", "shell=true", "check_output", "check_call", "run(", "passthru", "shell_exec", "proc_open"];
1757
- const OPEN_KEYWORDS = ["open(", "fopen("];
1905
+ const READ_KEYWORDS = IFA_READ_KEYWORDS;
1906
+ const WRITE_KEYWORDS2 = IFA_WRITE_KEYWORDS;
1907
+ const EXEC_KEYWORDS = IFA_EXEC_KEYWORDS;
1908
+ const OPEN_KEYWORDS = IFA_OPEN_KEYWORDS;
1758
1909
  const code = extractInlineCode(actionDetail);
1759
1910
  if (!code) return null;
1760
1911
  const codeLower = code.toLowerCase();
@@ -1817,7 +1968,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd, osType) {
1817
1968
  violatedLayers: ["L2.1"]
1818
1969
  };
1819
1970
  }
1820
- var ROOT_SENSITIVE_PATHS, HOME_SENSITIVE_PATHS, CREDENTIAL_PATHS, ROOT_SENSITIVE_PATHS_WINDOWS, HOME_SENSITIVE_PATHS_WINDOWS, CREDENTIAL_PATHS_WINDOWS, ROOT_SENSITIVE_PATHS_MACOS, HOME_SENSITIVE_PATHS_MACOS, CREDENTIAL_PATHS_MACOS, ROOT_SENSITIVE_PATHS_HARMONY, HOME_SENSITIVE_PATHS_HARMONY, CREDENTIAL_PATHS_HARMONY, _scopeIndex, NON_WRITE_TOOLS, NON_WRITE_TOOL_PREFIXES, WRITE_KEYWORDS, SED_INPLACE_RE, DEVNULL_REDIRECT_RE, CREATE_KEYWORDS, PASSWORD_MODIFY_PATTERNS, USER_DELETION_PATTERNS, INTENT_DEVIATION_PATTERNS, COMBO_STAGES, COMBO_CHAINS, INLINE_SCRIPT_RE, DIR_TRAVERSAL_KW;
1971
+ 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;
1821
1972
  var init_logic_rules = __esm({
1822
1973
  "src/engine/logic-rules.ts"() {
1823
1974
  "use strict";
@@ -1827,6 +1978,10 @@ var init_logic_rules = __esm({
1827
1978
  init_rules_loader();
1828
1979
  init_platform_traits();
1829
1980
  init_shell_split();
1981
+ _LOGIC = loadLogicRules();
1982
+ _HEURISTIC = loadHeuristicRules();
1983
+ EXCLUDED_PREFIXES = _HEURISTIC.sensitive_excluded_prefixes ?? [];
1984
+ TEMP_DIRS = (_HEURISTIC.temp_dirs ?? []).map((d) => d + "/");
1830
1985
  ROOT_SENSITIVE_PATHS = _loadSensitivePathScope("root");
1831
1986
  HOME_SENSITIVE_PATHS = _loadSensitivePathScope("home");
1832
1987
  CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path6, riskLevel, desc, dirDelOnly }) => ({ path: path6, riskLevel, desc, credential: true, dirDelOnly }));
@@ -1847,7 +2002,7 @@ var init_logic_rules = __esm({
1847
2002
  "list_dir",
1848
2003
  "count_text_length",
1849
2004
  "search",
1850
- // filemgr 只读三件套(globfiles 在 coordinator FULLY_SAFE_TOOLS 提前放行,此处兜底防御非 coordinator 入口)
2005
+ // filemgr 只读三件套(globfiles 在 platform-traits 的 safeActionTypes 提前放行,此处兜底防御非 coordinator 入口)
1851
2006
  "filemgr-globfiles",
1852
2007
  "filemgr-readfile",
1853
2008
  "filemgr-grepfiles",
@@ -1859,7 +2014,8 @@ var init_logic_rules = __esm({
1859
2014
  "tail",
1860
2015
  "cat"
1861
2016
  ]);
1862
- NON_WRITE_TOOL_PREFIXES = ["memory-", "skill-manager-", "mailbox-", "app-"];
2017
+ NON_WRITE_TOOL_PREFIXES = _LOGIC.non_write_tool_prefixes ?? [];
2018
+ PSEUDO_FS_PREFIXES = ["/sys/", "/proc/", "/dev/", "/run/"];
1863
2019
  WRITE_KEYWORDS = ["write", "\u5199\u5165", "save", "\u4FDD\u5B58", "modify", "\u4FEE\u6539", "update", "\u66F4\u65B0", "delete", "\u5220\u9664", "remove", "\u79FB\u9664", "create", "\u521B\u5EFA", "append", "\u8FFD\u52A0", "truncate", "\u622A\u65AD", "overwrite", "\u8986\u76D6", "replace", "\u66FF\u6362", "edit"];
1864
2020
  SED_INPLACE_RE = /\bsed\s+(?:-\S+\s+)*(?:-[a-zA-Z]*i[a-zA-Z]*|--in-place)\b/i;
1865
2021
  DEVNULL_REDIRECT_RE = /(?:2|&|1)?\s*>\s*\/dev\/null\b/g;
@@ -1900,33 +2056,32 @@ var init_logic_rules = __esm({
1900
2056
  }));
1901
2057
  })();
1902
2058
  INLINE_SCRIPT_RE = /(?:^|\s)(?:python|python3|python3\.\d+|perl|perl5|ruby|jruby|mruby|node|php|lua)\s+(?:-[a-zA-Z]*[cer][a-zA-Z]*\s+)/i;
1903
- DIR_TRAVERSAL_KW = [
1904
- "os.listdir",
1905
- "os.walk",
1906
- "os.scandir",
1907
- "glob.glob",
1908
- "glob.iglob",
1909
- "pathlib.",
1910
- "iterdir",
1911
- "rglob",
1912
- "scandir(",
1913
- "listdir("
1914
- ];
2059
+ DIR_TRAVERSAL_KW = loadInline().dir_traversal_keywords ?? [];
2060
+ _IFA = loadLogicRules().indirect_file_access ?? {};
2061
+ IFA_READ_KEYWORDS = _IFA.read_keywords ?? [];
2062
+ IFA_WRITE_KEYWORDS = _IFA.write_keywords ?? [];
2063
+ IFA_EXEC_KEYWORDS = _IFA.exec_keywords ?? [];
2064
+ IFA_OPEN_KEYWORDS = _IFA.open_keywords ?? [];
1915
2065
  }
1916
2066
  });
1917
2067
 
1918
2068
  // src/engine/asset-dir.ts
1919
- import { existsSync as existsSync3 } from "fs";
1920
- import { resolve as resolve2 } from "path";
2069
+ import { existsSync as existsSync5 } from "fs";
2070
+ import { join as join5, resolve as resolve3 } from "path";
1921
2071
  function assetDirCandidates(moduleDir, name) {
2072
+ const root = getRulesRoot();
2073
+ if (root) {
2074
+ const p = join5(root, name);
2075
+ return [p, p];
2076
+ }
1922
2077
  return [
1923
- resolve2(moduleDir, name),
1924
- resolve2(moduleDir, "../../..", "agent_moss", name)
2078
+ resolve3(moduleDir, name),
2079
+ resolve3(moduleDir, "../../..", "agent_moss", name)
1925
2080
  ];
1926
2081
  }
1927
2082
  function resolveAssetDir(moduleDir, name) {
1928
2083
  for (const dir of assetDirCandidates(moduleDir, name)) {
1929
- if (existsSync3(dir)) return dir;
2084
+ if (existsSync5(dir)) return dir;
1930
2085
  }
1931
2086
  return null;
1932
2087
  }
@@ -1934,12 +2089,13 @@ var init_asset_dir = __esm({
1934
2089
  "src/engine/asset-dir.ts"() {
1935
2090
  "use strict";
1936
2091
  init_esm_shims();
2092
+ init_paths();
1937
2093
  }
1938
2094
  });
1939
2095
 
1940
2096
  // src/engine/skill-loader.ts
1941
- import { readFileSync as readFileSync3, readdirSync as readdirSync3, existsSync as existsSync4 } from "fs";
1942
- import { resolve as resolve3, dirname as dirname2 } from "path";
2097
+ import { readFileSync as readFileSync5, readdirSync as readdirSync3, existsSync as existsSync6 } from "fs";
2098
+ import { resolve as resolve4, dirname as dirname3 } from "path";
1943
2099
  import { fileURLToPath as fileURLToPath3 } from "url";
1944
2100
  function loadAllSkills() {
1945
2101
  if (cachedSkills) return cachedSkills;
@@ -1953,13 +2109,13 @@ function loadAllSkills() {
1953
2109
  const files = readdirSync3(dir).filter((f) => f.endsWith(".md"));
1954
2110
  for (const f of files) {
1955
2111
  const name = f.replace(".md", "");
1956
- cachedSkills[name] = readFileSync3(resolve3(dir, f), "utf-8");
2112
+ cachedSkills[name] = readFileSync5(resolve4(dir, f), "utf-8");
1957
2113
  }
1958
2114
  console.log(`[AgentMoss] Loaded ${Object.keys(cachedSkills).length} skills from ${dir}`);
1959
2115
  return cachedSkills;
1960
2116
  }
1961
2117
  function userSkillsDir() {
1962
- return resolve3(getDataRoot(), "agent_moss_skills");
2118
+ return resolve4(getDataRoot(), "agent_moss_skills");
1963
2119
  }
1964
2120
  function runtimeCustomSkillKeywords() {
1965
2121
  if (isRuntimeConfigDisabled()) return {};
@@ -1983,8 +2139,8 @@ function loadUserSkill(name) {
1983
2139
  if (isRuntimeConfigDisabled()) return "";
1984
2140
  if (userSkillCache[name] !== void 0) return userSkillCache[name];
1985
2141
  try {
1986
- const p = resolve3(userSkillsDir(), `${name}.md`);
1987
- userSkillCache[name] = existsSync4(p) ? readFileSync3(p, "utf-8") : "";
2142
+ const p = resolve4(userSkillsDir(), `${name}.md`);
2143
+ userSkillCache[name] = existsSync6(p) ? readFileSync5(p, "utf-8") : "";
1988
2144
  } catch {
1989
2145
  userSkillCache[name] = "";
1990
2146
  }
@@ -2053,7 +2209,7 @@ var init_skill_loader = __esm({
2053
2209
  init_asset_dir();
2054
2210
  init_runtime_config_bridge();
2055
2211
  __filename3 = fileURLToPath3(import.meta.url);
2056
- __dirname3 = dirname2(__filename3);
2212
+ __dirname3 = dirname3(__filename3);
2057
2213
  SKILL_KEYWORD_MAP = (() => {
2058
2214
  const map = {};
2059
2215
  const cats = loadL3();
@@ -2133,10 +2289,10 @@ __export(runtime_config_exports, {
2133
2289
  updateSkillCategoryEnabled: () => updateSkillCategoryEnabled,
2134
2290
  updateSkillEnabled: () => updateSkillEnabled
2135
2291
  });
2136
- import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync5, mkdirSync, unlinkSync, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
2137
- import { join as join3 } from "path";
2292
+ import { readFileSync as readFileSync6, writeFileSync, existsSync as existsSync7, mkdirSync as mkdirSync2, unlinkSync, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
2293
+ import { join as join6 } from "path";
2138
2294
  import { fileURLToPath as fileURLToPath4 } from "url";
2139
- import { dirname as dirname3 } from "path";
2295
+ import { dirname as dirname4 } from "path";
2140
2296
  function runtimeDir() {
2141
2297
  return getRuntimeConfigDir();
2142
2298
  }
@@ -2163,7 +2319,7 @@ function saveRuntimeConfig(config) {
2163
2319
  }
2164
2320
  const p = getRuntimeConfigPath();
2165
2321
  const dir = p.substring(0, p.lastIndexOf("/"));
2166
- if (dir && !existsSync5(dir)) mkdirSync(dir, { recursive: true });
2322
+ if (dir && !existsSync7(dir)) mkdirSync2(dir, { recursive: true });
2167
2323
  writeFileSync(p, JSON.stringify(config, null, 2), "utf-8");
2168
2324
  invalidateRuntimeConfigCache();
2169
2325
  }
@@ -2197,7 +2353,7 @@ function loadRuntimeConfig() {
2197
2353
  };
2198
2354
  }
2199
2355
  const p = getRuntimeConfigPath();
2200
- if (!existsSync5(p)) {
2356
+ if (!existsSync7(p)) {
2201
2357
  const seeded = mergeConfig({}, generateSourceDefaults());
2202
2358
  try {
2203
2359
  saveRuntimeConfig(seeded);
@@ -2211,7 +2367,7 @@ function loadRuntimeConfig() {
2211
2367
  if (runtimeConfigCache && runtimeConfigCache.key === cacheKey) {
2212
2368
  return runtimeConfigCache.value;
2213
2369
  }
2214
- const data = JSON.parse(readFileSync4(p, "utf-8"));
2370
+ const data = JSON.parse(readFileSync6(p, "utf-8"));
2215
2371
  const merged = mergeConfig(data, generateSourceDefaults());
2216
2372
  const result = {
2217
2373
  version: data.version || 1,
@@ -2269,7 +2425,10 @@ function filterEnabledRules(rules, categoryEnabled = true) {
2269
2425
  }
2270
2426
  function _filterRulesByOs(rules, osType) {
2271
2427
  const accepted = acceptedOsValues(osType);
2272
- return rules.filter((r) => accepted.includes(String(r.os || "linux").toLowerCase()));
2428
+ return rules.filter((r) => {
2429
+ const os4 = String(r.os || "linux").toLowerCase();
2430
+ return os4 === "all" || accepted.includes(os4);
2431
+ });
2273
2432
  }
2274
2433
  function _userCustomRules(runtime, layers, requiredKey) {
2275
2434
  const list = Array.isArray(layers) ? layers : [layers];
@@ -2356,7 +2515,7 @@ function hasRuntimeConfig() {
2356
2515
  if (cached2 !== void 0) return cached2;
2357
2516
  let ok = false;
2358
2517
  try {
2359
- JSON.parse(readFileSync4(p, "utf-8"));
2518
+ JSON.parse(readFileSync6(p, "utf-8"));
2360
2519
  ok = true;
2361
2520
  } catch {
2362
2521
  ok = false;
@@ -2456,6 +2615,9 @@ function _l1InjectionKeywordsToRules() {
2456
2615
  keyword: kw.keyword,
2457
2616
  risk_level: kw.riskLevel,
2458
2617
  lang: kw.lang || "en",
2618
+ // 带上条目自己的 os(`all` = 全平台通用),别丢 —— 丢了 Windows 会拿到
2619
+ // 本不该给它的条目。与 Python `_l1_injection_keywords_to_rules` 对齐。
2620
+ os: kw.os,
2459
2621
  enabled: true,
2460
2622
  builtin: true
2461
2623
  }));
@@ -2468,7 +2630,7 @@ function _l2SensitivePathsToRules() {
2468
2630
  const all = [...root, ...home, ...credential];
2469
2631
  for (const sp of all) {
2470
2632
  const os4 = String(sp.os || "linux").toLowerCase();
2471
- const denyMode = _deriveDenyMode({
2633
+ const denyMode = sp.denyMode || _deriveDenyMode({
2472
2634
  deny_mode: sp.deny_mode,
2473
2635
  credential: sp.credential,
2474
2636
  read_only: sp.read_only
@@ -2573,8 +2735,9 @@ function _l3SkillsToRules() {
2573
2735
  }
2574
2736
  function _scenarioRulesToDefaults() {
2575
2737
  const out = {};
2576
- const scenDir = existsSync5(_SCENARIOS_DIR_PROD) ? _SCENARIOS_DIR_PROD : existsSync5(_SCENARIOS_DIR_DEV) ? _SCENARIOS_DIR_DEV : _SCENARIOS_DIR_PROD;
2577
- if (!existsSync5(scenDir)) return out;
2738
+ const _root = getRulesRoot();
2739
+ const scenDir = _root ? join6(_root, "scenarios") : existsSync7(_SCENARIOS_DIR_PROD) ? _SCENARIOS_DIR_PROD : existsSync7(_SCENARIOS_DIR_DEV) ? _SCENARIOS_DIR_DEV : _SCENARIOS_DIR_PROD;
2740
+ if (!existsSync7(scenDir)) return out;
2578
2741
  let entries;
2579
2742
  try {
2580
2743
  entries = readdirSync4(scenDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
@@ -2582,11 +2745,11 @@ function _scenarioRulesToDefaults() {
2582
2745
  return out;
2583
2746
  }
2584
2747
  for (const scenarioId of entries) {
2585
- const rulesFile = resolveScenarioRulesFile(scenarioId) ?? join3(scenDir, scenarioId, "rules.json");
2586
- if (!existsSync5(rulesFile)) continue;
2748
+ const rulesFile = resolveScenarioRulesFile(scenarioId) ?? join6(scenDir, scenarioId, "rules.json");
2749
+ if (!existsSync7(rulesFile)) continue;
2587
2750
  let data;
2588
2751
  try {
2589
- data = JSON.parse(readFileSync4(rulesFile, "utf-8"));
2752
+ data = JSON.parse(readFileSync6(rulesFile, "utf-8"));
2590
2753
  } catch {
2591
2754
  continue;
2592
2755
  }
@@ -2622,8 +2785,9 @@ function _scenarioRulesToDefaults() {
2622
2785
  }
2623
2786
  function _agentRulesToDefaults() {
2624
2787
  const out = {};
2625
- const agentsDir = existsSync5(_AGENTS_DIR_PROD) ? _AGENTS_DIR_PROD : existsSync5(_AGENTS_DIR_DEV) ? _AGENTS_DIR_DEV : _AGENTS_DIR_PROD;
2626
- if (!existsSync5(agentsDir)) return out;
2788
+ const _root = getRulesRoot();
2789
+ const agentsDir = _root ? join6(_root, "agents") : existsSync7(_AGENTS_DIR_PROD) ? _AGENTS_DIR_PROD : existsSync7(_AGENTS_DIR_DEV) ? _AGENTS_DIR_DEV : _AGENTS_DIR_PROD;
2790
+ if (!existsSync7(agentsDir)) return out;
2627
2791
  let entries;
2628
2792
  try {
2629
2793
  entries = readdirSync4(agentsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
@@ -2631,11 +2795,11 @@ function _agentRulesToDefaults() {
2631
2795
  return out;
2632
2796
  }
2633
2797
  for (const agentId of entries) {
2634
- const rulesFile = join3(agentsDir, agentId, "rules.json");
2635
- if (!existsSync5(rulesFile)) continue;
2798
+ const rulesFile = join6(agentsDir, agentId, "rules.json");
2799
+ if (!existsSync7(rulesFile)) continue;
2636
2800
  let data;
2637
2801
  try {
2638
- data = JSON.parse(readFileSync4(rulesFile, "utf-8"));
2802
+ data = JSON.parse(readFileSync6(rulesFile, "utf-8"));
2639
2803
  } catch {
2640
2804
  continue;
2641
2805
  }
@@ -3010,7 +3174,11 @@ function getDisabledSkillIds(runtime) {
3010
3174
  }
3011
3175
  function _filterDefaultsByOs(rules, osType) {
3012
3176
  const accepted = acceptedOsValues(osType);
3013
- return rules.filter((r) => !r.os || accepted.includes(String(r.os).toLowerCase()));
3177
+ return rules.filter((r) => {
3178
+ if (!r.os) return true;
3179
+ const os4 = String(r.os).toLowerCase();
3180
+ return os4 === "all" || accepted.includes(os4);
3181
+ });
3014
3182
  }
3015
3183
  function _hasRulesForOs(rules, osType) {
3016
3184
  return _filterRulesByOs(rules || [], osType || "linux").length > 0;
@@ -3239,12 +3407,12 @@ function addCustomSkill(skillId, category, keywords, content) {
3239
3407
  );
3240
3408
  }
3241
3409
  const runtime = loadRuntimeConfig();
3242
- const skillsDir = join3(getRuntimeConfigDir(), "agent_moss_skills");
3410
+ const skillsDir = join6(getRuntimeConfigDir(), "agent_moss_skills");
3243
3411
  try {
3244
- mkdirSync(skillsDir, { recursive: true });
3412
+ mkdirSync2(skillsDir, { recursive: true });
3245
3413
  } catch {
3246
3414
  }
3247
- writeFileSync(join3(skillsDir, `${skillId}.md`), content, "utf-8");
3415
+ writeFileSync(join6(skillsDir, `${skillId}.md`), content, "utf-8");
3248
3416
  const l3 = runtime.L3_skills || (runtime.L3_skills = {});
3249
3417
  const cat = l3[category] || (l3[category] = { category_enabled: true, category_desc: "", skills: [] });
3250
3418
  (cat.skills || (cat.skills = [])).push({ id: skillId, keywords, enabled: true, builtin: false });
@@ -3259,9 +3427,9 @@ function deleteCustomSkill(skillId, category) {
3259
3427
  if (idx >= 0) {
3260
3428
  if (skills[idx].builtin) return null;
3261
3429
  skills.splice(idx, 1);
3262
- const skillPath = join3(getRuntimeConfigDir(), "agent_moss_skills", `${skillId}.md`);
3430
+ const skillPath = join6(getRuntimeConfigDir(), "agent_moss_skills", `${skillId}.md`);
3263
3431
  try {
3264
- if (existsSync5(skillPath)) unlinkSync(skillPath);
3432
+ if (existsSync7(skillPath)) unlinkSync(skillPath);
3265
3433
  } catch {
3266
3434
  }
3267
3435
  saveRuntimeConfig(runtime);
@@ -3465,10 +3633,10 @@ var init_runtime_config = __esm({
3465
3633
  init_rules_loader();
3466
3634
  init_runtime_config_bridge();
3467
3635
  init_rules_mode();
3468
- __dirname4 = dirname3(fileURLToPath4(import.meta.url));
3636
+ __dirname4 = dirname4(fileURLToPath4(import.meta.url));
3469
3637
  ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
3470
3638
  RUNTIME_CONFIG_DIR = runtimeDir();
3471
- RUNTIME_CONFIG_PATH = join3(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
3639
+ RUNTIME_CONFIG_PATH = join6(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
3472
3640
  LAYER_DISABLE_ENV = {
3473
3641
  L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
3474
3642
  L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
@@ -3496,10 +3664,10 @@ var init_runtime_config = __esm({
3496
3664
  intent_consistency: ["intent_keywords", "reason"]
3497
3665
  };
3498
3666
  hasRuntimeConfigCache = /* @__PURE__ */ new Map();
3499
- _AGENTS_DIR_PROD = join3(__dirname4, "agents");
3500
- _AGENTS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "agents");
3501
- _SCENARIOS_DIR_PROD = join3(__dirname4, "scenarios");
3502
- _SCENARIOS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
3667
+ _AGENTS_DIR_PROD = join6(__dirname4, "agents");
3668
+ _AGENTS_DIR_DEV = join6(__dirname4, "..", "..", "..", "agent_moss", "agents");
3669
+ _SCENARIOS_DIR_PROD = join6(__dirname4, "scenarios");
3670
+ _SCENARIOS_DIR_DEV = join6(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
3503
3671
  _effectiveCache = /* @__PURE__ */ new WeakMap();
3504
3672
  SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
3505
3673
  registerRuntimeConfigBridge({
@@ -3837,16 +4005,23 @@ var init_config = __esm({
3837
4005
  });
3838
4006
 
3839
4007
  // src/agents/index.ts
3840
- import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
3841
- import { resolve as resolve4, dirname as dirname4 } from "path";
4008
+ import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
4009
+ import { resolve as resolve5, dirname as dirname5, join as join7 } from "path";
3842
4010
  import { fileURLToPath as fileURLToPath6 } from "url";
3843
4011
  function resolveAgentsDir() {
3844
- if (existsSync6(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
3845
- if (existsSync6(AGENTS_DIR_DEV_FALLBACK)) return AGENTS_DIR_DEV_FALLBACK;
4012
+ const root = getRulesRoot();
4013
+ if (root) return join7(root, "agents");
4014
+ if (existsSync8(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
4015
+ if (existsSync8(AGENTS_DIR_DEV_FALLBACK)) return AGENTS_DIR_DEV_FALLBACK;
3846
4016
  return AGENTS_DIR_PROD;
3847
4017
  }
3848
4018
  function getAgentExtensions(agentId) {
3849
4019
  if (!agentId) return null;
4020
+ if (!isSafeId(agentId)) {
4021
+ 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)}`);
4022
+ return null;
4023
+ }
4024
+ if (cache.size >= CACHE_MAX) cache.clear();
3850
4025
  const fp = getRuleSourceFingerprint();
3851
4026
  const cached2 = cache.get(agentId);
3852
4027
  if (cached2 && cached2.fp === fp) return cached2.ext;
@@ -3859,13 +4034,13 @@ function getAgentExtensions(agentId) {
3859
4034
  }
3860
4035
  if (ext === null) {
3861
4036
  const agentsDir = resolveAgentsDir();
3862
- const rulesFile = resolve4(agentsDir, agentId, "rules.json");
3863
- if (!existsSync6(rulesFile)) {
4037
+ const rulesFile = resolve5(agentsDir, agentId, "rules.json");
4038
+ if (!existsSync8(rulesFile)) {
3864
4039
  cache.set(agentId, { fp, ext: null });
3865
4040
  return null;
3866
4041
  }
3867
4042
  try {
3868
- const raw = readFileSync5(rulesFile, "utf-8");
4043
+ const raw = readFileSync7(rulesFile, "utf-8");
3869
4044
  ext = JSON.parse(raw);
3870
4045
  if (!ext.agent_id) {
3871
4046
  console.warn(`[AgentMoss agents] rules.json \u7F3A\u5C11 agent_id \u5B57\u6BB5: ${rulesFile}`);
@@ -3899,31 +4074,40 @@ function mergeAgentExtensions(...exts) {
3899
4074
  extra_skills: nonNull.flatMap((e) => e.extra_skills || [])
3900
4075
  };
3901
4076
  }
3902
- var __filename4, __dirname5, AGENTS_DIR_PROD, AGENTS_DIR_DEV_FALLBACK, cache;
4077
+ var __filename4, __dirname5, AGENTS_DIR_PROD, AGENTS_DIR_DEV_FALLBACK, cache, CACHE_MAX;
3903
4078
  var init_agents = __esm({
3904
4079
  "src/agents/index.ts"() {
3905
4080
  "use strict";
3906
4081
  init_esm_shims();
4082
+ init_paths();
3907
4083
  init_runtime_config();
3908
4084
  __filename4 = fileURLToPath6(import.meta.url);
3909
- __dirname5 = dirname4(__filename4);
3910
- AGENTS_DIR_PROD = resolve4(__dirname5, "agents");
3911
- AGENTS_DIR_DEV_FALLBACK = resolve4(__dirname5, "../../../agent_moss/agents");
4085
+ __dirname5 = dirname5(__filename4);
4086
+ AGENTS_DIR_PROD = resolve5(__dirname5, "agents");
4087
+ AGENTS_DIR_DEV_FALLBACK = resolve5(__dirname5, "../../../agent_moss/agents");
3912
4088
  cache = /* @__PURE__ */ new Map();
4089
+ CACHE_MAX = 512;
3913
4090
  }
3914
4091
  });
3915
4092
 
3916
4093
  // src/scenarios/index.ts
3917
- import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
3918
- import { resolve as resolve5, dirname as dirname5 } from "path";
4094
+ import { readFileSync as readFileSync8, existsSync as existsSync9 } from "fs";
4095
+ import { resolve as resolve6, dirname as dirname6, join as join8 } from "path";
3919
4096
  import { fileURLToPath as fileURLToPath7 } from "url";
3920
4097
  function resolveScenariosDir() {
3921
- if (existsSync7(SCENARIOS_DIR_PROD)) return SCENARIOS_DIR_PROD;
3922
- if (existsSync7(SCENARIOS_DIR_DEV_FALLBACK)) return SCENARIOS_DIR_DEV_FALLBACK;
4098
+ const root = getRulesRoot();
4099
+ if (root) return join8(root, "scenarios");
4100
+ if (existsSync9(SCENARIOS_DIR_PROD)) return SCENARIOS_DIR_PROD;
4101
+ if (existsSync9(SCENARIOS_DIR_DEV_FALLBACK)) return SCENARIOS_DIR_DEV_FALLBACK;
3923
4102
  return SCENARIOS_DIR_PROD;
3924
4103
  }
3925
4104
  function getScenarioExtensions(scenarioId) {
3926
4105
  if (!scenarioId) return null;
4106
+ if (!isSafeId(scenarioId)) {
4107
+ 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)}`);
4108
+ return null;
4109
+ }
4110
+ if (cache2.size >= CACHE_MAX2) cache2.clear();
3927
4111
  const fp = getRuleSourceFingerprint();
3928
4112
  const cached2 = cache2.get(scenarioId);
3929
4113
  if (cached2 && cached2.fp === fp) return cached2.ext;
@@ -3942,19 +4126,19 @@ function getScenarioExtensions(scenarioId) {
3942
4126
  }
3943
4127
  }
3944
4128
  if (ext === null) {
3945
- const builtinFile = resolve5(resolveScenariosDir(), scenarioId, "rules.json");
4129
+ const builtinFile = resolve6(resolveScenariosDir(), scenarioId, "rules.json");
3946
4130
  const rulesFile = overrideHit ?? builtinFile;
3947
4131
  if (overrideHit) {
3948
4132
  console.warn(
3949
4133
  `[AgentMoss] \u26A0\uFE0F \u573A\u666F ${scenarioId} \u89C4\u5219\u88AB\u8986\u76D6\u76EE\u5F55\u8986\u76D6: ${overrideHit}\uFF08\u5185\u7F6E\u9ED8\u8BA4 ${builtinFile} \u88AB\u5FFD\u7565\uFF09\u3002\u8BE5\u6587\u4EF6\u7531\u90E8\u7F72\u4FA7\u81EA\u884C\u7EF4\u62A4\uFF0C\u5185\u7F6E\u573A\u666F\u89C4\u5219\u66F4\u65B0\u4E0D\u4F1A\u53CD\u6620\u5230\u5B83\u4E0A\u9762\u3002`
3950
4134
  );
3951
4135
  }
3952
- if (!existsSync7(rulesFile)) {
4136
+ if (!existsSync9(rulesFile)) {
3953
4137
  cache2.set(scenarioId, { fp, ext: null });
3954
4138
  return null;
3955
4139
  }
3956
4140
  try {
3957
- const raw = readFileSync6(rulesFile, "utf-8");
4141
+ const raw = readFileSync8(rulesFile, "utf-8");
3958
4142
  const data = JSON.parse(raw);
3959
4143
  const id = data.scenario_id || data.agent_id || "";
3960
4144
  if (!id) {
@@ -3993,19 +4177,21 @@ function getScenarioExtensionsMulti(scenarioSpec) {
3993
4177
  if (nonNull.length === 0) return null;
3994
4178
  return mergeAgentExtensions(...nonNull);
3995
4179
  }
3996
- var __filename5, __dirname6, SCENARIOS_DIR_PROD, SCENARIOS_DIR_DEV_FALLBACK, cache2;
4180
+ var __filename5, __dirname6, SCENARIOS_DIR_PROD, SCENARIOS_DIR_DEV_FALLBACK, cache2, CACHE_MAX2;
3997
4181
  var init_scenarios = __esm({
3998
4182
  "src/scenarios/index.ts"() {
3999
4183
  "use strict";
4000
4184
  init_esm_shims();
4185
+ init_paths();
4001
4186
  init_agents();
4002
4187
  init_runtime_config();
4003
4188
  init_rules_loader();
4004
4189
  __filename5 = fileURLToPath7(import.meta.url);
4005
- __dirname6 = dirname5(__filename5);
4006
- SCENARIOS_DIR_PROD = resolve5(__dirname6, "scenarios");
4007
- SCENARIOS_DIR_DEV_FALLBACK = resolve5(__dirname6, "../../../agent_moss/scenarios");
4190
+ __dirname6 = dirname6(__filename5);
4191
+ SCENARIOS_DIR_PROD = resolve6(__dirname6, "scenarios");
4192
+ SCENARIOS_DIR_DEV_FALLBACK = resolve6(__dirname6, "../../../agent_moss/scenarios");
4008
4193
  cache2 = /* @__PURE__ */ new Map();
4194
+ CACHE_MAX2 = 512;
4009
4195
  }
4010
4196
  });
4011
4197
 
@@ -4404,7 +4590,7 @@ function severityRank(s) {
4404
4590
  }
4405
4591
  function isSearchActionType(actionType) {
4406
4592
  const at = actionType.toLowerCase();
4407
- return SEARCH_ACTION_PREFIXES.some((p) => at.startsWith(p)) || ["websearch", "web_search", "search", "query"].includes(at);
4593
+ return SEARCH_ACTION_PREFIXES.some((p) => at.startsWith(p)) || SEARCH_ACTION_EXACT.includes(at);
4408
4594
  }
4409
4595
  function checkSensitivePath(command, cwd, agentExt, osType) {
4410
4596
  const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
@@ -4694,8 +4880,7 @@ function isSshKeygenGenerate(command) {
4694
4880
  function checkDangerousPatterns(actionType, actionDetail) {
4695
4881
  if (actionType === "file_write" || actionType === "file_edit") return null;
4696
4882
  const scan = stripHeredocContent(actionDetail);
4697
- const TEMP_DIRS = ["/tmp", "/var/tmp", "/run", "/dev/shm"];
4698
- const isTempPath = TEMP_DIRS.some(
4883
+ const isTempPath = TEMP_DIRS2.some(
4699
4884
  (td) => new RegExp(`(?:^|[\\s/])${td.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[\\s/]|$)`).test(actionDetail)
4700
4885
  ) || /(?:^|[\s;]|&&\s*|\|\|\s*)var\/tmp(?:[/\s;]|$)/.test(actionDetail);
4701
4886
  const wildcardDeleteRe = /(?:\brm\b|\b(r?mdir|del|delete|remove|rmdir|unlink)\b|\b删除\b|\b移除\b)[\s\S]{0,40}?[*?]|[*?][\s\S]{0,40}?\b(?:rm|del|delete|remove|rmdir|unlink)\b|rm\s+-[a-zA-Z]*[rf][a-zA-Z]*\s+\S*[*?]/i;
@@ -4841,7 +5026,7 @@ function checkLateralMovement(actionDetail) {
4841
5026
  }
4842
5027
  return null;
4843
5028
  }
4844
- var INJECTION_KEYWORDS, DANGEROUS_PATTERNS2, DANGEROUS_PATTERNS_WINDOWS, DANGEROUS_PATTERNS_MACOS, DANGEROUS_PATTERNS_HARMONY, LOOPBACK_URL_RE, LOOPBACK_HOSTS, PATH_CONTEXT_TRIGGER_RE, HOME_PREFIX_RE, PY_EXEC_IO_RE, PY_PRINT_RE, PY_BIGNUM_RE, PY_INLINE_C_RE, PY_CMD_SUBST_C_RE, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES, HOME_TARGET_RE, HOME_ASSIGN_RE;
5029
+ var INJECTION_KEYWORDS, DANGEROUS_PATTERNS2, DANGEROUS_PATTERNS_WINDOWS, DANGEROUS_PATTERNS_MACOS, DANGEROUS_PATTERNS_HARMONY, LOOPBACK_URL_RE, HEURISTIC_RULES, LOOPBACK_HOSTS, TEMP_DIRS2, PATH_CONTEXT_TRIGGER_RE, HOME_PREFIX_RE, PY_EXEC_IO_RE, PY_PRINT_RE, PY_BIGNUM_RE, PY_INLINE_C_RE, PY_CMD_SUBST_C_RE, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES, SEARCH_ACTION_EXACT, HOME_TARGET_RE, HOME_ASSIGN_RE;
4845
5030
  var init_heuristic = __esm({
4846
5031
  "src/engine/heuristic.ts"() {
4847
5032
  "use strict";
@@ -4858,7 +5043,8 @@ var init_heuristic = __esm({
4858
5043
  return kws.map((k) => ({
4859
5044
  keyword: k.keyword,
4860
5045
  riskLevel: k.risk_level,
4861
- lang: k.lang || "en"
5046
+ lang: k.lang || "en",
5047
+ os: String(k.os || "all").toLowerCase()
4862
5048
  }));
4863
5049
  })();
4864
5050
  DANGEROUS_PATTERNS2 = _loadDangerousPatterns("linux");
@@ -4866,7 +5052,9 @@ var init_heuristic = __esm({
4866
5052
  DANGEROUS_PATTERNS_MACOS = _loadDangerousPatterns("macos");
4867
5053
  DANGEROUS_PATTERNS_HARMONY = _loadDangerousPatterns("harmony");
4868
5054
  LOOPBACK_URL_RE = /https?:\/\/([^\/\s;'"|&)]+)/i;
4869
- LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "::1", "0.0.0.0", "[::1]"]);
5055
+ HEURISTIC_RULES = loadHeuristicRules();
5056
+ LOOPBACK_HOSTS = new Set(HEURISTIC_RULES.loopback_hosts ?? []);
5057
+ TEMP_DIRS2 = HEURISTIC_RULES.temp_dirs ?? [];
4870
5058
  PATH_CONTEXT_TRIGGER_RE = /(?:^|[;&|]\s*)(?:rm|rmdir|unlink|ln|mv|cp|dd|truncate|shred|chmod|chown)\b/i;
4871
5059
  HOME_PREFIX_RE = /(?:~|~(?=\/)|\$HOME\b|\$\{HOME\}|\/home\/[a-z0-9_.-]+)/gi;
4872
5060
  PY_EXEC_IO_RE = /os\.system|os\.popen|subprocess|eval\s*\(|exec\s*\(|__import__|open\s*\(|socket|urllib|requests\.|shutil|os\.(?:remove|unlink|rename|makedirs?|listdir|scandir|walk|chmod|chown|kill|environ)|iterdir|glob\s*\(|path\(/i;
@@ -4874,16 +5062,17 @@ var init_heuristic = __esm({
4874
5062
  PY_BIGNUM_RE = /\d{6,}/;
4875
5063
  PY_INLINE_C_RE = /(python(?:3(?:\.\d+)?)?|perl5?|ruby|node|php|lua)\s+(?:-[a-zA-Z]+\s+)*-c\s+(?:"([^"]*)"|'([^']*)')/i;
4876
5064
  PY_CMD_SUBST_C_RE = /\$\(\s*(python(?:3(?:\.\d+)?)?|perl5?|ruby|node|php|lua)\s+(?:-[a-zA-Z]+\s+)*-c\s+(?:"([^"]*)"'|'([^']*)')\s*\)/i;
4877
- SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
4878
- SEARCH_ACTION_PREFIXES = ["search-", "web-", "browser-", "query-", "websearch", "web_search"];
5065
+ SENSITIVE_EXCLUDED_PREFIXES = HEURISTIC_RULES.sensitive_excluded_prefixes ?? [];
5066
+ SEARCH_ACTION_PREFIXES = HEURISTIC_RULES.search_action_prefixes ?? [];
5067
+ SEARCH_ACTION_EXACT = HEURISTIC_RULES.search_action_exact ?? [];
4879
5068
  HOME_TARGET_RE = /(?:^|[\s="'($])(?:~|(?:\$home|\$\{home\}))(?:\/(?:\.\*|\*|\.)?)?(?=[\s"';}|&)]|$)/i;
4880
5069
  HOME_ASSIGN_RE = /([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(~[A-Za-z0-9_./-]*|\$HOME[A-Za-z0-9_./-]*|\$\{HOME\}[A-Za-z0-9_./-]*|\/home\/[^\s;|&]+|\$\((?:[^()]|\([^()]*\))*\))(?=\s|;|$|\||&)/gi;
4881
5070
  }
4882
5071
  });
4883
5072
 
4884
5073
  // src/engine/template-loader.ts
4885
- import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
4886
- import { resolve as resolve6, dirname as dirname6 } from "path";
5074
+ import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
5075
+ import { resolve as resolve7, dirname as dirname7 } from "path";
4887
5076
  import { fileURLToPath as fileURLToPath8 } from "url";
4888
5077
  function loadTemplates() {
4889
5078
  if (cachedTemplates) return cachedTemplates;
@@ -4895,10 +5084,10 @@ function loadTemplates() {
4895
5084
  return cachedTemplates;
4896
5085
  }
4897
5086
  for (const file of TEMPLATE_FILES) {
4898
- const filePath = resolve6(dir, file);
4899
- if (existsSync8(filePath)) {
5087
+ const filePath = resolve7(dir, file);
5088
+ if (existsSync10(filePath)) {
4900
5089
  const key = file.replace(".txt", "").replace("_template", "");
4901
- cachedTemplates[key] = readFileSync7(filePath, "utf-8");
5090
+ cachedTemplates[key] = readFileSync9(filePath, "utf-8");
4902
5091
  }
4903
5092
  }
4904
5093
  console.log(`[AgentMoss] Loaded ${Object.keys(cachedTemplates).length} templates from ${dir}`);
@@ -4911,15 +5100,15 @@ var init_template_loader = __esm({
4911
5100
  init_esm_shims();
4912
5101
  init_asset_dir();
4913
5102
  __filename6 = fileURLToPath8(import.meta.url);
4914
- __dirname7 = dirname6(__filename6);
5103
+ __dirname7 = dirname7(__filename6);
4915
5104
  TEMPLATE_FILES = ["prompt1_template.txt", "prompt2_template.txt", "security_judge_template.txt"];
4916
5105
  cachedTemplates = null;
4917
5106
  }
4918
5107
  });
4919
5108
 
4920
5109
  // src/engine/script-analyzer.ts
4921
- import { readFileSync as readFileSync8 } from "fs";
4922
- import { resolve as resolve7, join as join4 } from "path";
5110
+ import { readFileSync as readFileSync10 } from "fs";
5111
+ import { resolve as resolve8, join as join9 } from "path";
4923
5112
  function emptyResult() {
4924
5113
  return { scriptPath: "", scriptContent: "", matchedKeywords: [], lineCount: 0, isSuspicious: false, error: "", riskLevel: "", riskReason: "" };
4925
5114
  }
@@ -4945,10 +5134,10 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
4945
5134
  const hitGroup2 = rule.group2.size === 0 ? true : [...rule.group2].some((k) => keywordSet.has(k));
4946
5135
  if (hitGroup1 && hitGroup2) return { level: rule.level, reason: rule.reason };
4947
5136
  }
4948
- const networkPostKeywords = /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]);
5137
+ const networkPostKeywords = new Set(KEYWORD_SETS.network_post ?? []);
4949
5138
  const hasNetworkPost = [...networkPostKeywords].some((k) => keywordSet.has(k));
4950
5139
  if (hasNetworkPost) {
4951
- const envPatterns = ["$env", "$(env)", "$USER", "$(hostname)", "$(pwd)", "$HOME"];
5140
+ const envPatterns = SCRIPT_RULES.env_exfil_patterns ?? [];
4952
5141
  const contentLower = scriptContent.toLowerCase();
4953
5142
  for (const p of envPatterns) {
4954
5143
  if (contentLower.includes(p.toLowerCase())) {
@@ -4972,8 +5161,8 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
4972
5161
  }
4973
5162
  function readScriptContent(scriptPath, maxLines = 500) {
4974
5163
  try {
4975
- const expandedPath = scriptPath === "~" || scriptPath.startsWith("~/") ? join4(process.env.HOME || "/home/user", scriptPath.slice(1)) : resolve7(scriptPath);
4976
- const raw = readFileSync8(expandedPath, "utf-8");
5164
+ const expandedPath = scriptPath === "~" || scriptPath.startsWith("~/") ? join9(process.env.HOME || "/home/user", scriptPath.slice(1)) : resolve8(scriptPath);
5165
+ const raw = readFileSync10(expandedPath, "utf-8");
4977
5166
  const lines = raw.split("\n").slice(0, maxLines);
4978
5167
  return { content: lines.join("\n"), lineCount: lines.length, error: "" };
4979
5168
  } catch (e) {
@@ -5022,90 +5211,28 @@ ${result.scriptContent}
5022
5211
  \`\`\`
5023
5212
  `;
5024
5213
  }
5025
- var SUSPICIOUS_PATTERNS, SCRIPT_EXEC_PATTERNS, KEYWORD_COMBINATION_RISKS, SINGLE_KEYWORD_RISKS, LEVEL_ORDER;
5214
+ var SCRIPT_RULES, SUSPICIOUS_PATTERNS, SCRIPT_EXEC_PATTERNS, KEYWORD_SETS, KEYWORD_COMBINATION_RISKS, SINGLE_KEYWORD_RISKS, LEVEL_ORDER;
5026
5215
  var init_script_analyzer = __esm({
5027
5216
  "src/engine/script-analyzer.ts"() {
5028
5217
  "use strict";
5029
5218
  init_esm_shims();
5030
- SUSPICIOUS_PATTERNS = [
5031
- // Network exfiltration (curl/wget POST in scripts is almost never legitimate)
5032
- { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
5033
- { name: "curl --data", pattern: /curl\s+(.*\s)?--data/i },
5034
- { name: "curl -d", pattern: /curl\s+(.*\s)?-d\s/i },
5035
- { name: "wget --post-data", pattern: /wget\s+(.*\s)?--post-data/i },
5036
- { name: "wget --post-file", pattern: /wget\s+(.*\s)?--post-file/i },
5037
- // Reverse shell
5038
- { name: "nc -e", pattern: /\bnc\s+.*-e\b/ },
5039
- { name: "ncat -e", pattern: /\bncat\s+.*-e\b/ },
5040
- { name: "/dev/tcp", pattern: /\/dev\/tcp\// },
5041
- { name: "/dev/udp", pattern: /\/dev\/udp\// },
5042
- // Base64 obfuscation
5043
- { name: "$(base64", pattern: /\$\(\s*base64/ },
5044
- { name: "`base64", pattern: /`\s*base64/ },
5045
- { name: "base64 decode pipe", pattern: /base64\s+(-d|--decode)\s*\|/ },
5046
- // Sensitive file access
5047
- { name: "/etc/shadow", pattern: /\/etc\/shadow/ },
5048
- { name: "id_rsa", pattern: /\bid_rsa\b/ },
5049
- { name: "id_ed25519", pattern: /\bid_ed25519\b/ },
5050
- // Persistence backdoor
5051
- { name: "crontab install", pattern: /crontab\s+-/ },
5052
- { name: "authorized_keys", pattern: /authorized_keys/ },
5053
- { name: "ssh no host key check", pattern: /ssh\s+.*StrictHostKeyChecking\s*=\s*no/ }
5054
- ];
5055
- SCRIPT_EXEC_PATTERNS = [
5056
- /bash\s+(\S+\.sh)/,
5057
- /sh\s+(\S+\.sh)/,
5058
- /\.\/(\S+\.sh)/,
5059
- /source\s+(\S+\.sh)/,
5060
- /\.\s+(\S+\.sh)/,
5061
- /zsh\s+(\S+\.sh)/
5062
- ];
5063
- KEYWORD_COMBINATION_RISKS = [
5064
- {
5065
- group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
5066
- group2: /* @__PURE__ */ new Set(["$(base64", "`base64", "base64 decode pipe"]),
5067
- level: "critical",
5068
- reason: "\u68C0\u6D4B\u5230\u7F51\u7EDC\u6570\u636E\u53D1\u9001 + Base64\u7F16\u7801\u7EC4\u5408\uFF0C\u6784\u6210 Critical \u7EA7\u522B\u6570\u636E\u5916\u4F20\u98CE\u9669\u3002\u653B\u51FB\u8005\u53EF\u80FD\u5C06\u654F\u611F\u6570\u636E\u7F16\u7801\u540E\u5916\u4F20\u3002"
5069
- },
5070
- {
5071
- group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
5072
- group2: /* @__PURE__ */ new Set(["/etc/shadow", "id_rsa", "id_ed25519"]),
5073
- level: "critical",
5074
- reason: "\u68C0\u6D4B\u5230\u7F51\u7EDC\u6570\u636E\u53D1\u9001 + \u654F\u611F\u6587\u4EF6\u8BBF\u95EE\u7EC4\u5408\uFF0C\u6784\u6210 Critical \u7EA7\u522B\u51ED\u8BC1\u5916\u4F20\u98CE\u9669\u3002"
5075
- },
5076
- {
5077
- group1: /* @__PURE__ */ new Set(["nc -e", "ncat -e", "/dev/tcp", "/dev/udp"]),
5078
- group2: /* @__PURE__ */ new Set(),
5079
- level: "high",
5080
- reason: "\u68C0\u6D4B\u5230\u53CD\u5F39 Shell \u7279\u5F81\uFF0C\u653B\u51FB\u8005\u53EF\u80FD\u5EFA\u7ACB\u8FDC\u7A0B\u63A7\u5236\u901A\u9053\u3002"
5081
- },
5082
- {
5083
- group1: /* @__PURE__ */ new Set(["crontab install", "authorized_keys", "ssh no host key check"]),
5084
- group2: /* @__PURE__ */ new Set(),
5085
- level: "high",
5086
- reason: "\u68C0\u6D4B\u5230\u6301\u4E45\u5316\u540E\u95E8\u7279\u5F81\uFF0C\u653B\u51FB\u8005\u53EF\u80FD\u5728\u7CFB\u7EDF\u4E2D\u690D\u5165\u540E\u95E8\u3002"
5087
- }
5088
- ];
5089
- SINGLE_KEYWORD_RISKS = {
5090
- "curl POST": { level: "high", reason: "\u68C0\u6D4B\u5230 curl POST \u8BF7\u6C42\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
5091
- "curl --data": { level: "high", reason: "\u68C0\u6D4B\u5230 curl --data \u53C2\u6570\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
5092
- "curl -d": { level: "high", reason: "\u68C0\u6D4B\u5230 curl -d \u53C2\u6570\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
5093
- "wget --post-data": { level: "high", reason: "\u68C0\u6D4B\u5230 wget POST \u8BF7\u6C42\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
5094
- "wget --post-file": { level: "high", reason: "\u68C0\u6D4B\u5230 wget \u6587\u4EF6\u4E0A\u4F20\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6587\u4EF6\u3002" },
5095
- "nc -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 nc -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
5096
- "ncat -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 ncat -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
5097
- "/dev/tcp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/tcp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
5098
- "/dev/udp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/udp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
5099
- "$(base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
5100
- "`base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
5101
- "base64 decode pipe": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u89E3\u7801\u540E\u7BA1\u9053\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
5102
- "/etc/shadow": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6\u3002" },
5103
- "id_rsa": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
5104
- "id_ed25519": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
5105
- "crontab install": { level: "high", reason: "\u68C0\u6D4B\u5230\u4FEE\u6539\u5B9A\u65F6\u4EFB\u52A1\uFF0C\u53EF\u80FD\u662F\u6301\u4E45\u5316\u540E\u95E8\u3002" },
5106
- "authorized_keys": { level: "high", reason: "\u68C0\u6D4B\u5230\u4FEE\u6539 SSH \u6388\u6743\u5BC6\u94A5\uFF0C\u53EF\u80FD\u662F\u6301\u4E45\u5316\u540E\u95E8\u3002" },
5107
- "ssh no host key check": { level: "high", reason: "\u68C0\u6D4B\u5230 SSH \u8DF3\u8FC7\u4E3B\u673A\u5BC6\u94A5\u9A8C\u8BC1\uFF0C\u53EF\u80FD\u662F\u6A2A\u5411\u79FB\u52A8\u3002" }
5108
- };
5219
+ init_rules_loader();
5220
+ SCRIPT_RULES = loadScriptRules();
5221
+ if (!SCRIPT_RULES || Object.keys(SCRIPT_RULES).length === 0) {
5222
+ console.error(
5223
+ "[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"
5224
+ );
5225
+ }
5226
+ SUSPICIOUS_PATTERNS = (SCRIPT_RULES.suspicious_patterns ?? []).map((r) => ({ name: r.name, pattern: compileRuleRegex(r) }));
5227
+ SCRIPT_EXEC_PATTERNS = ((SCRIPT_RULES.script_exec_patterns ?? {}).legacy ?? []).map((r) => compileRuleRegex(r));
5228
+ KEYWORD_SETS = SCRIPT_RULES.keyword_sets ?? {};
5229
+ KEYWORD_COMBINATION_RISKS = (SCRIPT_RULES.keyword_combination_risks ?? []).map((r) => ({
5230
+ group1: resolveKeywordSet(r.group1, KEYWORD_SETS),
5231
+ group2: resolveKeywordSet(r.group2, KEYWORD_SETS),
5232
+ level: String(r.level),
5233
+ reason: String(r.reason)
5234
+ }));
5235
+ SINGLE_KEYWORD_RISKS = SCRIPT_RULES.single_keyword_risks ?? {};
5109
5236
  LEVEL_ORDER = { low: 1, medium: 2, high: 3, critical: 4 };
5110
5237
  }
5111
5238
  });
@@ -5163,14 +5290,14 @@ function isSysArgv(node) {
5163
5290
  const prop = node.getChild("PropertyName");
5164
5291
  return prop !== null && srcSlice(prop) === "argv";
5165
5292
  }
5166
- function evalExpr(node, env) {
5293
+ function evalExpr(node, env2) {
5167
5294
  if (!node) return null;
5168
5295
  const name = node.name;
5169
5296
  if (name === "String") return stringValue(srcSlice(node));
5170
5297
  if (name === "Number") return srcSlice(node);
5171
5298
  if (name === "VariableName") {
5172
5299
  const id = srcSlice(node);
5173
- return id in env.vars ? env.vars[id] : null;
5300
+ return id in env2.vars ? env2.vars[id] : null;
5174
5301
  }
5175
5302
  if (name === "MemberExpression") {
5176
5303
  const bracket = node.getChild("[");
@@ -5180,13 +5307,13 @@ function evalExpr(node, env) {
5180
5307
  const idxNode = bracket.nextSibling;
5181
5308
  if (idxNode && idxNode.name === "Number") {
5182
5309
  const idx = parseInt(srcSlice(idxNode), 10);
5183
- if (idx >= 1 && idx <= env.argv.length) return env.argv[idx - 1];
5310
+ if (idx >= 1 && idx <= env2.argv.length) return env2.argv[idx - 1];
5184
5311
  return null;
5185
5312
  }
5186
5313
  }
5187
5314
  return null;
5188
5315
  }
5189
- if (isSysArgv(node)) return env.argv[0] ?? null;
5316
+ if (isSysArgv(node)) return env2.argv[0] ?? null;
5190
5317
  return null;
5191
5318
  }
5192
5319
  if (name === "CallExpression") {
@@ -5194,7 +5321,7 @@ function evalExpr(node, env) {
5194
5321
  if (func && func.name === "VariableName" && srcSlice(func) === "len") {
5195
5322
  const argList = node.getChild("ArgList");
5196
5323
  const inner = argList ? firstNamedChild(argList) : null;
5197
- if (inner && isSysArgv(inner)) return String(env.argv.length + 1);
5324
+ if (inner && isSysArgv(inner)) return String(env2.argv.length + 1);
5198
5325
  }
5199
5326
  return null;
5200
5327
  }
@@ -5206,16 +5333,16 @@ function evalExpr(node, env) {
5206
5333
  const condNode = ifKw.nextSibling;
5207
5334
  const orelseNode = elseKw.nextSibling;
5208
5335
  if (!thenNode || !condNode || !orelseNode) return null;
5209
- const cond = evalCondition(condNode, env);
5210
- if (cond === true) return evalExpr(thenNode, env);
5211
- if (cond === false) return evalExpr(orelseNode, env);
5336
+ const cond = evalCondition(condNode, env2);
5337
+ if (cond === true) return evalExpr(thenNode, env2);
5338
+ if (cond === false) return evalExpr(orelseNode, env2);
5212
5339
  return null;
5213
5340
  }
5214
5341
  if (name === "BinaryExpression") {
5215
5342
  const op = findOp(node);
5216
5343
  if (op === "+") {
5217
- const left = evalExpr(node.firstChild, env);
5218
- const right = evalExpr(lastNamedChild(node), env);
5344
+ const left = evalExpr(node.firstChild, env2);
5345
+ const right = evalExpr(lastNamedChild(node), env2);
5219
5346
  if (left !== null && right !== null) return left + right;
5220
5347
  }
5221
5348
  return null;
@@ -5248,7 +5375,7 @@ function findOp(node) {
5248
5375
  }
5249
5376
  return null;
5250
5377
  }
5251
- function evalCondition(node, env) {
5378
+ function evalCondition(node, env2) {
5252
5379
  if (!node) return null;
5253
5380
  const name = node.name;
5254
5381
  if (name === "Boolean") {
@@ -5259,7 +5386,7 @@ function evalCondition(node, env) {
5259
5386
  const op = findOp(node);
5260
5387
  if (op === "and" || op === "or") {
5261
5388
  const parts2 = namedChildren(node);
5262
- const vals = parts2.map((p) => evalCondition(p, env));
5389
+ const vals = parts2.map((p) => evalCondition(p, env2));
5263
5390
  if (op === "and") {
5264
5391
  if (vals.some((v) => v === false)) return false;
5265
5392
  if (vals.every((v) => v === true)) return true;
@@ -5273,13 +5400,13 @@ function evalCondition(node, env) {
5273
5400
  if (parts.length !== 3) return null;
5274
5401
  const [left, opNode, right] = parts;
5275
5402
  const opText = srcSlice(opNode).trim();
5276
- return evalCompare(left, opText, right, env);
5403
+ return evalCompare(left, opText, right, env2);
5277
5404
  }
5278
5405
  if (name === "UnaryExpression") {
5279
5406
  const kw = node.getChild("not");
5280
5407
  if (kw) {
5281
5408
  const operand = kw.nextSibling;
5282
- const v = evalCondition(operand, env);
5409
+ const v = evalCondition(operand, env2);
5283
5410
  return v === null ? null : !v;
5284
5411
  }
5285
5412
  return null;
@@ -5300,39 +5427,39 @@ function namedChildren(node) {
5300
5427
  }
5301
5428
  return out;
5302
5429
  }
5303
- function evalCompare(left, opText, right, env) {
5430
+ function evalCompare(left, opText, right, env2) {
5304
5431
  if (opText === "==" && left.name === "VariableName" && srcSlice(left) === "__name__") {
5305
- const rv = evalExpr(right, env);
5432
+ const rv = evalExpr(right, env2);
5306
5433
  if (rv !== null) return rv === "__main__";
5307
5434
  return null;
5308
5435
  }
5309
5436
  if (opText === "==" || opText === "!=") {
5310
- const lv = evalExpr(left, env);
5311
- const rv = evalExpr(right, env);
5437
+ const lv = evalExpr(left, env2);
5438
+ const rv = evalExpr(right, env2);
5312
5439
  if (lv !== null && rv !== null) {
5313
5440
  return opText === "==" ? lv === rv : lv !== rv;
5314
5441
  }
5315
5442
  return null;
5316
5443
  }
5317
5444
  if (opText === "in") {
5318
- const lv = evalExpr(left, env);
5445
+ const lv = evalExpr(left, env2);
5319
5446
  if (lv === null) return null;
5320
5447
  const rightName = right.name;
5321
5448
  if (rightName === "TupleExpression" || rightName === "ArrayExpression" || rightName === "SetExpression" || rightName === "ParenthesizedExpression") {
5322
5449
  const container = rightName === "ParenthesizedExpression" && right.firstChild && right.firstChild.name === "TupleExpression" ? right.firstChild : right;
5323
5450
  for (const elt of namedChildren(container)) {
5324
- const ev = evalExpr(elt, env);
5451
+ const ev = evalExpr(elt, env2);
5325
5452
  if (ev !== null && ev === lv) return true;
5326
5453
  }
5327
5454
  return false;
5328
5455
  }
5329
- const rv = evalExpr(right, env);
5456
+ const rv = evalExpr(right, env2);
5330
5457
  if (rv !== null) return rv.includes(lv);
5331
5458
  return null;
5332
5459
  }
5333
5460
  if (opText === ">" || opText === ">=" || opText === "<" || opText === "<=") {
5334
- const lv = evalExpr(left, env);
5335
- const rv = evalExpr(right, env);
5461
+ const lv = evalExpr(left, env2);
5462
+ const rv = evalExpr(right, env2);
5336
5463
  if (lv === null || rv === null) return null;
5337
5464
  const ln = parseInt(lv, 10);
5338
5465
  const rn = parseInt(rv, 10);
@@ -5344,71 +5471,71 @@ function evalCompare(left, opText, right, env) {
5344
5471
  }
5345
5472
  return null;
5346
5473
  }
5347
- function processStmts(stmts, env, lineStarts, lines, branches) {
5348
- for (const s of stmts) processStmt(s, env, lineStarts, lines, branches);
5474
+ function processStmts(stmts, env2, lineStarts, lines, branches) {
5475
+ for (const s of stmts) processStmt(s, env2, lineStarts, lines, branches);
5349
5476
  }
5350
- function invalidateEnvOnCalls(node, env) {
5477
+ function invalidateEnvOnCalls(node, env2) {
5351
5478
  const cursor = node.cursor();
5352
5479
  cursor.iterate((n) => {
5353
5480
  const nd = n.node;
5354
5481
  if (nd.name !== "CallExpression") return true;
5355
5482
  const func = nd.firstChild;
5356
5483
  if (func && func.name === "VariableName" && !PURE_BUILTINS.has(srcSlice(func))) {
5357
- for (const k of Object.keys(env.vars)) env.vars[k] = null;
5484
+ for (const k of Object.keys(env2.vars)) env2.vars[k] = null;
5358
5485
  return false;
5359
5486
  }
5360
5487
  return true;
5361
5488
  });
5362
5489
  }
5363
- function processStmt(stmt, env, lineStarts, lines, branches) {
5490
+ function processStmt(stmt, env2, lineStarts, lines, branches) {
5364
5491
  const name = stmt.name;
5365
5492
  if (name === "AssignStatement") {
5366
5493
  const target = stmt.firstChild;
5367
5494
  if (target && target.name === "VariableName") {
5368
5495
  const valNode = target.nextSibling ? target.nextSibling.nextSibling : null;
5369
- invalidateEnvOnCalls(stmt, env);
5370
- const val = evalExpr(valNode, env);
5371
- env.vars[srcSlice(target)] = val;
5496
+ invalidateEnvOnCalls(stmt, env2);
5497
+ const val = evalExpr(valNode, env2);
5498
+ env2.vars[srcSlice(target)] = val;
5372
5499
  }
5373
5500
  markNodeLines(stmt, lines, lineStarts);
5374
5501
  return;
5375
5502
  }
5376
5503
  if (name === "UpdateStatement") {
5377
5504
  const target = stmt.firstChild;
5378
- if (target && target.name === "VariableName") env.vars[srcSlice(target)] = null;
5379
- invalidateEnvOnCalls(stmt, env);
5505
+ if (target && target.name === "VariableName") env2.vars[srcSlice(target)] = null;
5506
+ invalidateEnvOnCalls(stmt, env2);
5380
5507
  markNodeLines(stmt, lines, lineStarts);
5381
5508
  return;
5382
5509
  }
5383
5510
  if (name === "IfStatement") {
5384
- processIf(stmt, env, lineStarts, lines, branches);
5511
+ processIf(stmt, env2, lineStarts, lines, branches);
5385
5512
  return;
5386
5513
  }
5387
5514
  if (name === "ForStatement" || name === "WhileStatement") {
5388
5515
  markNodeLines(stmt, lines, lineStarts);
5389
5516
  for (const body of stmt.getChildren("Body")) {
5390
- processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
5517
+ processStmts(bodyStmts(body), { argv: env2.argv, vars: { ...env2.vars } }, lineStarts, lines, branches);
5391
5518
  }
5392
5519
  return;
5393
5520
  }
5394
5521
  if (name === "TryStatement") {
5395
5522
  markNodeLines(stmt, lines, lineStarts);
5396
5523
  for (const body of stmt.getChildren("Body")) {
5397
- processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
5524
+ processStmts(bodyStmts(body), { argv: env2.argv, vars: { ...env2.vars } }, lineStarts, lines, branches);
5398
5525
  }
5399
5526
  return;
5400
5527
  }
5401
5528
  if (name === "WithStatement") {
5402
5529
  markNodeLines(stmt, lines, lineStarts);
5403
5530
  const body = stmt.getChild("Body");
5404
- if (body) processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
5531
+ if (body) processStmts(bodyStmts(body), { argv: env2.argv, vars: { ...env2.vars } }, lineStarts, lines, branches);
5405
5532
  return;
5406
5533
  }
5407
5534
  if (name === "MatchStatement") {
5408
5535
  markNodeLines(stmt, lines, lineStarts);
5409
5536
  for (const clause of stmt.getChildren("MatchClause")) {
5410
5537
  const body = clause.getChild("Body");
5411
- if (body) processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
5538
+ if (body) processStmts(bodyStmts(body), { argv: env2.argv, vars: { ...env2.vars } }, lineStarts, lines, branches);
5412
5539
  }
5413
5540
  return;
5414
5541
  }
@@ -5417,7 +5544,7 @@ function processStmt(stmt, env, lineStarts, lines, branches) {
5417
5544
  return;
5418
5545
  }
5419
5546
  if (name === "ExpressionStatement") {
5420
- invalidateEnvOnCalls(stmt, env);
5547
+ invalidateEnvOnCalls(stmt, env2);
5421
5548
  }
5422
5549
  markNodeLines(stmt, lines, lineStarts);
5423
5550
  }
@@ -5437,7 +5564,7 @@ function bodyStmts(body) {
5437
5564
  }
5438
5565
  return out;
5439
5566
  }
5440
- function processIf(stmt, env, lineStarts, lines, branches) {
5567
+ function processIf(stmt, env2, lineStarts, lines, branches) {
5441
5568
  const sections = [];
5442
5569
  let c = stmt.firstChild;
5443
5570
  while (c) {
@@ -5452,17 +5579,17 @@ function processIf(stmt, env, lineStarts, lines, branches) {
5452
5579
  }
5453
5580
  c = c.nextSibling;
5454
5581
  }
5455
- processIfSections(stmt, sections, 0, env, lineStarts, lines, branches);
5582
+ processIfSections(stmt, sections, 0, env2, lineStarts, lines, branches);
5456
5583
  }
5457
- function processIfSections(_stmt, sections, idx, env, lineStarts, lines, branches) {
5584
+ function processIfSections(_stmt, sections, idx, env2, lineStarts, lines, branches) {
5458
5585
  if (idx >= sections.length) return;
5459
5586
  const s = sections[idx];
5460
5587
  if (s.cond) markNodeLines(s.cond, lines, lineStarts);
5461
5588
  if (s.isElse) {
5462
- if (s.body) processStmts(bodyStmts(s.body), env, lineStarts, lines, branches);
5589
+ if (s.body) processStmts(bodyStmts(s.body), env2, lineStarts, lines, branches);
5463
5590
  return;
5464
5591
  }
5465
- const cond = s.cond ? evalCondition(s.cond, env) : null;
5592
+ const cond = s.cond ? evalCondition(s.cond, env2) : null;
5466
5593
  branches.push({
5467
5594
  kind: "if",
5468
5595
  label: s.cond ? srcSlice(s.cond) : "",
@@ -5470,18 +5597,18 @@ function processIfSections(_stmt, sections, idx, env, lineStarts, lines, branche
5470
5597
  reachable: cond !== false
5471
5598
  });
5472
5599
  if (cond === true) {
5473
- if (s.body) processStmts(bodyStmts(s.body), env, lineStarts, lines, branches);
5600
+ if (s.body) processStmts(bodyStmts(s.body), env2, lineStarts, lines, branches);
5474
5601
  return;
5475
5602
  }
5476
5603
  if (cond === false) {
5477
- processIfSections(_stmt, sections, idx + 1, env, lineStarts, lines, branches);
5604
+ processIfSections(_stmt, sections, idx + 1, env2, lineStarts, lines, branches);
5478
5605
  return;
5479
5606
  }
5480
- if (s.body) processStmts(bodyStmts(s.body), env, lineStarts, lines, branches);
5607
+ if (s.body) processStmts(bodyStmts(s.body), env2, lineStarts, lines, branches);
5481
5608
  for (let j = idx + 1; j < sections.length; j++) {
5482
5609
  const sj = sections[j];
5483
5610
  if (sj.cond) markNodeLines(sj.cond, lines, lineStarts);
5484
- if (sj.body) processStmts(bodyStmts(sj.body), env, lineStarts, lines, branches);
5611
+ if (sj.body) processStmts(bodyStmts(sj.body), env2, lineStarts, lines, branches);
5485
5612
  }
5486
5613
  }
5487
5614
  function parseShellString(s) {
@@ -5657,7 +5784,7 @@ var init_python_backend = __esm({
5657
5784
  SOURCE = c;
5658
5785
  const sourceLines = c.split("\n");
5659
5786
  const lineStarts = buildLineStarts(c);
5660
- const env = makeEnv(entryArgs || []);
5787
+ const env2 = makeEnv(entryArgs || []);
5661
5788
  const lines = /* @__PURE__ */ new Set();
5662
5789
  const branches = [];
5663
5790
  if (sourceLines.length > MAX_LINES) {
@@ -5684,7 +5811,7 @@ var init_python_backend = __esm({
5684
5811
  };
5685
5812
  }
5686
5813
  const stmts = bodyStmts(tree);
5687
- processStmts(stmts, env, lineStarts, lines, branches);
5814
+ processStmts(stmts, env2, lineStarts, lines, branches);
5688
5815
  const reachable = reconstruct(sourceLines, lines);
5689
5816
  let calls = extractCallsFromTree(tree, lines);
5690
5817
  if (calls.length === 0) calls = extractCallsRegex(c);
@@ -5912,7 +6039,7 @@ function tokenize(content) {
5912
6039
  }
5913
6040
  return stmts;
5914
6041
  }
5915
- function evalExpr2(text, env) {
6042
+ function evalExpr2(text, env2) {
5916
6043
  const isSingleQuoted = text.length >= 2 && text[0] === "'" && text[text.length - 1] === "'";
5917
6044
  if (text.length >= 2 && (text[0] === '"' || text[0] === "'") && text[text.length - 1] === text[0]) {
5918
6045
  text = text.slice(1, -1);
@@ -5928,19 +6055,19 @@ function evalExpr2(text, env) {
5928
6055
  const dm = inner.match(/^([A-Za-z_][A-Za-z0-9_]*|\d+):?-(.*)$/s);
5929
6056
  if (dm) {
5930
6057
  const key = /^\d+$/.test(dm[1]) ? `$${dm[1]}` : dm[1];
5931
- const bound = env.get(key);
6058
+ const bound = env2.get(key);
5932
6059
  if (bound && !(bound.kind === "concrete" && !bound.value)) {
5933
6060
  result.push(bound);
5934
6061
  } else {
5935
- result.push(evalExpr2(dm[2], env));
6062
+ result.push(evalExpr2(dm[2], env2));
5936
6063
  }
5937
6064
  continue;
5938
6065
  }
5939
6066
  if (/^\d+$/.test(inner)) {
5940
- const v = env.get(`$${inner}`);
6067
+ const v = env2.get(`$${inner}`);
5941
6068
  result.push(v || svalSym(`$${inner}`));
5942
6069
  } else {
5943
- const v = env.get(inner);
6070
+ const v = env2.get(inner);
5944
6071
  result.push(v || svalSym(inner));
5945
6072
  }
5946
6073
  continue;
@@ -5949,7 +6076,7 @@ function evalExpr2(text, env) {
5949
6076
  if (bare) {
5950
6077
  const varName = bare[1];
5951
6078
  const key = /^\d+$/.test(varName) ? `$${varName}` : varName;
5952
- const v = env.get(key);
6079
+ const v = env2.get(key);
5953
6080
  result.push(v || svalSym(p));
5954
6081
  continue;
5955
6082
  }
@@ -5959,13 +6086,13 @@ function evalExpr2(text, env) {
5959
6086
  return fold(svalExpr(result));
5960
6087
  }
5961
6088
  function initEnv(entryArgs) {
5962
- const env = /* @__PURE__ */ new Map();
6089
+ const env2 = /* @__PURE__ */ new Map();
5963
6090
  if (entryArgs && entryArgs.length) {
5964
- entryArgs.forEach((a, idx) => env.set(`$${idx + 1}`, svalConc(a)));
5965
- env.set("$@", svalConc(entryArgs.join(" ")));
5966
- env.set("$*", svalConc(entryArgs.join(" ")));
6091
+ entryArgs.forEach((a, idx) => env2.set(`$${idx + 1}`, svalConc(a)));
6092
+ env2.set("$@", svalConc(entryArgs.join(" ")));
6093
+ env2.set("$*", svalConc(entryArgs.join(" ")));
5967
6094
  }
5968
- return env;
6095
+ return env2;
5969
6096
  }
5970
6097
  function globToRegex(pattern) {
5971
6098
  let out = "^";
@@ -6023,12 +6150,12 @@ function evalNumericCompare(op, left, right) {
6023
6150
  return null;
6024
6151
  }
6025
6152
  }
6026
- function resolveVarSide(tok, env) {
6153
+ function resolveVarSide(tok, env2) {
6027
6154
  if (!tok.startsWith("$")) return tok;
6028
- const ev = evalExpr2(tok, env);
6155
+ const ev = evalExpr2(tok, env2);
6029
6156
  return ev.kind === "concrete" ? ev.value : null;
6030
6157
  }
6031
- function evalCondition2(cond, env) {
6158
+ function evalCondition2(cond, env2) {
6032
6159
  let inner = cond.trim().replace(/^\[\[?|\]\]?$/g, "").trim();
6033
6160
  if (/^-[A-Za-z]\s/.test(inner) || inner.startsWith("-d") || inner.startsWith("-f")) return null;
6034
6161
  const innerQ = inner.replace(/"/g, "").replace(/'/g, "");
@@ -6038,8 +6165,8 @@ function evalCondition2(cond, env) {
6038
6165
  if (!opm) continue;
6039
6166
  const left2 = innerQ.slice(0, opm.index).trim();
6040
6167
  const right2 = innerQ.slice(opm.index + opm[0].length).trim();
6041
- const lv2 = resolveVarSide(left2, env);
6042
- const rv2 = resolveVarSide(right2, env);
6168
+ const lv2 = resolveVarSide(left2, env2);
6169
+ const rv2 = resolveVarSide(right2, env2);
6043
6170
  if (lv2 === null || rv2 === null) return null;
6044
6171
  return evalNumericCompare(op2, lv2, rv2);
6045
6172
  }
@@ -6050,7 +6177,7 @@ function evalCondition2(cond, env) {
6050
6177
  let right = sm[3].trim();
6051
6178
  if (op === "=~") {
6052
6179
  if (right.startsWith("/") && right.endsWith("/")) right = right.slice(1, -1);
6053
- const lv2 = resolveVarSide(left, env);
6180
+ const lv2 = resolveVarSide(left, env2);
6054
6181
  if (lv2 === null) return null;
6055
6182
  try {
6056
6183
  return new RegExp(right).test(lv2);
@@ -6058,8 +6185,8 @@ function evalCondition2(cond, env) {
6058
6185
  return null;
6059
6186
  }
6060
6187
  }
6061
- const lv = resolveVarSide(left, env);
6062
- const rv = resolveVarSide(right, env);
6188
+ const lv = resolveVarSide(left, env2);
6189
+ const rv = resolveVarSide(right, env2);
6063
6190
  if (lv === null || rv === null) return null;
6064
6191
  if (op === "!=") return lv !== rv;
6065
6192
  return lv === rv;
@@ -6069,7 +6196,7 @@ function symbolicProcess(content, entryArgs) {
6069
6196
  const stmts = tokenize(content);
6070
6197
  if (!stmts.some((s) => s.kind === "case" || s.kind === "if" || s.kind === "assign")) return null;
6071
6198
  if (stmts.some((s) => !s.modelable)) return null;
6072
- const env = initEnv(entryArgs);
6199
+ const env2 = initEnv(entryArgs);
6073
6200
  let linesBudget = 0;
6074
6201
  let branchesBudget = 0;
6075
6202
  let modelable = false;
@@ -6079,9 +6206,9 @@ function symbolicProcess(content, entryArgs) {
6079
6206
  if (linesBudget > MAX_LINES2) return null;
6080
6207
  if (s.kind === "assign") {
6081
6208
  modelable = true;
6082
- if (env.size >= MAX_VARS) return null;
6083
- const v = evalExpr2(s.rhs, env);
6084
- env.set(s.name, fold(v));
6209
+ if (env2.size >= MAX_VARS) return null;
6210
+ const v = evalExpr2(s.rhs, env2);
6211
+ env2.set(s.name, fold(v));
6085
6212
  out.push(s.line);
6086
6213
  continue;
6087
6214
  }
@@ -6089,7 +6216,7 @@ function symbolicProcess(content, entryArgs) {
6089
6216
  modelable = true;
6090
6217
  branchesBudget += s.branches.length;
6091
6218
  if (branchesBudget > MAX_BRANCHES) return null;
6092
- const selVal = fold(evalExpr2(s.selector, env));
6219
+ const selVal = fold(evalExpr2(s.selector, env2));
6093
6220
  if (selVal.kind !== "concrete") return null;
6094
6221
  const concrete = selVal.value || "";
6095
6222
  let selected = null;
@@ -6116,7 +6243,7 @@ esac`);
6116
6243
  modelable = true;
6117
6244
  branchesBudget++;
6118
6245
  if (branchesBudget > MAX_BRANCHES) return null;
6119
- const truth = evalCondition2(s.cond, env);
6246
+ const truth = evalCondition2(s.cond, env2);
6120
6247
  if (truth === null) return null;
6121
6248
  const chosen = truth ? s.thenBody : s.elseBody;
6122
6249
  if (chosen === null) return null;
@@ -6649,9 +6776,9 @@ var init_tracker = __esm({
6649
6776
  });
6650
6777
 
6651
6778
  // src/engine/script-content-analyzer.ts
6652
- import { statSync as statSync4, existsSync as existsSync10 } from "fs";
6653
- import { resolve as resolve9, join as join6, isAbsolute as isAbsolute2 } from "path";
6654
- import { homedir as homedir3 } from "os";
6779
+ import { statSync as statSync4, existsSync as existsSync12 } from "fs";
6780
+ import { resolve as resolve10, join as join11, isAbsolute as isAbsolute3 } from "path";
6781
+ import { homedir as homedir4 } from "os";
6655
6782
  function emptyChain() {
6656
6783
  return {
6657
6784
  nodes: [],
@@ -6683,10 +6810,10 @@ function assessKeywordCombinationRisk2(matchedKeywords, scriptContent) {
6683
6810
  const hitGroup2 = rule.group2.size === 0 ? true : [...rule.group2].some((k) => keywordSet.has(k));
6684
6811
  if (hitGroup1 && hitGroup2) return { level: rule.level, reason: rule.reason };
6685
6812
  }
6686
- const networkPostKeywords = /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]);
6813
+ const networkPostKeywords = new Set(KEYWORD_SETS2.network_post ?? []);
6687
6814
  const hasNetworkPost = [...networkPostKeywords].some((k) => keywordSet.has(k));
6688
6815
  if (hasNetworkPost) {
6689
- const envPatterns = ["$env", "$(env)", "$USER", "$(hostname)", "$(pwd)", "$HOME"];
6816
+ const envPatterns = SCRIPT_RULES2.env_exfil_patterns ?? [];
6690
6817
  const contentLower = scriptContent.toLowerCase();
6691
6818
  for (const p of envPatterns) {
6692
6819
  if (contentLower.includes(p.toLowerCase())) {
@@ -6762,7 +6889,7 @@ function extractInlineScriptPath(actionDetail) {
6762
6889
  if (looksLikeScriptPath(tok)) {
6763
6890
  try {
6764
6891
  const resolved = resolvePath2(tok.replace(/^['"]|['"]$/g, ""));
6765
- if (existsSync10(resolved)) {
6892
+ if (existsSync12(resolved)) {
6766
6893
  const stat = statSync4(resolved);
6767
6894
  if (stat.isFile()) return resolved;
6768
6895
  }
@@ -6776,7 +6903,7 @@ function extractInlineScriptPath(actionDetail) {
6776
6903
  const p = m[1];
6777
6904
  try {
6778
6905
  const resolved = resolvePath2(p);
6779
- if (existsSync10(resolved)) {
6906
+ if (existsSync12(resolved)) {
6780
6907
  const stat = statSync4(resolved);
6781
6908
  if (stat.isFile()) return resolved;
6782
6909
  }
@@ -6792,7 +6919,7 @@ function extractInlineScriptPath(actionDetail) {
6792
6919
  let resolved;
6793
6920
  try {
6794
6921
  resolved = resolvePath2(p);
6795
- if (existsSync10(resolved)) {
6922
+ if (existsSync12(resolved)) {
6796
6923
  const stat = statSync4(resolved);
6797
6924
  if (stat.isFile()) return resolved;
6798
6925
  }
@@ -6833,9 +6960,9 @@ function shlexSplit(s) {
6833
6960
  function resolvePath2(p) {
6834
6961
  let expanded = p;
6835
6962
  if (expanded.startsWith("~/") || expanded === "~") {
6836
- expanded = join6(homedir3(), expanded.slice(1));
6963
+ expanded = join11(homedir4(), expanded.slice(1));
6837
6964
  }
6838
- return resolve9(expanded);
6965
+ return resolve10(expanded);
6839
6966
  }
6840
6967
  function scanExtensionPatterns(ext, content) {
6841
6968
  const pats = ext?.extra_command_patterns;
@@ -6956,8 +7083,8 @@ function analyzeScriptContent2(input, ext) {
6956
7083
  resolvedPath = inline;
6957
7084
  isInline = true;
6958
7085
  }
6959
- if (resolvedPath && !isAbsolute2(resolvedPath) && !resolvedPath.startsWith("~/")) {
6960
- resolvedPath = join6(input.cwd || process.cwd(), resolvedPath);
7086
+ if (resolvedPath && !isAbsolute3(resolvedPath) && !resolvedPath.startsWith("~/")) {
7087
+ resolvedPath = join11(input.cwd || process.cwd(), resolvedPath);
6961
7088
  }
6962
7089
  const l1Adapter = (content) => {
6963
7090
  const j = scanDangerousCommandPatterns("bash", content, input.osType);
@@ -7042,7 +7169,7 @@ ${contentPreview}
7042
7169
  if (chain.maxSizeReached) header += "\u26A0\uFE0F \u6CE8\u610F\uFF1A\u7D2F\u8BA1\u6587\u4EF6\u5927\u5C0F\u89E6\u8FBE\u4E0A\u9650\uFF0C\u90E8\u5206\u811A\u672C\u53EF\u80FD\u672A\u88AB\u5B8C\u6574\u8BFB\u53D6\u3002\n";
7043
7170
  return header + parts.join("\n---\n\n");
7044
7171
  }
7045
- var SUSPICIOUS_PATTERNS2, SCRIPT_EXEC_PATTERNS2, KEYWORD_COMBINATION_RISKS2, SINGLE_KEYWORD_RISKS2, LEVEL_ORDER2;
7172
+ var SCRIPT_RULES2, SUSPICIOUS_PATTERNS2, SCRIPT_EXEC_PATTERNS2, KEYWORD_SETS2, KEYWORD_COMBINATION_RISKS2, SINGLE_KEYWORD_RISKS2, LEVEL_ORDER2;
7046
7173
  var init_script_content_analyzer = __esm({
7047
7174
  "src/engine/script-content-analyzer.ts"() {
7048
7175
  "use strict";
@@ -7052,87 +7179,23 @@ var init_script_content_analyzer = __esm({
7052
7179
  init_logic_rules();
7053
7180
  init_inline_analyzer();
7054
7181
  init_tracker();
7055
- SUSPICIOUS_PATTERNS2 = [
7056
- { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
7057
- { name: "curl --data", pattern: /curl\s+(.*\s)?--data/i },
7058
- { name: "curl -d", pattern: /curl\s+(.*\s)?-d\s/i },
7059
- { name: "wget --post-data", pattern: /wget\s+(.*\s)?--post-data/i },
7060
- { name: "wget --post-file", pattern: /wget\s+(.*\s)?--post-file/i },
7061
- { name: "nc -e", pattern: /\bnc\s+.*-e\b/ },
7062
- { name: "ncat -e", pattern: /\bncat\s+.*-e\b/ },
7063
- { name: "/dev/tcp", pattern: /\/dev\/tcp\// },
7064
- { name: "/dev/udp", pattern: /\/dev\/udp\// },
7065
- { name: "$(base64", pattern: /\$\(\s*base64/ },
7066
- { name: "`base64", pattern: /`\s*base64/ },
7067
- { name: "base64 decode pipe", pattern: /base64\s+(-d|--decode)\s*\|/ },
7068
- { name: "/etc/shadow", pattern: /\/etc\/shadow/ },
7069
- { name: "id_rsa", pattern: /\bid_rsa\b/ },
7070
- { name: "id_ed25519", pattern: /\bid_ed25519\b/ },
7071
- { name: "crontab install", pattern: /crontab\s+-/ },
7072
- { name: "authorized_keys", pattern: /authorized_keys/ },
7073
- { name: "ssh no host key check", pattern: /ssh\s+.*StrictHostKeyChecking\s*=\s*no/ }
7074
- ];
7075
- SCRIPT_EXEC_PATTERNS2 = [
7076
- /(?:^|\s|&&|\|\|)\s*bash\s+(\S+)/i,
7077
- /(?:^|\s|&&|\|\|)\s*\/bin\/bash\s+(\S+)/i,
7078
- /(?:^|\s|&&|\|\|)\s*sh\s+(\S+)/i,
7079
- /(?:^|\s|&&|\|\|)\s*zsh\s+(\S+)/i,
7080
- /\.\/(\S+)/,
7081
- /(?:^|\s|&&|\|\|)\s*source\s+(\S+)/i,
7082
- /(?:^|\s|&&|\|\|)\s*\.\s+(\S+)/,
7083
- /(?:^|\s|&&|\|\|)\s*python3?\s+(\S+\.py)/i,
7084
- /(?:^|\s|&&|\|\|)\s*node\s+(\S+\.js)/i,
7085
- /(?:^|\s|&&|\|\|)\s*ruby\s+(\S+\.rb)/i,
7086
- /(?:^|\s|&&|\|\|)\s*perl\s+(\S+\.pl)/i,
7087
- /(?:^|\s|&&|\|\|)\s*php\s+(\S+\.php)/i,
7088
- /(?:^|\s|&&|\|\|)\s*lua\s+(\S+\.lua)/i
7089
- ];
7090
- KEYWORD_COMBINATION_RISKS2 = [
7091
- {
7092
- group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
7093
- group2: /* @__PURE__ */ new Set(["$(base64", "`base64", "base64 decode pipe"]),
7094
- level: "critical",
7095
- reason: "\u68C0\u6D4B\u5230\u7F51\u7EDC\u6570\u636E\u53D1\u9001 + Base64\u7F16\u7801\u7EC4\u5408\uFF0C\u6784\u6210 Critical \u7EA7\u522B\u6570\u636E\u5916\u4F20\u98CE\u9669\u3002\u653B\u51FB\u8005\u53EF\u80FD\u5C06\u654F\u611F\u6570\u636E\u7F16\u7801\u540E\u5916\u4F20\u3002"
7096
- },
7097
- {
7098
- group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
7099
- group2: /* @__PURE__ */ new Set(["/etc/shadow", "id_rsa", "id_ed25519"]),
7100
- level: "critical",
7101
- reason: "\u68C0\u6D4B\u5230\u7F51\u7EDC\u6570\u636E\u53D1\u9001 + \u654F\u611F\u6587\u4EF6\u8BBF\u95EE\u7EC4\u5408\uFF0C\u6784\u6210 Critical \u7EA7\u522B\u51ED\u8BC1\u5916\u4F20\u98CE\u9669\u3002"
7102
- },
7103
- {
7104
- group1: /* @__PURE__ */ new Set(["nc -e", "ncat -e", "/dev/tcp", "/dev/udp"]),
7105
- group2: /* @__PURE__ */ new Set(),
7106
- level: "high",
7107
- reason: "\u68C0\u6D4B\u5230\u53CD\u5F39 Shell \u7279\u5F81\uFF0C\u653B\u51FB\u8005\u53EF\u80FD\u5EFA\u7ACB\u8FDC\u7A0B\u63A7\u5236\u901A\u9053\u3002"
7108
- },
7109
- {
7110
- group1: /* @__PURE__ */ new Set(["crontab install", "authorized_keys", "ssh no host key check"]),
7111
- group2: /* @__PURE__ */ new Set(),
7112
- level: "high",
7113
- reason: "\u68C0\u6D4B\u5230\u6301\u4E45\u5316\u540E\u95E8\u7279\u5F81\uFF0C\u653B\u51FB\u8005\u53EF\u80FD\u5728\u7CFB\u7EDF\u4E2D\u690D\u5165\u540E\u95E8\u3002"
7114
- }
7115
- ];
7116
- SINGLE_KEYWORD_RISKS2 = {
7117
- "curl POST": { level: "high", reason: "\u68C0\u6D4B\u5230 curl POST \u8BF7\u6C42\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
7118
- "curl --data": { level: "high", reason: "\u68C0\u6D4B\u5230 curl --data \u53C2\u6570\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
7119
- "curl -d": { level: "high", reason: "\u68C0\u6D4B\u5230 curl -d \u53C2\u6570\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
7120
- "wget --post-data": { level: "high", reason: "\u68C0\u6D4B\u5230 wget POST \u8BF7\u6C42\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
7121
- "wget --post-file": { level: "high", reason: "\u68C0\u6D4B\u5230 wget \u6587\u4EF6\u4E0A\u4F20\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6587\u4EF6\u3002" },
7122
- "nc -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 nc -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
7123
- "ncat -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 ncat -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
7124
- "/dev/tcp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/tcp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
7125
- "/dev/udp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/udp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
7126
- "$(base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
7127
- "`base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
7128
- "base64 decode pipe": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u89E3\u7801\u540E\u7BA1\u9053\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
7129
- "/etc/shadow": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6\u3002" },
7130
- "id_rsa": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
7131
- "id_ed25519": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
7132
- "crontab install": { level: "high", reason: "\u68C0\u6D4B\u5230\u4FEE\u6539\u5B9A\u65F6\u4EFB\u52A1\uFF0C\u53EF\u80FD\u662F\u6301\u4E45\u5316\u540E\u95E8\u3002" },
7133
- "authorized_keys": { level: "high", reason: "\u68C0\u6D4B\u5230\u4FEE\u6539 SSH \u6388\u6743\u5BC6\u94A5\uFF0C\u53EF\u80FD\u662F\u6301\u4E45\u5316\u540E\u95E8\u3002" },
7134
- "ssh no host key check": { level: "high", reason: "\u68C0\u6D4B\u5230 SSH \u8DF3\u8FC7\u4E3B\u673A\u5BC6\u94A5\u9A8C\u8BC1\uFF0C\u53EF\u80FD\u662F\u6A2A\u5411\u79FB\u52A8\u3002" }
7135
- };
7182
+ init_rules_loader();
7183
+ SCRIPT_RULES2 = loadScriptRules();
7184
+ if (!SCRIPT_RULES2 || Object.keys(SCRIPT_RULES2).length === 0) {
7185
+ console.error(
7186
+ "[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"
7187
+ );
7188
+ }
7189
+ SUSPICIOUS_PATTERNS2 = (SCRIPT_RULES2.suspicious_patterns ?? []).map((r) => ({ name: r.name, pattern: compileRuleRegex(r) }));
7190
+ SCRIPT_EXEC_PATTERNS2 = ((SCRIPT_RULES2.script_exec_patterns ?? {}).chain ?? []).map((r) => compileRuleRegex(r));
7191
+ KEYWORD_SETS2 = SCRIPT_RULES2.keyword_sets ?? {};
7192
+ KEYWORD_COMBINATION_RISKS2 = (SCRIPT_RULES2.keyword_combination_risks ?? []).map((r) => ({
7193
+ group1: resolveKeywordSet(r.group1, KEYWORD_SETS2),
7194
+ group2: resolveKeywordSet(r.group2, KEYWORD_SETS2),
7195
+ level: String(r.level),
7196
+ reason: String(r.reason)
7197
+ }));
7198
+ SINGLE_KEYWORD_RISKS2 = SCRIPT_RULES2.single_keyword_risks ?? {};
7136
7199
  LEVEL_ORDER2 = { low: 1, medium: 2, high: 3, critical: 4 };
7137
7200
  }
7138
7201
  });
@@ -7147,16 +7210,16 @@ __export(token_stats_exports, {
7147
7210
  recordTokenUsage: () => recordTokenUsage,
7148
7211
  resetTokenStats: () => resetTokenStats
7149
7212
  });
7150
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync2, existsSync as existsSync11, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
7213
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync2, existsSync as existsSync13, mkdirSync as mkdirSync3, unlinkSync as unlinkSync2 } from "fs";
7151
7214
  function getStatsPath() {
7152
7215
  const envPath = process.env[ENV_STATS_PATH];
7153
7216
  if (envPath) return envPath;
7154
7217
  return getTokenStatsPath();
7155
7218
  }
7156
7219
  function loadAllRecords(statsPath) {
7157
- if (!existsSync11(statsPath)) return [];
7220
+ if (!existsSync13(statsPath)) return [];
7158
7221
  try {
7159
- const data = JSON.parse(readFileSync11(statsPath, "utf-8"));
7222
+ const data = JSON.parse(readFileSync13(statsPath, "utf-8"));
7160
7223
  if (Array.isArray(data)) return data;
7161
7224
  if (data && Array.isArray(data.records)) return data.records;
7162
7225
  return [];
@@ -7167,7 +7230,7 @@ function loadAllRecords(statsPath) {
7167
7230
  function saveRecords(statsPath, records) {
7168
7231
  try {
7169
7232
  const dir = statsPath.substring(0, statsPath.lastIndexOf("/"));
7170
- if (dir && !existsSync11(dir)) mkdirSync2(dir, { recursive: true });
7233
+ if (dir && !existsSync13(dir)) mkdirSync3(dir, { recursive: true });
7171
7234
  writeFileSync2(statsPath, JSON.stringify(records, null, 2), "utf-8");
7172
7235
  } catch (e) {
7173
7236
  }
@@ -7294,7 +7357,7 @@ function getTokenTrend(days = 0, startDate, endDate) {
7294
7357
  }
7295
7358
  function resetTokenStats() {
7296
7359
  const statsPath = getStatsPath();
7297
- if (existsSync11(statsPath)) {
7360
+ if (existsSync13(statsPath)) {
7298
7361
  try {
7299
7362
  unlinkSync2(statsPath);
7300
7363
  } catch {
@@ -7313,7 +7376,7 @@ var init_token_stats = __esm({
7313
7376
  });
7314
7377
 
7315
7378
  // src/engine/llm-analyzer.ts
7316
- import { appendFileSync } from "fs";
7379
+ import { openSync as openSync2, writeSync as writeSync2, closeSync as closeSync2, constants as fsConstants } from "fs";
7317
7380
  import OpenAI from "openai";
7318
7381
  async function runLLMAnalysis(input, previous) {
7319
7382
  if (!isLLMAvailable()) {
@@ -7329,7 +7392,15 @@ async function runLLMAnalysis(input, previous) {
7329
7392
  };
7330
7393
  }
7331
7394
  const globalConfig = getLLMConfig();
7332
- const config = input.llmConfig?.apiKey ? { ...globalConfig, ...input.llmConfig } : globalConfig;
7395
+ let perRequest = input.llmConfig;
7396
+ if (perRequest?.apiKey) {
7397
+ const [okUrl, why] = validateLlmBaseUrl(String(perRequest.baseUrl || ""));
7398
+ if (!okUrl) {
7399
+ console.warn("[AgentMoss] \u5FFD\u7565\u4E0D\u88AB\u5141\u8BB8\u7684 per-request llmConfig.baseUrl:", why);
7400
+ perRequest = { ...perRequest, baseUrl: void 0 };
7401
+ }
7402
+ }
7403
+ const config = perRequest?.apiKey ? { ...globalConfig, ...perRequest } : globalConfig;
7333
7404
  const rawDetail = input.aNext.actionDetail.replace(/^terminal-\w+:\s*command=([^,]+).*/, "$1").trim();
7334
7405
  const cacheKey = `${input.aNext.actionType}:${rawDetail || input.aNext.actionDetail}`;
7335
7406
  const cached2 = llmCache.get(cacheKey);
@@ -7358,6 +7429,10 @@ async function runLLMAnalysis(input, previous) {
7358
7429
  }
7359
7430
  function logPrompt(prompt, aNext, logPath) {
7360
7431
  if (!logPath) return;
7432
+ if (!pathWithin(logPath, logRoots())) {
7433
+ console.warn("[AgentMoss] \u5FFD\u7565\u8D8A\u754C\u7684 llm_log_path\uFF08\u4E0D\u5728\u5141\u8BB8\u6839\u76EE\u5F55\u5185\uFF09:", logPath);
7434
+ return;
7435
+ }
7361
7436
  try {
7362
7437
  const actionType = aNext.actionType || "unknown";
7363
7438
  const actionDetail = (aNext.actionDetail || "").slice(0, 100);
@@ -7365,7 +7440,14 @@ function logPrompt(prompt, aNext, logPath) {
7365
7440
  ${prompt}
7366
7441
  --- END PROMPT ---
7367
7442
  `;
7368
- appendFileSync(logPath, line, "utf-8");
7443
+ let flags = fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_APPEND;
7444
+ if (fsConstants.O_NOFOLLOW) flags |= fsConstants.O_NOFOLLOW;
7445
+ const fd = openSync2(logPath, flags, 384);
7446
+ try {
7447
+ writeSync2(fd, line);
7448
+ } finally {
7449
+ closeSync2(fd);
7450
+ }
7369
7451
  } catch (e) {
7370
7452
  console.warn("[AgentMoss] Failed to write LLM prompt to log:", e);
7371
7453
  }
@@ -7524,7 +7606,7 @@ async function analyzeWithLLM(prompt, config) {
7524
7606
  }
7525
7607
  if (attempt < maxRetries) {
7526
7608
  console.error(`[AgentMoss] LLM call failed (attempt ${attempt + 1}/${maxRetries + 1}), retrying in ${retryCfg.retryInterval}s:`, err.message);
7527
- await new Promise((resolve11) => setTimeout(resolve11, retryCfg.retryInterval * 1e3));
7609
+ await new Promise((resolve12) => setTimeout(resolve12, retryCfg.retryInterval * 1e3));
7528
7610
  } else {
7529
7611
  clearTimeout(totalTimeoutTimer);
7530
7612
  console.error("[AgentMoss] LLM call failed after all retries:", err.message);
@@ -7540,6 +7622,7 @@ var init_llm_analyzer = __esm({
7540
7622
  "src/engine/llm-analyzer.ts"() {
7541
7623
  "use strict";
7542
7624
  init_esm_shims();
7625
+ init_security_policy();
7543
7626
  init_config();
7544
7627
  init_skill_loader();
7545
7628
  init_template_loader();
@@ -7551,9 +7634,9 @@ var init_llm_analyzer = __esm({
7551
7634
  });
7552
7635
 
7553
7636
  // src/brain/rule-store.ts
7554
- import { existsSync as existsSync12, readFileSync as readFileSync12, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
7555
- import { join as join7 } from "path";
7556
- import { homedir as homedir4 } from "os";
7637
+ import { existsSync as existsSync14, readFileSync as readFileSync14, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "fs";
7638
+ import { join as join12 } from "path";
7639
+ import { homedir as homedir5 } from "os";
7557
7640
  function getRulesPath() {
7558
7641
  const envPath = process.env[ENV_RULES_PATH];
7559
7642
  if (envPath) return envPath;
@@ -7571,7 +7654,7 @@ var init_rule_store = __esm({
7571
7654
  "src/brain/rule-store.ts"() {
7572
7655
  "use strict";
7573
7656
  init_esm_shims();
7574
- DEFAULT_RULES_PATH = join7(homedir4(), ".agentmoss", "brain", "rules.json");
7657
+ DEFAULT_RULES_PATH = join12(homedir5(), ".agentmoss", "brain", "rules.json");
7575
7658
  ENV_RULES_PATH = "AGENT_MOSS_BRAIN_RULES_PATH";
7576
7659
  STATUS_DRAFT = "draft";
7577
7660
  STATUS_APPROVED = "approved";
@@ -7588,11 +7671,11 @@ var init_rule_store = __esm({
7588
7671
  this.data = this.load();
7589
7672
  }
7590
7673
  load() {
7591
- if (!existsSync12(this.path)) {
7674
+ if (!existsSync14(this.path)) {
7592
7675
  return { version: 1, rules: [], pattern_candidates: [] };
7593
7676
  }
7594
7677
  try {
7595
- const raw = JSON.parse(readFileSync12(this.path, "utf-8"));
7678
+ const raw = JSON.parse(readFileSync14(this.path, "utf-8"));
7596
7679
  return {
7597
7680
  version: raw.version || 1,
7598
7681
  rules: raw.rules || [],
@@ -7604,7 +7687,7 @@ var init_rule_store = __esm({
7604
7687
  }
7605
7688
  save() {
7606
7689
  const dir = this.path.substring(0, this.path.lastIndexOf("/"));
7607
- if (dir) mkdirSync3(dir, { recursive: true });
7690
+ if (dir) mkdirSync4(dir, { recursive: true });
7608
7691
  writeFileSync3(this.path, JSON.stringify(this.data, null, 2), "utf-8");
7609
7692
  }
7610
7693
  addRule(rule) {
@@ -8186,9 +8269,9 @@ var init_reviewer = __esm({
8186
8269
  });
8187
8270
 
8188
8271
  // src/brain/skill-manager.ts
8189
- import { existsSync as existsSync13, mkdirSync as mkdirSync4, readFileSync as readFileSync13, renameSync, writeFileSync as writeFileSync4 } from "fs";
8190
- import { homedir as homedir5 } from "os";
8191
- import { join as join8 } from "path";
8272
+ import { existsSync as existsSync15, mkdirSync as mkdirSync5, readFileSync as readFileSync15, renameSync, writeFileSync as writeFileSync4 } from "fs";
8273
+ import { homedir as homedir6 } from "os";
8274
+ import { join as join13 } from "path";
8192
8275
  import { randomUUID } from "crypto";
8193
8276
  function getMetaPath(override) {
8194
8277
  return override || process.env[ENV_SKILL_META_PATH] || DEFAULT_SKILL_META_PATH;
@@ -8198,14 +8281,14 @@ var init_skill_manager = __esm({
8198
8281
  "src/brain/skill-manager.ts"() {
8199
8282
  "use strict";
8200
8283
  init_esm_shims();
8201
- DEFAULT_SKILL_META_PATH = join8(homedir5(), ".agentmoss", "brain", "skill_meta.json");
8284
+ DEFAULT_SKILL_META_PATH = join13(homedir6(), ".agentmoss", "brain", "skill_meta.json");
8202
8285
  ENV_SKILL_META_PATH = "AGENT_MOSS_BRAIN_SKILL_META_PATH";
8203
8286
  BrainSkillManager = class {
8204
8287
  constructor(skillsDir, metaPath) {
8205
8288
  this.skillsDir = skillsDir;
8206
8289
  this.metaPath = getMetaPath(metaPath);
8207
8290
  try {
8208
- mkdirSync4(join8(this.metaPath, ".."), { recursive: true });
8291
+ mkdirSync5(join13(this.metaPath, ".."), { recursive: true });
8209
8292
  } catch {
8210
8293
  }
8211
8294
  this.load();
@@ -8215,9 +8298,9 @@ var init_skill_manager = __esm({
8215
8298
  skills = [];
8216
8299
  metaPath;
8217
8300
  load() {
8218
- if (!existsSync13(this.metaPath)) return;
8301
+ if (!existsSync15(this.metaPath)) return;
8219
8302
  try {
8220
- const data = JSON.parse(readFileSync13(this.metaPath, "utf-8"));
8303
+ const data = JSON.parse(readFileSync15(this.metaPath, "utf-8"));
8221
8304
  this.skills = data.skills || [];
8222
8305
  } catch {
8223
8306
  this.skills = [];
@@ -8240,7 +8323,7 @@ var init_skill_manager = __esm({
8240
8323
  let changed = false;
8241
8324
  for (const sk of this.skills) {
8242
8325
  if (sk.status !== "active") continue;
8243
- if (!existsSync13(join8(this.skillsDir, `${sk.name}.md`))) {
8326
+ if (!existsSync15(join13(this.skillsDir, `${sk.name}.md`))) {
8244
8327
  sk.status = "stale";
8245
8328
  changed = true;
8246
8329
  }
@@ -8251,9 +8334,9 @@ var init_skill_manager = __esm({
8251
8334
  addSkill(skill) {
8252
8335
  const id = skill.id || randomUUID();
8253
8336
  const name = skill.name || `brain_${id.slice(0, 8)}`;
8254
- if (!existsSync13(this.skillsDir)) return "";
8255
- const mdPath = join8(this.skillsDir, `${name}.md`);
8256
- if (existsSync13(mdPath)) return "";
8337
+ if (!existsSync15(this.skillsDir)) return "";
8338
+ const mdPath = join13(this.skillsDir, `${name}.md`);
8339
+ if (existsSync15(mdPath)) return "";
8257
8340
  try {
8258
8341
  writeFileSync4(mdPath, skill.content, "utf-8");
8259
8342
  } catch {
@@ -8302,12 +8385,12 @@ var init_skill_manager = __esm({
8302
8385
  const out = [];
8303
8386
  for (const sk of this.skills) {
8304
8387
  if (sk.origin_node !== "local" || sk.status !== "active") continue;
8305
- const mdPath = join8(this.skillsDir, `${sk.name}.md`);
8306
- if (!existsSync13(mdPath)) continue;
8388
+ const mdPath = join13(this.skillsDir, `${sk.name}.md`);
8389
+ if (!existsSync15(mdPath)) continue;
8307
8390
  out.push({
8308
8391
  name: sk.name,
8309
8392
  category: sk.category || "",
8310
- content: readFileSync13(mdPath, "utf-8"),
8393
+ content: readFileSync15(mdPath, "utf-8"),
8311
8394
  keywords: sk.keywords || []
8312
8395
  });
8313
8396
  }
@@ -8394,7 +8477,7 @@ var init_curator = __esm({
8394
8477
  });
8395
8478
 
8396
8479
  // src/brain/contributor.ts
8397
- import { dirname as dirname9 } from "path";
8480
+ import { dirname as dirname10 } from "path";
8398
8481
  import { fileURLToPath as fileURLToPath9 } from "url";
8399
8482
  var __dirname8;
8400
8483
  var init_contributor = __esm({
@@ -8404,7 +8487,7 @@ var init_contributor = __esm({
8404
8487
  init_rule_store();
8405
8488
  init_skill_manager();
8406
8489
  init_asset_dir();
8407
- __dirname8 = dirname9(fileURLToPath9(import.meta.url));
8490
+ __dirname8 = dirname10(fileURLToPath9(import.meta.url));
8408
8491
  }
8409
8492
  });
8410
8493
 
@@ -8595,9 +8678,10 @@ var init_decode = __esm({
8595
8678
  "src/engine/formal-engine/decode.ts"() {
8596
8679
  "use strict";
8597
8680
  init_esm_shims();
8681
+ init_rules_loader();
8598
8682
  B64_TOKEN_RE = /[A-Za-z0-9+/]+={0,2}/g;
8599
8683
  QUOTED_STR_RE = /('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`)/g;
8600
- OUTPUT_CMDS = /* @__PURE__ */ new Set(["echo", "printf", "print"]);
8684
+ OUTPUT_CMDS = new Set(loadFormalRules().output_commands ?? []);
8601
8685
  STAGE_PRIORITY = ["EXFIL", "PRIVESC", "PERSIST", "SCAN", "LISTEN"];
8602
8686
  PURPOSE_EXEC_RE = /subprocess\.\w+\s*\(|os\.system\s*\(|shell\s*=\s*True|\beval\s*\(|\bexec\s*\(|os\.popen\s*\(|\/exec\b/i;
8603
8687
  LISTEN_LEAD_RE = /http\.server|flask\s+run|nohup\b|nc\s+-l|socat\s+TCP-LISTEN/i;
@@ -8771,7 +8855,7 @@ function markIntegrity(actionDetail) {
8771
8855
  }
8772
8856
  return { integrity: "medium", isLowSource: false };
8773
8857
  }
8774
- function initNode(sessionId, seq, actionType, actionDetail) {
8858
+ function initNode(sessionId, seq, actionType, actionDetail, timestamp) {
8775
8859
  const { integrity, isLowSource } = markIntegrity(actionDetail);
8776
8860
  return {
8777
8861
  sessionId,
@@ -8781,7 +8865,8 @@ function initNode(sessionId, seq, actionType, actionDetail) {
8781
8865
  stage: markStage(actionDetail),
8782
8866
  resources: extractResources(actionDetail),
8783
8867
  integrity,
8784
- isLowSource
8868
+ isLowSource,
8869
+ timestamp: timestamp || void 0
8785
8870
  };
8786
8871
  }
8787
8872
  function buildSessionGraph(actionHistory, aNext, sessionId = "current") {
@@ -8791,13 +8876,21 @@ function buildSessionGraph(actionHistory, aNext, sessionId = "current") {
8791
8876
  const details = [];
8792
8877
  for (const a of actionHistory) {
8793
8878
  const ad = String(a.actionDetail || "");
8794
- if (ad) details.push({ actionType: String(a.actionType || ""), actionDetail: ad });
8879
+ if (ad) details.push({
8880
+ actionType: String(a.actionType || ""),
8881
+ actionDetail: ad,
8882
+ calledAt: a.calledAt || void 0
8883
+ });
8795
8884
  }
8796
8885
  if (aNext) {
8797
- details.push({ actionType: String(aNext.actionType || ""), actionDetail: String(aNext.actionDetail || "") });
8886
+ details.push({
8887
+ actionType: String(aNext.actionType || ""),
8888
+ actionDetail: String(aNext.actionDetail || ""),
8889
+ calledAt: aNext.calledAt || void 0
8890
+ });
8798
8891
  }
8799
8892
  details.forEach((d, seq) => {
8800
- const node = initNode(sessionId, seq, d.actionType, d.actionDetail);
8893
+ const node = initNode(sessionId, seq, d.actionType, d.actionDetail, d.calledAt);
8801
8894
  nodes.push(node);
8802
8895
  if (node.stage) stageFlags.add(node.stage);
8803
8896
  });
@@ -8823,7 +8916,8 @@ function buildMultiSessionGraph(sessions, aNext) {
8823
8916
  last.sessionId,
8824
8917
  last.nodes.length,
8825
8918
  String(aNext.actionType || ""),
8826
- String(aNext.actionDetail || "")
8919
+ String(aNext.actionDetail || ""),
8920
+ aNext.calledAt || void 0
8827
8921
  ));
8828
8922
  if (last.nodes.length > 1) {
8829
8923
  last.edges.push({ src: last.nodes.length - 2, dst: last.nodes.length - 1, kind: "next" });
@@ -8881,6 +8975,27 @@ var init_builder = __esm({
8881
8975
  });
8882
8976
 
8883
8977
  // src/engine/formal-engine/properties.ts
8978
+ function parseTs(ts) {
8979
+ if (!ts) return null;
8980
+ const t = Date.parse(String(ts).trim());
8981
+ return Number.isFinite(t) ? t : null;
8982
+ }
8983
+ function privescExfilWindowSec() {
8984
+ const raw = (process.env.AGENT_MOSS_PRIVESC_EXFIL_WINDOW_SEC || "").trim();
8985
+ if (!raw) return 1800;
8986
+ const n = parseInt(raw, 10);
8987
+ return Number.isFinite(n) ? Math.max(0, n) : 1800;
8988
+ }
8989
+ function privescInWindow(nodes, indexes, exfilIdx, windowSec) {
8990
+ const before = indexes.filter((i) => i < exfilIdx);
8991
+ if (windowSec <= 0) return before;
8992
+ const exfilTs = parseTs(nodes[exfilIdx].timestamp);
8993
+ if (exfilTs === null) return before;
8994
+ return before.filter((i) => {
8995
+ const ts = parseTs(nodes[i].timestamp);
8996
+ return ts === null || exfilTs - ts <= windowSec * 1e3;
8997
+ });
8998
+ }
8884
8999
  function loadCrossSessionChains() {
8885
9000
  if (CROSS_SESSION_CHAINS) return CROSS_SESSION_CHAINS;
8886
9001
  const combo = loadL2().combo_chains || {};
@@ -9072,6 +9187,7 @@ function checkNeverExfilAfterPrivesc(graph) {
9072
9187
  const nodes = allNodes(graph);
9073
9188
  if (nodes.length < 2) return violations;
9074
9189
  const witness = graph.witnessIdx;
9190
+ const windowSec = privescExfilWindowSec();
9075
9191
  if (witness !== void 0) {
9076
9192
  const evIdx = [];
9077
9193
  nodes.forEach((n, i) => {
@@ -9079,15 +9195,17 @@ function checkNeverExfilAfterPrivesc(graph) {
9079
9195
  });
9080
9196
  if (evIdx.length === 0) return violations;
9081
9197
  const firstEv = Math.min(...evIdx);
9082
- let pre = null;
9083
- for (let i = 0; i < firstEv; i++) {
9084
- if (isPrivescNode(nodes[i])) pre = nodes[i];
9085
- }
9086
- if (pre) {
9198
+ const privescIdx = [];
9199
+ nodes.forEach((n, i) => {
9200
+ if (isPrivescNode(n)) privescIdx.push(i);
9201
+ });
9202
+ const candidates = privescInWindow(nodes, privescIdx, firstEv, windowSec);
9203
+ if (candidates.length > 0) {
9204
+ const pre = nodes[candidates[candidates.length - 1]];
9087
9205
  const post = nodes[firstEv];
9088
9206
  return [{
9089
9207
  propertyId: "no-exfil-after-privesc",
9090
- reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
9208
+ 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",
9091
9209
  riskLevel: "critical",
9092
9210
  path: [
9093
9211
  { sessionId: pre.sessionId, seq: pre.seq, detail: pre.actionDetail.slice(0, 60) },
@@ -9100,18 +9218,18 @@ function checkNeverExfilAfterPrivesc(graph) {
9100
9218
  for (let i = 0; i < nodes.length; i++) {
9101
9219
  if (!isPrivescNode(nodes[i])) continue;
9102
9220
  for (let j = i + 1; j < nodes.length; j++) {
9103
- if (isExfilNode(nodes[j])) {
9104
- violations.push({
9105
- propertyId: "no-exfil-after-privesc",
9106
- reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
9107
- riskLevel: "critical",
9108
- path: [
9109
- { sessionId: nodes[i].sessionId, seq: nodes[i].seq, detail: nodes[i].actionDetail.slice(0, 60) },
9110
- { sessionId: nodes[j].sessionId, seq: nodes[j].seq, detail: nodes[j].actionDetail.slice(0, 60) }
9111
- ]
9112
- });
9113
- return violations;
9114
- }
9221
+ if (!isExfilNode(nodes[j])) continue;
9222
+ if (privescInWindow(nodes, [i], j, windowSec).length === 0) continue;
9223
+ violations.push({
9224
+ propertyId: "no-exfil-after-privesc",
9225
+ 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",
9226
+ riskLevel: "critical",
9227
+ path: [
9228
+ { sessionId: nodes[i].sessionId, seq: nodes[i].seq, detail: nodes[i].actionDetail.slice(0, 60) },
9229
+ { sessionId: nodes[j].sessionId, seq: nodes[j].seq, detail: nodes[j].actionDetail.slice(0, 60) }
9230
+ ]
9231
+ });
9232
+ return violations;
9115
9233
  }
9116
9234
  }
9117
9235
  return violations;
@@ -9274,6 +9392,10 @@ function checkCrossSessionReachability(graph) {
9274
9392
  }
9275
9393
  return violations;
9276
9394
  }
9395
+ function isDataSourceNode(n) {
9396
+ if (n.stage === "CRED" || n.isLowSource) return true;
9397
+ return DATA_SOURCE_READ_RE.test(n.actionDetail);
9398
+ }
9277
9399
  function checkSensitiveDataAggregation(graph) {
9278
9400
  const violations = [];
9279
9401
  const nodes = allNodes(graph);
@@ -9293,6 +9415,7 @@ function checkSensitiveDataAggregation(graph) {
9293
9415
  for (const r of n.resources) {
9294
9416
  for (const j of resIndex.get(r) || []) {
9295
9417
  if (j === exfilIdx) continue;
9418
+ if (!isDataSourceNode(nodes[j])) continue;
9296
9419
  srcs.add(`${nodes[j].sessionId}#${nodes[j].seq}`);
9297
9420
  }
9298
9421
  }
@@ -9315,7 +9438,7 @@ function checkSensitiveDataAggregation(graph) {
9315
9438
  }
9316
9439
  return violations;
9317
9440
  }
9318
- var CROSS_SESSION_CHAINS, CRED_FILE_RE, EXFIL_RE, EBPF_NET_RE, REMOTE_RE, EXFIL_ACTION_RE, CRED_STRICT_RE, INLINE_SHELL_RE, PKG_MGR, SHELLS, TOOL_REGISTRY, COMPUTE_RE, STRONG_COMPUTE_TYPES, DL_OR_LOW_RE, ALL_PROPERTIES;
9441
+ var CROSS_SESSION_CHAINS, CRED_FILE_RE, EXFIL_RE, EBPF_NET_RE, REMOTE_RE, EXFIL_ACTION_RE, CRED_STRICT_RE, INLINE_SHELL_RE, FORMAL_RULES, PKG_MGR, SHELLS, TOOL_REGISTRY, COMPUTE_RE, STRONG_COMPUTE_TYPES, DL_OR_LOW_RE, DATA_SOURCE_READ_RE, ALL_PROPERTIES;
9319
9442
  var init_properties = __esm({
9320
9443
  "src/engine/formal-engine/properties.ts"() {
9321
9444
  "use strict";
@@ -9332,12 +9455,14 @@ var init_properties = __esm({
9332
9455
  EXFIL_ACTION_RE = /\bcurl\b|\bwget\b|\bscp\b|\brsync\b|\brclone\b|\bftp\b|connect\s+.*\d+\.\d+\.\d+\.\d+|open\s+.*tcp|base64\s+.*\-o/i;
9333
9456
  CRED_STRICT_RE = /(known_hosts|\.ssh|id_rsa|\.pem|\b\.key\b|\/etc\/shadow|\/etc\/passwd|\.aws\/credentials|credentials\.json|netrc|password|secrets?\.ya?ml|credentials?\s*(file|path)?\s*(=|\:)|\bconfig.*(password|token))/i;
9334
9457
  INLINE_SHELL_RE = /(?:os\.system|os\.popen)\s*\(\s*['"]([^'"]+)['"]|subprocess\.[a-z_]+\(\s*\[([^\]]+)\]|subprocess\.[a-z_]+\(\s*['"]([^'"]+)['"]|(?:bash|sh|zsh)\s+-c\s*['"]([^'"]+)['"]/gi;
9335
- PKG_MGR = /* @__PURE__ */ new Set(["apt", "apt-get", "aptitude", "yum", "dnf", "pacman", "zypper", "apk", "brew"]);
9336
- SHELLS = /* @__PURE__ */ new Set(["bash", "sh", "zsh", "dash", "fish", "ksh", "csh", "tcsh", "nologin"]);
9458
+ FORMAL_RULES = loadFormalRules();
9459
+ PKG_MGR = new Set(FORMAL_RULES.privesc?.package_managers ?? []);
9460
+ SHELLS = new Set(FORMAL_RULES.privesc?.bare_shells ?? []);
9337
9461
  TOOL_REGISTRY = null;
9338
9462
  COMPUTE_RE = /subprocess|os\.system|shell\s*=\s*True|\beval\(|\bexec\(|bash\s+[^\s]|sh\s+-c|python3?\s+|node\s+|\btimeout\s+|nohup\s+|\b\.\/[\w./-]+/i;
9339
- STRONG_COMPUTE_TYPES = /* @__PURE__ */ new Set(["python", "node", "exec", "terminal", "cmd"]);
9463
+ STRONG_COMPUTE_TYPES = new Set(FORMAL_RULES.compute_action_types ?? []);
9340
9464
  DL_OR_LOW_RE = /curl\s+.*-o\b|wget\s+.*\s+-O\b|curl\s+.*\|\s*(bash|sh|python)|wget\s+.*\|\s*(bash|sh|python)|git\s+clone|docker\s+pull|pip\s+install|npm\s+install|base64\s+-d/i;
9465
+ 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;
9341
9466
  ALL_PROPERTIES = [
9342
9467
  { id: "cross-session-chain", check: checkCrossSessionChain, shortName: "P1" },
9343
9468
  { id: "sensitive-data-flow", check: checkSensitiveDataFlow, shortName: "P2" },
@@ -9860,10 +9985,10 @@ var init_stateful = __esm({
9860
9985
  import * as fs4 from "fs";
9861
9986
  import * as os2 from "os";
9862
9987
  import * as path4 from "path";
9863
- import { createHash } from "crypto";
9988
+ import { createHash as createHash3 } from "crypto";
9864
9989
  function defaultStateDbPath() {
9865
9990
  const dir = process.env.AGENT_MOSS_STATE_DIR || path4.join(os2.homedir(), ".agentmoss");
9866
- const tag = createHash("sha1").update("agentmoss-stateful").digest("hex").slice(0, 8);
9991
+ const tag = createHash3("sha1").update("agentmoss-stateful").digest("hex").slice(0, 8);
9867
9992
  return path4.join(dir, `stateful_chains_${tag}.json`);
9868
9993
  }
9869
9994
  var ChainStateStore;
@@ -9871,6 +9996,7 @@ var init_chain_state = __esm({
9871
9996
  "src/engine/formal-engine/chain_state.ts"() {
9872
9997
  "use strict";
9873
9998
  init_esm_shims();
9999
+ init_security_policy();
9874
10000
  init_stateful();
9875
10001
  ChainStateStore = class {
9876
10002
  filePath;
@@ -9897,8 +10023,8 @@ var init_chain_state = __esm({
9897
10023
  this._timer = setTimeout(() => {
9898
10024
  this._timer = null;
9899
10025
  try {
9900
- fs4.mkdirSync(path4.dirname(this.filePath), { recursive: true });
9901
- fs4.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2), "utf-8");
10026
+ ensurePrivateDir(path4.dirname(this.filePath));
10027
+ writePrivateText(this.filePath, JSON.stringify(this.data, null, 2));
9902
10028
  } catch {
9903
10029
  }
9904
10030
  }, this._flushDelayMs);
@@ -9910,8 +10036,8 @@ var init_chain_state = __esm({
9910
10036
  this._timer = null;
9911
10037
  }
9912
10038
  try {
9913
- fs4.mkdirSync(path4.dirname(this.filePath), { recursive: true });
9914
- fs4.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2), "utf-8");
10039
+ ensurePrivateDir(path4.dirname(this.filePath));
10040
+ writePrivateText(this.filePath, JSON.stringify(this.data, null, 2));
9915
10041
  } catch {
9916
10042
  }
9917
10043
  }
@@ -10123,7 +10249,7 @@ async function runAnalysis(input) {
10123
10249
  const actionType = input.aNext.actionType.toLowerCase();
10124
10250
  const rawDetail = extractRawCommand(input.aNext.actionDetail);
10125
10251
  const lowerDetail = rawDetail.toLowerCase();
10126
- if (FULLY_SAFE_TOOLS.has(actionType)) {
10252
+ if (getPlatformTraits(input.osType).safeActionTypes.has(actionType)) {
10127
10253
  return {
10128
10254
  allowed: true,
10129
10255
  reason: `\u5B8C\u5168\u5B89\u5168\u5DE5\u5177\uFF0C\u8DF3\u8FC7\u6DF1\u5EA6\u5206\u6790: ${actionType}`,
@@ -10166,7 +10292,7 @@ async function runAnalysis(input) {
10166
10292
  violatedLayers: []
10167
10293
  };
10168
10294
  }
10169
- let skipLLM = READONLY_SENSITIVE_TOOLS.has(actionType);
10295
+ let skipLLM = getPlatformTraits(input.osType).readonlySensitiveActionTypes.has(actionType);
10170
10296
  if (actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash") {
10171
10297
  const firstCmd = extractFirstCommand(lowerDetail);
10172
10298
  const { fullySafeCommands, readonlySensitiveCommands } = getPlatformTraits(input.osType);
@@ -10398,7 +10524,7 @@ async function runAnalysis(input) {
10398
10524
  }
10399
10525
  return llmResult;
10400
10526
  }
10401
- var FULLY_SAFE_TOOLS, TRUSTED_SKILLS_BASE, READONLY_SENSITIVE_TOOLS, _brainStore, _brainExtractor, _brainSkillManager, _brainCurator, _brainInitialized, _brainLastMaintenance, BRAIN_MAINT_INTERVAL_MS, formalEngine;
10527
+ var TRUSTED_SKILLS_BASE, _brainStore, _brainExtractor, _brainSkillManager, _brainCurator, _brainInitialized, _brainLastMaintenance, BRAIN_MAINT_INTERVAL_MS, formalEngine;
10402
10528
  var init_coordinator = __esm({
10403
10529
  "src/engine/coordinator.ts"() {
10404
10530
  "use strict";
@@ -10421,25 +10547,7 @@ var init_coordinator = __esm({
10421
10547
  init_asset_dir();
10422
10548
  init_runtime_config_bridge();
10423
10549
  init_formal_engine();
10424
- FULLY_SAFE_TOOLS = /* @__PURE__ */ new Set([
10425
- "ask_user_question",
10426
- "glob",
10427
- "list_dir",
10428
- "ls",
10429
- "count_text_length",
10430
- "filemgr-globfiles"
10431
- ]);
10432
10550
  TRUSTED_SKILLS_BASE = /* @__PURE__ */ new Set([]);
10433
- READONLY_SENSITIVE_TOOLS = /* @__PURE__ */ new Set([
10434
- "read",
10435
- "file_read",
10436
- "read_file",
10437
- "head",
10438
- "tail",
10439
- "grep",
10440
- "filemgr-readfile",
10441
- "filemgr-grepfiles"
10442
- ]);
10443
10551
  _brainStore = null;
10444
10552
  _brainExtractor = null;
10445
10553
  _brainSkillManager = null;
@@ -10454,13 +10562,156 @@ var init_coordinator = __esm({
10454
10562
  // src/server.ts
10455
10563
  init_esm_shims();
10456
10564
  import { Hono as Hono2 } from "hono";
10565
+
10566
+ // src/infra/rules-contract.ts
10567
+ init_esm_shims();
10568
+ init_paths();
10569
+ init_rules_loader();
10570
+ import { createHash } from "crypto";
10571
+ import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
10572
+ import { join as join3 } from "path";
10573
+ var RULES_SCHEMA_VERSION = 1;
10574
+ var REQUIRED_RULE_FILES = {
10575
+ "l1_rules.json": ["dangerous_commands", "injection_detection", "sensitive_path_access"],
10576
+ "l2_rules.json": ["intent_consistency", "password_modify_consent", "user_deletion_consent"],
10577
+ "l3_rules.json": [],
10578
+ "mc_rules.json": ["rules", "ap_patterns"],
10579
+ "tool_registry.json": ["tools"],
10580
+ "platform_traits.json": ["linux", "windows"],
10581
+ "inline_rules.json": ["read_write_keywords", "dir_traversal_keywords", "write_exec_chain_patterns"],
10582
+ "script_rules.json": [
10583
+ "suspicious_patterns",
10584
+ "keyword_combination_risks",
10585
+ "single_keyword_risks",
10586
+ "script_exec_patterns"
10587
+ ],
10588
+ "heuristic_rules.json": [
10589
+ "sensitive_excluded_prefixes",
10590
+ "search_action_prefixes",
10591
+ "loopback_hosts",
10592
+ "temp_dirs"
10593
+ ],
10594
+ "logic_rules.json": ["indirect_file_access", "write_commands", "source_arg_commands"],
10595
+ "formal_rules.json": ["privesc", "compute_action_types", "pattern_miner", "output_commands"]
10596
+ };
10597
+ var MANIFEST_FILE = "manifest.json";
10598
+ function sha256Short(data, n = 12) {
10599
+ return createHash("sha256").update(data).digest("hex").slice(0, n);
10600
+ }
10601
+ function rulesDir() {
10602
+ if (getRulesRoot()) return join3(getRulesRoot(), "rules");
10603
+ const probe = resolveRulesFile("l1_rules.json");
10604
+ return probe ? probe.slice(0, probe.length - "l1_rules.json".length - 1) : null;
10605
+ }
10606
+ function checkRulesAssets() {
10607
+ const rep = {
10608
+ ok: true,
10609
+ schemaVersion: 0,
10610
+ fingerprint: "",
10611
+ fileHashes: {},
10612
+ errors: [],
10613
+ external: getRulesRoot() !== null
10614
+ };
10615
+ const dir = rulesDir();
10616
+ if (!dir) {
10617
+ rep.errors.push("\u627E\u4E0D\u5230\u89C4\u5219\u76EE\u5F55\uFF08\u5305\u5185\u89C4\u5219\u6587\u4EF6\u7F3A\u5931\uFF0C\u4E14\u672A\u8BBE AGENT_MOSS_RULES_ROOT\uFF09");
10618
+ rep.ok = false;
10619
+ return rep;
10620
+ }
10621
+ const manifestPath = join3(dir, MANIFEST_FILE);
10622
+ if (!existsSync2(manifestPath)) {
10623
+ rep.errors.push(`\u7F3A\u5C11 ${MANIFEST_FILE}\uFF08\u89C4\u5219\u5305\u5FC5\u987B\u58F0\u660E schema \u7248\u672C\uFF09`);
10624
+ } else {
10625
+ try {
10626
+ const m = JSON.parse(readFileSync3(manifestPath, "utf-8")) || {};
10627
+ const ver = m.schema_version;
10628
+ if (typeof ver !== "number" || !Number.isInteger(ver)) {
10629
+ rep.errors.push(`${MANIFEST_FILE} \u7F3A\u5C11\u6574\u6570 schema_version`);
10630
+ } else {
10631
+ rep.schemaVersion = ver;
10632
+ if (ver !== RULES_SCHEMA_VERSION) {
10633
+ rep.errors.push(
10634
+ `schema \u7248\u672C\u4E0D\u5339\u914D\uFF1A\u89C4\u5219\u5305\u662F ${ver}\uFF0C\u672C\u5F15\u64CE\u8BA4\u8BC6 ${RULES_SCHEMA_VERSION}\u3002\u8BF7\u5347\u7EA7\u5F15\u64CE\u6216\u6539\u7528\u5BF9\u5E94\u7248\u672C\u7684\u89C4\u5219\u5305\u3002`
10635
+ );
10636
+ }
10637
+ }
10638
+ } catch (e) {
10639
+ rep.errors.push(`${MANIFEST_FILE} \u8BFB\u53D6/\u89E3\u6790\u5931\u8D25: ${String(e)}`);
10640
+ }
10641
+ }
10642
+ const fpParts = [];
10643
+ for (const [name, requiredKeys] of Object.entries(REQUIRED_RULE_FILES)) {
10644
+ const p = join3(dir, name);
10645
+ if (!existsSync2(p)) {
10646
+ rep.errors.push(`\u7F3A\u5C11\u89C4\u5219\u6587\u4EF6 ${name}`);
10647
+ rep.fileHashes[name] = "MISSING";
10648
+ fpParts.push(`${name}:MISSING`);
10649
+ continue;
10650
+ }
10651
+ let raw = null;
10652
+ try {
10653
+ raw = readFileSync3(p);
10654
+ } catch (e) {
10655
+ rep.errors.push(`${name} \u8BFB\u53D6\u5931\u8D25: ${String(e)}`);
10656
+ rep.fileHashes[name] = "UNREADABLE";
10657
+ fpParts.push(`${name}:UNREADABLE`);
10658
+ continue;
10659
+ }
10660
+ rep.fileHashes[name] = sha256Short(raw);
10661
+ fpParts.push(`${name}:${rep.fileHashes[name]}`);
10662
+ let data = null;
10663
+ try {
10664
+ data = JSON.parse(raw.toString("utf-8")) || {};
10665
+ } catch (e) {
10666
+ rep.errors.push(`${name} \u89E3\u6790\u5931\u8D25: ${String(e)}`);
10667
+ rep.fileHashes[name] = "BADJSON";
10668
+ continue;
10669
+ }
10670
+ if (typeof data !== "object" || Array.isArray(data)) {
10671
+ rep.errors.push(`${name} \u9876\u5C42\u5FC5\u987B\u662F JSON \u5BF9\u8C61`);
10672
+ continue;
10673
+ }
10674
+ const missing = requiredKeys.filter((k) => !(k in data));
10675
+ if (missing.length) rep.errors.push(`${name} \u7F3A\u5C11\u9876\u5C42\u952E: ${missing.join(", ")}`);
10676
+ }
10677
+ rep.fingerprint = sha256Short([...fpParts].sort().join("\n"));
10678
+ rep.ok = rep.errors.length === 0;
10679
+ return rep;
10680
+ }
10681
+ function formatRulesIntegrity(rep) {
10682
+ const src = rep.external ? `\u5916\u90E8\u89C4\u5219\u5305 ${getRulesRoot()}` : "\u5305\u5185\u89C4\u5219";
10683
+ const head = `\u89C4\u5219\u5305: ${src} | schema v${rep.schemaVersion} | \u6307\u7EB9 ${rep.fingerprint || "-"}`;
10684
+ if (rep.ok) return head;
10685
+ return `${head} | \u274C ${rep.errors.length} \u4E2A\u95EE\u9898: ${rep.errors.slice(0, 5).join("\uFF1B")}`;
10686
+ }
10687
+ function enforceRulesContract() {
10688
+ const rep = checkRulesAssets();
10689
+ if (rep.ok) {
10690
+ console.log(`[AgentMoss] ${formatRulesIntegrity(rep)}`);
10691
+ return rep;
10692
+ }
10693
+ const msg = formatRulesIntegrity(rep);
10694
+ if (rep.external) {
10695
+ console.error(`[AgentMoss] \u274C ${msg}`);
10696
+ throw new Error(
10697
+ `\u89C4\u5219\u5305\u6821\u9A8C\u5931\u8D25\uFF0C\u62D2\u7EDD\u542F\u52A8\uFF08AGENT_MOSS_RULES_ROOT \u5DF2\u663E\u5F0F\u6307\u5B9A\uFF0C\u4E0D\u5141\u8BB8\u5E26\u7740\u6B8B\u7F3A\u89C4\u5219\u8FD0\u884C\uFF09\uFF1A${rep.errors.join("\uFF1B")}`
10698
+ );
10699
+ }
10700
+ console.error(`[AgentMoss] \u26A0\uFE0F ${msg}\uFF08\u4F7F\u7528\u5305\u5185\u89C4\u5219\uFF0C\u7167\u5E38\u542F\u52A8\uFF1B\u82E5\u662F npm \u5B89\u88C5\u8BF7\u91CD\u88C5\uFF09`);
10701
+ return rep;
10702
+ }
10703
+
10704
+ // src/server.ts
10705
+ init_security_policy();
10457
10706
  import { cors } from "hono/cors";
10458
10707
  import { logger } from "hono/logger";
10459
10708
  import net from "net";
10460
- import { existsSync as existsSync19 } from "fs";
10709
+ import { existsSync as existsSync21 } from "fs";
10461
10710
 
10462
10711
  // src/routes.ts
10463
10712
  init_esm_shims();
10713
+ init_security_policy();
10714
+ import { randomBytes as randomBytes2 } from "crypto";
10464
10715
 
10465
10716
  // src/models.ts
10466
10717
  init_esm_shims();
@@ -10480,7 +10731,10 @@ var ActionItemSchema = z.object({
10480
10731
  // 工具返回给 agent 的内容。仅 L2 的授权(consent)判定用:判断「用户是否已授权」
10481
10732
  // 看的是 ask_user_question 的**回答**,不是提问正文。历史 bug:模型里没有这个字段、
10482
10733
  // 路由也没透传,TS 的授权判定退化成「提问正文非空」,等于问过任何问题即已授权。
10483
- output: z.string().optional().describe("Tool result / user answer (needed by L2 consent checks)")
10734
+ output: z.string().optional().describe("Tool result / user answer (needed by L2 consent checks)"),
10735
+ // 动作真实发生时间(可选)。供 P4「提权→外传」时间窗口判定;缺省时该性质
10736
+ // 退回不设窗口的旧行为。与 Python ActionItem.called_at 对齐。
10737
+ called_at: z.string().optional().describe("Action timestamp (optional)")
10484
10738
  });
10485
10739
  var PromptTurnSchema = z.object({
10486
10740
  text: z.string().default("").describe("User prompt text at this turn"),
@@ -10546,23 +10800,39 @@ var ReviewResponseSchema = z.object({
10546
10800
 
10547
10801
  // src/engine/analyzer.ts
10548
10802
  init_esm_shims();
10803
+ init_security_policy();
10549
10804
  init_coordinator();
10550
- import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync16, writeFileSync as writeFileSync7 } from "fs";
10551
- import { tmpdir as tmpdir2 } from "os";
10552
- import { join as join11, resolve as resolve10 } from "path";
10805
+ import { existsSync as existsSync18, readFileSync as readFileSync18 } from "fs";
10806
+ import { tmpdir as tmpdir3 } from "os";
10807
+ import { join as join16, resolve as resolve11 } from "path";
10553
10808
 
10554
10809
  // src/infra/policy-cache.ts
10555
10810
  init_esm_shims();
10811
+ init_security_policy();
10812
+ init_paths();
10556
10813
  init_config();
10557
- import { createHash as createHash2 } from "crypto";
10558
- import { existsSync as existsSync15, mkdirSync as mkdirSync6, readdirSync as readdirSync5, readFileSync as readFileSync15, rmdirSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync6 } from "fs";
10559
- import { tmpdir } from "os";
10560
- import { join as join10 } from "path";
10561
- var CACHE_DIR = join10(tmpdir(), "agent_moss_policy_cache");
10814
+ import { createHash as createHash4 } from "crypto";
10815
+ import { existsSync as existsSync17, lstatSync as lstatSync2, readdirSync as readdirSync5, readFileSync as readFileSync17, rmdirSync, unlinkSync as unlinkSync3 } from "fs";
10816
+ import { tmpdir as tmpdir2 } from "os";
10817
+ import { join as join15 } from "path";
10818
+ var CACHE_DIR = join15(tmpdir2(), "agent_moss_policy_cache");
10562
10819
  var cacheDirOverride = null;
10563
10820
  function cacheDir() {
10564
10821
  return cacheDirOverride ?? CACHE_DIR;
10565
10822
  }
10823
+ function safeCacheDir() {
10824
+ const dir = cacheDir();
10825
+ if (ensurePrivateDir(dir)) return dir;
10826
+ try {
10827
+ const fallback = join15(getDataRoot(), "policy_cache");
10828
+ if (ensurePrivateDir(fallback)) {
10829
+ console.warn(`[AgentMoss] \u7F13\u5B58\u76EE\u5F55 ${dir} \u4E0D\u5B89\u5168\uFF08\u88AB\u5176\u4ED6\u7528\u6237\u62E5\u6709\u6216\u4E3A\u7B26\u53F7\u94FE\u63A5\uFF09\uFF0C\u5DF2\u56DE\u9000\u5230 ${fallback}`);
10830
+ return fallback;
10831
+ }
10832
+ } catch {
10833
+ }
10834
+ return dir;
10835
+ }
10566
10836
  var PolicyCache = class {
10567
10837
  // Map 的插入序即 LRU 序(get 时重新 set 到末尾)
10568
10838
  constructor(options) {
@@ -10574,9 +10844,9 @@ var PolicyCache = class {
10574
10844
  return `${sessionId}::${promptSession}`;
10575
10845
  }
10576
10846
  cacheFilePath(sessionId, promptSession) {
10577
- const promptHash = createHash2("md5").update(promptSession).digest("hex").slice(0, 12);
10578
- const safeSessionId = sessionId.replace(/\//g, "_").replace(/\\/g, "_");
10579
- return join10(cacheDir(), safeSessionId, `${promptHash}.toml`);
10847
+ const promptHash = createHash4("md5").update(promptSession).digest("hex").slice(0, 12);
10848
+ const safeSessionId = sessionId.replace(/\//g, "_").replace(/\\/g, "_").replace(/\.\./g, "__");
10849
+ return join15(safeCacheDir(), safeSessionId, `${promptHash}.toml`);
10580
10850
  }
10581
10851
  /** 取缓存:内存 → 文件;未命中返回 null。 */
10582
10852
  get(sessionId, promptSession) {
@@ -10589,9 +10859,9 @@ var PolicyCache = class {
10589
10859
  return hit;
10590
10860
  }
10591
10861
  const file = this.cacheFilePath(sessionId, promptSession);
10592
- if (existsSync15(file)) {
10862
+ if (existsSync17(file) && !lstatSync2(file).isSymbolicLink()) {
10593
10863
  try {
10594
- const policy = readFileSync15(file, "utf-8");
10864
+ const policy = readFileSync17(file, "utf-8");
10595
10865
  this.remember(key, policy);
10596
10866
  return policy;
10597
10867
  } catch {
@@ -10605,8 +10875,10 @@ var PolicyCache = class {
10605
10875
  this.remember(this.cacheKey(sessionId, promptSession), policy);
10606
10876
  const file = this.cacheFilePath(sessionId, promptSession);
10607
10877
  try {
10608
- mkdirSync6(join10(cacheDir(), sessionId.replace(/\//g, "_").replace(/\\/g, "_")), { recursive: true });
10609
- writeFileSync6(file, policy, "utf-8");
10878
+ const dir = safeCacheDir();
10879
+ const sub = sessionId.replace(/\//g, "_").replace(/\\/g, "_").replace(/\.\./g, "__");
10880
+ if (!ensurePrivateDir(join15(dir, sub))) return;
10881
+ writePrivateText(file, policy);
10610
10882
  } catch {
10611
10883
  }
10612
10884
  }
@@ -10630,13 +10902,13 @@ var PolicyCache = class {
10630
10902
  for (const k of [...this.memory.keys()]) {
10631
10903
  if (k.startsWith(`${sessionId}::`)) this.memory.delete(k);
10632
10904
  }
10633
- const safeSessionId = sessionId.replace(/\//g, "_").replace(/\\/g, "_");
10634
- const dir = join10(cacheDir(), safeSessionId);
10905
+ const safeSessionId = sessionId.replace(/\//g, "_").replace(/\\/g, "_").replace(/\.\./g, "__");
10906
+ const dir = join15(cacheDir(), safeSessionId);
10635
10907
  try {
10636
- if (existsSync15(dir)) {
10908
+ if (existsSync17(dir)) {
10637
10909
  for (const f of readdirSync5(dir)) {
10638
10910
  try {
10639
- unlinkSync3(join10(dir, f));
10911
+ unlinkSync3(join15(dir, f));
10640
10912
  } catch {
10641
10913
  }
10642
10914
  }
@@ -10648,11 +10920,11 @@ var PolicyCache = class {
10648
10920
  clear() {
10649
10921
  this.memory.clear();
10650
10922
  try {
10651
- if (existsSync15(cacheDir())) {
10923
+ if (existsSync17(cacheDir())) {
10652
10924
  for (const sessionDir of readdirSync5(cacheDir())) {
10653
- const dir = join10(cacheDir(), sessionDir);
10925
+ const dir = join15(cacheDir(), sessionDir);
10654
10926
  try {
10655
- for (const f of readdirSync5(dir)) unlinkSync3(join10(dir, f));
10927
+ for (const f of readdirSync5(dir)) unlinkSync3(join15(dir, f));
10656
10928
  rmdirSync(dir);
10657
10929
  } catch {
10658
10930
  }
@@ -10739,7 +11011,7 @@ ${safe(policy, 2e3)}`);
10739
11011
 
10740
11012
  // src/engine/analyzer.ts
10741
11013
  import { parse as parseToml } from "smol-toml";
10742
- var POLICY_OUTPUT_DIR = join11(tmpdir2(), "agent_moss");
11014
+ var POLICY_OUTPUT_DIR = join16(tmpdir3(), "agent_moss");
10743
11015
  var policyOutputDirOverride = null;
10744
11016
  function policyOutputDir() {
10745
11017
  return policyOutputDirOverride ?? POLICY_OUTPUT_DIR;
@@ -10773,10 +11045,13 @@ function savePolicyToFile(sessionId, policyToml) {
10773
11045
  if (!isPolicySaveEnabled()) return null;
10774
11046
  try {
10775
11047
  const outDir = policyOutputDir();
10776
- mkdirSync7(outDir, { recursive: true });
10777
- const safeName = sessionId.replace(/\//g, "_").replace(/\\/g, "_");
10778
- const filepath = join11(outDir, `${safeName}.toml`);
10779
- writeFileSync7(filepath, policyToml, "utf-8");
11048
+ if (!ensurePrivateDir(outDir)) {
11049
+ console.warn(`[AgentMoss] Policy \u8F93\u51FA\u76EE\u5F55 ${outDir} \u4E0D\u5B89\u5168\uFF0C\u8DF3\u8FC7\u843D\u76D8\uFF08\u4E0D\u5F71\u54CD\u5224\u5B9A\uFF09`);
11050
+ return null;
11051
+ }
11052
+ const safeName = sessionId.replace(/\//g, "_").replace(/\\/g, "_").replace(/\.\./g, "__");
11053
+ const filepath = join16(outDir, `${safeName}.toml`);
11054
+ writePrivateText(filepath, policyToml);
10780
11055
  return filepath;
10781
11056
  } catch (err) {
10782
11057
  console.warn(`[AgentMoss] \u7B56\u7565\u6587\u4EF6\u843D\u76D8\u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u5224\u5B9A\uFF09: ${policyOutputDir()}: ${String(err)}`);
@@ -10813,12 +11088,12 @@ function validateToml(tomlStr) {
10813
11088
  function loadPolicyMappingDoc() {
10814
11089
  const dir = resolveAssetDir(__dirname, "templates");
10815
11090
  if (dir) {
10816
- const p = resolve10(dir, "policy_mapping.md");
10817
- if (existsSync16(p)) return readFileSync16(p, "utf-8");
11091
+ const p = resolve11(dir, "policy_mapping.md");
11092
+ if (existsSync18(p)) return readFileSync18(p, "utf-8");
10818
11093
  }
10819
11094
  for (const cand of assetDirCandidates(__dirname, "templates")) {
10820
- const p = resolve10(cand, "policy_mapping.md");
10821
- if (existsSync16(p)) return readFileSync16(p, "utf-8");
11095
+ const p = resolve11(cand, "policy_mapping.md");
11096
+ if (existsSync18(p)) return readFileSync18(p, "utf-8");
10822
11097
  }
10823
11098
  return "";
10824
11099
  }
@@ -11093,9 +11368,11 @@ init_formal_engine();
11093
11368
 
11094
11369
  // src/engine/formal-engine/pattern_miner.ts
11095
11370
  init_esm_shims();
11371
+ init_rules_loader();
11096
11372
  init_builder();
11097
- var DANGER_STAGES = /* @__PURE__ */ new Set(["PRIVESC", "EXFIL", "LISTEN"]);
11098
- var SENSITIVE_PAIRS = /* @__PURE__ */ new Set(["CRED|LISTEN", "LISTEN|CRED"]);
11373
+ var _PM = loadFormalRules().pattern_miner ?? {};
11374
+ var DANGER_STAGES = new Set(_PM.danger_stages ?? []);
11375
+ var SENSITIVE_PAIRS = new Set((_PM.sensitive_pairs ?? []).map((p) => p.join("|")));
11099
11376
  function projectStages(actionHistory) {
11100
11377
  const stages = [];
11101
11378
  let prev = null;
@@ -11335,9 +11612,9 @@ function normalizeObservablePayload(body) {
11335
11612
  init_rule_store();
11336
11613
  init_skill_manager();
11337
11614
  init_asset_dir();
11338
- import { join as join13, dirname as dirname11 } from "path";
11615
+ import { join as join18, dirname as dirname12 } from "path";
11339
11616
  import { fileURLToPath as fileURLToPath10 } from "url";
11340
- var __dirname9 = dirname11(fileURLToPath10(import.meta.url));
11617
+ var __dirname9 = dirname12(fileURLToPath10(import.meta.url));
11341
11618
  async function healthRoute(c) {
11342
11619
  return c.json(HealthResponseSchema.parse({
11343
11620
  instance: process.env.AGENT_MOSS_INSTANCE || ""
@@ -11352,7 +11629,23 @@ async function analyzeRoute(c) {
11352
11629
  if (!osType) {
11353
11630
  osType = process.platform === "win32" ? "windows" : "linux";
11354
11631
  }
11355
- const agentId = req.agent_id || req.metadata?.agent_type || "";
11632
+ const _principal = principalOf(c.req.raw.headers, new URL(c.req.url).pathname);
11633
+ const [_boundOk, agentId] = bindAgent(_principal, req.agent_id || req.metadata?.agent_type || "");
11634
+ if (!_boundOk) {
11635
+ return c.json({ detail: `agent_id \u4E0E\u6240\u6301\u51ED\u8BC1\u4E0D\u5339\u914D: ${agentId}` }, 403);
11636
+ }
11637
+ const _sweepKey = _principal || `ip:${c.req.header("x-forwarded-for") || "local"}`;
11638
+ const [_sweepN, _sweepHit] = noteSessionAccess(_sweepKey, req.session_id);
11639
+ if (_sweepHit) {
11640
+ if (sessionSweepAction() === "deny") {
11641
+ return c.json({
11642
+ detail: `\u77ED\u65F6\u95F4\u5185\u8BBF\u95EE\u4E86 ${_sweepN} \u4E2A\u4E0D\u540C\u4F1A\u8BDD\uFF0C\u8D85\u51FA\u9608\u503C ${sessionSweepThreshold()}\uFF08\u7591\u4F3C\u5192\u5145/\u626B\u63CF\uFF09`
11643
+ }, 429);
11644
+ }
11645
+ console.warn(
11646
+ `[AgentMoss] \u26A0\uFE0F \u8DE8\u4F1A\u8BDD\u6279\u91CF\u8BBF\u95EE: key=${_sweepKey} \u7A97\u53E3\u5185 ${_sweepN} \u4E2A\u4E0D\u540C session\uFF08\u9608\u503C ${sessionSweepThreshold()}\uFF09`
11647
+ );
11648
+ }
11356
11649
  const result = await analyze({
11357
11650
  sessionId: req.session_id,
11358
11651
  promptSession: req.prompt_session,
@@ -11363,14 +11656,16 @@ async function analyzeRoute(c) {
11363
11656
  actionHistory: req.action_history.map((h) => ({
11364
11657
  actionType: h.action_type,
11365
11658
  actionDetail: h.action_detail,
11366
- output: h.output
11659
+ output: h.output,
11660
+ calledAt: h.called_at
11367
11661
  })),
11368
11662
  sessionHistory: (req.session_history || []).map((s) => ({
11369
11663
  sessionId: s.session_id,
11370
11664
  actionHistory: (s.action_history || []).map((h) => ({
11371
11665
  actionType: h.action_type,
11372
11666
  actionDetail: h.action_detail,
11373
- output: h.output
11667
+ output: h.output,
11668
+ calledAt: h.called_at
11374
11669
  }))
11375
11670
  })),
11376
11671
  // prompt_history 必须透传:L2 意图一致性(logic-rules.ts 的
@@ -11397,7 +11692,8 @@ async function analyzeRoute(c) {
11397
11692
  // agent_id:AgentGate V1 normalizer 历史上把 agent_type 塞 metadata,
11398
11693
  // 顶层为空时兜底(OpenDesk 的 agent-moss 需 agent_id 加载专属规则)。
11399
11694
  agentId,
11400
- llmConfig: req.metadata?.llm_config,
11695
+ // 严格模式:完全不接受 per-request LLM 配置(默认接受,保持原行为)
11696
+ llmConfig: perRequestLlmEnabled() ? req.metadata?.llm_config : void 0,
11401
11697
  llmLogPath: req.metadata?.llm_log_path
11402
11698
  });
11403
11699
  const durationMs = performance.now() - t0;
@@ -11445,7 +11741,8 @@ async function analyzeRoute(c) {
11445
11741
  violated_policy: "",
11446
11742
  confidence: 90,
11447
11743
  analysis_duration_ms: Math.round((performance.now() - t0) * 10) / 10,
11448
- violation_chain: judgment.violation_chain
11744
+ // 跨会话节点脱敏(时间线按 agent_id 共享,原样返回等于泄露别人的命令原文)
11745
+ violation_chain: redactChain(judgment.violation_chain, req.session_id)
11449
11746
  })
11450
11747
  );
11451
11748
  }
@@ -11469,14 +11766,19 @@ async function analyzeRoute(c) {
11469
11766
  })
11470
11767
  );
11471
11768
  } catch (err) {
11472
- console.error("[AgentMoss] Analysis error:", err);
11473
11769
  if (err instanceof SyntaxError) {
11474
11770
  return c.json({ detail: err.message, error: "Invalid JSON" }, 422);
11475
11771
  }
11476
11772
  if (err?.issues) {
11477
11773
  return c.json({ detail: err.issues, error: "Validation error" }, 422);
11478
11774
  }
11479
- return c.json({ detail: String(err), error: "Internal error" }, 500);
11775
+ if (errorDetailMode() === "full") {
11776
+ console.error("[AgentMoss] Analysis error:", err);
11777
+ return c.json({ detail: String(err), error: "Internal error" }, 500);
11778
+ }
11779
+ const ref = randomBytes2(4).toString("hex");
11780
+ console.error(`[AgentMoss] Analysis internal error (ref=${ref}):`, err);
11781
+ return c.json({ detail: `Internal server error (ref: ${ref})`, error: "Internal error" }, 500);
11480
11782
  }
11481
11783
  }
11482
11784
  var reviewStore = null;
@@ -11515,6 +11817,11 @@ async function reviewRoute(c) {
11515
11817
  if (!tracker) {
11516
11818
  tracker = new StatefulChainTracker(agentId);
11517
11819
  }
11820
+ if (req.db_path && !pathWithin(req.db_path, reviewRoots())) {
11821
+ return c.json({
11822
+ detail: "db_path \u4E0D\u5728\u5141\u8BB8\u8BFB\u53D6\u7684\u76EE\u5F55\u5185\uFF1B\u6807\u51C6 AgentGate \u90E8\u7F72\u65E0\u9700\u914D\u7F6E\uFF0C\u81EA\u5B9A\u4E49\u8DEF\u5F84\u8BF7\u52A0\u5165 AGENT_MOSS_REVIEW_PATHS"
11823
+ }, 403);
11824
+ }
11518
11825
  let sessions = [];
11519
11826
  if (req.db_path) {
11520
11827
  sessions = loadAgentGateSessions(req.db_path);
@@ -11578,15 +11885,20 @@ async function reviewRoute(c) {
11578
11885
  discovered_patterns: discovered
11579
11886
  }));
11580
11887
  } catch (err) {
11581
- console.error("[AgentMoss] Review error:", err);
11582
- return c.json({ error: "Internal error", detail: String(err) }, 500);
11888
+ if (errorDetailMode() === "full") {
11889
+ console.error("[AgentMoss] Review error:", err);
11890
+ return c.json({ error: "Internal error", detail: String(err) }, 500);
11891
+ }
11892
+ const ref = randomBytes2(4).toString("hex");
11893
+ console.error(`[AgentMoss] Review internal error (ref=${ref}):`, err);
11894
+ return c.json({ error: "Internal error", detail: `Internal server error (ref: ${ref})` }, 500);
11583
11895
  }
11584
11896
  }
11585
11897
  function getBrainRuleStore() {
11586
11898
  return new BrainRuleStore();
11587
11899
  }
11588
11900
  function getBrainSkillManager() {
11589
- const dir = resolveAssetDir(__dirname9, "skills") ?? join13(__dirname9, "..", "..", "agent_moss", "skills");
11901
+ const dir = resolveAssetDir(__dirname9, "skills") ?? join18(__dirname9, "..", "..", "agent_moss", "skills");
11590
11902
  return new BrainSkillManager(dir);
11591
11903
  }
11592
11904
  async function brainRulesRoute(c) {
@@ -11689,11 +12001,12 @@ init_rules_loader();
11689
12001
  init_asset_dir();
11690
12002
  init_rules_mode();
11691
12003
  import { Hono } from "hono";
11692
- import { readFileSync as readFileSync18, existsSync as existsSync18, unlinkSync as unlinkSync4 } from "fs";
11693
- import { join as join14, dirname as dirname12 } from "path";
12004
+ import { timingSafeEqual as timingSafeEqual2 } from "crypto";
12005
+ import { readFileSync as readFileSync20, existsSync as existsSync20, unlinkSync as unlinkSync4 } from "fs";
12006
+ import { join as join19, dirname as dirname13 } from "path";
11694
12007
  import { fileURLToPath as fileURLToPath11 } from "url";
11695
- var __dirname10 = dirname12(fileURLToPath11(import.meta.url));
11696
- var STATIC_DIR = join14(__dirname10, "static");
12008
+ var __dirname10 = dirname13(fileURLToPath11(import.meta.url));
12009
+ var STATIC_DIR = join19(__dirname10, "static");
11697
12010
  var CONSOLE_TOKEN_ENV = "AGENT_MOSS_CONSOLE_TOKEN";
11698
12011
  function getConsoleToken() {
11699
12012
  return process.env[CONSOLE_TOKEN_ENV] || "";
@@ -11702,7 +12015,11 @@ function verifyAuth(req) {
11702
12015
  const token = getConsoleToken();
11703
12016
  if (!token) return true;
11704
12017
  const auth = req.headers.get("Authorization") || "";
11705
- if (auth.startsWith("Bearer ")) return auth.slice(7) === token;
12018
+ if (auth.startsWith("Bearer ")) {
12019
+ const a = Buffer.from(auth.slice(7), "utf-8");
12020
+ const b = Buffer.from(token, "utf-8");
12021
+ return a.length === b.length && timingSafeEqual2(a, b);
12022
+ }
11706
12023
  return false;
11707
12024
  }
11708
12025
  function authMiddleware() {
@@ -11731,9 +12048,9 @@ function createConsoleRouter() {
11731
12048
  console2.use("/api/*", authMiddleware());
11732
12049
  console2.use("/api/*", builtinOnlyReadOnlyMiddleware());
11733
12050
  const serveIndex = (c) => {
11734
- const indexFile = join14(STATIC_DIR, "index.html");
11735
- if (existsSync18(indexFile)) {
11736
- return c.html(readFileSync18(indexFile, "utf-8"));
12051
+ const indexFile = join19(STATIC_DIR, "index.html");
12052
+ if (existsSync20(indexFile)) {
12053
+ return c.html(readFileSync20(indexFile, "utf-8"));
11737
12054
  }
11738
12055
  return c.html("<h1>AgentMoss Policy Console</h1><p>Frontend not built. Use /console/api/* endpoints.</p>");
11739
12056
  };
@@ -11928,15 +12245,15 @@ function createConsoleRouter() {
11928
12245
  if (!/^[A-Za-z0-9_-]+$/.test(skillId)) {
11929
12246
  return c.json({ detail: "\u975E\u6CD5 skill id" }, 400);
11930
12247
  }
11931
- const userPath = join14(dirname12(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
12248
+ const userPath = join19(dirname13(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
11932
12249
  const builtinDir = resolveAssetDir(__dirname10, "skills");
11933
12250
  const candidates = [
11934
12251
  ...isRuntimeConfigDisabled() ? [] : [userPath],
11935
- builtinDir ? join14(builtinDir, `${skillId}.md`) : ""
12252
+ builtinDir ? join19(builtinDir, `${skillId}.md`) : ""
11936
12253
  ].filter(Boolean);
11937
12254
  for (const p of candidates) {
11938
- if (existsSync18(p)) {
11939
- return c.json({ skill_id: skillId, path: p, content: readFileSync18(p, "utf-8") });
12255
+ if (existsSync20(p)) {
12256
+ return c.json({ skill_id: skillId, path: p, content: readFileSync20(p, "utf-8") });
11940
12257
  }
11941
12258
  }
11942
12259
  return c.json({ detail: `skill ${skillId} \u4E0D\u5B58\u5728` }, 404);
@@ -12033,7 +12350,7 @@ function createConsoleRouter() {
12033
12350
  const defaults = generateSourceDefaults();
12034
12351
  try {
12035
12352
  const p = getRuntimeConfigPath();
12036
- if (existsSync18(p)) unlinkSync4(p);
12353
+ if (existsSync20(p)) unlinkSync4(p);
12037
12354
  } catch {
12038
12355
  }
12039
12356
  return c.json(withL22View(defaults));
@@ -12090,7 +12407,7 @@ function logDeploymentSelfCheck() {
12090
12407
  console.log("\u5C42\u7EA7\u5F00\u5173: " + layerKeys.map((k) => `${k}=${isLayerEnabled(k) ? "on" : "OFF"}`).join(" "));
12091
12408
  if (!builtinOnly) {
12092
12409
  try {
12093
- const dirs = rulesDirs().filter((d) => existsSync19(d));
12410
+ const dirs = rulesDirs().filter((d) => existsSync21(d));
12094
12411
  if (dirs.length > 0) {
12095
12412
  console.log(`\u89C4\u5219\u76EE\u5F55: ${dirs.join(", ")}\uFF08\u53EA\u8BFB\u5176\u4E2D ${OVERRIDE_FILENAME} / ${EXTRA_FILENAME}\uFF09`);
12096
12413
  }
@@ -12128,10 +12445,134 @@ function logDeploymentSelfCheck() {
12128
12445
  console.error(`\u90E8\u7F72\u81EA\u68C0\u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u670D\u52A1\uFF09: ${exc instanceof Error ? exc.message : String(exc)}`);
12129
12446
  }
12130
12447
  }
12448
+ var PooledLimiter = class {
12449
+ counts = /* @__PURE__ */ new Map();
12450
+ tryAcquire(key, limit) {
12451
+ if (limit <= 0) return true;
12452
+ const cur = this.counts.get(key) || 0;
12453
+ if (cur >= limit) return false;
12454
+ this.counts.set(key, cur + 1);
12455
+ return true;
12456
+ }
12457
+ release(key) {
12458
+ const cur = this.counts.get(key) || 0;
12459
+ if (cur <= 1) this.counts.delete(key);
12460
+ else this.counts.set(key, cur - 1);
12461
+ }
12462
+ };
12463
+ var RateLimiter = class {
12464
+ constructor(qps, burst) {
12465
+ this.qps = qps;
12466
+ this.burst = burst;
12467
+ }
12468
+ qps;
12469
+ burst;
12470
+ buckets = /* @__PURE__ */ new Map();
12471
+ get enabled() {
12472
+ return this.qps > 0;
12473
+ }
12474
+ allow(key) {
12475
+ if (!this.enabled) return true;
12476
+ const burst = this.burst ?? Math.max(this.qps, 1) * 2;
12477
+ const now = Date.now() / 1e3;
12478
+ const [tokens, last] = this.buckets.get(key) ?? [burst, now];
12479
+ const next = Math.min(burst, tokens + (now - last) * this.qps);
12480
+ if (next < 1) {
12481
+ this.buckets.set(key, [next, now]);
12482
+ return false;
12483
+ }
12484
+ this.buckets.set(key, [next - 1, now]);
12485
+ if (this.buckets.size > 1e4) this.buckets.clear();
12486
+ return true;
12487
+ }
12488
+ };
12131
12489
  function createApp() {
12490
+ enforceRulesContract();
12491
+ hardenExistingPaths();
12132
12492
  const app = new Hono2();
12133
- app.use("*", cors({ origin: "*", allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], allowHeaders: ["*"] }));
12493
+ app.use("*", cors({
12494
+ origin: (o) => allowedOrigins().includes(o) ? o : null,
12495
+ allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
12496
+ allowHeaders: ["*"]
12497
+ }));
12134
12498
  app.use("*", logger());
12499
+ const pools = new PooledLimiter();
12500
+ const limiter = new RateLimiter(rateLimitQps());
12501
+ app.use("*", async (c, next) => {
12502
+ const path6 = new URL(c.req.url).pathname;
12503
+ const method = c.req.method.toUpperCase();
12504
+ if (method === "OPTIONS") return next();
12505
+ const maxBody = maxBodyBytes();
12506
+ const cl = c.req.header("content-length");
12507
+ if (cl && maxBody > 0) {
12508
+ const n = Number.parseInt(cl, 10);
12509
+ if (Number.isFinite(n) && n > maxBody) {
12510
+ return c.json({ detail: `\u8BF7\u6C42\u4F53\u8D85\u8FC7\u4E0A\u9650 ${maxBody} \u5B57\u8282` }, 413);
12511
+ }
12512
+ }
12513
+ if (originCheckEnabled()) {
12514
+ const origin = c.req.header("origin") || "";
12515
+ if (origin && !originAllowed(origin, c.req.header("host") || "")) {
12516
+ console.warn(`[AgentMoss] \u5DF2\u62E6\u622A\u8DE8\u6E90\u8BF7\u6C42: origin=${origin} path=${path6}`);
12517
+ return c.json({ detail: `\u8DE8\u6E90\u6765\u6E90\u672A\u88AB\u5141\u8BB8: ${origin}` }, 403);
12518
+ }
12519
+ }
12520
+ const [okAuth, principal] = checkRequestAuth(c.req.raw.headers, path6);
12521
+ const isPublicPage = !path6.startsWith("/console/api") && (path6 === "/console" || path6.startsWith("/console/"));
12522
+ if (!okAuth && !isPublicPage) {
12523
+ return c.json({ detail: "Unauthorized: invalid or missing Bearer token" }, 401);
12524
+ }
12525
+ if (limiter.enabled && path6.startsWith("/api/")) {
12526
+ const key = principal ? "p:" + principalHash(principal) : "c:" + (c.req.header("x-forwarded-for") || "local");
12527
+ if (!limiter.allow(key)) {
12528
+ return c.json({ detail: "\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" }, 429);
12529
+ }
12530
+ }
12531
+ const ip = c.req.header("x-forwarded-for") || "local";
12532
+ const keys = [];
12533
+ const take = (key, limit) => {
12534
+ if (pools.tryAcquire(key, limit)) {
12535
+ keys.push(key);
12536
+ return true;
12537
+ }
12538
+ return false;
12539
+ };
12540
+ const busy = (detail, status = 503) => {
12541
+ c.header("Retry-After", "1");
12542
+ c.header("Connection", "close");
12543
+ return c.json({ detail }, status);
12544
+ };
12545
+ try {
12546
+ if (!principal && authEnabled()) {
12547
+ if (!take("unauth:total", unauthInflight())) {
12548
+ return busy("\u672A\u8BA4\u8BC1\u8BF7\u6C42\u5E76\u53D1\u8D85\u9650\uFF0C\u8BF7\u6301\u51ED\u8BC1\u8BBF\u95EE");
12549
+ }
12550
+ if (!take(`unauth:ip:${ip}`, unauthPerIp())) {
12551
+ return busy("\u8BE5\u6765\u6E90\u7684\u672A\u8BA4\u8BC1\u8BF7\u6C42\u5E76\u53D1\u8D85\u9650");
12552
+ }
12553
+ } else {
12554
+ const bound = boundAgentOf(principal);
12555
+ if (bound && !take(`agent:${bound}`, agentInflight())) {
12556
+ return busy(`agent '${bound}' \u7684\u5E76\u53D1\u8D85\u9650\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5`, 429);
12557
+ }
12558
+ }
12559
+ const pool = poolOf(path6);
12560
+ if (!take(`pool:${pool}`, poolLimit(pool))) {
12561
+ const qkey = principal ? "p:" + principalHash(principal) : "c:local";
12562
+ const [qn, abusive] = noteQueueReject(qkey);
12563
+ if (abusive && queueAbuseAction() === "deny") {
12564
+ return busy("\u5E76\u53D1\u62D2\u7EDD\u8FC7\u4E8E\u9891\u7E41\uFF0C\u7591\u4F3C\u6076\u610F\u8BF7\u6C42", 429);
12565
+ }
12566
+ if (abusive) {
12567
+ console.warn(`[AgentMoss] \u26A0\uFE0F \u7591\u4F3C\u6076\u610F\u5E76\u53D1\u5360\u7528: key=${qkey} \u7A97\u53E3\u5185\u88AB\u62D2 ${qn} \u6B21`);
12568
+ }
12569
+ return busy("\u670D\u52A1\u7E41\u5FD9\uFF08\u5E76\u53D1\u5DF2\u6EE1\uFF09\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5");
12570
+ }
12571
+ return await next();
12572
+ } finally {
12573
+ while (keys.length) pools.release(keys.pop());
12574
+ }
12575
+ });
12135
12576
  app.get("/api/v1/health", healthRoute);
12136
12577
  app.post("/api/v1/analyze", analyzeRoute);
12137
12578
  app.post("/api/v1/review", reviewRoute);
@@ -12149,12 +12590,12 @@ function createApp() {
12149
12590
  }
12150
12591
  async function findFreePort(startPort, host = "127.0.0.1", scan = 100) {
12151
12592
  for (let port = startPort; port < startPort + scan; port++) {
12152
- const free = await new Promise((resolve11) => {
12593
+ const free = await new Promise((resolve12) => {
12153
12594
  const server = net.createServer();
12154
12595
  server.listen(port, host, () => {
12155
- server.close(() => resolve11(true));
12596
+ server.close(() => resolve12(true));
12156
12597
  });
12157
- server.on("error", () => resolve11(false));
12598
+ server.on("error", () => resolve12(false));
12158
12599
  });
12159
12600
  if (free) {
12160
12601
  if (port !== startPort) {
@@ -12197,6 +12638,7 @@ async function warmUpEngine() {
12197
12638
  }
12198
12639
  }
12199
12640
  async function startServer(host = "127.0.0.1", port = 9090) {
12641
+ failFastOnAuthError();
12200
12642
  const actualPort = await findFreePort(port, host);
12201
12643
  const app = createApp();
12202
12644
  return {
@@ -12206,7 +12648,12 @@ async function startServer(host = "127.0.0.1", port = 9090) {
12206
12648
  logDeploymentSelfCheck();
12207
12649
  await warmUpEngine();
12208
12650
  const { serve: honoServe } = await import("@hono/node-server");
12209
- honoServe({ fetch: app.fetch, hostname: host, port: actualPort });
12651
+ const srv = honoServe({ fetch: app.fetch, hostname: host, port: actualPort });
12652
+ try {
12653
+ srv.maxConnections = maxConnections();
12654
+ srv.keepAliveTimeout = keepAliveSec() * 1e3;
12655
+ } catch {
12656
+ }
12210
12657
  }
12211
12658
  };
12212
12659
  }
@@ -12217,28 +12664,29 @@ async function startUnixSocketServer(socketPath = "/var/run/agent_moss/agent_mos
12217
12664
  socketPath,
12218
12665
  close: async () => {
12219
12666
  if (!server) return;
12220
- await new Promise((resolve11) => {
12221
- server.close(() => resolve11());
12667
+ await new Promise((resolve12) => {
12668
+ server.close(() => resolve12());
12222
12669
  });
12223
12670
  server = null;
12224
12671
  },
12225
12672
  serve: async () => {
12226
- const { mkdirSync: mkdirSync8, existsSync: existsSync20, unlinkSync: unlinkSync5 } = await import("fs");
12227
- const { dirname: dirname13 } = await import("path");
12228
- const dir = dirname13(socketPath);
12673
+ failFastOnAuthError();
12674
+ const { mkdirSync: mkdirSync8, existsSync: existsSync22, unlinkSync: unlinkSync5 } = await import("fs");
12675
+ const { dirname: dirname14 } = await import("path");
12676
+ const dir = dirname14(socketPath);
12229
12677
  if (dir) {
12230
12678
  try {
12231
12679
  mkdirSync8(dir, { recursive: true });
12232
12680
  } catch {
12233
12681
  }
12234
12682
  }
12235
- if (existsSync20(socketPath)) {
12683
+ if (existsSync22(socketPath)) {
12236
12684
  const { connect } = await import("net");
12237
- const inUse = await new Promise((resolve11) => {
12685
+ const inUse = await new Promise((resolve12) => {
12238
12686
  const probe = connect(socketPath);
12239
12687
  const done = (v) => {
12240
12688
  probe.destroy();
12241
- resolve11(v);
12689
+ resolve12(v);
12242
12690
  };
12243
12691
  probe.once("connect", () => done(true));
12244
12692
  probe.once("error", () => done(false));
@@ -12261,10 +12709,22 @@ async function startUnixSocketServer(socketPath = "/var/run/agent_moss/agent_mos
12261
12709
  const { getRequestListener } = await import("@hono/node-server");
12262
12710
  const { createServer } = await import("http");
12263
12711
  server = createServer(getRequestListener(app.fetch));
12264
- await new Promise((resolve11, reject) => {
12712
+ try {
12713
+ server.maxConnections = maxConnections();
12714
+ server.keepAliveTimeout = keepAliveSec() * 1e3;
12715
+ } catch {
12716
+ }
12717
+ await new Promise((resolve12, reject) => {
12265
12718
  server.once("error", reject);
12266
- server.listen(socketPath, () => resolve11());
12719
+ server.listen(socketPath, () => resolve12());
12267
12720
  });
12721
+ try {
12722
+ const { chmodSync: chmodSync2 } = await import("fs");
12723
+ chmodSync2(socketPath, socketMode());
12724
+ console.log(`[AgentMoss] socket \u6743\u9650: 0${socketMode().toString(8)}\uFF08AGENT_MOSS_SOCKET_MODE\uFF09`);
12725
+ } catch (e) {
12726
+ console.warn(`[AgentMoss] \u8BBE\u7F6E socket \u6743\u9650\u5931\u8D25: ${String(e)}`);
12727
+ }
12268
12728
  console.log(`AgentMoss HTTP server on unix socket: ${socketPath}`);
12269
12729
  }
12270
12730
  };