@kenhkl/agent-moss 0.15.5 → 0.16.3

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.
@@ -1,13 +1,5 @@
1
1
  var __defProp = Object.defineProperty;
2
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
2
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
- var __hasOwnProp = Object.prototype.hasOwnProperty;
5
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
6
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
7
- }) : x)(function(x) {
8
- if (typeof require !== "undefined") return require.apply(this, arguments);
9
- throw Error('Dynamic require of "' + x + '" is not supported');
10
- });
11
3
  var __esm = (fn, res) => function __init() {
12
4
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
13
5
  };
@@ -15,15 +7,6 @@ var __export = (target, all) => {
15
7
  for (var name in all)
16
8
  __defProp(target, name, { get: all[name], enumerable: true });
17
9
  };
18
- var __copyProps = (to, from, except, desc) => {
19
- if (from && typeof from === "object" || typeof from === "function") {
20
- for (let key of __getOwnPropNames(from))
21
- if (!__hasOwnProp.call(to, key) && key !== except)
22
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
- }
24
- return to;
25
- };
26
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
10
 
28
11
  // node_modules/tsup/assets/esm_shims.js
29
12
  import path from "path";
@@ -122,278 +105,31 @@ var init_paths = __esm({
122
105
  }
123
106
  });
124
107
 
125
- // src/config.ts
126
- var config_exports = {};
127
- __export(config_exports, {
128
- detectProvider: () => detectProvider,
129
- getCustomRules: () => getCustomRules,
130
- getLLMConfig: () => getLLMConfig,
131
- getLLMFailMode: () => getLLMFailMode,
132
- getProviderHeaders: () => getProviderHeaders,
133
- getRetryConfig: () => getRetryConfig,
134
- getTimeoutConfig: () => getTimeoutConfig,
135
- isBrainEnabled: () => isBrainEnabled,
136
- isFormalObserveMode: () => isFormalObserveMode,
137
- isHeuristicEnabled: () => isHeuristicEnabled,
138
- isLLMAvailable: () => isLLMAvailable,
139
- isLogicRulesEnabled: () => isLogicRulesEnabled,
140
- probeLLMConnection: () => probeLLMConnection,
141
- updateLLMConfig: () => updateLLMConfig
142
- });
143
- import fs from "fs";
144
- import path2 from "path";
145
- import { fileURLToPath as fileURLToPath2 } from "url";
146
- function loadConfigFile(filePath) {
147
- try {
148
- const raw = fs.readFileSync(filePath, "utf-8");
149
- return JSON.parse(raw);
150
- } catch {
151
- return null;
152
- }
153
- }
154
- function resolveConfigPath() {
155
- const envPath = process.env.AGENT_MOSS_CONFIG_PATH;
156
- if (envPath) return envPath;
157
- const userConfig = path2.join(getConfigDir(), "config.json");
158
- if (fs.existsSync(userConfig)) return userConfig;
159
- const defaultCandidates = [
160
- path2.join(__dirname, "config.json.example"),
161
- path2.join(__dirname, "..", "..", "agent_moss", "infra", "config.json.example")
162
- ];
163
- const defaultConfig = defaultCandidates.find((p) => fs.existsSync(p));
164
- if (defaultConfig) {
165
- const configDir = path2.dirname(userConfig);
166
- fs.mkdirSync(configDir, { recursive: true });
167
- const pkgConfig = loadConfigFile(defaultConfig);
168
- const safeConfig = pkgConfig ? { ...pkgConfig, llm: { ...pkgConfig.llm, api_key: "" } } : {};
169
- fs.writeFileSync(userConfig, JSON.stringify(safeConfig, null, 4), "utf-8");
170
- console.log(`[AgentMoss] \u5DF2\u521B\u5EFA\u7528\u6237\u914D\u7F6E\u6587\u4EF6: ${userConfig}`);
171
- console.log(`[AgentMoss] \u8BF7\u7F16\u8F91\u8BE5\u6587\u4EF6\u914D\u7F6E\u4F60\u7684 LLM API Key`);
172
- }
173
- return userConfig;
174
- }
175
- function loadLLMConfigFromFile(configPath) {
176
- const data = loadConfigFile(configPath);
177
- if (!data?.llm) return {};
178
- const llm = data.llm;
179
- return {
180
- provider: llm.provider || "",
181
- baseUrl: llm.base_url || "",
182
- apiKey: llm.api_key || "",
183
- model: llm.model || "",
184
- temperature: llm.temperature ?? 0.1,
185
- maxTokens: llm.max_tokens ?? 4096,
186
- timeout: llm.timeout ?? 40
187
- };
188
- }
189
- function loadTimeoutRetryFromFile(configPath) {
190
- const data = loadConfigFile(configPath);
191
- const t = data?.timeout ?? {};
192
- const r = data?.retry ?? {};
193
- return {
194
- timeout: {
195
- totalTimeout: Number(t.total_timeout ?? 90),
196
- prompt1Timeout: Number(t.prompt1_timeout ?? 40),
197
- prompt2Timeout: Number(t.prompt2_timeout ?? 20),
198
- stepInterval: Number(t.step_interval ?? 0)
199
- },
200
- retry: {
201
- maxRetries: Number(r.max_retries ?? 2),
202
- retryInterval: Number(r.retry_interval ?? 2)
203
- }
204
- };
205
- }
206
- function getTimeoutConfig() {
207
- return timeoutConfig;
208
- }
209
- function getRetryConfig() {
210
- return retryConfig;
211
- }
212
- function wildcardToRegExp(pattern) {
213
- return pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, ".*");
214
- }
215
- function loadCustomRules() {
216
- const rules = [];
217
- try {
218
- const raw = process.env.AGENT_MOSS_CUSTOM_RULES;
219
- if (raw) {
220
- const parsed = JSON.parse(raw);
221
- if (Array.isArray(parsed)) {
222
- for (const r of parsed) {
223
- if (r && r.pattern && r.action) rules.push({ pattern: r.pattern, action: r.action, severity: r.severity || "high" });
224
- }
225
- }
226
- }
227
- } catch {
228
- }
229
- for (const rulesPath of USER_RULES_PATHS) {
230
- try {
231
- if (!fs.existsSync(rulesPath)) continue;
232
- const data = JSON.parse(fs.readFileSync(rulesPath, "utf-8"));
233
- const actions = Array.isArray(data?.sensitive_actions) ? data.sensitive_actions : [];
234
- for (const r of actions) {
235
- const p = r?.pattern;
236
- if (!p) continue;
237
- rules.push({ pattern: wildcardToRegExp(p), action: "Deny", severity: "high" });
238
- }
239
- break;
240
- } catch {
241
- }
242
- }
243
- return rules;
244
- }
245
- function getLLMConfig() {
246
- return llmConfig;
247
- }
248
- function updateLLMConfig(partial) {
249
- llmConfig = { ...llmConfig, ...partial };
250
- }
251
- function getCustomRules() {
252
- return customRules;
253
- }
254
- function isLLMAvailable() {
255
- if (process.env.AGENT_MOSS_DISABLE_LLM === "1") return false;
256
- if (!layerEnabledOrDefault("L3_llm_analysis")) return false;
257
- return llmConfig.apiKey.length > 0;
108
+ // src/engine/shell-split.ts
109
+ function hasCommandSubstitution(text) {
110
+ return CMD_SUBSTITUTION.test(text);
258
111
  }
259
- async function probeLLMConnection(timeoutMs = 8e3) {
260
- if (!isLLMAvailable()) return false;
261
- const OpenAI3 = (await import("openai")).default;
262
- const config = getLLMConfig();
263
- const provider = detectProvider(config.baseUrl, config.provider);
264
- const extraHeaders = getProviderHeaders(provider);
265
- const client = new OpenAI3({
266
- baseURL: config.baseUrl,
267
- apiKey: config.apiKey,
268
- timeout: timeoutMs,
269
- maxRetries: 0,
270
- defaultHeaders: Object.keys(extraHeaders).length > 0 ? extraHeaders : void 0
271
- });
272
- try {
273
- const resp = await client.chat.completions.create({
274
- model: config.model,
275
- messages: [{ role: "user", content: "ping" }],
276
- max_tokens: 1
277
- });
278
- return !!(resp && resp.choices && resp.choices[0]);
279
- } catch {
280
- return false;
281
- }
282
- }
283
- function getLLMFailMode() {
284
- const val = (process.env.AGENT_MOSS_LLM_FAIL_MODE || "fail_open").toLowerCase();
285
- return val === "fail_closed" ? "fail_closed" : "fail_open";
286
- }
287
- function detectProvider(baseUrl, providerHint = "") {
288
- if (providerHint) {
289
- const hint = providerHint.toLowerCase();
290
- if (["claude", "anthropic"].includes(hint)) return "anthropic";
291
- if (["google", "gemini"].includes(hint)) return "gemini";
292
- if (["zai-coding-plan", "zhipu-coding-plan", "zhipuai-coding-plan"].includes(hint)) return "zai-coding-plan";
293
- if (["zai", "z-ai", "z.ai", "zai-cn", "zai-china", "zhipu", "glm-cn", "bigmodel"].includes(hint)) return "zhipu";
294
- if (["xai", "xai-grok"].includes(hint)) return "xai";
295
- if (["minimax", "minimax-openai"].includes(hint)) return "minimax";
296
- if (["minimax-anthropic"].includes(hint)) return "minimax-anthropic";
297
- if (["other", "custom", "local"].includes(hint)) return "openai-compatible";
298
- return hint;
299
- }
300
- const url = baseUrl.toLowerCase();
301
- if (url.includes("openrouter.ai")) return "openrouter";
302
- if (url.includes("api.openai.com")) return "openai";
303
- if (url.includes("anthropic.com") || url.includes("api.anthropic.com")) return "anthropic";
304
- if (url.includes("generativelanguage.googleapis.com") || url.includes("gemini")) return "gemini";
305
- if (url.includes("api.deepseek.com")) return "deepseek";
306
- if (url.includes("api.groq.com")) return "groq";
307
- if (url.includes("api.mistral.ai")) return "mistral";
308
- if (url.includes("api.together.xyz")) return "together";
309
- if (url.includes("api.x.ai")) return "xai";
310
- if (url.includes("api.minimaxi.com")) return "minimax";
311
- if (url.includes("api-ai.gitcode.com")) return "gitcode";
312
- if (url.includes("api.z.ai")) return "zai-coding-plan";
313
- if (url.includes("open.bigmodel.cn") || url.includes("bigmodel")) return "zhipu";
314
- if (url.includes(":11434")) return "ollama";
315
- return "openai-compatible";
316
- }
317
- function getProviderHeaders(provider) {
318
- return PROVIDER_HEADERS[provider] || {};
319
- }
320
- function isHeuristicEnabled() {
321
- if (process.env.AGENT_MOSS_DISABLE_HEURISTIC === "1") return false;
322
- return layerEnabledOrDefault("L1_heuristic");
323
- }
324
- function layerEnabledOrDefault(key) {
325
- try {
326
- const { isLayerEnabled: isLayerEnabled2 } = __require("../infra/runtime-config");
327
- return isLayerEnabled2(key);
328
- } catch {
329
- return true;
330
- }
331
- }
332
- function isBrainEnabled() {
333
- return process.env.AGENT_MOSS_BRAIN_ENABLED === "1";
112
+ function splitCommandSegments(command) {
113
+ return command.split(CMD_SEPARATORS).map((s) => s.trim()).filter((s) => s.length > 0);
334
114
  }
335
- function isLogicRulesEnabled() {
336
- if (process.env.AGENT_MOSS_DISABLE_LOGIC_RULES === "1") return false;
337
- return layerEnabledOrDefault("L2_logic_rules");
338
- }
339
- function isFormalObserveMode() {
340
- return process.env.AGENT_MOSS_FORMAL_OBSERVE === "1";
115
+ function extractFirstCommand(command) {
116
+ const segments = splitCommandSegments(command);
117
+ return segments.length ? segments[0] : "";
341
118
  }
342
- var timeoutConfig, retryConfig, llmConfig, __configModuleDir, USER_RULES_PATHS, customRules, PROVIDER_HEADERS;
343
- var init_config = __esm({
344
- "src/config.ts"() {
119
+ var CMD_SEPARATORS, CMD_SUBSTITUTION;
120
+ var init_shell_split = __esm({
121
+ "src/engine/shell-split.ts"() {
345
122
  "use strict";
346
123
  init_esm_shims();
347
- init_paths();
348
- llmConfig = (() => {
349
- const configPath = resolveConfigPath();
350
- const fileConfig = fs.existsSync(configPath) ? loadLLMConfigFromFile(configPath) : {};
351
- const tr = loadTimeoutRetryFromFile(configPath);
352
- timeoutConfig = tr.timeout;
353
- retryConfig = tr.retry;
354
- return {
355
- provider: process.env.AGENT_MOSS_LLM_PROVIDER || fileConfig.provider || "openai",
356
- baseUrl: process.env.AGENT_MOSS_LLM_BASE_URL || fileConfig.baseUrl || "https://api.openai.com/v1",
357
- apiKey: process.env.AGENT_MOSS_LLM_API_KEY || process.env.OPENAI_API_KEY || fileConfig.apiKey || "",
358
- model: process.env.AGENT_MOSS_LLM_MODEL || fileConfig.model || "gpt-4o",
359
- temperature: parseFloat(process.env.AGENT_MOSS_LLM_TEMPERATURE || String(fileConfig.temperature ?? "0.1")),
360
- maxTokens: parseInt(process.env.AGENT_MOSS_LLM_MAX_TOKENS || String(fileConfig.maxTokens ?? "4096"), 10),
361
- // 优先用 env,否则用 timeout 块的 prompt1Timeout(对齐 py)
362
- timeout: parseInt(process.env.AGENT_MOSS_LLM_TIMEOUT || String(fileConfig.timeout ?? String(tr.timeout.prompt1Timeout)), 10)
363
- };
364
- })();
365
- __configModuleDir = (() => {
366
- try {
367
- return path2.dirname(fileURLToPath2(import.meta.url));
368
- } catch {
369
- return typeof __dirname !== "undefined" ? __dirname : process.cwd();
370
- }
371
- })();
372
- USER_RULES_PATHS = [
373
- path2.join(__configModuleDir, "rules", "user_rules.json"),
374
- // 源在 ts/src/rules(一般不存在)
375
- path2.join(__configModuleDir, "..", "rules", "user_rules.json"),
376
- // dist: dist/engine/../rules = dist/rules ✅
377
- path2.join(__configModuleDir, "..", "..", "agent_moss", "rules", "user_rules.json")
378
- // dev src: ts/src/../../agent_moss
379
- ];
380
- customRules = loadCustomRules();
381
- PROVIDER_HEADERS = {
382
- "openrouter": {
383
- "HTTP-Referer": "shturl.cc/LlVD5pyWViHjM",
384
- "X-OpenRouter-Title": "AgentMoss"
385
- },
386
- "xai": {
387
- "HTTP-Referer": "shturl.cc/LlVD5pyWViHjM"
388
- }
389
- };
124
+ CMD_SEPARATORS = /\|\||&&|[|;&\n]/;
125
+ CMD_SUBSTITUTION = /\$\(|`|</;
390
126
  }
391
127
  });
392
128
 
393
129
  // src/engine/inline-analyzer.ts
394
130
  function isLiteralTextCommand(command) {
395
- ECHO_CMD_RE.lastIndex = 0;
396
- if (!ECHO_CMD_RE.test(command)) return false;
131
+ const segments = splitCommandSegments(command);
132
+ if (segments.length === 0 || segments.some((s) => !ECHO_SEG_RE.test(s))) return false;
397
133
  if (command.includes(">")) {
398
134
  let d = command.replace(/<=>|>=|<=|<>|=>/g, " ");
399
135
  d = d.replace(/(?:2|&|1)?\s*>\s*\/dev\/null\b/g, "");
@@ -403,6 +139,9 @@ function isLiteralTextCommand(command) {
403
139
  return false;
404
140
  }
405
141
  }
142
+ if (/\|\s*\b(?:tee|tac|install)\b/i.test(command)) {
143
+ return false;
144
+ }
406
145
  return true;
407
146
  }
408
147
  function extractInlineCode(command) {
@@ -480,14 +219,15 @@ function analyzeInlineScript(command) {
480
219
  riskType: "file_access"
481
220
  };
482
221
  }
483
- var INLINE_CODE_RE, ECHO_STRING_RE, ECHO_CMD_RE, READ_WRITE_KEYWORDS, WRITE_EXEC_CHAIN_PATTERNS, SHELL_RC_FRAGMENTS, SENSITIVE_PATH_FRAGMENTS, REDIRECT_TO_SENSITIVE_RE, DIR_TRAVERSAL_KEYWORDS;
222
+ 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;
484
223
  var init_inline_analyzer = __esm({
485
224
  "src/engine/inline-analyzer.ts"() {
486
225
  "use strict";
487
226
  init_esm_shims();
227
+ init_shell_split();
488
228
  INLINE_CODE_RE = /(?:python|python3|perl|perl5|ruby|jruby|mruby|node|php|lua)\s+(?:-[a-zA-Z]*[cera][a-zA-Z]*\s+)"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
489
229
  ECHO_STRING_RE = /\b(?:echo|printf)\s+"([^"\\]*(?:\\.[^"\\]*)*)"|\b(?:echo|printf)\s+'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
490
- ECHO_CMD_RE = /(?:^|\s|;|&&|\|\|)(?:echo|printf)\s/gi;
230
+ ECHO_SEG_RE = /^\s*(?:echo|printf)(?:\s|$)/i;
491
231
  READ_WRITE_KEYWORDS = [
492
232
  // 函数调用形态(关键词后跟 '(')
493
233
  "open(",
@@ -536,9 +276,34 @@ var init_inline_analyzer = __esm({
536
276
  "passthru",
537
277
  "shell_exec",
538
278
  "proc_open",
539
- // Perl-Ruby
279
+ // Perl/Ruby
540
280
  "<stdin>",
541
- "open my"
281
+ "open my",
282
+ // Node/JS(2026-09 补,与 Python _READ_WRITE_KEYWORDS 同步):历史缺口 ——
283
+ // 识别表里一条 Node API 都没有,于是 node -e "require('fs').readFileSync('/etc/shadow')"
284
+ // 判为纯文本字面量 → 放行(真漏报)。
285
+ "readfilesync(",
286
+ "writefilesync(",
287
+ "appendfilesync(",
288
+ "unlinksync(",
289
+ "rmsync(",
290
+ "mkdirsync(",
291
+ "execsync(",
292
+ "spawnsync(",
293
+ "execfile",
294
+ "createreadstream",
295
+ "createwritestream",
296
+ "fs.read",
297
+ "fs.write",
298
+ "fs.promises",
299
+ "require('fs')",
300
+ 'require("fs")',
301
+ "require('node:fs')",
302
+ 'require("node:fs")',
303
+ "require('child_process')",
304
+ 'require("child_process")',
305
+ "require('https')",
306
+ 'require("https")'
542
307
  ];
543
308
  WRITE_EXEC_CHAIN_PATTERNS = [
544
309
  // echo/cat/printf "..." > file && bash/sh/python file
@@ -593,15 +358,45 @@ var init_inline_analyzer = __esm({
593
358
  }
594
359
  });
595
360
 
596
- // src/infra/rules-loader.ts
597
- import { readFileSync as readFileSync2, existsSync } from "fs";
598
- import { resolve, dirname, join as join2 } from "path";
599
- import { fileURLToPath as fileURLToPath3 } from "url";
600
- import { homedir as homedir2 } from "os";
361
+ // src/infra/runtime-config-bridge.ts
362
+ function registerRuntimeConfigBridge(bridge) {
363
+ runtimeConfigBridge.current = bridge;
364
+ }
365
+ var runtimeConfigBridge;
366
+ var init_runtime_config_bridge = __esm({
367
+ "src/infra/runtime-config-bridge.ts"() {
368
+ "use strict";
369
+ init_esm_shims();
370
+ runtimeConfigBridge = { current: null };
371
+ }
372
+ });
373
+
374
+ // src/infra/rules-loader.ts
375
+ import { readFileSync as readFileSync2, existsSync } from "fs";
376
+ import { resolve, dirname } from "path";
377
+ import { fileURLToPath as fileURLToPath2 } from "url";
601
378
  function dataRootDir() {
602
- return process.env.AGENT_MOSS_DATA_DIR || join2(homedir2(), ".config", "agentmoss");
379
+ return getDataRoot();
380
+ }
381
+ function overrideRulesDir() {
382
+ return resolve(dataRootDir(), "rules");
383
+ }
384
+ function warnOverride(name, path5, data) {
385
+ if (warnedOverride.has(path5)) return;
386
+ warnedOverride.add(path5);
387
+ console.warn(
388
+ `[AgentMoss] \u89C4\u5219\u6587\u4EF6\u88AB\u7528\u6237\u6570\u636E\u76EE\u5F55\u8986\u76D6: ${path5}\uFF08\u5185\u7F6E\u9ED8\u8BA4\u88AB\u5FFD\u7565\uFF09\u3002\u8BE5\u6587\u4EF6\u7531\u90E8\u7F72\u4FA7\u81EA\u884C\u7EF4\u62A4 \u2014\u2014 **\u5185\u7F6E\u89C4\u5219\u66F4\u65B0\u4E0D\u4F1A\u53CD\u6620\u5230\u5B83\u4E0A\u9762**\uFF1B\u5220\u9664\u8BE5\u6587\u4EF6\u5373\u6062\u590D\u5185\u7F6E\u89C4\u5219\u3002\u82E5\u53EA\u60F3\u9010\u6761\u542F\u505C/\u589E\u5220\uFF0C\u8BF7\u6539\u7528 runtime config\uFF08Console\uFF09\u3002`
389
+ );
390
+ const missing = (EXPECTED_KEYS[name] || []).filter((k) => !(k in data));
391
+ if (missing.length > 0) {
392
+ console.warn(
393
+ `\u26A0\uFE0F [AgentMoss] \u8986\u76D6\u6587\u4EF6 ${path5} \u7F3A\u5C11\u9876\u5C42\u952E [${missing.join(", ")}] \u2014\u2014 \u5BF9\u5E94\u7C7B\u522B\u4F1A**\u4E00\u6761\u89C4\u5219\u90FD\u4E0D\u8DD1**\uFF08\u9759\u9ED8\u5931\u6548\uFF09\u3002\u82E5\u53EA\u60F3\u6539\u5176\u4E2D\u4E00\u7C7B\uFF0C\u8BF7\u5148\u62F7\u8D1D\u5B8C\u6574\u7684\u5185\u7F6E\u6587\u4EF6\u518D\u6539\u3002`
394
+ );
395
+ }
603
396
  }
604
397
  function findRulesFile(name) {
398
+ const override = resolve(overrideRulesDir(), name);
399
+ if (existsSync(override)) return override;
605
400
  for (const dir of RULES_DIR_CANDS) {
606
401
  const p = resolve(dir, name);
607
402
  if (existsSync(p)) return p;
@@ -611,11 +406,16 @@ function findRulesFile(name) {
611
406
  function loadJson(name) {
612
407
  const p = findRulesFile(name);
613
408
  if (!p) return {};
409
+ const isOverride = p === resolve(overrideRulesDir(), name);
410
+ let data;
614
411
  try {
615
- return JSON.parse(readFileSync2(p, "utf-8")) || {};
412
+ data = JSON.parse(readFileSync2(p, "utf-8")) || {};
616
413
  } catch {
414
+ if (isOverride) warnOverride(name, p, {});
617
415
  return {};
618
416
  }
417
+ if (isOverride) warnOverride(name, p, data);
418
+ return data;
619
419
  }
620
420
  function loadL1() {
621
421
  return loadJson("l1_rules.json");
@@ -630,8 +430,8 @@ function loadMC() {
630
430
  const mc = loadJson("mc_rules.json");
631
431
  const rules = mc.rules || [];
632
432
  try {
633
- const rt = (init_runtime_config(), __toCommonJS(runtime_config_exports));
634
- if (rules.length > 0 && typeof rt.hasRuntimeConfig === "function" && rt.hasRuntimeConfig()) {
433
+ const rt = runtimeConfigBridge.current;
434
+ if (rules.length > 0 && rt?.hasRuntimeConfig()) {
635
435
  const ltl = rt.loadRuntimeConfig()?.L2_2?.ltl_rules || {};
636
436
  const rtRules = ltl.rules || [];
637
437
  const enabledById = {};
@@ -649,16 +449,26 @@ function loadMC() {
649
449
  function loadToolRegistry() {
650
450
  return loadJson("tool_registry.json");
651
451
  }
652
- var __filename2, __dirname2, RULES_DIR_CANDS;
452
+ var __filename2, __dirname2, EXPECTED_KEYS, warnedOverride, RULES_DIR_CANDS;
653
453
  var init_rules_loader = __esm({
654
454
  "src/infra/rules-loader.ts"() {
655
455
  "use strict";
656
456
  init_esm_shims();
657
- __filename2 = fileURLToPath3(import.meta.url);
457
+ init_paths();
458
+ init_runtime_config_bridge();
459
+ __filename2 = fileURLToPath2(import.meta.url);
658
460
  __dirname2 = dirname(__filename2);
461
+ EXPECTED_KEYS = {
462
+ "l1_rules.json": ["dangerous_commands", "injection_detection", "sensitive_path_access"],
463
+ "l2_rules.json": ["intent_consistency", "password_modify_consent", "user_deletion_consent"],
464
+ "l3_rules.json": [],
465
+ "mc_rules.json": ["rules"],
466
+ "tool_registry.json": ["tools"]
467
+ };
468
+ warnedOverride = /* @__PURE__ */ new Set();
659
469
  RULES_DIR_CANDS = (() => {
660
470
  const cands = [
661
- // 用户数据目录覆盖优先(与 Python rules_loader._load_json 的
471
+ // 用户数据目录覆盖优先(与 Python rules_loader.resolve_rules_file 的
662
472
  // <data_root>/rules/<name> 优先逻辑对齐)。用户安装 pip/npm 包后把自定义
663
473
  // mc_rules.json 放到 <data_root>/rules/ 即可覆盖内置默认,无需改源码。
664
474
  resolve(dataRootDir(), "rules"),
@@ -677,21 +487,455 @@ var init_rules_loader = __esm({
677
487
  }
678
488
  });
679
489
 
490
+ // src/engine/platform-traits.ts
491
+ function getPlatformTraits(osType) {
492
+ const accepted = acceptedOsValues(osType);
493
+ if (accepted.includes("windows")) return WINDOWS_TRAITS;
494
+ if (accepted.includes("macos")) return MACOS_TRAITS;
495
+ if (accepted.includes("harmony")) return HARMONY_TRAITS;
496
+ return LINUX_TRAITS;
497
+ }
498
+ var LINUX_TRAITS, WINDOWS_TRAITS, MACOS_TRAITS, HARMONY_TRAITS;
499
+ var init_platform_traits = __esm({
500
+ "src/engine/platform-traits.ts"() {
501
+ "use strict";
502
+ init_esm_shims();
503
+ init_logic_rules();
504
+ LINUX_TRAITS = {
505
+ writeKeywords: [
506
+ "write",
507
+ "\u5199\u5165",
508
+ "save",
509
+ "\u4FDD\u5B58",
510
+ "modify",
511
+ "\u4FEE\u6539",
512
+ "update",
513
+ "\u66F4\u65B0",
514
+ "delete",
515
+ "\u5220\u9664",
516
+ "remove",
517
+ "\u79FB\u9664",
518
+ "create",
519
+ "\u521B\u5EFA",
520
+ "append",
521
+ "\u8FFD\u52A0",
522
+ "truncate",
523
+ "\u622A\u65AD",
524
+ "overwrite",
525
+ "\u8986\u76D6",
526
+ "replace",
527
+ "\u66FF\u6362",
528
+ "edit"
529
+ ],
530
+ readKeywords: [
531
+ "read",
532
+ "\u8BFB\u53D6",
533
+ "cat",
534
+ "head",
535
+ "tail",
536
+ "less",
537
+ "more",
538
+ "view",
539
+ "\u67E5\u770B",
540
+ "open",
541
+ "\u6253\u5F00",
542
+ "load",
543
+ "\u52A0\u8F7D",
544
+ "grep",
545
+ "search",
546
+ "\u641C\u7D22",
547
+ "find",
548
+ "\u67E5\u627E"
549
+ ],
550
+ writeCommandRes: [
551
+ /\brm\b/,
552
+ /\bunlink\b/,
553
+ /\brmdir\b/,
554
+ /\bshred\b/,
555
+ /\bcp\b/,
556
+ /\bmv\b/,
557
+ /\binstall\b/,
558
+ /\btee\b/,
559
+ /\bdd\b/,
560
+ /\bmkfs\b/,
561
+ /\b(fdisk|parted|cfdisk|sfdisk)\b/,
562
+ /\bchmod\b/,
563
+ /\bchown\b/,
564
+ /\btruncate\b/
565
+ ],
566
+ readOnlyCommands: /* @__PURE__ */ new Set([
567
+ "cat",
568
+ "head",
569
+ "tail",
570
+ "less",
571
+ "more",
572
+ "grep",
573
+ "find",
574
+ "awk",
575
+ "sed",
576
+ "sort",
577
+ "uniq",
578
+ "wc",
579
+ "cut",
580
+ "strings",
581
+ "od",
582
+ "xxd",
583
+ "hexdump",
584
+ "tr",
585
+ "file",
586
+ "stat",
587
+ "du",
588
+ "df",
589
+ "ls",
590
+ "dir",
591
+ "tree",
592
+ "nl",
593
+ "tac",
594
+ "rev",
595
+ "echo",
596
+ "printf",
597
+ "lsblk",
598
+ "blockdev",
599
+ "smartctl",
600
+ "udevadm",
601
+ "dmidecode",
602
+ "lscpu",
603
+ "lspci",
604
+ "lsusb",
605
+ "lsmem",
606
+ "lsns",
607
+ "lsof",
608
+ "hwinfo",
609
+ "inxi",
610
+ "fdisk",
611
+ "parted",
612
+ "dmesg",
613
+ "journalctl",
614
+ "systemctl",
615
+ "hostnamectl",
616
+ "localectl",
617
+ "timedatectl",
618
+ "ps",
619
+ "top",
620
+ "free",
621
+ "vmstat",
622
+ "iostat",
623
+ "mpstat",
624
+ "pidof",
625
+ "pgrep",
626
+ "ip",
627
+ "ifconfig",
628
+ "route",
629
+ "ss",
630
+ "netstat",
631
+ "arp",
632
+ "ethtool",
633
+ "nmcli",
634
+ "uname",
635
+ "arch",
636
+ "nproc",
637
+ "getconf",
638
+ "getent",
639
+ "id",
640
+ "whoami",
641
+ "who",
642
+ "w",
643
+ "last",
644
+ "uptime",
645
+ "lsmod",
646
+ "modinfo",
647
+ "sysctl"
648
+ ]),
649
+ fullySafeCommands: [
650
+ /^ls(?:\s|$)/i,
651
+ /^dir(?:\s|$)/i,
652
+ /^pwd(?:\s|$)/i,
653
+ /^which\s+/i,
654
+ /^whereis\s+/i,
655
+ /^realpath\s+/i,
656
+ /^basename\s+/i,
657
+ /^dirname\s+/i,
658
+ /^file\s+/i,
659
+ /^stat\s+/i,
660
+ /^du(?:\s|$)/i,
661
+ /^echo\s+/i,
662
+ /^printf\s+/i,
663
+ /^type\s+/i,
664
+ /^command\s+/i,
665
+ // 系统信息查询(只读,不涉及文件/网络/权限)
666
+ /^whoami(?:\s|$)/i,
667
+ /^id(?:\s|$)/i,
668
+ /^hostname(?:\s|$)/i,
669
+ /^uname(?:\s|$)/i,
670
+ /^date(?:\s|$)/i,
671
+ /^env(?:\s|$)/i,
672
+ /^printenv(?:\s|$)/i,
673
+ /^tty(?:\s|$)/i,
674
+ /^arch(?:\s|$)/i,
675
+ /^uptime(?:\s|$)/i,
676
+ /^groups(?:\s|$)/i,
677
+ /^logname(?:\s|$)/i,
678
+ // 系统资源/硬件信息(只读,不读文件内容、无状态变更选项)
679
+ // 与上面 uptime/uname/id 同性质;缺了它们时开 L3 的部署每查一次磁盘/内存
680
+ // 都要走 LLM(实测 df -h 从 5ms 变成十几秒)。
681
+ /^df(?:\s|$)/i,
682
+ /^free(?:\s|$)/i,
683
+ /^vmstat(?:\s|$)/i,
684
+ /^nproc(?:\s|$)/i,
685
+ /^lscpu(?:\s|$)/i,
686
+ /^lsblk(?:\s|$)/i,
687
+ /^lspci(?:\s|$)/i,
688
+ /^lsusb(?:\s|$)/i,
689
+ /^lsmod(?:\s|$)/i
690
+ ],
691
+ readonlySensitiveCommands: [
692
+ /^cat\s+/i,
693
+ /^head(?:\s|$)/i,
694
+ /^tail(?:\s|$)/i,
695
+ /^less(?:\s|$)/i,
696
+ /^more(?:\s|$)/i,
697
+ /^wc(?:\s|$)/i,
698
+ /^grep(?:\s|$)/i,
699
+ /^find\s+/i,
700
+ /^ag(?:\s|$)/i,
701
+ /^rg(?:\s|$)/i,
702
+ /^awk\s+/i,
703
+ /^sed\s+/i,
704
+ /^cut\s+/i,
705
+ /^sort(?:\s|$)/i,
706
+ /^uniq(?:\s|$)/i,
707
+ // 进程/网络/打开文件信息:只读,但会暴露进程命令行、连接对端、打开的文件
708
+ // 路径,故只跳 L3、保留 L2(L1 敏感路径检测照常生效)。
709
+ /^ps(?:\s|$)/i,
710
+ /^ss(?:\s|$)/i,
711
+ /^netstat(?:\s|$)/i,
712
+ /^lsof(?:\s|$)/i,
713
+ // 只读子命令级白名单:匹配的是「第一段命令」而非首词(extractFirstCommand
714
+ // 按 | && || 切分),所以能精确到子命令。systemctl stop/disable 等不在此列。
715
+ /^systemctl\s+(?:status|is-active|is-enabled|is-failed|list-units|list-unit-files|list-dependencies|show|cat)\b/i,
716
+ /^service\s+\S+\s+status\b/i,
717
+ // journalctl 整族是只读的,但 --vacuum-*/--rotate/--flush/--sync 会删改日志,
718
+ // 用否定前瞻挡掉后再放行。注意前瞻里必须用 .* 扫描**整段**:只写 (?!\s+--...)
719
+ // 的话只看紧跟 journalctl 的第一个参数,
720
+ // `journalctl -u sshd --vacuum-size=100M` 就漏过去了。
721
+ /^journalctl\b(?!.*--(?:vacuum-size|vacuum-time|vacuum-files|rotate|flush|sync)\b)/i
722
+ ]
723
+ };
724
+ WINDOWS_TRAITS = {
725
+ writeKeywords: [
726
+ "write",
727
+ "\u5199\u5165",
728
+ "save",
729
+ "\u4FDD\u5B58",
730
+ "modify",
731
+ "\u4FEE\u6539",
732
+ "update",
733
+ "\u66F4\u65B0",
734
+ "delete",
735
+ "\u5220\u9664",
736
+ "remove",
737
+ "\u79FB\u9664",
738
+ "create",
739
+ "\u521B\u5EFA",
740
+ "append",
741
+ "\u8FFD\u52A0",
742
+ "overwrite",
743
+ "\u8986\u76D6",
744
+ "replace",
745
+ "\u66FF\u6362",
746
+ "set",
747
+ "\u8BBE\u7F6E"
748
+ // 注意:这里**不能放 `reg` / `注册表`**。关键词是子串包含匹配,放进去等于
749
+ // 「只要命令里出现 reg 字样就算写操作」——`reg query`(读)会被判成写,
750
+ // 进而误拦对 deny_write 路径的只读查询。注册表的写判定交给下面的
751
+ // writeCommandRes(只认写子命令)。两端语义必须一致。
752
+ ],
753
+ readKeywords: [
754
+ "read",
755
+ "\u8BFB\u53D6",
756
+ "type",
757
+ "findstr",
758
+ "dir",
759
+ "view",
760
+ "\u67E5\u770B",
761
+ "open",
762
+ "\u6253\u5F00",
763
+ "load",
764
+ "\u52A0\u8F7D",
765
+ "search",
766
+ "\u641C\u7D22",
767
+ "find",
768
+ "\u67E5\u627E",
769
+ "get",
770
+ "\u83B7\u53D6"
771
+ ],
772
+ // Windows 写/删命令:del/copy/move/xcopy/replace/rmdir/icacls/reg delete 等
773
+ writeCommandRes: [
774
+ /\b(del|erase)\b/,
775
+ /\brmdir\b/,
776
+ /\b(rd)\b/,
777
+ /\bcopy\b/,
778
+ /\b(xcopy|robocopy)\b/,
779
+ /\b(move|movefile)\b/,
780
+ /\breplace\b/,
781
+ /\bicacls\b/,
782
+ /\battrib\b/,
783
+ // 注册表:**只认写子命令**(对齐 Python WindowsProfile.write_command_res)。
784
+ // 此前是 /\breg\b/,把 reg query(读)也判成写 → 误拦对 deny_write 路径的只读查询。
785
+ // 写子命令 add/delete/copy/save/restore/import/load/unload;
786
+ // 读子命令 query/compare/export/flags/help 不在此列。
787
+ /\breg(?:\.exe)?\s+(?:add|delete|copy|save|restore|import|load|unload)\b/,
788
+ /\bformat\b/,
789
+ /\bdiskpart\b/,
790
+ /\bmkdir\b/,
791
+ /\bmd\b/,
792
+ /\bsetx\b/,
793
+ /\bwmic\b/
794
+ ],
795
+ // Windows 只读命令(重定向写判定用):type/findstr/more/tree/dir 等
796
+ readOnlyCommands: /* @__PURE__ */ new Set([
797
+ "type",
798
+ "findstr",
799
+ "more",
800
+ "tree",
801
+ "dir",
802
+ "where",
803
+ "ver",
804
+ "cls",
805
+ "echo",
806
+ "help",
807
+ "fc",
808
+ "comp",
809
+ "find",
810
+ "sort",
811
+ "powershell",
812
+ "cmd",
813
+ "netstat",
814
+ "ipconfig",
815
+ "tasklist",
816
+ "systeminfo",
817
+ "driverquery",
818
+ "reg",
819
+ "wmic",
820
+ "whoami",
821
+ "hostname",
822
+ "getmac",
823
+ "query",
824
+ "w32tm",
825
+ "diskpart"
826
+ ]),
827
+ // Windows 完全安全命令(对齐 WindowsProfile.fully_safe_commands)
828
+ fullySafeCommands: [
829
+ /^dir(?:\s|$)/,
830
+ /^where\s+/,
831
+ /^echo\s+/,
832
+ /^cd(?:\s|$)/,
833
+ /^tree(?:\s|$)/,
834
+ /^help(?:\s|$)/,
835
+ /^cls(?:\s|$)/,
836
+ /^ver(?:\s|$)/,
837
+ // 系统信息查询(只读,不读文件内容、无状态变更选项),与 Linux 侧 df/free 对齐
838
+ /^whoami(?:\s|$)/,
839
+ /^hostname(?:\s|$)/,
840
+ /^systeminfo(?:\s|$)/,
841
+ /^tasklist(?:\s|$)/,
842
+ /^getmac(?:\s|$)/,
843
+ /^driverquery(?:\s|$)/,
844
+ /^netstat(?:\s|$)/
845
+ ],
846
+ // Windows 安全但可能访问敏感路径的命令(对齐 WindowsProfile.readonly_sensitive_commands)
847
+ readonlySensitiveCommands: [
848
+ /^type\s+/,
849
+ /^findstr\s+/,
850
+ // 只读子命令级白名单(匹配「第一段命令」而非首词);
851
+ // sc create/delete/start/stop、reg add/delete/set 均不在此列。
852
+ /^sc\s+query\b/,
853
+ /^reg\s+query\b/
854
+ ]
855
+ };
856
+ MACOS_TRAITS = LINUX_TRAITS;
857
+ HARMONY_TRAITS = LINUX_TRAITS;
858
+ }
859
+ });
860
+
680
861
  // src/engine/logic-rules.ts
681
- import * as fs2 from "fs";
682
- function _loadSensitivePathScope(scope) {
862
+ import * as fs from "fs";
863
+ function acceptedOsValues(osType) {
864
+ const t = String(osType || "").toLowerCase();
865
+ if (t === "windows") return ["windows"];
866
+ if (t === "macos" || t === "darwin" || t === "osx" || t === "mac") return ["linux", "macos"];
867
+ if (t === "harmony" || t === "hmos" || t === "openharmony" || t === "hongmeng") return ["linux", "harmony"];
868
+ return ["linux"];
869
+ }
870
+ function _loadSensitivePathScope(scope, osType = "linux") {
871
+ const accepted = acceptedOsValues(osType);
683
872
  const sps = loadL1().sensitive_path_access || [];
684
- return sps.filter((sp) => sp.scope === scope).map((sp) => ({
873
+ return sps.filter((sp) => sp.scope === scope).filter((sp) => accepted.includes(String(sp.os || "linux").toLowerCase())).map((sp) => ({
685
874
  path: sp.path,
686
875
  riskLevel: sp.risk_level,
687
876
  desc: sp.desc,
688
877
  credential: sp.deny_mode === "deny_both" ? true : void 0,
689
- dirDelOnly: sp.dir_del_only === true ? true : void 0
878
+ readOnly: sp.read_only === true || sp.deny_mode === "deny_read" ? true : void 0,
879
+ denyMode: sp.deny_mode || void 0,
880
+ dirDelOnly: sp.dir_del_only === true ? true : void 0,
881
+ // 保留条目的原始平台归属(非查询用的 osType)—— seed 分组去重用
882
+ os: String(sp.os || "linux").toLowerCase()
690
883
  }));
691
884
  }
692
- function isWriteOperation(actionType, actionDetail, writeKeywords = WRITE_KEYWORDS) {
885
+ function _scopeOf(os3, path5) {
886
+ if (!_scopeIndex) {
887
+ _scopeIndex = /* @__PURE__ */ new Map();
888
+ for (const sp of loadL1().sensitive_path_access || []) {
889
+ const key = `${String(sp.os || "linux").toLowerCase()}|${sp.path}`;
890
+ if (!_scopeIndex.has(key)) _scopeIndex.set(key, sp.scope);
891
+ }
892
+ }
893
+ return _scopeIndex.get(`${String(os3 || "linux").toLowerCase()}|${path5}`) || "root";
894
+ }
895
+ function getSensitivePathsForOs(osType) {
896
+ const effective = runtimeConfigBridge.current?.effectiveSensitivePaths(osType);
897
+ if (effective) {
898
+ const root = [];
899
+ const home = [];
900
+ const credential = [];
901
+ for (const r of effective) {
902
+ const entry = {
903
+ path: r.path,
904
+ riskLevel: r.riskLevel,
905
+ desc: r.desc,
906
+ // ⚠️ 不要强制 credential:用户在 Console 把某条从 deny_both 改成 deny_write
907
+ // 之后,强制 credential 会让读操作仍被拦(改了不生效)。一律以 runtime 为准。
908
+ credential: r.credential === true ? true : void 0,
909
+ readOnly: r.readOnly === true ? true : void 0,
910
+ denyMode: r.denyMode || void 0,
911
+ dirDelOnly: r.dirDelOnly === true ? true : void 0
912
+ };
913
+ const scope = _scopeOf(r.os, r.path);
914
+ if (scope === "home") home.push(entry);
915
+ else if (scope === "credential") credential.push(entry);
916
+ else root.push(entry);
917
+ }
918
+ return { root, home, credential };
919
+ }
920
+ return getSensitivePathsForOsBuiltin(osType);
921
+ }
922
+ function getSensitivePathsForOsBuiltin(osType) {
923
+ const t = String(osType || "").toLowerCase();
924
+ if (t === "windows") {
925
+ return { root: ROOT_SENSITIVE_PATHS_WINDOWS, home: HOME_SENSITIVE_PATHS_WINDOWS, credential: CREDENTIAL_PATHS_WINDOWS };
926
+ }
927
+ if (t === "macos" || t === "darwin" || t === "osx" || t === "mac") {
928
+ return { root: ROOT_SENSITIVE_PATHS_MACOS, home: HOME_SENSITIVE_PATHS_MACOS, credential: CREDENTIAL_PATHS_MACOS };
929
+ }
930
+ if (t === "harmony" || t === "hmos" || t === "openharmony" || t === "hongmeng") {
931
+ return { root: ROOT_SENSITIVE_PATHS_HARMONY, home: HOME_SENSITIVE_PATHS_HARMONY, credential: CREDENTIAL_PATHS_HARMONY };
932
+ }
933
+ return { root: ROOT_SENSITIVE_PATHS, home: HOME_SENSITIVE_PATHS, credential: CREDENTIAL_PATHS };
934
+ }
935
+ function isWriteOperation(actionType, actionDetail, writeKeywords = WRITE_KEYWORDS, osType) {
693
936
  if (writeKeywords.some((kw) => actionType.includes(kw) || actionDetail.includes(kw))) return true;
694
- if (WRITE_COMMAND_RES.some((rx) => rx.test(actionDetail))) return true;
937
+ const { writeCommandRes } = getPlatformTraits(osType);
938
+ if (writeCommandRes.some((rx) => rx.test(actionDetail))) return true;
695
939
  if (/\bsed\s+-[a-zA-Z]*i/i.test(actionDetail)) return true;
696
940
  for (const seg of actionDetail.split(/&&|\|\||;|\n/)) {
697
941
  const trimmed = seg.trim();
@@ -725,6 +969,12 @@ function isWriteOperation(actionType, actionDetail, writeKeywords = WRITE_KEYWOR
725
969
  }
726
970
  return false;
727
971
  }
972
+ function getIntentPatternsForOs(osType) {
973
+ const effective = runtimeConfigBridge.current?.effectiveIntentPatterns(osType);
974
+ if (effective) return effective;
975
+ const accepted = acceptedOsValues(osType);
976
+ return INTENT_DEVIATION_PATTERNS.filter((p) => accepted.includes(p.os));
977
+ }
728
978
  function matchDangerousAction(actionDetail, action) {
729
979
  if (/[^\x00-\x7f]/.test(action)) {
730
980
  return actionDetail.includes(action);
@@ -759,38 +1009,47 @@ function collectIntentTexts(promptSession, promptHistory) {
759
1009
  }
760
1010
  return deduped;
761
1011
  }
762
- function runLogicCheck(input) {
1012
+ function checkIntentConsistency(input) {
1013
+ const { aNext, promptSession, promptHistory } = input;
1014
+ const actionDetail = aNext.actionDetail.toLowerCase();
1015
+ const actionType = aNext.actionType.toLowerCase();
1016
+ const EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
1017
+ if (EXCLUDED_PREFIXES.some((p) => actionType.startsWith(p))) return null;
1018
+ if (INLINE_SCRIPT_RE.test(actionDetail)) return null;
1019
+ const intentTexts = collectIntentTexts(promptSession, promptHistory);
1020
+ for (const pattern of getIntentPatternsForOs(input.osType)) {
1021
+ const intentMatched = intentTexts.some(
1022
+ (text) => pattern.intentKeywords.some((kw) => text.includes(kw))
1023
+ );
1024
+ const dangerousMatched = pattern.dangerousActions.some((kw) => matchDangerousAction(actionDetail, kw));
1025
+ if (intentMatched && dangerousMatched) {
1026
+ return {
1027
+ allowed: false,
1028
+ reason: pattern.reason,
1029
+ riskLevel: "high",
1030
+ riskType: "intent_deviation",
1031
+ confidence: 85,
1032
+ source: "logic_rule",
1033
+ violatedLayers: ["L2.1"]
1034
+ };
1035
+ }
1036
+ }
1037
+ return null;
1038
+ }
1039
+ function runLogicCheck(input, opts) {
763
1040
  const { actionHistory, aNext, cwd } = input;
764
1041
  const actionDetail = aNext.actionDetail.toLowerCase();
765
1042
  const actionType = aNext.actionType.toLowerCase();
766
1043
  const resolvedCwd = cwd || "/";
767
1044
  const EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
768
1045
  const isExcludedType = EXCLUDED_PREFIXES.some((p) => actionType.startsWith(p));
769
- const rbwResult = checkReadBeforeWrite(actionType, actionDetail, actionHistory, input.reason, resolvedCwd);
770
- if (rbwResult) return rbwResult;
771
- if (!isExcludedType) {
772
- if (!INLINE_SCRIPT_RE.test(actionDetail)) {
773
- const intentTexts = collectIntentTexts(input.promptSession, input.promptHistory);
774
- for (const pattern of INTENT_DEVIATION_PATTERNS) {
775
- const intentMatched = intentTexts.some(
776
- (text) => pattern.intentKeywords.some((kw) => text.includes(kw))
777
- );
778
- const dangerousMatched = pattern.dangerousActions.some((kw) => matchDangerousAction(actionDetail, kw));
779
- if (intentMatched && dangerousMatched) {
780
- return {
781
- allowed: false,
782
- reason: pattern.reason,
783
- riskLevel: "high",
784
- riskType: "intent_deviation",
785
- confidence: 85,
786
- source: "logic_rule",
787
- violatedLayers: ["L2.1"]
788
- };
789
- }
790
- }
791
- }
1046
+ if (!opts?.skipReadBeforeWrite) {
1047
+ const rbwResult = checkReadBeforeWrite(actionType, actionDetail, actionHistory, input.reason, resolvedCwd, input.osType);
1048
+ if (rbwResult) return rbwResult;
792
1049
  }
793
- const indirectResult = checkIndirectFileAccess(actionType, actionDetail, resolvedCwd);
1050
+ const intentResult = checkIntentConsistency(input);
1051
+ if (intentResult) return intentResult;
1052
+ const indirectResult = checkIndirectFileAccess(actionType, actionDetail, resolvedCwd, input.osType);
794
1053
  if (indirectResult) return indirectResult;
795
1054
  if (INLINE_SCRIPT_RE.test(actionDetail)) {
796
1055
  } else if (containsPrivilegeEscalation(actionDetail)) {
@@ -813,9 +1072,9 @@ function runLogicCheck(input) {
813
1072
  }
814
1073
  }
815
1074
  }
816
- const pwdResult = checkPasswordConsent(actionType, actionDetail, actionHistory);
1075
+ const pwdResult = checkPasswordConsent(actionType, actionDetail, actionHistory, input.osType);
817
1076
  if (pwdResult) return pwdResult;
818
- const userDelResult = checkUserDeletionConsent(actionType, actionDetail, actionHistory);
1077
+ const userDelResult = checkUserDeletionConsent(actionType, actionDetail, actionHistory, input.osType);
819
1078
  if (userDelResult) return userDelResult;
820
1079
  const comboResult = checkComboChain(actionHistory, aNext.actionDetail);
821
1080
  if (comboResult) return comboResult;
@@ -866,22 +1125,33 @@ function checkComboChain(actionHistory, actionDetailRaw) {
866
1125
  }
867
1126
  return null;
868
1127
  }
869
- function extractFilePaths(text) {
1128
+ function extractFilePaths(text, osType) {
870
1129
  const paths = [];
871
1130
  const unixPaths = text.match(/(?:^|\s)(\/[^\s;|&><'"]+)/g);
872
1131
  if (unixPaths) paths.push(...unixPaths.map((p) => p.trim()));
1132
+ const isWindows = String(osType || "").toLowerCase() === "windows";
1133
+ if (isWindows) {
1134
+ const winPaths = text.match(/(?:^|\s)([A-Za-z]:[\\\/][^\s;|&><'"]+)/g);
1135
+ if (winPaths) paths.push(...winPaths.map((p) => p.trim()));
1136
+ const uncPaths = text.match(/(?:^|\s)(\\{2}[^\\\s]+(?:\\[^\s;|&><'"]*))/g);
1137
+ if (uncPaths) paths.push(...uncPaths.map((p) => p.trim()));
1138
+ }
873
1139
  const textNoQuotes = text.replace(/'[^']*'|"[^"]*"/g, "");
874
1140
  const kvPaths = textNoQuotes.match(/(?:=)(\/[^\s;|&><'"]+)/g);
875
1141
  if (kvPaths) paths.push(...kvPaths.map((p) => p.replace(/^=/, "")));
876
1142
  const quotedPaths = text.match(/["']\/([^"']+)["']/g);
877
1143
  if (quotedPaths) paths.push(...quotedPaths.map((p) => p.replace(/["']/g, "")));
1144
+ if (isWindows) {
1145
+ const quotedWinPaths = text.match(/["']([A-Za-z]:[\\\/][^"']+)["']/g);
1146
+ if (quotedWinPaths) paths.push(...quotedWinPaths.map((p) => p.replace(/["']/g, "")));
1147
+ }
878
1148
  return [...new Set(paths)];
879
1149
  }
880
- function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, cwd) {
1150
+ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, cwd, osType) {
881
1151
  if (NON_WRITE_TOOL_PREFIXES.some((p) => actionType.startsWith(p))) return null;
882
1152
  if (NON_WRITE_TOOLS.has(actionType)) return null;
883
1153
  const isBashLike = actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash";
884
- const isWrite = isBashLike ? isWriteOperation(actionType, actionDetail) : WRITE_KEYWORDS.some((kw) => actionType.includes(kw));
1154
+ const isWrite = isBashLike ? isWriteOperation(actionType, actionDetail, void 0, osType) : getPlatformTraits(osType).writeKeywords.some((kw) => actionType.includes(kw));
885
1155
  if (!isWrite) return null;
886
1156
  const WRITE_CMDS = /* @__PURE__ */ new Set(["cp", "mv", "install", "tee", "dd", "truncate", "mkfs", "fdisk", "parted"]);
887
1157
  if (isBashLike) {
@@ -925,7 +1195,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
925
1195
  if (firstWord === "cd") {
926
1196
  continue;
927
1197
  }
928
- if (READ_ONLY_COMMANDS.has(firstWord)) {
1198
+ if (getPlatformTraits(osType).readOnlyCommands.has(firstWord)) {
929
1199
  const segUnquoted = trimmed.replace(new RegExp(`>{1,2}\\s*(['"])([~./][^'"]+)\\1`, "g"), (_m, _q, p) => ">" + p);
930
1200
  const segNoDevnull = segUnquoted.replace(DEVNULL_REDIRECT_RE, "");
931
1201
  const segNoQuotes = segNoDevnull.replace(/'[^']*'|"[^"]*"/g, "");
@@ -933,7 +1203,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
933
1203
  if (m) targetPaths.push(m[1] || m[2] || m[3]);
934
1204
  continue;
935
1205
  }
936
- const segPaths = extractFilePaths(trimmed);
1206
+ const segPaths = extractFilePaths(trimmed, osType);
937
1207
  const segPathsFiltered = firstWord === "wget" || firstWord === "curl" ? segPaths.filter((p) => p !== "/dev/null") : segPaths;
938
1208
  if (SOURCE_ARG_CMDS.has(firstWord)) {
939
1209
  if (segPathsFiltered.length > 1) targetPaths.push(...segPathsFiltered.slice(1));
@@ -955,20 +1225,30 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
955
1225
  for (const action of actionHistory) {
956
1226
  const histDetail = action.actionDetail.toLowerCase();
957
1227
  const histType = action.actionType.toLowerCase();
958
- const isRead = READ_KEYWORDS.some((kw) => histType.includes(kw) || histDetail.includes(kw));
1228
+ const isRead = getPlatformTraits(osType).readKeywords.some((kw) => histType.includes(kw) || histDetail.includes(kw));
959
1229
  if (isRead) {
960
- for (const p of extractFilePaths(histDetail)) {
1230
+ for (const p of extractFilePaths(histDetail, osType)) {
961
1231
  historyReadPaths.add(p);
962
1232
  }
963
1233
  }
964
1234
  }
1235
+ const TEMP_DIRS = ["/tmp/", "/var/tmp/", "/dev/shm/", "/run/"];
1236
+ let filteredPaths = uniqueTargetPaths;
1237
+ if (/\bdd\b/i.test(actionDetail) && /\bif=\/dev\/zero\b/i.test(actionDetail)) {
1238
+ filteredPaths = uniqueTargetPaths.filter(
1239
+ (p) => !TEMP_DIRS.some((td) => p.startsWith(td))
1240
+ );
1241
+ }
1242
+ if (filteredPaths.length === 0) {
1243
+ return null;
1244
+ }
965
1245
  const unreadPaths = [];
966
- for (const path5 of uniqueTargetPaths) {
1246
+ for (const path5 of filteredPaths) {
967
1247
  const hasRead = [...historyReadPaths].some(
968
1248
  (rp) => path5 === rp || path5.startsWith(rp) || rp.startsWith(path5)
969
1249
  );
970
1250
  if (!hasRead) {
971
- let isCreate = !fs2.existsSync(path5);
1251
+ let isCreate = !fs.existsSync(path5);
972
1252
  if (!isCreate) {
973
1253
  isCreate = CREATE_KEYWORDS.some((kw) => reason.toLowerCase().includes(kw) || actionDetail.includes(kw));
974
1254
  }
@@ -990,14 +1270,18 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
990
1270
  }
991
1271
  return null;
992
1272
  }
993
- function checkPasswordConsent(actionType, actionDetail, actionHistory) {
1273
+ function hasUserConsent(actionHistory) {
1274
+ return actionHistory.some((h) => {
1275
+ const t = String(h.actionType || "").toLowerCase();
1276
+ return t === "ask_user_question" && String(h.output || "").trim().length > 0;
1277
+ });
1278
+ }
1279
+ function checkPasswordConsent(actionType, actionDetail, actionHistory, osType) {
994
1280
  if (actionType !== "bash") return null;
995
- const isPasswordModify = PASSWORD_MODIFY_PATTERNS.some((p) => p.test(actionDetail));
1281
+ const pwdPatterns = runtimeConfigBridge.current?.effectivePasswordPatterns(osType) ?? PASSWORD_MODIFY_PATTERNS;
1282
+ const isPasswordModify = pwdPatterns.some((p) => p.test(actionDetail));
996
1283
  if (!isPasswordModify) return null;
997
- const hasConsent = actionHistory.some(
998
- (h) => h.actionType.toLowerCase() === "ask_user_question" && h.actionDetail.trim().length > 0
999
- );
1000
- if (hasConsent) return null;
1284
+ if (hasUserConsent(actionHistory)) return null;
1001
1285
  return {
1002
1286
  allowed: false,
1003
1287
  reason: "\u6267\u884C\u975E\u4EA4\u4E92\u5F0F\u5BC6\u7801\u4FEE\u6539\u547D\u4EE4\uFF0C\u4F46\u5386\u53F2\u6267\u884C\u5E8F\u5217\u4E2D\u672A\u53D1\u73B0\u5411\u7528\u6237\u786E\u8BA4\u5BC6\u7801\u7684 ask_user_question \u64CD\u4F5C\uFF0C\u53EF\u80FD\u5B58\u5728\u672A\u6388\u6743\u7684\u5BC6\u7801\u4FEE\u6539\u98CE\u9669",
@@ -1008,14 +1292,12 @@ function checkPasswordConsent(actionType, actionDetail, actionHistory) {
1008
1292
  violatedLayers: ["L2.1"]
1009
1293
  };
1010
1294
  }
1011
- function checkUserDeletionConsent(actionType, actionDetail, actionHistory) {
1295
+ function checkUserDeletionConsent(actionType, actionDetail, actionHistory, osType) {
1012
1296
  if (actionType !== "bash") return null;
1013
- const isUserDeletion = USER_DELETION_PATTERNS.some((p) => p.test(actionDetail));
1297
+ const udPatterns = runtimeConfigBridge.current?.effectiveUserDeletionPatterns(osType) ?? USER_DELETION_PATTERNS;
1298
+ const isUserDeletion = udPatterns.some((p) => p.test(actionDetail));
1014
1299
  if (!isUserDeletion) return null;
1015
- const hasConsent = actionHistory.some(
1016
- (h) => h.actionType.toLowerCase() === "ask_user_question" && h.actionDetail.trim().length > 0
1017
- );
1018
- if (hasConsent) return null;
1300
+ if (hasUserConsent(actionHistory)) return null;
1019
1301
  return {
1020
1302
  allowed: false,
1021
1303
  reason: "\u6267\u884C\u5220\u9664\u7CFB\u7EDF\u7528\u6237\u547D\u4EE4\uFF0C\u4F46\u5386\u53F2\u6267\u884C\u5E8F\u5217\u4E2D\u672A\u53D1\u73B0\u5411\u7528\u6237\u786E\u8BA4\u7684 ask_user_question \u64CD\u4F5C\uFF0C\u53EF\u80FD\u5B58\u5728\u672A\u6388\u6743\u7684\u7528\u6237\u5220\u9664\u98CE\u9669",
@@ -1038,7 +1320,7 @@ function containsPrivilegeEscalation(actionDetail) {
1038
1320
  }
1039
1321
  return false;
1040
1322
  }
1041
- function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1323
+ function checkIndirectFileAccess(actionType, actionDetail, cwd, osType) {
1042
1324
  if (actionType !== "bash" && actionType !== "executebash" && actionType !== "terminal-executebash") {
1043
1325
  return null;
1044
1326
  }
@@ -1051,14 +1333,14 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1051
1333
  if (!hasTraversal) {
1052
1334
  return null;
1053
1335
  }
1054
- const READ_KEYWORDS2 = ["read(", "readline", "readlines", "fread", "fgets", "load(", "getpass", "file_get_contents"];
1336
+ const READ_KEYWORDS = ["read(", "readline", "readlines", "fread", "fgets", "load(", "getpass", "file_get_contents"];
1055
1337
  const WRITE_KEYWORDS2 = ["write(", "writelines", "fwrite", "file_put_contents"];
1056
1338
  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"];
1057
1339
  const OPEN_KEYWORDS = ["open(", "fopen("];
1058
1340
  const code = extractInlineCode(actionDetail);
1059
1341
  if (!code) return null;
1060
1342
  const codeLower = code.toLowerCase();
1061
- const hasAnyOp = READ_KEYWORDS2.some((kw) => codeLower.includes(kw)) || WRITE_KEYWORDS2.some((kw) => codeLower.includes(kw)) || EXEC_KEYWORDS.some((kw) => codeLower.includes(kw)) || OPEN_KEYWORDS.some((kw) => codeLower.includes(kw));
1343
+ const hasAnyOp = READ_KEYWORDS.some((kw) => codeLower.includes(kw)) || WRITE_KEYWORDS2.some((kw) => codeLower.includes(kw)) || EXEC_KEYWORDS.some((kw) => codeLower.includes(kw)) || OPEN_KEYWORDS.some((kw) => codeLower.includes(kw));
1062
1344
  if (!hasAnyOp) {
1063
1345
  return null;
1064
1346
  }
@@ -1066,15 +1348,16 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1066
1348
  const hasExec = EXEC_KEYWORDS.some((kw) => codeLower.includes(kw));
1067
1349
  const blockAllSensitive = hasWrite || hasExec;
1068
1350
  let actualFiles = [];
1069
- if (cwd && cwd !== "/") {
1351
+ if (cwd) {
1070
1352
  try {
1071
- actualFiles = fs2.readdirSync(cwd);
1353
+ actualFiles = fs.readdirSync(cwd);
1072
1354
  } catch {
1073
1355
  }
1074
1356
  }
1075
1357
  if (actualFiles.length > 0) {
1076
- for (const cp of CREDENTIAL_PATHS) {
1077
- const fname = cp.path.split("/").pop() || cp.path;
1358
+ const { root: rootPaths, home: homePaths, credential: credentialPaths } = getSensitivePathsForOs(osType);
1359
+ for (const cp of credentialPaths) {
1360
+ const fname = cp.path.split(/[/\\]/).pop() || cp.path;
1078
1361
  if (actualFiles.some((f) => f === fname || f.toLowerCase() === fname.toLowerCase())) {
1079
1362
  return {
1080
1363
  allowed: false,
@@ -1089,8 +1372,8 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1089
1372
  }
1090
1373
  }
1091
1374
  if (blockAllSensitive) {
1092
- for (const sp of [...ROOT_SENSITIVE_PATHS, ...HOME_SENSITIVE_PATHS]) {
1093
- const fname = sp.path.split("/").pop() || sp.path;
1375
+ for (const sp of [...rootPaths, ...homePaths]) {
1376
+ const fname = sp.path.split(/[/\\]/).pop() || sp.path;
1094
1377
  if (actualFiles.some((f) => f === fname || f.toLowerCase() === fname.toLowerCase())) {
1095
1378
  return {
1096
1379
  allowed: false,
@@ -1116,16 +1399,28 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
1116
1399
  violatedLayers: ["L2.1"]
1117
1400
  };
1118
1401
  }
1119
- var ROOT_SENSITIVE_PATHS, HOME_SENSITIVE_PATHS, CREDENTIAL_PATHS, NON_WRITE_TOOLS, NON_WRITE_TOOL_PREFIXES, WRITE_KEYWORDS, WRITE_COMMAND_RES, READ_ONLY_COMMANDS, DEVNULL_REDIRECT_RE, READ_KEYWORDS, CREATE_KEYWORDS, PASSWORD_MODIFY_PATTERNS, USER_DELETION_PATTERNS, INTENT_DEVIATION_PATTERNS, COMBO_STAGES, COMBO_CHAINS, INLINE_SCRIPT_RE, DIR_TRAVERSAL_KW;
1402
+ 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, DEVNULL_REDIRECT_RE, CREATE_KEYWORDS, PASSWORD_MODIFY_PATTERNS, USER_DELETION_PATTERNS, INTENT_DEVIATION_PATTERNS, COMBO_STAGES, COMBO_CHAINS, INLINE_SCRIPT_RE, DIR_TRAVERSAL_KW;
1120
1403
  var init_logic_rules = __esm({
1121
1404
  "src/engine/logic-rules.ts"() {
1122
1405
  "use strict";
1123
1406
  init_esm_shims();
1124
1407
  init_inline_analyzer();
1408
+ init_runtime_config_bridge();
1125
1409
  init_rules_loader();
1410
+ init_platform_traits();
1126
1411
  ROOT_SENSITIVE_PATHS = _loadSensitivePathScope("root");
1127
1412
  HOME_SENSITIVE_PATHS = _loadSensitivePathScope("home");
1128
1413
  CREDENTIAL_PATHS = _loadSensitivePathScope("credential").map(({ path: path5, riskLevel, desc, dirDelOnly }) => ({ path: path5, riskLevel, desc, credential: true, dirDelOnly }));
1414
+ ROOT_SENSITIVE_PATHS_WINDOWS = _loadSensitivePathScope("root", "windows");
1415
+ HOME_SENSITIVE_PATHS_WINDOWS = _loadSensitivePathScope("home", "windows");
1416
+ CREDENTIAL_PATHS_WINDOWS = _loadSensitivePathScope("credential", "windows").map(({ path: path5, riskLevel, desc, dirDelOnly }) => ({ path: path5, riskLevel, desc, credential: true, dirDelOnly }));
1417
+ ROOT_SENSITIVE_PATHS_MACOS = _loadSensitivePathScope("root", "macos");
1418
+ HOME_SENSITIVE_PATHS_MACOS = _loadSensitivePathScope("home", "macos");
1419
+ CREDENTIAL_PATHS_MACOS = _loadSensitivePathScope("credential", "macos").map(({ path: path5, riskLevel, desc, dirDelOnly }) => ({ path: path5, riskLevel, desc, credential: true, dirDelOnly }));
1420
+ ROOT_SENSITIVE_PATHS_HARMONY = _loadSensitivePathScope("root", "harmony");
1421
+ HOME_SENSITIVE_PATHS_HARMONY = _loadSensitivePathScope("home", "harmony");
1422
+ CREDENTIAL_PATHS_HARMONY = _loadSensitivePathScope("credential", "harmony").map(({ path: path5, riskLevel, desc, dirDelOnly }) => ({ path: path5, riskLevel, desc, credential: true, dirDelOnly }));
1423
+ _scopeIndex = null;
1129
1424
  NON_WRITE_TOOLS = /* @__PURE__ */ new Set([
1130
1425
  "ask_user_question",
1131
1426
  "glob",
@@ -1147,111 +1442,7 @@ var init_logic_rules = __esm({
1147
1442
  ]);
1148
1443
  NON_WRITE_TOOL_PREFIXES = ["memory-", "skill-manager-", "mailbox-", "app-"];
1149
1444
  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"];
1150
- WRITE_COMMAND_RES = [
1151
- /\brm\b/,
1152
- /\bunlink\b/,
1153
- /\brmdir\b/,
1154
- /\bshred\b/,
1155
- /\bcp\b/,
1156
- /\bmv\b/,
1157
- /\binstall\b/,
1158
- /\btee\b/,
1159
- /\bdd\b/,
1160
- /\bmkfs\b/,
1161
- /\b(fdisk|parted|cfdisk|sfdisk)\b/,
1162
- /\bchmod\b/,
1163
- /\bchown\b/,
1164
- /\btruncate\b/
1165
- ];
1166
- READ_ONLY_COMMANDS = /* @__PURE__ */ new Set([
1167
- "cat",
1168
- "head",
1169
- "tail",
1170
- "less",
1171
- "more",
1172
- "grep",
1173
- "find",
1174
- "awk",
1175
- "sed",
1176
- "sort",
1177
- "uniq",
1178
- "wc",
1179
- "cut",
1180
- "strings",
1181
- "od",
1182
- "xxd",
1183
- "hexdump",
1184
- "tr",
1185
- "file",
1186
- "stat",
1187
- "du",
1188
- "df",
1189
- "ls",
1190
- "dir",
1191
- "tree",
1192
- "nl",
1193
- "tac",
1194
- "rev",
1195
- // NEW-FP-I: echo/printf 是文本输出命令,参数是字面量非写目标。加入此集让
1196
- // read_before_write 的 echo 段走只读命令分支(只提取重定向目标,不提取参数路径),
1197
- // 避免 echo "cat /etc/shadow" > /tmp/x.sh 的引号内 /etc/shadow 被当写目标误报。
1198
- // echo "x" > /tmp/x.sh 的重定向目标 /tmp/x.sh 仍被提取(真实写不漏报)。
1199
- "echo",
1200
- "printf",
1201
- "lsblk",
1202
- "blockdev",
1203
- "smartctl",
1204
- "udevadm",
1205
- "dmidecode",
1206
- "lscpu",
1207
- "lspci",
1208
- "lsusb",
1209
- "lsmem",
1210
- "lsns",
1211
- "lsof",
1212
- "hwinfo",
1213
- "inxi",
1214
- "fdisk",
1215
- "parted",
1216
- "dmesg",
1217
- "journalctl",
1218
- "systemctl",
1219
- "hostnamectl",
1220
- "localectl",
1221
- "timedatectl",
1222
- "ps",
1223
- "top",
1224
- "free",
1225
- "vmstat",
1226
- "iostat",
1227
- "mpstat",
1228
- "pidof",
1229
- "pgrep",
1230
- "ip",
1231
- "ifconfig",
1232
- "route",
1233
- "ss",
1234
- "netstat",
1235
- "arp",
1236
- "ethtool",
1237
- "nmcli",
1238
- "uname",
1239
- "arch",
1240
- "nproc",
1241
- "getconf",
1242
- "getent",
1243
- "id",
1244
- "whoami",
1245
- "who",
1246
- "w",
1247
- "last",
1248
- "uptime",
1249
- "lsmod",
1250
- "modinfo",
1251
- "sysctl"
1252
- ]);
1253
1445
  DEVNULL_REDIRECT_RE = /(?:2|&|1)?\s*>\s*\/dev\/null\b/g;
1254
- READ_KEYWORDS = ["read", "\u8BFB\u53D6", "cat", "head", "tail", "less", "more", "view", "\u67E5\u770B", "open", "\u6253\u5F00", "load", "\u52A0\u8F7D", "grep", "search", "\u641C\u7D22", "find", "\u67E5\u627E"];
1255
1446
  CREATE_KEYWORDS = ["\u521B\u5EFA", "\u65B0\u5EFA", "create", "new file", "touch"];
1256
1447
  PASSWORD_MODIFY_PATTERNS = (() => {
1257
1448
  const pats = loadL2().password_modify_consent || [];
@@ -1266,7 +1457,8 @@ var init_logic_rules = __esm({
1266
1457
  return pats.map((p) => ({
1267
1458
  intentKeywords: p.intent_keywords,
1268
1459
  dangerousActions: p.dangerous_actions,
1269
- reason: p.reason
1460
+ reason: p.reason,
1461
+ os: String(p.os || "linux").toLowerCase()
1270
1462
  }));
1271
1463
  })();
1272
1464
  COMBO_STAGES = (() => {
@@ -1303,34 +1495,90 @@ var init_logic_rules = __esm({
1303
1495
  }
1304
1496
  });
1305
1497
 
1498
+ // src/engine/asset-dir.ts
1499
+ import { existsSync as existsSync3 } from "fs";
1500
+ import { resolve as resolve2 } from "path";
1501
+ function assetDirCandidates(moduleDir, name) {
1502
+ return [
1503
+ resolve2(moduleDir, name),
1504
+ resolve2(moduleDir, "../../..", "agent_moss", name)
1505
+ ];
1506
+ }
1507
+ function resolveAssetDir(moduleDir, name) {
1508
+ for (const dir of assetDirCandidates(moduleDir, name)) {
1509
+ if (existsSync3(dir)) return dir;
1510
+ }
1511
+ return null;
1512
+ }
1513
+ var init_asset_dir = __esm({
1514
+ "src/engine/asset-dir.ts"() {
1515
+ "use strict";
1516
+ init_esm_shims();
1517
+ }
1518
+ });
1519
+
1306
1520
  // src/engine/skill-loader.ts
1307
- import { readFileSync as readFileSync3, readdirSync as readdirSync2, existsSync as existsSync3 } from "fs";
1308
- import { resolve as resolve2, dirname as dirname2 } from "path";
1309
- import { fileURLToPath as fileURLToPath4 } from "url";
1521
+ import { readFileSync as readFileSync3, readdirSync as readdirSync2, existsSync as existsSync4 } from "fs";
1522
+ import { resolve as resolve3, dirname as dirname2 } from "path";
1523
+ import { fileURLToPath as fileURLToPath3 } from "url";
1310
1524
  function loadAllSkills() {
1311
1525
  if (cachedSkills) return cachedSkills;
1312
1526
  cachedSkills = {};
1313
- const dir = existsSync3(SKILLS_DIR) ? SKILLS_DIR : SKILLS_DIR_FALLBACK;
1314
- if (!existsSync3(dir)) {
1315
- console.warn("[AgentMoss] Skills directory not found:", SKILLS_DIR, "and fallback:", SKILLS_DIR_FALLBACK);
1527
+ const dir = resolveAssetDir(__dirname3, "skills");
1528
+ if (!dir) {
1529
+ const [primary, fallback] = assetDirCandidates(__dirname3, "skills");
1530
+ console.warn("[AgentMoss] Skills directory not found:", primary, "and fallback:", fallback);
1316
1531
  return cachedSkills;
1317
1532
  }
1318
1533
  const files = readdirSync2(dir).filter((f) => f.endsWith(".md"));
1319
1534
  for (const f of files) {
1320
1535
  const name = f.replace(".md", "");
1321
- cachedSkills[name] = readFileSync3(resolve2(dir, f), "utf-8");
1536
+ cachedSkills[name] = readFileSync3(resolve3(dir, f), "utf-8");
1322
1537
  }
1323
1538
  console.log(`[AgentMoss] Loaded ${Object.keys(cachedSkills).length} skills from ${dir}`);
1324
1539
  return cachedSkills;
1325
1540
  }
1541
+ function userSkillsDir() {
1542
+ return resolve3(getDataRoot(), "agent_moss_skills");
1543
+ }
1544
+ function runtimeCustomSkillKeywords() {
1545
+ try {
1546
+ const rt = runtimeConfigBridge.current?.loadRuntimeConfig?.();
1547
+ if (!rt) return {};
1548
+ const out = {};
1549
+ for (const cat of Object.values(rt.L3_skills || {})) {
1550
+ if (!cat || typeof cat !== "object" || cat.category_enabled === false) continue;
1551
+ for (const sk of cat.skills || []) {
1552
+ if (!sk || sk.builtin !== false || sk.enabled === false || !sk.id) continue;
1553
+ out[sk.id] = sk.keywords || [];
1554
+ }
1555
+ }
1556
+ return out;
1557
+ } catch {
1558
+ return {};
1559
+ }
1560
+ }
1561
+ function loadUserSkill(name) {
1562
+ if (userSkillCache[name] !== void 0) return userSkillCache[name];
1563
+ try {
1564
+ const p = resolve3(userSkillsDir(), `${name}.md`);
1565
+ userSkillCache[name] = existsSync4(p) ? readFileSync3(p, "utf-8") : "";
1566
+ } catch {
1567
+ userSkillCache[name] = "";
1568
+ }
1569
+ return userSkillCache[name];
1570
+ }
1326
1571
  function matchSkills(actionType, actionDetail, reason, maxSkills = 3) {
1327
1572
  const skills = loadAllSkills();
1328
1573
  const combined = `${actionType} ${actionDetail} ${reason}`.toLowerCase();
1329
1574
  const actionDetailLower = actionDetail.toLowerCase();
1330
1575
  const actionTypeLower = actionType.toLowerCase();
1331
1576
  const reasonLower = reason.toLowerCase();
1577
+ const disabledSkillIds = runtimeConfigBridge.current?.disabledSkillIds() ?? /* @__PURE__ */ new Set();
1578
+ const skillMap = { ...SKILL_KEYWORD_MAP, ...runtimeCustomSkillKeywords() };
1332
1579
  const scores = {};
1333
- for (const [skillName, keywords] of Object.entries(SKILL_KEYWORD_MAP)) {
1580
+ for (const [skillName, keywords] of Object.entries(skillMap)) {
1581
+ if (disabledSkillIds.has(skillName)) continue;
1334
1582
  let score = 0;
1335
1583
  for (const kw of keywords) {
1336
1584
  if (combined.includes(kw)) {
@@ -1347,7 +1595,7 @@ function matchSkills(actionType, actionDetail, reason, maxSkills = 3) {
1347
1595
  const maxScore = top[0]?.[1] || 1;
1348
1596
  const results = [];
1349
1597
  for (const [name, score] of top) {
1350
- const content = skills[name];
1598
+ const content = skills[name] || loadUserSkill(name);
1351
1599
  if (content) {
1352
1600
  results.push({
1353
1601
  skillName: name,
@@ -1372,16 +1620,17 @@ function formatSkillsForPrompt(skills) {
1372
1620
  ${s.skillContent}`
1373
1621
  ).join("\n\n---\n\n");
1374
1622
  }
1375
- var __filename3, __dirname3, SKILLS_DIR, SKILLS_DIR_FALLBACK, SKILL_KEYWORD_MAP, cachedSkills;
1623
+ var __filename3, __dirname3, SKILL_KEYWORD_MAP, cachedSkills, userSkillCache;
1376
1624
  var init_skill_loader = __esm({
1377
1625
  "src/engine/skill-loader.ts"() {
1378
1626
  "use strict";
1379
1627
  init_esm_shims();
1380
1628
  init_rules_loader();
1381
- __filename3 = fileURLToPath4(import.meta.url);
1629
+ init_paths();
1630
+ init_asset_dir();
1631
+ init_runtime_config_bridge();
1632
+ __filename3 = fileURLToPath3(import.meta.url);
1382
1633
  __dirname3 = dirname2(__filename3);
1383
- SKILLS_DIR = resolve2(__dirname3, "../skills");
1384
- SKILLS_DIR_FALLBACK = resolve2(__dirname3, "../../../agent_moss/skills");
1385
1634
  SKILL_KEYWORD_MAP = (() => {
1386
1635
  const map = {};
1387
1636
  const cats = loadL3();
@@ -1394,6 +1643,7 @@ var init_skill_loader = __esm({
1394
1643
  return map;
1395
1644
  })();
1396
1645
  cachedSkills = null;
1646
+ userSkillCache = {};
1397
1647
  }
1398
1648
  });
1399
1649
 
@@ -1410,6 +1660,12 @@ __export(runtime_config_exports, {
1410
1660
  deleteCustomRule: () => deleteCustomRule,
1411
1661
  deleteCustomSkill: () => deleteCustomSkill,
1412
1662
  deleteScenarioRule: () => deleteScenarioRule,
1663
+ effectiveDangerousPatterns: () => effectiveDangerousPatterns,
1664
+ effectiveInjectionKeywords: () => effectiveInjectionKeywords,
1665
+ effectiveIntentPatterns: () => effectiveIntentPatterns,
1666
+ effectivePasswordPatterns: () => effectivePasswordPatterns,
1667
+ effectiveSensitivePaths: () => effectiveSensitivePaths,
1668
+ effectiveUserDeletionPatterns: () => effectiveUserDeletionPatterns,
1413
1669
  filterEnabledRules: () => filterEnabledRules,
1414
1670
  generateSourceDefaults: () => generateSourceDefaults,
1415
1671
  getAgentRules: () => getAgentRules,
@@ -1453,9 +1709,9 @@ __export(runtime_config_exports, {
1453
1709
  updateSkillCategoryEnabled: () => updateSkillCategoryEnabled,
1454
1710
  updateSkillEnabled: () => updateSkillEnabled
1455
1711
  });
1456
- import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync4, mkdirSync, unlinkSync, readdirSync as readdirSync3, statSync } from "fs";
1712
+ import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync5, mkdirSync, unlinkSync, readdirSync as readdirSync3, statSync } from "fs";
1457
1713
  import { join as join3 } from "path";
1458
- import { fileURLToPath as fileURLToPath5 } from "url";
1714
+ import { fileURLToPath as fileURLToPath4 } from "url";
1459
1715
  import { dirname as dirname3 } from "path";
1460
1716
  function runtimeDir() {
1461
1717
  return getRuntimeConfigDir();
@@ -1468,7 +1724,7 @@ function getRuntimeConfigPath() {
1468
1724
  function saveRuntimeConfig(config) {
1469
1725
  const p = getRuntimeConfigPath();
1470
1726
  const dir = p.substring(0, p.lastIndexOf("/"));
1471
- if (dir && !existsSync4(dir)) mkdirSync(dir, { recursive: true });
1727
+ if (dir && !existsSync5(dir)) mkdirSync(dir, { recursive: true });
1472
1728
  writeFileSync(p, JSON.stringify(config, null, 2), "utf-8");
1473
1729
  invalidateRuntimeConfigCache();
1474
1730
  }
@@ -1486,10 +1742,11 @@ function emptyRuntimeConfig() {
1486
1742
  }
1487
1743
  function invalidateRuntimeConfigCache() {
1488
1744
  runtimeConfigCache = null;
1745
+ hasRuntimeConfigCache.clear();
1489
1746
  }
1490
1747
  function loadRuntimeConfig() {
1491
1748
  const p = getRuntimeConfigPath();
1492
- if (!existsSync4(p)) {
1749
+ if (!existsSync5(p)) {
1493
1750
  const seeded = mergeConfig({}, generateSourceDefaults());
1494
1751
  try {
1495
1752
  saveRuntimeConfig(seeded);
@@ -1510,7 +1767,13 @@ function loadRuntimeConfig() {
1510
1767
  layers: {
1511
1768
  L1_heuristic: data.layers?.L1_heuristic ?? true,
1512
1769
  L2_logic_rules: data.layers?.L2_logic_rules ?? true,
1513
- L3_llm_analysis: data.layers?.L3_llm_analysis ?? true
1770
+ L3_llm_analysis: data.layers?.L3_llm_analysis ?? true,
1771
+ // ⚠️ agent_rules_enabled / scenario_rules_enabled 必须一并带上:
1772
+ // isLayerEnabled 的判据是 `layerKey in layers`,键丢了就恒真,
1773
+ // 于是控制台的「agent 规则 / 场景规则」总开关**永远是开**(点了没反应)。
1774
+ // 这两个键本来就写在 seed 里(DEFAULT_LAYERS),只是这里没透传。
1775
+ agent_rules_enabled: data.layers?.agent_rules_enabled ?? true,
1776
+ scenario_rules_enabled: data.layers?.scenario_rules_enabled ?? true
1514
1777
  },
1515
1778
  L1_rules: merged.L1_rules || {},
1516
1779
  L2_rules: merged.L2_rules || {},
@@ -1552,21 +1815,48 @@ function filterEnabledRules(rules, categoryEnabled = true) {
1552
1815
  if (!categoryEnabled) return [];
1553
1816
  return rules.filter((r) => r.enabled !== false);
1554
1817
  }
1555
- function getEnabledL1CommandPatterns(runtime) {
1818
+ function _filterRulesByOs(rules, osType) {
1819
+ const accepted = acceptedOsValues(osType);
1820
+ return rules.filter((r) => accepted.includes(String(r.os || "linux").toLowerCase()));
1821
+ }
1822
+ function _userCustomRules(runtime, layers, requiredKey) {
1823
+ const list = Array.isArray(layers) ? layers : [layers];
1824
+ const out = [];
1825
+ for (const layer of list) {
1826
+ const cat = runtime?.[layer]?.user_custom;
1827
+ const rules = filterEnabledRules(cat?.rules || [], cat?.category_enabled);
1828
+ for (const r of rules) {
1829
+ if (!r[requiredKey]) continue;
1830
+ out.push(r.risk_level ? r : { ...r, risk_level: "high" });
1831
+ }
1832
+ }
1833
+ return out;
1834
+ }
1835
+ function _mergeUserCustom(primary, runtime, layers, key) {
1836
+ const extra = _userCustomRules(runtime, layers, key);
1837
+ return extra.length > 0 ? [...primary, ...extra] : [...primary];
1838
+ }
1839
+ function getEnabledL1CommandPatterns(runtime, osType) {
1556
1840
  const cat = runtime.L1_rules?.dangerous_commands || { category_enabled: true, rules: [] };
1557
- return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => ({
1841
+ let rules = filterEnabledRules(cat.rules || [], cat.category_enabled);
1842
+ rules = _mergeUserCustom(rules, runtime, "L1_rules", "pattern");
1843
+ if (osType) rules = _filterRulesByOs(rules, osType);
1844
+ return rules.map((r) => ({
1558
1845
  pattern: r.pattern || "",
1559
1846
  risk_level: r.risk_level || "",
1560
1847
  risk_type: r.risk_type || "",
1561
1848
  reason: r.reason || ""
1562
1849
  }));
1563
1850
  }
1564
- function getEnabledL2SensitivePaths(runtime) {
1851
+ function getEnabledL2SensitivePaths(runtime, osType) {
1565
1852
  let cat = runtime.L1_rules?.sensitive_path_access;
1566
1853
  if (!cat || !cat.rules || cat.rules.length === 0) {
1567
1854
  cat = runtime.L2_rules?.sensitive_path_access || { category_enabled: true, rules: [] };
1568
1855
  }
1569
- return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => {
1856
+ let rules = filterEnabledRules(cat.rules || [], cat.category_enabled);
1857
+ rules = _mergeUserCustom(rules, runtime, ["L1_rules", "L2_rules"], "path");
1858
+ if (osType) rules = _filterRulesByOs(rules, osType);
1859
+ return rules.map((r) => {
1570
1860
  const out = { path: r.path || "", risk_level: r.risk_level || "", desc: r.desc || "", deny_mode: r.deny_mode || "deny_write" };
1571
1861
  if (r.credential) out.credential = true;
1572
1862
  if (r.read_only) out.read_only = true;
@@ -1576,13 +1866,24 @@ function getEnabledL2SensitivePaths(runtime) {
1576
1866
  }
1577
1867
  function hasRuntimeConfig() {
1578
1868
  const p = getRuntimeConfigPath();
1579
- if (!existsSync4(p)) return false;
1869
+ let key;
1580
1870
  try {
1581
- JSON.parse(readFileSync4(p, "utf-8"));
1582
- return true;
1871
+ const st = statSync(p);
1872
+ key = `${p}|${st.mtimeMs}|${st.size}`;
1583
1873
  } catch {
1584
1874
  return false;
1585
1875
  }
1876
+ const cached = hasRuntimeConfigCache.get(key);
1877
+ if (cached !== void 0) return cached;
1878
+ let ok = false;
1879
+ try {
1880
+ JSON.parse(readFileSync4(p, "utf-8"));
1881
+ ok = true;
1882
+ } catch {
1883
+ ok = false;
1884
+ }
1885
+ hasRuntimeConfigCache.set(key, ok);
1886
+ return ok;
1586
1887
  }
1587
1888
  function getDisabledL2RulesWithSkipL3() {
1588
1889
  let runtime;
@@ -1647,15 +1948,28 @@ function _syncDenyModeFields(rule) {
1647
1948
  }
1648
1949
  }
1649
1950
  function _l1CommandPatternsToRules() {
1650
- return DANGEROUS_PATTERNS.map((p) => ({
1651
- id: _generatePatternId(p.pattern instanceof RegExp ? p.pattern.source : p.pattern),
1652
- pattern: p.pattern instanceof RegExp ? p.pattern.source : p.pattern,
1653
- risk_level: p.severity,
1654
- risk_type: p.riskType,
1655
- reason: p.description,
1656
- enabled: true,
1657
- builtin: true
1658
- }));
1951
+ const out = [];
1952
+ const seen = /* @__PURE__ */ new Set();
1953
+ for (const osName of ["linux", "windows", "macos", "harmony"]) {
1954
+ for (const p of getDangerousPatternsForOs(osName)) {
1955
+ const src = p.pattern instanceof RegExp ? p.pattern.source : String(p.pattern);
1956
+ const rule = {
1957
+ id: _generatePatternId(src),
1958
+ pattern: src,
1959
+ risk_level: p.severity,
1960
+ risk_type: p.riskType,
1961
+ reason: p.description,
1962
+ enabled: true,
1963
+ builtin: true,
1964
+ os: p.os || "linux"
1965
+ };
1966
+ const fp = _ruleFingerprint(rule);
1967
+ if (seen.has(fp)) continue;
1968
+ seen.add(fp);
1969
+ out.push(rule);
1970
+ }
1971
+ }
1972
+ return out;
1659
1973
  }
1660
1974
  function _l1InjectionKeywordsToRules() {
1661
1975
  return INJECTION_KEYWORDS.map((kw) => ({
@@ -1668,28 +1982,42 @@ function _l1InjectionKeywordsToRules() {
1668
1982
  }));
1669
1983
  }
1670
1984
  function _l2SensitivePathsToRules() {
1671
- const all = [...ROOT_SENSITIVE_PATHS, ...HOME_SENSITIVE_PATHS, ...CREDENTIAL_PATHS];
1672
- return all.map((sp) => {
1673
- const denyMode = _deriveDenyMode({
1674
- deny_mode: sp.deny_mode,
1675
- credential: sp.credential,
1676
- read_only: sp.read_only
1677
- });
1678
- const rule = {
1679
- id: `path_${_slugify(sp.path)}`,
1680
- path: sp.path,
1681
- risk_level: sp.riskLevel,
1682
- desc: sp.desc,
1683
- deny_mode: denyMode,
1684
- source_deny_mode: denyMode,
1685
- skip_l3_on_disabled: true,
1686
- enabled: true,
1687
- builtin: true
1688
- };
1689
- _syncDenyModeFields(rule);
1690
- if (sp.dirDelOnly) rule.dir_del_only = true;
1691
- return rule;
1692
- });
1985
+ const out = [];
1986
+ const seen = /* @__PURE__ */ new Set();
1987
+ for (const osName of ["linux", "windows", "macos", "harmony"]) {
1988
+ const { root, home, credential } = getSensitivePathsForOsBuiltin(osName);
1989
+ const all = [...root, ...home, ...credential];
1990
+ for (const sp of all) {
1991
+ const os3 = String(sp.os || "linux").toLowerCase();
1992
+ const denyMode = _deriveDenyMode({
1993
+ deny_mode: sp.deny_mode,
1994
+ credential: sp.credential,
1995
+ read_only: sp.read_only
1996
+ });
1997
+ const rule = {
1998
+ // id = path_<平台>_<slug>_<hash8>:平台段消除跨平台同名(.ssh/.aws 各一条),
1999
+ // hash 段消除同平台 slug 撞车(macOS 的 /Library/LaunchAgents 与
2000
+ // Library/LaunchAgents)—— Console 按 id 操作,撞 id 会误改别的规则。
2001
+ id: `path_${os3}_${_slugify(sp.path)}_${_fnv1a32(sp.path)}`,
2002
+ path: sp.path,
2003
+ risk_level: sp.riskLevel,
2004
+ desc: sp.desc,
2005
+ deny_mode: denyMode,
2006
+ source_deny_mode: denyMode,
2007
+ skip_l3_on_disabled: true,
2008
+ enabled: true,
2009
+ builtin: true,
2010
+ os: os3
2011
+ };
2012
+ _syncDenyModeFields(rule);
2013
+ if (sp.dirDelOnly) rule.dir_del_only = true;
2014
+ const fp = _ruleFingerprint(rule);
2015
+ if (seen.has(fp)) continue;
2016
+ seen.add(fp);
2017
+ out.push(rule);
2018
+ }
2019
+ }
2020
+ return out;
1693
2021
  }
1694
2022
  function _l2IntentPatternsToRules() {
1695
2023
  return INTENT_DEVIATION_PATTERNS.map((pat, i) => ({
@@ -1766,8 +2094,8 @@ function _l3SkillsToRules() {
1766
2094
  }
1767
2095
  function _scenarioRulesToDefaults() {
1768
2096
  const out = {};
1769
- const scenDir = existsSync4(_SCENARIOS_DIR_PROD) ? _SCENARIOS_DIR_PROD : existsSync4(_SCENARIOS_DIR_DEV) ? _SCENARIOS_DIR_DEV : _SCENARIOS_DIR_PROD;
1770
- if (!existsSync4(scenDir)) return out;
2097
+ const scenDir = existsSync5(_SCENARIOS_DIR_PROD) ? _SCENARIOS_DIR_PROD : existsSync5(_SCENARIOS_DIR_DEV) ? _SCENARIOS_DIR_DEV : _SCENARIOS_DIR_PROD;
2098
+ if (!existsSync5(scenDir)) return out;
1771
2099
  let entries;
1772
2100
  try {
1773
2101
  entries = readdirSync3(scenDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
@@ -1776,7 +2104,7 @@ function _scenarioRulesToDefaults() {
1776
2104
  }
1777
2105
  for (const scenarioId of entries) {
1778
2106
  const rulesFile = join3(scenDir, scenarioId, "rules.json");
1779
- if (!existsSync4(rulesFile)) continue;
2107
+ if (!existsSync5(rulesFile)) continue;
1780
2108
  let data;
1781
2109
  try {
1782
2110
  data = JSON.parse(readFileSync4(rulesFile, "utf-8"));
@@ -1815,8 +2143,8 @@ function _scenarioRulesToDefaults() {
1815
2143
  }
1816
2144
  function _agentRulesToDefaults() {
1817
2145
  const out = {};
1818
- const agentsDir = existsSync4(_AGENTS_DIR_PROD) ? _AGENTS_DIR_PROD : existsSync4(_AGENTS_DIR_DEV) ? _AGENTS_DIR_DEV : _AGENTS_DIR_PROD;
1819
- if (!existsSync4(agentsDir)) return out;
2146
+ const agentsDir = existsSync5(_AGENTS_DIR_PROD) ? _AGENTS_DIR_PROD : existsSync5(_AGENTS_DIR_DEV) ? _AGENTS_DIR_DEV : _AGENTS_DIR_PROD;
2147
+ if (!existsSync5(agentsDir)) return out;
1820
2148
  let entries;
1821
2149
  try {
1822
2150
  entries = readdirSync3(agentsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
@@ -1825,7 +2153,7 @@ function _agentRulesToDefaults() {
1825
2153
  }
1826
2154
  for (const agentId of entries) {
1827
2155
  const rulesFile = join3(agentsDir, agentId, "rules.json");
1828
- if (!existsSync4(rulesFile)) continue;
2156
+ if (!existsSync5(rulesFile)) continue;
1829
2157
  let data;
1830
2158
  try {
1831
2159
  data = JSON.parse(readFileSync4(rulesFile, "utf-8"));
@@ -1921,7 +2249,7 @@ function _ltlRulesToRuntime() {
1921
2249
  let mc = {};
1922
2250
  for (const dir of cands) {
1923
2251
  const p = join3(dir, "mc_rules.json");
1924
- if (existsSync4(p)) {
2252
+ if (existsSync5(p)) {
1925
2253
  try {
1926
2254
  mc = JSON.parse(readFileSync4(p, "utf-8")) || {};
1927
2255
  } catch {
@@ -1950,17 +2278,27 @@ function normalizePatternForFingerprint(pattern) {
1950
2278
  return pattern.replace(/\\\//g, "/");
1951
2279
  }
1952
2280
  function _ruleFingerprint(rule) {
2281
+ const osTag = String(rule.os || "linux").toLowerCase();
1953
2282
  if (rule.intent_keywords && rule.intent_keywords.length || rule.dangerous_actions && rule.dangerous_actions.length) {
1954
2283
  return JSON.stringify([
2284
+ osTag,
1955
2285
  [...rule.intent_keywords || []].sort(),
1956
2286
  [...rule.dangerous_actions || []].sort()
1957
2287
  ]);
1958
2288
  }
1959
- if (rule.path) return JSON.stringify([normalizePatternForFingerprint(rule.path)]);
1960
- if (rule.pattern) return JSON.stringify([normalizePatternForFingerprint(rule.pattern)]);
1961
- if (rule.keyword) return JSON.stringify([rule.keyword, rule.lang || ""]);
1962
- if (rule.value) return JSON.stringify([rule.value]);
1963
- return JSON.stringify([rule.id || ""]);
2289
+ if (rule.path) return JSON.stringify([osTag, normalizePatternForFingerprint(rule.path)]);
2290
+ if (rule.pattern) return JSON.stringify([osTag, normalizePatternForFingerprint(rule.pattern)]);
2291
+ if (rule.keyword) return JSON.stringify([osTag, rule.keyword, rule.lang || ""]);
2292
+ if (rule.value) return JSON.stringify([osTag, rule.value]);
2293
+ return JSON.stringify([osTag, rule.id || ""]);
2294
+ }
2295
+ function _applyRuleEnabled(rule, enabled) {
2296
+ rule.enabled = enabled;
2297
+ if (enabled) {
2298
+ delete rule.user_disabled;
2299
+ } else {
2300
+ rule.user_disabled = true;
2301
+ }
1964
2302
  }
1965
2303
  function _mergeRuleCategories(userCfg, srcDefaults, layerKey) {
1966
2304
  const userLayer = userCfg[layerKey] || (userCfg[layerKey] = {});
@@ -1983,13 +2321,14 @@ function _mergeRuleCategories(userCfg, srcDefaults, layerKey) {
1983
2321
  srcFps.add(fp);
1984
2322
  if (userByFp.has(fp)) {
1985
2323
  const existing = userByFp.get(fp);
1986
- const userEnabled = existing.enabled;
2324
+ const userDisabled = existing.user_disabled === true;
1987
2325
  const userSkipL3 = existing.skip_l3_on_disabled;
1988
2326
  const userDenyMode = existing.deny_mode;
1989
2327
  const userSourceDeny = existing.source_deny_mode;
1990
2328
  Object.keys(existing).forEach((k) => delete existing[k]);
1991
2329
  Object.assign(existing, srcR);
1992
- existing.enabled = userEnabled;
2330
+ existing.enabled = !userDisabled;
2331
+ if (userDisabled) existing.user_disabled = true;
1993
2332
  if (userSkipL3 !== void 0) existing.skip_l3_on_disabled = userSkipL3;
1994
2333
  if (userDenyMode && userSourceDeny && userDenyMode !== userSourceDeny) {
1995
2334
  existing.deny_mode = userDenyMode;
@@ -2015,7 +2354,19 @@ function _mergeRuleCategories(userCfg, srcDefaults, layerKey) {
2015
2354
  }
2016
2355
  for (const catName of Object.keys(userLayer)) {
2017
2356
  if (catName === "user_custom") continue;
2018
- if (!(catName in srcLayer)) delete userLayer[catName];
2357
+ if (!(catName in srcLayer)) {
2358
+ const kept = (userLayer[catName].rules || []).filter((r) => r.builtin !== true);
2359
+ if (kept.length) {
2360
+ const uc = userLayer.user_custom || (userLayer.user_custom = {
2361
+ category_enabled: true,
2362
+ category_desc: "",
2363
+ rules: []
2364
+ });
2365
+ const existing = new Set((uc.rules || []).map((r) => r.id));
2366
+ uc.rules = [...uc.rules || [], ...kept.filter((r) => !existing.has(r.id))];
2367
+ }
2368
+ delete userLayer[catName];
2369
+ }
2019
2370
  }
2020
2371
  }
2021
2372
  function _mergeSkillCategories(userCfg, srcDefaults, layerKey) {
@@ -2063,10 +2414,11 @@ function _mergeAgentRules(userCfg, srcDefaults, partition = "agent_rules") {
2063
2414
  srcFps.add(fp);
2064
2415
  if (userByFp.has(fp)) {
2065
2416
  const existing = userByFp.get(fp);
2066
- const userEnabled = existing.enabled;
2417
+ const userDisabled = existing.user_disabled === true;
2067
2418
  Object.keys(existing).forEach((k) => delete existing[k]);
2068
2419
  Object.assign(existing, srcR);
2069
- existing.enabled = userEnabled;
2420
+ existing.enabled = !userDisabled;
2421
+ if (userDisabled) existing.user_disabled = true;
2070
2422
  } else {
2071
2423
  userRules.push({ ...srcR, enabled: true });
2072
2424
  }
@@ -2130,17 +2482,23 @@ function _migrateCategoriesAcrossLayers(cfg, srcLayer, dstLayer, catNames) {
2130
2482
  delete src[catName];
2131
2483
  }
2132
2484
  }
2133
- function getEnabledL1InjectionKeywords(runtime) {
2485
+ function getEnabledL1InjectionKeywords(runtime, osType) {
2134
2486
  const cat = runtime.L1_rules?.injection_detection || { category_enabled: true, rules: [] };
2135
- return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => ({
2487
+ let rules = filterEnabledRules(cat.rules || [], cat.category_enabled);
2488
+ rules = _mergeUserCustom(rules, runtime, "L1_rules", "keyword");
2489
+ if (osType) rules = _filterRulesByOs(rules, osType);
2490
+ return rules.map((r) => ({
2136
2491
  keyword: r.keyword || "",
2137
2492
  risk_level: r.risk_level || "",
2138
2493
  lang: r.lang || "en"
2139
2494
  }));
2140
2495
  }
2141
- function getEnabledL2IntentPatterns(runtime) {
2496
+ function getEnabledL2IntentPatterns(runtime, osType) {
2142
2497
  const cat = runtime.L2_rules?.intent_consistency || { category_enabled: true, rules: [] };
2143
- return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => ({
2498
+ let rules = filterEnabledRules(cat.rules || [], cat.category_enabled);
2499
+ rules = _mergeUserCustom(rules, runtime, "L2_rules", "intent_keywords");
2500
+ if (osType) rules = _filterRulesByOs(rules, osType);
2501
+ return rules.map((r) => ({
2144
2502
  intent_keywords: r.intent_keywords || [],
2145
2503
  dangerous_actions: r.dangerous_actions || [],
2146
2504
  reason: r.reason || ""
@@ -2148,11 +2506,13 @@ function getEnabledL2IntentPatterns(runtime) {
2148
2506
  }
2149
2507
  function getEnabledL2PasswordPatterns(runtime) {
2150
2508
  const cat = runtime.L2_rules?.password_modify_consent || { category_enabled: true, rules: [] };
2151
- return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => r.pattern || "");
2509
+ const rules = _mergeUserCustom(filterEnabledRules(cat.rules || [], cat.category_enabled), runtime, "L2_rules", "pattern");
2510
+ return rules.map((r) => r.pattern || "");
2152
2511
  }
2153
2512
  function getEnabledL2UserDeletionPatterns(runtime) {
2154
2513
  const cat = runtime.L2_rules?.user_deletion_consent || { category_enabled: true, rules: [] };
2155
- return filterEnabledRules(cat.rules || [], cat.category_enabled).map((r) => r.pattern || "");
2514
+ const rules = _mergeUserCustom(filterEnabledRules(cat.rules || [], cat.category_enabled), runtime, "L2_rules", "pattern");
2515
+ return rules.map((r) => r.pattern || "");
2156
2516
  }
2157
2517
  function isL2CategoryEnabled(runtime, categoryKey) {
2158
2518
  const cat = runtime.L2_rules?.[categoryKey];
@@ -2171,45 +2531,126 @@ function getDisabledSkillIds(runtime) {
2171
2531
  }
2172
2532
  return disabled;
2173
2533
  }
2174
- function getEnabledL1CommandPatternsOrDefault(runtime) {
2175
- const enabled = getEnabledL1CommandPatterns(runtime);
2176
- if (enabled.length) return enabled;
2177
- if (!hasRuntimeConfig()) return _l1CommandPatternsToRules();
2178
- return [];
2179
- }
2180
- function getEnabledL1InjectionKeywordsOrDefault(runtime) {
2181
- const enabled = getEnabledL1InjectionKeywords(runtime);
2182
- if (enabled.length) return enabled;
2183
- if (!hasRuntimeConfig()) return _l1InjectionKeywordsToRules();
2184
- return [];
2185
- }
2186
- function getEnabledL2SensitivePathsOrDefault(runtime) {
2187
- const enabled = getEnabledL2SensitivePaths(runtime);
2188
- if (enabled.length) return enabled;
2189
- if (!hasRuntimeConfig()) return _l2SensitivePathsToRules();
2190
- return [];
2191
- }
2192
- function getEnabledL2IntentPatternsOrDefault(runtime) {
2193
- const enabled = getEnabledL2IntentPatterns(runtime);
2194
- if (enabled.length) return enabled;
2195
- if (!hasRuntimeConfig()) return _l2IntentPatternsToRules();
2196
- return [];
2197
- }
2198
- function getEnabledL2PasswordPatternsOrDefault(runtime) {
2199
- const enabled = getEnabledL2PasswordPatterns(runtime);
2200
- if (enabled.length) return enabled;
2201
- if (!hasRuntimeConfig()) {
2202
- return PASSWORD_MODIFY_PATTERNS.map((p) => p.source);
2203
- }
2204
- return [];
2205
- }
2206
- function getEnabledL2UserDeletionPatternsOrDefault(runtime) {
2207
- const enabled = getEnabledL2UserDeletionPatterns(runtime);
2208
- if (enabled.length) return enabled;
2209
- if (!hasRuntimeConfig()) {
2210
- return USER_DELETION_PATTERNS.map((p) => p.source);
2211
- }
2212
- return [];
2534
+ function _filterDefaultsByOs(rules, osType) {
2535
+ const accepted = acceptedOsValues(osType);
2536
+ return rules.filter((r) => !r.os || accepted.includes(String(r.os).toLowerCase()));
2537
+ }
2538
+ function _hasRulesForOs(rules, osType) {
2539
+ return _filterRulesByOs(rules || [], osType || "linux").length > 0;
2540
+ }
2541
+ function getEnabledL1CommandPatternsOrDefault(runtime, osType) {
2542
+ const cat = runtime.L1_rules?.dangerous_commands;
2543
+ if (_hasRulesForOs(cat?.rules, osType)) {
2544
+ return getEnabledL1CommandPatterns(runtime, osType);
2545
+ }
2546
+ return _mergeUserCustom(_filterDefaultsByOs(_l1CommandPatternsToRules(), osType), runtime, "L1_rules", "pattern");
2547
+ }
2548
+ function getEnabledL1InjectionKeywordsOrDefault(runtime, osType) {
2549
+ const cat = runtime.L1_rules?.injection_detection;
2550
+ if (_hasRulesForOs(cat?.rules, osType)) {
2551
+ return getEnabledL1InjectionKeywords(runtime, osType);
2552
+ }
2553
+ return _mergeUserCustom(_l1InjectionKeywordsToRules(), runtime, "L1_rules", "keyword");
2554
+ }
2555
+ function getEnabledL2SensitivePathsOrDefault(runtime, osType) {
2556
+ const spCat = _sensitivePathCategoryOf(runtime);
2557
+ if (_hasRulesForOs(spCat?.rules, osType)) {
2558
+ return getEnabledL2SensitivePaths(runtime, osType);
2559
+ }
2560
+ return _mergeUserCustom(_filterDefaultsByOs(_l2SensitivePathsToRules(), osType), runtime, ["L1_rules", "L2_rules"], "path");
2561
+ }
2562
+ function getEnabledL2IntentPatternsOrDefault(runtime, osType) {
2563
+ const cat = runtime.L2_rules?.intent_consistency;
2564
+ if (_hasRulesForOs(cat?.rules, osType)) {
2565
+ return getEnabledL2IntentPatterns(runtime, osType);
2566
+ }
2567
+ return _mergeUserCustom(_filterDefaultsByOs(_l2IntentPatternsToRules(), osType), runtime, "L2_rules", "intent_keywords");
2568
+ }
2569
+ function getEnabledL2PasswordPatternsOrDefault(runtime, osType) {
2570
+ const cat = runtime.L2_rules?.password_modify_consent;
2571
+ if (_hasRulesForOs(cat?.rules, osType)) {
2572
+ return getEnabledL2PasswordPatterns(runtime);
2573
+ }
2574
+ return PASSWORD_MODIFY_PATTERNS.map((p) => p.source);
2575
+ }
2576
+ function getEnabledL2UserDeletionPatternsOrDefault(runtime, osType) {
2577
+ const cat = runtime.L2_rules?.user_deletion_consent;
2578
+ if (_hasRulesForOs(cat?.rules, osType)) {
2579
+ return getEnabledL2UserDeletionPatterns(runtime);
2580
+ }
2581
+ return USER_DELETION_PATTERNS.map((p) => p.source);
2582
+ }
2583
+ function _effective(osType, key, compute) {
2584
+ const runtime = loadRuntimeConfig();
2585
+ let bucket = _effectiveCache.get(runtime);
2586
+ if (!bucket) {
2587
+ bucket = /* @__PURE__ */ new Map();
2588
+ _effectiveCache.set(runtime, bucket);
2589
+ }
2590
+ const k = `${key}|${osType || ""}`;
2591
+ if (bucket.has(k)) return bucket.get(k);
2592
+ const value = compute(runtime);
2593
+ bucket.set(k, value);
2594
+ return value;
2595
+ }
2596
+ function effectiveDangerousPatterns(osType) {
2597
+ return _effective(osType, "danger", (rt) => {
2598
+ const rules = getEnabledL1CommandPatternsOrDefault(rt, osType);
2599
+ return rules.filter((r) => r.pattern).map((r) => ({
2600
+ pattern: new RegExp(String(r.pattern), "i"),
2601
+ severity: String(r.risk_level || "medium"),
2602
+ riskType: String(r.risk_type || ""),
2603
+ description: String(r.reason || ""),
2604
+ os: String(r.os || "linux").toLowerCase()
2605
+ }));
2606
+ });
2607
+ }
2608
+ function effectiveInjectionKeywords(osType) {
2609
+ return _effective(osType, "injection", (rt) => {
2610
+ const rules = getEnabledL1InjectionKeywordsOrDefault(rt, osType);
2611
+ return rules.map((r) => ({
2612
+ keyword: String(r.keyword || ""),
2613
+ riskLevel: String(r.risk_level || ""),
2614
+ lang: String(r.lang || "en")
2615
+ })).filter((r) => r.keyword);
2616
+ });
2617
+ }
2618
+ function effectiveSensitivePaths(osType) {
2619
+ return _effective(osType, "sensitive", (rt) => {
2620
+ const rules = getEnabledL2SensitivePathsOrDefault(rt, osType);
2621
+ return rules.filter((r) => r.path).map((r) => ({
2622
+ path: String(r.path),
2623
+ riskLevel: String(r.risk_level || "high"),
2624
+ desc: String(r.desc || ""),
2625
+ credential: r.credential === true ? true : void 0,
2626
+ readOnly: r.read_only === true ? true : void 0,
2627
+ dirDelOnly: r.dir_del_only === true ? true : void 0,
2628
+ denyMode: String(r.deny_mode || ""),
2629
+ os: String(r.os || "linux").toLowerCase()
2630
+ }));
2631
+ });
2632
+ }
2633
+ function effectiveIntentPatterns(osType) {
2634
+ return _effective(osType, "intent", (rt) => {
2635
+ const rules = getEnabledL2IntentPatternsOrDefault(rt, osType);
2636
+ return rules.map((r) => ({
2637
+ intentKeywords: r.intent_keywords || [],
2638
+ dangerousActions: r.dangerous_actions || [],
2639
+ reason: String(r.reason || ""),
2640
+ os: String(r.os || "linux").toLowerCase()
2641
+ }));
2642
+ });
2643
+ }
2644
+ function effectivePasswordPatterns(osType) {
2645
+ return _effective(osType, "password", (rt) => getEnabledL2PasswordPatternsOrDefault(rt, osType).map((p) => new RegExp(p, "i")));
2646
+ }
2647
+ function effectiveUserDeletionPatterns(osType) {
2648
+ return _effective(osType, "userdel", (rt) => getEnabledL2UserDeletionPatternsOrDefault(rt, osType).map((p) => new RegExp(p, "i")));
2649
+ }
2650
+ function _sensitivePathCategoryOf(runtime) {
2651
+ const l1 = runtime.L1_rules?.sensitive_path_access;
2652
+ if (l1 && (l1.rules || []).length > 0) return l1;
2653
+ return runtime.L2_rules?.sensitive_path_access;
2213
2654
  }
2214
2655
  function _validateSkillId(skillId) {
2215
2656
  if (!skillId || !SKILL_ID_RE.test(skillId)) {
@@ -2226,7 +2667,7 @@ function updateRuleEnabled(layer, category, ruleId, enabled) {
2226
2667
  const runtime = loadRuntimeConfig();
2227
2668
  for (const r of runtime[layer]?.[category]?.rules || []) {
2228
2669
  if (r.id === ruleId) {
2229
- r.enabled = enabled;
2670
+ _applyRuleEnabled(r, enabled);
2230
2671
  break;
2231
2672
  }
2232
2673
  }
@@ -2296,7 +2737,7 @@ function deleteCustomRule(layer, category, ruleId) {
2296
2737
  function addCustomSkill(skillId, category, keywords, content) {
2297
2738
  _validateSkillId(skillId);
2298
2739
  const runtime = loadRuntimeConfig();
2299
- const skillsDir = join3(RUNTIME_CONFIG_DIR, "agent_moss_skills");
2740
+ const skillsDir = join3(getRuntimeConfigDir(), "agent_moss_skills");
2300
2741
  try {
2301
2742
  mkdirSync(skillsDir, { recursive: true });
2302
2743
  } catch {
@@ -2316,9 +2757,9 @@ function deleteCustomSkill(skillId, category) {
2316
2757
  if (idx >= 0) {
2317
2758
  if (skills[idx].builtin) return null;
2318
2759
  skills.splice(idx, 1);
2319
- const skillPath = join3(RUNTIME_CONFIG_DIR, "agent_moss_skills", `${skillId}.md`);
2760
+ const skillPath = join3(getRuntimeConfigDir(), "agent_moss_skills", `${skillId}.md`);
2320
2761
  try {
2321
- if (existsSync4(skillPath)) unlinkSync(skillPath);
2762
+ if (existsSync5(skillPath)) unlinkSync(skillPath);
2322
2763
  } catch {
2323
2764
  }
2324
2765
  saveRuntimeConfig(runtime);
@@ -2397,7 +2838,7 @@ function updateAgentRuleEnabled(agentId, category, ruleFp, enabled) {
2397
2838
  const targetFp = _ruleFingerprint(ruleFp);
2398
2839
  for (const r of cat.rules || []) {
2399
2840
  if (_ruleFingerprint(r) === targetFp) {
2400
- r.enabled = enabled;
2841
+ _applyRuleEnabled(r, enabled);
2401
2842
  break;
2402
2843
  }
2403
2844
  }
@@ -2432,130 +2873,412 @@ function updateAgentCategoryEnabled(agentId, category, enabled) {
2432
2873
  saveRuntimeConfig(runtime);
2433
2874
  return runtime;
2434
2875
  }
2435
- function getScenarioRules(runtime) {
2436
- return runtime.scenario_rules || {};
2876
+ function getScenarioRules(runtime) {
2877
+ return runtime.scenario_rules || {};
2878
+ }
2879
+ function getEnabledScenarioExtensions(runtime, scenarioId) {
2880
+ const scenData = runtime.scenario_rules?.[scenarioId];
2881
+ if (!scenData) return null;
2882
+ const cmdPatterns = _agentCategoryEnabledRules(scenData.extra_command_patterns).filter((r) => r.pattern).map((r) => ({
2883
+ pattern: r.pattern,
2884
+ risk_level: r.risk_level || "",
2885
+ risk_type: r.risk_type || "",
2886
+ reason: r.reason || ""
2887
+ }));
2888
+ const injKeywords = _agentCategoryEnabledRules(scenData.extra_injection_keywords).filter((r) => r.keyword).map((r) => ({
2889
+ keyword: r.keyword,
2890
+ risk_level: r.risk_level || "",
2891
+ lang: r.lang || "en"
2892
+ }));
2893
+ const sensitivePaths = _agentCategoryEnabledRules(scenData.extra_sensitive_paths).filter((r) => r.path).map((r) => ({
2894
+ path: r.path,
2895
+ risk_level: r.risk_level || "",
2896
+ desc: r.desc || "",
2897
+ credential: r.credential,
2898
+ read_only: r.read_only,
2899
+ deny_mode: r.deny_mode
2900
+ }));
2901
+ return {
2902
+ agent_id: scenarioId,
2903
+ description: "",
2904
+ extra_command_patterns: cmdPatterns,
2905
+ extra_injection_keywords: injKeywords,
2906
+ extra_sensitive_paths: sensitivePaths,
2907
+ builtin_safe_skills: [],
2908
+ builtin_safe_action_types: [],
2909
+ extra_skills: []
2910
+ };
2911
+ }
2912
+ function updateScenarioRuleEnabled(scenarioId, category, ruleFp, enabled) {
2913
+ const runtime = loadRuntimeConfig();
2914
+ const cat = runtime.scenario_rules?.[scenarioId]?.[category];
2915
+ if (cat) {
2916
+ const targetFp = _ruleFingerprint(ruleFp);
2917
+ for (const r of cat.rules || []) {
2918
+ if (_ruleFingerprint(r) === targetFp) {
2919
+ _applyRuleEnabled(r, enabled);
2920
+ break;
2921
+ }
2922
+ }
2923
+ }
2924
+ saveRuntimeConfig(runtime);
2925
+ return runtime;
2926
+ }
2927
+ function addScenarioRule(scenarioId, category, rule) {
2928
+ const runtime = loadRuntimeConfig();
2929
+ if (!runtime.scenario_rules) runtime.scenario_rules = {};
2930
+ if (!runtime.scenario_rules[scenarioId]) runtime.scenario_rules[scenarioId] = {};
2931
+ const cats = runtime.scenario_rules[scenarioId];
2932
+ if (!cats[category]) cats[category] = { category_enabled: true, category_desc: "", rules: [] };
2933
+ (cats[category].rules || (cats[category].rules = [])).push({ ...rule, enabled: true, builtin: false });
2934
+ saveRuntimeConfig(runtime);
2935
+ return runtime;
2936
+ }
2937
+ function deleteScenarioRule(scenarioId, category, ruleFp) {
2938
+ const runtime = loadRuntimeConfig();
2939
+ const cat = runtime.scenario_rules?.[scenarioId]?.[category];
2940
+ if (cat) {
2941
+ const targetFp = _ruleFingerprint(ruleFp);
2942
+ cat.rules = (cat.rules || []).filter((r) => _ruleFingerprint(r) !== targetFp);
2943
+ }
2944
+ saveRuntimeConfig(runtime);
2945
+ return runtime;
2946
+ }
2947
+ function updateScenarioCategoryEnabled(scenarioId, category, enabled) {
2948
+ const runtime = loadRuntimeConfig();
2949
+ const cat = runtime.scenario_rules?.[scenarioId]?.[category];
2950
+ if (cat) cat.category_enabled = enabled;
2951
+ saveRuntimeConfig(runtime);
2952
+ return runtime;
2953
+ }
2954
+ var __dirname4, ENV_RUNTIME_CONFIG_PATH, RUNTIME_CONFIG_DIR, RUNTIME_CONFIG_PATH, LAYER_DISABLE_ENV, DEFAULT_LAYERS, DENY_MODE_LABELS, runtimeConfigCache, hasRuntimeConfigCache, _AGENTS_DIR_PROD, _AGENTS_DIR_DEV, _SCENARIOS_DIR_PROD, _SCENARIOS_DIR_DEV, _effectiveCache, SKILL_ID_RE;
2955
+ var init_runtime_config = __esm({
2956
+ "src/infra/runtime-config.ts"() {
2957
+ "use strict";
2958
+ init_esm_shims();
2959
+ init_paths();
2960
+ init_heuristic();
2961
+ init_logic_rules();
2962
+ init_skill_loader();
2963
+ init_runtime_config_bridge();
2964
+ __dirname4 = dirname3(fileURLToPath4(import.meta.url));
2965
+ ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
2966
+ RUNTIME_CONFIG_DIR = runtimeDir();
2967
+ RUNTIME_CONFIG_PATH = join3(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
2968
+ LAYER_DISABLE_ENV = {
2969
+ L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
2970
+ L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
2971
+ L3_llm_analysis: "AGENT_MOSS_DISABLE_LLM"
2972
+ };
2973
+ DEFAULT_LAYERS = {
2974
+ L1_heuristic: true,
2975
+ L2_logic_rules: true,
2976
+ L3_llm_analysis: true,
2977
+ agent_rules_enabled: true,
2978
+ scenario_rules_enabled: true
2979
+ };
2980
+ DENY_MODE_LABELS = {
2981
+ deny_write: "\u4EC5\u62E6\u622A\u5199\u5165",
2982
+ deny_read: "\u4EC5\u62E6\u622A\u8BFB\u53D6",
2983
+ deny_both: "\u8BFB\u5199\u5747\u62E6\u622A"
2984
+ };
2985
+ runtimeConfigCache = null;
2986
+ hasRuntimeConfigCache = /* @__PURE__ */ new Map();
2987
+ _AGENTS_DIR_PROD = join3(__dirname4, "agents");
2988
+ _AGENTS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "agents");
2989
+ _SCENARIOS_DIR_PROD = join3(__dirname4, "scenarios");
2990
+ _SCENARIOS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
2991
+ _effectiveCache = /* @__PURE__ */ new WeakMap();
2992
+ SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
2993
+ registerRuntimeConfigBridge({
2994
+ hasRuntimeConfig,
2995
+ loadRuntimeConfig,
2996
+ effectiveDangerousPatterns,
2997
+ effectiveInjectionKeywords,
2998
+ effectiveSensitivePaths,
2999
+ effectiveIntentPatterns,
3000
+ effectivePasswordPatterns,
3001
+ effectiveUserDeletionPatterns,
3002
+ disabledSkillIds: () => getDisabledSkillIds(loadRuntimeConfig())
3003
+ });
3004
+ }
3005
+ });
3006
+
3007
+ // src/config.ts
3008
+ var config_exports = {};
3009
+ __export(config_exports, {
3010
+ detectProvider: () => detectProvider,
3011
+ getCustomRules: () => getCustomRules,
3012
+ getLLMConfig: () => getLLMConfig,
3013
+ getLLMFailMode: () => getLLMFailMode,
3014
+ getProviderHeaders: () => getProviderHeaders,
3015
+ getRetryConfig: () => getRetryConfig,
3016
+ getTimeoutConfig: () => getTimeoutConfig,
3017
+ isBrainEnabled: () => isBrainEnabled,
3018
+ isFormalObserveMode: () => isFormalObserveMode,
3019
+ isHeuristicEnabled: () => isHeuristicEnabled,
3020
+ isLLMAvailable: () => isLLMAvailable,
3021
+ isLogicRulesEnabled: () => isLogicRulesEnabled,
3022
+ probeLLMConnection: () => probeLLMConnection,
3023
+ updateLLMConfig: () => updateLLMConfig
3024
+ });
3025
+ import fs2 from "fs";
3026
+ import path2 from "path";
3027
+ import { fileURLToPath as fileURLToPath5 } from "url";
3028
+ function loadConfigFile(filePath) {
3029
+ try {
3030
+ const raw = fs2.readFileSync(filePath, "utf-8");
3031
+ return JSON.parse(raw);
3032
+ } catch {
3033
+ return null;
3034
+ }
3035
+ }
3036
+ function resolveConfigPath() {
3037
+ const envPath = process.env.AGENT_MOSS_CONFIG_PATH;
3038
+ if (envPath) return envPath;
3039
+ const userConfig = path2.join(getConfigDir(), "config.json");
3040
+ if (fs2.existsSync(userConfig)) return userConfig;
3041
+ const defaultCandidates = [
3042
+ path2.join(__dirname, "config.json.example"),
3043
+ path2.join(__dirname, "..", "..", "agent_moss", "infra", "config.json.example")
3044
+ ];
3045
+ const defaultConfig = defaultCandidates.find((p) => fs2.existsSync(p));
3046
+ if (defaultConfig) {
3047
+ const configDir = path2.dirname(userConfig);
3048
+ fs2.mkdirSync(configDir, { recursive: true });
3049
+ const pkgConfig = loadConfigFile(defaultConfig);
3050
+ const safeConfig = pkgConfig ? { ...pkgConfig, llm: { ...pkgConfig.llm, api_key: "" } } : {};
3051
+ fs2.writeFileSync(userConfig, JSON.stringify(safeConfig, null, 4), "utf-8");
3052
+ console.log(`[AgentMoss] \u5DF2\u521B\u5EFA\u7528\u6237\u914D\u7F6E\u6587\u4EF6: ${userConfig}`);
3053
+ console.log(`[AgentMoss] \u8BF7\u7F16\u8F91\u8BE5\u6587\u4EF6\u914D\u7F6E\u4F60\u7684 LLM API Key`);
3054
+ }
3055
+ return userConfig;
3056
+ }
3057
+ function loadLLMConfigFromFile(configPath) {
3058
+ const data = loadConfigFile(configPath);
3059
+ if (!data?.llm) return {};
3060
+ const llm = data.llm;
3061
+ return {
3062
+ provider: llm.provider || "",
3063
+ baseUrl: llm.base_url || "",
3064
+ apiKey: llm.api_key || "",
3065
+ model: llm.model || "",
3066
+ temperature: llm.temperature ?? 0.1,
3067
+ maxTokens: llm.max_tokens ?? 4096,
3068
+ timeout: llm.timeout ?? 40
3069
+ };
3070
+ }
3071
+ function loadTimeoutRetryFromFile(configPath) {
3072
+ const data = loadConfigFile(configPath);
3073
+ const t = data?.timeout ?? {};
3074
+ const r = data?.retry ?? {};
3075
+ return {
3076
+ timeout: {
3077
+ totalTimeout: Number(t.total_timeout ?? 90),
3078
+ prompt1Timeout: Number(t.prompt1_timeout ?? 40),
3079
+ prompt2Timeout: Number(t.prompt2_timeout ?? 20),
3080
+ stepInterval: Number(t.step_interval ?? 0)
3081
+ },
3082
+ retry: {
3083
+ maxRetries: Number(r.max_retries ?? 2),
3084
+ retryInterval: Number(r.retry_interval ?? 2)
3085
+ }
3086
+ };
3087
+ }
3088
+ function getTimeoutConfig() {
3089
+ return timeoutConfig;
3090
+ }
3091
+ function getRetryConfig() {
3092
+ return retryConfig;
3093
+ }
3094
+ function wildcardToRegExp(pattern) {
3095
+ return pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, ".*");
3096
+ }
3097
+ function loadCustomRules() {
3098
+ const rules = [];
3099
+ try {
3100
+ const raw = process.env.AGENT_MOSS_CUSTOM_RULES;
3101
+ if (raw) {
3102
+ const parsed = JSON.parse(raw);
3103
+ if (Array.isArray(parsed)) {
3104
+ for (const r of parsed) {
3105
+ if (r && r.pattern && r.action) rules.push({ pattern: r.pattern, action: r.action, severity: r.severity || "high" });
3106
+ }
3107
+ }
3108
+ }
3109
+ } catch {
3110
+ }
3111
+ for (const rulesPath of USER_RULES_PATHS) {
3112
+ try {
3113
+ if (!fs2.existsSync(rulesPath)) continue;
3114
+ const data = JSON.parse(fs2.readFileSync(rulesPath, "utf-8"));
3115
+ const actions = Array.isArray(data?.sensitive_actions) ? data.sensitive_actions : [];
3116
+ for (const r of actions) {
3117
+ const p = r?.pattern;
3118
+ if (!p) continue;
3119
+ rules.push({ pattern: wildcardToRegExp(p), action: "Deny", severity: "high" });
3120
+ }
3121
+ break;
3122
+ } catch {
3123
+ }
3124
+ }
3125
+ return rules;
3126
+ }
3127
+ function getLLMConfig() {
3128
+ return llmConfig;
3129
+ }
3130
+ function updateLLMConfig(partial) {
3131
+ llmConfig = { ...llmConfig, ...partial };
3132
+ }
3133
+ function getCustomRules() {
3134
+ return customRules;
3135
+ }
3136
+ function isLLMAvailable() {
3137
+ if (process.env.AGENT_MOSS_DISABLE_LLM === "1") return false;
3138
+ if (!layerEnabledOrDefault("L3_llm_analysis")) return false;
3139
+ return llmConfig.apiKey.length > 0;
3140
+ }
3141
+ async function probeLLMConnection(timeoutMs = 3e4) {
3142
+ if (!isLLMAvailable()) return false;
3143
+ const OpenAI3 = (await import("openai")).default;
3144
+ const config = getLLMConfig();
3145
+ const provider = detectProvider(config.baseUrl, config.provider);
3146
+ const extraHeaders = getProviderHeaders(provider);
3147
+ const client = new OpenAI3({
3148
+ baseURL: config.baseUrl,
3149
+ apiKey: config.apiKey,
3150
+ timeout: timeoutMs,
3151
+ maxRetries: 0,
3152
+ defaultHeaders: Object.keys(extraHeaders).length > 0 ? extraHeaders : void 0
3153
+ });
3154
+ try {
3155
+ const resp = await client.chat.completions.create({
3156
+ model: config.model,
3157
+ messages: [{ role: "user", content: "ping" }],
3158
+ max_tokens: 1
3159
+ });
3160
+ return !!(resp && resp.choices && resp.choices[0]);
3161
+ } catch {
3162
+ return false;
3163
+ }
3164
+ }
3165
+ function getLLMFailMode() {
3166
+ const val = (process.env.AGENT_MOSS_LLM_FAIL_MODE || "fail_open").toLowerCase();
3167
+ return val === "fail_closed" ? "fail_closed" : "fail_open";
3168
+ }
3169
+ function detectProvider(baseUrl, providerHint = "") {
3170
+ if (providerHint) {
3171
+ const hint = providerHint.toLowerCase();
3172
+ if (["claude", "anthropic"].includes(hint)) return "anthropic";
3173
+ if (["google", "gemini"].includes(hint)) return "gemini";
3174
+ if (["zai-coding-plan", "zhipu-coding-plan", "zhipuai-coding-plan"].includes(hint)) return "zai-coding-plan";
3175
+ if (["zai", "z-ai", "z.ai", "zai-cn", "zai-china", "zhipu", "glm-cn", "bigmodel"].includes(hint)) return "zhipu";
3176
+ if (["xai", "xai-grok"].includes(hint)) return "xai";
3177
+ if (["minimax", "minimax-openai"].includes(hint)) return "minimax";
3178
+ if (["minimax-anthropic"].includes(hint)) return "minimax-anthropic";
3179
+ if (["other", "custom", "local"].includes(hint)) return "openai-compatible";
3180
+ return hint;
3181
+ }
3182
+ const url = baseUrl.toLowerCase();
3183
+ if (url.includes("openrouter.ai")) return "openrouter";
3184
+ if (url.includes("api.openai.com")) return "openai";
3185
+ if (url.includes("anthropic.com") || url.includes("api.anthropic.com")) return "anthropic";
3186
+ if (url.includes("generativelanguage.googleapis.com") || url.includes("gemini")) return "gemini";
3187
+ if (url.includes("api.deepseek.com")) return "deepseek";
3188
+ if (url.includes("api.groq.com")) return "groq";
3189
+ if (url.includes("api.mistral.ai")) return "mistral";
3190
+ if (url.includes("api.together.xyz")) return "together";
3191
+ if (url.includes("api.x.ai")) return "xai";
3192
+ if (url.includes("api.minimaxi.com")) return "minimax";
3193
+ if (url.includes("api-ai.gitcode.com")) return "gitcode";
3194
+ if (url.includes("api.z.ai")) return "zai-coding-plan";
3195
+ if (url.includes("open.bigmodel.cn") || url.includes("bigmodel")) return "zhipu";
3196
+ if (url.includes(":11434")) return "ollama";
3197
+ return "openai-compatible";
3198
+ }
3199
+ function getProviderHeaders(provider) {
3200
+ return PROVIDER_HEADERS[provider] || {};
2437
3201
  }
2438
- function getEnabledScenarioExtensions(runtime, scenarioId) {
2439
- const scenData = runtime.scenario_rules?.[scenarioId];
2440
- if (!scenData) return null;
2441
- const cmdPatterns = _agentCategoryEnabledRules(scenData.extra_command_patterns).filter((r) => r.pattern).map((r) => ({
2442
- pattern: r.pattern,
2443
- risk_level: r.risk_level || "",
2444
- risk_type: r.risk_type || "",
2445
- reason: r.reason || ""
2446
- }));
2447
- const injKeywords = _agentCategoryEnabledRules(scenData.extra_injection_keywords).filter((r) => r.keyword).map((r) => ({
2448
- keyword: r.keyword,
2449
- risk_level: r.risk_level || "",
2450
- lang: r.lang || "en"
2451
- }));
2452
- const sensitivePaths = _agentCategoryEnabledRules(scenData.extra_sensitive_paths).filter((r) => r.path).map((r) => ({
2453
- path: r.path,
2454
- risk_level: r.risk_level || "",
2455
- desc: r.desc || "",
2456
- credential: r.credential,
2457
- read_only: r.read_only,
2458
- deny_mode: r.deny_mode
2459
- }));
2460
- return {
2461
- agent_id: scenarioId,
2462
- description: "",
2463
- extra_command_patterns: cmdPatterns,
2464
- extra_injection_keywords: injKeywords,
2465
- extra_sensitive_paths: sensitivePaths,
2466
- builtin_safe_skills: [],
2467
- builtin_safe_action_types: [],
2468
- extra_skills: []
2469
- };
3202
+ function isHeuristicEnabled() {
3203
+ if (process.env.AGENT_MOSS_DISABLE_HEURISTIC === "1") return false;
3204
+ return layerEnabledOrDefault("L1_heuristic");
2470
3205
  }
2471
- function updateScenarioRuleEnabled(scenarioId, category, ruleFp, enabled) {
2472
- const runtime = loadRuntimeConfig();
2473
- const cat = runtime.scenario_rules?.[scenarioId]?.[category];
2474
- if (cat) {
2475
- const targetFp = _ruleFingerprint(ruleFp);
2476
- for (const r of cat.rules || []) {
2477
- if (_ruleFingerprint(r) === targetFp) {
2478
- r.enabled = enabled;
2479
- break;
2480
- }
2481
- }
3206
+ function layerEnabledOrDefault(key) {
3207
+ try {
3208
+ return isLayerEnabled(key);
3209
+ } catch {
3210
+ return true;
2482
3211
  }
2483
- saveRuntimeConfig(runtime);
2484
- return runtime;
2485
3212
  }
2486
- function addScenarioRule(scenarioId, category, rule) {
2487
- const runtime = loadRuntimeConfig();
2488
- if (!runtime.scenario_rules) runtime.scenario_rules = {};
2489
- if (!runtime.scenario_rules[scenarioId]) runtime.scenario_rules[scenarioId] = {};
2490
- const cats = runtime.scenario_rules[scenarioId];
2491
- if (!cats[category]) cats[category] = { category_enabled: true, category_desc: "", rules: [] };
2492
- (cats[category].rules || (cats[category].rules = [])).push({ ...rule, enabled: true, builtin: false });
2493
- saveRuntimeConfig(runtime);
2494
- return runtime;
3213
+ function isBrainEnabled() {
3214
+ return process.env.AGENT_MOSS_BRAIN_ENABLED === "1";
2495
3215
  }
2496
- function deleteScenarioRule(scenarioId, category, ruleFp) {
2497
- const runtime = loadRuntimeConfig();
2498
- const cat = runtime.scenario_rules?.[scenarioId]?.[category];
2499
- if (cat) {
2500
- const targetFp = _ruleFingerprint(ruleFp);
2501
- cat.rules = (cat.rules || []).filter((r) => _ruleFingerprint(r) !== targetFp);
2502
- }
2503
- saveRuntimeConfig(runtime);
2504
- return runtime;
3216
+ function isLogicRulesEnabled() {
3217
+ if (process.env.AGENT_MOSS_DISABLE_LOGIC_RULES === "1") return false;
3218
+ return layerEnabledOrDefault("L2_logic_rules");
2505
3219
  }
2506
- function updateScenarioCategoryEnabled(scenarioId, category, enabled) {
2507
- const runtime = loadRuntimeConfig();
2508
- const cat = runtime.scenario_rules?.[scenarioId]?.[category];
2509
- if (cat) cat.category_enabled = enabled;
2510
- saveRuntimeConfig(runtime);
2511
- return runtime;
3220
+ function isFormalObserveMode() {
3221
+ return process.env.AGENT_MOSS_FORMAL_OBSERVE === "1";
2512
3222
  }
2513
- var __dirname4, ENV_RUNTIME_CONFIG_PATH, RUNTIME_CONFIG_DIR, RUNTIME_CONFIG_PATH, LAYER_DISABLE_ENV, DEFAULT_LAYERS, DENY_MODE_LABELS, runtimeConfigCache, _AGENTS_DIR_PROD, _AGENTS_DIR_DEV, _SCENARIOS_DIR_PROD, _SCENARIOS_DIR_DEV, SKILL_ID_RE;
2514
- var init_runtime_config = __esm({
2515
- "src/infra/runtime-config.ts"() {
3223
+ var timeoutConfig, retryConfig, llmConfig, __configModuleDir, USER_RULES_PATHS, customRules, PROVIDER_HEADERS;
3224
+ var init_config = __esm({
3225
+ "src/config.ts"() {
2516
3226
  "use strict";
2517
3227
  init_esm_shims();
2518
3228
  init_paths();
2519
- init_heuristic();
2520
- init_logic_rules();
2521
- init_skill_loader();
2522
- __dirname4 = dirname3(fileURLToPath5(import.meta.url));
2523
- ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
2524
- RUNTIME_CONFIG_DIR = runtimeDir();
2525
- RUNTIME_CONFIG_PATH = join3(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
2526
- LAYER_DISABLE_ENV = {
2527
- L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
2528
- L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
2529
- L3_llm_analysis: "AGENT_MOSS_DISABLE_LLM"
2530
- };
2531
- DEFAULT_LAYERS = {
2532
- L1_heuristic: true,
2533
- L2_logic_rules: true,
2534
- L3_llm_analysis: true,
2535
- agent_rules_enabled: true,
2536
- scenario_rules_enabled: true
2537
- };
2538
- DENY_MODE_LABELS = {
2539
- deny_write: "\u4EC5\u62E6\u622A\u5199\u5165",
2540
- deny_read: "\u4EC5\u62E6\u622A\u8BFB\u53D6",
2541
- deny_both: "\u8BFB\u5199\u5747\u62E6\u622A"
3229
+ init_runtime_config();
3230
+ llmConfig = (() => {
3231
+ const configPath = resolveConfigPath();
3232
+ const fileConfig = fs2.existsSync(configPath) ? loadLLMConfigFromFile(configPath) : {};
3233
+ const tr = loadTimeoutRetryFromFile(configPath);
3234
+ timeoutConfig = tr.timeout;
3235
+ retryConfig = tr.retry;
3236
+ return {
3237
+ provider: process.env.AGENT_MOSS_LLM_PROVIDER || fileConfig.provider || "openai",
3238
+ baseUrl: process.env.AGENT_MOSS_LLM_BASE_URL || fileConfig.baseUrl || "https://api.openai.com/v1",
3239
+ apiKey: process.env.AGENT_MOSS_LLM_API_KEY || process.env.OPENAI_API_KEY || fileConfig.apiKey || "",
3240
+ model: process.env.AGENT_MOSS_LLM_MODEL || fileConfig.model || "gpt-4o",
3241
+ temperature: parseFloat(process.env.AGENT_MOSS_LLM_TEMPERATURE || String(fileConfig.temperature ?? "0.1")),
3242
+ maxTokens: parseInt(process.env.AGENT_MOSS_LLM_MAX_TOKENS || String(fileConfig.maxTokens ?? "4096"), 10),
3243
+ // 优先用 env,否则用 timeout 块的 prompt1Timeout(对齐 py)
3244
+ timeout: parseInt(process.env.AGENT_MOSS_LLM_TIMEOUT || String(fileConfig.timeout ?? String(tr.timeout.prompt1Timeout)), 10)
3245
+ };
3246
+ })();
3247
+ __configModuleDir = (() => {
3248
+ try {
3249
+ return path2.dirname(fileURLToPath5(import.meta.url));
3250
+ } catch {
3251
+ return typeof __dirname !== "undefined" ? __dirname : process.cwd();
3252
+ }
3253
+ })();
3254
+ USER_RULES_PATHS = [
3255
+ path2.join(__configModuleDir, "rules", "user_rules.json"),
3256
+ // 源在 ts/src/rules(一般不存在)
3257
+ path2.join(__configModuleDir, "..", "rules", "user_rules.json"),
3258
+ // dist: dist/engine/../rules = dist/rules ✅
3259
+ path2.join(__configModuleDir, "..", "..", "agent_moss", "rules", "user_rules.json")
3260
+ // dev src: ts/src/../../agent_moss
3261
+ ];
3262
+ customRules = loadCustomRules();
3263
+ PROVIDER_HEADERS = {
3264
+ "openrouter": {
3265
+ "HTTP-Referer": "shturl.cc/LlVD5pyWViHjM",
3266
+ "X-OpenRouter-Title": "AgentMoss"
3267
+ },
3268
+ "xai": {
3269
+ "HTTP-Referer": "shturl.cc/LlVD5pyWViHjM"
3270
+ }
2542
3271
  };
2543
- runtimeConfigCache = null;
2544
- _AGENTS_DIR_PROD = join3(__dirname4, "agents");
2545
- _AGENTS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "agents");
2546
- _SCENARIOS_DIR_PROD = join3(__dirname4, "scenarios");
2547
- _SCENARIOS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
2548
- SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
2549
3272
  }
2550
3273
  });
2551
3274
 
2552
3275
  // src/agents/index.ts
2553
- import { readFileSync as readFileSync5, existsSync as existsSync5 } from "fs";
2554
- import { resolve as resolve3, dirname as dirname4 } from "path";
3276
+ import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
3277
+ import { resolve as resolve4, dirname as dirname4 } from "path";
2555
3278
  import { fileURLToPath as fileURLToPath6 } from "url";
2556
3279
  function resolveAgentsDir() {
2557
- if (existsSync5(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
2558
- if (existsSync5(AGENTS_DIR_DEV_FALLBACK)) return AGENTS_DIR_DEV_FALLBACK;
3280
+ if (existsSync6(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
3281
+ if (existsSync6(AGENTS_DIR_DEV_FALLBACK)) return AGENTS_DIR_DEV_FALLBACK;
2559
3282
  return AGENTS_DIR_PROD;
2560
3283
  }
2561
3284
  function getAgentExtensions(agentId) {
@@ -2563,16 +3286,15 @@ function getAgentExtensions(agentId) {
2563
3286
  if (cache.has(agentId)) return cache.get(agentId) ?? null;
2564
3287
  let ext = null;
2565
3288
  try {
2566
- const { hasRuntimeConfig: hasRuntimeConfig2, loadRuntimeConfig: loadRuntimeConfig2, getEnabledAgentExtensions: getEnabledAgentExtensions2 } = (init_runtime_config(), __toCommonJS(runtime_config_exports));
2567
- if (hasRuntimeConfig2()) {
2568
- ext = getEnabledAgentExtensions2(loadRuntimeConfig2(), agentId);
3289
+ if (hasRuntimeConfig()) {
3290
+ ext = getEnabledAgentExtensions(loadRuntimeConfig(), agentId);
2569
3291
  }
2570
3292
  } catch {
2571
3293
  }
2572
3294
  if (ext === null) {
2573
3295
  const agentsDir = resolveAgentsDir();
2574
- const rulesFile = resolve3(agentsDir, agentId, "rules.json");
2575
- if (!existsSync5(rulesFile)) {
3296
+ const rulesFile = resolve4(agentsDir, agentId, "rules.json");
3297
+ if (!existsSync6(rulesFile)) {
2576
3298
  cache.set(agentId, null);
2577
3299
  return null;
2578
3300
  }
@@ -2591,7 +3313,7 @@ function getAgentExtensions(agentId) {
2591
3313
  }
2592
3314
  }
2593
3315
  cache.set(agentId, ext);
2594
- console.log(
3316
+ console.error(
2595
3317
  `[AgentMoss agents] \u52A0\u8F7D agent ${agentId} \u5B9A\u5236\u89C4\u5219: ${ext.extra_command_patterns?.length ?? 0} cmd patterns, ${ext.extra_injection_keywords?.length ?? 0} injection keywords, ${ext.extra_sensitive_paths?.length ?? 0} sensitive paths, ${ext.builtin_safe_skills?.length ?? 0} builtin safe skills, ${ext.builtin_safe_action_types?.length ?? 0} builtin safe action types`
2596
3318
  );
2597
3319
  return ext;
@@ -2616,21 +3338,22 @@ var init_agents = __esm({
2616
3338
  "src/agents/index.ts"() {
2617
3339
  "use strict";
2618
3340
  init_esm_shims();
3341
+ init_runtime_config();
2619
3342
  __filename4 = fileURLToPath6(import.meta.url);
2620
3343
  __dirname5 = dirname4(__filename4);
2621
- AGENTS_DIR_PROD = resolve3(__dirname5, "agents");
2622
- AGENTS_DIR_DEV_FALLBACK = resolve3(__dirname5, "../../../agent_moss/agents");
3344
+ AGENTS_DIR_PROD = resolve4(__dirname5, "agents");
3345
+ AGENTS_DIR_DEV_FALLBACK = resolve4(__dirname5, "../../../agent_moss/agents");
2623
3346
  cache = /* @__PURE__ */ new Map();
2624
3347
  }
2625
3348
  });
2626
3349
 
2627
3350
  // src/scenarios/index.ts
2628
- import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
2629
- import { resolve as resolve4, dirname as dirname5 } from "path";
3351
+ import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
3352
+ import { resolve as resolve5, dirname as dirname5 } from "path";
2630
3353
  import { fileURLToPath as fileURLToPath7 } from "url";
2631
3354
  function resolveScenariosDir() {
2632
- if (existsSync6(SCENARIOS_DIR_PROD)) return SCENARIOS_DIR_PROD;
2633
- if (existsSync6(SCENARIOS_DIR_DEV_FALLBACK)) return SCENARIOS_DIR_DEV_FALLBACK;
3355
+ if (existsSync7(SCENARIOS_DIR_PROD)) return SCENARIOS_DIR_PROD;
3356
+ if (existsSync7(SCENARIOS_DIR_DEV_FALLBACK)) return SCENARIOS_DIR_DEV_FALLBACK;
2634
3357
  return SCENARIOS_DIR_PROD;
2635
3358
  }
2636
3359
  function getScenarioExtensions(scenarioId) {
@@ -2638,16 +3361,15 @@ function getScenarioExtensions(scenarioId) {
2638
3361
  if (cache2.has(scenarioId)) return cache2.get(scenarioId) ?? null;
2639
3362
  let ext = null;
2640
3363
  try {
2641
- const { hasRuntimeConfig: hasRuntimeConfig2, loadRuntimeConfig: loadRuntimeConfig2, getEnabledScenarioExtensions: getEnabledScenarioExtensions2 } = (init_runtime_config(), __toCommonJS(runtime_config_exports));
2642
- if (hasRuntimeConfig2()) {
2643
- ext = getEnabledScenarioExtensions2(loadRuntimeConfig2(), scenarioId);
3364
+ if (hasRuntimeConfig()) {
3365
+ ext = getEnabledScenarioExtensions(loadRuntimeConfig(), scenarioId);
2644
3366
  }
2645
3367
  } catch {
2646
3368
  }
2647
3369
  if (ext === null) {
2648
3370
  const scenariosDir = resolveScenariosDir();
2649
- const rulesFile = resolve4(scenariosDir, scenarioId, "rules.json");
2650
- if (!existsSync6(rulesFile)) {
3371
+ const rulesFile = resolve5(scenariosDir, scenarioId, "rules.json");
3372
+ if (!existsSync7(rulesFile)) {
2651
3373
  cache2.set(scenarioId, null);
2652
3374
  return null;
2653
3375
  }
@@ -2677,7 +3399,7 @@ function getScenarioExtensions(scenarioId) {
2677
3399
  }
2678
3400
  }
2679
3401
  cache2.set(scenarioId, ext);
2680
- console.log(
3402
+ console.error(
2681
3403
  `[AgentMoss scenarios] \u52A0\u8F7D\u573A\u666F ${scenarioId} \u5B9A\u5236\u89C4\u5219: ${ext.extra_command_patterns?.length ?? 0} cmd patterns, ${ext.extra_injection_keywords?.length ?? 0} injection keywords, ${ext.extra_sensitive_paths?.length ?? 0} sensitive paths`
2682
3404
  );
2683
3405
  return ext;
@@ -2697,15 +3419,72 @@ var init_scenarios = __esm({
2697
3419
  "use strict";
2698
3420
  init_esm_shims();
2699
3421
  init_agents();
3422
+ init_runtime_config();
2700
3423
  __filename5 = fileURLToPath7(import.meta.url);
2701
3424
  __dirname6 = dirname5(__filename5);
2702
- SCENARIOS_DIR_PROD = resolve4(__dirname6, "scenarios");
2703
- SCENARIOS_DIR_DEV_FALLBACK = resolve4(__dirname6, "../../../agent_moss/scenarios");
3425
+ SCENARIOS_DIR_PROD = resolve5(__dirname6, "scenarios");
3426
+ SCENARIOS_DIR_DEV_FALLBACK = resolve5(__dirname6, "../../../agent_moss/scenarios");
2704
3427
  cache2 = /* @__PURE__ */ new Map();
2705
3428
  }
2706
3429
  });
2707
3430
 
2708
3431
  // src/engine/heuristic.ts
3432
+ function _effectiveDangerousPatterns(osType) {
3433
+ return runtimeConfigBridge.current?.effectiveDangerousPatterns(osType) ?? getDangerousPatternsForOs(osType);
3434
+ }
3435
+ function _loadDangerousPatterns(osType) {
3436
+ const accepted = acceptedOsValues(osType);
3437
+ const cmds = loadL1().dangerous_commands || [];
3438
+ return cmds.filter((c) => accepted.includes(String(c.os || "linux").toLowerCase())).map((c) => ({
3439
+ pattern: new RegExp(c.pattern, "i"),
3440
+ severity: c.risk_level,
3441
+ riskType: c.risk_type,
3442
+ description: c.reason,
3443
+ // 保留条目的原始平台归属(非查询用的 osType)—— seed 生成据此分组去重
3444
+ os: String(c.os || "linux").toLowerCase()
3445
+ }));
3446
+ }
3447
+ function getDangerousPatternsForOs(osType) {
3448
+ const t = String(osType || "").toLowerCase();
3449
+ if (t === "windows") return DANGEROUS_PATTERNS_WINDOWS;
3450
+ if (t === "macos") return DANGEROUS_PATTERNS_MACOS;
3451
+ if (t === "harmony" || t === "hmos" || t === "openharmony" || t === "hongmeng") return DANGEROUS_PATTERNS_HARMONY;
3452
+ return DANGEROUS_PATTERNS2;
3453
+ }
3454
+ function normalizeShellObfuscation(text) {
3455
+ let t = text.replace(/(?<=\w)(?:''|"")(?=\w)/g, "");
3456
+ t = t.replace(/\$\{IFS\}|\$IFS/gi, " ");
3457
+ return t;
3458
+ }
3459
+ function scanDangerousCommandPatterns(actionType, actionDetail, osType) {
3460
+ const scanDetail = normalizePathSegments(stripLiteralTextSegments(actionDetail));
3461
+ const scanCommands = String(osType || "").toLowerCase() === "windows" ? [
3462
+ `${actionType}: ${scanDetail}`,
3463
+ scanDetail,
3464
+ `${actionType}: ${scanDetail.replace(/\//g, "\\")}`,
3465
+ scanDetail.replace(/\//g, "\\")
3466
+ ] : [`${actionType}: ${scanDetail}`, scanDetail];
3467
+ const deob = normalizeShellObfuscation(scanDetail);
3468
+ if (deob !== scanDetail) scanCommands.push(`${actionType}: ${deob}`, deob);
3469
+ let top = null;
3470
+ for (const rule of _effectiveDangerousPatterns(osType)) {
3471
+ if (scanCommands.some((c) => rule.pattern.test(c))) {
3472
+ if (!top || severityRank(rule.severity) > severityRank(top.sev)) {
3473
+ top = { sev: rule.severity, type: rule.riskType, desc: rule.description };
3474
+ }
3475
+ }
3476
+ }
3477
+ if (!top) return null;
3478
+ return {
3479
+ allowed: false,
3480
+ reason: `\u68C0\u6D4B\u5230\u9AD8\u5371\u64CD\u4F5C: ${top.desc}`,
3481
+ riskLevel: top.sev,
3482
+ riskType: top.type,
3483
+ confidence: 95,
3484
+ source: "heuristic",
3485
+ violatedLayers: ["L1.1"]
3486
+ };
3487
+ }
2709
3488
  function runHeuristicCheck(input) {
2710
3489
  const command = `${input.aNext.actionType}: ${input.aNext.actionDetail}`;
2711
3490
  const matched = [];
@@ -2732,11 +3511,23 @@ function runHeuristicCheck(input) {
2732
3511
  }
2733
3512
  }
2734
3513
  const scanDetail = normalizePathSegments(stripLiteralTextSegments(input.aNext.actionDetail));
2735
- const scanCommand = `${input.aNext.actionType}: ${scanDetail}`;
3514
+ const scanCommands = String(input.osType || "").toLowerCase() === "windows" ? [
3515
+ `${input.aNext.actionType}: ${scanDetail}`,
3516
+ scanDetail,
3517
+ `${input.aNext.actionType}: ${scanDetail.replace(/\//g, "\\")}`,
3518
+ scanDetail.replace(/\//g, "\\")
3519
+ ] : [`${input.aNext.actionType}: ${scanDetail}`, scanDetail];
3520
+ const deobfuscated = normalizeShellObfuscation(scanDetail);
3521
+ if (deobfuscated !== scanDetail) {
3522
+ scanCommands.push(
3523
+ `${input.aNext.actionType}: ${deobfuscated}`,
3524
+ deobfuscated
3525
+ );
3526
+ }
2736
3527
  let topRiskType = "";
2737
3528
  const exemptKeygen = isSshKeygenGenerate(input.aNext.actionDetail);
2738
- for (const rule of DANGEROUS_PATTERNS) {
2739
- if (rule.pattern.test(scanCommand)) {
3529
+ for (const rule of _effectiveDangerousPatterns(input.osType)) {
3530
+ if (scanCommands.some((c) => rule.pattern.test(c))) {
2740
3531
  if (exemptKeygen && rule.riskType === "file_access") continue;
2741
3532
  matched.push(rule.description);
2742
3533
  if (severityRank(rule.severity) > severityRank(highestSeverity)) {
@@ -2754,7 +3545,7 @@ function runHeuristicCheck(input) {
2754
3545
  for (const rule of agentExt.extra_command_patterns) {
2755
3546
  try {
2756
3547
  const re = new RegExp(rule.pattern, "i");
2757
- if (re.test(scanCommand)) {
3548
+ if (scanCommands.some((c) => re.test(c))) {
2758
3549
  matched.push(`agent \u4E13\u5C5E\u89C4\u5219: ${rule.reason}`);
2759
3550
  if (severityRank(rule.risk_level) > severityRank(highestSeverity)) {
2760
3551
  highestSeverity = rule.risk_level;
@@ -2766,7 +3557,7 @@ function runHeuristicCheck(input) {
2766
3557
  }
2767
3558
  }
2768
3559
  const injectionText = isSearchActionType(input.aNext.actionType) ? `${input.aNext.actionType} ${input.reason || ""} ${input.promptSession || ""}`.toLowerCase() : `${input.aNext.actionType} ${input.aNext.actionDetail} ${input.reason || ""} ${input.promptSession || ""}`.toLowerCase();
2769
- const injectionHits = checkInjectionKeywords(injectionText, agentExt?.extra_injection_keywords);
3560
+ const injectionHits = checkInjectionKeywords(injectionText, agentExt?.extra_injection_keywords, input.osType);
2770
3561
  if (injectionHits.length > 0) {
2771
3562
  const topHit = injectionHits.reduce((a, b) => severityRank(a.riskLevel) >= severityRank(b.riskLevel) ? a : b);
2772
3563
  matched.push(...injectionHits.map((h) => `\u6CE8\u5165\u5173\u952E\u8BCD [${h.lang}]: ${h.keyword}`));
@@ -2779,7 +3570,7 @@ function runHeuristicCheck(input) {
2779
3570
  const detailL = input.aNext.actionDetail.toLowerCase();
2780
3571
  const isExcludedType = SENSITIVE_EXCLUDED_PREFIXES.some((p) => actionTypeL.startsWith(p));
2781
3572
  const dangerousResult = checkDangerousPatterns(actionTypeL, detailL);
2782
- const pathResult = isExcludedType ? null : checkSensitivePath(detailL, input.cwd || "/", agentExt);
3573
+ const pathResult = isExcludedType ? null : checkSensitivePath(detailL, input.cwd || "/", agentExt, input.osType);
2783
3574
  const homeResult = checkHomeDeletion(actionTypeL, detailL);
2784
3575
  const mergeResults = [dangerousResult, pathResult, homeResult].filter(Boolean);
2785
3576
  if (mergeResults.length > 0) {
@@ -2815,9 +3606,10 @@ function runHeuristicCheck(input) {
2815
3606
  if (lateralResult) return lateralResult;
2816
3607
  return null;
2817
3608
  }
2818
- function checkInjectionKeywords(text, extraKeywords) {
3609
+ function checkInjectionKeywords(text, extraKeywords, osType) {
2819
3610
  const hits = [];
2820
- for (const entry of INJECTION_KEYWORDS) {
3611
+ const baseKeywords = runtimeConfigBridge.current?.effectiveInjectionKeywords(osType) ?? INJECTION_KEYWORDS;
3612
+ for (const entry of baseKeywords) {
2821
3613
  if (text.includes(entry.keyword.toLowerCase())) {
2822
3614
  hits.push(entry);
2823
3615
  }
@@ -2877,7 +3669,7 @@ function buildPathContext(actionDetail) {
2877
3669
  if (!PATH_CONTEXT_TRIGGER_RE.test(actionDetail)) return "";
2878
3670
  const stripped = stripLiteralTextSegments(actionDetail);
2879
3671
  const lines = [];
2880
- for (const seg of splitCommandSegments(stripped)) {
3672
+ for (const seg of splitCommandSegments2(stripped)) {
2881
3673
  for (const [raw, lex] of resolvePathTokens(seg)) {
2882
3674
  lines.push(`- \u547D\u4EE4\u6BB5 \`${seg.trim().slice(0, 60)}\`\uFF1A\`${raw}\` \u8BCD\u6CD5\u89E3\u6790\u4E3A \`${lex}\``);
2883
3675
  }
@@ -2887,12 +3679,12 @@ function buildPathContext(actionDetail) {
2887
3679
  }
2888
3680
  function stripLiteralTextSegments(actionDetail) {
2889
3681
  const writeScriptExec = />\s*[/~]\S*\.(sh|py|pl|rb)\b/.test(actionDetail) && /(?:&&|\|\||;|\n|\|)\s*(?:bash|sh|zsh|dash|python|perl|ruby|node)/.test(actionDetail);
2890
- const pipeToInterpreter = /\|\s*(?:bash|sh|zsh|dash|python|perl|ruby|node)\b/.test(actionDetail);
3682
+ const pipeToInterpreter = /\|\s*(?:bash|sh|zsh|dash|python|perl|ruby|node|crontab|at)\b/.test(actionDetail);
2891
3683
  if (writeScriptExec || pipeToInterpreter) {
2892
3684
  return actionDetail;
2893
3685
  }
2894
3686
  const resultParts = [];
2895
- for (const seg of splitCommandSegments(actionDetail)) {
3687
+ for (const seg of splitCommandSegments2(actionDetail)) {
2896
3688
  const fw = (seg.trim().split(/\s+/)[0] || "").toLowerCase();
2897
3689
  if (fw === "echo" || fw === "printf") {
2898
3690
  const segUnquotedRedirect = seg.replace(
@@ -2912,7 +3704,7 @@ function stripHeredocContent(actionDetail) {
2912
3704
  "<<heredoc>>\n"
2913
3705
  );
2914
3706
  }
2915
- function normalizePathSegments(text) {
3707
+ function normalizePathSegments(text, foldQuotes = false) {
2916
3708
  const protTokens = [];
2917
3709
  let t = text.replace(/\b(https?|ftp|file):\/\//gi, (m) => {
2918
3710
  const scheme = m.slice(0, m.indexOf(":"));
@@ -2920,10 +3712,12 @@ function normalizePathSegments(text) {
2920
3712
  return `${scheme}__PROT${protTokens.length - 1}__`;
2921
3713
  });
2922
3714
  const quoteTokens = [];
2923
- t = t.replace(/'[^']*'|"[^"]*"/g, (m) => {
2924
- quoteTokens.push(m);
2925
- return `__Q${quoteTokens.length - 1}__`;
2926
- });
3715
+ if (!foldQuotes) {
3716
+ t = t.replace(/'[^']*'|"[^"]*"/g, (m) => {
3717
+ quoteTokens.push(m);
3718
+ return `__Q${quoteTokens.length - 1}__`;
3719
+ });
3720
+ }
2927
3721
  t = t.replace(/\/{2,}/g, "/");
2928
3722
  t = t.replace(/\/\.\//g, "/");
2929
3723
  for (let i = 0; i < quoteTokens.length; i++) {
@@ -2934,7 +3728,12 @@ function normalizePathSegments(text) {
2934
3728
  }
2935
3729
  return t;
2936
3730
  }
2937
- function splitCommandSegments(actionDetail) {
3731
+ function pathScanVariants(actionDetailLower) {
3732
+ const base = stripLiteralTextSegments(actionDetailLower);
3733
+ const deep = normalizePathSegments(base, true);
3734
+ return deep === base ? [base] : [base, deep];
3735
+ }
3736
+ function splitCommandSegments2(actionDetail) {
2938
3737
  const segments = [];
2939
3738
  let cur = "";
2940
3739
  let quote = null;
@@ -2983,11 +3782,15 @@ function isSearchActionType(actionType) {
2983
3782
  const at = actionType.toLowerCase();
2984
3783
  return SEARCH_ACTION_PREFIXES.some((p) => at.startsWith(p)) || ["websearch", "web_search", "search", "query"].includes(at);
2985
3784
  }
2986
- function checkSensitivePath(command, cwd, agentExt) {
3785
+ function checkSensitivePath(command, cwd, agentExt, osType) {
2987
3786
  const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
2988
- const isWriteOpFlag = isWriteOperation("", command);
3787
+ const { root: rootPaths, home: homePaths, credential: credentialPaths } = getSensitivePathsForOs(osType);
3788
+ const isWriteOpFlag = isWriteOperation("", command, void 0, osType);
2989
3789
  const keygenGenerate = isSshKeygenGenerate(command);
2990
- const pathScanText = normalizePathSegments(stripLiteralTextSegments(command));
3790
+ const pathScanTexts = pathScanVariants(command);
3791
+ if (String(osType || "").toLowerCase() === "windows") {
3792
+ pathScanTexts.push(...pathScanTexts.map((t) => t.replace(/\//g, "\\")));
3793
+ }
2991
3794
  const pathMatches = (text, path5) => {
2992
3795
  const parts = path5.split(/(\(\?!.*?\))/);
2993
3796
  const escaped = parts.map((part) => {
@@ -2997,8 +3800,9 @@ function checkSensitivePath(command, cwd, agentExt) {
2997
3800
  const re = path5.startsWith(".") || path5.startsWith("/") ? new RegExp(`(?:^|[\\s/\\\\'"])${escaped}\\b`, "i") : new RegExp(`\\b${escaped}\\b`, "i");
2998
3801
  return re.test(text);
2999
3802
  };
3000
- for (const sp of ROOT_SENSITIVE_PATHS) {
3001
- if (pathMatches(pathScanText, sp.path.toLowerCase())) {
3803
+ const pathMatchesAny = (path5) => pathScanTexts.some((t) => pathMatches(t, path5));
3804
+ for (const sp of rootPaths) {
3805
+ if (pathMatchesAny(sp.path.toLowerCase())) {
3002
3806
  if (keygenGenerate) continue;
3003
3807
  if (sp.path === "/dev/random") {
3004
3808
  if (isWriteOpFlag) {
@@ -3014,9 +3818,9 @@ function checkSensitivePath(command, cwd, agentExt) {
3014
3818
  violatedLayers: ["L1.1"]
3015
3819
  };
3016
3820
  }
3017
- if (!isWriteOpFlag && !sp.credential) {
3018
- continue;
3019
- }
3821
+ const denyMode = sp.denyMode || (sp.credential ? "deny_both" : sp.readOnly ? "deny_read" : "deny_write");
3822
+ if (denyMode === "deny_write" && !isWriteOpFlag) continue;
3823
+ if (denyMode === "deny_read" && isWriteOpFlag) continue;
3020
3824
  return {
3021
3825
  allowed: false,
3022
3826
  reason: `\u5C1D\u8BD5\u8BBF\u95EE\u7CFB\u7EDF\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
@@ -3029,17 +3833,19 @@ function checkSensitivePath(command, cwd, agentExt) {
3029
3833
  };
3030
3834
  }
3031
3835
  }
3032
- for (const sp of HOME_SENSITIVE_PATHS) {
3836
+ for (const sp of homePaths) {
3033
3837
  const expanded = `${expandedCwd}/${sp.path}`;
3034
3838
  let pPath = sp.path;
3035
3839
  if (!sp.path.endsWith(".pub")) pPath = `${sp.path}(?!\\.pub)`;
3036
3840
  let dirPrefixMatch = null;
3037
3841
  if (sp.dirDelOnly) {
3038
3842
  const dirRe = (t) => new RegExp(`(?:^|[\\s/\\\\'"])${t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:/|[\\s'"\\\\]|$)`, "i");
3039
- dirPrefixMatch = dirRe(sp.path).test(pathScanText) || dirRe(expanded.toLowerCase()).test(pathScanText);
3843
+ dirPrefixMatch = pathScanTexts.some(
3844
+ (t) => dirRe(sp.path).test(t) || dirRe(expanded.toLowerCase()).test(t)
3845
+ );
3040
3846
  }
3041
- const pathReMatch = sp.dirDelOnly ? !!dirPrefixMatch : pathMatches(pathScanText, pPath);
3042
- const expandedReMatch = sp.dirDelOnly ? false : pathMatches(pathScanText, expanded + (sp.path.endsWith(".pub") ? "" : "(?!\\.pub)"));
3847
+ const pathReMatch = sp.dirDelOnly ? !!dirPrefixMatch : pathMatchesAny(pPath);
3848
+ const expandedReMatch = sp.dirDelOnly ? false : pathMatchesAny(expanded + (sp.path.endsWith(".pub") ? "" : "(?!\\.pub)"));
3043
3849
  if (pathReMatch || expandedReMatch) {
3044
3850
  if (keygenGenerate) continue;
3045
3851
  if (sp.dirDelOnly) {
@@ -3069,9 +3875,9 @@ function checkSensitivePath(command, cwd, agentExt) {
3069
3875
  }
3070
3876
  continue;
3071
3877
  }
3072
- if (!isWriteOpFlag && !sp.credential) {
3073
- continue;
3074
- }
3878
+ const denyMode = sp.denyMode || (sp.credential ? "deny_both" : sp.readOnly ? "deny_read" : "deny_write");
3879
+ if (denyMode === "deny_write" && !isWriteOpFlag) continue;
3880
+ if (denyMode === "deny_read" && isWriteOpFlag) continue;
3075
3881
  return {
3076
3882
  allowed: false,
3077
3883
  reason: `\u5C1D\u8BD5\u8BBF\u95EE\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
@@ -3108,7 +3914,7 @@ function checkSensitivePath(command, cwd, agentExt) {
3108
3914
  }
3109
3915
  }
3110
3916
  }
3111
- for (const cp of CREDENTIAL_PATHS) {
3917
+ for (const cp of credentialPaths) {
3112
3918
  const escaped = cp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3113
3919
  const re = cp.path.startsWith(".") ? new RegExp(`(?:^|[\\s/\\\\'"])${escaped}\\b`) : new RegExp(`\\b${escaped}\\b`);
3114
3920
  if (re.test(command)) {
@@ -3228,7 +4034,7 @@ function isDeleteActionForDir(command, dirPath) {
3228
4034
  }
3229
4035
  for (const seg of low.split(/&&|\|\||;|\n/)) {
3230
4036
  const fw = (seg.trim().split(/\s+/)[0] || "").toLowerCase();
3231
- if ((fw === "rm" || fw === "unlink" || fw === "rmdir") && low.includes(dpath)) {
4037
+ if ((fw === "rm" || fw === "unlink" || fw === "rmdir") && seg.includes(dpath)) {
3232
4038
  return true;
3233
4039
  }
3234
4040
  }
@@ -3411,7 +4217,7 @@ function checkLateralMovement(actionDetail) {
3411
4217
  }
3412
4218
  return null;
3413
4219
  }
3414
- var INJECTION_KEYWORDS, DANGEROUS_PATTERNS, PATH_CONTEXT_TRIGGER_RE, HOME_PREFIX_RE, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES, HOME_TARGET_RE, HOME_ASSIGN_RE;
4220
+ var INJECTION_KEYWORDS, DANGEROUS_PATTERNS2, DANGEROUS_PATTERNS_WINDOWS, DANGEROUS_PATTERNS_MACOS, DANGEROUS_PATTERNS_HARMONY, PATH_CONTEXT_TRIGGER_RE, HOME_PREFIX_RE, SENSITIVE_EXCLUDED_PREFIXES, SEARCH_ACTION_PREFIXES, HOME_TARGET_RE, HOME_ASSIGN_RE;
3415
4221
  var init_heuristic = __esm({
3416
4222
  "src/engine/heuristic.ts"() {
3417
4223
  "use strict";
@@ -3420,6 +4226,7 @@ var init_heuristic = __esm({
3420
4226
  init_agents();
3421
4227
  init_scenarios();
3422
4228
  init_runtime_config();
4229
+ init_runtime_config_bridge();
3423
4230
  init_rules_loader();
3424
4231
  init_logic_rules();
3425
4232
  INJECTION_KEYWORDS = (() => {
@@ -3430,15 +4237,10 @@ var init_heuristic = __esm({
3430
4237
  lang: k.lang || "en"
3431
4238
  }));
3432
4239
  })();
3433
- DANGEROUS_PATTERNS = (() => {
3434
- const cmds = loadL1().dangerous_commands || [];
3435
- return cmds.map((c) => ({
3436
- pattern: new RegExp(c.pattern, "i"),
3437
- severity: c.risk_level,
3438
- riskType: c.risk_type,
3439
- description: c.reason
3440
- }));
3441
- })();
4240
+ DANGEROUS_PATTERNS2 = _loadDangerousPatterns("linux");
4241
+ DANGEROUS_PATTERNS_WINDOWS = _loadDangerousPatterns("windows");
4242
+ DANGEROUS_PATTERNS_MACOS = _loadDangerousPatterns("macos");
4243
+ DANGEROUS_PATTERNS_HARMONY = _loadDangerousPatterns("harmony");
3442
4244
  PATH_CONTEXT_TRIGGER_RE = /(?:^|[;&|]\s*)(?:rm|rmdir|unlink|ln|mv|cp|dd|truncate|shred|chmod|chown)\b/i;
3443
4245
  HOME_PREFIX_RE = /(?:~|~(?=\/)|\$HOME\b|\$\{HOME\}|\/home\/[a-z0-9_.-]+)/gi;
3444
4246
  SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
@@ -3458,14 +4260,14 @@ __export(token_stats_exports, {
3458
4260
  recordTokenUsage: () => recordTokenUsage,
3459
4261
  resetTokenStats: () => resetTokenStats
3460
4262
  });
3461
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
4263
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync2, existsSync as existsSync11, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
3462
4264
  function getStatsPath() {
3463
4265
  const envPath = process.env[ENV_STATS_PATH];
3464
4266
  if (envPath) return envPath;
3465
4267
  return getTokenStatsPath();
3466
4268
  }
3467
4269
  function loadAllRecords(statsPath) {
3468
- if (!existsSync10(statsPath)) return [];
4270
+ if (!existsSync11(statsPath)) return [];
3469
4271
  try {
3470
4272
  const data = JSON.parse(readFileSync11(statsPath, "utf-8"));
3471
4273
  if (Array.isArray(data)) return data;
@@ -3478,7 +4280,7 @@ function loadAllRecords(statsPath) {
3478
4280
  function saveRecords(statsPath, records) {
3479
4281
  try {
3480
4282
  const dir = statsPath.substring(0, statsPath.lastIndexOf("/"));
3481
- if (dir && !existsSync10(dir)) mkdirSync2(dir, { recursive: true });
4283
+ if (dir && !existsSync11(dir)) mkdirSync2(dir, { recursive: true });
3482
4284
  writeFileSync2(statsPath, JSON.stringify(records, null, 2), "utf-8");
3483
4285
  } catch (e) {
3484
4286
  }
@@ -3605,7 +4407,7 @@ function getTokenTrend(days = 0, startDate, endDate) {
3605
4407
  }
3606
4408
  function resetTokenStats() {
3607
4409
  const statsPath = getStatsPath();
3608
- if (existsSync10(statsPath)) {
4410
+ if (existsSync11(statsPath)) {
3609
4411
  try {
3610
4412
  unlinkSync2(statsPath);
3611
4413
  } catch {
@@ -3627,6 +4429,8 @@ var init_token_stats = __esm({
3627
4429
  init_esm_shims();
3628
4430
  init_heuristic();
3629
4431
  init_logic_rules();
4432
+ init_platform_traits();
4433
+ init_shell_split();
3630
4434
  init_inline_analyzer();
3631
4435
 
3632
4436
  // src/engine/llm-analyzer.ts
@@ -3636,26 +4440,26 @@ init_skill_loader();
3636
4440
 
3637
4441
  // src/engine/template-loader.ts
3638
4442
  init_esm_shims();
3639
- import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
3640
- import { resolve as resolve5, dirname as dirname6 } from "path";
4443
+ init_asset_dir();
4444
+ import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
4445
+ import { resolve as resolve6, dirname as dirname6 } from "path";
3641
4446
  import { fileURLToPath as fileURLToPath8 } from "url";
3642
4447
  var __filename6 = fileURLToPath8(import.meta.url);
3643
4448
  var __dirname7 = dirname6(__filename6);
3644
- var TEMPLATES_DIR = resolve5(__dirname7, "../templates");
3645
- var TEMPLATES_DIR_FALLBACK = resolve5(__dirname7, "../../../agent_moss/templates");
3646
4449
  var TEMPLATE_FILES = ["prompt1_template.txt", "prompt2_template.txt", "security_judge_template.txt"];
3647
4450
  var cachedTemplates = null;
3648
4451
  function loadTemplates() {
3649
4452
  if (cachedTemplates) return cachedTemplates;
3650
4453
  cachedTemplates = {};
3651
- const dir = existsSync7(TEMPLATES_DIR) ? TEMPLATES_DIR : TEMPLATES_DIR_FALLBACK;
3652
- if (!existsSync7(dir)) {
3653
- console.warn("[AgentMoss] Templates directory not found:", TEMPLATES_DIR, "and fallback:", TEMPLATES_DIR_FALLBACK);
4454
+ const dir = resolveAssetDir(__dirname7, "templates");
4455
+ if (!dir) {
4456
+ const [primary, fallback] = assetDirCandidates(__dirname7, "templates");
4457
+ console.warn("[AgentMoss] Templates directory not found:", primary, "and fallback:", fallback);
3654
4458
  return cachedTemplates;
3655
4459
  }
3656
4460
  for (const file of TEMPLATE_FILES) {
3657
- const filePath = resolve5(dir, file);
3658
- if (existsSync7(filePath)) {
4461
+ const filePath = resolve6(dir, file);
4462
+ if (existsSync8(filePath)) {
3659
4463
  const key = file.replace(".txt", "").replace("_template", "");
3660
4464
  cachedTemplates[key] = readFileSync7(filePath, "utf-8");
3661
4465
  }
@@ -3667,7 +4471,7 @@ function loadTemplates() {
3667
4471
  // src/engine/script-analyzer.ts
3668
4472
  init_esm_shims();
3669
4473
  import { readFileSync as readFileSync8 } from "fs";
3670
- import { resolve as resolve6, join as join4 } from "path";
4474
+ import { resolve as resolve7, join as join4 } from "path";
3671
4475
  var SUSPICIOUS_PATTERNS = [
3672
4476
  // Network exfiltration (curl/wget POST in scripts is almost never legitimate)
3673
4477
  { name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
@@ -3800,7 +4604,7 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
3800
4604
  }
3801
4605
  function readScriptContent(scriptPath, maxLines = 500) {
3802
4606
  try {
3803
- const expandedPath = scriptPath.startsWith("~") ? join4(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve6(scriptPath);
4607
+ const expandedPath = scriptPath === "~" || scriptPath.startsWith("~/") ? join4(process.env.HOME || "/home/user", scriptPath.slice(1)) : resolve7(scriptPath);
3804
4608
  const raw = readFileSync8(expandedPath, "utf-8");
3805
4609
  const lines = raw.split("\n").slice(0, maxLines);
3806
4610
  return { content: lines.join("\n"), lineCount: lines.length, error: "" };
@@ -3854,11 +4658,12 @@ ${result.scriptContent}
3854
4658
  // src/engine/script-content-analyzer.ts
3855
4659
  init_esm_shims();
3856
4660
  init_heuristic();
4661
+ init_config();
3857
4662
  init_logic_rules();
3858
4663
  init_inline_analyzer();
3859
- import { statSync as statSync3, existsSync as existsSync9 } from "fs";
3860
- import { resolve as resolve8, join as join6, isAbsolute as isAbsolute2 } from "path";
3861
- import { homedir as homedir4 } from "os";
4664
+ import { statSync as statSync3, existsSync as existsSync10 } from "fs";
4665
+ import { resolve as resolve9, join as join6, isAbsolute as isAbsolute2 } from "path";
4666
+ import { homedir as homedir3 } from "os";
3862
4667
 
3863
4668
  // src/engine/script-engine/tracker.ts
3864
4669
  init_esm_shims();
@@ -4685,7 +5490,7 @@ function tokenize(content) {
4685
5490
  }
4686
5491
  return stmts;
4687
5492
  }
4688
- var EXPR_SPLIT_RE = /(\$\{?\d+\}?|\$\{?[A-Za-z_][A-Za-z0-9_]*\}?)/;
5493
+ var EXPR_SPLIT_RE = /(\$\{[^}]*\}|\$[A-Za-z_][A-Za-z0-9_]*|\$\d+)/;
4689
5494
  function evalExpr2(text, env) {
4690
5495
  const isSingleQuoted = text.length >= 2 && text[0] === "'" && text[text.length - 1] === "'";
4691
5496
  if (text.length >= 2 && (text[0] === '"' || text[0] === "'") && text[text.length - 1] === text[0]) {
@@ -4696,9 +5501,20 @@ function evalExpr2(text, env) {
4696
5501
  const result = [];
4697
5502
  for (const p of parts) {
4698
5503
  if (!p) continue;
4699
- const brace = p.match(/^\$\{([^}]+)\}$/);
5504
+ const brace = p.match(/^\$\{([^}]*)\}$/);
4700
5505
  if (brace) {
4701
5506
  const inner = brace[1];
5507
+ const dm = inner.match(/^([A-Za-z_][A-Za-z0-9_]*|\d+):?-(.*)$/s);
5508
+ if (dm) {
5509
+ const key = /^\d+$/.test(dm[1]) ? `$${dm[1]}` : dm[1];
5510
+ const bound = env.get(key);
5511
+ if (bound && !(bound.kind === "concrete" && !bound.value)) {
5512
+ result.push(bound);
5513
+ } else {
5514
+ result.push(evalExpr2(dm[2], env));
5515
+ }
5516
+ continue;
5517
+ }
4702
5518
  if (/^\d+$/.test(inner)) {
4703
5519
  const v = env.get(`$${inner}`);
4704
5520
  result.push(v || svalSym(`$${inner}`));
@@ -5151,7 +5967,15 @@ var ShellBackend = class {
5151
5967
  // src/engine/script-engine/tracker.ts
5152
5968
  var DEFAULT_MAX_DEPTH = 14;
5153
5969
  var DEFAULT_MAX_NODES = 64;
5154
- var DEFAULT_MAX_FILE_SIZE = 2 * 1024 * 1024;
5970
+ function maxFileSizeFromEnv() {
5971
+ const raw = (process.env.AGENT_MOSS_SCRIPT_MAX_FILE_SIZE || "").trim();
5972
+ if (raw) {
5973
+ const v = Number(raw);
5974
+ if (Number.isFinite(v) && v > 0) return v;
5975
+ }
5976
+ return 2 * 1024 * 1024;
5977
+ }
5978
+ var DEFAULT_MAX_FILE_SIZE = maxFileSizeFromEnv();
5155
5979
  var DEFAULT_MAX_TOTAL_SIZE = 8 * 1024 * 1024;
5156
5980
  var BACKENDS = {
5157
5981
  ".py": new PythonBackend()
@@ -5199,6 +6023,7 @@ function emptyNode(pathStr, depth) {
5199
6023
  depth,
5200
6024
  childPaths: [],
5201
6025
  error: "",
6026
+ unanalyzable: false,
5202
6027
  l1Hit: false,
5203
6028
  l1RiskLevel: "",
5204
6029
  l1RiskType: "",
@@ -5280,6 +6105,7 @@ var RecursiveTracker = class {
5280
6105
  node2.error = error;
5281
6106
  node2.fileSize = fileSize;
5282
6107
  node2.lineCount = lineCount;
6108
+ node2.unanalyzable = error.startsWith("\u811A\u672C\u6587\u4EF6\u8FC7\u5927");
5283
6109
  this.result.nodes.push(node2);
5284
6110
  this.visiting.delete(pathStr);
5285
6111
  return;
@@ -5511,12 +6337,12 @@ function extractInlineScriptPath(actionDetail) {
5511
6337
  const subShellRe = /subprocess\.(?:run|call|Popen|check_output|check_call)\s*\(\s*(['"])(.*?)\1[^)]*?shell\s*=\s*true/gi;
5512
6338
  while ((m = subShellRe.exec(code)) !== null) candidates.push(m[2]);
5513
6339
  for (const cand of candidates) {
5514
- const tokens = cand.includes(",") ? cand.split(/\s*,\s*/).map((t) => t.trim().replace(/^['"]|['"]$/g, "")) : cand.split(/\s+/);
6340
+ const tokens = cand.includes(",") ? cand.split(/\s*,\s*/).map((t) => t.trim().replace(/^['"]|['"]$/g, "")) : shlexSplit(cand);
5515
6341
  for (const tok of tokens) {
5516
6342
  if (looksLikeScriptPath(tok)) {
5517
6343
  try {
5518
6344
  const resolved = resolvePath2(tok.replace(/^['"]|['"]$/g, ""));
5519
- if (existsSync9(resolved)) {
6345
+ if (existsSync10(resolved)) {
5520
6346
  const stat = statSync3(resolved);
5521
6347
  if (stat.isFile()) return resolved;
5522
6348
  }
@@ -5530,7 +6356,7 @@ function extractInlineScriptPath(actionDetail) {
5530
6356
  const p = m[1];
5531
6357
  try {
5532
6358
  const resolved = resolvePath2(p);
5533
- if (existsSync9(resolved)) {
6359
+ if (existsSync10(resolved)) {
5534
6360
  const stat = statSync3(resolved);
5535
6361
  if (stat.isFile()) return resolved;
5536
6362
  }
@@ -5546,7 +6372,7 @@ function extractInlineScriptPath(actionDetail) {
5546
6372
  let resolved;
5547
6373
  try {
5548
6374
  resolved = resolvePath2(p);
5549
- if (existsSync9(resolved)) {
6375
+ if (existsSync10(resolved)) {
5550
6376
  const stat = statSync3(resolved);
5551
6377
  if (stat.isFile()) return resolved;
5552
6378
  }
@@ -5555,12 +6381,41 @@ function extractInlineScriptPath(actionDetail) {
5555
6381
  }
5556
6382
  return null;
5557
6383
  }
6384
+ function shlexSplit(s) {
6385
+ const out = [];
6386
+ let cur = "";
6387
+ let quote = null;
6388
+ let quoted = false;
6389
+ for (let i = 0; i < s.length; i++) {
6390
+ const ch = s[i];
6391
+ if (quote) {
6392
+ if (ch === quote) quote = null;
6393
+ else if (ch === "\\" && quote === '"' && i + 1 < s.length) cur += s[++i];
6394
+ else cur += ch;
6395
+ } else if (ch === '"' || ch === "'") {
6396
+ quote = ch;
6397
+ quoted = true;
6398
+ } else if (ch === "\\" && i + 1 < s.length) {
6399
+ cur += s[++i];
6400
+ } else if (/\s/.test(ch)) {
6401
+ if (cur || quoted) {
6402
+ out.push(cur);
6403
+ cur = "";
6404
+ quoted = false;
6405
+ }
6406
+ } else {
6407
+ cur += ch;
6408
+ }
6409
+ }
6410
+ if (cur || quoted) out.push(cur);
6411
+ return out;
6412
+ }
5558
6413
  function resolvePath2(p) {
5559
6414
  let expanded = p;
5560
6415
  if (expanded.startsWith("~/") || expanded === "~") {
5561
- expanded = join6(homedir4(), expanded.slice(1));
6416
+ expanded = join6(homedir3(), expanded.slice(1));
5562
6417
  }
5563
- return resolve8(expanded);
6418
+ return resolve9(expanded);
5564
6419
  }
5565
6420
  function scanScriptChainWithL1(chain, input) {
5566
6421
  for (const node of chain.nodes) {
@@ -5569,7 +6424,7 @@ function scanScriptChainWithL1(chain, input) {
5569
6424
  ...input,
5570
6425
  aNext: { actionType: "bash", actionDetail: node.preprocessedContent }
5571
6426
  };
5572
- const l1 = runHeuristicCheck(virtualInput);
6427
+ const l1 = scanDangerousCommandPatterns("bash", node.preprocessedContent, input.osType);
5573
6428
  if (l1 && !l1.allowed) {
5574
6429
  node.l1Hit = true;
5575
6430
  node.l1RiskLevel = l1.riskLevel;
@@ -5592,18 +6447,25 @@ function scanScriptChainWithL2(chain, input) {
5592
6447
  ...input,
5593
6448
  aNext: { actionType: "bash", actionDetail: node.preprocessedContent }
5594
6449
  };
5595
- const l2 = runLogicCheck(virtualInput);
5596
- if (l2 && !l2.allowed) {
5597
- node.l2Hit = true;
5598
- node.l2ViolatedRule = `script_logic:${node.scriptPath}`;
5599
- node.l2RiskLevel = l2.riskLevel;
5600
- node.l2RiskType = l2.riskType;
5601
- node.l2Reason = `\u811A\u672C ${node.scriptPath} (\u6DF1\u5EA6 ${node.depth}) \u4E2D: ${l2.reason}`;
5602
- if (l2.riskLevel === "critical") {
5603
- chain.hasCritical = true;
5604
- chain.firstHitNode = node;
5605
- break;
5606
- }
6450
+ let l2 = null;
6451
+ if (isLogicRulesEnabled()) {
6452
+ l2 = checkSensitivePath(
6453
+ node.preprocessedContent.toLowerCase(),
6454
+ input.cwd || "/",
6455
+ null,
6456
+ input.osType
6457
+ ) || checkIntentConsistency(virtualInput);
6458
+ }
6459
+ if (!l2 || l2.allowed) continue;
6460
+ node.l2Hit = true;
6461
+ node.l2ViolatedRule = `script_logic:${node.scriptPath}`;
6462
+ node.l2RiskLevel = l2.riskLevel;
6463
+ node.l2RiskType = l2.riskType;
6464
+ node.l2Reason = `\u811A\u672C ${node.scriptPath} (\u6DF1\u5EA6 ${node.depth}) \u4E2D: ${l2.reason}`;
6465
+ if (l2.riskLevel === "critical") {
6466
+ chain.hasCritical = true;
6467
+ chain.firstHitNode = node;
6468
+ break;
5607
6469
  }
5608
6470
  }
5609
6471
  if (!chain.hasHigh) {
@@ -5642,13 +6504,16 @@ function analyzeScriptContent2(input) {
5642
6504
  resolvedPath = join6(input.cwd || process.cwd(), resolvedPath);
5643
6505
  }
5644
6506
  const l1Adapter = (content) => {
5645
- const virtualInput = {
5646
- ...input,
5647
- aNext: { actionType: "bash", actionDetail: content }
5648
- };
5649
- const j = runHeuristicCheck(virtualInput);
6507
+ const j = scanDangerousCommandPatterns("bash", content, input.osType);
5650
6508
  if (j && !j.allowed) {
5651
- return { hit: true, riskLevel: j.riskLevel, riskType: j.riskType, reason: j.reason, matchedPatterns: [] };
6509
+ return {
6510
+ hit: true,
6511
+ riskLevel: j.riskLevel,
6512
+ riskType: j.riskType,
6513
+ reason: j.reason,
6514
+ matchedPatterns: []
6515
+ // SecurityJudgment 不带模式列表(见 types.ts);留空即可
6516
+ };
5652
6517
  }
5653
6518
  return { hit: false, riskLevel: "", riskType: "", reason: "", matchedPatterns: [] };
5654
6519
  };
@@ -5936,7 +6801,7 @@ async function analyzeWithLLM(prompt, config) {
5936
6801
  }
5937
6802
  if (attempt < maxRetries) {
5938
6803
  console.error(`[AgentMoss] LLM call failed (attempt ${attempt + 1}/${maxRetries + 1}), retrying in ${retryCfg.retryInterval}s:`, err.message);
5939
- await new Promise((resolve9) => setTimeout(resolve9, retryCfg.retryInterval * 1e3));
6804
+ await new Promise((resolve10) => setTimeout(resolve10, retryCfg.retryInterval * 1e3));
5940
6805
  } else {
5941
6806
  clearTimeout(totalTimeoutTimer);
5942
6807
  console.error("[AgentMoss] LLM call failed after all retries:", err.message);
@@ -5959,10 +6824,10 @@ init_esm_shims();
5959
6824
 
5960
6825
  // src/brain/rule-store.ts
5961
6826
  init_esm_shims();
5962
- import { existsSync as existsSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
6827
+ import { existsSync as existsSync12, readFileSync as readFileSync12, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
5963
6828
  import { join as join7 } from "path";
5964
- import { homedir as homedir5 } from "os";
5965
- var DEFAULT_RULES_PATH = join7(homedir5(), ".agentmoss", "brain", "rules.json");
6829
+ import { homedir as homedir4 } from "os";
6830
+ var DEFAULT_RULES_PATH = join7(homedir4(), ".agentmoss", "brain", "rules.json");
5966
6831
  var ENV_RULES_PATH = "AGENT_MOSS_BRAIN_RULES_PATH";
5967
6832
  var STATUS_DRAFT = "draft";
5968
6833
  var STATUS_APPROVED = "approved";
@@ -5991,7 +6856,7 @@ var BrainRuleStore = class {
5991
6856
  this.data = this.load();
5992
6857
  }
5993
6858
  load() {
5994
- if (!existsSync11(this.path)) {
6859
+ if (!existsSync12(this.path)) {
5995
6860
  return { version: 1, rules: [], pattern_candidates: [] };
5996
6861
  }
5997
6862
  try {
@@ -7429,63 +8294,15 @@ var READONLY_SENSITIVE_TOOLS = /* @__PURE__ */ new Set([
7429
8294
  "filemgr-readfile",
7430
8295
  "filemgr-grepfiles"
7431
8296
  ]);
7432
- var FULLY_SAFE_BASH_PATTERNS = [
7433
- /^ls(?:\s|$)/i,
7434
- /^dir(?:\s|$)/i,
7435
- /^pwd(?:\s|$)/i,
7436
- /^which\s+/i,
7437
- /^whereis\s+/i,
7438
- /^realpath\s+/i,
7439
- /^basename\s+/i,
7440
- /^dirname\s+/i,
7441
- /^file\s+/i,
7442
- /^stat\s+/i,
7443
- /^du(?:\s|$)/i,
7444
- /^echo\s+/i,
7445
- /^printf\s+/i,
7446
- /^type\s+/i,
7447
- /^command\s+/i,
7448
- // 系统信息查询(只读,不涉及文件/网络/权限)
7449
- /^whoami(?:\s|$)/i,
7450
- /^id(?:\s|$)/i,
7451
- /^hostname(?:\s|$)/i,
7452
- /^uname(?:\s|$)/i,
7453
- /^date(?:\s|$)/i,
7454
- /^env(?:\s|$)/i,
7455
- /^printenv(?:\s|$)/i,
7456
- /^tty(?:\s|$)/i,
7457
- /^arch(?:\s|$)/i,
7458
- /^uptime(?:\s|$)/i,
7459
- /^groups(?:\s|$)/i,
7460
- /^logname(?:\s|$)/i
7461
- ];
7462
- var READONLY_SENSITIVE_BASH_PATTERNS = [
7463
- /^cat\s+/i,
7464
- /^head(?:\s|$)/i,
7465
- /^tail(?:\s|$)/i,
7466
- /^less(?:\s|$)/i,
7467
- /^more(?:\s|$)/i,
7468
- /^wc(?:\s|$)/i,
7469
- /^grep(?:\s|$)/i,
7470
- /^find\s+/i,
7471
- /^ag(?:\s|$)/i,
7472
- /^rg(?:\s|$)/i,
7473
- /^awk\s+/i,
7474
- /^sed\s+/i,
7475
- /^cut\s+/i,
7476
- /^sort(?:\s|$)/i,
7477
- /^uniq(?:\s|$)/i
7478
- ];
7479
- function extractFirstCommand(command) {
7480
- let main = command.split("|")[0].trim();
7481
- for (const sep of ["&&", "||"]) {
7482
- const idx = main.indexOf(sep);
7483
- if (idx !== -1) {
7484
- main = main.substring(0, idx).trim();
7485
- break;
7486
- }
7487
- }
7488
- return main;
8297
+ function whitelistTier(command, fullySafe, readonlySensitive) {
8298
+ const segments = splitCommandSegments(command);
8299
+ if (segments.length === 0) return "";
8300
+ if (segments.some((seg) => hasCommandSubstitution(seg))) return "";
8301
+ const hitFull = (seg) => matchesAny(seg, fullySafe);
8302
+ const hitAny = (seg) => hitFull(seg) || matchesAny(seg, readonlySensitive);
8303
+ if (segments.every(hitFull)) return "full";
8304
+ if (segments.every(hitAny)) return "readonly";
8305
+ return "";
7489
8306
  }
7490
8307
  function extractRawCommand(detail) {
7491
8308
  const m = detail.match(/command=([^,]+)/);
@@ -7520,7 +8337,7 @@ async function runAnalysis(input) {
7520
8337
  if (heuristicResult && !heuristicResult.allowed) {
7521
8338
  const skipInlineFileAccess = (isInlineScriptCmd || isLiteralTextCmd) && heuristicResult.riskType === "file_access";
7522
8339
  if (!skipInlineFileAccess) {
7523
- const shouldDenyL1 = heuristicResult.riskLevel === "high" || heuristicResult.riskLevel === "critical" || heuristicResult.riskLevel === "medium" && heuristicResult.riskType === "file_access";
8340
+ const shouldDenyL1 = heuristicResult.riskLevel === "high" || heuristicResult.riskLevel === "critical" || heuristicResult.riskLevel === "medium" && heuristicResult.riskType === "file_access" || heuristicResult.riskLevel === "medium" && heuristicResult.riskType === "resource_exhaustion";
7524
8341
  if (shouldDenyL1) {
7525
8342
  return heuristicResult;
7526
8343
  }
@@ -7530,6 +8347,19 @@ async function runAnalysis(input) {
7530
8347
  if (input.aNext.actionType.toLowerCase() === "bash") {
7531
8348
  scriptChain = analyzeScriptContent2(input);
7532
8349
  if (scriptChain.nodes.length > 0) {
8350
+ for (const node of scriptChain.nodes) {
8351
+ if (node.unanalyzable) {
8352
+ return {
8353
+ allowed: false,
8354
+ reason: `\u811A\u672C\u5B58\u5728\u4F46\u5185\u5BB9\u65E0\u6CD5\u9759\u6001\u5206\u6790\uFF08${node.error}\uFF09\u2014\u2014 \u65E0\u6CD5\u786E\u8BA4\u5176\u5B89\u5168\u6027\uFF0C\u6309\u4E0D\u53EF\u4FE1\u5904\u7406\u3002\u6587\u4EF6: ${node.scriptPath}\u3002\u5982\u786E\u5C5E\u6B63\u5E38\u5927\u811A\u672C\uFF0C\u53EF\u8BBE AGENT_MOSS_SCRIPT_MAX_FILE_SIZE \u653E\u5BBD\u4E0A\u9650\u3002`,
8355
+ riskLevel: "high",
8356
+ riskType: "script_execution",
8357
+ confidence: 80,
8358
+ source: "heuristic_script_scan",
8359
+ violatedLayers: ["L1.2"]
8360
+ };
8361
+ }
8362
+ }
7533
8363
  for (const node of scriptChain.nodes) {
7534
8364
  if (node.l1Hit && (node.l1RiskLevel === "high" || node.l1RiskLevel === "critical")) {
7535
8365
  return {
@@ -7617,11 +8447,15 @@ async function runAnalysis(input) {
7617
8447
  let skipLLM = READONLY_SENSITIVE_TOOLS.has(actionType);
7618
8448
  if (actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash") {
7619
8449
  const firstCmd = extractFirstCommand(lowerDetail);
7620
- if (matchesAny(firstCmd, FULLY_SAFE_BASH_PATTERNS)) {
7621
- const fullCmdHit = DANGEROUS_PATTERNS.some(
8450
+ const { fullySafeCommands, readonlySensitiveCommands } = getPlatformTraits(input.osType);
8451
+ const tier = whitelistTier(lowerDetail, fullySafeCommands, readonlySensitiveCommands);
8452
+ if (tier === "full") {
8453
+ const fastPassScanText = normalizePathSegments(stripLiteralTextSegments(lowerDetail));
8454
+ const osPatterns = getDangerousPatternsForOs(input.osType);
8455
+ const fullCmdHit = osPatterns.some(
7622
8456
  (p) => p.severity === "critical" || p.severity === "high"
7623
- ) && DANGEROUS_PATTERNS.some(
7624
- (p) => (p.severity === "critical" || p.severity === "high") && p.pattern.test(lowerDetail)
8457
+ ) && osPatterns.some(
8458
+ (p) => (p.severity === "critical" || p.severity === "high") && p.pattern.test(fastPassScanText)
7625
8459
  );
7626
8460
  const hasRedirectWrite = isWriteOperation(firstCmd, lowerDetail);
7627
8461
  if (!fullCmdHit && !hasRedirectWrite) {
@@ -7636,7 +8470,7 @@ async function runAnalysis(input) {
7636
8470
  };
7637
8471
  }
7638
8472
  }
7639
- if (matchesAny(firstCmd, READONLY_SENSITIVE_BASH_PATTERNS)) {
8473
+ if (tier !== "") {
7640
8474
  skipLLM = true;
7641
8475
  }
7642
8476
  }
@@ -7645,8 +8479,8 @@ async function runAnalysis(input) {
7645
8479
  const effectiveResult = logicResult && !logicResult.allowed ? logicResult : l1FileAccessHit ? heuristicResult : null;
7646
8480
  if (effectiveResult) {
7647
8481
  const isCredentialPath = !!effectiveResult.isCredential;
7648
- const skipInlineFileAccess = (isInlineScriptCmd || isLiteralTextCmd) && effectiveResult.riskType === "file_access";
7649
- if (skipInlineFileAccess) {
8482
+ const skipInlineFileAccess = (isInlineScriptCmd || isLiteralTextCmd) && l1FileAccessHit;
8483
+ if (isInlineScriptCmd || isLiteralTextCmd) {
7650
8484
  const inlineJudgment = analyzeInlineScript(input.aNext.actionDetail);
7651
8485
  if (inlineJudgment.actionable) {
7652
8486
  if (!inlineJudgment.allowed) {
@@ -7845,7 +8679,11 @@ async function runAnalysis(input) {
7845
8679
  export {
7846
8680
  TRUSTED_SKILLS_BASE,
7847
8681
  _resetBrainForTest,
7848
- runAnalysis
8682
+ extractFirstCommand,
8683
+ matchesAny,
8684
+ runAnalysis,
8685
+ splitCommandSegments,
8686
+ whitelistTier
7849
8687
  };
7850
8688
  //! 顺序即 fail-closed 短路优先级:verify 逐个执行,首个返回违规的性质成为
7851
8689
  //! first_violation,后续不再评估。调整顺序影响判定归属,须与 Python
@@ -7860,4 +8698,3 @@ export {
7860
8698
  //! P6 mc-ltl-rules LTL 模型检测
7861
8699
  //! P7 low-integrity-command-exec 低完整性命令执行
7862
8700
  //! P8 sensitive-data-aggregation 敏感数据聚合外传
7863
- //# sourceMappingURL=coordinator.js.map