@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/cli.js
CHANGED
|
@@ -43,6 +43,90 @@ var init_esm_shims = __esm({
|
|
|
43
43
|
}
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
+
// src/infra/paths.ts
|
|
47
|
+
import { join } from "path";
|
|
48
|
+
import { homedir } from "os";
|
|
49
|
+
import { readFileSync } from "fs";
|
|
50
|
+
function isRoot() {
|
|
51
|
+
const getuid = process.getuid;
|
|
52
|
+
return typeof getuid === "function" && getuid() === 0;
|
|
53
|
+
}
|
|
54
|
+
function resolveOwnerHome() {
|
|
55
|
+
for (const name of [process.env.SUDO_USER, process.env.LOGNAME, process.env.USER]) {
|
|
56
|
+
if (name && name !== "root" && name !== "") {
|
|
57
|
+
const home = homeOf(name);
|
|
58
|
+
if (home) return home;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const passwd = readFileSync("/etc/passwd", "utf-8");
|
|
63
|
+
for (const line of passwd.split("\n")) {
|
|
64
|
+
if (!line || line.startsWith("#")) continue;
|
|
65
|
+
const parts = line.split(":");
|
|
66
|
+
if (parts.length < 6) continue;
|
|
67
|
+
const uid = parseInt(parts[2], 10);
|
|
68
|
+
const name = parts[0];
|
|
69
|
+
if (uid >= 1e3 && name && !name.startsWith("_")) {
|
|
70
|
+
return parts[5];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
function homeOf(name) {
|
|
78
|
+
try {
|
|
79
|
+
const passwd = readFileSync("/etc/passwd", "utf-8");
|
|
80
|
+
for (const line of passwd.split("\n")) {
|
|
81
|
+
if (!line || line.startsWith("#")) continue;
|
|
82
|
+
const parts = line.split(":");
|
|
83
|
+
if (parts.length >= 6 && parts[0] === name) {
|
|
84
|
+
return parts[5];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
} catch {
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
function getDataRoot() {
|
|
92
|
+
const envPath = process.env[ENV_DATA_ROOT];
|
|
93
|
+
if (envPath) return envPath;
|
|
94
|
+
if (isRoot()) {
|
|
95
|
+
const ownerHome = resolveOwnerHome();
|
|
96
|
+
if (ownerHome) {
|
|
97
|
+
return join(ownerHome, ".config", "agentmoss");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return DEFAULT_DATA_ROOT;
|
|
101
|
+
}
|
|
102
|
+
function getInstance() {
|
|
103
|
+
const v = process.env[ENV_INSTANCE] || "";
|
|
104
|
+
return v.trim().replace(/^[/\\]+|[/\\]+$/g, "");
|
|
105
|
+
}
|
|
106
|
+
function getConfigDir() {
|
|
107
|
+
return getDataRoot();
|
|
108
|
+
}
|
|
109
|
+
function getRuntimeConfigDir() {
|
|
110
|
+
return getDataRoot();
|
|
111
|
+
}
|
|
112
|
+
function getTokenStatsPath() {
|
|
113
|
+
const instance = getInstance();
|
|
114
|
+
if (instance) {
|
|
115
|
+
return join(getDataRoot(), instance, "agent_moss_token_stats.json");
|
|
116
|
+
}
|
|
117
|
+
return join(getDataRoot(), "agent_moss_token_stats.json");
|
|
118
|
+
}
|
|
119
|
+
var ENV_DATA_ROOT, ENV_INSTANCE, DEFAULT_DATA_ROOT;
|
|
120
|
+
var init_paths = __esm({
|
|
121
|
+
"src/infra/paths.ts"() {
|
|
122
|
+
"use strict";
|
|
123
|
+
init_esm_shims();
|
|
124
|
+
ENV_DATA_ROOT = "AGENT_MOSS_DATA_DIR";
|
|
125
|
+
ENV_INSTANCE = "AGENT_MOSS_INSTANCE";
|
|
126
|
+
DEFAULT_DATA_ROOT = join(homedir(), ".config", "agentmoss");
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
46
130
|
// src/config.ts
|
|
47
131
|
var config_exports = {};
|
|
48
132
|
__export(config_exports, {
|
|
@@ -56,11 +140,12 @@ __export(config_exports, {
|
|
|
56
140
|
isHeuristicEnabled: () => isHeuristicEnabled,
|
|
57
141
|
isLLMAvailable: () => isLLMAvailable,
|
|
58
142
|
isLogicRulesEnabled: () => isLogicRulesEnabled,
|
|
143
|
+
probeLLMConnection: () => probeLLMConnection,
|
|
59
144
|
updateLLMConfig: () => updateLLMConfig
|
|
60
145
|
});
|
|
61
146
|
import fs from "fs";
|
|
62
147
|
import path2 from "path";
|
|
63
|
-
import
|
|
148
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
64
149
|
function loadConfigFile(filePath) {
|
|
65
150
|
try {
|
|
66
151
|
const raw = fs.readFileSync(filePath, "utf-8");
|
|
@@ -72,7 +157,7 @@ function loadConfigFile(filePath) {
|
|
|
72
157
|
function resolveConfigPath() {
|
|
73
158
|
const envPath = process.env.AGENT_MOSS_CONFIG_PATH;
|
|
74
159
|
if (envPath) return envPath;
|
|
75
|
-
const userConfig = path2.join(
|
|
160
|
+
const userConfig = path2.join(getConfigDir(), "config.json");
|
|
76
161
|
if (fs.existsSync(userConfig)) return userConfig;
|
|
77
162
|
const defaultConfig = path2.join(__dirname, "..", "..", "agent_moss", "infra", "config.json.example");
|
|
78
163
|
if (fs.existsSync(defaultConfig)) {
|
|
@@ -123,16 +208,38 @@ function getTimeoutConfig() {
|
|
|
123
208
|
function getRetryConfig() {
|
|
124
209
|
return retryConfig;
|
|
125
210
|
}
|
|
211
|
+
function wildcardToRegExp(pattern) {
|
|
212
|
+
return pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, ".*");
|
|
213
|
+
}
|
|
126
214
|
function loadCustomRules() {
|
|
215
|
+
const rules = [];
|
|
127
216
|
try {
|
|
128
217
|
const raw = process.env.AGENT_MOSS_CUSTOM_RULES;
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
218
|
+
if (raw) {
|
|
219
|
+
const parsed = JSON.parse(raw);
|
|
220
|
+
if (Array.isArray(parsed)) {
|
|
221
|
+
for (const r of parsed) {
|
|
222
|
+
if (r && r.pattern && r.action) rules.push({ pattern: r.pattern, action: r.action, severity: r.severity || "high" });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
133
226
|
} catch {
|
|
134
|
-
return [];
|
|
135
227
|
}
|
|
228
|
+
for (const rulesPath of USER_RULES_PATHS) {
|
|
229
|
+
try {
|
|
230
|
+
if (!fs.existsSync(rulesPath)) continue;
|
|
231
|
+
const data = JSON.parse(fs.readFileSync(rulesPath, "utf-8"));
|
|
232
|
+
const actions = Array.isArray(data?.sensitive_actions) ? data.sensitive_actions : [];
|
|
233
|
+
for (const r of actions) {
|
|
234
|
+
const p = r?.pattern;
|
|
235
|
+
if (!p) continue;
|
|
236
|
+
rules.push({ pattern: wildcardToRegExp(p), action: "Deny", severity: "high" });
|
|
237
|
+
}
|
|
238
|
+
break;
|
|
239
|
+
} catch {
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return rules;
|
|
136
243
|
}
|
|
137
244
|
function getLLMConfig() {
|
|
138
245
|
return llmConfig;
|
|
@@ -147,6 +254,30 @@ function isLLMAvailable() {
|
|
|
147
254
|
if (process.env.AGENT_MOSS_DISABLE_LLM === "1") return false;
|
|
148
255
|
return llmConfig.apiKey.length > 0;
|
|
149
256
|
}
|
|
257
|
+
async function probeLLMConnection(timeoutMs = 8e3) {
|
|
258
|
+
if (!isLLMAvailable()) return false;
|
|
259
|
+
const OpenAI2 = (await import("openai")).default;
|
|
260
|
+
const config = getLLMConfig();
|
|
261
|
+
const provider = detectProvider(config.baseUrl, config.provider);
|
|
262
|
+
const extraHeaders = getProviderHeaders(provider);
|
|
263
|
+
const client = new OpenAI2({
|
|
264
|
+
baseURL: config.baseUrl,
|
|
265
|
+
apiKey: config.apiKey,
|
|
266
|
+
timeout: timeoutMs,
|
|
267
|
+
maxRetries: 0,
|
|
268
|
+
defaultHeaders: Object.keys(extraHeaders).length > 0 ? extraHeaders : void 0
|
|
269
|
+
});
|
|
270
|
+
try {
|
|
271
|
+
const resp = await client.chat.completions.create({
|
|
272
|
+
model: config.model,
|
|
273
|
+
messages: [{ role: "user", content: "ping" }],
|
|
274
|
+
max_tokens: 1
|
|
275
|
+
});
|
|
276
|
+
return !!(resp && resp.choices && resp.choices[0]);
|
|
277
|
+
} catch {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
150
281
|
function getLLMFailMode() {
|
|
151
282
|
const val = (process.env.AGENT_MOSS_LLM_FAIL_MODE || "fail_open").toLowerCase();
|
|
152
283
|
return val === "fail_closed" ? "fail_closed" : "fail_open";
|
|
@@ -190,11 +321,12 @@ function isHeuristicEnabled() {
|
|
|
190
321
|
function isLogicRulesEnabled() {
|
|
191
322
|
return process.env.AGENT_MOSS_DISABLE_LOGIC_RULES !== "1";
|
|
192
323
|
}
|
|
193
|
-
var timeoutConfig, retryConfig, llmConfig, customRules, PROVIDER_HEADERS;
|
|
324
|
+
var timeoutConfig, retryConfig, llmConfig, __configModuleDir, USER_RULES_PATHS, customRules, PROVIDER_HEADERS;
|
|
194
325
|
var init_config = __esm({
|
|
195
326
|
"src/config.ts"() {
|
|
196
327
|
"use strict";
|
|
197
328
|
init_esm_shims();
|
|
329
|
+
init_paths();
|
|
198
330
|
llmConfig = (() => {
|
|
199
331
|
const configPath = resolveConfigPath();
|
|
200
332
|
const fileConfig = fs.existsSync(configPath) ? loadLLMConfigFromFile(configPath) : {};
|
|
@@ -212,6 +344,21 @@ var init_config = __esm({
|
|
|
212
344
|
timeout: parseInt(process.env.AGENT_MOSS_LLM_TIMEOUT || String(fileConfig.timeout ?? String(tr.timeout.prompt1Timeout)), 10)
|
|
213
345
|
};
|
|
214
346
|
})();
|
|
347
|
+
__configModuleDir = (() => {
|
|
348
|
+
try {
|
|
349
|
+
return path2.dirname(fileURLToPath2(import.meta.url));
|
|
350
|
+
} catch {
|
|
351
|
+
return typeof __dirname !== "undefined" ? __dirname : process.cwd();
|
|
352
|
+
}
|
|
353
|
+
})();
|
|
354
|
+
USER_RULES_PATHS = [
|
|
355
|
+
path2.join(__configModuleDir, "rules", "user_rules.json"),
|
|
356
|
+
// 源在 ts/src/rules(一般不存在)
|
|
357
|
+
path2.join(__configModuleDir, "..", "rules", "user_rules.json"),
|
|
358
|
+
// dist: dist/engine/../rules = dist/rules ✅
|
|
359
|
+
path2.join(__configModuleDir, "..", "..", "agent_moss", "rules", "user_rules.json")
|
|
360
|
+
// dev src: ts/src/../../agent_moss
|
|
361
|
+
];
|
|
215
362
|
customRules = loadCustomRules();
|
|
216
363
|
PROVIDER_HEADERS = {
|
|
217
364
|
"openrouter": {
|
|
@@ -228,7 +375,17 @@ var init_config = __esm({
|
|
|
228
375
|
// src/engine/inline-analyzer.ts
|
|
229
376
|
function isLiteralTextCommand(command) {
|
|
230
377
|
ECHO_CMD_RE.lastIndex = 0;
|
|
231
|
-
|
|
378
|
+
if (!ECHO_CMD_RE.test(command)) return false;
|
|
379
|
+
if (command.includes(">")) {
|
|
380
|
+
let d = command.replace(/<=>|>=|<=|<>|=>/g, " ");
|
|
381
|
+
d = d.replace(/(?:2|&|1)?\s*>\s*\/dev\/null\b/g, "");
|
|
382
|
+
d = d.replace(new RegExp(`>{1,2}\\s*(['"])([~./][^'"]+)\\1`, "g"), (_m, _q, p) => ">" + p);
|
|
383
|
+
d = d.replace(/'[^']*'|"[^"]*"/g, "");
|
|
384
|
+
if (/>{1,2}\s*(?!&)\S/.test(d)) {
|
|
385
|
+
return false;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return true;
|
|
232
389
|
}
|
|
233
390
|
function extractInlineCode(command) {
|
|
234
391
|
const parts = [];
|
|
@@ -305,7 +462,7 @@ function analyzeInlineScript(command) {
|
|
|
305
462
|
riskType: "file_access"
|
|
306
463
|
};
|
|
307
464
|
}
|
|
308
|
-
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;
|
|
465
|
+
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;
|
|
309
466
|
var init_inline_analyzer = __esm({
|
|
310
467
|
"src/engine/inline-analyzer.ts"() {
|
|
311
468
|
"use strict";
|
|
@@ -375,6 +532,13 @@ var init_inline_analyzer = __esm({
|
|
|
375
532
|
// xargs bash / xargs sh
|
|
376
533
|
/\|\s*xargs\s+(?:bash|sh|zsh)/gi
|
|
377
534
|
];
|
|
535
|
+
SHELL_RC_FRAGMENTS = [
|
|
536
|
+
".bashrc",
|
|
537
|
+
".zshrc",
|
|
538
|
+
".profile",
|
|
539
|
+
".bash_profile",
|
|
540
|
+
".zprofile"
|
|
541
|
+
];
|
|
378
542
|
SENSITIVE_PATH_FRAGMENTS = [
|
|
379
543
|
"/etc/shadow",
|
|
380
544
|
"/etc/gshadow",
|
|
@@ -391,9 +555,10 @@ var init_inline_analyzer = __esm({
|
|
|
391
555
|
"/dev/kmsg",
|
|
392
556
|
".ssh/id_rsa",
|
|
393
557
|
".ssh/id_ed25519",
|
|
394
|
-
".ssh/authorized_keys"
|
|
558
|
+
".ssh/authorized_keys",
|
|
559
|
+
...SHELL_RC_FRAGMENTS
|
|
395
560
|
];
|
|
396
|
-
REDIRECT_TO_SENSITIVE_RE = new RegExp(`>{1,2}\\s*(${SENSITIVE_PATH_FRAGMENTS.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})`, "i");
|
|
561
|
+
REDIRECT_TO_SENSITIVE_RE = new RegExp(`>{1,2}\\s*(?:\\S*/)?(${SENSITIVE_PATH_FRAGMENTS.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})(?:\\s|$)`, "i");
|
|
397
562
|
DIR_TRAVERSAL_KEYWORDS = [
|
|
398
563
|
// Python 目录遍历
|
|
399
564
|
"os.listdir",
|
|
@@ -416,11 +581,34 @@ function isWriteOperation(actionType, actionDetail, writeKeywords = WRITE_KEYWOR
|
|
|
416
581
|
if (writeKeywords.some((kw) => actionType.includes(kw) || actionDetail.includes(kw))) return true;
|
|
417
582
|
if (WRITE_COMMAND_RES.some((rx) => rx.test(actionDetail))) return true;
|
|
418
583
|
if (/\bsed\s+-[a-zA-Z]*i/i.test(actionDetail)) return true;
|
|
584
|
+
for (const seg of actionDetail.split(/&&|\|\||;|\n/)) {
|
|
585
|
+
const trimmed = seg.trim();
|
|
586
|
+
if (!trimmed) continue;
|
|
587
|
+
const parts = trimmed.split(/\s+/);
|
|
588
|
+
const fw = parts[0]?.toLowerCase() || "";
|
|
589
|
+
if (fw === "wget" || fw === "curl") {
|
|
590
|
+
const isStdout = /\s(?:-O|-o)-(?:\s|$)/.test(trimmed) || /\s--output-document=-(?:\s|$)/i.test(trimmed) || /\s--output=-(?:\s|$)/i.test(trimmed);
|
|
591
|
+
if (isStdout) continue;
|
|
592
|
+
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);
|
|
593
|
+
if (isDevnull) continue;
|
|
594
|
+
if (/\s(?:-O|--output-document|--output)(?:=|\s|$)/i.test(trimmed) || /\s-O\S/.test(trimmed)) {
|
|
595
|
+
return true;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
if (fw === "rsync" && !/--list-only|--dry-run/i.test(trimmed) && !/(^|[^a-zA-Z])-n([^a-zA-Z]|$)/.test(trimmed)) {
|
|
599
|
+
return true;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
419
602
|
if (actionDetail.includes(">")) {
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
603
|
+
const detailWithoutCompare = actionDetail.replace(/<=>|>=|<=|<>|=>/g, " ");
|
|
604
|
+
const detailWithoutDevnull = detailWithoutCompare.replace(DEVNULL_REDIRECT_RE, "");
|
|
605
|
+
const detailUnquotedRedirect = detailWithoutDevnull.replace(
|
|
606
|
+
/>{1,2}\s*(['"])([~./][^'"]+)\1/g,
|
|
607
|
+
(_m, _q, p) => ">" + p
|
|
608
|
+
);
|
|
609
|
+
const detailWithoutQuotes = detailUnquotedRedirect.replace(/'[^']*'|"[^"]*"/g, "");
|
|
610
|
+
if (/>{1,2}\s*(?!&)\S/.test(detailWithoutQuotes)) {
|
|
611
|
+
return true;
|
|
424
612
|
}
|
|
425
613
|
}
|
|
426
614
|
return false;
|
|
@@ -444,27 +632,31 @@ function runLogicCheck(input) {
|
|
|
444
632
|
if (rbwResult) return rbwResult;
|
|
445
633
|
if (input.promptSession && input.promptSession.trim()) {
|
|
446
634
|
if (!isExcludedType) {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
const
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
635
|
+
if (INLINE_SCRIPT_RE.test(actionDetail)) {
|
|
636
|
+
} else {
|
|
637
|
+
const promptLower = input.promptSession.toLowerCase();
|
|
638
|
+
for (const pattern of INTENT_DEVIATION_PATTERNS) {
|
|
639
|
+
const intentMatched = pattern.intentKeywords.some((kw) => promptLower.includes(kw));
|
|
640
|
+
const dangerousMatched = pattern.dangerousActions.some((kw) => matchDangerousAction(actionDetail, kw));
|
|
641
|
+
if (intentMatched && dangerousMatched) {
|
|
642
|
+
return {
|
|
643
|
+
allowed: false,
|
|
644
|
+
reason: pattern.reason,
|
|
645
|
+
riskLevel: "high",
|
|
646
|
+
riskType: "intent_deviation",
|
|
647
|
+
confidence: 85,
|
|
648
|
+
source: "logic_rule",
|
|
649
|
+
violatedLayers: ["2"]
|
|
650
|
+
};
|
|
651
|
+
}
|
|
461
652
|
}
|
|
462
653
|
}
|
|
463
654
|
}
|
|
464
655
|
}
|
|
465
656
|
const indirectResult = checkIndirectFileAccess(actionType, actionDetail, resolvedCwd);
|
|
466
657
|
if (indirectResult) return indirectResult;
|
|
467
|
-
if (
|
|
658
|
+
if (INLINE_SCRIPT_RE.test(actionDetail)) {
|
|
659
|
+
} else if (containsPrivilegeEscalation(actionDetail)) {
|
|
468
660
|
const detailAfterSudo = actionDetail.replace(/(^|\|\||&&|\s)sudo\s+/g, " ");
|
|
469
661
|
const isAdminCmd = ["apt", "systemctl", "yum", "dnf", "pacman", "zypper", "apk"].some((kw) => detailAfterSudo.includes(kw));
|
|
470
662
|
if (!isAdminCmd) {
|
|
@@ -494,6 +686,9 @@ function extractFilePaths(text) {
|
|
|
494
686
|
const paths = [];
|
|
495
687
|
const unixPaths = text.match(/(?:^|\s)(\/[^\s;|&><'"]+)/g);
|
|
496
688
|
if (unixPaths) paths.push(...unixPaths.map((p) => p.trim()));
|
|
689
|
+
const textNoQuotes = text.replace(/'[^']*'|"[^"]*"/g, "");
|
|
690
|
+
const kvPaths = textNoQuotes.match(/(?:=)(\/[^\s;|&><'"]+)/g);
|
|
691
|
+
if (kvPaths) paths.push(...kvPaths.map((p) => p.replace(/^=/, "")));
|
|
497
692
|
const quotedPaths = text.match(/["']\/([^"']+)["']/g);
|
|
498
693
|
if (quotedPaths) paths.push(...quotedPaths.map((p) => p.replace(/["']/g, "")));
|
|
499
694
|
return [...new Set(paths)];
|
|
@@ -504,8 +699,74 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
|
|
|
504
699
|
const isBashLike = actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash";
|
|
505
700
|
const isWrite = isBashLike ? isWriteOperation(actionType, actionDetail) : WRITE_KEYWORDS.some((kw) => actionType.includes(kw));
|
|
506
701
|
if (!isWrite) return null;
|
|
507
|
-
const
|
|
508
|
-
if (
|
|
702
|
+
const WRITE_CMDS = /* @__PURE__ */ new Set(["cp", "mv", "install", "tee", "dd", "truncate", "mkfs", "fdisk", "parted"]);
|
|
703
|
+
if (isBashLike) {
|
|
704
|
+
const detailNoCompare = actionDetail.replace(/<=>|>=|<=|<>|=>/g, " ");
|
|
705
|
+
const detailNoDevnull = detailNoCompare.replace(DEVNULL_REDIRECT_RE, "");
|
|
706
|
+
const detailUnquotedRedirect = detailNoDevnull.replace(
|
|
707
|
+
/>{1,2}\s*(['"])([~./][^'"]+)\1/g,
|
|
708
|
+
(_m, _q, p) => ">" + p
|
|
709
|
+
);
|
|
710
|
+
const detailNoQuotes = detailUnquotedRedirect.replace(/'[^']*'|"[^"]*"/g, "");
|
|
711
|
+
const hasRedirectWrite = />{1,2}\s*(?!&)\S/.test(detailNoQuotes);
|
|
712
|
+
let hasWriteSeg = hasRedirectWrite;
|
|
713
|
+
if (!hasWriteSeg) {
|
|
714
|
+
for (const seg of actionDetail.split(/&&|\|\||;|\n/)) {
|
|
715
|
+
const trimmed = seg.trim();
|
|
716
|
+
if (!trimmed) continue;
|
|
717
|
+
const fw = trimmed.split(/\s+/)[0]?.toLowerCase() || "";
|
|
718
|
+
if (WRITE_CMDS.has(fw) || /\bsed\s+-[a-zA-Z]*i/i.test(trimmed)) {
|
|
719
|
+
hasWriteSeg = true;
|
|
720
|
+
break;
|
|
721
|
+
}
|
|
722
|
+
if ((fw === "wget" || fw === "curl") && (/\s(?:-O|--output-document|--output)(?:=|\s|$)/i.test(trimmed) || /\s-O\S/.test(trimmed))) {
|
|
723
|
+
hasWriteSeg = true;
|
|
724
|
+
break;
|
|
725
|
+
}
|
|
726
|
+
if (fw === "rsync" && !/--list-only|--dry-run/i.test(trimmed) && !/(^|[^a-zA-Z])-n([^a-zA-Z]|$)/.test(trimmed)) {
|
|
727
|
+
hasWriteSeg = true;
|
|
728
|
+
break;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
if (!hasWriteSeg) return null;
|
|
733
|
+
}
|
|
734
|
+
const SOURCE_ARG_CMDS = /* @__PURE__ */ new Set(["cp", "mv", "install", "rsync"]);
|
|
735
|
+
const REDIRECT_TARGET_RE = new RegExp(`>{1,2}\\s*(?!&)(?:'(/[^']+)'|"(/[^\\"]+)"|(/[^\\s;|&><'\\"]+))`);
|
|
736
|
+
const targetPaths = [];
|
|
737
|
+
for (const seg of actionDetail.split(/&&|\|\||;|\n/)) {
|
|
738
|
+
const trimmed = seg.trim();
|
|
739
|
+
if (!trimmed) continue;
|
|
740
|
+
const firstWord = trimmed.split(/\s+/)[0]?.toLowerCase() || "";
|
|
741
|
+
if (firstWord === "cd") {
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
if (READ_ONLY_COMMANDS.has(firstWord)) {
|
|
745
|
+
const segUnquoted = trimmed.replace(new RegExp(`>{1,2}\\s*(['"])([~./][^'"]+)\\1`, "g"), (_m, _q, p) => ">" + p);
|
|
746
|
+
const segNoDevnull = segUnquoted.replace(DEVNULL_REDIRECT_RE, "");
|
|
747
|
+
const segNoQuotes = segNoDevnull.replace(/'[^']*'|"[^"]*"/g, "");
|
|
748
|
+
const m = segNoQuotes.match(REDIRECT_TARGET_RE);
|
|
749
|
+
if (m) targetPaths.push(m[1] || m[2] || m[3]);
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
const segPaths = extractFilePaths(trimmed);
|
|
753
|
+
const segPathsFiltered = firstWord === "wget" || firstWord === "curl" ? segPaths.filter((p) => p !== "/dev/null") : segPaths;
|
|
754
|
+
if (SOURCE_ARG_CMDS.has(firstWord)) {
|
|
755
|
+
if (segPathsFiltered.length > 1) targetPaths.push(...segPathsFiltered.slice(1));
|
|
756
|
+
} else if (firstWord === "dd") {
|
|
757
|
+
const ifSourceMatches = trimmed.match(/\bif=(\/[^\s;|&><'"]+)/g);
|
|
758
|
+
const ifSourcePaths = new Set(ifSourceMatches ? ifSourceMatches.map((m) => m.replace(/^if=/, "")) : []);
|
|
759
|
+
const devNullMatches = trimmed.match(/\bof=(\/dev\/\S+)/g);
|
|
760
|
+
const devNullTargets = new Set(devNullMatches ? devNullMatches.map((m) => m.replace(/^of=/, "")) : []);
|
|
761
|
+
for (const sp of segPaths) {
|
|
762
|
+
if (!ifSourcePaths.has(sp) && !devNullTargets.has(sp)) targetPaths.push(sp);
|
|
763
|
+
}
|
|
764
|
+
} else {
|
|
765
|
+
targetPaths.push(...segPathsFiltered);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
const uniqueTargetPaths = [...new Set(targetPaths)];
|
|
769
|
+
if (uniqueTargetPaths.length === 0) return null;
|
|
509
770
|
const historyReadPaths = /* @__PURE__ */ new Set();
|
|
510
771
|
for (const action of actionHistory) {
|
|
511
772
|
const histDetail = action.actionDetail.toLowerCase();
|
|
@@ -518,7 +779,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
|
|
|
518
779
|
}
|
|
519
780
|
}
|
|
520
781
|
const unreadPaths = [];
|
|
521
|
-
for (const path3 of
|
|
782
|
+
for (const path3 of uniqueTargetPaths) {
|
|
522
783
|
const hasRead = [...historyReadPaths].some(
|
|
523
784
|
(rp) => path3 === rp || path3.startsWith(rp) || rp.startsWith(path3)
|
|
524
785
|
);
|
|
@@ -581,6 +842,18 @@ function checkUserDeletionConsent(actionType, actionDetail, actionHistory) {
|
|
|
581
842
|
violatedLayers: ["2"]
|
|
582
843
|
};
|
|
583
844
|
}
|
|
845
|
+
function containsPrivilegeEscalation(actionDetail) {
|
|
846
|
+
const ad = actionDetail.toLowerCase();
|
|
847
|
+
for (const seg of ad.split(/&&|\|\||;|\n/)) {
|
|
848
|
+
const trimmed = seg.trim();
|
|
849
|
+
if (!trimmed) continue;
|
|
850
|
+
const parts = trimmed.split(/\s+/);
|
|
851
|
+
const fw = parts[0]?.toLowerCase() || "";
|
|
852
|
+
if (fw === "sudo" || fw === "su" || fw === "setuid") return true;
|
|
853
|
+
if (fw === "chmod" && /\bu\s*\+\s*s\b/.test(trimmed)) return true;
|
|
854
|
+
}
|
|
855
|
+
return false;
|
|
856
|
+
}
|
|
584
857
|
function checkIndirectFileAccess(actionType, actionDetail, cwd) {
|
|
585
858
|
if (actionType !== "bash" && actionType !== "executebash" && actionType !== "terminal-executebash") {
|
|
586
859
|
return null;
|
|
@@ -685,12 +958,13 @@ var init_logic_rules = __esm({
|
|
|
685
958
|
{ path: "/sys/", riskLevel: "high", desc: "sysfs \u5185\u6838\u63A5\u53E3" },
|
|
686
959
|
// 设备文件(/dev/null、/dev/zero、/dev/urandom 已移除,误报率极高)
|
|
687
960
|
{ path: "/dev/random", riskLevel: "high", desc: "\u963B\u585E\u5F0F\u968F\u673A\u6570\u8BBE\u5907\uFF08\u8BFB\u53D6\u8017\u5C3D\u71B5\u6C60\uFF09" },
|
|
688
|
-
{ path: "/dev/mem", riskLevel: "critical", desc: "\u7269\u7406\u5185\u5B58\u8BBF\u95EE\u8BBE\u5907" },
|
|
961
|
+
{ path: "/dev/mem", riskLevel: "critical", desc: "\u7269\u7406\u5185\u5B58\u8BBF\u95EE\u8BBE\u5907\uFF08\u8BFB\u5199\u5747\u5371\u9669\uFF09", credential: true },
|
|
689
962
|
{ path: "/dev/kmsg", riskLevel: "high", desc: "\u5185\u6838\u6D88\u606F\u7F13\u51B2\u533A" },
|
|
690
963
|
// SSH 主机私钥
|
|
691
|
-
|
|
692
|
-
{ path: "/etc/ssh/
|
|
693
|
-
{ path: "/etc/ssh/
|
|
964
|
+
// SSH 主机私钥(排除 .pub 公钥变体,对齐 Python .ssh/id_rsa(?!\.pub) 范式)
|
|
965
|
+
{ path: "/etc/ssh/ssh_host_rsa_key(?!\\.pub)", riskLevel: "critical", desc: "SSH \u4E3B\u673A RSA \u79C1\u94A5", credential: true },
|
|
966
|
+
{ path: "/etc/ssh/ssh_host_ed25519_key(?!\\.pub)", riskLevel: "critical", desc: "SSH \u4E3B\u673A Ed25519 \u79C1\u94A5", credential: true },
|
|
967
|
+
{ path: "/etc/ssh/ssh_host_ecdsa_key(?!\\.pub)", riskLevel: "critical", desc: "SSH \u4E3B\u673A ECDSA \u79C1\u94A5", credential: true }
|
|
694
968
|
];
|
|
695
969
|
HOME_SENSITIVE_PATHS = [
|
|
696
970
|
{ path: ".ssh/id_rsa", riskLevel: "critical", desc: "SSH \u79C1\u94A5", credential: true },
|
|
@@ -731,7 +1005,7 @@ var init_logic_rules = __esm({
|
|
|
731
1005
|
"cat"
|
|
732
1006
|
]);
|
|
733
1007
|
NON_WRITE_TOOL_PREFIXES = ["memory-", "skill-manager-", "mailbox-", "app-"];
|
|
734
|
-
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"];
|
|
1008
|
+
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"];
|
|
735
1009
|
WRITE_COMMAND_RES = [
|
|
736
1010
|
/\brm\b/,
|
|
737
1011
|
/\bunlink\b/,
|
|
@@ -777,6 +1051,12 @@ var init_logic_rules = __esm({
|
|
|
777
1051
|
"nl",
|
|
778
1052
|
"tac",
|
|
779
1053
|
"rev",
|
|
1054
|
+
// NEW-FP-I: echo/printf 是文本输出命令,参数是字面量非写目标。加入此集让
|
|
1055
|
+
// read_before_write 的 echo 段走只读命令分支(只提取重定向目标,不提取参数路径),
|
|
1056
|
+
// 避免 echo "cat /etc/shadow" > /tmp/x.sh 的引号内 /etc/shadow 被当写目标误报。
|
|
1057
|
+
// echo "x" > /tmp/x.sh 的重定向目标 /tmp/x.sh 仍被提取(真实写不漏报)。
|
|
1058
|
+
"echo",
|
|
1059
|
+
"printf",
|
|
780
1060
|
"lsblk",
|
|
781
1061
|
"blockdev",
|
|
782
1062
|
"smartctl",
|
|
@@ -829,7 +1109,7 @@ var init_logic_rules = __esm({
|
|
|
829
1109
|
"modinfo",
|
|
830
1110
|
"sysctl"
|
|
831
1111
|
]);
|
|
832
|
-
DEVNULL_REDIRECT_RE = /(?:2|&|1)?\s*>\s*\/dev\/null\b
|
|
1112
|
+
DEVNULL_REDIRECT_RE = /(?:2|&|1)?\s*>\s*\/dev\/null\b/g;
|
|
833
1113
|
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"];
|
|
834
1114
|
CREATE_KEYWORDS = ["\u521B\u5EFA", "\u65B0\u5EFA", "create", "new file", "touch"];
|
|
835
1115
|
PASSWORD_MODIFY_PATTERNS = [
|
|
@@ -877,9 +1157,9 @@ var init_logic_rules = __esm({
|
|
|
877
1157
|
});
|
|
878
1158
|
|
|
879
1159
|
// src/engine/skill-loader.ts
|
|
880
|
-
import { readFileSync, readdirSync as readdirSync2, existsSync as existsSync2 } from "fs";
|
|
1160
|
+
import { readFileSync as readFileSync2, readdirSync as readdirSync2, existsSync as existsSync2 } from "fs";
|
|
881
1161
|
import { resolve, dirname } from "path";
|
|
882
|
-
import { fileURLToPath as
|
|
1162
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
883
1163
|
function loadAllSkills() {
|
|
884
1164
|
if (cachedSkills) return cachedSkills;
|
|
885
1165
|
cachedSkills = {};
|
|
@@ -891,7 +1171,7 @@ function loadAllSkills() {
|
|
|
891
1171
|
const files = readdirSync2(dir).filter((f) => f.endsWith(".md"));
|
|
892
1172
|
for (const f of files) {
|
|
893
1173
|
const name = f.replace(".md", "");
|
|
894
|
-
cachedSkills[name] =
|
|
1174
|
+
cachedSkills[name] = readFileSync2(resolve(dir, f), "utf-8");
|
|
895
1175
|
}
|
|
896
1176
|
console.log(`[AgentMoss] Loaded ${Object.keys(cachedSkills).length} skills from ${dir}`);
|
|
897
1177
|
return cachedSkills;
|
|
@@ -950,7 +1230,7 @@ var init_skill_loader = __esm({
|
|
|
950
1230
|
"src/engine/skill-loader.ts"() {
|
|
951
1231
|
"use strict";
|
|
952
1232
|
init_esm_shims();
|
|
953
|
-
__filename2 =
|
|
1233
|
+
__filename2 = fileURLToPath3(import.meta.url);
|
|
954
1234
|
__dirname2 = dirname(__filename2);
|
|
955
1235
|
SKILLS_DIR = resolve(__dirname2, "../skills");
|
|
956
1236
|
SKILLS_DIR_FALLBACK = resolve(__dirname2, "../../../agent_moss/skills");
|
|
@@ -1022,11 +1302,13 @@ __export(runtime_config_exports, {
|
|
|
1022
1302
|
updateSkillCategoryEnabled: () => updateSkillCategoryEnabled,
|
|
1023
1303
|
updateSkillEnabled: () => updateSkillEnabled
|
|
1024
1304
|
});
|
|
1025
|
-
import { readFileSync as
|
|
1026
|
-
import { join } from "path";
|
|
1027
|
-
import {
|
|
1028
|
-
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
1305
|
+
import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync3, mkdirSync, unlinkSync, readdirSync as readdirSync3 } from "fs";
|
|
1306
|
+
import { join as join2 } from "path";
|
|
1307
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
1029
1308
|
import { dirname as dirname2 } from "path";
|
|
1309
|
+
function runtimeDir() {
|
|
1310
|
+
return getRuntimeConfigDir();
|
|
1311
|
+
}
|
|
1030
1312
|
function getRuntimeConfigPath() {
|
|
1031
1313
|
const envPath = process.env[ENV_RUNTIME_CONFIG_PATH];
|
|
1032
1314
|
if (envPath) return envPath;
|
|
@@ -1059,7 +1341,7 @@ function loadRuntimeConfig() {
|
|
|
1059
1341
|
return seeded;
|
|
1060
1342
|
}
|
|
1061
1343
|
try {
|
|
1062
|
-
const data = JSON.parse(
|
|
1344
|
+
const data = JSON.parse(readFileSync3(p, "utf-8"));
|
|
1063
1345
|
const merged = mergeConfig(data, generateSourceDefaults());
|
|
1064
1346
|
return {
|
|
1065
1347
|
version: data.version || 1,
|
|
@@ -1129,7 +1411,7 @@ function hasRuntimeConfig() {
|
|
|
1129
1411
|
const p = getRuntimeConfigPath();
|
|
1130
1412
|
if (!existsSync3(p)) return false;
|
|
1131
1413
|
try {
|
|
1132
|
-
JSON.parse(
|
|
1414
|
+
JSON.parse(readFileSync3(p, "utf-8"));
|
|
1133
1415
|
return true;
|
|
1134
1416
|
} catch {
|
|
1135
1417
|
return false;
|
|
@@ -1317,11 +1599,11 @@ function _agentRulesToDefaults() {
|
|
|
1317
1599
|
return out;
|
|
1318
1600
|
}
|
|
1319
1601
|
for (const agentId of entries) {
|
|
1320
|
-
const rulesFile =
|
|
1602
|
+
const rulesFile = join2(agentsDir, agentId, "rules.json");
|
|
1321
1603
|
if (!existsSync3(rulesFile)) continue;
|
|
1322
1604
|
let data;
|
|
1323
1605
|
try {
|
|
1324
|
-
data = JSON.parse(
|
|
1606
|
+
data = JSON.parse(readFileSync3(rulesFile, "utf-8"));
|
|
1325
1607
|
} catch {
|
|
1326
1608
|
continue;
|
|
1327
1609
|
}
|
|
@@ -1737,12 +2019,12 @@ function deleteCustomRule(layer, category, ruleId) {
|
|
|
1737
2019
|
function addCustomSkill(skillId, category, keywords, content) {
|
|
1738
2020
|
_validateSkillId(skillId);
|
|
1739
2021
|
const runtime = loadRuntimeConfig();
|
|
1740
|
-
const skillsDir =
|
|
2022
|
+
const skillsDir = join2(RUNTIME_CONFIG_DIR, "agent_moss_skills");
|
|
1741
2023
|
try {
|
|
1742
2024
|
mkdirSync(skillsDir, { recursive: true });
|
|
1743
2025
|
} catch {
|
|
1744
2026
|
}
|
|
1745
|
-
writeFileSync(
|
|
2027
|
+
writeFileSync(join2(skillsDir, `${skillId}.md`), content, "utf-8");
|
|
1746
2028
|
const l3 = runtime.L3_skills || (runtime.L3_skills = {});
|
|
1747
2029
|
const cat = l3[category] || (l3[category] = { category_enabled: true, category_desc: "", skills: [] });
|
|
1748
2030
|
(cat.skills || (cat.skills = [])).push({ id: skillId, keywords, enabled: true, builtin: false });
|
|
@@ -1757,7 +2039,7 @@ function deleteCustomSkill(skillId, category) {
|
|
|
1757
2039
|
if (idx >= 0) {
|
|
1758
2040
|
if (skills[idx].builtin) return null;
|
|
1759
2041
|
skills.splice(idx, 1);
|
|
1760
|
-
const skillPath =
|
|
2042
|
+
const skillPath = join2(RUNTIME_CONFIG_DIR, "agent_moss_skills", `${skillId}.md`);
|
|
1761
2043
|
try {
|
|
1762
2044
|
if (existsSync3(skillPath)) unlinkSync(skillPath);
|
|
1763
2045
|
} catch {
|
|
@@ -1873,18 +2155,19 @@ function updateAgentCategoryEnabled(agentId, category, enabled) {
|
|
|
1873
2155
|
saveRuntimeConfig(runtime);
|
|
1874
2156
|
return runtime;
|
|
1875
2157
|
}
|
|
1876
|
-
var __dirname3, RUNTIME_CONFIG_DIR, RUNTIME_CONFIG_PATH,
|
|
2158
|
+
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;
|
|
1877
2159
|
var init_runtime_config = __esm({
|
|
1878
2160
|
"src/infra/runtime-config.ts"() {
|
|
1879
2161
|
"use strict";
|
|
1880
2162
|
init_esm_shims();
|
|
2163
|
+
init_paths();
|
|
1881
2164
|
init_heuristic();
|
|
1882
2165
|
init_logic_rules();
|
|
1883
2166
|
init_skill_loader();
|
|
1884
|
-
__dirname3 = dirname2(
|
|
1885
|
-
RUNTIME_CONFIG_DIR = join(homedir(), ".config", "agentmoss");
|
|
1886
|
-
RUNTIME_CONFIG_PATH = join(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
|
|
2167
|
+
__dirname3 = dirname2(fileURLToPath4(import.meta.url));
|
|
1887
2168
|
ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
|
|
2169
|
+
RUNTIME_CONFIG_DIR = runtimeDir();
|
|
2170
|
+
RUNTIME_CONFIG_PATH = join2(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
|
|
1888
2171
|
LAYER_DISABLE_ENV = {
|
|
1889
2172
|
L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
|
|
1890
2173
|
L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
|
|
@@ -1895,16 +2178,16 @@ var init_runtime_config = __esm({
|
|
|
1895
2178
|
deny_read: "\u4EC5\u62E6\u622A\u8BFB\u53D6",
|
|
1896
2179
|
deny_both: "\u8BFB\u5199\u5747\u62E6\u622A"
|
|
1897
2180
|
};
|
|
1898
|
-
_AGENTS_DIR_PROD =
|
|
1899
|
-
_AGENTS_DIR_DEV =
|
|
2181
|
+
_AGENTS_DIR_PROD = join2(__dirname3, "agents");
|
|
2182
|
+
_AGENTS_DIR_DEV = join2(__dirname3, "..", "..", "..", "agent_moss", "agents");
|
|
1900
2183
|
SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
|
|
1901
2184
|
}
|
|
1902
2185
|
});
|
|
1903
2186
|
|
|
1904
2187
|
// src/agents/index.ts
|
|
1905
|
-
import { readFileSync as
|
|
2188
|
+
import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
|
|
1906
2189
|
import { resolve as resolve2, dirname as dirname3 } from "path";
|
|
1907
|
-
import { fileURLToPath as
|
|
2190
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
1908
2191
|
function resolveAgentsDir() {
|
|
1909
2192
|
if (existsSync4(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
|
|
1910
2193
|
if (existsSync4(AGENTS_DIR_DEV_FALLBACK)) return AGENTS_DIR_DEV_FALLBACK;
|
|
@@ -1929,7 +2212,7 @@ function getAgentExtensions(agentId) {
|
|
|
1929
2212
|
return null;
|
|
1930
2213
|
}
|
|
1931
2214
|
try {
|
|
1932
|
-
const raw =
|
|
2215
|
+
const raw = readFileSync4(rulesFile, "utf-8");
|
|
1933
2216
|
ext = JSON.parse(raw);
|
|
1934
2217
|
if (!ext.agent_id) {
|
|
1935
2218
|
console.warn(`[AgentMoss agents] rules.json \u7F3A\u5C11 agent_id \u5B57\u6BB5: ${rulesFile}`);
|
|
@@ -1953,7 +2236,7 @@ var init_agents = __esm({
|
|
|
1953
2236
|
"src/agents/index.ts"() {
|
|
1954
2237
|
"use strict";
|
|
1955
2238
|
init_esm_shims();
|
|
1956
|
-
__filename3 =
|
|
2239
|
+
__filename3 = fileURLToPath5(import.meta.url);
|
|
1957
2240
|
__dirname4 = dirname3(__filename3);
|
|
1958
2241
|
AGENTS_DIR_PROD = resolve2(__dirname4, "agents");
|
|
1959
2242
|
AGENTS_DIR_DEV_FALLBACK = resolve2(__dirname4, "../../../agent_moss/agents");
|
|
@@ -1973,19 +2256,25 @@ function runHeuristicCheck(input) {
|
|
|
1973
2256
|
const re = new RegExp(rule.pattern);
|
|
1974
2257
|
if (re.test(command)) {
|
|
1975
2258
|
if (rule.action === "Deny") {
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
2259
|
+
return {
|
|
2260
|
+
allowed: false,
|
|
2261
|
+
reason: `\u547D\u4E2D\u7528\u6237\u654F\u611F\u52A8\u4F5C\u89C4\u5219: ${rule.description || rule.pattern}`,
|
|
2262
|
+
riskLevel: rule.severity,
|
|
2263
|
+
riskType: "sensitive_action",
|
|
2264
|
+
confidence: 90,
|
|
2265
|
+
source: "heuristic",
|
|
2266
|
+
violatedLayers: ["1.1"]
|
|
2267
|
+
};
|
|
1981
2268
|
}
|
|
1982
2269
|
}
|
|
1983
2270
|
} catch {
|
|
1984
2271
|
}
|
|
1985
2272
|
}
|
|
2273
|
+
const scanDetail = stripLiteralTextSegments(input.aNext.actionDetail);
|
|
2274
|
+
const scanCommand = `${input.aNext.actionType}: ${scanDetail}`;
|
|
1986
2275
|
let topRiskType = "";
|
|
1987
2276
|
for (const rule of DANGEROUS_PATTERNS) {
|
|
1988
|
-
if (rule.pattern.test(
|
|
2277
|
+
if (rule.pattern.test(scanCommand)) {
|
|
1989
2278
|
matched.push(rule.description);
|
|
1990
2279
|
if (severityRank(rule.severity) > severityRank(highestSeverity)) {
|
|
1991
2280
|
highestSeverity = rule.severity;
|
|
@@ -1999,7 +2288,7 @@ function runHeuristicCheck(input) {
|
|
|
1999
2288
|
for (const rule of agentExt.extra_command_patterns) {
|
|
2000
2289
|
try {
|
|
2001
2290
|
const re = new RegExp(rule.pattern, "i");
|
|
2002
|
-
if (re.test(
|
|
2291
|
+
if (re.test(scanCommand)) {
|
|
2003
2292
|
matched.push(`agent \u4E13\u5C5E\u89C4\u5219: ${rule.reason}`);
|
|
2004
2293
|
if (severityRank(rule.risk_level) > severityRank(highestSeverity)) {
|
|
2005
2294
|
highestSeverity = rule.risk_level;
|
|
@@ -2023,14 +2312,12 @@ function runHeuristicCheck(input) {
|
|
|
2023
2312
|
const actionTypeL = input.aNext.actionType.toLowerCase();
|
|
2024
2313
|
const detailL = input.aNext.actionDetail.toLowerCase();
|
|
2025
2314
|
const isExcludedType = SENSITIVE_EXCLUDED_PREFIXES.some((p) => actionTypeL.startsWith(p));
|
|
2315
|
+
const dangerousResult = checkDangerousPatterns(actionTypeL, detailL);
|
|
2316
|
+
if (dangerousResult) return dangerousResult;
|
|
2026
2317
|
if (!isExcludedType) {
|
|
2027
2318
|
const pathResult = checkSensitivePath(detailL, input.cwd || "/", agentExt);
|
|
2028
2319
|
if (pathResult) return pathResult;
|
|
2029
2320
|
}
|
|
2030
|
-
const dangerousResult = checkDangerousPatterns(actionTypeL, detailL);
|
|
2031
|
-
if (dangerousResult) return dangerousResult;
|
|
2032
|
-
const lateralResult = checkLateralMovement(detailL);
|
|
2033
|
-
if (lateralResult) return lateralResult;
|
|
2034
2321
|
if (matched.length > 0) {
|
|
2035
2322
|
return {
|
|
2036
2323
|
allowed: false,
|
|
@@ -2043,6 +2330,8 @@ function runHeuristicCheck(input) {
|
|
|
2043
2330
|
// 对齐 py coordinator.py:165(L1.1 = 命中高危命令模式/注入关键词)
|
|
2044
2331
|
};
|
|
2045
2332
|
}
|
|
2333
|
+
const lateralResult = checkLateralMovement(detailL);
|
|
2334
|
+
if (lateralResult) return lateralResult;
|
|
2046
2335
|
return null;
|
|
2047
2336
|
}
|
|
2048
2337
|
function checkInjectionKeywords(text, extraKeywords) {
|
|
@@ -2065,6 +2354,68 @@ function isInlineScriptCommand(actionDetail) {
|
|
|
2065
2354
|
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;
|
|
2066
2355
|
return re.test(actionDetail);
|
|
2067
2356
|
}
|
|
2357
|
+
function stripLiteralTextSegments(actionDetail) {
|
|
2358
|
+
const writeScriptExec = />\s*[/~]\S*\.(sh|py|pl|rb)\b/.test(actionDetail) && /(?:&&|\|\||;|\n|\|)\s*(?:bash|sh|zsh|dash|python|perl|ruby|node)/.test(actionDetail);
|
|
2359
|
+
const pipeToInterpreter = /\|\s*(?:bash|sh|zsh|dash|python|perl|ruby|node)\b/.test(actionDetail);
|
|
2360
|
+
if (writeScriptExec || pipeToInterpreter) {
|
|
2361
|
+
return actionDetail;
|
|
2362
|
+
}
|
|
2363
|
+
const resultParts = [];
|
|
2364
|
+
for (const seg of splitCommandSegments(actionDetail)) {
|
|
2365
|
+
const fw = (seg.trim().split(/\s+/)[0] || "").toLowerCase();
|
|
2366
|
+
if (fw === "echo" || fw === "printf") {
|
|
2367
|
+
const segUnquotedRedirect = seg.replace(
|
|
2368
|
+
/>{1,2}\s*(['"])([~./][^'"]+)\1/g,
|
|
2369
|
+
(_m, _q, p) => ">" + p
|
|
2370
|
+
);
|
|
2371
|
+
resultParts.push(segUnquotedRedirect.replace(/'[^']*'|"[^"]*"/g, ""));
|
|
2372
|
+
} else {
|
|
2373
|
+
resultParts.push(seg);
|
|
2374
|
+
}
|
|
2375
|
+
}
|
|
2376
|
+
return resultParts.join("");
|
|
2377
|
+
}
|
|
2378
|
+
function splitCommandSegments(actionDetail) {
|
|
2379
|
+
const segments = [];
|
|
2380
|
+
let cur = "";
|
|
2381
|
+
let quote = null;
|
|
2382
|
+
for (let i = 0; i < actionDetail.length; i++) {
|
|
2383
|
+
const ch = actionDetail[i];
|
|
2384
|
+
if (quote) {
|
|
2385
|
+
cur += ch;
|
|
2386
|
+
if (ch === quote) quote = null;
|
|
2387
|
+
continue;
|
|
2388
|
+
}
|
|
2389
|
+
if (ch === "'" || ch === '"') {
|
|
2390
|
+
quote = ch;
|
|
2391
|
+
cur += ch;
|
|
2392
|
+
continue;
|
|
2393
|
+
}
|
|
2394
|
+
if (ch === ";" || ch === "\n") {
|
|
2395
|
+
segments.push(cur);
|
|
2396
|
+
segments.push(ch);
|
|
2397
|
+
cur = "";
|
|
2398
|
+
continue;
|
|
2399
|
+
}
|
|
2400
|
+
if (ch === "&" && actionDetail[i + 1] === "&") {
|
|
2401
|
+
segments.push(cur);
|
|
2402
|
+
segments.push("&&");
|
|
2403
|
+
cur = "";
|
|
2404
|
+
i++;
|
|
2405
|
+
continue;
|
|
2406
|
+
}
|
|
2407
|
+
if (ch === "|" && actionDetail[i + 1] === "|") {
|
|
2408
|
+
segments.push(cur);
|
|
2409
|
+
segments.push("||");
|
|
2410
|
+
cur = "";
|
|
2411
|
+
i++;
|
|
2412
|
+
continue;
|
|
2413
|
+
}
|
|
2414
|
+
cur += ch;
|
|
2415
|
+
}
|
|
2416
|
+
if (cur) segments.push(cur);
|
|
2417
|
+
return segments;
|
|
2418
|
+
}
|
|
2068
2419
|
function severityRank(s) {
|
|
2069
2420
|
const ranks = { low: 1, medium: 2, high: 3, critical: 4 };
|
|
2070
2421
|
return ranks[s] || 0;
|
|
@@ -2072,13 +2423,18 @@ function severityRank(s) {
|
|
|
2072
2423
|
function checkSensitivePath(command, cwd, agentExt) {
|
|
2073
2424
|
const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
|
|
2074
2425
|
const isWriteOpFlag = isWriteOperation("", command);
|
|
2426
|
+
const pathScanText = stripLiteralTextSegments(command);
|
|
2075
2427
|
const pathMatches = (text, path3) => {
|
|
2076
|
-
const
|
|
2428
|
+
const parts = path3.split(/(\(\?!.*?\))/);
|
|
2429
|
+
const escaped = parts.map((part) => {
|
|
2430
|
+
if (part.startsWith("(?!")) return part;
|
|
2431
|
+
return part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2432
|
+
}).join("");
|
|
2077
2433
|
const re = path3.startsWith(".") || path3.startsWith("/") ? new RegExp(`(?:^|[\\s/\\\\'"])${escaped}\\b`, "i") : new RegExp(`\\b${escaped}\\b`, "i");
|
|
2078
2434
|
return re.test(text);
|
|
2079
2435
|
};
|
|
2080
2436
|
for (const sp of ROOT_SENSITIVE_PATHS) {
|
|
2081
|
-
if (pathMatches(
|
|
2437
|
+
if (pathMatches(pathScanText, sp.path.toLowerCase())) {
|
|
2082
2438
|
if (sp.path === "/dev/random") {
|
|
2083
2439
|
if (isWriteOpFlag) {
|
|
2084
2440
|
continue;
|
|
@@ -2112,7 +2468,7 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2112
2468
|
const expanded = `${expandedCwd}/${sp.path}`;
|
|
2113
2469
|
const pathRe = sp.path.endsWith(".pub") ? new RegExp(sp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i") : new RegExp(sp.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "(?!\\.pub)", "i");
|
|
2114
2470
|
const expandedRe = sp.path.endsWith(".pub") ? new RegExp(expanded.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i") : new RegExp(expanded.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "(?!\\.pub)", "i");
|
|
2115
|
-
if (pathRe.test(
|
|
2471
|
+
if (pathRe.test(pathScanText) || expandedRe.test(pathScanText)) {
|
|
2116
2472
|
if (!isWriteOpFlag && !sp.credential) {
|
|
2117
2473
|
continue;
|
|
2118
2474
|
}
|
|
@@ -2183,8 +2539,10 @@ function checkSensitivePath(command, cwd, agentExt) {
|
|
|
2183
2539
|
}
|
|
2184
2540
|
function checkDangerousPatterns(actionType, actionDetail) {
|
|
2185
2541
|
if (actionType === "file_write" || actionType === "file_edit") return null;
|
|
2186
|
-
const TEMP_DIRS = ["/tmp
|
|
2187
|
-
const isTempPath = TEMP_DIRS.some(
|
|
2542
|
+
const TEMP_DIRS = ["/tmp", "/var/tmp", "/run", "/dev/shm"];
|
|
2543
|
+
const isTempPath = TEMP_DIRS.some(
|
|
2544
|
+
(td) => new RegExp(`(?:^|[\\s/])${td.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:[\\s/]|$)`).test(actionDetail)
|
|
2545
|
+
);
|
|
2188
2546
|
if (!isTempPath && (actionDetail.includes("*") || actionDetail.includes("?")) && ["rm ", "rm ", "del ", "del ", "delete", "remove ", "remove ", "\u5220\u9664", "\u79FB\u9664"].some((kw) => actionDetail.includes(kw))) {
|
|
2189
2547
|
return {
|
|
2190
2548
|
allowed: false,
|
|
@@ -2196,7 +2554,22 @@ function checkDangerousPatterns(actionType, actionDetail) {
|
|
|
2196
2554
|
violatedLayers: ["1.1"]
|
|
2197
2555
|
};
|
|
2198
2556
|
}
|
|
2199
|
-
|
|
2557
|
+
const findWriteMatch = actionDetail.match(
|
|
2558
|
+
/\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
|
|
2559
|
+
);
|
|
2560
|
+
if (findWriteMatch) {
|
|
2561
|
+
const cmdHit = findWriteMatch[1] || "xargs";
|
|
2562
|
+
return {
|
|
2563
|
+
allowed: false,
|
|
2564
|
+
reason: `\u68C0\u6D4B\u5230 find \u6279\u91CF\u6267\u884C\u5199\u64CD\u4F5C (${cmdHit})\uFF0C\u53EF\u80FD\u6279\u91CF\u4FEE\u6539/\u5220\u9664\u6587\u4EF6`,
|
|
2565
|
+
riskLevel: "high",
|
|
2566
|
+
riskType: "script_execution",
|
|
2567
|
+
confidence: 90,
|
|
2568
|
+
source: "heuristic",
|
|
2569
|
+
violatedLayers: ["1.1"]
|
|
2570
|
+
};
|
|
2571
|
+
}
|
|
2572
|
+
if (/>\s*\/etc\//.test(actionDetail) || />\s*\/boot\//.test(actionDetail)) {
|
|
2200
2573
|
return {
|
|
2201
2574
|
allowed: false,
|
|
2202
2575
|
reason: "\u68C0\u6D4B\u5230\u91CD\u5B9A\u5411\u8986\u76D6\u5199\u5165\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55",
|
|
@@ -2207,17 +2580,95 @@ function checkDangerousPatterns(actionType, actionDetail) {
|
|
|
2207
2580
|
violatedLayers: ["1.1"]
|
|
2208
2581
|
};
|
|
2209
2582
|
}
|
|
2583
|
+
if (/>\s*\/dev\/(sd|hd|nvme|vd|loop|mem|kmsg|port|zero|random|urandom|full|core)/i.test(actionDetail)) {
|
|
2584
|
+
return {
|
|
2585
|
+
allowed: false,
|
|
2586
|
+
reason: "\u68C0\u6D4B\u5230\u91CD\u5B9A\u5411\u5199\u5165\u8BBE\u5907\u6587\u4EF6",
|
|
2587
|
+
riskLevel: "high",
|
|
2588
|
+
riskType: "file_access",
|
|
2589
|
+
confidence: 90,
|
|
2590
|
+
source: "heuristic",
|
|
2591
|
+
violatedLayers: ["1.1"]
|
|
2592
|
+
};
|
|
2593
|
+
}
|
|
2210
2594
|
return null;
|
|
2211
2595
|
}
|
|
2212
2596
|
function extractRemoteHost(command) {
|
|
2213
|
-
const
|
|
2214
|
-
|
|
2597
|
+
const tokens = command.split(/\s+/).map((t) => t.replace(/^["']|["']$/g, ""));
|
|
2598
|
+
for (const tok of tokens) {
|
|
2599
|
+
const am = tok.match(/^([\w.-]+)@([\w.-]+)(:.*)?$/);
|
|
2600
|
+
if (am && am[2] && !isLocalHost(am[2])) {
|
|
2601
|
+
return `${am[1]}@${am[2]}`;
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
for (const tok of tokens) {
|
|
2605
|
+
const hm = tok.match(/^([\w.-]+):\/(.*)?$/);
|
|
2606
|
+
if (hm && hm[1] && !isLocalHost(hm[1])) {
|
|
2607
|
+
return hm[1];
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
const skipValueOpts = /* @__PURE__ */ new Set([
|
|
2611
|
+
"-p",
|
|
2612
|
+
"-l",
|
|
2613
|
+
"-i",
|
|
2614
|
+
"-o",
|
|
2615
|
+
"-b",
|
|
2616
|
+
"-c",
|
|
2617
|
+
"-m",
|
|
2618
|
+
"-F",
|
|
2619
|
+
"-e",
|
|
2620
|
+
"-S",
|
|
2621
|
+
"-W",
|
|
2622
|
+
"-J",
|
|
2623
|
+
"-I",
|
|
2624
|
+
"-T",
|
|
2625
|
+
"-B",
|
|
2626
|
+
"-L",
|
|
2627
|
+
"-R",
|
|
2628
|
+
"-D",
|
|
2629
|
+
"-E",
|
|
2630
|
+
"-M",
|
|
2631
|
+
"-O",
|
|
2632
|
+
"-P",
|
|
2633
|
+
"-q"
|
|
2634
|
+
]);
|
|
2635
|
+
let foundCmd = false;
|
|
2636
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
2637
|
+
const t = tokens[i];
|
|
2638
|
+
if (t === "ssh" || t === "scp" || t === "rsync") {
|
|
2639
|
+
foundCmd = true;
|
|
2640
|
+
continue;
|
|
2641
|
+
}
|
|
2642
|
+
if (!foundCmd) continue;
|
|
2643
|
+
if (t.startsWith("-")) {
|
|
2644
|
+
if (skipValueOpts.has(t) && i + 1 < tokens.length) {
|
|
2645
|
+
i++;
|
|
2646
|
+
}
|
|
2647
|
+
continue;
|
|
2648
|
+
}
|
|
2649
|
+
if (t.startsWith("/")) continue;
|
|
2650
|
+
return t;
|
|
2651
|
+
}
|
|
2652
|
+
return null;
|
|
2215
2653
|
}
|
|
2216
2654
|
function isLocalHost(host) {
|
|
2217
2655
|
const localNames = ["localhost", "127.0.0.1", "::1", process.env.HOSTNAME || "", "0.0.0.0"];
|
|
2218
2656
|
return localNames.includes(host.replace(/.*@/, ""));
|
|
2219
2657
|
}
|
|
2220
2658
|
function checkLateralMovement(actionDetail) {
|
|
2659
|
+
if (/\b(?:python|python3|perl|ruby|node|php|lua)\b\s+.*-[cer][a-zA-Z]*\s/.test(actionDetail)) return null;
|
|
2660
|
+
const segments = actionDetail.split(/&&|\|\||;|\n/).map((s) => s.trim()).filter(Boolean);
|
|
2661
|
+
let hasRealCall = false;
|
|
2662
|
+
for (const seg of segments) {
|
|
2663
|
+
const parts = seg.split(/\s+/);
|
|
2664
|
+
const fw = (parts[0] || "").toLowerCase();
|
|
2665
|
+
if (fw === "echo" || fw === "printf") continue;
|
|
2666
|
+
if (/(?:ssh|scp|rsync)\s+/.test(seg)) {
|
|
2667
|
+
hasRealCall = true;
|
|
2668
|
+
break;
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
if (!hasRealCall) return null;
|
|
2221
2672
|
if (/(?:ssh|scp|rsync)\s+/.test(actionDetail)) {
|
|
2222
2673
|
const targetHost = extractRemoteHost(actionDetail);
|
|
2223
2674
|
if (targetHost && !isLocalHost(targetHost)) {
|
|
@@ -2317,10 +2768,14 @@ var init_heuristic = __esm({
|
|
|
2317
2768
|
{ 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" },
|
|
2318
2769
|
{ pattern: /:\(\)\s*\{.*:\|:&.*\};:/i, severity: "critical", description: "Fork Bomb", riskType: "resource_exhaustion" },
|
|
2319
2770
|
// --- Critical: remote code execution ---
|
|
2320
|
-
|
|
2771
|
+
// curl|bash 优先匹配 user_rules(high/sensitive_action),见下方 UserRuleMatcher 段;
|
|
2772
|
+
// 非 bash 的 curl|sh/zsh/dash 走 CommandPatternScanner critical。两条分流由
|
|
2773
|
+
// runHeuristicCheck 的规则顺序保证(bash 在前 high,其余 fall-through 到 critical)。
|
|
2774
|
+
{ pattern: /curl\s+.*\|\s*bash/i, severity: "high", description: "curl pipe bash \u6267\u884C (user_rules)", riskType: "sensitive_action" },
|
|
2775
|
+
{ pattern: /curl\s+.*\|\s*(sh|zsh|dash)/i, severity: "critical", description: "curl pipe shell \u6267\u884C", riskType: "script_execution" },
|
|
2321
2776
|
{ pattern: /wget\s+.*\|\s*(bash|sh)/i, severity: "critical", description: "wget pipe shell \u6267\u884C", riskType: "script_execution" },
|
|
2322
2777
|
{ pattern: /eval\s+\$\(curl|eval\s+\$\(wget/i, severity: "critical", description: "eval \u8FDC\u7A0B\u4EE3\u7801\u6267\u884C", riskType: "script_execution" },
|
|
2323
|
-
{ pattern: /base64\s+-d\s+.*\|\s*bash/i, severity: "
|
|
2778
|
+
{ pattern: /base64\s+-d\s+.*\|\s*bash/i, severity: "high", description: "base64 \u89E3\u7801\u6267\u884C (user_rules)", riskType: "sensitive_action" },
|
|
2324
2779
|
// --- Critical: reverse shell ---
|
|
2325
2780
|
{ pattern: /\/dev\/tcp\/|bash\s+-i\s+>&/i, severity: "critical", description: "\u53CD\u5F39 Shell", riskType: "script_execution" },
|
|
2326
2781
|
{ pattern: /nc\s+-[el]\s+.*\d{1,5}/i, severity: "high", description: "Netcat \u53CD\u5F39\u8FDE\u63A5", riskType: "lateral_movement" },
|
|
@@ -2331,13 +2786,25 @@ var init_heuristic = __esm({
|
|
|
2331
2786
|
{ pattern: /\.ssh\/id_rsa(?!\.pub)/i, severity: "critical", description: "\u8BBF\u95EE SSH \u79C1\u94A5\u6587\u4EF6", riskType: "file_access" },
|
|
2332
2787
|
// --- Critical: privilege escalation / container escape ---
|
|
2333
2788
|
{ 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" },
|
|
2334
|
-
// ---
|
|
2335
|
-
|
|
2789
|
+
// --- Critical: dangerous permissions / ownership (提权 / 权限敞口) ---
|
|
2790
|
+
// 修正「只拦 777、xx7 漏放」的缺口:
|
|
2791
|
+
// 1) 八进制末位 7:other 位含写+执行敞口(chmod 777/647/007/4777 等);644/755/1700 不命中
|
|
2792
|
+
// 2) 八进制 setuid/setgid 位:首位 4(setuid)/2(setgid) 的 4 位八进制(chmod 4755/2755)
|
|
2793
|
+
// 3) 符号 setuid/setgid:chmod u+s / g+s / a+s
|
|
2794
|
+
// 普通权限修改(chmod 644)不在此列——L2 read_before_write 已豁免 chmod/chown(不写内容),
|
|
2795
|
+
// 危险形态在此 L1 专门拦,以「提权」正确理由拦截,不再靠 RBW 误打误撞。
|
|
2796
|
+
{ 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" },
|
|
2797
|
+
{ 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" },
|
|
2798
|
+
{ 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" },
|
|
2336
2799
|
{ 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" },
|
|
2337
2800
|
// sudo 提权 — 只拦截 sudo 后跟的危险命令,不一刀切拦截所有 sudo
|
|
2338
2801
|
{ 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" },
|
|
2339
2802
|
// --- High: system config overwrite ---
|
|
2803
|
+
// > /etc/ 重定向覆盖:对齐 Python CommandPatternScanner `>\s*/etc/`(不兼容引号路径),
|
|
2804
|
+
// 引号包裹的重定向目标(cat > '/etc/ssh/sshd_config')走 checkSensitivePath 敏感路径 high。
|
|
2340
2805
|
{ pattern: />\s*\/etc\//i, severity: "critical", description: "\u8986\u76D6\u5199\u5165\u7CFB\u7EDF\u914D\u7F6E\u76EE\u5F55", riskType: "file_access" },
|
|
2806
|
+
{ pattern: />\s*\/boot\//i, severity: "critical", description: "\u8986\u76D6\u5199\u5165\u542F\u52A8\u5F15\u5BFC\u76EE\u5F55", riskType: "file_access" },
|
|
2807
|
+
// 注:/proc/ 重定向覆盖走 checkSensitivePath 的 high(内核参数 /proc/sys/),此处不拦 /proc/ 避免遮蔽(对齐 Python)。
|
|
2341
2808
|
// --- High: download script + execute risk ---
|
|
2342
2809
|
{ 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" },
|
|
2343
2810
|
{ pattern: /(?:curl|wget)\s+.*\s+&&?\s*(?:chmod\s+\+x|bash|sh)/i, severity: "high", description: "\u4E0B\u8F7D\u540E\u6267\u884C", riskType: "script_execution" },
|
|
@@ -2353,7 +2820,11 @@ var init_heuristic = __esm({
|
|
|
2353
2820
|
// --- Medium: credential export ---
|
|
2354
2821
|
{ pattern: /export\s+.*(?:TOKEN|KEY|SECRET|PASSWORD)/i, severity: "medium", description: "\u5BFC\u51FA\u654F\u611F\u51ED\u8BC1", riskType: "data_exfiltration" },
|
|
2355
2822
|
// --- High: shell rc file append (persistence) ---
|
|
2823
|
+
// 两条规则:第一条匹配 ~ 与 /home/<user>/ 形态;第二条按文件名兜底,
|
|
2824
|
+
// 覆盖非标准 home 路径(如鸿蒙 /storage/Users/currentUser/.bashrc、
|
|
2825
|
+
// /root/.bashrc),避免按 home 前缀枚举导致漏检持久化后门。
|
|
2356
2826
|
{ 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" },
|
|
2827
|
+
{ 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" },
|
|
2357
2828
|
// --- High: device file write (只拦截重定向写入设备文件,不拦截读取) ---
|
|
2358
2829
|
{ 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" },
|
|
2359
2830
|
// --- High: non-interactive password modification ---
|
|
@@ -2381,9 +2852,9 @@ var init_heuristic = __esm({
|
|
|
2381
2852
|
});
|
|
2382
2853
|
|
|
2383
2854
|
// src/engine/template-loader.ts
|
|
2384
|
-
import { readFileSync as
|
|
2855
|
+
import { readFileSync as readFileSync5, existsSync as existsSync5 } from "fs";
|
|
2385
2856
|
import { resolve as resolve3, dirname as dirname4 } from "path";
|
|
2386
|
-
import { fileURLToPath as
|
|
2857
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
2387
2858
|
function loadTemplates() {
|
|
2388
2859
|
if (cachedTemplates) return cachedTemplates;
|
|
2389
2860
|
cachedTemplates = {};
|
|
@@ -2396,7 +2867,7 @@ function loadTemplates() {
|
|
|
2396
2867
|
const filePath = resolve3(dir, file);
|
|
2397
2868
|
if (existsSync5(filePath)) {
|
|
2398
2869
|
const key = file.replace(".txt", "").replace("_template", "");
|
|
2399
|
-
cachedTemplates[key] =
|
|
2870
|
+
cachedTemplates[key] = readFileSync5(filePath, "utf-8");
|
|
2400
2871
|
}
|
|
2401
2872
|
}
|
|
2402
2873
|
console.log(`[AgentMoss] Loaded ${Object.keys(cachedTemplates).length} templates from ${dir}`);
|
|
@@ -2407,7 +2878,7 @@ var init_template_loader = __esm({
|
|
|
2407
2878
|
"src/engine/template-loader.ts"() {
|
|
2408
2879
|
"use strict";
|
|
2409
2880
|
init_esm_shims();
|
|
2410
|
-
__filename4 =
|
|
2881
|
+
__filename4 = fileURLToPath6(import.meta.url);
|
|
2411
2882
|
__dirname5 = dirname4(__filename4);
|
|
2412
2883
|
TEMPLATES_DIR = resolve3(__dirname5, "../templates");
|
|
2413
2884
|
TEMPLATES_DIR_FALLBACK = resolve3(__dirname5, "../../../agent_moss/templates");
|
|
@@ -2417,8 +2888,8 @@ var init_template_loader = __esm({
|
|
|
2417
2888
|
});
|
|
2418
2889
|
|
|
2419
2890
|
// src/engine/script-analyzer.ts
|
|
2420
|
-
import { readFileSync as
|
|
2421
|
-
import { resolve as resolve4, join as
|
|
2891
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
2892
|
+
import { resolve as resolve4, join as join3 } from "path";
|
|
2422
2893
|
function emptyResult() {
|
|
2423
2894
|
return { scriptPath: "", scriptContent: "", matchedKeywords: [], lineCount: 0, isSuspicious: false, error: "", riskLevel: "", riskReason: "" };
|
|
2424
2895
|
}
|
|
@@ -2471,8 +2942,8 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
|
|
|
2471
2942
|
}
|
|
2472
2943
|
function readScriptContent(scriptPath, maxLines = 500) {
|
|
2473
2944
|
try {
|
|
2474
|
-
const expandedPath = scriptPath.startsWith("~") ?
|
|
2475
|
-
const raw =
|
|
2945
|
+
const expandedPath = scriptPath.startsWith("~") ? join3(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve4(scriptPath);
|
|
2946
|
+
const raw = readFileSync6(expandedPath, "utf-8");
|
|
2476
2947
|
const lines = raw.split("\n").slice(0, maxLines);
|
|
2477
2948
|
return { content: lines.join("\n"), lineCount: lines.length, error: "" };
|
|
2478
2949
|
} catch (e) {
|
|
@@ -2610,8 +3081,8 @@ var init_script_analyzer = __esm({
|
|
|
2610
3081
|
});
|
|
2611
3082
|
|
|
2612
3083
|
// src/engine/script-content-analyzer.ts
|
|
2613
|
-
import { readFileSync as
|
|
2614
|
-
import { resolve as resolve5, join as
|
|
3084
|
+
import { readFileSync as readFileSync7, statSync, existsSync as existsSync6 } from "fs";
|
|
3085
|
+
import { resolve as resolve5, join as join4, isAbsolute, dirname as dirname5 } from "path";
|
|
2615
3086
|
import { homedir as homedir2 } from "os";
|
|
2616
3087
|
function emptyChain() {
|
|
2617
3088
|
return {
|
|
@@ -2770,7 +3241,7 @@ function extractInlineScriptPath(actionDetail) {
|
|
|
2770
3241
|
function resolvePath(p) {
|
|
2771
3242
|
let expanded = p;
|
|
2772
3243
|
if (expanded.startsWith("~/") || expanded === "~") {
|
|
2773
|
-
expanded =
|
|
3244
|
+
expanded = join4(homedir2(), expanded.slice(1));
|
|
2774
3245
|
}
|
|
2775
3246
|
return resolve5(expanded);
|
|
2776
3247
|
}
|
|
@@ -2783,7 +3254,7 @@ function readScriptContent2(scriptPath, maxFileSize = 2 * 1024 * 1024) {
|
|
|
2783
3254
|
if (stat.size > maxFileSize) {
|
|
2784
3255
|
return { content: "", lineCount: 0, fileSize: stat.size, error: `\u811A\u672C\u6587\u4EF6\u8FC7\u5927 (${stat.size} bytes)\uFF0C\u8DF3\u8FC7\u8BFB\u53D6` };
|
|
2785
3256
|
}
|
|
2786
|
-
const content =
|
|
3257
|
+
const content = readFileSync7(path3, "utf-8");
|
|
2787
3258
|
const lineCount = content ? content.split("\n").length : 0;
|
|
2788
3259
|
return { content, lineCount, fileSize: stat.size, error: "" };
|
|
2789
3260
|
} catch (e) {
|
|
@@ -2800,7 +3271,7 @@ function extractChildScriptPaths(content, baseDir) {
|
|
|
2800
3271
|
const pathStr = m[1];
|
|
2801
3272
|
if (pathStr.startsWith("-") || pathStr.startsWith("$")) continue;
|
|
2802
3273
|
if (pathStr.includes("/dev/") || pathStr.startsWith("|") || pathStr.startsWith(";")) continue;
|
|
2803
|
-
const full = isAbsolute(pathStr) ? pathStr :
|
|
3274
|
+
const full = isAbsolute(pathStr) ? pathStr : join4(baseDir, pathStr);
|
|
2804
3275
|
const resolved = resolvePath(full);
|
|
2805
3276
|
if (existsSync6(resolved)) {
|
|
2806
3277
|
try {
|
|
@@ -3153,17 +3624,16 @@ __export(token_stats_exports, {
|
|
|
3153
3624
|
recordTokenUsage: () => recordTokenUsage,
|
|
3154
3625
|
resetTokenStats: () => resetTokenStats
|
|
3155
3626
|
});
|
|
3156
|
-
import { readFileSync as
|
|
3157
|
-
import { join as join4 } from "path";
|
|
3158
|
-
import { homedir as homedir3 } from "os";
|
|
3627
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync2, existsSync as existsSync7, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
3159
3628
|
function getStatsPath() {
|
|
3160
3629
|
const envPath = process.env[ENV_STATS_PATH];
|
|
3161
|
-
|
|
3630
|
+
if (envPath) return envPath;
|
|
3631
|
+
return getTokenStatsPath();
|
|
3162
3632
|
}
|
|
3163
3633
|
function loadAllRecords(statsPath) {
|
|
3164
3634
|
if (!existsSync7(statsPath)) return [];
|
|
3165
3635
|
try {
|
|
3166
|
-
const data = JSON.parse(
|
|
3636
|
+
const data = JSON.parse(readFileSync8(statsPath, "utf-8"));
|
|
3167
3637
|
if (Array.isArray(data)) return data;
|
|
3168
3638
|
if (data && Array.isArray(data.records)) return data.records;
|
|
3169
3639
|
return [];
|
|
@@ -3303,13 +3773,12 @@ function resetTokenStats() {
|
|
|
3303
3773
|
}
|
|
3304
3774
|
}
|
|
3305
3775
|
}
|
|
3306
|
-
var
|
|
3776
|
+
var ENV_STATS_PATH, MAX_RECORDS;
|
|
3307
3777
|
var init_token_stats = __esm({
|
|
3308
3778
|
"src/engine/token-stats.ts"() {
|
|
3309
3779
|
"use strict";
|
|
3310
3780
|
init_esm_shims();
|
|
3311
|
-
|
|
3312
|
-
STATS_FILE = join4(STATS_DIR, "agent_moss_token_stats.json");
|
|
3781
|
+
init_paths();
|
|
3313
3782
|
ENV_STATS_PATH = "AGENT_MOSS_TOKEN_STATS_PATH";
|
|
3314
3783
|
MAX_RECORDS = 1e4;
|
|
3315
3784
|
}
|
|
@@ -3487,11 +3956,13 @@ async function analyzeWithLLM(prompt, config) {
|
|
|
3487
3956
|
} catch {
|
|
3488
3957
|
}
|
|
3489
3958
|
clearTimeout(totalTimeoutTimer);
|
|
3959
|
+
const riskLevel = parsed.riskLevel ?? parsed.risk_level;
|
|
3960
|
+
const riskType = parsed.riskType ?? parsed.risk_type;
|
|
3490
3961
|
return {
|
|
3491
3962
|
allowed: parsed.allowed !== false,
|
|
3492
3963
|
reason: parsed.reason || "LLM \u5206\u6790\u5B8C\u6210",
|
|
3493
|
-
riskLevel: ["low", "medium", "high", "critical"].includes(
|
|
3494
|
-
riskType:
|
|
3964
|
+
riskLevel: ["low", "medium", "high", "critical"].includes(riskLevel) ? riskLevel : "low",
|
|
3965
|
+
riskType: riskType || "",
|
|
3495
3966
|
confidence: typeof parsed.confidence === "number" ? Math.min(100, Math.max(0, parsed.confidence)) : 70,
|
|
3496
3967
|
source: "llm",
|
|
3497
3968
|
violatedLayers: ["3"]
|
|
@@ -3560,7 +4031,10 @@ async function runAnalysis(input) {
|
|
|
3560
4031
|
if (heuristicResult && !heuristicResult.allowed) {
|
|
3561
4032
|
const skipInlineFileAccess = (isInlineScriptCmd || isLiteralTextCmd) && heuristicResult.riskType === "file_access";
|
|
3562
4033
|
if (!skipInlineFileAccess) {
|
|
3563
|
-
|
|
4034
|
+
const shouldDenyL1 = heuristicResult.riskLevel === "high" || heuristicResult.riskLevel === "critical" || heuristicResult.riskLevel === "medium" && heuristicResult.riskType === "file_access";
|
|
4035
|
+
if (shouldDenyL1) {
|
|
4036
|
+
return heuristicResult;
|
|
4037
|
+
}
|
|
3564
4038
|
}
|
|
3565
4039
|
}
|
|
3566
4040
|
let scriptChain = null;
|
|
@@ -3638,7 +4112,8 @@ async function runAnalysis(input) {
|
|
|
3638
4112
|
) && DANGEROUS_PATTERNS.some(
|
|
3639
4113
|
(p) => (p.severity === "critical" || p.severity === "high") && p.pattern.test(lowerDetail)
|
|
3640
4114
|
);
|
|
3641
|
-
|
|
4115
|
+
const hasRedirectWrite = isWriteOperation(firstCmd, lowerDetail);
|
|
4116
|
+
if (!fullCmdHit && !hasRedirectWrite) {
|
|
3642
4117
|
return {
|
|
3643
4118
|
allowed: true,
|
|
3644
4119
|
reason: `\u5B89\u5168\u7684\u53EA\u8BFB bash \u547D\u4EE4: ${firstCmd}`,
|
|
@@ -3937,7 +4412,7 @@ var require_bindings = __commonJS({
|
|
|
3937
4412
|
init_esm_shims();
|
|
3938
4413
|
var fs4 = __require("fs");
|
|
3939
4414
|
var path3 = __require("path");
|
|
3940
|
-
var
|
|
4415
|
+
var fileURLToPath9 = require_file_uri_to_path();
|
|
3941
4416
|
var join6 = path3.join;
|
|
3942
4417
|
var dirname7 = path3.dirname;
|
|
3943
4418
|
var exists = fs4.accessSync && function(path4) {
|
|
@@ -4049,7 +4524,7 @@ var require_bindings = __commonJS({
|
|
|
4049
4524
|
Error.stackTraceLimit = origSTL;
|
|
4050
4525
|
var fileSchema = "file://";
|
|
4051
4526
|
if (fileName.indexOf(fileSchema) === 0) {
|
|
4052
|
-
fileName =
|
|
4527
|
+
fileName = fileURLToPath9(fileName);
|
|
4053
4528
|
}
|
|
4054
4529
|
return fileName;
|
|
4055
4530
|
};
|
|
@@ -4895,10 +5370,10 @@ init_esm_shims();
|
|
|
4895
5370
|
init_runtime_config();
|
|
4896
5371
|
init_token_stats();
|
|
4897
5372
|
import { Hono } from "hono";
|
|
4898
|
-
import { readFileSync as
|
|
5373
|
+
import { readFileSync as readFileSync9, existsSync as existsSync9 } from "fs";
|
|
4899
5374
|
import { join as join5, dirname as dirname6 } from "path";
|
|
4900
|
-
import { fileURLToPath as
|
|
4901
|
-
var __dirname6 = dirname6(
|
|
5375
|
+
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
5376
|
+
var __dirname6 = dirname6(fileURLToPath7(import.meta.url));
|
|
4902
5377
|
var STATIC_DIR = join5(__dirname6, "static");
|
|
4903
5378
|
var CONSOLE_TOKEN_ENV = "AGENT_MOSS_CONSOLE_TOKEN";
|
|
4904
5379
|
function getConsoleToken() {
|
|
@@ -4925,7 +5400,7 @@ function createConsoleRouter() {
|
|
|
4925
5400
|
const serveIndex = (c) => {
|
|
4926
5401
|
const indexFile = join5(STATIC_DIR, "index.html");
|
|
4927
5402
|
if (existsSync9(indexFile)) {
|
|
4928
|
-
return c.html(
|
|
5403
|
+
return c.html(readFileSync9(indexFile, "utf-8"));
|
|
4929
5404
|
}
|
|
4930
5405
|
return c.html("<h1>AgentMoss Policy Console</h1><p>Frontend not built. Use /console/api/* endpoints.</p>");
|
|
4931
5406
|
};
|
|
@@ -4933,12 +5408,12 @@ function createConsoleRouter() {
|
|
|
4933
5408
|
console2.get("", serveIndex);
|
|
4934
5409
|
console2.get("/api/layers", (c) => {
|
|
4935
5410
|
const rc = loadRuntimeConfig();
|
|
4936
|
-
return c.json({ layers: rc.layers });
|
|
5411
|
+
return c.json({ layers: rc.layers, env_overrides: getEnvOverrides() });
|
|
4937
5412
|
});
|
|
4938
5413
|
console2.put("/api/layers", async (c) => {
|
|
4939
5414
|
const body = await c.req.json();
|
|
4940
5415
|
const rc = updateLayerEnabled(body.layer_key, body.enabled);
|
|
4941
|
-
return c.json({ layers: rc.layers });
|
|
5416
|
+
return c.json({ layers: rc.layers, env_overrides: getEnvOverrides() });
|
|
4942
5417
|
});
|
|
4943
5418
|
console2.get("/api/rules", (c) => {
|
|
4944
5419
|
const rc = loadRuntimeConfig();
|
|
@@ -4947,22 +5422,22 @@ function createConsoleRouter() {
|
|
|
4947
5422
|
console2.put("/api/rules/enabled", async (c) => {
|
|
4948
5423
|
const b = await c.req.json();
|
|
4949
5424
|
const rc = updateRuleEnabled(b.layer, b.category, b.rule_id, b.enabled);
|
|
4950
|
-
return c.json(
|
|
5425
|
+
return c.json(rc);
|
|
4951
5426
|
});
|
|
4952
5427
|
console2.put("/api/rules/deny_mode", async (c) => {
|
|
4953
5428
|
const b = await c.req.json();
|
|
4954
5429
|
const rc = updateRuleDenyMode(b.layer, b.category, b.rule_id, b.deny_mode);
|
|
4955
|
-
return c.json(
|
|
5430
|
+
return c.json(rc);
|
|
4956
5431
|
});
|
|
4957
5432
|
console2.put("/api/rules/skip_l3", async (c) => {
|
|
4958
5433
|
const b = await c.req.json();
|
|
4959
5434
|
const rc = updateRuleSkipL3(b.layer, b.category, b.rule_id, b.skip_l3_on_disabled);
|
|
4960
|
-
return c.json(
|
|
5435
|
+
return c.json(rc);
|
|
4961
5436
|
});
|
|
4962
5437
|
console2.put("/api/categories/enabled", async (c) => {
|
|
4963
5438
|
const b = await c.req.json();
|
|
4964
5439
|
const rc = updateCategoryEnabled(b.layer, b.category, b.enabled);
|
|
4965
|
-
return c.json(
|
|
5440
|
+
return c.json(rc);
|
|
4966
5441
|
});
|
|
4967
5442
|
console2.post("/api/rules", async (c) => {
|
|
4968
5443
|
const b = await c.req.json();
|
|
@@ -4978,38 +5453,38 @@ function createConsoleRouter() {
|
|
|
4978
5453
|
credential: b.credential,
|
|
4979
5454
|
deny_mode: b.deny_mode
|
|
4980
5455
|
});
|
|
4981
|
-
return c.json(
|
|
5456
|
+
return c.json(rc);
|
|
4982
5457
|
});
|
|
4983
5458
|
console2.delete("/api/rules", async (c) => {
|
|
4984
5459
|
const b = await c.req.json();
|
|
4985
5460
|
deleteCustomRule(b.layer, b.category, b.rule_id);
|
|
4986
5461
|
const rc = loadRuntimeConfig();
|
|
4987
|
-
return c.json(
|
|
5462
|
+
return c.json(rc);
|
|
4988
5463
|
});
|
|
4989
5464
|
console2.get("/api/skills", (c) => {
|
|
4990
5465
|
const rc = loadRuntimeConfig();
|
|
4991
|
-
return c.json(
|
|
5466
|
+
return c.json(rc.L3_skills);
|
|
4992
5467
|
});
|
|
4993
5468
|
console2.put("/api/skills/enabled", async (c) => {
|
|
4994
5469
|
const b = await c.req.json();
|
|
4995
5470
|
const rc = updateSkillEnabled(b.skill_id, b.enabled);
|
|
4996
|
-
return c.json(
|
|
5471
|
+
return c.json(rc);
|
|
4997
5472
|
});
|
|
4998
5473
|
console2.put("/api/skill-categories/enabled", async (c) => {
|
|
4999
5474
|
const b = await c.req.json();
|
|
5000
5475
|
const rc = updateSkillCategoryEnabled(b.category, b.enabled);
|
|
5001
|
-
return c.json(
|
|
5476
|
+
return c.json(rc);
|
|
5002
5477
|
});
|
|
5003
5478
|
console2.post("/api/skills", async (c) => {
|
|
5004
5479
|
const b = await c.req.json();
|
|
5005
5480
|
const rc = addCustomSkill(b.skill_id, b.category || "user_custom", b.keywords || [], b.content);
|
|
5006
|
-
return c.json(
|
|
5481
|
+
return c.json(rc);
|
|
5007
5482
|
});
|
|
5008
5483
|
console2.delete("/api/skills", async (c) => {
|
|
5009
5484
|
const b = await c.req.json();
|
|
5010
5485
|
deleteCustomSkill(b.skill_id, b.category);
|
|
5011
5486
|
const rc = loadRuntimeConfig();
|
|
5012
|
-
return c.json(
|
|
5487
|
+
return c.json(rc);
|
|
5013
5488
|
});
|
|
5014
5489
|
console2.get("/api/skills/:skill_id/content", async (c) => {
|
|
5015
5490
|
const skillId = c.req.param("skill_id");
|
|
@@ -5019,10 +5494,10 @@ function createConsoleRouter() {
|
|
|
5019
5494
|
const skillPath = join5(dirname6(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
|
|
5020
5495
|
if (!existsSync9(skillPath)) {
|
|
5021
5496
|
const builtinPath = join5(__dirname6, "..", "..", "agent_moss", "skills", `${skillId}.md`);
|
|
5022
|
-
if (existsSync9(builtinPath)) return c.text(
|
|
5497
|
+
if (existsSync9(builtinPath)) return c.text(readFileSync9(builtinPath, "utf-8"));
|
|
5023
5498
|
return c.json({ error: "skill not found" }, 404);
|
|
5024
5499
|
}
|
|
5025
|
-
return c.text(
|
|
5500
|
+
return c.text(readFileSync9(skillPath, "utf-8"));
|
|
5026
5501
|
});
|
|
5027
5502
|
console2.get("/api/agents", (c) => {
|
|
5028
5503
|
const rc = loadRuntimeConfig();
|
|
@@ -5064,7 +5539,7 @@ function createConsoleRouter() {
|
|
|
5064
5539
|
});
|
|
5065
5540
|
console2.get("/api/config", (c) => {
|
|
5066
5541
|
const rc = loadRuntimeConfig();
|
|
5067
|
-
return c.json(
|
|
5542
|
+
return c.json(rc);
|
|
5068
5543
|
});
|
|
5069
5544
|
console2.get("/api/env-overrides", (c) => {
|
|
5070
5545
|
return c.json({ overrides: getEnvOverrides() });
|
|
@@ -5072,7 +5547,7 @@ function createConsoleRouter() {
|
|
|
5072
5547
|
console2.post("/api/reset", (c) => {
|
|
5073
5548
|
const defaults = generateSourceDefaults();
|
|
5074
5549
|
saveRuntimeConfig(defaults);
|
|
5075
|
-
return c.json(
|
|
5550
|
+
return c.json(defaults);
|
|
5076
5551
|
});
|
|
5077
5552
|
console2.get("/api/token-stats", (c) => {
|
|
5078
5553
|
const days = parseInt(c.req.query("days") || "7", 10);
|
|
@@ -5080,7 +5555,7 @@ function createConsoleRouter() {
|
|
|
5080
5555
|
});
|
|
5081
5556
|
console2.get("/api/token-stats/recent", (c) => {
|
|
5082
5557
|
const limit = parseInt(c.req.query("limit") || "20", 10);
|
|
5083
|
-
return c.json(
|
|
5558
|
+
return c.json(getRecentRecords(limit));
|
|
5084
5559
|
});
|
|
5085
5560
|
console2.get("/api/token-stats/trend", (c) => {
|
|
5086
5561
|
const days = parseInt(c.req.query("days") || "7", 10);
|
|
@@ -5136,7 +5611,7 @@ async function startServer(host = "127.0.0.1", port = 9090) {
|
|
|
5136
5611
|
}
|
|
5137
5612
|
|
|
5138
5613
|
// src/cli.ts
|
|
5139
|
-
import { fileURLToPath as
|
|
5614
|
+
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
5140
5615
|
import { realpathSync } from "fs";
|
|
5141
5616
|
function printHelp() {
|
|
5142
5617
|
console.log(`AgentMoss security analysis engine (TypeScript v${version})
|
|
@@ -5227,7 +5702,7 @@ async function main() {
|
|
|
5227
5702
|
const { execSync } = await import("child_process");
|
|
5228
5703
|
const fs4 = await import("fs");
|
|
5229
5704
|
const path3 = await import("path");
|
|
5230
|
-
const { fileURLToPath:
|
|
5705
|
+
const { fileURLToPath: fileURLToPath9 } = await import("url");
|
|
5231
5706
|
if (process.getuid?.() !== 0) {
|
|
5232
5707
|
try {
|
|
5233
5708
|
execSync("sudo -n true", { stdio: "ignore", timeout: 5e3 });
|
|
@@ -5242,7 +5717,7 @@ async function main() {
|
|
|
5242
5717
|
execSync(sudoArgs.join(" "), { stdio: "inherit" });
|
|
5243
5718
|
process.exit(0);
|
|
5244
5719
|
}
|
|
5245
|
-
const __filename5 =
|
|
5720
|
+
const __filename5 = fileURLToPath9(import.meta.url);
|
|
5246
5721
|
const __dirname7 = path3.dirname(__filename5);
|
|
5247
5722
|
let srcPath = path3.join(__dirname7, "data", "agent_moss.service");
|
|
5248
5723
|
if (!fs4.existsSync(srcPath)) {
|
|
@@ -5384,9 +5859,9 @@ async function main() {
|
|
|
5384
5859
|
var isMain = (() => {
|
|
5385
5860
|
try {
|
|
5386
5861
|
if (!process.argv[1]) return false;
|
|
5387
|
-
const thisFile = realpathSync(
|
|
5862
|
+
const thisFile = realpathSync(fileURLToPath8(import.meta.url));
|
|
5388
5863
|
const argv1 = realpathSync(
|
|
5389
|
-
process.argv[1].startsWith("file://") ?
|
|
5864
|
+
process.argv[1].startsWith("file://") ? fileURLToPath8(process.argv[1]) : process.argv[1]
|
|
5390
5865
|
);
|
|
5391
5866
|
return thisFile === argv1;
|
|
5392
5867
|
} catch {
|