@kenhkl/agent-moss 0.15.7 → 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.
- package/dist/cli.js +1773 -1660
- package/dist/engine/coordinator.d.ts +12 -1
- package/dist/engine/coordinator.js +1663 -850
- package/dist/rules/l1_rules.json +1306 -4
- package/dist/rules/l2_rules.json +285 -243
- package/dist/scenarios/bank/rules.json +5 -5
- package/dist/server.d.ts +14 -1
- package/dist/server.js +3717 -3434
- package/package.json +1 -1
|
@@ -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/
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
260
|
-
|
|
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
|
|
336
|
-
|
|
337
|
-
return
|
|
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
|
|
343
|
-
var
|
|
344
|
-
"src/
|
|
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
|
-
|
|
348
|
-
|
|
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
|
-
|
|
396
|
-
if (!
|
|
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, "");
|
|
@@ -483,14 +219,15 @@ function analyzeInlineScript(command) {
|
|
|
483
219
|
riskType: "file_access"
|
|
484
220
|
};
|
|
485
221
|
}
|
|
486
|
-
var INLINE_CODE_RE, ECHO_STRING_RE,
|
|
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;
|
|
487
223
|
var init_inline_analyzer = __esm({
|
|
488
224
|
"src/engine/inline-analyzer.ts"() {
|
|
489
225
|
"use strict";
|
|
490
226
|
init_esm_shims();
|
|
227
|
+
init_shell_split();
|
|
491
228
|
INLINE_CODE_RE = /(?:python|python3|perl|perl5|ruby|jruby|mruby|node|php|lua)\s+(?:-[a-zA-Z]*[cera][a-zA-Z]*\s+)"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
|
|
492
229
|
ECHO_STRING_RE = /\b(?:echo|printf)\s+"([^"\\]*(?:\\.[^"\\]*)*)"|\b(?:echo|printf)\s+'([^'\\]*(?:\\.[^'\\]*)*)'/gi;
|
|
493
|
-
|
|
230
|
+
ECHO_SEG_RE = /^\s*(?:echo|printf)(?:\s|$)/i;
|
|
494
231
|
READ_WRITE_KEYWORDS = [
|
|
495
232
|
// 函数调用形态(关键词后跟 '(')
|
|
496
233
|
"open(",
|
|
@@ -539,9 +276,34 @@ var init_inline_analyzer = __esm({
|
|
|
539
276
|
"passthru",
|
|
540
277
|
"shell_exec",
|
|
541
278
|
"proc_open",
|
|
542
|
-
// Perl
|
|
279
|
+
// Perl/Ruby
|
|
543
280
|
"<stdin>",
|
|
544
|
-
"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")'
|
|
545
307
|
];
|
|
546
308
|
WRITE_EXEC_CHAIN_PATTERNS = [
|
|
547
309
|
// echo/cat/printf "..." > file && bash/sh/python file
|
|
@@ -596,15 +358,45 @@ var init_inline_analyzer = __esm({
|
|
|
596
358
|
}
|
|
597
359
|
});
|
|
598
360
|
|
|
599
|
-
// src/infra/
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
603
|
-
import { homedir as homedir2 } from "os";
|
|
604
|
-
function dataRootDir() {
|
|
605
|
-
return process.env.AGENT_MOSS_DATA_DIR || join2(homedir2(), ".config", "agentmoss");
|
|
361
|
+
// src/infra/runtime-config-bridge.ts
|
|
362
|
+
function registerRuntimeConfigBridge(bridge) {
|
|
363
|
+
runtimeConfigBridge.current = bridge;
|
|
606
364
|
}
|
|
607
|
-
|
|
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";
|
|
378
|
+
function dataRootDir() {
|
|
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
|
+
}
|
|
396
|
+
}
|
|
397
|
+
function findRulesFile(name) {
|
|
398
|
+
const override = resolve(overrideRulesDir(), name);
|
|
399
|
+
if (existsSync(override)) return override;
|
|
608
400
|
for (const dir of RULES_DIR_CANDS) {
|
|
609
401
|
const p = resolve(dir, name);
|
|
610
402
|
if (existsSync(p)) return p;
|
|
@@ -614,11 +406,16 @@ function findRulesFile(name) {
|
|
|
614
406
|
function loadJson(name) {
|
|
615
407
|
const p = findRulesFile(name);
|
|
616
408
|
if (!p) return {};
|
|
409
|
+
const isOverride = p === resolve(overrideRulesDir(), name);
|
|
410
|
+
let data;
|
|
617
411
|
try {
|
|
618
|
-
|
|
412
|
+
data = JSON.parse(readFileSync2(p, "utf-8")) || {};
|
|
619
413
|
} catch {
|
|
414
|
+
if (isOverride) warnOverride(name, p, {});
|
|
620
415
|
return {};
|
|
621
416
|
}
|
|
417
|
+
if (isOverride) warnOverride(name, p, data);
|
|
418
|
+
return data;
|
|
622
419
|
}
|
|
623
420
|
function loadL1() {
|
|
624
421
|
return loadJson("l1_rules.json");
|
|
@@ -633,8 +430,8 @@ function loadMC() {
|
|
|
633
430
|
const mc = loadJson("mc_rules.json");
|
|
634
431
|
const rules = mc.rules || [];
|
|
635
432
|
try {
|
|
636
|
-
const rt =
|
|
637
|
-
if (rules.length > 0 &&
|
|
433
|
+
const rt = runtimeConfigBridge.current;
|
|
434
|
+
if (rules.length > 0 && rt?.hasRuntimeConfig()) {
|
|
638
435
|
const ltl = rt.loadRuntimeConfig()?.L2_2?.ltl_rules || {};
|
|
639
436
|
const rtRules = ltl.rules || [];
|
|
640
437
|
const enabledById = {};
|
|
@@ -652,16 +449,26 @@ function loadMC() {
|
|
|
652
449
|
function loadToolRegistry() {
|
|
653
450
|
return loadJson("tool_registry.json");
|
|
654
451
|
}
|
|
655
|
-
var __filename2, __dirname2, RULES_DIR_CANDS;
|
|
452
|
+
var __filename2, __dirname2, EXPECTED_KEYS, warnedOverride, RULES_DIR_CANDS;
|
|
656
453
|
var init_rules_loader = __esm({
|
|
657
454
|
"src/infra/rules-loader.ts"() {
|
|
658
455
|
"use strict";
|
|
659
456
|
init_esm_shims();
|
|
660
|
-
|
|
457
|
+
init_paths();
|
|
458
|
+
init_runtime_config_bridge();
|
|
459
|
+
__filename2 = fileURLToPath2(import.meta.url);
|
|
661
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();
|
|
662
469
|
RULES_DIR_CANDS = (() => {
|
|
663
470
|
const cands = [
|
|
664
|
-
// 用户数据目录覆盖优先(与 Python rules_loader.
|
|
471
|
+
// 用户数据目录覆盖优先(与 Python rules_loader.resolve_rules_file 的
|
|
665
472
|
// <data_root>/rules/<name> 优先逻辑对齐)。用户安装 pip/npm 包后把自定义
|
|
666
473
|
// mc_rules.json 放到 <data_root>/rules/ 即可覆盖内置默认,无需改源码。
|
|
667
474
|
resolve(dataRootDir(), "rules"),
|
|
@@ -680,21 +487,455 @@ var init_rules_loader = __esm({
|
|
|
680
487
|
}
|
|
681
488
|
});
|
|
682
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
|
+
|
|
683
861
|
// src/engine/logic-rules.ts
|
|
684
|
-
import * as
|
|
685
|
-
function
|
|
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);
|
|
686
872
|
const sps = loadL1().sensitive_path_access || [];
|
|
687
|
-
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) => ({
|
|
688
874
|
path: sp.path,
|
|
689
875
|
riskLevel: sp.risk_level,
|
|
690
876
|
desc: sp.desc,
|
|
691
877
|
credential: sp.deny_mode === "deny_both" ? true : void 0,
|
|
692
|
-
|
|
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()
|
|
693
883
|
}));
|
|
694
884
|
}
|
|
695
|
-
function
|
|
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) {
|
|
696
936
|
if (writeKeywords.some((kw) => actionType.includes(kw) || actionDetail.includes(kw))) return true;
|
|
697
|
-
|
|
937
|
+
const { writeCommandRes } = getPlatformTraits(osType);
|
|
938
|
+
if (writeCommandRes.some((rx) => rx.test(actionDetail))) return true;
|
|
698
939
|
if (/\bsed\s+-[a-zA-Z]*i/i.test(actionDetail)) return true;
|
|
699
940
|
for (const seg of actionDetail.split(/&&|\|\||;|\n/)) {
|
|
700
941
|
const trimmed = seg.trim();
|
|
@@ -728,6 +969,12 @@ function isWriteOperation(actionType, actionDetail, writeKeywords = WRITE_KEYWOR
|
|
|
728
969
|
}
|
|
729
970
|
return false;
|
|
730
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
|
+
}
|
|
731
978
|
function matchDangerousAction(actionDetail, action) {
|
|
732
979
|
if (/[^\x00-\x7f]/.test(action)) {
|
|
733
980
|
return actionDetail.includes(action);
|
|
@@ -762,38 +1009,47 @@ function collectIntentTexts(promptSession, promptHistory) {
|
|
|
762
1009
|
}
|
|
763
1010
|
return deduped;
|
|
764
1011
|
}
|
|
765
|
-
function
|
|
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) {
|
|
766
1040
|
const { actionHistory, aNext, cwd } = input;
|
|
767
1041
|
const actionDetail = aNext.actionDetail.toLowerCase();
|
|
768
1042
|
const actionType = aNext.actionType.toLowerCase();
|
|
769
1043
|
const resolvedCwd = cwd || "/";
|
|
770
1044
|
const EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
|
|
771
1045
|
const isExcludedType = EXCLUDED_PREFIXES.some((p) => actionType.startsWith(p));
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
if (!INLINE_SCRIPT_RE.test(actionDetail)) {
|
|
776
|
-
const intentTexts = collectIntentTexts(input.promptSession, input.promptHistory);
|
|
777
|
-
for (const pattern of INTENT_DEVIATION_PATTERNS) {
|
|
778
|
-
const intentMatched = intentTexts.some(
|
|
779
|
-
(text) => pattern.intentKeywords.some((kw) => text.includes(kw))
|
|
780
|
-
);
|
|
781
|
-
const dangerousMatched = pattern.dangerousActions.some((kw) => matchDangerousAction(actionDetail, kw));
|
|
782
|
-
if (intentMatched && dangerousMatched) {
|
|
783
|
-
return {
|
|
784
|
-
allowed: false,
|
|
785
|
-
reason: pattern.reason,
|
|
786
|
-
riskLevel: "high",
|
|
787
|
-
riskType: "intent_deviation",
|
|
788
|
-
confidence: 85,
|
|
789
|
-
source: "logic_rule",
|
|
790
|
-
violatedLayers: ["L2.1"]
|
|
791
|
-
};
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
}
|
|
1046
|
+
if (!opts?.skipReadBeforeWrite) {
|
|
1047
|
+
const rbwResult = checkReadBeforeWrite(actionType, actionDetail, actionHistory, input.reason, resolvedCwd, input.osType);
|
|
1048
|
+
if (rbwResult) return rbwResult;
|
|
795
1049
|
}
|
|
796
|
-
const
|
|
1050
|
+
const intentResult = checkIntentConsistency(input);
|
|
1051
|
+
if (intentResult) return intentResult;
|
|
1052
|
+
const indirectResult = checkIndirectFileAccess(actionType, actionDetail, resolvedCwd, input.osType);
|
|
797
1053
|
if (indirectResult) return indirectResult;
|
|
798
1054
|
if (INLINE_SCRIPT_RE.test(actionDetail)) {
|
|
799
1055
|
} else if (containsPrivilegeEscalation(actionDetail)) {
|
|
@@ -816,9 +1072,9 @@ function runLogicCheck(input) {
|
|
|
816
1072
|
}
|
|
817
1073
|
}
|
|
818
1074
|
}
|
|
819
|
-
const pwdResult = checkPasswordConsent(actionType, actionDetail, actionHistory);
|
|
1075
|
+
const pwdResult = checkPasswordConsent(actionType, actionDetail, actionHistory, input.osType);
|
|
820
1076
|
if (pwdResult) return pwdResult;
|
|
821
|
-
const userDelResult = checkUserDeletionConsent(actionType, actionDetail, actionHistory);
|
|
1077
|
+
const userDelResult = checkUserDeletionConsent(actionType, actionDetail, actionHistory, input.osType);
|
|
822
1078
|
if (userDelResult) return userDelResult;
|
|
823
1079
|
const comboResult = checkComboChain(actionHistory, aNext.actionDetail);
|
|
824
1080
|
if (comboResult) return comboResult;
|
|
@@ -869,22 +1125,33 @@ function checkComboChain(actionHistory, actionDetailRaw) {
|
|
|
869
1125
|
}
|
|
870
1126
|
return null;
|
|
871
1127
|
}
|
|
872
|
-
function extractFilePaths(text) {
|
|
1128
|
+
function extractFilePaths(text, osType) {
|
|
873
1129
|
const paths = [];
|
|
874
1130
|
const unixPaths = text.match(/(?:^|\s)(\/[^\s;|&><'"]+)/g);
|
|
875
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
|
+
}
|
|
876
1139
|
const textNoQuotes = text.replace(/'[^']*'|"[^"]*"/g, "");
|
|
877
1140
|
const kvPaths = textNoQuotes.match(/(?:=)(\/[^\s;|&><'"]+)/g);
|
|
878
1141
|
if (kvPaths) paths.push(...kvPaths.map((p) => p.replace(/^=/, "")));
|
|
879
1142
|
const quotedPaths = text.match(/["']\/([^"']+)["']/g);
|
|
880
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
|
+
}
|
|
881
1148
|
return [...new Set(paths)];
|
|
882
1149
|
}
|
|
883
|
-
function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, cwd) {
|
|
1150
|
+
function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, cwd, osType) {
|
|
884
1151
|
if (NON_WRITE_TOOL_PREFIXES.some((p) => actionType.startsWith(p))) return null;
|
|
885
1152
|
if (NON_WRITE_TOOLS.has(actionType)) return null;
|
|
886
1153
|
const isBashLike = actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash";
|
|
887
|
-
const isWrite = isBashLike ? isWriteOperation(actionType, actionDetail) :
|
|
1154
|
+
const isWrite = isBashLike ? isWriteOperation(actionType, actionDetail, void 0, osType) : getPlatformTraits(osType).writeKeywords.some((kw) => actionType.includes(kw));
|
|
888
1155
|
if (!isWrite) return null;
|
|
889
1156
|
const WRITE_CMDS = /* @__PURE__ */ new Set(["cp", "mv", "install", "tee", "dd", "truncate", "mkfs", "fdisk", "parted"]);
|
|
890
1157
|
if (isBashLike) {
|
|
@@ -928,7 +1195,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
|
|
|
928
1195
|
if (firstWord === "cd") {
|
|
929
1196
|
continue;
|
|
930
1197
|
}
|
|
931
|
-
if (
|
|
1198
|
+
if (getPlatformTraits(osType).readOnlyCommands.has(firstWord)) {
|
|
932
1199
|
const segUnquoted = trimmed.replace(new RegExp(`>{1,2}\\s*(['"])([~./][^'"]+)\\1`, "g"), (_m, _q, p) => ">" + p);
|
|
933
1200
|
const segNoDevnull = segUnquoted.replace(DEVNULL_REDIRECT_RE, "");
|
|
934
1201
|
const segNoQuotes = segNoDevnull.replace(/'[^']*'|"[^"]*"/g, "");
|
|
@@ -936,7 +1203,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
|
|
|
936
1203
|
if (m) targetPaths.push(m[1] || m[2] || m[3]);
|
|
937
1204
|
continue;
|
|
938
1205
|
}
|
|
939
|
-
const segPaths = extractFilePaths(trimmed);
|
|
1206
|
+
const segPaths = extractFilePaths(trimmed, osType);
|
|
940
1207
|
const segPathsFiltered = firstWord === "wget" || firstWord === "curl" ? segPaths.filter((p) => p !== "/dev/null") : segPaths;
|
|
941
1208
|
if (SOURCE_ARG_CMDS.has(firstWord)) {
|
|
942
1209
|
if (segPathsFiltered.length > 1) targetPaths.push(...segPathsFiltered.slice(1));
|
|
@@ -958,20 +1225,30 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
|
|
|
958
1225
|
for (const action of actionHistory) {
|
|
959
1226
|
const histDetail = action.actionDetail.toLowerCase();
|
|
960
1227
|
const histType = action.actionType.toLowerCase();
|
|
961
|
-
const isRead =
|
|
1228
|
+
const isRead = getPlatformTraits(osType).readKeywords.some((kw) => histType.includes(kw) || histDetail.includes(kw));
|
|
962
1229
|
if (isRead) {
|
|
963
|
-
for (const p of extractFilePaths(histDetail)) {
|
|
1230
|
+
for (const p of extractFilePaths(histDetail, osType)) {
|
|
964
1231
|
historyReadPaths.add(p);
|
|
965
1232
|
}
|
|
966
1233
|
}
|
|
967
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
|
+
}
|
|
968
1245
|
const unreadPaths = [];
|
|
969
|
-
for (const path5 of
|
|
1246
|
+
for (const path5 of filteredPaths) {
|
|
970
1247
|
const hasRead = [...historyReadPaths].some(
|
|
971
1248
|
(rp) => path5 === rp || path5.startsWith(rp) || rp.startsWith(path5)
|
|
972
1249
|
);
|
|
973
1250
|
if (!hasRead) {
|
|
974
|
-
let isCreate = !
|
|
1251
|
+
let isCreate = !fs.existsSync(path5);
|
|
975
1252
|
if (!isCreate) {
|
|
976
1253
|
isCreate = CREATE_KEYWORDS.some((kw) => reason.toLowerCase().includes(kw) || actionDetail.includes(kw));
|
|
977
1254
|
}
|
|
@@ -993,14 +1270,18 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
|
|
|
993
1270
|
}
|
|
994
1271
|
return null;
|
|
995
1272
|
}
|
|
996
|
-
function
|
|
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) {
|
|
997
1280
|
if (actionType !== "bash") return null;
|
|
998
|
-
const
|
|
1281
|
+
const pwdPatterns = runtimeConfigBridge.current?.effectivePasswordPatterns(osType) ?? PASSWORD_MODIFY_PATTERNS;
|
|
1282
|
+
const isPasswordModify = pwdPatterns.some((p) => p.test(actionDetail));
|
|
999
1283
|
if (!isPasswordModify) return null;
|
|
1000
|
-
|
|
1001
|
-
(h) => h.actionType.toLowerCase() === "ask_user_question" && h.actionDetail.trim().length > 0
|
|
1002
|
-
);
|
|
1003
|
-
if (hasConsent) return null;
|
|
1284
|
+
if (hasUserConsent(actionHistory)) return null;
|
|
1004
1285
|
return {
|
|
1005
1286
|
allowed: false,
|
|
1006
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",
|
|
@@ -1011,14 +1292,12 @@ function checkPasswordConsent(actionType, actionDetail, actionHistory) {
|
|
|
1011
1292
|
violatedLayers: ["L2.1"]
|
|
1012
1293
|
};
|
|
1013
1294
|
}
|
|
1014
|
-
function checkUserDeletionConsent(actionType, actionDetail, actionHistory) {
|
|
1295
|
+
function checkUserDeletionConsent(actionType, actionDetail, actionHistory, osType) {
|
|
1015
1296
|
if (actionType !== "bash") return null;
|
|
1016
|
-
const
|
|
1297
|
+
const udPatterns = runtimeConfigBridge.current?.effectiveUserDeletionPatterns(osType) ?? USER_DELETION_PATTERNS;
|
|
1298
|
+
const isUserDeletion = udPatterns.some((p) => p.test(actionDetail));
|
|
1017
1299
|
if (!isUserDeletion) return null;
|
|
1018
|
-
|
|
1019
|
-
(h) => h.actionType.toLowerCase() === "ask_user_question" && h.actionDetail.trim().length > 0
|
|
1020
|
-
);
|
|
1021
|
-
if (hasConsent) return null;
|
|
1300
|
+
if (hasUserConsent(actionHistory)) return null;
|
|
1022
1301
|
return {
|
|
1023
1302
|
allowed: false,
|
|
1024
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",
|
|
@@ -1041,7 +1320,7 @@ function containsPrivilegeEscalation(actionDetail) {
|
|
|
1041
1320
|
}
|
|
1042
1321
|
return false;
|
|
1043
1322
|
}
|
|
1044
|
-
function checkIndirectFileAccess(actionType, actionDetail, cwd) {
|
|
1323
|
+
function checkIndirectFileAccess(actionType, actionDetail, cwd, osType) {
|
|
1045
1324
|
if (actionType !== "bash" && actionType !== "executebash" && actionType !== "terminal-executebash") {
|
|
1046
1325
|
return null;
|
|
1047
1326
|
}
|
|
@@ -1054,14 +1333,14 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
|
|
|
1054
1333
|
if (!hasTraversal) {
|
|
1055
1334
|
return null;
|
|
1056
1335
|
}
|
|
1057
|
-
const
|
|
1336
|
+
const READ_KEYWORDS = ["read(", "readline", "readlines", "fread", "fgets", "load(", "getpass", "file_get_contents"];
|
|
1058
1337
|
const WRITE_KEYWORDS2 = ["write(", "writelines", "fwrite", "file_put_contents"];
|
|
1059
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"];
|
|
1060
1339
|
const OPEN_KEYWORDS = ["open(", "fopen("];
|
|
1061
1340
|
const code = extractInlineCode(actionDetail);
|
|
1062
1341
|
if (!code) return null;
|
|
1063
1342
|
const codeLower = code.toLowerCase();
|
|
1064
|
-
const hasAnyOp =
|
|
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));
|
|
1065
1344
|
if (!hasAnyOp) {
|
|
1066
1345
|
return null;
|
|
1067
1346
|
}
|
|
@@ -1069,15 +1348,16 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
|
|
|
1069
1348
|
const hasExec = EXEC_KEYWORDS.some((kw) => codeLower.includes(kw));
|
|
1070
1349
|
const blockAllSensitive = hasWrite || hasExec;
|
|
1071
1350
|
let actualFiles = [];
|
|
1072
|
-
if (cwd
|
|
1351
|
+
if (cwd) {
|
|
1073
1352
|
try {
|
|
1074
|
-
actualFiles =
|
|
1353
|
+
actualFiles = fs.readdirSync(cwd);
|
|
1075
1354
|
} catch {
|
|
1076
1355
|
}
|
|
1077
1356
|
}
|
|
1078
1357
|
if (actualFiles.length > 0) {
|
|
1079
|
-
|
|
1080
|
-
|
|
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;
|
|
1081
1361
|
if (actualFiles.some((f) => f === fname || f.toLowerCase() === fname.toLowerCase())) {
|
|
1082
1362
|
return {
|
|
1083
1363
|
allowed: false,
|
|
@@ -1092,8 +1372,8 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
|
|
|
1092
1372
|
}
|
|
1093
1373
|
}
|
|
1094
1374
|
if (blockAllSensitive) {
|
|
1095
|
-
for (const sp of [...
|
|
1096
|
-
const fname = sp.path.split(
|
|
1375
|
+
for (const sp of [...rootPaths, ...homePaths]) {
|
|
1376
|
+
const fname = sp.path.split(/[/\\]/).pop() || sp.path;
|
|
1097
1377
|
if (actualFiles.some((f) => f === fname || f.toLowerCase() === fname.toLowerCase())) {
|
|
1098
1378
|
return {
|
|
1099
1379
|
allowed: false,
|
|
@@ -1119,16 +1399,28 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd) {
|
|
|
1119
1399
|
violatedLayers: ["L2.1"]
|
|
1120
1400
|
};
|
|
1121
1401
|
}
|
|
1122
|
-
var ROOT_SENSITIVE_PATHS, HOME_SENSITIVE_PATHS, CREDENTIAL_PATHS,
|
|
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;
|
|
1123
1403
|
var init_logic_rules = __esm({
|
|
1124
1404
|
"src/engine/logic-rules.ts"() {
|
|
1125
1405
|
"use strict";
|
|
1126
1406
|
init_esm_shims();
|
|
1127
1407
|
init_inline_analyzer();
|
|
1408
|
+
init_runtime_config_bridge();
|
|
1128
1409
|
init_rules_loader();
|
|
1410
|
+
init_platform_traits();
|
|
1129
1411
|
ROOT_SENSITIVE_PATHS = _loadSensitivePathScope("root");
|
|
1130
1412
|
HOME_SENSITIVE_PATHS = _loadSensitivePathScope("home");
|
|
1131
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;
|
|
1132
1424
|
NON_WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
1133
1425
|
"ask_user_question",
|
|
1134
1426
|
"glob",
|
|
@@ -1150,111 +1442,7 @@ var init_logic_rules = __esm({
|
|
|
1150
1442
|
]);
|
|
1151
1443
|
NON_WRITE_TOOL_PREFIXES = ["memory-", "skill-manager-", "mailbox-", "app-"];
|
|
1152
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"];
|
|
1153
|
-
WRITE_COMMAND_RES = [
|
|
1154
|
-
/\brm\b/,
|
|
1155
|
-
/\bunlink\b/,
|
|
1156
|
-
/\brmdir\b/,
|
|
1157
|
-
/\bshred\b/,
|
|
1158
|
-
/\bcp\b/,
|
|
1159
|
-
/\bmv\b/,
|
|
1160
|
-
/\binstall\b/,
|
|
1161
|
-
/\btee\b/,
|
|
1162
|
-
/\bdd\b/,
|
|
1163
|
-
/\bmkfs\b/,
|
|
1164
|
-
/\b(fdisk|parted|cfdisk|sfdisk)\b/,
|
|
1165
|
-
/\bchmod\b/,
|
|
1166
|
-
/\bchown\b/,
|
|
1167
|
-
/\btruncate\b/
|
|
1168
|
-
];
|
|
1169
|
-
READ_ONLY_COMMANDS = /* @__PURE__ */ new Set([
|
|
1170
|
-
"cat",
|
|
1171
|
-
"head",
|
|
1172
|
-
"tail",
|
|
1173
|
-
"less",
|
|
1174
|
-
"more",
|
|
1175
|
-
"grep",
|
|
1176
|
-
"find",
|
|
1177
|
-
"awk",
|
|
1178
|
-
"sed",
|
|
1179
|
-
"sort",
|
|
1180
|
-
"uniq",
|
|
1181
|
-
"wc",
|
|
1182
|
-
"cut",
|
|
1183
|
-
"strings",
|
|
1184
|
-
"od",
|
|
1185
|
-
"xxd",
|
|
1186
|
-
"hexdump",
|
|
1187
|
-
"tr",
|
|
1188
|
-
"file",
|
|
1189
|
-
"stat",
|
|
1190
|
-
"du",
|
|
1191
|
-
"df",
|
|
1192
|
-
"ls",
|
|
1193
|
-
"dir",
|
|
1194
|
-
"tree",
|
|
1195
|
-
"nl",
|
|
1196
|
-
"tac",
|
|
1197
|
-
"rev",
|
|
1198
|
-
// NEW-FP-I: echo/printf 是文本输出命令,参数是字面量非写目标。加入此集让
|
|
1199
|
-
// read_before_write 的 echo 段走只读命令分支(只提取重定向目标,不提取参数路径),
|
|
1200
|
-
// 避免 echo "cat /etc/shadow" > /tmp/x.sh 的引号内 /etc/shadow 被当写目标误报。
|
|
1201
|
-
// echo "x" > /tmp/x.sh 的重定向目标 /tmp/x.sh 仍被提取(真实写不漏报)。
|
|
1202
|
-
"echo",
|
|
1203
|
-
"printf",
|
|
1204
|
-
"lsblk",
|
|
1205
|
-
"blockdev",
|
|
1206
|
-
"smartctl",
|
|
1207
|
-
"udevadm",
|
|
1208
|
-
"dmidecode",
|
|
1209
|
-
"lscpu",
|
|
1210
|
-
"lspci",
|
|
1211
|
-
"lsusb",
|
|
1212
|
-
"lsmem",
|
|
1213
|
-
"lsns",
|
|
1214
|
-
"lsof",
|
|
1215
|
-
"hwinfo",
|
|
1216
|
-
"inxi",
|
|
1217
|
-
"fdisk",
|
|
1218
|
-
"parted",
|
|
1219
|
-
"dmesg",
|
|
1220
|
-
"journalctl",
|
|
1221
|
-
"systemctl",
|
|
1222
|
-
"hostnamectl",
|
|
1223
|
-
"localectl",
|
|
1224
|
-
"timedatectl",
|
|
1225
|
-
"ps",
|
|
1226
|
-
"top",
|
|
1227
|
-
"free",
|
|
1228
|
-
"vmstat",
|
|
1229
|
-
"iostat",
|
|
1230
|
-
"mpstat",
|
|
1231
|
-
"pidof",
|
|
1232
|
-
"pgrep",
|
|
1233
|
-
"ip",
|
|
1234
|
-
"ifconfig",
|
|
1235
|
-
"route",
|
|
1236
|
-
"ss",
|
|
1237
|
-
"netstat",
|
|
1238
|
-
"arp",
|
|
1239
|
-
"ethtool",
|
|
1240
|
-
"nmcli",
|
|
1241
|
-
"uname",
|
|
1242
|
-
"arch",
|
|
1243
|
-
"nproc",
|
|
1244
|
-
"getconf",
|
|
1245
|
-
"getent",
|
|
1246
|
-
"id",
|
|
1247
|
-
"whoami",
|
|
1248
|
-
"who",
|
|
1249
|
-
"w",
|
|
1250
|
-
"last",
|
|
1251
|
-
"uptime",
|
|
1252
|
-
"lsmod",
|
|
1253
|
-
"modinfo",
|
|
1254
|
-
"sysctl"
|
|
1255
|
-
]);
|
|
1256
1445
|
DEVNULL_REDIRECT_RE = /(?:2|&|1)?\s*>\s*\/dev\/null\b/g;
|
|
1257
|
-
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"];
|
|
1258
1446
|
CREATE_KEYWORDS = ["\u521B\u5EFA", "\u65B0\u5EFA", "create", "new file", "touch"];
|
|
1259
1447
|
PASSWORD_MODIFY_PATTERNS = (() => {
|
|
1260
1448
|
const pats = loadL2().password_modify_consent || [];
|
|
@@ -1269,7 +1457,8 @@ var init_logic_rules = __esm({
|
|
|
1269
1457
|
return pats.map((p) => ({
|
|
1270
1458
|
intentKeywords: p.intent_keywords,
|
|
1271
1459
|
dangerousActions: p.dangerous_actions,
|
|
1272
|
-
reason: p.reason
|
|
1460
|
+
reason: p.reason,
|
|
1461
|
+
os: String(p.os || "linux").toLowerCase()
|
|
1273
1462
|
}));
|
|
1274
1463
|
})();
|
|
1275
1464
|
COMBO_STAGES = (() => {
|
|
@@ -1329,9 +1518,9 @@ var init_asset_dir = __esm({
|
|
|
1329
1518
|
});
|
|
1330
1519
|
|
|
1331
1520
|
// src/engine/skill-loader.ts
|
|
1332
|
-
import { readFileSync as readFileSync3, readdirSync as readdirSync2 } from "fs";
|
|
1521
|
+
import { readFileSync as readFileSync3, readdirSync as readdirSync2, existsSync as existsSync4 } from "fs";
|
|
1333
1522
|
import { resolve as resolve3, dirname as dirname2 } from "path";
|
|
1334
|
-
import { fileURLToPath as
|
|
1523
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1335
1524
|
function loadAllSkills() {
|
|
1336
1525
|
if (cachedSkills) return cachedSkills;
|
|
1337
1526
|
cachedSkills = {};
|
|
@@ -1349,14 +1538,47 @@ function loadAllSkills() {
|
|
|
1349
1538
|
console.log(`[AgentMoss] Loaded ${Object.keys(cachedSkills).length} skills from ${dir}`);
|
|
1350
1539
|
return cachedSkills;
|
|
1351
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
|
+
}
|
|
1352
1571
|
function matchSkills(actionType, actionDetail, reason, maxSkills = 3) {
|
|
1353
1572
|
const skills = loadAllSkills();
|
|
1354
1573
|
const combined = `${actionType} ${actionDetail} ${reason}`.toLowerCase();
|
|
1355
1574
|
const actionDetailLower = actionDetail.toLowerCase();
|
|
1356
1575
|
const actionTypeLower = actionType.toLowerCase();
|
|
1357
1576
|
const reasonLower = reason.toLowerCase();
|
|
1577
|
+
const disabledSkillIds = runtimeConfigBridge.current?.disabledSkillIds() ?? /* @__PURE__ */ new Set();
|
|
1578
|
+
const skillMap = { ...SKILL_KEYWORD_MAP, ...runtimeCustomSkillKeywords() };
|
|
1358
1579
|
const scores = {};
|
|
1359
|
-
for (const [skillName, keywords] of Object.entries(
|
|
1580
|
+
for (const [skillName, keywords] of Object.entries(skillMap)) {
|
|
1581
|
+
if (disabledSkillIds.has(skillName)) continue;
|
|
1360
1582
|
let score = 0;
|
|
1361
1583
|
for (const kw of keywords) {
|
|
1362
1584
|
if (combined.includes(kw)) {
|
|
@@ -1373,7 +1595,7 @@ function matchSkills(actionType, actionDetail, reason, maxSkills = 3) {
|
|
|
1373
1595
|
const maxScore = top[0]?.[1] || 1;
|
|
1374
1596
|
const results = [];
|
|
1375
1597
|
for (const [name, score] of top) {
|
|
1376
|
-
const content = skills[name];
|
|
1598
|
+
const content = skills[name] || loadUserSkill(name);
|
|
1377
1599
|
if (content) {
|
|
1378
1600
|
results.push({
|
|
1379
1601
|
skillName: name,
|
|
@@ -1398,14 +1620,16 @@ function formatSkillsForPrompt(skills) {
|
|
|
1398
1620
|
${s.skillContent}`
|
|
1399
1621
|
).join("\n\n---\n\n");
|
|
1400
1622
|
}
|
|
1401
|
-
var __filename3, __dirname3, SKILL_KEYWORD_MAP, cachedSkills;
|
|
1623
|
+
var __filename3, __dirname3, SKILL_KEYWORD_MAP, cachedSkills, userSkillCache;
|
|
1402
1624
|
var init_skill_loader = __esm({
|
|
1403
1625
|
"src/engine/skill-loader.ts"() {
|
|
1404
1626
|
"use strict";
|
|
1405
1627
|
init_esm_shims();
|
|
1406
1628
|
init_rules_loader();
|
|
1629
|
+
init_paths();
|
|
1407
1630
|
init_asset_dir();
|
|
1408
|
-
|
|
1631
|
+
init_runtime_config_bridge();
|
|
1632
|
+
__filename3 = fileURLToPath3(import.meta.url);
|
|
1409
1633
|
__dirname3 = dirname2(__filename3);
|
|
1410
1634
|
SKILL_KEYWORD_MAP = (() => {
|
|
1411
1635
|
const map = {};
|
|
@@ -1419,6 +1643,7 @@ var init_skill_loader = __esm({
|
|
|
1419
1643
|
return map;
|
|
1420
1644
|
})();
|
|
1421
1645
|
cachedSkills = null;
|
|
1646
|
+
userSkillCache = {};
|
|
1422
1647
|
}
|
|
1423
1648
|
});
|
|
1424
1649
|
|
|
@@ -1435,6 +1660,12 @@ __export(runtime_config_exports, {
|
|
|
1435
1660
|
deleteCustomRule: () => deleteCustomRule,
|
|
1436
1661
|
deleteCustomSkill: () => deleteCustomSkill,
|
|
1437
1662
|
deleteScenarioRule: () => deleteScenarioRule,
|
|
1663
|
+
effectiveDangerousPatterns: () => effectiveDangerousPatterns,
|
|
1664
|
+
effectiveInjectionKeywords: () => effectiveInjectionKeywords,
|
|
1665
|
+
effectiveIntentPatterns: () => effectiveIntentPatterns,
|
|
1666
|
+
effectivePasswordPatterns: () => effectivePasswordPatterns,
|
|
1667
|
+
effectiveSensitivePaths: () => effectiveSensitivePaths,
|
|
1668
|
+
effectiveUserDeletionPatterns: () => effectiveUserDeletionPatterns,
|
|
1438
1669
|
filterEnabledRules: () => filterEnabledRules,
|
|
1439
1670
|
generateSourceDefaults: () => generateSourceDefaults,
|
|
1440
1671
|
getAgentRules: () => getAgentRules,
|
|
@@ -1478,9 +1709,9 @@ __export(runtime_config_exports, {
|
|
|
1478
1709
|
updateSkillCategoryEnabled: () => updateSkillCategoryEnabled,
|
|
1479
1710
|
updateSkillEnabled: () => updateSkillEnabled
|
|
1480
1711
|
});
|
|
1481
|
-
import { readFileSync as readFileSync4, writeFileSync, existsSync as
|
|
1712
|
+
import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync5, mkdirSync, unlinkSync, readdirSync as readdirSync3, statSync } from "fs";
|
|
1482
1713
|
import { join as join3 } from "path";
|
|
1483
|
-
import { fileURLToPath as
|
|
1714
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
1484
1715
|
import { dirname as dirname3 } from "path";
|
|
1485
1716
|
function runtimeDir() {
|
|
1486
1717
|
return getRuntimeConfigDir();
|
|
@@ -1493,7 +1724,7 @@ function getRuntimeConfigPath() {
|
|
|
1493
1724
|
function saveRuntimeConfig(config) {
|
|
1494
1725
|
const p = getRuntimeConfigPath();
|
|
1495
1726
|
const dir = p.substring(0, p.lastIndexOf("/"));
|
|
1496
|
-
if (dir && !
|
|
1727
|
+
if (dir && !existsSync5(dir)) mkdirSync(dir, { recursive: true });
|
|
1497
1728
|
writeFileSync(p, JSON.stringify(config, null, 2), "utf-8");
|
|
1498
1729
|
invalidateRuntimeConfigCache();
|
|
1499
1730
|
}
|
|
@@ -1511,10 +1742,11 @@ function emptyRuntimeConfig() {
|
|
|
1511
1742
|
}
|
|
1512
1743
|
function invalidateRuntimeConfigCache() {
|
|
1513
1744
|
runtimeConfigCache = null;
|
|
1745
|
+
hasRuntimeConfigCache.clear();
|
|
1514
1746
|
}
|
|
1515
1747
|
function loadRuntimeConfig() {
|
|
1516
1748
|
const p = getRuntimeConfigPath();
|
|
1517
|
-
if (!
|
|
1749
|
+
if (!existsSync5(p)) {
|
|
1518
1750
|
const seeded = mergeConfig({}, generateSourceDefaults());
|
|
1519
1751
|
try {
|
|
1520
1752
|
saveRuntimeConfig(seeded);
|
|
@@ -1535,7 +1767,13 @@ function loadRuntimeConfig() {
|
|
|
1535
1767
|
layers: {
|
|
1536
1768
|
L1_heuristic: data.layers?.L1_heuristic ?? true,
|
|
1537
1769
|
L2_logic_rules: data.layers?.L2_logic_rules ?? true,
|
|
1538
|
-
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
|
|
1539
1777
|
},
|
|
1540
1778
|
L1_rules: merged.L1_rules || {},
|
|
1541
1779
|
L2_rules: merged.L2_rules || {},
|
|
@@ -1577,21 +1815,48 @@ function filterEnabledRules(rules, categoryEnabled = true) {
|
|
|
1577
1815
|
if (!categoryEnabled) return [];
|
|
1578
1816
|
return rules.filter((r) => r.enabled !== false);
|
|
1579
1817
|
}
|
|
1580
|
-
function
|
|
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) {
|
|
1581
1840
|
const cat = runtime.L1_rules?.dangerous_commands || { category_enabled: true, rules: [] };
|
|
1582
|
-
|
|
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) => ({
|
|
1583
1845
|
pattern: r.pattern || "",
|
|
1584
1846
|
risk_level: r.risk_level || "",
|
|
1585
1847
|
risk_type: r.risk_type || "",
|
|
1586
1848
|
reason: r.reason || ""
|
|
1587
1849
|
}));
|
|
1588
1850
|
}
|
|
1589
|
-
function getEnabledL2SensitivePaths(runtime) {
|
|
1851
|
+
function getEnabledL2SensitivePaths(runtime, osType) {
|
|
1590
1852
|
let cat = runtime.L1_rules?.sensitive_path_access;
|
|
1591
1853
|
if (!cat || !cat.rules || cat.rules.length === 0) {
|
|
1592
1854
|
cat = runtime.L2_rules?.sensitive_path_access || { category_enabled: true, rules: [] };
|
|
1593
1855
|
}
|
|
1594
|
-
|
|
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) => {
|
|
1595
1860
|
const out = { path: r.path || "", risk_level: r.risk_level || "", desc: r.desc || "", deny_mode: r.deny_mode || "deny_write" };
|
|
1596
1861
|
if (r.credential) out.credential = true;
|
|
1597
1862
|
if (r.read_only) out.read_only = true;
|
|
@@ -1601,13 +1866,24 @@ function getEnabledL2SensitivePaths(runtime) {
|
|
|
1601
1866
|
}
|
|
1602
1867
|
function hasRuntimeConfig() {
|
|
1603
1868
|
const p = getRuntimeConfigPath();
|
|
1604
|
-
|
|
1869
|
+
let key;
|
|
1605
1870
|
try {
|
|
1606
|
-
|
|
1607
|
-
|
|
1871
|
+
const st = statSync(p);
|
|
1872
|
+
key = `${p}|${st.mtimeMs}|${st.size}`;
|
|
1608
1873
|
} catch {
|
|
1609
1874
|
return false;
|
|
1610
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;
|
|
1611
1887
|
}
|
|
1612
1888
|
function getDisabledL2RulesWithSkipL3() {
|
|
1613
1889
|
let runtime;
|
|
@@ -1672,15 +1948,28 @@ function _syncDenyModeFields(rule) {
|
|
|
1672
1948
|
}
|
|
1673
1949
|
}
|
|
1674
1950
|
function _l1CommandPatternsToRules() {
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
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;
|
|
1684
1973
|
}
|
|
1685
1974
|
function _l1InjectionKeywordsToRules() {
|
|
1686
1975
|
return INJECTION_KEYWORDS.map((kw) => ({
|
|
@@ -1693,28 +1982,42 @@ function _l1InjectionKeywordsToRules() {
|
|
|
1693
1982
|
}));
|
|
1694
1983
|
}
|
|
1695
1984
|
function _l2SensitivePathsToRules() {
|
|
1696
|
-
const
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
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;
|
|
1718
2021
|
}
|
|
1719
2022
|
function _l2IntentPatternsToRules() {
|
|
1720
2023
|
return INTENT_DEVIATION_PATTERNS.map((pat, i) => ({
|
|
@@ -1791,8 +2094,8 @@ function _l3SkillsToRules() {
|
|
|
1791
2094
|
}
|
|
1792
2095
|
function _scenarioRulesToDefaults() {
|
|
1793
2096
|
const out = {};
|
|
1794
|
-
const scenDir =
|
|
1795
|
-
if (!
|
|
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;
|
|
1796
2099
|
let entries;
|
|
1797
2100
|
try {
|
|
1798
2101
|
entries = readdirSync3(scenDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
@@ -1801,7 +2104,7 @@ function _scenarioRulesToDefaults() {
|
|
|
1801
2104
|
}
|
|
1802
2105
|
for (const scenarioId of entries) {
|
|
1803
2106
|
const rulesFile = join3(scenDir, scenarioId, "rules.json");
|
|
1804
|
-
if (!
|
|
2107
|
+
if (!existsSync5(rulesFile)) continue;
|
|
1805
2108
|
let data;
|
|
1806
2109
|
try {
|
|
1807
2110
|
data = JSON.parse(readFileSync4(rulesFile, "utf-8"));
|
|
@@ -1840,8 +2143,8 @@ function _scenarioRulesToDefaults() {
|
|
|
1840
2143
|
}
|
|
1841
2144
|
function _agentRulesToDefaults() {
|
|
1842
2145
|
const out = {};
|
|
1843
|
-
const agentsDir =
|
|
1844
|
-
if (!
|
|
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;
|
|
1845
2148
|
let entries;
|
|
1846
2149
|
try {
|
|
1847
2150
|
entries = readdirSync3(agentsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
@@ -1850,7 +2153,7 @@ function _agentRulesToDefaults() {
|
|
|
1850
2153
|
}
|
|
1851
2154
|
for (const agentId of entries) {
|
|
1852
2155
|
const rulesFile = join3(agentsDir, agentId, "rules.json");
|
|
1853
|
-
if (!
|
|
2156
|
+
if (!existsSync5(rulesFile)) continue;
|
|
1854
2157
|
let data;
|
|
1855
2158
|
try {
|
|
1856
2159
|
data = JSON.parse(readFileSync4(rulesFile, "utf-8"));
|
|
@@ -1946,7 +2249,7 @@ function _ltlRulesToRuntime() {
|
|
|
1946
2249
|
let mc = {};
|
|
1947
2250
|
for (const dir of cands) {
|
|
1948
2251
|
const p = join3(dir, "mc_rules.json");
|
|
1949
|
-
if (
|
|
2252
|
+
if (existsSync5(p)) {
|
|
1950
2253
|
try {
|
|
1951
2254
|
mc = JSON.parse(readFileSync4(p, "utf-8")) || {};
|
|
1952
2255
|
} catch {
|
|
@@ -1975,17 +2278,27 @@ function normalizePatternForFingerprint(pattern) {
|
|
|
1975
2278
|
return pattern.replace(/\\\//g, "/");
|
|
1976
2279
|
}
|
|
1977
2280
|
function _ruleFingerprint(rule) {
|
|
2281
|
+
const osTag = String(rule.os || "linux").toLowerCase();
|
|
1978
2282
|
if (rule.intent_keywords && rule.intent_keywords.length || rule.dangerous_actions && rule.dangerous_actions.length) {
|
|
1979
2283
|
return JSON.stringify([
|
|
2284
|
+
osTag,
|
|
1980
2285
|
[...rule.intent_keywords || []].sort(),
|
|
1981
2286
|
[...rule.dangerous_actions || []].sort()
|
|
1982
2287
|
]);
|
|
1983
2288
|
}
|
|
1984
|
-
if (rule.path) return JSON.stringify([normalizePatternForFingerprint(rule.path)]);
|
|
1985
|
-
if (rule.pattern) return JSON.stringify([normalizePatternForFingerprint(rule.pattern)]);
|
|
1986
|
-
if (rule.keyword) return JSON.stringify([rule.keyword, rule.lang || ""]);
|
|
1987
|
-
if (rule.value) return JSON.stringify([rule.value]);
|
|
1988
|
-
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
|
+
}
|
|
1989
2302
|
}
|
|
1990
2303
|
function _mergeRuleCategories(userCfg, srcDefaults, layerKey) {
|
|
1991
2304
|
const userLayer = userCfg[layerKey] || (userCfg[layerKey] = {});
|
|
@@ -2008,13 +2321,14 @@ function _mergeRuleCategories(userCfg, srcDefaults, layerKey) {
|
|
|
2008
2321
|
srcFps.add(fp);
|
|
2009
2322
|
if (userByFp.has(fp)) {
|
|
2010
2323
|
const existing = userByFp.get(fp);
|
|
2011
|
-
const
|
|
2324
|
+
const userDisabled = existing.user_disabled === true;
|
|
2012
2325
|
const userSkipL3 = existing.skip_l3_on_disabled;
|
|
2013
2326
|
const userDenyMode = existing.deny_mode;
|
|
2014
2327
|
const userSourceDeny = existing.source_deny_mode;
|
|
2015
2328
|
Object.keys(existing).forEach((k) => delete existing[k]);
|
|
2016
2329
|
Object.assign(existing, srcR);
|
|
2017
|
-
existing.enabled =
|
|
2330
|
+
existing.enabled = !userDisabled;
|
|
2331
|
+
if (userDisabled) existing.user_disabled = true;
|
|
2018
2332
|
if (userSkipL3 !== void 0) existing.skip_l3_on_disabled = userSkipL3;
|
|
2019
2333
|
if (userDenyMode && userSourceDeny && userDenyMode !== userSourceDeny) {
|
|
2020
2334
|
existing.deny_mode = userDenyMode;
|
|
@@ -2040,7 +2354,19 @@ function _mergeRuleCategories(userCfg, srcDefaults, layerKey) {
|
|
|
2040
2354
|
}
|
|
2041
2355
|
for (const catName of Object.keys(userLayer)) {
|
|
2042
2356
|
if (catName === "user_custom") continue;
|
|
2043
|
-
if (!(catName in srcLayer))
|
|
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
|
+
}
|
|
2044
2370
|
}
|
|
2045
2371
|
}
|
|
2046
2372
|
function _mergeSkillCategories(userCfg, srcDefaults, layerKey) {
|
|
@@ -2088,10 +2414,11 @@ function _mergeAgentRules(userCfg, srcDefaults, partition = "agent_rules") {
|
|
|
2088
2414
|
srcFps.add(fp);
|
|
2089
2415
|
if (userByFp.has(fp)) {
|
|
2090
2416
|
const existing = userByFp.get(fp);
|
|
2091
|
-
const
|
|
2417
|
+
const userDisabled = existing.user_disabled === true;
|
|
2092
2418
|
Object.keys(existing).forEach((k) => delete existing[k]);
|
|
2093
2419
|
Object.assign(existing, srcR);
|
|
2094
|
-
existing.enabled =
|
|
2420
|
+
existing.enabled = !userDisabled;
|
|
2421
|
+
if (userDisabled) existing.user_disabled = true;
|
|
2095
2422
|
} else {
|
|
2096
2423
|
userRules.push({ ...srcR, enabled: true });
|
|
2097
2424
|
}
|
|
@@ -2155,17 +2482,23 @@ function _migrateCategoriesAcrossLayers(cfg, srcLayer, dstLayer, catNames) {
|
|
|
2155
2482
|
delete src[catName];
|
|
2156
2483
|
}
|
|
2157
2484
|
}
|
|
2158
|
-
function getEnabledL1InjectionKeywords(runtime) {
|
|
2485
|
+
function getEnabledL1InjectionKeywords(runtime, osType) {
|
|
2159
2486
|
const cat = runtime.L1_rules?.injection_detection || { category_enabled: true, rules: [] };
|
|
2160
|
-
|
|
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) => ({
|
|
2161
2491
|
keyword: r.keyword || "",
|
|
2162
2492
|
risk_level: r.risk_level || "",
|
|
2163
2493
|
lang: r.lang || "en"
|
|
2164
2494
|
}));
|
|
2165
2495
|
}
|
|
2166
|
-
function getEnabledL2IntentPatterns(runtime) {
|
|
2496
|
+
function getEnabledL2IntentPatterns(runtime, osType) {
|
|
2167
2497
|
const cat = runtime.L2_rules?.intent_consistency || { category_enabled: true, rules: [] };
|
|
2168
|
-
|
|
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) => ({
|
|
2169
2502
|
intent_keywords: r.intent_keywords || [],
|
|
2170
2503
|
dangerous_actions: r.dangerous_actions || [],
|
|
2171
2504
|
reason: r.reason || ""
|
|
@@ -2173,11 +2506,13 @@ function getEnabledL2IntentPatterns(runtime) {
|
|
|
2173
2506
|
}
|
|
2174
2507
|
function getEnabledL2PasswordPatterns(runtime) {
|
|
2175
2508
|
const cat = runtime.L2_rules?.password_modify_consent || { category_enabled: true, rules: [] };
|
|
2176
|
-
|
|
2509
|
+
const rules = _mergeUserCustom(filterEnabledRules(cat.rules || [], cat.category_enabled), runtime, "L2_rules", "pattern");
|
|
2510
|
+
return rules.map((r) => r.pattern || "");
|
|
2177
2511
|
}
|
|
2178
2512
|
function getEnabledL2UserDeletionPatterns(runtime) {
|
|
2179
2513
|
const cat = runtime.L2_rules?.user_deletion_consent || { category_enabled: true, rules: [] };
|
|
2180
|
-
|
|
2514
|
+
const rules = _mergeUserCustom(filterEnabledRules(cat.rules || [], cat.category_enabled), runtime, "L2_rules", "pattern");
|
|
2515
|
+
return rules.map((r) => r.pattern || "");
|
|
2181
2516
|
}
|
|
2182
2517
|
function isL2CategoryEnabled(runtime, categoryKey) {
|
|
2183
2518
|
const cat = runtime.L2_rules?.[categoryKey];
|
|
@@ -2196,45 +2531,126 @@ function getDisabledSkillIds(runtime) {
|
|
|
2196
2531
|
}
|
|
2197
2532
|
return disabled;
|
|
2198
2533
|
}
|
|
2199
|
-
function
|
|
2200
|
-
const
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
if (
|
|
2209
|
-
|
|
2210
|
-
}
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
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;
|
|
2238
2654
|
}
|
|
2239
2655
|
function _validateSkillId(skillId) {
|
|
2240
2656
|
if (!skillId || !SKILL_ID_RE.test(skillId)) {
|
|
@@ -2251,7 +2667,7 @@ function updateRuleEnabled(layer, category, ruleId, enabled) {
|
|
|
2251
2667
|
const runtime = loadRuntimeConfig();
|
|
2252
2668
|
for (const r of runtime[layer]?.[category]?.rules || []) {
|
|
2253
2669
|
if (r.id === ruleId) {
|
|
2254
|
-
r
|
|
2670
|
+
_applyRuleEnabled(r, enabled);
|
|
2255
2671
|
break;
|
|
2256
2672
|
}
|
|
2257
2673
|
}
|
|
@@ -2321,7 +2737,7 @@ function deleteCustomRule(layer, category, ruleId) {
|
|
|
2321
2737
|
function addCustomSkill(skillId, category, keywords, content) {
|
|
2322
2738
|
_validateSkillId(skillId);
|
|
2323
2739
|
const runtime = loadRuntimeConfig();
|
|
2324
|
-
const skillsDir = join3(
|
|
2740
|
+
const skillsDir = join3(getRuntimeConfigDir(), "agent_moss_skills");
|
|
2325
2741
|
try {
|
|
2326
2742
|
mkdirSync(skillsDir, { recursive: true });
|
|
2327
2743
|
} catch {
|
|
@@ -2341,9 +2757,9 @@ function deleteCustomSkill(skillId, category) {
|
|
|
2341
2757
|
if (idx >= 0) {
|
|
2342
2758
|
if (skills[idx].builtin) return null;
|
|
2343
2759
|
skills.splice(idx, 1);
|
|
2344
|
-
const skillPath = join3(
|
|
2760
|
+
const skillPath = join3(getRuntimeConfigDir(), "agent_moss_skills", `${skillId}.md`);
|
|
2345
2761
|
try {
|
|
2346
|
-
if (
|
|
2762
|
+
if (existsSync5(skillPath)) unlinkSync(skillPath);
|
|
2347
2763
|
} catch {
|
|
2348
2764
|
}
|
|
2349
2765
|
saveRuntimeConfig(runtime);
|
|
@@ -2422,7 +2838,7 @@ function updateAgentRuleEnabled(agentId, category, ruleFp, enabled) {
|
|
|
2422
2838
|
const targetFp = _ruleFingerprint(ruleFp);
|
|
2423
2839
|
for (const r of cat.rules || []) {
|
|
2424
2840
|
if (_ruleFingerprint(r) === targetFp) {
|
|
2425
|
-
r
|
|
2841
|
+
_applyRuleEnabled(r, enabled);
|
|
2426
2842
|
break;
|
|
2427
2843
|
}
|
|
2428
2844
|
}
|
|
@@ -2457,130 +2873,412 @@ function updateAgentCategoryEnabled(agentId, category, enabled) {
|
|
|
2457
2873
|
saveRuntimeConfig(runtime);
|
|
2458
2874
|
return runtime;
|
|
2459
2875
|
}
|
|
2460
|
-
function getScenarioRules(runtime) {
|
|
2461
|
-
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] || {};
|
|
2462
3201
|
}
|
|
2463
|
-
function
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
const cmdPatterns = _agentCategoryEnabledRules(scenData.extra_command_patterns).filter((r) => r.pattern).map((r) => ({
|
|
2467
|
-
pattern: r.pattern,
|
|
2468
|
-
risk_level: r.risk_level || "",
|
|
2469
|
-
risk_type: r.risk_type || "",
|
|
2470
|
-
reason: r.reason || ""
|
|
2471
|
-
}));
|
|
2472
|
-
const injKeywords = _agentCategoryEnabledRules(scenData.extra_injection_keywords).filter((r) => r.keyword).map((r) => ({
|
|
2473
|
-
keyword: r.keyword,
|
|
2474
|
-
risk_level: r.risk_level || "",
|
|
2475
|
-
lang: r.lang || "en"
|
|
2476
|
-
}));
|
|
2477
|
-
const sensitivePaths = _agentCategoryEnabledRules(scenData.extra_sensitive_paths).filter((r) => r.path).map((r) => ({
|
|
2478
|
-
path: r.path,
|
|
2479
|
-
risk_level: r.risk_level || "",
|
|
2480
|
-
desc: r.desc || "",
|
|
2481
|
-
credential: r.credential,
|
|
2482
|
-
read_only: r.read_only,
|
|
2483
|
-
deny_mode: r.deny_mode
|
|
2484
|
-
}));
|
|
2485
|
-
return {
|
|
2486
|
-
agent_id: scenarioId,
|
|
2487
|
-
description: "",
|
|
2488
|
-
extra_command_patterns: cmdPatterns,
|
|
2489
|
-
extra_injection_keywords: injKeywords,
|
|
2490
|
-
extra_sensitive_paths: sensitivePaths,
|
|
2491
|
-
builtin_safe_skills: [],
|
|
2492
|
-
builtin_safe_action_types: [],
|
|
2493
|
-
extra_skills: []
|
|
2494
|
-
};
|
|
3202
|
+
function isHeuristicEnabled() {
|
|
3203
|
+
if (process.env.AGENT_MOSS_DISABLE_HEURISTIC === "1") return false;
|
|
3204
|
+
return layerEnabledOrDefault("L1_heuristic");
|
|
2495
3205
|
}
|
|
2496
|
-
function
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
for (const r of cat.rules || []) {
|
|
2502
|
-
if (_ruleFingerprint(r) === targetFp) {
|
|
2503
|
-
r.enabled = enabled;
|
|
2504
|
-
break;
|
|
2505
|
-
}
|
|
2506
|
-
}
|
|
3206
|
+
function layerEnabledOrDefault(key) {
|
|
3207
|
+
try {
|
|
3208
|
+
return isLayerEnabled(key);
|
|
3209
|
+
} catch {
|
|
3210
|
+
return true;
|
|
2507
3211
|
}
|
|
2508
|
-
saveRuntimeConfig(runtime);
|
|
2509
|
-
return runtime;
|
|
2510
3212
|
}
|
|
2511
|
-
function
|
|
2512
|
-
|
|
2513
|
-
if (!runtime.scenario_rules) runtime.scenario_rules = {};
|
|
2514
|
-
if (!runtime.scenario_rules[scenarioId]) runtime.scenario_rules[scenarioId] = {};
|
|
2515
|
-
const cats = runtime.scenario_rules[scenarioId];
|
|
2516
|
-
if (!cats[category]) cats[category] = { category_enabled: true, category_desc: "", rules: [] };
|
|
2517
|
-
(cats[category].rules || (cats[category].rules = [])).push({ ...rule, enabled: true, builtin: false });
|
|
2518
|
-
saveRuntimeConfig(runtime);
|
|
2519
|
-
return runtime;
|
|
3213
|
+
function isBrainEnabled() {
|
|
3214
|
+
return process.env.AGENT_MOSS_BRAIN_ENABLED === "1";
|
|
2520
3215
|
}
|
|
2521
|
-
function
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
if (cat) {
|
|
2525
|
-
const targetFp = _ruleFingerprint(ruleFp);
|
|
2526
|
-
cat.rules = (cat.rules || []).filter((r) => _ruleFingerprint(r) !== targetFp);
|
|
2527
|
-
}
|
|
2528
|
-
saveRuntimeConfig(runtime);
|
|
2529
|
-
return runtime;
|
|
3216
|
+
function isLogicRulesEnabled() {
|
|
3217
|
+
if (process.env.AGENT_MOSS_DISABLE_LOGIC_RULES === "1") return false;
|
|
3218
|
+
return layerEnabledOrDefault("L2_logic_rules");
|
|
2530
3219
|
}
|
|
2531
|
-
function
|
|
2532
|
-
|
|
2533
|
-
const cat = runtime.scenario_rules?.[scenarioId]?.[category];
|
|
2534
|
-
if (cat) cat.category_enabled = enabled;
|
|
2535
|
-
saveRuntimeConfig(runtime);
|
|
2536
|
-
return runtime;
|
|
3220
|
+
function isFormalObserveMode() {
|
|
3221
|
+
return process.env.AGENT_MOSS_FORMAL_OBSERVE === "1";
|
|
2537
3222
|
}
|
|
2538
|
-
var
|
|
2539
|
-
var
|
|
2540
|
-
"src/
|
|
3223
|
+
var timeoutConfig, retryConfig, llmConfig, __configModuleDir, USER_RULES_PATHS, customRules, PROVIDER_HEADERS;
|
|
3224
|
+
var init_config = __esm({
|
|
3225
|
+
"src/config.ts"() {
|
|
2541
3226
|
"use strict";
|
|
2542
3227
|
init_esm_shims();
|
|
2543
3228
|
init_paths();
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
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
|
+
}
|
|
2567
3271
|
};
|
|
2568
|
-
runtimeConfigCache = null;
|
|
2569
|
-
_AGENTS_DIR_PROD = join3(__dirname4, "agents");
|
|
2570
|
-
_AGENTS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "agents");
|
|
2571
|
-
_SCENARIOS_DIR_PROD = join3(__dirname4, "scenarios");
|
|
2572
|
-
_SCENARIOS_DIR_DEV = join3(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
|
|
2573
|
-
SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
|
|
2574
3272
|
}
|
|
2575
3273
|
});
|
|
2576
3274
|
|
|
2577
3275
|
// src/agents/index.ts
|
|
2578
|
-
import { readFileSync as readFileSync5, existsSync as
|
|
3276
|
+
import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
|
|
2579
3277
|
import { resolve as resolve4, dirname as dirname4 } from "path";
|
|
2580
3278
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
2581
3279
|
function resolveAgentsDir() {
|
|
2582
|
-
if (
|
|
2583
|
-
if (
|
|
3280
|
+
if (existsSync6(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
|
|
3281
|
+
if (existsSync6(AGENTS_DIR_DEV_FALLBACK)) return AGENTS_DIR_DEV_FALLBACK;
|
|
2584
3282
|
return AGENTS_DIR_PROD;
|
|
2585
3283
|
}
|
|
2586
3284
|
function getAgentExtensions(agentId) {
|
|
@@ -2588,16 +3286,15 @@ function getAgentExtensions(agentId) {
|
|
|
2588
3286
|
if (cache.has(agentId)) return cache.get(agentId) ?? null;
|
|
2589
3287
|
let ext = null;
|
|
2590
3288
|
try {
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
ext = getEnabledAgentExtensions2(loadRuntimeConfig2(), agentId);
|
|
3289
|
+
if (hasRuntimeConfig()) {
|
|
3290
|
+
ext = getEnabledAgentExtensions(loadRuntimeConfig(), agentId);
|
|
2594
3291
|
}
|
|
2595
3292
|
} catch {
|
|
2596
3293
|
}
|
|
2597
3294
|
if (ext === null) {
|
|
2598
3295
|
const agentsDir = resolveAgentsDir();
|
|
2599
3296
|
const rulesFile = resolve4(agentsDir, agentId, "rules.json");
|
|
2600
|
-
if (!
|
|
3297
|
+
if (!existsSync6(rulesFile)) {
|
|
2601
3298
|
cache.set(agentId, null);
|
|
2602
3299
|
return null;
|
|
2603
3300
|
}
|
|
@@ -2616,7 +3313,7 @@ function getAgentExtensions(agentId) {
|
|
|
2616
3313
|
}
|
|
2617
3314
|
}
|
|
2618
3315
|
cache.set(agentId, ext);
|
|
2619
|
-
console.
|
|
3316
|
+
console.error(
|
|
2620
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`
|
|
2621
3318
|
);
|
|
2622
3319
|
return ext;
|
|
@@ -2641,6 +3338,7 @@ var init_agents = __esm({
|
|
|
2641
3338
|
"src/agents/index.ts"() {
|
|
2642
3339
|
"use strict";
|
|
2643
3340
|
init_esm_shims();
|
|
3341
|
+
init_runtime_config();
|
|
2644
3342
|
__filename4 = fileURLToPath6(import.meta.url);
|
|
2645
3343
|
__dirname5 = dirname4(__filename4);
|
|
2646
3344
|
AGENTS_DIR_PROD = resolve4(__dirname5, "agents");
|
|
@@ -2650,12 +3348,12 @@ var init_agents = __esm({
|
|
|
2650
3348
|
});
|
|
2651
3349
|
|
|
2652
3350
|
// src/scenarios/index.ts
|
|
2653
|
-
import { readFileSync as readFileSync6, existsSync as
|
|
3351
|
+
import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
|
|
2654
3352
|
import { resolve as resolve5, dirname as dirname5 } from "path";
|
|
2655
3353
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
2656
3354
|
function resolveScenariosDir() {
|
|
2657
|
-
if (
|
|
2658
|
-
if (
|
|
3355
|
+
if (existsSync7(SCENARIOS_DIR_PROD)) return SCENARIOS_DIR_PROD;
|
|
3356
|
+
if (existsSync7(SCENARIOS_DIR_DEV_FALLBACK)) return SCENARIOS_DIR_DEV_FALLBACK;
|
|
2659
3357
|
return SCENARIOS_DIR_PROD;
|
|
2660
3358
|
}
|
|
2661
3359
|
function getScenarioExtensions(scenarioId) {
|
|
@@ -2663,16 +3361,15 @@ function getScenarioExtensions(scenarioId) {
|
|
|
2663
3361
|
if (cache2.has(scenarioId)) return cache2.get(scenarioId) ?? null;
|
|
2664
3362
|
let ext = null;
|
|
2665
3363
|
try {
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
ext = getEnabledScenarioExtensions2(loadRuntimeConfig2(), scenarioId);
|
|
3364
|
+
if (hasRuntimeConfig()) {
|
|
3365
|
+
ext = getEnabledScenarioExtensions(loadRuntimeConfig(), scenarioId);
|
|
2669
3366
|
}
|
|
2670
3367
|
} catch {
|
|
2671
3368
|
}
|
|
2672
3369
|
if (ext === null) {
|
|
2673
3370
|
const scenariosDir = resolveScenariosDir();
|
|
2674
3371
|
const rulesFile = resolve5(scenariosDir, scenarioId, "rules.json");
|
|
2675
|
-
if (!
|
|
3372
|
+
if (!existsSync7(rulesFile)) {
|
|
2676
3373
|
cache2.set(scenarioId, null);
|
|
2677
3374
|
return null;
|
|
2678
3375
|
}
|
|
@@ -2702,7 +3399,7 @@ function getScenarioExtensions(scenarioId) {
|
|
|
2702
3399
|
}
|
|
2703
3400
|
}
|
|
2704
3401
|
cache2.set(scenarioId, ext);
|
|
2705
|
-
console.
|
|
3402
|
+
console.error(
|
|
2706
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`
|
|
2707
3404
|
);
|
|
2708
3405
|
return ext;
|
|
@@ -2722,6 +3419,7 @@ var init_scenarios = __esm({
|
|
|
2722
3419
|
"use strict";
|
|
2723
3420
|
init_esm_shims();
|
|
2724
3421
|
init_agents();
|
|
3422
|
+
init_runtime_config();
|
|
2725
3423
|
__filename5 = fileURLToPath7(import.meta.url);
|
|
2726
3424
|
__dirname6 = dirname5(__filename5);
|
|
2727
3425
|
SCENARIOS_DIR_PROD = resolve5(__dirname6, "scenarios");
|
|
@@ -2731,6 +3429,62 @@ var init_scenarios = __esm({
|
|
|
2731
3429
|
});
|
|
2732
3430
|
|
|
2733
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
|
+
}
|
|
2734
3488
|
function runHeuristicCheck(input) {
|
|
2735
3489
|
const command = `${input.aNext.actionType}: ${input.aNext.actionDetail}`;
|
|
2736
3490
|
const matched = [];
|
|
@@ -2757,11 +3511,23 @@ function runHeuristicCheck(input) {
|
|
|
2757
3511
|
}
|
|
2758
3512
|
}
|
|
2759
3513
|
const scanDetail = normalizePathSegments(stripLiteralTextSegments(input.aNext.actionDetail));
|
|
2760
|
-
const
|
|
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
|
+
}
|
|
2761
3527
|
let topRiskType = "";
|
|
2762
3528
|
const exemptKeygen = isSshKeygenGenerate(input.aNext.actionDetail);
|
|
2763
|
-
for (const rule of
|
|
2764
|
-
if (rule.pattern.test(
|
|
3529
|
+
for (const rule of _effectiveDangerousPatterns(input.osType)) {
|
|
3530
|
+
if (scanCommands.some((c) => rule.pattern.test(c))) {
|
|
2765
3531
|
if (exemptKeygen && rule.riskType === "file_access") continue;
|
|
2766
3532
|
matched.push(rule.description);
|
|
2767
3533
|
if (severityRank(rule.severity) > severityRank(highestSeverity)) {
|
|
@@ -2779,7 +3545,7 @@ function runHeuristicCheck(input) {
|
|
|
2779
3545
|
for (const rule of agentExt.extra_command_patterns) {
|
|
2780
3546
|
try {
|
|
2781
3547
|
const re = new RegExp(rule.pattern, "i");
|
|
2782
|
-
if (re.test(
|
|
3548
|
+
if (scanCommands.some((c) => re.test(c))) {
|
|
2783
3549
|
matched.push(`agent \u4E13\u5C5E\u89C4\u5219: ${rule.reason}`);
|
|
2784
3550
|
if (severityRank(rule.risk_level) > severityRank(highestSeverity)) {
|
|
2785
3551
|
highestSeverity = rule.risk_level;
|
|
@@ -2791,7 +3557,7 @@ function runHeuristicCheck(input) {
|
|
|
2791
3557
|
}
|
|
2792
3558
|
}
|
|
2793
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();
|
|
2794
|
-
const injectionHits = checkInjectionKeywords(injectionText, agentExt?.extra_injection_keywords);
|
|
3560
|
+
const injectionHits = checkInjectionKeywords(injectionText, agentExt?.extra_injection_keywords, input.osType);
|
|
2795
3561
|
if (injectionHits.length > 0) {
|
|
2796
3562
|
const topHit = injectionHits.reduce((a, b) => severityRank(a.riskLevel) >= severityRank(b.riskLevel) ? a : b);
|
|
2797
3563
|
matched.push(...injectionHits.map((h) => `\u6CE8\u5165\u5173\u952E\u8BCD [${h.lang}]: ${h.keyword}`));
|
|
@@ -2804,7 +3570,7 @@ function runHeuristicCheck(input) {
|
|
|
2804
3570
|
const detailL = input.aNext.actionDetail.toLowerCase();
|
|
2805
3571
|
const isExcludedType = SENSITIVE_EXCLUDED_PREFIXES.some((p) => actionTypeL.startsWith(p));
|
|
2806
3572
|
const dangerousResult = checkDangerousPatterns(actionTypeL, detailL);
|
|
2807
|
-
const pathResult = isExcludedType ? null : checkSensitivePath(detailL, input.cwd || "/", agentExt);
|
|
3573
|
+
const pathResult = isExcludedType ? null : checkSensitivePath(detailL, input.cwd || "/", agentExt, input.osType);
|
|
2808
3574
|
const homeResult = checkHomeDeletion(actionTypeL, detailL);
|
|
2809
3575
|
const mergeResults = [dangerousResult, pathResult, homeResult].filter(Boolean);
|
|
2810
3576
|
if (mergeResults.length > 0) {
|
|
@@ -2840,9 +3606,10 @@ function runHeuristicCheck(input) {
|
|
|
2840
3606
|
if (lateralResult) return lateralResult;
|
|
2841
3607
|
return null;
|
|
2842
3608
|
}
|
|
2843
|
-
function checkInjectionKeywords(text, extraKeywords) {
|
|
3609
|
+
function checkInjectionKeywords(text, extraKeywords, osType) {
|
|
2844
3610
|
const hits = [];
|
|
2845
|
-
|
|
3611
|
+
const baseKeywords = runtimeConfigBridge.current?.effectiveInjectionKeywords(osType) ?? INJECTION_KEYWORDS;
|
|
3612
|
+
for (const entry of baseKeywords) {
|
|
2846
3613
|
if (text.includes(entry.keyword.toLowerCase())) {
|
|
2847
3614
|
hits.push(entry);
|
|
2848
3615
|
}
|
|
@@ -2902,7 +3669,7 @@ function buildPathContext(actionDetail) {
|
|
|
2902
3669
|
if (!PATH_CONTEXT_TRIGGER_RE.test(actionDetail)) return "";
|
|
2903
3670
|
const stripped = stripLiteralTextSegments(actionDetail);
|
|
2904
3671
|
const lines = [];
|
|
2905
|
-
for (const seg of
|
|
3672
|
+
for (const seg of splitCommandSegments2(stripped)) {
|
|
2906
3673
|
for (const [raw, lex] of resolvePathTokens(seg)) {
|
|
2907
3674
|
lines.push(`- \u547D\u4EE4\u6BB5 \`${seg.trim().slice(0, 60)}\`\uFF1A\`${raw}\` \u8BCD\u6CD5\u89E3\u6790\u4E3A \`${lex}\``);
|
|
2908
3675
|
}
|
|
@@ -2917,7 +3684,7 @@ function stripLiteralTextSegments(actionDetail) {
|
|
|
2917
3684
|
return actionDetail;
|
|
2918
3685
|
}
|
|
2919
3686
|
const resultParts = [];
|
|
2920
|
-
for (const seg of
|
|
3687
|
+
for (const seg of splitCommandSegments2(actionDetail)) {
|
|
2921
3688
|
const fw = (seg.trim().split(/\s+/)[0] || "").toLowerCase();
|
|
2922
3689
|
if (fw === "echo" || fw === "printf") {
|
|
2923
3690
|
const segUnquotedRedirect = seg.replace(
|
|
@@ -2937,7 +3704,7 @@ function stripHeredocContent(actionDetail) {
|
|
|
2937
3704
|
"<<heredoc>>\n"
|
|
2938
3705
|
);
|
|
2939
3706
|
}
|
|
2940
|
-
function normalizePathSegments(text) {
|
|
3707
|
+
function normalizePathSegments(text, foldQuotes = false) {
|
|
2941
3708
|
const protTokens = [];
|
|
2942
3709
|
let t = text.replace(/\b(https?|ftp|file):\/\//gi, (m) => {
|
|
2943
3710
|
const scheme = m.slice(0, m.indexOf(":"));
|
|
@@ -2945,10 +3712,12 @@ function normalizePathSegments(text) {
|
|
|
2945
3712
|
return `${scheme}__PROT${protTokens.length - 1}__`;
|
|
2946
3713
|
});
|
|
2947
3714
|
const quoteTokens = [];
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
3715
|
+
if (!foldQuotes) {
|
|
3716
|
+
t = t.replace(/'[^']*'|"[^"]*"/g, (m) => {
|
|
3717
|
+
quoteTokens.push(m);
|
|
3718
|
+
return `__Q${quoteTokens.length - 1}__`;
|
|
3719
|
+
});
|
|
3720
|
+
}
|
|
2952
3721
|
t = t.replace(/\/{2,}/g, "/");
|
|
2953
3722
|
t = t.replace(/\/\.\//g, "/");
|
|
2954
3723
|
for (let i = 0; i < quoteTokens.length; i++) {
|
|
@@ -2959,7 +3728,12 @@ function normalizePathSegments(text) {
|
|
|
2959
3728
|
}
|
|
2960
3729
|
return t;
|
|
2961
3730
|
}
|
|
2962
|
-
function
|
|
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) {
|
|
2963
3737
|
const segments = [];
|
|
2964
3738
|
let cur = "";
|
|
2965
3739
|
let quote = null;
|
|
@@ -3008,11 +3782,15 @@ function isSearchActionType(actionType) {
|
|
|
3008
3782
|
const at = actionType.toLowerCase();
|
|
3009
3783
|
return SEARCH_ACTION_PREFIXES.some((p) => at.startsWith(p)) || ["websearch", "web_search", "search", "query"].includes(at);
|
|
3010
3784
|
}
|
|
3011
|
-
function checkSensitivePath(command, cwd, agentExt) {
|
|
3785
|
+
function checkSensitivePath(command, cwd, agentExt, osType) {
|
|
3012
3786
|
const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
|
|
3013
|
-
const
|
|
3787
|
+
const { root: rootPaths, home: homePaths, credential: credentialPaths } = getSensitivePathsForOs(osType);
|
|
3788
|
+
const isWriteOpFlag = isWriteOperation("", command, void 0, osType);
|
|
3014
3789
|
const keygenGenerate = isSshKeygenGenerate(command);
|
|
3015
|
-
const
|
|
3790
|
+
const pathScanTexts = pathScanVariants(command);
|
|
3791
|
+
if (String(osType || "").toLowerCase() === "windows") {
|
|
3792
|
+
pathScanTexts.push(...pathScanTexts.map((t) => t.replace(/\//g, "\\")));
|
|
3793
|
+
}
|
|
3016
3794
|
const pathMatches = (text, path5) => {
|
|
3017
3795
|
const parts = path5.split(/(\(\?!.*?\))/);
|
|
3018
3796
|
const escaped = parts.map((part) => {
|
|
@@ -3022,8 +3800,9 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
3022
3800
|
const re = path5.startsWith(".") || path5.startsWith("/") ? new RegExp(`(?:^|[\\s/\\\\'"])${escaped}\\b`, "i") : new RegExp(`\\b${escaped}\\b`, "i");
|
|
3023
3801
|
return re.test(text);
|
|
3024
3802
|
};
|
|
3025
|
-
|
|
3026
|
-
|
|
3803
|
+
const pathMatchesAny = (path5) => pathScanTexts.some((t) => pathMatches(t, path5));
|
|
3804
|
+
for (const sp of rootPaths) {
|
|
3805
|
+
if (pathMatchesAny(sp.path.toLowerCase())) {
|
|
3027
3806
|
if (keygenGenerate) continue;
|
|
3028
3807
|
if (sp.path === "/dev/random") {
|
|
3029
3808
|
if (isWriteOpFlag) {
|
|
@@ -3039,9 +3818,9 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
3039
3818
|
violatedLayers: ["L1.1"]
|
|
3040
3819
|
};
|
|
3041
3820
|
}
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
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;
|
|
3045
3824
|
return {
|
|
3046
3825
|
allowed: false,
|
|
3047
3826
|
reason: `\u5C1D\u8BD5\u8BBF\u95EE\u7CFB\u7EDF\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
|
|
@@ -3054,17 +3833,19 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
3054
3833
|
};
|
|
3055
3834
|
}
|
|
3056
3835
|
}
|
|
3057
|
-
for (const sp of
|
|
3836
|
+
for (const sp of homePaths) {
|
|
3058
3837
|
const expanded = `${expandedCwd}/${sp.path}`;
|
|
3059
3838
|
let pPath = sp.path;
|
|
3060
3839
|
if (!sp.path.endsWith(".pub")) pPath = `${sp.path}(?!\\.pub)`;
|
|
3061
3840
|
let dirPrefixMatch = null;
|
|
3062
3841
|
if (sp.dirDelOnly) {
|
|
3063
3842
|
const dirRe = (t) => new RegExp(`(?:^|[\\s/\\\\'"])${t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:/|[\\s'"\\\\]|$)`, "i");
|
|
3064
|
-
dirPrefixMatch =
|
|
3843
|
+
dirPrefixMatch = pathScanTexts.some(
|
|
3844
|
+
(t) => dirRe(sp.path).test(t) || dirRe(expanded.toLowerCase()).test(t)
|
|
3845
|
+
);
|
|
3065
3846
|
}
|
|
3066
|
-
const pathReMatch = sp.dirDelOnly ? !!dirPrefixMatch :
|
|
3067
|
-
const expandedReMatch = sp.dirDelOnly ? false :
|
|
3847
|
+
const pathReMatch = sp.dirDelOnly ? !!dirPrefixMatch : pathMatchesAny(pPath);
|
|
3848
|
+
const expandedReMatch = sp.dirDelOnly ? false : pathMatchesAny(expanded + (sp.path.endsWith(".pub") ? "" : "(?!\\.pub)"));
|
|
3068
3849
|
if (pathReMatch || expandedReMatch) {
|
|
3069
3850
|
if (keygenGenerate) continue;
|
|
3070
3851
|
if (sp.dirDelOnly) {
|
|
@@ -3094,9 +3875,9 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
3094
3875
|
}
|
|
3095
3876
|
continue;
|
|
3096
3877
|
}
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
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;
|
|
3100
3881
|
return {
|
|
3101
3882
|
allowed: false,
|
|
3102
3883
|
reason: `\u5C1D\u8BD5\u8BBF\u95EE\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
|
|
@@ -3133,7 +3914,7 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
3133
3914
|
}
|
|
3134
3915
|
}
|
|
3135
3916
|
}
|
|
3136
|
-
for (const cp of
|
|
3917
|
+
for (const cp of credentialPaths) {
|
|
3137
3918
|
const escaped = cp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3138
3919
|
const re = cp.path.startsWith(".") ? new RegExp(`(?:^|[\\s/\\\\'"])${escaped}\\b`) : new RegExp(`\\b${escaped}\\b`);
|
|
3139
3920
|
if (re.test(command)) {
|
|
@@ -3253,7 +4034,7 @@ function isDeleteActionForDir(command, dirPath) {
|
|
|
3253
4034
|
}
|
|
3254
4035
|
for (const seg of low.split(/&&|\|\||;|\n/)) {
|
|
3255
4036
|
const fw = (seg.trim().split(/\s+/)[0] || "").toLowerCase();
|
|
3256
|
-
if ((fw === "rm" || fw === "unlink" || fw === "rmdir") &&
|
|
4037
|
+
if ((fw === "rm" || fw === "unlink" || fw === "rmdir") && seg.includes(dpath)) {
|
|
3257
4038
|
return true;
|
|
3258
4039
|
}
|
|
3259
4040
|
}
|
|
@@ -3436,7 +4217,7 @@ function checkLateralMovement(actionDetail) {
|
|
|
3436
4217
|
}
|
|
3437
4218
|
return null;
|
|
3438
4219
|
}
|
|
3439
|
-
var INJECTION_KEYWORDS,
|
|
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;
|
|
3440
4221
|
var init_heuristic = __esm({
|
|
3441
4222
|
"src/engine/heuristic.ts"() {
|
|
3442
4223
|
"use strict";
|
|
@@ -3445,6 +4226,7 @@ var init_heuristic = __esm({
|
|
|
3445
4226
|
init_agents();
|
|
3446
4227
|
init_scenarios();
|
|
3447
4228
|
init_runtime_config();
|
|
4229
|
+
init_runtime_config_bridge();
|
|
3448
4230
|
init_rules_loader();
|
|
3449
4231
|
init_logic_rules();
|
|
3450
4232
|
INJECTION_KEYWORDS = (() => {
|
|
@@ -3455,15 +4237,10 @@ var init_heuristic = __esm({
|
|
|
3455
4237
|
lang: k.lang || "en"
|
|
3456
4238
|
}));
|
|
3457
4239
|
})();
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
severity: c.risk_level,
|
|
3463
|
-
riskType: c.risk_type,
|
|
3464
|
-
description: c.reason
|
|
3465
|
-
}));
|
|
3466
|
-
})();
|
|
4240
|
+
DANGEROUS_PATTERNS2 = _loadDangerousPatterns("linux");
|
|
4241
|
+
DANGEROUS_PATTERNS_WINDOWS = _loadDangerousPatterns("windows");
|
|
4242
|
+
DANGEROUS_PATTERNS_MACOS = _loadDangerousPatterns("macos");
|
|
4243
|
+
DANGEROUS_PATTERNS_HARMONY = _loadDangerousPatterns("harmony");
|
|
3467
4244
|
PATH_CONTEXT_TRIGGER_RE = /(?:^|[;&|]\s*)(?:rm|rmdir|unlink|ln|mv|cp|dd|truncate|shred|chmod|chown)\b/i;
|
|
3468
4245
|
HOME_PREFIX_RE = /(?:~|~(?=\/)|\$HOME\b|\$\{HOME\}|\/home\/[a-z0-9_.-]+)/gi;
|
|
3469
4246
|
SENSITIVE_EXCLUDED_PREFIXES = ["memory-", "skill-", "skill-manager-", "mailbox-", "app-"];
|
|
@@ -3483,14 +4260,14 @@ __export(token_stats_exports, {
|
|
|
3483
4260
|
recordTokenUsage: () => recordTokenUsage,
|
|
3484
4261
|
resetTokenStats: () => resetTokenStats
|
|
3485
4262
|
});
|
|
3486
|
-
import { readFileSync as readFileSync11, writeFileSync as writeFileSync2, existsSync as
|
|
4263
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync2, existsSync as existsSync11, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
3487
4264
|
function getStatsPath() {
|
|
3488
4265
|
const envPath = process.env[ENV_STATS_PATH];
|
|
3489
4266
|
if (envPath) return envPath;
|
|
3490
4267
|
return getTokenStatsPath();
|
|
3491
4268
|
}
|
|
3492
4269
|
function loadAllRecords(statsPath) {
|
|
3493
|
-
if (!
|
|
4270
|
+
if (!existsSync11(statsPath)) return [];
|
|
3494
4271
|
try {
|
|
3495
4272
|
const data = JSON.parse(readFileSync11(statsPath, "utf-8"));
|
|
3496
4273
|
if (Array.isArray(data)) return data;
|
|
@@ -3503,7 +4280,7 @@ function loadAllRecords(statsPath) {
|
|
|
3503
4280
|
function saveRecords(statsPath, records) {
|
|
3504
4281
|
try {
|
|
3505
4282
|
const dir = statsPath.substring(0, statsPath.lastIndexOf("/"));
|
|
3506
|
-
if (dir && !
|
|
4283
|
+
if (dir && !existsSync11(dir)) mkdirSync2(dir, { recursive: true });
|
|
3507
4284
|
writeFileSync2(statsPath, JSON.stringify(records, null, 2), "utf-8");
|
|
3508
4285
|
} catch (e) {
|
|
3509
4286
|
}
|
|
@@ -3630,7 +4407,7 @@ function getTokenTrend(days = 0, startDate, endDate) {
|
|
|
3630
4407
|
}
|
|
3631
4408
|
function resetTokenStats() {
|
|
3632
4409
|
const statsPath = getStatsPath();
|
|
3633
|
-
if (
|
|
4410
|
+
if (existsSync11(statsPath)) {
|
|
3634
4411
|
try {
|
|
3635
4412
|
unlinkSync2(statsPath);
|
|
3636
4413
|
} catch {
|
|
@@ -3652,6 +4429,8 @@ var init_token_stats = __esm({
|
|
|
3652
4429
|
init_esm_shims();
|
|
3653
4430
|
init_heuristic();
|
|
3654
4431
|
init_logic_rules();
|
|
4432
|
+
init_platform_traits();
|
|
4433
|
+
init_shell_split();
|
|
3655
4434
|
init_inline_analyzer();
|
|
3656
4435
|
|
|
3657
4436
|
// src/engine/llm-analyzer.ts
|
|
@@ -3662,7 +4441,7 @@ init_skill_loader();
|
|
|
3662
4441
|
// src/engine/template-loader.ts
|
|
3663
4442
|
init_esm_shims();
|
|
3664
4443
|
init_asset_dir();
|
|
3665
|
-
import { readFileSync as readFileSync7, existsSync as
|
|
4444
|
+
import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
|
|
3666
4445
|
import { resolve as resolve6, dirname as dirname6 } from "path";
|
|
3667
4446
|
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
3668
4447
|
var __filename6 = fileURLToPath8(import.meta.url);
|
|
@@ -3680,7 +4459,7 @@ function loadTemplates() {
|
|
|
3680
4459
|
}
|
|
3681
4460
|
for (const file of TEMPLATE_FILES) {
|
|
3682
4461
|
const filePath = resolve6(dir, file);
|
|
3683
|
-
if (
|
|
4462
|
+
if (existsSync8(filePath)) {
|
|
3684
4463
|
const key = file.replace(".txt", "").replace("_template", "");
|
|
3685
4464
|
cachedTemplates[key] = readFileSync7(filePath, "utf-8");
|
|
3686
4465
|
}
|
|
@@ -3825,7 +4604,7 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
|
|
|
3825
4604
|
}
|
|
3826
4605
|
function readScriptContent(scriptPath, maxLines = 500) {
|
|
3827
4606
|
try {
|
|
3828
|
-
const expandedPath = scriptPath.startsWith("
|
|
4607
|
+
const expandedPath = scriptPath === "~" || scriptPath.startsWith("~/") ? join4(process.env.HOME || "/home/user", scriptPath.slice(1)) : resolve7(scriptPath);
|
|
3829
4608
|
const raw = readFileSync8(expandedPath, "utf-8");
|
|
3830
4609
|
const lines = raw.split("\n").slice(0, maxLines);
|
|
3831
4610
|
return { content: lines.join("\n"), lineCount: lines.length, error: "" };
|
|
@@ -3879,11 +4658,12 @@ ${result.scriptContent}
|
|
|
3879
4658
|
// src/engine/script-content-analyzer.ts
|
|
3880
4659
|
init_esm_shims();
|
|
3881
4660
|
init_heuristic();
|
|
4661
|
+
init_config();
|
|
3882
4662
|
init_logic_rules();
|
|
3883
4663
|
init_inline_analyzer();
|
|
3884
|
-
import { statSync as statSync3, existsSync as
|
|
4664
|
+
import { statSync as statSync3, existsSync as existsSync10 } from "fs";
|
|
3885
4665
|
import { resolve as resolve9, join as join6, isAbsolute as isAbsolute2 } from "path";
|
|
3886
|
-
import { homedir as
|
|
4666
|
+
import { homedir as homedir3 } from "os";
|
|
3887
4667
|
|
|
3888
4668
|
// src/engine/script-engine/tracker.ts
|
|
3889
4669
|
init_esm_shims();
|
|
@@ -4710,7 +5490,7 @@ function tokenize(content) {
|
|
|
4710
5490
|
}
|
|
4711
5491
|
return stmts;
|
|
4712
5492
|
}
|
|
4713
|
-
var EXPR_SPLIT_RE = /(\$\{
|
|
5493
|
+
var EXPR_SPLIT_RE = /(\$\{[^}]*\}|\$[A-Za-z_][A-Za-z0-9_]*|\$\d+)/;
|
|
4714
5494
|
function evalExpr2(text, env) {
|
|
4715
5495
|
const isSingleQuoted = text.length >= 2 && text[0] === "'" && text[text.length - 1] === "'";
|
|
4716
5496
|
if (text.length >= 2 && (text[0] === '"' || text[0] === "'") && text[text.length - 1] === text[0]) {
|
|
@@ -4721,9 +5501,20 @@ function evalExpr2(text, env) {
|
|
|
4721
5501
|
const result = [];
|
|
4722
5502
|
for (const p of parts) {
|
|
4723
5503
|
if (!p) continue;
|
|
4724
|
-
const brace = p.match(/^\$\{([^}]
|
|
5504
|
+
const brace = p.match(/^\$\{([^}]*)\}$/);
|
|
4725
5505
|
if (brace) {
|
|
4726
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
|
+
}
|
|
4727
5518
|
if (/^\d+$/.test(inner)) {
|
|
4728
5519
|
const v = env.get(`$${inner}`);
|
|
4729
5520
|
result.push(v || svalSym(`$${inner}`));
|
|
@@ -5176,7 +5967,15 @@ var ShellBackend = class {
|
|
|
5176
5967
|
// src/engine/script-engine/tracker.ts
|
|
5177
5968
|
var DEFAULT_MAX_DEPTH = 14;
|
|
5178
5969
|
var DEFAULT_MAX_NODES = 64;
|
|
5179
|
-
|
|
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();
|
|
5180
5979
|
var DEFAULT_MAX_TOTAL_SIZE = 8 * 1024 * 1024;
|
|
5181
5980
|
var BACKENDS = {
|
|
5182
5981
|
".py": new PythonBackend()
|
|
@@ -5224,6 +6023,7 @@ function emptyNode(pathStr, depth) {
|
|
|
5224
6023
|
depth,
|
|
5225
6024
|
childPaths: [],
|
|
5226
6025
|
error: "",
|
|
6026
|
+
unanalyzable: false,
|
|
5227
6027
|
l1Hit: false,
|
|
5228
6028
|
l1RiskLevel: "",
|
|
5229
6029
|
l1RiskType: "",
|
|
@@ -5305,6 +6105,7 @@ var RecursiveTracker = class {
|
|
|
5305
6105
|
node2.error = error;
|
|
5306
6106
|
node2.fileSize = fileSize;
|
|
5307
6107
|
node2.lineCount = lineCount;
|
|
6108
|
+
node2.unanalyzable = error.startsWith("\u811A\u672C\u6587\u4EF6\u8FC7\u5927");
|
|
5308
6109
|
this.result.nodes.push(node2);
|
|
5309
6110
|
this.visiting.delete(pathStr);
|
|
5310
6111
|
return;
|
|
@@ -5536,12 +6337,12 @@ function extractInlineScriptPath(actionDetail) {
|
|
|
5536
6337
|
const subShellRe = /subprocess\.(?:run|call|Popen|check_output|check_call)\s*\(\s*(['"])(.*?)\1[^)]*?shell\s*=\s*true/gi;
|
|
5537
6338
|
while ((m = subShellRe.exec(code)) !== null) candidates.push(m[2]);
|
|
5538
6339
|
for (const cand of candidates) {
|
|
5539
|
-
const tokens = cand.includes(",") ? cand.split(/\s*,\s*/).map((t) => t.trim().replace(/^['"]|['"]$/g, "")) : cand
|
|
6340
|
+
const tokens = cand.includes(",") ? cand.split(/\s*,\s*/).map((t) => t.trim().replace(/^['"]|['"]$/g, "")) : shlexSplit(cand);
|
|
5540
6341
|
for (const tok of tokens) {
|
|
5541
6342
|
if (looksLikeScriptPath(tok)) {
|
|
5542
6343
|
try {
|
|
5543
6344
|
const resolved = resolvePath2(tok.replace(/^['"]|['"]$/g, ""));
|
|
5544
|
-
if (
|
|
6345
|
+
if (existsSync10(resolved)) {
|
|
5545
6346
|
const stat = statSync3(resolved);
|
|
5546
6347
|
if (stat.isFile()) return resolved;
|
|
5547
6348
|
}
|
|
@@ -5555,7 +6356,7 @@ function extractInlineScriptPath(actionDetail) {
|
|
|
5555
6356
|
const p = m[1];
|
|
5556
6357
|
try {
|
|
5557
6358
|
const resolved = resolvePath2(p);
|
|
5558
|
-
if (
|
|
6359
|
+
if (existsSync10(resolved)) {
|
|
5559
6360
|
const stat = statSync3(resolved);
|
|
5560
6361
|
if (stat.isFile()) return resolved;
|
|
5561
6362
|
}
|
|
@@ -5571,7 +6372,7 @@ function extractInlineScriptPath(actionDetail) {
|
|
|
5571
6372
|
let resolved;
|
|
5572
6373
|
try {
|
|
5573
6374
|
resolved = resolvePath2(p);
|
|
5574
|
-
if (
|
|
6375
|
+
if (existsSync10(resolved)) {
|
|
5575
6376
|
const stat = statSync3(resolved);
|
|
5576
6377
|
if (stat.isFile()) return resolved;
|
|
5577
6378
|
}
|
|
@@ -5580,10 +6381,39 @@ function extractInlineScriptPath(actionDetail) {
|
|
|
5580
6381
|
}
|
|
5581
6382
|
return null;
|
|
5582
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
|
+
}
|
|
5583
6413
|
function resolvePath2(p) {
|
|
5584
6414
|
let expanded = p;
|
|
5585
6415
|
if (expanded.startsWith("~/") || expanded === "~") {
|
|
5586
|
-
expanded = join6(
|
|
6416
|
+
expanded = join6(homedir3(), expanded.slice(1));
|
|
5587
6417
|
}
|
|
5588
6418
|
return resolve9(expanded);
|
|
5589
6419
|
}
|
|
@@ -5594,7 +6424,7 @@ function scanScriptChainWithL1(chain, input) {
|
|
|
5594
6424
|
...input,
|
|
5595
6425
|
aNext: { actionType: "bash", actionDetail: node.preprocessedContent }
|
|
5596
6426
|
};
|
|
5597
|
-
const l1 =
|
|
6427
|
+
const l1 = scanDangerousCommandPatterns("bash", node.preprocessedContent, input.osType);
|
|
5598
6428
|
if (l1 && !l1.allowed) {
|
|
5599
6429
|
node.l1Hit = true;
|
|
5600
6430
|
node.l1RiskLevel = l1.riskLevel;
|
|
@@ -5617,18 +6447,25 @@ function scanScriptChainWithL2(chain, input) {
|
|
|
5617
6447
|
...input,
|
|
5618
6448
|
aNext: { actionType: "bash", actionDetail: node.preprocessedContent }
|
|
5619
6449
|
};
|
|
5620
|
-
|
|
5621
|
-
if (
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
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;
|
|
5632
6469
|
}
|
|
5633
6470
|
}
|
|
5634
6471
|
if (!chain.hasHigh) {
|
|
@@ -5667,13 +6504,16 @@ function analyzeScriptContent2(input) {
|
|
|
5667
6504
|
resolvedPath = join6(input.cwd || process.cwd(), resolvedPath);
|
|
5668
6505
|
}
|
|
5669
6506
|
const l1Adapter = (content) => {
|
|
5670
|
-
const
|
|
5671
|
-
...input,
|
|
5672
|
-
aNext: { actionType: "bash", actionDetail: content }
|
|
5673
|
-
};
|
|
5674
|
-
const j = runHeuristicCheck(virtualInput);
|
|
6507
|
+
const j = scanDangerousCommandPatterns("bash", content, input.osType);
|
|
5675
6508
|
if (j && !j.allowed) {
|
|
5676
|
-
return {
|
|
6509
|
+
return {
|
|
6510
|
+
hit: true,
|
|
6511
|
+
riskLevel: j.riskLevel,
|
|
6512
|
+
riskType: j.riskType,
|
|
6513
|
+
reason: j.reason,
|
|
6514
|
+
matchedPatterns: []
|
|
6515
|
+
// SecurityJudgment 不带模式列表(见 types.ts);留空即可
|
|
6516
|
+
};
|
|
5677
6517
|
}
|
|
5678
6518
|
return { hit: false, riskLevel: "", riskType: "", reason: "", matchedPatterns: [] };
|
|
5679
6519
|
};
|
|
@@ -5984,10 +6824,10 @@ init_esm_shims();
|
|
|
5984
6824
|
|
|
5985
6825
|
// src/brain/rule-store.ts
|
|
5986
6826
|
init_esm_shims();
|
|
5987
|
-
import { existsSync as
|
|
6827
|
+
import { existsSync as existsSync12, readFileSync as readFileSync12, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
5988
6828
|
import { join as join7 } from "path";
|
|
5989
|
-
import { homedir as
|
|
5990
|
-
var DEFAULT_RULES_PATH = join7(
|
|
6829
|
+
import { homedir as homedir4 } from "os";
|
|
6830
|
+
var DEFAULT_RULES_PATH = join7(homedir4(), ".agentmoss", "brain", "rules.json");
|
|
5991
6831
|
var ENV_RULES_PATH = "AGENT_MOSS_BRAIN_RULES_PATH";
|
|
5992
6832
|
var STATUS_DRAFT = "draft";
|
|
5993
6833
|
var STATUS_APPROVED = "approved";
|
|
@@ -6016,7 +6856,7 @@ var BrainRuleStore = class {
|
|
|
6016
6856
|
this.data = this.load();
|
|
6017
6857
|
}
|
|
6018
6858
|
load() {
|
|
6019
|
-
if (!
|
|
6859
|
+
if (!existsSync12(this.path)) {
|
|
6020
6860
|
return { version: 1, rules: [], pattern_candidates: [] };
|
|
6021
6861
|
}
|
|
6022
6862
|
try {
|
|
@@ -7454,63 +8294,15 @@ var READONLY_SENSITIVE_TOOLS = /* @__PURE__ */ new Set([
|
|
|
7454
8294
|
"filemgr-readfile",
|
|
7455
8295
|
"filemgr-grepfiles"
|
|
7456
8296
|
]);
|
|
7457
|
-
|
|
7458
|
-
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
|
|
7465
|
-
|
|
7466
|
-
/^file\s+/i,
|
|
7467
|
-
/^stat\s+/i,
|
|
7468
|
-
/^du(?:\s|$)/i,
|
|
7469
|
-
/^echo\s+/i,
|
|
7470
|
-
/^printf\s+/i,
|
|
7471
|
-
/^type\s+/i,
|
|
7472
|
-
/^command\s+/i,
|
|
7473
|
-
// 系统信息查询(只读,不涉及文件/网络/权限)
|
|
7474
|
-
/^whoami(?:\s|$)/i,
|
|
7475
|
-
/^id(?:\s|$)/i,
|
|
7476
|
-
/^hostname(?:\s|$)/i,
|
|
7477
|
-
/^uname(?:\s|$)/i,
|
|
7478
|
-
/^date(?:\s|$)/i,
|
|
7479
|
-
/^env(?:\s|$)/i,
|
|
7480
|
-
/^printenv(?:\s|$)/i,
|
|
7481
|
-
/^tty(?:\s|$)/i,
|
|
7482
|
-
/^arch(?:\s|$)/i,
|
|
7483
|
-
/^uptime(?:\s|$)/i,
|
|
7484
|
-
/^groups(?:\s|$)/i,
|
|
7485
|
-
/^logname(?:\s|$)/i
|
|
7486
|
-
];
|
|
7487
|
-
var READONLY_SENSITIVE_BASH_PATTERNS = [
|
|
7488
|
-
/^cat\s+/i,
|
|
7489
|
-
/^head(?:\s|$)/i,
|
|
7490
|
-
/^tail(?:\s|$)/i,
|
|
7491
|
-
/^less(?:\s|$)/i,
|
|
7492
|
-
/^more(?:\s|$)/i,
|
|
7493
|
-
/^wc(?:\s|$)/i,
|
|
7494
|
-
/^grep(?:\s|$)/i,
|
|
7495
|
-
/^find\s+/i,
|
|
7496
|
-
/^ag(?:\s|$)/i,
|
|
7497
|
-
/^rg(?:\s|$)/i,
|
|
7498
|
-
/^awk\s+/i,
|
|
7499
|
-
/^sed\s+/i,
|
|
7500
|
-
/^cut\s+/i,
|
|
7501
|
-
/^sort(?:\s|$)/i,
|
|
7502
|
-
/^uniq(?:\s|$)/i
|
|
7503
|
-
];
|
|
7504
|
-
function extractFirstCommand(command) {
|
|
7505
|
-
let main = command.split("|")[0].trim();
|
|
7506
|
-
for (const sep of ["&&", "||"]) {
|
|
7507
|
-
const idx = main.indexOf(sep);
|
|
7508
|
-
if (idx !== -1) {
|
|
7509
|
-
main = main.substring(0, idx).trim();
|
|
7510
|
-
break;
|
|
7511
|
-
}
|
|
7512
|
-
}
|
|
7513
|
-
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 "";
|
|
7514
8306
|
}
|
|
7515
8307
|
function extractRawCommand(detail) {
|
|
7516
8308
|
const m = detail.match(/command=([^,]+)/);
|
|
@@ -7545,7 +8337,7 @@ async function runAnalysis(input) {
|
|
|
7545
8337
|
if (heuristicResult && !heuristicResult.allowed) {
|
|
7546
8338
|
const skipInlineFileAccess = (isInlineScriptCmd || isLiteralTextCmd) && heuristicResult.riskType === "file_access";
|
|
7547
8339
|
if (!skipInlineFileAccess) {
|
|
7548
|
-
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";
|
|
7549
8341
|
if (shouldDenyL1) {
|
|
7550
8342
|
return heuristicResult;
|
|
7551
8343
|
}
|
|
@@ -7555,6 +8347,19 @@ async function runAnalysis(input) {
|
|
|
7555
8347
|
if (input.aNext.actionType.toLowerCase() === "bash") {
|
|
7556
8348
|
scriptChain = analyzeScriptContent2(input);
|
|
7557
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
|
+
}
|
|
7558
8363
|
for (const node of scriptChain.nodes) {
|
|
7559
8364
|
if (node.l1Hit && (node.l1RiskLevel === "high" || node.l1RiskLevel === "critical")) {
|
|
7560
8365
|
return {
|
|
@@ -7642,11 +8447,15 @@ async function runAnalysis(input) {
|
|
|
7642
8447
|
let skipLLM = READONLY_SENSITIVE_TOOLS.has(actionType);
|
|
7643
8448
|
if (actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash") {
|
|
7644
8449
|
const firstCmd = extractFirstCommand(lowerDetail);
|
|
7645
|
-
|
|
7646
|
-
|
|
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(
|
|
7647
8456
|
(p) => p.severity === "critical" || p.severity === "high"
|
|
7648
|
-
) &&
|
|
7649
|
-
(p) => (p.severity === "critical" || p.severity === "high") && p.pattern.test(
|
|
8457
|
+
) && osPatterns.some(
|
|
8458
|
+
(p) => (p.severity === "critical" || p.severity === "high") && p.pattern.test(fastPassScanText)
|
|
7650
8459
|
);
|
|
7651
8460
|
const hasRedirectWrite = isWriteOperation(firstCmd, lowerDetail);
|
|
7652
8461
|
if (!fullCmdHit && !hasRedirectWrite) {
|
|
@@ -7661,7 +8470,7 @@ async function runAnalysis(input) {
|
|
|
7661
8470
|
};
|
|
7662
8471
|
}
|
|
7663
8472
|
}
|
|
7664
|
-
if (
|
|
8473
|
+
if (tier !== "") {
|
|
7665
8474
|
skipLLM = true;
|
|
7666
8475
|
}
|
|
7667
8476
|
}
|
|
@@ -7670,8 +8479,8 @@ async function runAnalysis(input) {
|
|
|
7670
8479
|
const effectiveResult = logicResult && !logicResult.allowed ? logicResult : l1FileAccessHit ? heuristicResult : null;
|
|
7671
8480
|
if (effectiveResult) {
|
|
7672
8481
|
const isCredentialPath = !!effectiveResult.isCredential;
|
|
7673
|
-
const skipInlineFileAccess = (isInlineScriptCmd || isLiteralTextCmd) &&
|
|
7674
|
-
if (
|
|
8482
|
+
const skipInlineFileAccess = (isInlineScriptCmd || isLiteralTextCmd) && l1FileAccessHit;
|
|
8483
|
+
if (isInlineScriptCmd || isLiteralTextCmd) {
|
|
7675
8484
|
const inlineJudgment = analyzeInlineScript(input.aNext.actionDetail);
|
|
7676
8485
|
if (inlineJudgment.actionable) {
|
|
7677
8486
|
if (!inlineJudgment.allowed) {
|
|
@@ -7870,7 +8679,11 @@ async function runAnalysis(input) {
|
|
|
7870
8679
|
export {
|
|
7871
8680
|
TRUSTED_SKILLS_BASE,
|
|
7872
8681
|
_resetBrainForTest,
|
|
7873
|
-
|
|
8682
|
+
extractFirstCommand,
|
|
8683
|
+
matchesAny,
|
|
8684
|
+
runAnalysis,
|
|
8685
|
+
splitCommandSegments,
|
|
8686
|
+
whitelistTier
|
|
7874
8687
|
};
|
|
7875
8688
|
//! 顺序即 fail-closed 短路优先级:verify 逐个执行,首个返回违规的性质成为
|
|
7876
8689
|
//! first_violation,后续不再评估。调整顺序影响判定归属,须与 Python
|