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