@kenhkl/agent-moss 0.10.5 → 0.11.2
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 +617 -142
- package/dist/cli.js.map +1 -1
- package/dist/engine/coordinator.js +588 -113
- package/dist/engine/coordinator.js.map +1 -1
- package/dist/rules/user_rules.json +77 -0
- package/dist/server.js +612 -137
- package/dist/server.js.map +1 -1
- package/dist/static/index.html +21 -5
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -42,6 +42,90 @@ var init_esm_shims = __esm({
|
|
|
42
42
|
}
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
+
// src/infra/paths.ts
|
|
46
|
+
import { join } from "path";
|
|
47
|
+
import { homedir } from "os";
|
|
48
|
+
import { readFileSync } from "fs";
|
|
49
|
+
function isRoot() {
|
|
50
|
+
const getuid = process.getuid;
|
|
51
|
+
return typeof getuid === "function" && getuid() === 0;
|
|
52
|
+
}
|
|
53
|
+
function resolveOwnerHome() {
|
|
54
|
+
for (const name of [process.env.SUDO_USER, process.env.LOGNAME, process.env.USER]) {
|
|
55
|
+
if (name && name !== "root" && name !== "") {
|
|
56
|
+
const home = homeOf(name);
|
|
57
|
+
if (home) return home;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const passwd = readFileSync("/etc/passwd", "utf-8");
|
|
62
|
+
for (const line of passwd.split("\n")) {
|
|
63
|
+
if (!line || line.startsWith("#")) continue;
|
|
64
|
+
const parts = line.split(":");
|
|
65
|
+
if (parts.length < 6) continue;
|
|
66
|
+
const uid = parseInt(parts[2], 10);
|
|
67
|
+
const name = parts[0];
|
|
68
|
+
if (uid >= 1e3 && name && !name.startsWith("_")) {
|
|
69
|
+
return parts[5];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
} catch {
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
function homeOf(name) {
|
|
77
|
+
try {
|
|
78
|
+
const passwd = readFileSync("/etc/passwd", "utf-8");
|
|
79
|
+
for (const line of passwd.split("\n")) {
|
|
80
|
+
if (!line || line.startsWith("#")) continue;
|
|
81
|
+
const parts = line.split(":");
|
|
82
|
+
if (parts.length >= 6 && parts[0] === name) {
|
|
83
|
+
return parts[5];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} catch {
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
function getDataRoot() {
|
|
91
|
+
const envPath = process.env[ENV_DATA_ROOT];
|
|
92
|
+
if (envPath) return envPath;
|
|
93
|
+
if (isRoot()) {
|
|
94
|
+
const ownerHome = resolveOwnerHome();
|
|
95
|
+
if (ownerHome) {
|
|
96
|
+
return join(ownerHome, ".config", "agentmoss");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return DEFAULT_DATA_ROOT;
|
|
100
|
+
}
|
|
101
|
+
function getInstance() {
|
|
102
|
+
const v = process.env[ENV_INSTANCE] || "";
|
|
103
|
+
return v.trim().replace(/^[/\\]+|[/\\]+$/g, "");
|
|
104
|
+
}
|
|
105
|
+
function getConfigDir() {
|
|
106
|
+
return getDataRoot();
|
|
107
|
+
}
|
|
108
|
+
function getRuntimeConfigDir() {
|
|
109
|
+
return getDataRoot();
|
|
110
|
+
}
|
|
111
|
+
function getTokenStatsPath() {
|
|
112
|
+
const instance = getInstance();
|
|
113
|
+
if (instance) {
|
|
114
|
+
return join(getDataRoot(), instance, "agent_moss_token_stats.json");
|
|
115
|
+
}
|
|
116
|
+
return join(getDataRoot(), "agent_moss_token_stats.json");
|
|
117
|
+
}
|
|
118
|
+
var ENV_DATA_ROOT, ENV_INSTANCE, DEFAULT_DATA_ROOT;
|
|
119
|
+
var init_paths = __esm({
|
|
120
|
+
"src/infra/paths.ts"() {
|
|
121
|
+
"use strict";
|
|
122
|
+
init_esm_shims();
|
|
123
|
+
ENV_DATA_ROOT = "AGENT_MOSS_DATA_DIR";
|
|
124
|
+
ENV_INSTANCE = "AGENT_MOSS_INSTANCE";
|
|
125
|
+
DEFAULT_DATA_ROOT = join(homedir(), ".config", "agentmoss");
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
45
129
|
// src/config.ts
|
|
46
130
|
var config_exports = {};
|
|
47
131
|
__export(config_exports, {
|
|
@@ -55,11 +139,12 @@ __export(config_exports, {
|
|
|
55
139
|
isHeuristicEnabled: () => isHeuristicEnabled,
|
|
56
140
|
isLLMAvailable: () => isLLMAvailable,
|
|
57
141
|
isLogicRulesEnabled: () => isLogicRulesEnabled,
|
|
142
|
+
probeLLMConnection: () => probeLLMConnection,
|
|
58
143
|
updateLLMConfig: () => updateLLMConfig
|
|
59
144
|
});
|
|
60
145
|
import fs from "fs";
|
|
61
146
|
import path2 from "path";
|
|
62
|
-
import
|
|
147
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
63
148
|
function loadConfigFile(filePath) {
|
|
64
149
|
try {
|
|
65
150
|
const raw = fs.readFileSync(filePath, "utf-8");
|
|
@@ -71,7 +156,7 @@ function loadConfigFile(filePath) {
|
|
|
71
156
|
function resolveConfigPath() {
|
|
72
157
|
const envPath = process.env.AGENT_MOSS_CONFIG_PATH;
|
|
73
158
|
if (envPath) return envPath;
|
|
74
|
-
const userConfig = path2.join(
|
|
159
|
+
const userConfig = path2.join(getConfigDir(), "config.json");
|
|
75
160
|
if (fs.existsSync(userConfig)) return userConfig;
|
|
76
161
|
const defaultConfig = path2.join(__dirname, "..", "..", "agent_moss", "infra", "config.json.example");
|
|
77
162
|
if (fs.existsSync(defaultConfig)) {
|
|
@@ -122,16 +207,38 @@ function getTimeoutConfig() {
|
|
|
122
207
|
function getRetryConfig() {
|
|
123
208
|
return retryConfig;
|
|
124
209
|
}
|
|
210
|
+
function wildcardToRegExp(pattern) {
|
|
211
|
+
return pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, ".*");
|
|
212
|
+
}
|
|
125
213
|
function loadCustomRules() {
|
|
214
|
+
const rules = [];
|
|
126
215
|
try {
|
|
127
216
|
const raw = process.env.AGENT_MOSS_CUSTOM_RULES;
|
|
128
|
-
if (
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
217
|
+
if (raw) {
|
|
218
|
+
const parsed = JSON.parse(raw);
|
|
219
|
+
if (Array.isArray(parsed)) {
|
|
220
|
+
for (const r of parsed) {
|
|
221
|
+
if (r && r.pattern && r.action) rules.push({ pattern: r.pattern, action: r.action, severity: r.severity || "high" });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
132
225
|
} catch {
|
|
133
|
-
return [];
|
|
134
226
|
}
|
|
227
|
+
for (const rulesPath of USER_RULES_PATHS) {
|
|
228
|
+
try {
|
|
229
|
+
if (!fs.existsSync(rulesPath)) continue;
|
|
230
|
+
const data = JSON.parse(fs.readFileSync(rulesPath, "utf-8"));
|
|
231
|
+
const actions = Array.isArray(data?.sensitive_actions) ? data.sensitive_actions : [];
|
|
232
|
+
for (const r of actions) {
|
|
233
|
+
const p = r?.pattern;
|
|
234
|
+
if (!p) continue;
|
|
235
|
+
rules.push({ pattern: wildcardToRegExp(p), action: "Deny", severity: "high" });
|
|
236
|
+
}
|
|
237
|
+
break;
|
|
238
|
+
} catch {
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return rules;
|
|
135
242
|
}
|
|
136
243
|
function getLLMConfig() {
|
|
137
244
|
return llmConfig;
|
|
@@ -146,6 +253,30 @@ function isLLMAvailable() {
|
|
|
146
253
|
if (process.env.AGENT_MOSS_DISABLE_LLM === "1") return false;
|
|
147
254
|
return llmConfig.apiKey.length > 0;
|
|
148
255
|
}
|
|
256
|
+
async function probeLLMConnection(timeoutMs = 8e3) {
|
|
257
|
+
if (!isLLMAvailable()) return false;
|
|
258
|
+
const OpenAI2 = (await import("openai")).default;
|
|
259
|
+
const config = getLLMConfig();
|
|
260
|
+
const provider = detectProvider(config.baseUrl, config.provider);
|
|
261
|
+
const extraHeaders = getProviderHeaders(provider);
|
|
262
|
+
const client = new OpenAI2({
|
|
263
|
+
baseURL: config.baseUrl,
|
|
264
|
+
apiKey: config.apiKey,
|
|
265
|
+
timeout: timeoutMs,
|
|
266
|
+
maxRetries: 0,
|
|
267
|
+
defaultHeaders: Object.keys(extraHeaders).length > 0 ? extraHeaders : void 0
|
|
268
|
+
});
|
|
269
|
+
try {
|
|
270
|
+
const resp = await client.chat.completions.create({
|
|
271
|
+
model: config.model,
|
|
272
|
+
messages: [{ role: "user", content: "ping" }],
|
|
273
|
+
max_tokens: 1
|
|
274
|
+
});
|
|
275
|
+
return !!(resp && resp.choices && resp.choices[0]);
|
|
276
|
+
} catch {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
149
280
|
function getLLMFailMode() {
|
|
150
281
|
const val = (process.env.AGENT_MOSS_LLM_FAIL_MODE || "fail_open").toLowerCase();
|
|
151
282
|
return val === "fail_closed" ? "fail_closed" : "fail_open";
|
|
@@ -189,11 +320,12 @@ function isHeuristicEnabled() {
|
|
|
189
320
|
function isLogicRulesEnabled() {
|
|
190
321
|
return process.env.AGENT_MOSS_DISABLE_LOGIC_RULES !== "1";
|
|
191
322
|
}
|
|
192
|
-
var timeoutConfig, retryConfig, llmConfig, customRules, PROVIDER_HEADERS;
|
|
323
|
+
var timeoutConfig, retryConfig, llmConfig, __configModuleDir, USER_RULES_PATHS, customRules, PROVIDER_HEADERS;
|
|
193
324
|
var init_config = __esm({
|
|
194
325
|
"src/config.ts"() {
|
|
195
326
|
"use strict";
|
|
196
327
|
init_esm_shims();
|
|
328
|
+
init_paths();
|
|
197
329
|
llmConfig = (() => {
|
|
198
330
|
const configPath = resolveConfigPath();
|
|
199
331
|
const fileConfig = fs.existsSync(configPath) ? loadLLMConfigFromFile(configPath) : {};
|
|
@@ -211,6 +343,21 @@ var init_config = __esm({
|
|
|
211
343
|
timeout: parseInt(process.env.AGENT_MOSS_LLM_TIMEOUT || String(fileConfig.timeout ?? String(tr.timeout.prompt1Timeout)), 10)
|
|
212
344
|
};
|
|
213
345
|
})();
|
|
346
|
+
__configModuleDir = (() => {
|
|
347
|
+
try {
|
|
348
|
+
return path2.dirname(fileURLToPath2(import.meta.url));
|
|
349
|
+
} catch {
|
|
350
|
+
return typeof __dirname !== "undefined" ? __dirname : process.cwd();
|
|
351
|
+
}
|
|
352
|
+
})();
|
|
353
|
+
USER_RULES_PATHS = [
|
|
354
|
+
path2.join(__configModuleDir, "rules", "user_rules.json"),
|
|
355
|
+
// 源在 ts/src/rules(一般不存在)
|
|
356
|
+
path2.join(__configModuleDir, "..", "rules", "user_rules.json"),
|
|
357
|
+
// dist: dist/engine/../rules = dist/rules ✅
|
|
358
|
+
path2.join(__configModuleDir, "..", "..", "agent_moss", "rules", "user_rules.json")
|
|
359
|
+
// dev src: ts/src/../../agent_moss
|
|
360
|
+
];
|
|
214
361
|
customRules = loadCustomRules();
|
|
215
362
|
PROVIDER_HEADERS = {
|
|
216
363
|
"openrouter": {
|
|
@@ -227,7 +374,17 @@ var init_config = __esm({
|
|
|
227
374
|
// src/engine/inline-analyzer.ts
|
|
228
375
|
function isLiteralTextCommand(command) {
|
|
229
376
|
ECHO_CMD_RE.lastIndex = 0;
|
|
230
|
-
|
|
377
|
+
if (!ECHO_CMD_RE.test(command)) return false;
|
|
378
|
+
if (command.includes(">")) {
|
|
379
|
+
let d = command.replace(/<=>|>=|<=|<>|=>/g, " ");
|
|
380
|
+
d = d.replace(/(?:2|&|1)?\s*>\s*\/dev\/null\b/g, "");
|
|
381
|
+
d = d.replace(new RegExp(`>{1,2}\\s*(['"])([~./][^'"]+)\\1`, "g"), (_m, _q, p) => ">" + p);
|
|
382
|
+
d = d.replace(/'[^']*'|"[^"]*"/g, "");
|
|
383
|
+
if (/>{1,2}\s*(?!&)\S/.test(d)) {
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return true;
|
|
231
388
|
}
|
|
232
389
|
function extractInlineCode(command) {
|
|
233
390
|
const parts = [];
|
|
@@ -304,7 +461,7 @@ function analyzeInlineScript(command) {
|
|
|
304
461
|
riskType: "file_access"
|
|
305
462
|
};
|
|
306
463
|
}
|
|
307
|
-
var INLINE_CODE_RE, ECHO_STRING_RE, ECHO_CMD_RE, READ_WRITE_KEYWORDS, WRITE_EXEC_CHAIN_PATTERNS, SENSITIVE_PATH_FRAGMENTS, REDIRECT_TO_SENSITIVE_RE, DIR_TRAVERSAL_KEYWORDS;
|
|
464
|
+
var INLINE_CODE_RE, ECHO_STRING_RE, ECHO_CMD_RE, READ_WRITE_KEYWORDS, WRITE_EXEC_CHAIN_PATTERNS, SHELL_RC_FRAGMENTS, SENSITIVE_PATH_FRAGMENTS, REDIRECT_TO_SENSITIVE_RE, DIR_TRAVERSAL_KEYWORDS;
|
|
308
465
|
var init_inline_analyzer = __esm({
|
|
309
466
|
"src/engine/inline-analyzer.ts"() {
|
|
310
467
|
"use strict";
|
|
@@ -374,6 +531,13 @@ var init_inline_analyzer = __esm({
|
|
|
374
531
|
// xargs bash / xargs sh
|
|
375
532
|
/\|\s*xargs\s+(?:bash|sh|zsh)/gi
|
|
376
533
|
];
|
|
534
|
+
SHELL_RC_FRAGMENTS = [
|
|
535
|
+
".bashrc",
|
|
536
|
+
".zshrc",
|
|
537
|
+
".profile",
|
|
538
|
+
".bash_profile",
|
|
539
|
+
".zprofile"
|
|
540
|
+
];
|
|
377
541
|
SENSITIVE_PATH_FRAGMENTS = [
|
|
378
542
|
"/etc/shadow",
|
|
379
543
|
"/etc/gshadow",
|
|
@@ -390,9 +554,10 @@ var init_inline_analyzer = __esm({
|
|
|
390
554
|
"/dev/kmsg",
|
|
391
555
|
".ssh/id_rsa",
|
|
392
556
|
".ssh/id_ed25519",
|
|
393
|
-
".ssh/authorized_keys"
|
|
557
|
+
".ssh/authorized_keys",
|
|
558
|
+
...SHELL_RC_FRAGMENTS
|
|
394
559
|
];
|
|
395
|
-
REDIRECT_TO_SENSITIVE_RE = new RegExp(`>{1,2}\\s*(${SENSITIVE_PATH_FRAGMENTS.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})`, "i");
|
|
560
|
+
REDIRECT_TO_SENSITIVE_RE = new RegExp(`>{1,2}\\s*(?:\\S*/)?(${SENSITIVE_PATH_FRAGMENTS.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})(?:\\s|$)`, "i");
|
|
396
561
|
DIR_TRAVERSAL_KEYWORDS = [
|
|
397
562
|
// Python 目录遍历
|
|
398
563
|
"os.listdir",
|
|
@@ -415,11 +580,34 @@ function isWriteOperation(actionType, actionDetail, writeKeywords = WRITE_KEYWOR
|
|
|
415
580
|
if (writeKeywords.some((kw) => actionType.includes(kw) || actionDetail.includes(kw))) return true;
|
|
416
581
|
if (WRITE_COMMAND_RES.some((rx) => rx.test(actionDetail))) return true;
|
|
417
582
|
if (/\bsed\s+-[a-zA-Z]*i/i.test(actionDetail)) return true;
|
|
583
|
+
for (const seg of actionDetail.split(/&&|\|\||;|\n/)) {
|
|
584
|
+
const trimmed = seg.trim();
|
|
585
|
+
if (!trimmed) continue;
|
|
586
|
+
const parts = trimmed.split(/\s+/);
|
|
587
|
+
const fw = parts[0]?.toLowerCase() || "";
|
|
588
|
+
if (fw === "wget" || fw === "curl") {
|
|
589
|
+
const isStdout = /\s(?:-O|-o)-(?:\s|$)/.test(trimmed) || /\s--output-document=-(?:\s|$)/i.test(trimmed) || /\s--output=-(?:\s|$)/i.test(trimmed);
|
|
590
|
+
if (isStdout) continue;
|
|
591
|
+
const isDevnull = /\s(?:-O|--output-document|--output)\s*\/dev\/null(?:\s|$)/i.test(trimmed) || /\s(?:-O|--output-document|--output)=[/]?dev\/null(?:\s|$)/i.test(trimmed);
|
|
592
|
+
if (isDevnull) continue;
|
|
593
|
+
if (/\s(?:-O|--output-document|--output)(?:=|\s|$)/i.test(trimmed) || /\s-O\S/.test(trimmed)) {
|
|
594
|
+
return true;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
if (fw === "rsync" && !/--list-only|--dry-run/i.test(trimmed) && !/(^|[^a-zA-Z])-n([^a-zA-Z]|$)/.test(trimmed)) {
|
|
598
|
+
return true;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
418
601
|
if (actionDetail.includes(">")) {
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
602
|
+
const detailWithoutCompare = actionDetail.replace(/<=>|>=|<=|<>|=>/g, " ");
|
|
603
|
+
const detailWithoutDevnull = detailWithoutCompare.replace(DEVNULL_REDIRECT_RE, "");
|
|
604
|
+
const detailUnquotedRedirect = detailWithoutDevnull.replace(
|
|
605
|
+
/>{1,2}\s*(['"])([~./][^'"]+)\1/g,
|
|
606
|
+
(_m, _q, p) => ">" + p
|
|
607
|
+
);
|
|
608
|
+
const detailWithoutQuotes = detailUnquotedRedirect.replace(/'[^']*'|"[^"]*"/g, "");
|
|
609
|
+
if (/>{1,2}\s*(?!&)\S/.test(detailWithoutQuotes)) {
|
|
610
|
+
return true;
|
|
423
611
|
}
|
|
424
612
|
}
|
|
425
613
|
return false;
|
|
@@ -443,27 +631,31 @@ function runLogicCheck(input) {
|
|
|
443
631
|
if (rbwResult) return rbwResult;
|
|
444
632
|
if (input.promptSession && input.promptSession.trim()) {
|
|
445
633
|
if (!isExcludedType) {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
const
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
634
|
+
if (INLINE_SCRIPT_RE.test(actionDetail)) {
|
|
635
|
+
} else {
|
|
636
|
+
const promptLower = input.promptSession.toLowerCase();
|
|
637
|
+
for (const pattern of INTENT_DEVIATION_PATTERNS) {
|
|
638
|
+
const intentMatched = pattern.intentKeywords.some((kw) => promptLower.includes(kw));
|
|
639
|
+
const dangerousMatched = pattern.dangerousActions.some((kw) => matchDangerousAction(actionDetail, kw));
|
|
640
|
+
if (intentMatched && dangerousMatched) {
|
|
641
|
+
return {
|
|
642
|
+
allowed: false,
|
|
643
|
+
reason: pattern.reason,
|
|
644
|
+
riskLevel: "high",
|
|
645
|
+
riskType: "intent_deviation",
|
|
646
|
+
confidence: 85,
|
|
647
|
+
source: "logic_rule",
|
|
648
|
+
violatedLayers: ["2"]
|
|
649
|
+
};
|
|
650
|
+
}
|
|
460
651
|
}
|
|
461
652
|
}
|
|
462
653
|
}
|
|
463
654
|
}
|
|
464
655
|
const indirectResult = checkIndirectFileAccess(actionType, actionDetail, resolvedCwd);
|
|
465
656
|
if (indirectResult) return indirectResult;
|
|
466
|
-
if (
|
|
657
|
+
if (INLINE_SCRIPT_RE.test(actionDetail)) {
|
|
658
|
+
} else if (containsPrivilegeEscalation(actionDetail)) {
|
|
467
659
|
const detailAfterSudo = actionDetail.replace(/(^|\|\||&&|\s)sudo\s+/g, " ");
|
|
468
660
|
const isAdminCmd = ["apt", "systemctl", "yum", "dnf", "pacman", "zypper", "apk"].some((kw) => detailAfterSudo.includes(kw));
|
|
469
661
|
if (!isAdminCmd) {
|
|
@@ -493,6 +685,9 @@ function extractFilePaths(text) {
|
|
|
493
685
|
const paths = [];
|
|
494
686
|
const unixPaths = text.match(/(?:^|\s)(\/[^\s;|&><'"]+)/g);
|
|
495
687
|
if (unixPaths) paths.push(...unixPaths.map((p) => p.trim()));
|
|
688
|
+
const textNoQuotes = text.replace(/'[^']*'|"[^"]*"/g, "");
|
|
689
|
+
const kvPaths = textNoQuotes.match(/(?:=)(\/[^\s;|&><'"]+)/g);
|
|
690
|
+
if (kvPaths) paths.push(...kvPaths.map((p) => p.replace(/^=/, "")));
|
|
496
691
|
const quotedPaths = text.match(/["']\/([^"']+)["']/g);
|
|
497
692
|
if (quotedPaths) paths.push(...quotedPaths.map((p) => p.replace(/["']/g, "")));
|
|
498
693
|
return [...new Set(paths)];
|
|
@@ -503,8 +698,74 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
|
|
|
503
698
|
const isBashLike = actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash";
|
|
504
699
|
const isWrite = isBashLike ? isWriteOperation(actionType, actionDetail) : WRITE_KEYWORDS.some((kw) => actionType.includes(kw));
|
|
505
700
|
if (!isWrite) return null;
|
|
506
|
-
const
|
|
507
|
-
if (
|
|
701
|
+
const WRITE_CMDS = /* @__PURE__ */ new Set(["cp", "mv", "install", "tee", "dd", "truncate", "mkfs", "fdisk", "parted"]);
|
|
702
|
+
if (isBashLike) {
|
|
703
|
+
const detailNoCompare = actionDetail.replace(/<=>|>=|<=|<>|=>/g, " ");
|
|
704
|
+
const detailNoDevnull = detailNoCompare.replace(DEVNULL_REDIRECT_RE, "");
|
|
705
|
+
const detailUnquotedRedirect = detailNoDevnull.replace(
|
|
706
|
+
/>{1,2}\s*(['"])([~./][^'"]+)\1/g,
|
|
707
|
+
(_m, _q, p) => ">" + p
|
|
708
|
+
);
|
|
709
|
+
const detailNoQuotes = detailUnquotedRedirect.replace(/'[^']*'|"[^"]*"/g, "");
|
|
710
|
+
const hasRedirectWrite = />{1,2}\s*(?!&)\S/.test(detailNoQuotes);
|
|
711
|
+
let hasWriteSeg = hasRedirectWrite;
|
|
712
|
+
if (!hasWriteSeg) {
|
|
713
|
+
for (const seg of actionDetail.split(/&&|\|\||;|\n/)) {
|
|
714
|
+
const trimmed = seg.trim();
|
|
715
|
+
if (!trimmed) continue;
|
|
716
|
+
const fw = trimmed.split(/\s+/)[0]?.toLowerCase() || "";
|
|
717
|
+
if (WRITE_CMDS.has(fw) || /\bsed\s+-[a-zA-Z]*i/i.test(trimmed)) {
|
|
718
|
+
hasWriteSeg = true;
|
|
719
|
+
break;
|
|
720
|
+
}
|
|
721
|
+
if ((fw === "wget" || fw === "curl") && (/\s(?:-O|--output-document|--output)(?:=|\s|$)/i.test(trimmed) || /\s-O\S/.test(trimmed))) {
|
|
722
|
+
hasWriteSeg = true;
|
|
723
|
+
break;
|
|
724
|
+
}
|
|
725
|
+
if (fw === "rsync" && !/--list-only|--dry-run/i.test(trimmed) && !/(^|[^a-zA-Z])-n([^a-zA-Z]|$)/.test(trimmed)) {
|
|
726
|
+
hasWriteSeg = true;
|
|
727
|
+
break;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
if (!hasWriteSeg) return null;
|
|
732
|
+
}
|
|
733
|
+
const SOURCE_ARG_CMDS = /* @__PURE__ */ new Set(["cp", "mv", "install", "rsync"]);
|
|
734
|
+
const REDIRECT_TARGET_RE = new RegExp(`>{1,2}\\s*(?!&)(?:'(/[^']+)'|"(/[^\\"]+)"|(/[^\\s;|&><'\\"]+))`);
|
|
735
|
+
const targetPaths = [];
|
|
736
|
+
for (const seg of actionDetail.split(/&&|\|\||;|\n/)) {
|
|
737
|
+
const trimmed = seg.trim();
|
|
738
|
+
if (!trimmed) continue;
|
|
739
|
+
const firstWord = trimmed.split(/\s+/)[0]?.toLowerCase() || "";
|
|
740
|
+
if (firstWord === "cd") {
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
if (READ_ONLY_COMMANDS.has(firstWord)) {
|
|
744
|
+
const segUnquoted = trimmed.replace(new RegExp(`>{1,2}\\s*(['"])([~./][^'"]+)\\1`, "g"), (_m, _q, p) => ">" + p);
|
|
745
|
+
const segNoDevnull = segUnquoted.replace(DEVNULL_REDIRECT_RE, "");
|
|
746
|
+
const segNoQuotes = segNoDevnull.replace(/'[^']*'|"[^"]*"/g, "");
|
|
747
|
+
const m = segNoQuotes.match(REDIRECT_TARGET_RE);
|
|
748
|
+
if (m) targetPaths.push(m[1] || m[2] || m[3]);
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
const segPaths = extractFilePaths(trimmed);
|
|
752
|
+
const segPathsFiltered = firstWord === "wget" || firstWord === "curl" ? segPaths.filter((p) => p !== "/dev/null") : segPaths;
|
|
753
|
+
if (SOURCE_ARG_CMDS.has(firstWord)) {
|
|
754
|
+
if (segPathsFiltered.length > 1) targetPaths.push(...segPathsFiltered.slice(1));
|
|
755
|
+
} else if (firstWord === "dd") {
|
|
756
|
+
const ifSourceMatches = trimmed.match(/\bif=(\/[^\s;|&><'"]+)/g);
|
|
757
|
+
const ifSourcePaths = new Set(ifSourceMatches ? ifSourceMatches.map((m) => m.replace(/^if=/, "")) : []);
|
|
758
|
+
const devNullMatches = trimmed.match(/\bof=(\/dev\/\S+)/g);
|
|
759
|
+
const devNullTargets = new Set(devNullMatches ? devNullMatches.map((m) => m.replace(/^of=/, "")) : []);
|
|
760
|
+
for (const sp of segPaths) {
|
|
761
|
+
if (!ifSourcePaths.has(sp) && !devNullTargets.has(sp)) targetPaths.push(sp);
|
|
762
|
+
}
|
|
763
|
+
} else {
|
|
764
|
+
targetPaths.push(...segPathsFiltered);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
const uniqueTargetPaths = [...new Set(targetPaths)];
|
|
768
|
+
if (uniqueTargetPaths.length === 0) return null;
|
|
508
769
|
const historyReadPaths = /* @__PURE__ */ new Set();
|
|
509
770
|
for (const action of actionHistory) {
|
|
510
771
|
const histDetail = action.actionDetail.toLowerCase();
|
|
@@ -517,7 +778,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
|
|
|
517
778
|
}
|
|
518
779
|
}
|
|
519
780
|
const unreadPaths = [];
|
|
520
|
-
for (const path3 of
|
|
781
|
+
for (const path3 of uniqueTargetPaths) {
|
|
521
782
|
const hasRead = [...historyReadPaths].some(
|
|
522
783
|
(rp) => path3 === rp || path3.startsWith(rp) || rp.startsWith(path3)
|
|
523
784
|
);
|
|
@@ -580,6 +841,18 @@ function checkUserDeletionConsent(actionType, actionDetail, actionHistory) {
|
|
|
580
841
|
violatedLayers: ["2"]
|
|
581
842
|
};
|
|
582
843
|
}
|
|
844
|
+
function containsPrivilegeEscalation(actionDetail) {
|
|
845
|
+
const ad = actionDetail.toLowerCase();
|
|
846
|
+
for (const seg of ad.split(/&&|\|\||;|\n/)) {
|
|
847
|
+
const trimmed = seg.trim();
|
|
848
|
+
if (!trimmed) continue;
|
|
849
|
+
const parts = trimmed.split(/\s+/);
|
|
850
|
+
const fw = parts[0]?.toLowerCase() || "";
|
|
851
|
+
if (fw === "sudo" || fw === "su" || fw === "setuid") return true;
|
|
852
|
+
if (fw === "chmod" && /\bu\s*\+\s*s\b/.test(trimmed)) return true;
|
|
853
|
+
}
|
|
854
|
+
return false;
|
|
855
|
+
}
|
|
583
856
|
function checkIndirectFileAccess(actionType, actionDetail, cwd) {
|
|
584
857
|
if (actionType !== "bash" && actionType !== "executebash" && actionType !== "terminal-executebash") {
|
|
585
858
|
return null;
|
|
@@ -684,12 +957,13 @@ var init_logic_rules = __esm({
|
|
|
684
957
|
{ path: "/sys/", riskLevel: "high", desc: "sysfs \u5185\u6838\u63A5\u53E3" },
|
|
685
958
|
// 设备文件(/dev/null、/dev/zero、/dev/urandom 已移除,误报率极高)
|
|
686
959
|
{ path: "/dev/random", riskLevel: "high", desc: "\u963B\u585E\u5F0F\u968F\u673A\u6570\u8BBE\u5907\uFF08\u8BFB\u53D6\u8017\u5C3D\u71B5\u6C60\uFF09" },
|
|
687
|
-
{ path: "/dev/mem", riskLevel: "critical", desc: "\u7269\u7406\u5185\u5B58\u8BBF\u95EE\u8BBE\u5907" },
|
|
960
|
+
{ path: "/dev/mem", riskLevel: "critical", desc: "\u7269\u7406\u5185\u5B58\u8BBF\u95EE\u8BBE\u5907\uFF08\u8BFB\u5199\u5747\u5371\u9669\uFF09", credential: true },
|
|
688
961
|
{ path: "/dev/kmsg", riskLevel: "high", desc: "\u5185\u6838\u6D88\u606F\u7F13\u51B2\u533A" },
|
|
689
962
|
// SSH 主机私钥
|
|
690
|
-
|
|
691
|
-
{ path: "/etc/ssh/
|
|
692
|
-
{ path: "/etc/ssh/
|
|
963
|
+
// SSH 主机私钥(排除 .pub 公钥变体,对齐 Python .ssh/id_rsa(?!\.pub) 范式)
|
|
964
|
+
{ path: "/etc/ssh/ssh_host_rsa_key(?!\\.pub)", riskLevel: "critical", desc: "SSH \u4E3B\u673A RSA \u79C1\u94A5", credential: true },
|
|
965
|
+
{ path: "/etc/ssh/ssh_host_ed25519_key(?!\\.pub)", riskLevel: "critical", desc: "SSH \u4E3B\u673A Ed25519 \u79C1\u94A5", credential: true },
|
|
966
|
+
{ path: "/etc/ssh/ssh_host_ecdsa_key(?!\\.pub)", riskLevel: "critical", desc: "SSH \u4E3B\u673A ECDSA \u79C1\u94A5", credential: true }
|
|
693
967
|
];
|
|
694
968
|
HOME_SENSITIVE_PATHS = [
|
|
695
969
|
{ path: ".ssh/id_rsa", riskLevel: "critical", desc: "SSH \u79C1\u94A5", credential: true },
|
|
@@ -730,7 +1004,7 @@ var init_logic_rules = __esm({
|
|
|
730
1004
|
"cat"
|
|
731
1005
|
]);
|
|
732
1006
|
NON_WRITE_TOOL_PREFIXES = ["memory-", "skill-manager-", "mailbox-", "app-"];
|
|
733
|
-
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"];
|
|
1007
|
+
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"];
|
|
734
1008
|
WRITE_COMMAND_RES = [
|
|
735
1009
|
/\brm\b/,
|
|
736
1010
|
/\bunlink\b/,
|
|
@@ -776,6 +1050,12 @@ var init_logic_rules = __esm({
|
|
|
776
1050
|
"nl",
|
|
777
1051
|
"tac",
|
|
778
1052
|
"rev",
|
|
1053
|
+
// NEW-FP-I: echo/printf 是文本输出命令,参数是字面量非写目标。加入此集让
|
|
1054
|
+
// read_before_write 的 echo 段走只读命令分支(只提取重定向目标,不提取参数路径),
|
|
1055
|
+
// 避免 echo "cat /etc/shadow" > /tmp/x.sh 的引号内 /etc/shadow 被当写目标误报。
|
|
1056
|
+
// echo "x" > /tmp/x.sh 的重定向目标 /tmp/x.sh 仍被提取(真实写不漏报)。
|
|
1057
|
+
"echo",
|
|
1058
|
+
"printf",
|
|
779
1059
|
"lsblk",
|
|
780
1060
|
"blockdev",
|
|
781
1061
|
"smartctl",
|
|
@@ -828,7 +1108,7 @@ var init_logic_rules = __esm({
|
|
|
828
1108
|
"modinfo",
|
|
829
1109
|
"sysctl"
|
|
830
1110
|
]);
|
|
831
|
-
DEVNULL_REDIRECT_RE = /(?:2|&|1)?\s*>\s*\/dev\/null\b
|
|
1111
|
+
DEVNULL_REDIRECT_RE = /(?:2|&|1)?\s*>\s*\/dev\/null\b/g;
|
|
832
1112
|
READ_KEYWORDS = ["read", "\u8BFB\u53D6", "cat", "head", "tail", "less", "more", "view", "\u67E5\u770B", "open", "\u6253\u5F00", "load", "\u52A0\u8F7D", "grep", "search", "\u641C\u7D22", "find", "\u67E5\u627E"];
|
|
833
1113
|
CREATE_KEYWORDS = ["\u521B\u5EFA", "\u65B0\u5EFA", "create", "new file", "touch"];
|
|
834
1114
|
PASSWORD_MODIFY_PATTERNS = [
|
|
@@ -876,9 +1156,9 @@ var init_logic_rules = __esm({
|
|
|
876
1156
|
});
|
|
877
1157
|
|
|
878
1158
|
// src/engine/skill-loader.ts
|
|
879
|
-
import { readFileSync, readdirSync as readdirSync2, existsSync as existsSync2 } from "fs";
|
|
1159
|
+
import { readFileSync as readFileSync2, readdirSync as readdirSync2, existsSync as existsSync2 } from "fs";
|
|
880
1160
|
import { resolve, dirname } from "path";
|
|
881
|
-
import { fileURLToPath as
|
|
1161
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
882
1162
|
function loadAllSkills() {
|
|
883
1163
|
if (cachedSkills) return cachedSkills;
|
|
884
1164
|
cachedSkills = {};
|
|
@@ -890,7 +1170,7 @@ function loadAllSkills() {
|
|
|
890
1170
|
const files = readdirSync2(dir).filter((f) => f.endsWith(".md"));
|
|
891
1171
|
for (const f of files) {
|
|
892
1172
|
const name = f.replace(".md", "");
|
|
893
|
-
cachedSkills[name] =
|
|
1173
|
+
cachedSkills[name] = readFileSync2(resolve(dir, f), "utf-8");
|
|
894
1174
|
}
|
|
895
1175
|
console.log(`[AgentMoss] Loaded ${Object.keys(cachedSkills).length} skills from ${dir}`);
|
|
896
1176
|
return cachedSkills;
|
|
@@ -949,7 +1229,7 @@ var init_skill_loader = __esm({
|
|
|
949
1229
|
"src/engine/skill-loader.ts"() {
|
|
950
1230
|
"use strict";
|
|
951
1231
|
init_esm_shims();
|
|
952
|
-
__filename2 =
|
|
1232
|
+
__filename2 = fileURLToPath3(import.meta.url);
|
|
953
1233
|
__dirname2 = dirname(__filename2);
|
|
954
1234
|
SKILLS_DIR = resolve(__dirname2, "../skills");
|
|
955
1235
|
SKILLS_DIR_FALLBACK = resolve(__dirname2, "../../../agent_moss/skills");
|
|
@@ -1021,11 +1301,13 @@ __export(runtime_config_exports, {
|
|
|
1021
1301
|
updateSkillCategoryEnabled: () => updateSkillCategoryEnabled,
|
|
1022
1302
|
updateSkillEnabled: () => updateSkillEnabled
|
|
1023
1303
|
});
|
|
1024
|
-
import { readFileSync as
|
|
1025
|
-
import { join } from "path";
|
|
1026
|
-
import {
|
|
1027
|
-
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1304
|
+
import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync3, mkdirSync, unlinkSync, readdirSync as readdirSync3 } from "fs";
|
|
1305
|
+
import { join as join2 } from "path";
|
|
1306
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
1028
1307
|
import { dirname as dirname2 } from "path";
|
|
1308
|
+
function runtimeDir() {
|
|
1309
|
+
return getRuntimeConfigDir();
|
|
1310
|
+
}
|
|
1029
1311
|
function getRuntimeConfigPath() {
|
|
1030
1312
|
const envPath = process.env[ENV_RUNTIME_CONFIG_PATH];
|
|
1031
1313
|
if (envPath) return envPath;
|
|
@@ -1058,7 +1340,7 @@ function loadRuntimeConfig() {
|
|
|
1058
1340
|
return seeded;
|
|
1059
1341
|
}
|
|
1060
1342
|
try {
|
|
1061
|
-
const data = JSON.parse(
|
|
1343
|
+
const data = JSON.parse(readFileSync3(p, "utf-8"));
|
|
1062
1344
|
const merged = mergeConfig(data, generateSourceDefaults());
|
|
1063
1345
|
return {
|
|
1064
1346
|
version: data.version || 1,
|
|
@@ -1128,7 +1410,7 @@ function hasRuntimeConfig() {
|
|
|
1128
1410
|
const p = getRuntimeConfigPath();
|
|
1129
1411
|
if (!existsSync3(p)) return false;
|
|
1130
1412
|
try {
|
|
1131
|
-
JSON.parse(
|
|
1413
|
+
JSON.parse(readFileSync3(p, "utf-8"));
|
|
1132
1414
|
return true;
|
|
1133
1415
|
} catch {
|
|
1134
1416
|
return false;
|
|
@@ -1316,11 +1598,11 @@ function _agentRulesToDefaults() {
|
|
|
1316
1598
|
return out;
|
|
1317
1599
|
}
|
|
1318
1600
|
for (const agentId of entries) {
|
|
1319
|
-
const rulesFile =
|
|
1601
|
+
const rulesFile = join2(agentsDir, agentId, "rules.json");
|
|
1320
1602
|
if (!existsSync3(rulesFile)) continue;
|
|
1321
1603
|
let data;
|
|
1322
1604
|
try {
|
|
1323
|
-
data = JSON.parse(
|
|
1605
|
+
data = JSON.parse(readFileSync3(rulesFile, "utf-8"));
|
|
1324
1606
|
} catch {
|
|
1325
1607
|
continue;
|
|
1326
1608
|
}
|
|
@@ -1736,12 +2018,12 @@ function deleteCustomRule(layer, category, ruleId) {
|
|
|
1736
2018
|
function addCustomSkill(skillId, category, keywords, content) {
|
|
1737
2019
|
_validateSkillId(skillId);
|
|
1738
2020
|
const runtime = loadRuntimeConfig();
|
|
1739
|
-
const skillsDir =
|
|
2021
|
+
const skillsDir = join2(RUNTIME_CONFIG_DIR, "agent_moss_skills");
|
|
1740
2022
|
try {
|
|
1741
2023
|
mkdirSync(skillsDir, { recursive: true });
|
|
1742
2024
|
} catch {
|
|
1743
2025
|
}
|
|
1744
|
-
writeFileSync(
|
|
2026
|
+
writeFileSync(join2(skillsDir, `${skillId}.md`), content, "utf-8");
|
|
1745
2027
|
const l3 = runtime.L3_skills || (runtime.L3_skills = {});
|
|
1746
2028
|
const cat = l3[category] || (l3[category] = { category_enabled: true, category_desc: "", skills: [] });
|
|
1747
2029
|
(cat.skills || (cat.skills = [])).push({ id: skillId, keywords, enabled: true, builtin: false });
|
|
@@ -1756,7 +2038,7 @@ function deleteCustomSkill(skillId, category) {
|
|
|
1756
2038
|
if (idx >= 0) {
|
|
1757
2039
|
if (skills[idx].builtin) return null;
|
|
1758
2040
|
skills.splice(idx, 1);
|
|
1759
|
-
const skillPath =
|
|
2041
|
+
const skillPath = join2(RUNTIME_CONFIG_DIR, "agent_moss_skills", `${skillId}.md`);
|
|
1760
2042
|
try {
|
|
1761
2043
|
if (existsSync3(skillPath)) unlinkSync(skillPath);
|
|
1762
2044
|
} catch {
|
|
@@ -1872,18 +2154,19 @@ function updateAgentCategoryEnabled(agentId, category, enabled) {
|
|
|
1872
2154
|
saveRuntimeConfig(runtime);
|
|
1873
2155
|
return runtime;
|
|
1874
2156
|
}
|
|
1875
|
-
var __dirname3, RUNTIME_CONFIG_DIR, RUNTIME_CONFIG_PATH,
|
|
2157
|
+
var __dirname3, ENV_RUNTIME_CONFIG_PATH, RUNTIME_CONFIG_DIR, RUNTIME_CONFIG_PATH, LAYER_DISABLE_ENV, DENY_MODE_LABELS, _AGENTS_DIR_PROD, _AGENTS_DIR_DEV, SKILL_ID_RE;
|
|
1876
2158
|
var init_runtime_config = __esm({
|
|
1877
2159
|
"src/infra/runtime-config.ts"() {
|
|
1878
2160
|
"use strict";
|
|
1879
2161
|
init_esm_shims();
|
|
2162
|
+
init_paths();
|
|
1880
2163
|
init_heuristic();
|
|
1881
2164
|
init_logic_rules();
|
|
1882
2165
|
init_skill_loader();
|
|
1883
|
-
__dirname3 = dirname2(
|
|
1884
|
-
RUNTIME_CONFIG_DIR = join(homedir(), ".config", "agentmoss");
|
|
1885
|
-
RUNTIME_CONFIG_PATH = join(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
|
|
2166
|
+
__dirname3 = dirname2(fileURLToPath4(import.meta.url));
|
|
1886
2167
|
ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
|
|
2168
|
+
RUNTIME_CONFIG_DIR = runtimeDir();
|
|
2169
|
+
RUNTIME_CONFIG_PATH = join2(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
|
|
1887
2170
|
LAYER_DISABLE_ENV = {
|
|
1888
2171
|
L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
|
|
1889
2172
|
L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
|
|
@@ -1894,16 +2177,16 @@ var init_runtime_config = __esm({
|
|
|
1894
2177
|
deny_read: "\u4EC5\u62E6\u622A\u8BFB\u53D6",
|
|
1895
2178
|
deny_both: "\u8BFB\u5199\u5747\u62E6\u622A"
|
|
1896
2179
|
};
|
|
1897
|
-
_AGENTS_DIR_PROD =
|
|
1898
|
-
_AGENTS_DIR_DEV =
|
|
2180
|
+
_AGENTS_DIR_PROD = join2(__dirname3, "agents");
|
|
2181
|
+
_AGENTS_DIR_DEV = join2(__dirname3, "..", "..", "..", "agent_moss", "agents");
|
|
1899
2182
|
SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
|
|
1900
2183
|
}
|
|
1901
2184
|
});
|
|
1902
2185
|
|
|
1903
2186
|
// src/agents/index.ts
|
|
1904
|
-
import { readFileSync as
|
|
2187
|
+
import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
|
|
1905
2188
|
import { resolve as resolve2, dirname as dirname3 } from "path";
|
|
1906
|
-
import { fileURLToPath as
|
|
2189
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
1907
2190
|
function resolveAgentsDir() {
|
|
1908
2191
|
if (existsSync4(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
|
|
1909
2192
|
if (existsSync4(AGENTS_DIR_DEV_FALLBACK)) return AGENTS_DIR_DEV_FALLBACK;
|
|
@@ -1928,7 +2211,7 @@ function getAgentExtensions(agentId) {
|
|
|
1928
2211
|
return null;
|
|
1929
2212
|
}
|
|
1930
2213
|
try {
|
|
1931
|
-
const raw =
|
|
2214
|
+
const raw = readFileSync4(rulesFile, "utf-8");
|
|
1932
2215
|
ext = JSON.parse(raw);
|
|
1933
2216
|
if (!ext.agent_id) {
|
|
1934
2217
|
console.warn(`[AgentMoss agents] rules.json \u7F3A\u5C11 agent_id \u5B57\u6BB5: ${rulesFile}`);
|
|
@@ -1952,7 +2235,7 @@ var init_agents = __esm({
|
|
|
1952
2235
|
"src/agents/index.ts"() {
|
|
1953
2236
|
"use strict";
|
|
1954
2237
|
init_esm_shims();
|
|
1955
|
-
__filename3 =
|
|
2238
|
+
__filename3 = fileURLToPath5(import.meta.url);
|
|
1956
2239
|
__dirname4 = dirname3(__filename3);
|
|
1957
2240
|
AGENTS_DIR_PROD = resolve2(__dirname4, "agents");
|
|
1958
2241
|
AGENTS_DIR_DEV_FALLBACK = resolve2(__dirname4, "../../../agent_moss/agents");
|
|
@@ -1972,19 +2255,25 @@ function runHeuristicCheck(input) {
|
|
|
1972
2255
|
const re = new RegExp(rule.pattern);
|
|
1973
2256
|
if (re.test(command)) {
|
|
1974
2257
|
if (rule.action === "Deny") {
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
2258
|
+
return {
|
|
2259
|
+
allowed: false,
|
|
2260
|
+
reason: `\u547D\u4E2D\u7528\u6237\u654F\u611F\u52A8\u4F5C\u89C4\u5219: ${rule.description || rule.pattern}`,
|
|
2261
|
+
riskLevel: rule.severity,
|
|
2262
|
+
riskType: "sensitive_action",
|
|
2263
|
+
confidence: 90,
|
|
2264
|
+
source: "heuristic",
|
|
2265
|
+
violatedLayers: ["1.1"]
|
|
2266
|
+
};
|
|
1980
2267
|
}
|
|
1981
2268
|
}
|
|
1982
2269
|
} catch {
|
|
1983
2270
|
}
|
|
1984
2271
|
}
|
|
2272
|
+
const scanDetail = stripLiteralTextSegments(input.aNext.actionDetail);
|
|
2273
|
+
const scanCommand = `${input.aNext.actionType}: ${scanDetail}`;
|
|
1985
2274
|
let topRiskType = "";
|
|
1986
2275
|
for (const rule of DANGEROUS_PATTERNS) {
|
|
1987
|
-
if (rule.pattern.test(
|
|
2276
|
+
if (rule.pattern.test(scanCommand)) {
|
|
1988
2277
|
matched.push(rule.description);
|
|
1989
2278
|
if (severityRank(rule.severity) > severityRank(highestSeverity)) {
|
|
1990
2279
|
highestSeverity = rule.severity;
|
|
@@ -1998,7 +2287,7 @@ function runHeuristicCheck(input) {
|
|
|
1998
2287
|
for (const rule of agentExt.extra_command_patterns) {
|
|
1999
2288
|
try {
|
|
2000
2289
|
const re = new RegExp(rule.pattern, "i");
|
|
2001
|
-
if (re.test(
|
|
2290
|
+
if (re.test(scanCommand)) {
|
|
2002
2291
|
matched.push(`agent \u4E13\u5C5E\u89C4\u5219: ${rule.reason}`);
|
|
2003
2292
|
if (severityRank(rule.risk_level) > severityRank(highestSeverity)) {
|
|
2004
2293
|
highestSeverity = rule.risk_level;
|
|
@@ -2022,14 +2311,12 @@ function runHeuristicCheck(input) {
|
|
|
2022
2311
|
const actionTypeL = input.aNext.actionType.toLowerCase();
|
|
2023
2312
|
const detailL = input.aNext.actionDetail.toLowerCase();
|
|
2024
2313
|
const isExcludedType = SENSITIVE_EXCLUDED_PREFIXES.some((p) => actionTypeL.startsWith(p));
|
|
2314
|
+
const dangerousResult = checkDangerousPatterns(actionTypeL, detailL);
|
|
2315
|
+
if (dangerousResult) return dangerousResult;
|
|
2025
2316
|
if (!isExcludedType) {
|
|
2026
2317
|
const pathResult = checkSensitivePath(detailL, input.cwd || "/", agentExt);
|
|
2027
2318
|
if (pathResult) return pathResult;
|
|
2028
2319
|
}
|
|
2029
|
-
const dangerousResult = checkDangerousPatterns(actionTypeL, detailL);
|
|
2030
|
-
if (dangerousResult) return dangerousResult;
|
|
2031
|
-
const lateralResult = checkLateralMovement(detailL);
|
|
2032
|
-
if (lateralResult) return lateralResult;
|
|
2033
2320
|
if (matched.length > 0) {
|
|
2034
2321
|
return {
|
|
2035
2322
|
allowed: false,
|
|
@@ -2042,6 +2329,8 @@ function runHeuristicCheck(input) {
|
|
|
2042
2329
|
// 对齐 py coordinator.py:165(L1.1 = 命中高危命令模式/注入关键词)
|
|
2043
2330
|
};
|
|
2044
2331
|
}
|
|
2332
|
+
const lateralResult = checkLateralMovement(detailL);
|
|
2333
|
+
if (lateralResult) return lateralResult;
|
|
2045
2334
|
return null;
|
|
2046
2335
|
}
|
|
2047
2336
|
function checkInjectionKeywords(text, extraKeywords) {
|
|
@@ -2064,6 +2353,68 @@ function isInlineScriptCommand(actionDetail) {
|
|
|
2064
2353
|
const re = /(?:^|\s)(?:python|python3|python3\.\d+|perl|perl5|ruby|jruby|mruby|node|php|lua)\s+(?:-[a-zA-Z]*[cer][a-zA-Z]*\s+)/i;
|
|
2065
2354
|
return re.test(actionDetail);
|
|
2066
2355
|
}
|
|
2356
|
+
function stripLiteralTextSegments(actionDetail) {
|
|
2357
|
+
const writeScriptExec = />\s*[/~]\S*\.(sh|py|pl|rb)\b/.test(actionDetail) && /(?:&&|\|\||;|\n|\|)\s*(?:bash|sh|zsh|dash|python|perl|ruby|node)/.test(actionDetail);
|
|
2358
|
+
const pipeToInterpreter = /\|\s*(?:bash|sh|zsh|dash|python|perl|ruby|node)\b/.test(actionDetail);
|
|
2359
|
+
if (writeScriptExec || pipeToInterpreter) {
|
|
2360
|
+
return actionDetail;
|
|
2361
|
+
}
|
|
2362
|
+
const resultParts = [];
|
|
2363
|
+
for (const seg of splitCommandSegments(actionDetail)) {
|
|
2364
|
+
const fw = (seg.trim().split(/\s+/)[0] || "").toLowerCase();
|
|
2365
|
+
if (fw === "echo" || fw === "printf") {
|
|
2366
|
+
const segUnquotedRedirect = seg.replace(
|
|
2367
|
+
/>{1,2}\s*(['"])([~./][^'"]+)\1/g,
|
|
2368
|
+
(_m, _q, p) => ">" + p
|
|
2369
|
+
);
|
|
2370
|
+
resultParts.push(segUnquotedRedirect.replace(/'[^']*'|"[^"]*"/g, ""));
|
|
2371
|
+
} else {
|
|
2372
|
+
resultParts.push(seg);
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
return resultParts.join("");
|
|
2376
|
+
}
|
|
2377
|
+
function splitCommandSegments(actionDetail) {
|
|
2378
|
+
const segments = [];
|
|
2379
|
+
let cur = "";
|
|
2380
|
+
let quote = null;
|
|
2381
|
+
for (let i = 0; i < actionDetail.length; i++) {
|
|
2382
|
+
const ch = actionDetail[i];
|
|
2383
|
+
if (quote) {
|
|
2384
|
+
cur += ch;
|
|
2385
|
+
if (ch === quote) quote = null;
|
|
2386
|
+
continue;
|
|
2387
|
+
}
|
|
2388
|
+
if (ch === "'" || ch === '"') {
|
|
2389
|
+
quote = ch;
|
|
2390
|
+
cur += ch;
|
|
2391
|
+
continue;
|
|
2392
|
+
}
|
|
2393
|
+
if (ch === ";" || ch === "\n") {
|
|
2394
|
+
segments.push(cur);
|
|
2395
|
+
segments.push(ch);
|
|
2396
|
+
cur = "";
|
|
2397
|
+
continue;
|
|
2398
|
+
}
|
|
2399
|
+
if (ch === "&" && actionDetail[i + 1] === "&") {
|
|
2400
|
+
segments.push(cur);
|
|
2401
|
+
segments.push("&&");
|
|
2402
|
+
cur = "";
|
|
2403
|
+
i++;
|
|
2404
|
+
continue;
|
|
2405
|
+
}
|
|
2406
|
+
if (ch === "|" && actionDetail[i + 1] === "|") {
|
|
2407
|
+
segments.push(cur);
|
|
2408
|
+
segments.push("||");
|
|
2409
|
+
cur = "";
|
|
2410
|
+
i++;
|
|
2411
|
+
continue;
|
|
2412
|
+
}
|
|
2413
|
+
cur += ch;
|
|
2414
|
+
}
|
|
2415
|
+
if (cur) segments.push(cur);
|
|
2416
|
+
return segments;
|
|
2417
|
+
}
|
|
2067
2418
|
function severityRank(s) {
|
|
2068
2419
|
const ranks = { low: 1, medium: 2, high: 3, critical: 4 };
|
|
2069
2420
|
return ranks[s] || 0;
|
|
@@ -2071,13 +2422,18 @@ function severityRank(s) {
|
|
|
2071
2422
|
function checkSensitivePath(command, cwd, agentExt) {
|
|
2072
2423
|
const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
|
|
2073
2424
|
const isWriteOpFlag = isWriteOperation("", command);
|
|
2425
|
+
const pathScanText = stripLiteralTextSegments(command);
|
|
2074
2426
|
const pathMatches = (text, path3) => {
|
|
2075
|
-
const
|
|
2427
|
+
const parts = path3.split(/(\(\?!.*?\))/);
|
|
2428
|
+
const escaped = parts.map((part) => {
|
|
2429
|
+
if (part.startsWith("(?!")) return part;
|
|
2430
|
+
return part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2431
|
+
}).join("");
|
|
2076
2432
|
const re = path3.startsWith(".") || path3.startsWith("/") ? new RegExp(`(?:^|[\\s/\\\\'"])${escaped}\\b`, "i") : new RegExp(`\\b${escaped}\\b`, "i");
|
|
2077
2433
|
return re.test(text);
|
|
2078
2434
|
};
|
|
2079
2435
|
for (const sp of ROOT_SENSITIVE_PATHS) {
|
|
2080
|
-
if (pathMatches(
|
|
2436
|
+
if (pathMatches(pathScanText, sp.path.toLowerCase())) {
|
|
2081
2437
|
if (sp.path === "/dev/random") {
|
|
2082
2438
|
if (isWriteOpFlag) {
|
|
2083
2439
|
continue;
|
|
@@ -2111,7 +2467,7 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2111
2467
|
const expanded = `${expandedCwd}/${sp.path}`;
|
|
2112
2468
|
const pathRe = sp.path.endsWith(".pub") ? new RegExp(sp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i") : new RegExp(sp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "(?!\\.pub)", "i");
|
|
2113
2469
|
const expandedRe = sp.path.endsWith(".pub") ? new RegExp(expanded.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i") : new RegExp(expanded.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "(?!\\.pub)", "i");
|
|
2114
|
-
if (pathRe.test(
|
|
2470
|
+
if (pathRe.test(pathScanText) || expandedRe.test(pathScanText)) {
|
|
2115
2471
|
if (!isWriteOpFlag && !sp.credential) {
|
|
2116
2472
|
continue;
|
|
2117
2473
|
}
|
|
@@ -2182,8 +2538,10 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2182
2538
|
}
|
|
2183
2539
|
function checkDangerousPatterns(actionType, actionDetail) {
|
|
2184
2540
|
if (actionType === "file_write" || actionType === "file_edit") return null;
|
|
2185
|
-
const TEMP_DIRS = ["/tmp
|
|
2186
|
-
const isTempPath = TEMP_DIRS.some(
|
|
2541
|
+
const TEMP_DIRS = ["/tmp", "/var/tmp", "/run", "/dev/shm"];
|
|
2542
|
+
const isTempPath = TEMP_DIRS.some(
|
|
2543
|
+
(td) => new RegExp(`(?:^|[\\s/])${td.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[\\s/]|$)`).test(actionDetail)
|
|
2544
|
+
);
|
|
2187
2545
|
if (!isTempPath && (actionDetail.includes("*") || actionDetail.includes("?")) && ["rm ", "rm ", "del ", "del ", "delete", "remove ", "remove ", "\u5220\u9664", "\u79FB\u9664"].some((kw) => actionDetail.includes(kw))) {
|
|
2188
2546
|
return {
|
|
2189
2547
|
allowed: false,
|
|
@@ -2195,7 +2553,22 @@ function checkDangerousPatterns(actionType, actionDetail) {
|
|
|
2195
2553
|
violatedLayers: ["1.1"]
|
|
2196
2554
|
};
|
|
2197
2555
|
}
|
|
2198
|
-
|
|
2556
|
+
const findWriteMatch = actionDetail.match(
|
|
2557
|
+
/\bfind\b.*?(?:(?:^|\s)-exec(?:dir)?\s+(chmod|chown|cp|mv|rm|dd|tee|truncate|mkfs|shred|rmdir|unlink)\b|(?:^|\s)\|\s*xargs\s+(?:sed\s+-[a-zA-Z]*i|chmod|chown|cp|mv|rm|dd|tee|truncate|mkfs|shred|rmdir|unlink)\b)/i
|
|
2558
|
+
);
|
|
2559
|
+
if (findWriteMatch) {
|
|
2560
|
+
const cmdHit = findWriteMatch[1] || "xargs";
|
|
2561
|
+
return {
|
|
2562
|
+
allowed: false,
|
|
2563
|
+
reason: `\u68C0\u6D4B\u5230 find \u6279\u91CF\u6267\u884C\u5199\u64CD\u4F5C (${cmdHit})\uFF0C\u53EF\u80FD\u6279\u91CF\u4FEE\u6539/\u5220\u9664\u6587\u4EF6`,
|
|
2564
|
+
riskLevel: "high",
|
|
2565
|
+
riskType: "script_execution",
|
|
2566
|
+
confidence: 90,
|
|
2567
|
+
source: "heuristic",
|
|
2568
|
+
violatedLayers: ["1.1"]
|
|
2569
|
+
};
|
|
2570
|
+
}
|
|
2571
|
+
if (/>\s*\/etc\//.test(actionDetail) || />\s*\/boot\//.test(actionDetail)) {
|
|
2199
2572
|
return {
|
|
2200
2573
|
allowed: false,
|
|
2201
2574
|
reason: "\u68C0\u6D4B\u5230\u91CD\u5B9A\u5411\u8986\u76D6\u5199\u5165\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55",
|
|
@@ -2206,17 +2579,95 @@ function checkDangerousPatterns(actionType, actionDetail) {
|
|
|
2206
2579
|
violatedLayers: ["1.1"]
|
|
2207
2580
|
};
|
|
2208
2581
|
}
|
|
2582
|
+
if (/>\s*\/dev\/(sd|hd|nvme|vd|loop|mem|kmsg|port|zero|random|urandom|full|core)/i.test(actionDetail)) {
|
|
2583
|
+
return {
|
|
2584
|
+
allowed: false,
|
|
2585
|
+
reason: "\u68C0\u6D4B\u5230\u91CD\u5B9A\u5411\u5199\u5165\u8BBE\u5907\u6587\u4EF6",
|
|
2586
|
+
riskLevel: "high",
|
|
2587
|
+
riskType: "file_access",
|
|
2588
|
+
confidence: 90,
|
|
2589
|
+
source: "heuristic",
|
|
2590
|
+
violatedLayers: ["1.1"]
|
|
2591
|
+
};
|
|
2592
|
+
}
|
|
2209
2593
|
return null;
|
|
2210
2594
|
}
|
|
2211
2595
|
function extractRemoteHost(command) {
|
|
2212
|
-
const
|
|
2213
|
-
|
|
2596
|
+
const tokens = command.split(/\s+/).map((t) => t.replace(/^["']|["']$/g, ""));
|
|
2597
|
+
for (const tok of tokens) {
|
|
2598
|
+
const am = tok.match(/^([\w.-]+)@([\w.-]+)(:.*)?$/);
|
|
2599
|
+
if (am && am[2] && !isLocalHost(am[2])) {
|
|
2600
|
+
return `${am[1]}@${am[2]}`;
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
for (const tok of tokens) {
|
|
2604
|
+
const hm = tok.match(/^([\w.-]+):\/(.*)?$/);
|
|
2605
|
+
if (hm && hm[1] && !isLocalHost(hm[1])) {
|
|
2606
|
+
return hm[1];
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
const skipValueOpts = /* @__PURE__ */ new Set([
|
|
2610
|
+
"-p",
|
|
2611
|
+
"-l",
|
|
2612
|
+
"-i",
|
|
2613
|
+
"-o",
|
|
2614
|
+
"-b",
|
|
2615
|
+
"-c",
|
|
2616
|
+
"-m",
|
|
2617
|
+
"-F",
|
|
2618
|
+
"-e",
|
|
2619
|
+
"-S",
|
|
2620
|
+
"-W",
|
|
2621
|
+
"-J",
|
|
2622
|
+
"-I",
|
|
2623
|
+
"-T",
|
|
2624
|
+
"-B",
|
|
2625
|
+
"-L",
|
|
2626
|
+
"-R",
|
|
2627
|
+
"-D",
|
|
2628
|
+
"-E",
|
|
2629
|
+
"-M",
|
|
2630
|
+
"-O",
|
|
2631
|
+
"-P",
|
|
2632
|
+
"-q"
|
|
2633
|
+
]);
|
|
2634
|
+
let foundCmd = false;
|
|
2635
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
2636
|
+
const t = tokens[i];
|
|
2637
|
+
if (t === "ssh" || t === "scp" || t === "rsync") {
|
|
2638
|
+
foundCmd = true;
|
|
2639
|
+
continue;
|
|
2640
|
+
}
|
|
2641
|
+
if (!foundCmd) continue;
|
|
2642
|
+
if (t.startsWith("-")) {
|
|
2643
|
+
if (skipValueOpts.has(t) && i + 1 < tokens.length) {
|
|
2644
|
+
i++;
|
|
2645
|
+
}
|
|
2646
|
+
continue;
|
|
2647
|
+
}
|
|
2648
|
+
if (t.startsWith("/")) continue;
|
|
2649
|
+
return t;
|
|
2650
|
+
}
|
|
2651
|
+
return null;
|
|
2214
2652
|
}
|
|
2215
2653
|
function isLocalHost(host) {
|
|
2216
2654
|
const localNames = ["localhost", "127.0.0.1", "::1", process.env.HOSTNAME || "", "0.0.0.0"];
|
|
2217
2655
|
return localNames.includes(host.replace(/.*@/, ""));
|
|
2218
2656
|
}
|
|
2219
2657
|
function checkLateralMovement(actionDetail) {
|
|
2658
|
+
if (/\b(?:python|python3|perl|ruby|node|php|lua)\b\s+.*-[cer][a-zA-Z]*\s/.test(actionDetail)) return null;
|
|
2659
|
+
const segments = actionDetail.split(/&&|\|\||;|\n/).map((s) => s.trim()).filter(Boolean);
|
|
2660
|
+
let hasRealCall = false;
|
|
2661
|
+
for (const seg of segments) {
|
|
2662
|
+
const parts = seg.split(/\s+/);
|
|
2663
|
+
const fw = (parts[0] || "").toLowerCase();
|
|
2664
|
+
if (fw === "echo" || fw === "printf") continue;
|
|
2665
|
+
if (/(?:ssh|scp|rsync)\s+/.test(seg)) {
|
|
2666
|
+
hasRealCall = true;
|
|
2667
|
+
break;
|
|
2668
|
+
}
|
|
2669
|
+
}
|
|
2670
|
+
if (!hasRealCall) return null;
|
|
2220
2671
|
if (/(?:ssh|scp|rsync)\s+/.test(actionDetail)) {
|
|
2221
2672
|
const targetHost = extractRemoteHost(actionDetail);
|
|
2222
2673
|
if (targetHost && !isLocalHost(targetHost)) {
|
|
@@ -2316,10 +2767,14 @@ var init_heuristic = __esm({
|
|
|
2316
2767
|
{ pattern: /mkfs\.\w+\s+\/dev\/(sd|hd|nvme|vd|loop|mapper|md|dm)\S+/i, severity: "critical", description: "\u683C\u5F0F\u5316\u5757\u8BBE\u5907 (mkfs /dev/\u5757\u8BBE\u5907)", riskType: "script_execution" },
|
|
2317
2768
|
{ pattern: /:\(\)\s*\{.*:\|:&.*\};:/i, severity: "critical", description: "Fork Bomb", riskType: "resource_exhaustion" },
|
|
2318
2769
|
// --- Critical: remote code execution ---
|
|
2319
|
-
|
|
2770
|
+
// curl|bash 优先匹配 user_rules(high/sensitive_action),见下方 UserRuleMatcher 段;
|
|
2771
|
+
// 非 bash 的 curl|sh/zsh/dash 走 CommandPatternScanner critical。两条分流由
|
|
2772
|
+
// runHeuristicCheck 的规则顺序保证(bash 在前 high,其余 fall-through 到 critical)。
|
|
2773
|
+
{ pattern: /curl\s+.*\|\s*bash/i, severity: "high", description: "curl pipe bash \u6267\u884C (user_rules)", riskType: "sensitive_action" },
|
|
2774
|
+
{ pattern: /curl\s+.*\|\s*(sh|zsh|dash)/i, severity: "critical", description: "curl pipe shell \u6267\u884C", riskType: "script_execution" },
|
|
2320
2775
|
{ pattern: /wget\s+.*\|\s*(bash|sh)/i, severity: "critical", description: "wget pipe shell \u6267\u884C", riskType: "script_execution" },
|
|
2321
2776
|
{ pattern: /eval\s+\$\(curl|eval\s+\$\(wget/i, severity: "critical", description: "eval \u8FDC\u7A0B\u4EE3\u7801\u6267\u884C", riskType: "script_execution" },
|
|
2322
|
-
{ pattern: /base64\s+-d\s+.*\|\s*bash/i, severity: "
|
|
2777
|
+
{ pattern: /base64\s+-d\s+.*\|\s*bash/i, severity: "high", description: "base64 \u89E3\u7801\u6267\u884C (user_rules)", riskType: "sensitive_action" },
|
|
2323
2778
|
// --- Critical: reverse shell ---
|
|
2324
2779
|
{ pattern: /\/dev\/tcp\/|bash\s+-i\s+>&/i, severity: "critical", description: "\u53CD\u5F39 Shell", riskType: "script_execution" },
|
|
2325
2780
|
{ pattern: /nc\s+-[el]\s+.*\d{1,5}/i, severity: "high", description: "Netcat \u53CD\u5F39\u8FDE\u63A5", riskType: "lateral_movement" },
|
|
@@ -2330,13 +2785,25 @@ var init_heuristic = __esm({
|
|
|
2330
2785
|
{ pattern: /\.ssh\/id_rsa(?!\.pub)/i, severity: "critical", description: "\u8BBF\u95EE SSH \u79C1\u94A5\u6587\u4EF6", riskType: "file_access" },
|
|
2331
2786
|
// --- Critical: privilege escalation / container escape ---
|
|
2332
2787
|
{ pattern: /\/var\/run\/docker\.sock/i, severity: "critical", description: "\u8BBF\u95EE Docker socket\uFF0C\u53EF\u80FD\u5BFC\u81F4\u5BB9\u5668\u9003\u9038", riskType: "privilege_escalation" },
|
|
2333
|
-
// ---
|
|
2334
|
-
|
|
2788
|
+
// --- Critical: dangerous permissions / ownership (提权 / 权限敞口) ---
|
|
2789
|
+
// 修正「只拦 777、xx7 漏放」的缺口:
|
|
2790
|
+
// 1) 八进制末位 7:other 位含写+执行敞口(chmod 777/647/007/4777 等);644/755/1700 不命中
|
|
2791
|
+
// 2) 八进制 setuid/setgid 位:首位 4(setuid)/2(setgid) 的 4 位八进制(chmod 4755/2755)
|
|
2792
|
+
// 3) 符号 setuid/setgid:chmod u+s / g+s / a+s
|
|
2793
|
+
// 普通权限修改(chmod 644)不在此列——L2 read_before_write 已豁免 chmod/chown(不写内容),
|
|
2794
|
+
// 危险形态在此 L1 专门拦,以「提权」正确理由拦截,不再靠 RBW 误打误撞。
|
|
2795
|
+
{ pattern: /chmod\s+[0-7]{0,3}7\b/i, severity: "critical", description: "\u5371\u9669\u6743\u9650\u8BBE\u7F6E (chmod ...7, other \u4F4D\u542B\u5199+\u6267\u884C\u655E\u53E3)", riskType: "privilege_escalation" },
|
|
2796
|
+
{ pattern: /chmod\s+[24][0-7]{3}\b/i, severity: "critical", description: "setuid/setgid \u4F4D\u8BBE\u7F6E (chmod 4xxx/2xxx, \u63D0\u6743)", riskType: "privilege_escalation" },
|
|
2797
|
+
{ pattern: /chmod\s+[ugoa]\+s\b/i, severity: "critical", description: "\u7B26\u53F7 setuid/setgid \u8BBE\u7F6E (chmod u+s/g+s/a+s, \u63D0\u6743)", riskType: "privilege_escalation" },
|
|
2335
2798
|
{ pattern: /chown\s+root|chown\s+-R\s+\w+:\w+\s+\//i, severity: "high", description: "\u4FEE\u6539\u6240\u6709\u8005\u4E3A root / \u9012\u5F52\u4FEE\u6539\u6839\u76EE\u5F55\u6240\u6709\u8005", riskType: "script_execution" },
|
|
2336
2799
|
// sudo 提权 — 只拦截 sudo 后跟的危险命令,不一刀切拦截所有 sudo
|
|
2337
2800
|
{ pattern: /sudo\s+(rm|chmod|chown|dd|mkfs|format|iptables|crontab|passwd|userdel|useradd|groupdel)\b/i, severity: "high", description: "\u63D0\u6743\u6267\u884C\u9AD8\u5371\u547D\u4EE4 (sudo + \u5371\u9669\u64CD\u4F5C)", riskType: "privilege_escalation" },
|
|
2338
2801
|
// --- High: system config overwrite ---
|
|
2802
|
+
// > /etc/ 重定向覆盖:对齐 Python CommandPatternScanner `>\s*/etc/`(不兼容引号路径),
|
|
2803
|
+
// 引号包裹的重定向目标(cat > '/etc/ssh/sshd_config')走 checkSensitivePath 敏感路径 high。
|
|
2339
2804
|
{ pattern: />\s*\/etc\//i, severity: "critical", description: "\u8986\u76D6\u5199\u5165\u7CFB\u7EDF\u914D\u7F6E\u76EE\u5F55", riskType: "file_access" },
|
|
2805
|
+
{ pattern: />\s*\/boot\//i, severity: "critical", description: "\u8986\u76D6\u5199\u5165\u542F\u52A8\u5F15\u5BFC\u76EE\u5F55", riskType: "file_access" },
|
|
2806
|
+
// 注:/proc/ 重定向覆盖走 checkSensitivePath 的 high(内核参数 /proc/sys/),此处不拦 /proc/ 避免遮蔽(对齐 Python)。
|
|
2340
2807
|
// --- High: download script + execute risk ---
|
|
2341
2808
|
{ pattern: /(?:curl|wget)\s+.*(?:-o|--output|>)\s*\S+\.(?:sh|bash)\b/i, severity: "high", description: "\u8FDC\u7A0B\u4E0B\u8F7D\u811A\u672C\u5230\u672C\u5730", riskType: "script_execution" },
|
|
2342
2809
|
{ pattern: /(?:curl|wget)\s+.*\s+&&?\s*(?:chmod\s+\+x|bash|sh)/i, severity: "high", description: "\u4E0B\u8F7D\u540E\u6267\u884C", riskType: "script_execution" },
|
|
@@ -2352,7 +2819,11 @@ var init_heuristic = __esm({
|
|
|
2352
2819
|
// --- Medium: credential export ---
|
|
2353
2820
|
{ pattern: /export\s+.*(?:TOKEN|KEY|SECRET|PASSWORD)/i, severity: "medium", description: "\u5BFC\u51FA\u654F\u611F\u51ED\u8BC1", riskType: "data_exfiltration" },
|
|
2354
2821
|
// --- High: shell rc file append (persistence) ---
|
|
2822
|
+
// 两条规则:第一条匹配 ~ 与 /home/<user>/ 形态;第二条按文件名兜底,
|
|
2823
|
+
// 覆盖非标准 home 路径(如鸿蒙 /storage/Users/currentUser/.bashrc、
|
|
2824
|
+
// /root/.bashrc),避免按 home 前缀枚举导致漏检持久化后门。
|
|
2355
2825
|
{ pattern: />>\s*(~|\/home\/[^/]+)\/(\.bashrc|\.zshrc|\.profile|\.bash_profile|\.zprofile)/, severity: "high", description: "\u68C0\u6D4B\u5230\u4FEE\u6539 Shell \u914D\u7F6E\u6587\u4EF6\uFF08~/.bashrc \u7B49\uFF09\uFF0C\u53EF\u80FD\u7528\u4E8E\u6301\u4E45\u5316\u540E\u95E8", riskType: "persistence" },
|
|
2826
|
+
{ pattern: />>\s*\S*\/(\.bashrc|\.zshrc|\.profile|\.bash_profile|\.zprofile)\b/, severity: "high", description: "\u68C0\u6D4B\u5230\u4FEE\u6539 Shell \u914D\u7F6E\u6587\u4EF6\uFF08.bashrc \u7B49\uFF09\uFF0C\u53EF\u80FD\u7528\u4E8E\u6301\u4E45\u5316\u540E\u95E8", riskType: "persistence" },
|
|
2356
2827
|
// --- High: device file write (只拦截重定向写入设备文件,不拦截读取) ---
|
|
2357
2828
|
{ pattern: />\s*\/dev\/(sd|hd|nvme|vd|loop|mem|kmsg|port|zero|random|urandom|full|core)/i, severity: "high", description: "\u91CD\u5B9A\u5411\u5199\u5165\u8BBE\u5907\u6587\u4EF6", riskType: "file_access" },
|
|
2358
2829
|
// --- High: non-interactive password modification ---
|
|
@@ -2388,17 +2859,16 @@ __export(token_stats_exports, {
|
|
|
2388
2859
|
recordTokenUsage: () => recordTokenUsage,
|
|
2389
2860
|
resetTokenStats: () => resetTokenStats
|
|
2390
2861
|
});
|
|
2391
|
-
import { readFileSync as
|
|
2392
|
-
import { join as join4 } from "path";
|
|
2393
|
-
import { homedir as homedir3 } from "os";
|
|
2862
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync2, existsSync as existsSync7, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
2394
2863
|
function getStatsPath() {
|
|
2395
2864
|
const envPath = process.env[ENV_STATS_PATH];
|
|
2396
|
-
|
|
2865
|
+
if (envPath) return envPath;
|
|
2866
|
+
return getTokenStatsPath();
|
|
2397
2867
|
}
|
|
2398
2868
|
function loadAllRecords(statsPath) {
|
|
2399
2869
|
if (!existsSync7(statsPath)) return [];
|
|
2400
2870
|
try {
|
|
2401
|
-
const data = JSON.parse(
|
|
2871
|
+
const data = JSON.parse(readFileSync8(statsPath, "utf-8"));
|
|
2402
2872
|
if (Array.isArray(data)) return data;
|
|
2403
2873
|
if (data && Array.isArray(data.records)) return data.records;
|
|
2404
2874
|
return [];
|
|
@@ -2538,13 +3008,12 @@ function resetTokenStats() {
|
|
|
2538
3008
|
}
|
|
2539
3009
|
}
|
|
2540
3010
|
}
|
|
2541
|
-
var
|
|
3011
|
+
var ENV_STATS_PATH, MAX_RECORDS;
|
|
2542
3012
|
var init_token_stats = __esm({
|
|
2543
3013
|
"src/engine/token-stats.ts"() {
|
|
2544
3014
|
"use strict";
|
|
2545
3015
|
init_esm_shims();
|
|
2546
|
-
|
|
2547
|
-
STATS_FILE = join4(STATS_DIR, "agent_moss_token_stats.json");
|
|
3016
|
+
init_paths();
|
|
2548
3017
|
ENV_STATS_PATH = "AGENT_MOSS_TOKEN_STATS_PATH";
|
|
2549
3018
|
MAX_RECORDS = 1e4;
|
|
2550
3019
|
}
|
|
@@ -2632,7 +3101,7 @@ var require_bindings = __commonJS({
|
|
|
2632
3101
|
init_esm_shims();
|
|
2633
3102
|
var fs4 = __require("fs");
|
|
2634
3103
|
var path3 = __require("path");
|
|
2635
|
-
var
|
|
3104
|
+
var fileURLToPath8 = require_file_uri_to_path();
|
|
2636
3105
|
var join6 = path3.join;
|
|
2637
3106
|
var dirname7 = path3.dirname;
|
|
2638
3107
|
var exists = fs4.accessSync && function(path4) {
|
|
@@ -2744,7 +3213,7 @@ var require_bindings = __commonJS({
|
|
|
2744
3213
|
Error.stackTraceLimit = origSTL;
|
|
2745
3214
|
var fileSchema = "file://";
|
|
2746
3215
|
if (fileName.indexOf(fileSchema) === 0) {
|
|
2747
|
-
fileName =
|
|
3216
|
+
fileName = fileURLToPath8(fileName);
|
|
2748
3217
|
}
|
|
2749
3218
|
return fileName;
|
|
2750
3219
|
};
|
|
@@ -3409,10 +3878,10 @@ init_skill_loader();
|
|
|
3409
3878
|
|
|
3410
3879
|
// src/engine/template-loader.ts
|
|
3411
3880
|
init_esm_shims();
|
|
3412
|
-
import { readFileSync as
|
|
3881
|
+
import { readFileSync as readFileSync5, existsSync as existsSync5 } from "fs";
|
|
3413
3882
|
import { resolve as resolve3, dirname as dirname4 } from "path";
|
|
3414
|
-
import { fileURLToPath as
|
|
3415
|
-
var __filename4 =
|
|
3883
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
3884
|
+
var __filename4 = fileURLToPath6(import.meta.url);
|
|
3416
3885
|
var __dirname5 = dirname4(__filename4);
|
|
3417
3886
|
var TEMPLATES_DIR = resolve3(__dirname5, "../templates");
|
|
3418
3887
|
var TEMPLATES_DIR_FALLBACK = resolve3(__dirname5, "../../../agent_moss/templates");
|
|
@@ -3430,7 +3899,7 @@ function loadTemplates() {
|
|
|
3430
3899
|
const filePath = resolve3(dir, file);
|
|
3431
3900
|
if (existsSync5(filePath)) {
|
|
3432
3901
|
const key = file.replace(".txt", "").replace("_template", "");
|
|
3433
|
-
cachedTemplates[key] =
|
|
3902
|
+
cachedTemplates[key] = readFileSync5(filePath, "utf-8");
|
|
3434
3903
|
}
|
|
3435
3904
|
}
|
|
3436
3905
|
console.log(`[AgentMoss] Loaded ${Object.keys(cachedTemplates).length} templates from ${dir}`);
|
|
@@ -3439,8 +3908,8 @@ function loadTemplates() {
|
|
|
3439
3908
|
|
|
3440
3909
|
// src/engine/script-analyzer.ts
|
|
3441
3910
|
init_esm_shims();
|
|
3442
|
-
import { readFileSync as
|
|
3443
|
-
import { resolve as resolve4, join as
|
|
3911
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
3912
|
+
import { resolve as resolve4, join as join3 } from "path";
|
|
3444
3913
|
var SUSPICIOUS_PATTERNS = [
|
|
3445
3914
|
// Network exfiltration (curl/wget POST in scripts is almost never legitimate)
|
|
3446
3915
|
{ name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
|
|
@@ -3573,8 +4042,8 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
|
|
|
3573
4042
|
}
|
|
3574
4043
|
function readScriptContent(scriptPath, maxLines = 500) {
|
|
3575
4044
|
try {
|
|
3576
|
-
const expandedPath = scriptPath.startsWith("~") ?
|
|
3577
|
-
const raw =
|
|
4045
|
+
const expandedPath = scriptPath.startsWith("~") ? join3(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve4(scriptPath);
|
|
4046
|
+
const raw = readFileSync6(expandedPath, "utf-8");
|
|
3578
4047
|
const lines = raw.split("\n").slice(0, maxLines);
|
|
3579
4048
|
return { content: lines.join("\n"), lineCount: lines.length, error: "" };
|
|
3580
4049
|
} catch (e) {
|
|
@@ -3628,8 +4097,8 @@ ${result.scriptContent}
|
|
|
3628
4097
|
init_esm_shims();
|
|
3629
4098
|
init_heuristic();
|
|
3630
4099
|
init_logic_rules();
|
|
3631
|
-
import { readFileSync as
|
|
3632
|
-
import { resolve as resolve5, join as
|
|
4100
|
+
import { readFileSync as readFileSync7, statSync, existsSync as existsSync6 } from "fs";
|
|
4101
|
+
import { resolve as resolve5, join as join4, isAbsolute, dirname as dirname5 } from "path";
|
|
3633
4102
|
import { homedir as homedir2 } from "os";
|
|
3634
4103
|
function emptyChain() {
|
|
3635
4104
|
return {
|
|
@@ -3870,7 +4339,7 @@ function extractInlineScriptPath(actionDetail) {
|
|
|
3870
4339
|
function resolvePath(p) {
|
|
3871
4340
|
let expanded = p;
|
|
3872
4341
|
if (expanded.startsWith("~/") || expanded === "~") {
|
|
3873
|
-
expanded =
|
|
4342
|
+
expanded = join4(homedir2(), expanded.slice(1));
|
|
3874
4343
|
}
|
|
3875
4344
|
return resolve5(expanded);
|
|
3876
4345
|
}
|
|
@@ -3883,7 +4352,7 @@ function readScriptContent2(scriptPath, maxFileSize = 2 * 1024 * 1024) {
|
|
|
3883
4352
|
if (stat.size > maxFileSize) {
|
|
3884
4353
|
return { content: "", lineCount: 0, fileSize: stat.size, error: `\u811A\u672C\u6587\u4EF6\u8FC7\u5927 (${stat.size} bytes)\uFF0C\u8DF3\u8FC7\u8BFB\u53D6` };
|
|
3885
4354
|
}
|
|
3886
|
-
const content =
|
|
4355
|
+
const content = readFileSync7(path3, "utf-8");
|
|
3887
4356
|
const lineCount = content ? content.split("\n").length : 0;
|
|
3888
4357
|
return { content, lineCount, fileSize: stat.size, error: "" };
|
|
3889
4358
|
} catch (e) {
|
|
@@ -3900,7 +4369,7 @@ function extractChildScriptPaths(content, baseDir) {
|
|
|
3900
4369
|
const pathStr = m[1];
|
|
3901
4370
|
if (pathStr.startsWith("-") || pathStr.startsWith("$")) continue;
|
|
3902
4371
|
if (pathStr.includes("/dev/") || pathStr.startsWith("|") || pathStr.startsWith(";")) continue;
|
|
3903
|
-
const full = isAbsolute(pathStr) ? pathStr :
|
|
4372
|
+
const full = isAbsolute(pathStr) ? pathStr : join4(baseDir, pathStr);
|
|
3904
4373
|
const resolved = resolvePath(full);
|
|
3905
4374
|
if (existsSync6(resolved)) {
|
|
3906
4375
|
try {
|
|
@@ -4327,11 +4796,13 @@ async function analyzeWithLLM(prompt, config) {
|
|
|
4327
4796
|
} catch {
|
|
4328
4797
|
}
|
|
4329
4798
|
clearTimeout(totalTimeoutTimer);
|
|
4799
|
+
const riskLevel = parsed.riskLevel ?? parsed.risk_level;
|
|
4800
|
+
const riskType = parsed.riskType ?? parsed.risk_type;
|
|
4330
4801
|
return {
|
|
4331
4802
|
allowed: parsed.allowed !== false,
|
|
4332
4803
|
reason: parsed.reason || "LLM \u5206\u6790\u5B8C\u6210",
|
|
4333
|
-
riskLevel: ["low", "medium", "high", "critical"].includes(
|
|
4334
|
-
riskType:
|
|
4804
|
+
riskLevel: ["low", "medium", "high", "critical"].includes(riskLevel) ? riskLevel : "low",
|
|
4805
|
+
riskType: riskType || "",
|
|
4335
4806
|
confidence: typeof parsed.confidence === "number" ? Math.min(100, Math.max(0, parsed.confidence)) : 70,
|
|
4336
4807
|
source: "llm",
|
|
4337
4808
|
violatedLayers: ["3"]
|
|
@@ -4449,7 +4920,10 @@ async function runAnalysis(input) {
|
|
|
4449
4920
|
if (heuristicResult && !heuristicResult.allowed) {
|
|
4450
4921
|
const skipInlineFileAccess = (isInlineScriptCmd || isLiteralTextCmd) && heuristicResult.riskType === "file_access";
|
|
4451
4922
|
if (!skipInlineFileAccess) {
|
|
4452
|
-
|
|
4923
|
+
const shouldDenyL1 = heuristicResult.riskLevel === "high" || heuristicResult.riskLevel === "critical" || heuristicResult.riskLevel === "medium" && heuristicResult.riskType === "file_access";
|
|
4924
|
+
if (shouldDenyL1) {
|
|
4925
|
+
return heuristicResult;
|
|
4926
|
+
}
|
|
4453
4927
|
}
|
|
4454
4928
|
}
|
|
4455
4929
|
let scriptChain = null;
|
|
@@ -4527,7 +5001,8 @@ async function runAnalysis(input) {
|
|
|
4527
5001
|
) && DANGEROUS_PATTERNS.some(
|
|
4528
5002
|
(p) => (p.severity === "critical" || p.severity === "high") && p.pattern.test(lowerDetail)
|
|
4529
5003
|
);
|
|
4530
|
-
|
|
5004
|
+
const hasRedirectWrite = isWriteOperation(firstCmd, lowerDetail);
|
|
5005
|
+
if (!fullCmdHit && !hasRedirectWrite) {
|
|
4531
5006
|
return {
|
|
4532
5007
|
allowed: true,
|
|
4533
5008
|
reason: `\u5B89\u5168\u7684\u53EA\u8BFB bash \u547D\u4EE4: ${firstCmd}`,
|
|
@@ -4852,10 +5327,10 @@ init_esm_shims();
|
|
|
4852
5327
|
init_runtime_config();
|
|
4853
5328
|
init_token_stats();
|
|
4854
5329
|
import { Hono } from "hono";
|
|
4855
|
-
import { readFileSync as
|
|
5330
|
+
import { readFileSync as readFileSync9, existsSync as existsSync9 } from "fs";
|
|
4856
5331
|
import { join as join5, dirname as dirname6 } from "path";
|
|
4857
|
-
import { fileURLToPath as
|
|
4858
|
-
var __dirname6 = dirname6(
|
|
5332
|
+
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
5333
|
+
var __dirname6 = dirname6(fileURLToPath7(import.meta.url));
|
|
4859
5334
|
var STATIC_DIR = join5(__dirname6, "static");
|
|
4860
5335
|
var CONSOLE_TOKEN_ENV = "AGENT_MOSS_CONSOLE_TOKEN";
|
|
4861
5336
|
function getConsoleToken() {
|
|
@@ -4882,7 +5357,7 @@ function createConsoleRouter() {
|
|
|
4882
5357
|
const serveIndex = (c) => {
|
|
4883
5358
|
const indexFile = join5(STATIC_DIR, "index.html");
|
|
4884
5359
|
if (existsSync9(indexFile)) {
|
|
4885
|
-
return c.html(
|
|
5360
|
+
return c.html(readFileSync9(indexFile, "utf-8"));
|
|
4886
5361
|
}
|
|
4887
5362
|
return c.html("<h1>AgentMoss Policy Console</h1><p>Frontend not built. Use /console/api/* endpoints.</p>");
|
|
4888
5363
|
};
|
|
@@ -4890,12 +5365,12 @@ function createConsoleRouter() {
|
|
|
4890
5365
|
console2.get("", serveIndex);
|
|
4891
5366
|
console2.get("/api/layers", (c) => {
|
|
4892
5367
|
const rc = loadRuntimeConfig();
|
|
4893
|
-
return c.json({ layers: rc.layers });
|
|
5368
|
+
return c.json({ layers: rc.layers, env_overrides: getEnvOverrides() });
|
|
4894
5369
|
});
|
|
4895
5370
|
console2.put("/api/layers", async (c) => {
|
|
4896
5371
|
const body = await c.req.json();
|
|
4897
5372
|
const rc = updateLayerEnabled(body.layer_key, body.enabled);
|
|
4898
|
-
return c.json({ layers: rc.layers });
|
|
5373
|
+
return c.json({ layers: rc.layers, env_overrides: getEnvOverrides() });
|
|
4899
5374
|
});
|
|
4900
5375
|
console2.get("/api/rules", (c) => {
|
|
4901
5376
|
const rc = loadRuntimeConfig();
|
|
@@ -4904,22 +5379,22 @@ function createConsoleRouter() {
|
|
|
4904
5379
|
console2.put("/api/rules/enabled", async (c) => {
|
|
4905
5380
|
const b = await c.req.json();
|
|
4906
5381
|
const rc = updateRuleEnabled(b.layer, b.category, b.rule_id, b.enabled);
|
|
4907
|
-
return c.json(
|
|
5382
|
+
return c.json(rc);
|
|
4908
5383
|
});
|
|
4909
5384
|
console2.put("/api/rules/deny_mode", async (c) => {
|
|
4910
5385
|
const b = await c.req.json();
|
|
4911
5386
|
const rc = updateRuleDenyMode(b.layer, b.category, b.rule_id, b.deny_mode);
|
|
4912
|
-
return c.json(
|
|
5387
|
+
return c.json(rc);
|
|
4913
5388
|
});
|
|
4914
5389
|
console2.put("/api/rules/skip_l3", async (c) => {
|
|
4915
5390
|
const b = await c.req.json();
|
|
4916
5391
|
const rc = updateRuleSkipL3(b.layer, b.category, b.rule_id, b.skip_l3_on_disabled);
|
|
4917
|
-
return c.json(
|
|
5392
|
+
return c.json(rc);
|
|
4918
5393
|
});
|
|
4919
5394
|
console2.put("/api/categories/enabled", async (c) => {
|
|
4920
5395
|
const b = await c.req.json();
|
|
4921
5396
|
const rc = updateCategoryEnabled(b.layer, b.category, b.enabled);
|
|
4922
|
-
return c.json(
|
|
5397
|
+
return c.json(rc);
|
|
4923
5398
|
});
|
|
4924
5399
|
console2.post("/api/rules", async (c) => {
|
|
4925
5400
|
const b = await c.req.json();
|
|
@@ -4935,38 +5410,38 @@ function createConsoleRouter() {
|
|
|
4935
5410
|
credential: b.credential,
|
|
4936
5411
|
deny_mode: b.deny_mode
|
|
4937
5412
|
});
|
|
4938
|
-
return c.json(
|
|
5413
|
+
return c.json(rc);
|
|
4939
5414
|
});
|
|
4940
5415
|
console2.delete("/api/rules", async (c) => {
|
|
4941
5416
|
const b = await c.req.json();
|
|
4942
5417
|
deleteCustomRule(b.layer, b.category, b.rule_id);
|
|
4943
5418
|
const rc = loadRuntimeConfig();
|
|
4944
|
-
return c.json(
|
|
5419
|
+
return c.json(rc);
|
|
4945
5420
|
});
|
|
4946
5421
|
console2.get("/api/skills", (c) => {
|
|
4947
5422
|
const rc = loadRuntimeConfig();
|
|
4948
|
-
return c.json(
|
|
5423
|
+
return c.json(rc.L3_skills);
|
|
4949
5424
|
});
|
|
4950
5425
|
console2.put("/api/skills/enabled", async (c) => {
|
|
4951
5426
|
const b = await c.req.json();
|
|
4952
5427
|
const rc = updateSkillEnabled(b.skill_id, b.enabled);
|
|
4953
|
-
return c.json(
|
|
5428
|
+
return c.json(rc);
|
|
4954
5429
|
});
|
|
4955
5430
|
console2.put("/api/skill-categories/enabled", async (c) => {
|
|
4956
5431
|
const b = await c.req.json();
|
|
4957
5432
|
const rc = updateSkillCategoryEnabled(b.category, b.enabled);
|
|
4958
|
-
return c.json(
|
|
5433
|
+
return c.json(rc);
|
|
4959
5434
|
});
|
|
4960
5435
|
console2.post("/api/skills", async (c) => {
|
|
4961
5436
|
const b = await c.req.json();
|
|
4962
5437
|
const rc = addCustomSkill(b.skill_id, b.category || "user_custom", b.keywords || [], b.content);
|
|
4963
|
-
return c.json(
|
|
5438
|
+
return c.json(rc);
|
|
4964
5439
|
});
|
|
4965
5440
|
console2.delete("/api/skills", async (c) => {
|
|
4966
5441
|
const b = await c.req.json();
|
|
4967
5442
|
deleteCustomSkill(b.skill_id, b.category);
|
|
4968
5443
|
const rc = loadRuntimeConfig();
|
|
4969
|
-
return c.json(
|
|
5444
|
+
return c.json(rc);
|
|
4970
5445
|
});
|
|
4971
5446
|
console2.get("/api/skills/:skill_id/content", async (c) => {
|
|
4972
5447
|
const skillId = c.req.param("skill_id");
|
|
@@ -4976,10 +5451,10 @@ function createConsoleRouter() {
|
|
|
4976
5451
|
const skillPath = join5(dirname6(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
|
|
4977
5452
|
if (!existsSync9(skillPath)) {
|
|
4978
5453
|
const builtinPath = join5(__dirname6, "..", "..", "agent_moss", "skills", `${skillId}.md`);
|
|
4979
|
-
if (existsSync9(builtinPath)) return c.text(
|
|
5454
|
+
if (existsSync9(builtinPath)) return c.text(readFileSync9(builtinPath, "utf-8"));
|
|
4980
5455
|
return c.json({ error: "skill not found" }, 404);
|
|
4981
5456
|
}
|
|
4982
|
-
return c.text(
|
|
5457
|
+
return c.text(readFileSync9(skillPath, "utf-8"));
|
|
4983
5458
|
});
|
|
4984
5459
|
console2.get("/api/agents", (c) => {
|
|
4985
5460
|
const rc = loadRuntimeConfig();
|
|
@@ -5021,7 +5496,7 @@ function createConsoleRouter() {
|
|
|
5021
5496
|
});
|
|
5022
5497
|
console2.get("/api/config", (c) => {
|
|
5023
5498
|
const rc = loadRuntimeConfig();
|
|
5024
|
-
return c.json(
|
|
5499
|
+
return c.json(rc);
|
|
5025
5500
|
});
|
|
5026
5501
|
console2.get("/api/env-overrides", (c) => {
|
|
5027
5502
|
return c.json({ overrides: getEnvOverrides() });
|
|
@@ -5029,7 +5504,7 @@ function createConsoleRouter() {
|
|
|
5029
5504
|
console2.post("/api/reset", (c) => {
|
|
5030
5505
|
const defaults = generateSourceDefaults();
|
|
5031
5506
|
saveRuntimeConfig(defaults);
|
|
5032
|
-
return c.json(
|
|
5507
|
+
return c.json(defaults);
|
|
5033
5508
|
});
|
|
5034
5509
|
console2.get("/api/token-stats", (c) => {
|
|
5035
5510
|
const days = parseInt(c.req.query("days") || "7", 10);
|
|
@@ -5037,7 +5512,7 @@ function createConsoleRouter() {
|
|
|
5037
5512
|
});
|
|
5038
5513
|
console2.get("/api/token-stats/recent", (c) => {
|
|
5039
5514
|
const limit = parseInt(c.req.query("limit") || "20", 10);
|
|
5040
|
-
return c.json(
|
|
5515
|
+
return c.json(getRecentRecords(limit));
|
|
5041
5516
|
});
|
|
5042
5517
|
console2.get("/api/token-stats/trend", (c) => {
|
|
5043
5518
|
const days = parseInt(c.req.query("days") || "7", 10);
|