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