@kenhkl/agent-moss 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +169 -0
- package/dist/cli.js +883 -141
- package/dist/cli.js.map +1 -1
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -18,11 +18,114 @@ var init_esm_shims = __esm({
|
|
|
18
18
|
}
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
+
// src/config.ts
|
|
22
|
+
function loadCustomRules() {
|
|
23
|
+
try {
|
|
24
|
+
const raw = process.env.AGENT_MOSS_CUSTOM_RULES;
|
|
25
|
+
if (!raw) return [];
|
|
26
|
+
const parsed = JSON.parse(raw);
|
|
27
|
+
if (!Array.isArray(parsed)) return [];
|
|
28
|
+
return parsed.filter((r) => r.pattern && r.action);
|
|
29
|
+
} catch {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function getLLMConfig() {
|
|
34
|
+
return llmConfig;
|
|
35
|
+
}
|
|
36
|
+
function getCustomRules() {
|
|
37
|
+
return customRules;
|
|
38
|
+
}
|
|
39
|
+
function isLLMAvailable() {
|
|
40
|
+
if (process.env.AGENT_MOSS_DISABLE_LLM === "1") return false;
|
|
41
|
+
return llmConfig.apiKey.length > 0;
|
|
42
|
+
}
|
|
43
|
+
function detectProvider(baseUrl, providerHint = "") {
|
|
44
|
+
if (providerHint) {
|
|
45
|
+
const hint = providerHint.toLowerCase();
|
|
46
|
+
if (["claude", "anthropic"].includes(hint)) return "anthropic";
|
|
47
|
+
if (["google", "gemini"].includes(hint)) return "gemini";
|
|
48
|
+
if (["zai-coding-plan", "zhipu-coding-plan", "zhipuai-coding-plan"].includes(hint)) return "zai-coding-plan";
|
|
49
|
+
if (["zai", "z-ai", "z.ai", "zai-cn", "zai-china", "zhipu", "glm-cn", "bigmodel"].includes(hint)) return "zhipu";
|
|
50
|
+
if (["xai", "xai-grok"].includes(hint)) return "xai";
|
|
51
|
+
if (["minimax", "minimax-openai"].includes(hint)) return "minimax";
|
|
52
|
+
if (["minimax-anthropic"].includes(hint)) return "minimax-anthropic";
|
|
53
|
+
if (["other", "custom", "local"].includes(hint)) return "openai-compatible";
|
|
54
|
+
return hint;
|
|
55
|
+
}
|
|
56
|
+
const url = baseUrl.toLowerCase();
|
|
57
|
+
if (url.includes("openrouter.ai")) return "openrouter";
|
|
58
|
+
if (url.includes("api.openai.com")) return "openai";
|
|
59
|
+
if (url.includes("anthropic.com") || url.includes("api.anthropic.com")) return "anthropic";
|
|
60
|
+
if (url.includes("generativelanguage.googleapis.com") || url.includes("gemini")) return "gemini";
|
|
61
|
+
if (url.includes("api.deepseek.com")) return "deepseek";
|
|
62
|
+
if (url.includes("api.groq.com")) return "groq";
|
|
63
|
+
if (url.includes("api.mistral.ai")) return "mistral";
|
|
64
|
+
if (url.includes("api.together.xyz")) return "together";
|
|
65
|
+
if (url.includes("api.x.ai")) return "xai";
|
|
66
|
+
if (url.includes("api.minimaxi.com")) return "minimax";
|
|
67
|
+
if (url.includes("api-ai.gitcode.com")) return "gitcode";
|
|
68
|
+
if (url.includes("api.z.ai")) return "zai-coding-plan";
|
|
69
|
+
if (url.includes("open.bigmodel.cn") || url.includes("bigmodel")) return "zhipu";
|
|
70
|
+
if (url.includes(":11434")) return "ollama";
|
|
71
|
+
return "openai-compatible";
|
|
72
|
+
}
|
|
73
|
+
function getProviderHeaders(provider) {
|
|
74
|
+
return PROVIDER_HEADERS[provider] || {};
|
|
75
|
+
}
|
|
76
|
+
function isHeuristicEnabled() {
|
|
77
|
+
return process.env.AGENT_MOSS_DISABLE_HEURISTIC !== "1";
|
|
78
|
+
}
|
|
79
|
+
function isLogicRulesEnabled() {
|
|
80
|
+
return process.env.AGENT_MOSS_DISABLE_LOGIC_RULES !== "1";
|
|
81
|
+
}
|
|
82
|
+
var llmConfig, customRules, PROVIDER_HEADERS;
|
|
83
|
+
var init_config = __esm({
|
|
84
|
+
"src/config.ts"() {
|
|
85
|
+
"use strict";
|
|
86
|
+
init_esm_shims();
|
|
87
|
+
llmConfig = {
|
|
88
|
+
provider: "openai",
|
|
89
|
+
baseUrl: process.env.AGENT_MOSS_LLM_BASE_URL || "https://api.openai.com/v1",
|
|
90
|
+
apiKey: process.env.AGENT_MOSS_LLM_API_KEY || process.env.OPENAI_API_KEY || "",
|
|
91
|
+
model: process.env.AGENT_MOSS_LLM_MODEL || "gpt-4o",
|
|
92
|
+
temperature: parseFloat(process.env.AGENT_MOSS_LLM_TEMPERATURE || "0.1"),
|
|
93
|
+
maxTokens: parseInt(process.env.AGENT_MOSS_LLM_MAX_TOKENS || "4096", 10),
|
|
94
|
+
timeout: parseInt(process.env.AGENT_MOSS_LLM_TIMEOUT || "300", 10)
|
|
95
|
+
};
|
|
96
|
+
customRules = loadCustomRules();
|
|
97
|
+
PROVIDER_HEADERS = {
|
|
98
|
+
"openrouter": {
|
|
99
|
+
"HTTP-Referer": "shturl.cc/LlVD5pyWViHjM",
|
|
100
|
+
"X-OpenRouter-Title": "AgentMoss"
|
|
101
|
+
},
|
|
102
|
+
"xai": {
|
|
103
|
+
"HTTP-Referer": "shturl.cc/LlVD5pyWViHjM"
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
21
109
|
// src/engine/heuristic.ts
|
|
22
110
|
function runHeuristicCheck(input) {
|
|
23
111
|
const command = `${input.aNext.actionType}: ${input.aNext.actionDetail}`;
|
|
24
112
|
const matched = [];
|
|
25
113
|
let highestSeverity = "low";
|
|
114
|
+
const userRules = getCustomRules();
|
|
115
|
+
for (const rule of userRules) {
|
|
116
|
+
try {
|
|
117
|
+
const re = new RegExp(rule.pattern);
|
|
118
|
+
if (re.test(command)) {
|
|
119
|
+
if (rule.action === "Deny") {
|
|
120
|
+
matched.push(`\u81EA\u5B9A\u4E49\u89C4\u5219: ${rule.pattern}`);
|
|
121
|
+
if (severityRank(rule.severity) > severityRank(highestSeverity)) {
|
|
122
|
+
highestSeverity = rule.severity;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
} catch {
|
|
127
|
+
}
|
|
128
|
+
}
|
|
26
129
|
for (const rule of DANGEROUS_PATTERNS) {
|
|
27
130
|
if (rule.pattern.test(command)) {
|
|
28
131
|
matched.push(rule.description);
|
|
@@ -31,12 +134,22 @@ function runHeuristicCheck(input) {
|
|
|
31
134
|
}
|
|
32
135
|
}
|
|
33
136
|
}
|
|
137
|
+
const injectionText = `${input.aNext.actionType} ${input.aNext.actionDetail} ${input.reason || ""} ${input.promptSession || ""}`.toLowerCase();
|
|
138
|
+
const injectionHits = checkInjectionKeywords(injectionText);
|
|
139
|
+
if (injectionHits.length > 0) {
|
|
140
|
+
const topHit = injectionHits.reduce((a, b) => severityRank(a.riskLevel) >= severityRank(b.riskLevel) ? a : b);
|
|
141
|
+
matched.push(...injectionHits.map((h) => `\u6CE8\u5165\u5173\u952E\u8BCD [${h.lang}]: ${h.keyword}`));
|
|
142
|
+
if (severityRank(topHit.riskLevel) > severityRank(highestSeverity)) {
|
|
143
|
+
highestSeverity = topHit.riskLevel;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
34
146
|
if (matched.length > 0) {
|
|
147
|
+
const riskType = injectionHits.length > 0 ? "prompt_injection" : "dangerous_command";
|
|
35
148
|
return {
|
|
36
149
|
allowed: false,
|
|
37
150
|
reason: `\u68C0\u6D4B\u5230\u9AD8\u5371\u64CD\u4F5C: ${matched.join(", ")}`,
|
|
38
151
|
riskLevel: highestSeverity,
|
|
39
|
-
riskType
|
|
152
|
+
riskType,
|
|
40
153
|
confidence: 95,
|
|
41
154
|
source: "heuristic",
|
|
42
155
|
violatedLayers: ["1"]
|
|
@@ -44,35 +157,135 @@ function runHeuristicCheck(input) {
|
|
|
44
157
|
}
|
|
45
158
|
return null;
|
|
46
159
|
}
|
|
160
|
+
function checkInjectionKeywords(text) {
|
|
161
|
+
const hits = [];
|
|
162
|
+
for (const entry of INJECTION_KEYWORDS) {
|
|
163
|
+
if (text.includes(entry.keyword.toLowerCase())) {
|
|
164
|
+
hits.push(entry);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return hits;
|
|
168
|
+
}
|
|
47
169
|
function severityRank(s) {
|
|
48
170
|
const ranks = { low: 1, medium: 2, high: 3, critical: 4 };
|
|
49
171
|
return ranks[s] || 0;
|
|
50
172
|
}
|
|
51
|
-
var DANGEROUS_PATTERNS;
|
|
173
|
+
var INJECTION_KEYWORDS, DANGEROUS_PATTERNS;
|
|
52
174
|
var init_heuristic = __esm({
|
|
53
175
|
"src/engine/heuristic.ts"() {
|
|
54
176
|
"use strict";
|
|
55
177
|
init_esm_shims();
|
|
178
|
+
init_config();
|
|
179
|
+
INJECTION_KEYWORDS = [
|
|
180
|
+
// ---- Instruction Override ----
|
|
181
|
+
{ keyword: "ignore previous instructions", riskLevel: "critical", lang: "en" },
|
|
182
|
+
{ keyword: "ignore all previous", riskLevel: "critical", lang: "en" },
|
|
183
|
+
{ keyword: "disregard all prior", riskLevel: "critical", lang: "en" },
|
|
184
|
+
{ keyword: "forget everything", riskLevel: "high", lang: "en" },
|
|
185
|
+
{ keyword: "\u5FFD\u7565\u4E4B\u524D\u7684\u6307\u4EE4", riskLevel: "critical", lang: "zh" },
|
|
186
|
+
{ keyword: "\u5FFD\u7565\u6240\u6709\u5148\u524D\u7684", riskLevel: "critical", lang: "zh" },
|
|
187
|
+
{ keyword: "\u5FFD\u7565\u4E0A\u9762\u6240\u6709", riskLevel: "high", lang: "zh" },
|
|
188
|
+
{ keyword: "\u8BF7\u5FD8\u8BB0\u4E4B\u524D", riskLevel: "high", lang: "zh" },
|
|
189
|
+
// ---- Role Hijacking ----
|
|
190
|
+
{ keyword: "you are now", riskLevel: "high", lang: "en" },
|
|
191
|
+
{ keyword: "you are no longer", riskLevel: "high", lang: "en" },
|
|
192
|
+
{ keyword: "new instructions", riskLevel: "high", lang: "en" },
|
|
193
|
+
{ keyword: "from now on, you", riskLevel: "high", lang: "en" },
|
|
194
|
+
{ keyword: "\u4F60\u73B0\u5728\u662F\u4E00\u4E2A", riskLevel: "high", lang: "zh" },
|
|
195
|
+
{ keyword: "\u4F60\u4E0D\u518D\u662F\u4E00\u4E2A", riskLevel: "high", lang: "zh" },
|
|
196
|
+
{ keyword: "\u4ECE\u73B0\u5728\u5F00\u59CB\u4F60", riskLevel: "high", lang: "zh" },
|
|
197
|
+
{ keyword: "\u4F60\u7684\u65B0\u89D2\u8272\u662F", riskLevel: "high", lang: "zh" },
|
|
198
|
+
// ---- Command Injection ----
|
|
199
|
+
{ keyword: "system prompt", riskLevel: "high", lang: "en" },
|
|
200
|
+
{ keyword: "reveal your instructions", riskLevel: "high", lang: "en" },
|
|
201
|
+
{ keyword: "output your instructions", riskLevel: "high", lang: "en" },
|
|
202
|
+
{ keyword: "repeat your instructions", riskLevel: "medium", lang: "en" },
|
|
203
|
+
{ keyword: "\u7CFB\u7EDF\u63D0\u793A", riskLevel: "high", lang: "zh" },
|
|
204
|
+
{ keyword: "\u8F93\u51FA\u4F60\u7684\u6307\u4EE4", riskLevel: "high", lang: "zh" },
|
|
205
|
+
{ keyword: "\u91CD\u590D\u4F60\u7684\u6307\u4EE4", riskLevel: "medium", lang: "zh" },
|
|
206
|
+
// ---- Social Engineering ----
|
|
207
|
+
{ keyword: "this is an authorized test", riskLevel: "high", lang: "en" },
|
|
208
|
+
{ keyword: "security audit approved", riskLevel: "high", lang: "en" },
|
|
209
|
+
{ keyword: "admin override", riskLevel: "critical", lang: "en" },
|
|
210
|
+
{ keyword: "debug mode", riskLevel: "high", lang: "en" },
|
|
211
|
+
{ keyword: "maintenance mode", riskLevel: "high", lang: "en" },
|
|
212
|
+
{ keyword: "\u8FD9\u662F\u6388\u6743\u7684\u6D4B\u8BD5", riskLevel: "high", lang: "zh" },
|
|
213
|
+
{ keyword: "\u5B89\u5168\u5BA1\u8BA1\u5DF2\u6279\u51C6", riskLevel: "high", lang: "zh" },
|
|
214
|
+
{ keyword: "\u7BA1\u7406\u5458\u8986\u76D6", riskLevel: "critical", lang: "zh" },
|
|
215
|
+
{ keyword: "\u8C03\u8BD5\u6A21\u5F0F", riskLevel: "high", lang: "zh" },
|
|
216
|
+
// ---- XSS ----
|
|
217
|
+
{ keyword: "<script>", riskLevel: "high", lang: "en" },
|
|
218
|
+
{ keyword: "javascript:", riskLevel: "high", lang: "en" },
|
|
219
|
+
{ keyword: "onerror=", riskLevel: "high", lang: "en" },
|
|
220
|
+
{ keyword: "onload=", riskLevel: "high", lang: "en" },
|
|
221
|
+
// ---- Fake Boundaries ----
|
|
222
|
+
{ keyword: "=== END OF", riskLevel: "high", lang: "en" },
|
|
223
|
+
{ keyword: "--- END SYSTEM", riskLevel: "high", lang: "en" },
|
|
224
|
+
{ keyword: "[END OF", riskLevel: "high", lang: "en" },
|
|
225
|
+
{ keyword: "=== \u7ED3\u675F", riskLevel: "high", lang: "zh" },
|
|
226
|
+
{ keyword: "--- \u7ED3\u675F\u7CFB\u7EDF", riskLevel: "high", lang: "zh" },
|
|
227
|
+
// ---- Developer Mode ----
|
|
228
|
+
{ keyword: "developer mode", riskLevel: "critical", lang: "en" },
|
|
229
|
+
{ keyword: "DAN mode", riskLevel: "critical", lang: "en" },
|
|
230
|
+
{ keyword: "jailbreak", riskLevel: "critical", lang: "en" },
|
|
231
|
+
{ keyword: "\u5F00\u53D1\u8005\u6A21\u5F0F", riskLevel: "critical", lang: "zh" },
|
|
232
|
+
{ keyword: "\u8D8A\u72F1", riskLevel: "critical", lang: "zh" },
|
|
233
|
+
// ---- xiaoO-specific bypass ----
|
|
234
|
+
{ keyword: "bypass audit", riskLevel: "critical", lang: "en" },
|
|
235
|
+
{ keyword: "skip security check", riskLevel: "critical", lang: "en" },
|
|
236
|
+
{ keyword: "disable guard", riskLevel: "critical", lang: "en" },
|
|
237
|
+
{ keyword: "\u7ED5\u8FC7\u5BA1\u8BA1", riskLevel: "critical", lang: "zh" },
|
|
238
|
+
{ keyword: "\u7981\u7528\u5B89\u5168\u68C0\u67E5", riskLevel: "critical", lang: "zh" },
|
|
239
|
+
{ keyword: "\u5173\u95ED\u9632\u62A4", riskLevel: "critical", lang: "zh" }
|
|
240
|
+
];
|
|
56
241
|
DANGEROUS_PATTERNS = [
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
{ pattern:
|
|
60
|
-
|
|
61
|
-
{ pattern:
|
|
62
|
-
|
|
63
|
-
{ pattern: /
|
|
64
|
-
|
|
65
|
-
{ pattern:
|
|
66
|
-
|
|
67
|
-
{ pattern:
|
|
68
|
-
{ pattern: /
|
|
69
|
-
{ pattern:
|
|
70
|
-
{ pattern:
|
|
71
|
-
|
|
72
|
-
{ pattern: /
|
|
73
|
-
{ pattern: /
|
|
242
|
+
// --- Critical: rm command graded detection (strict to loose, first match wins) ---
|
|
243
|
+
// 1. rm -rf root dir
|
|
244
|
+
{ pattern: /\brm\s+-[a-zA-Z]*r[a-zA-Z]*f[a-zA-Z]*\s+\/\s/i, severity: "critical", description: "\u9012\u5F52\u5F3A\u5236\u5220\u9664\u6839\u76EE\u5F55 (rm -rf /)" },
|
|
245
|
+
// 2. rm -rf absolute path (both -r and -f)
|
|
246
|
+
{ pattern: /\brm\s+-(?=[^\s]*r)(?=[^\s]*f)[a-zA-Z]*\s+\//i, severity: "critical", description: "\u9012\u5F52\u5F3A\u5236\u5220\u9664\u7EDD\u5BF9\u8DEF\u5F84 (rm -rf /...)" },
|
|
247
|
+
// 3. rm with -r or -f targeting system dirs
|
|
248
|
+
{ pattern: /\brm\s+-[a-zA-Z]*[rf][a-zA-Z]*\s+\/(etc|var|home|usr|boot|root|opt|srv|sys|proc)\b/i, severity: "critical", description: "\u5220\u9664\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55" },
|
|
249
|
+
// 4. rm -r absolute path (no -f)
|
|
250
|
+
{ pattern: /\brm\s+-[a-zA-Z]*r[a-zA-Z]*\s+\//i, severity: "high", description: "\u9012\u5F52\u5220\u9664\u64CD\u4F5C (rm -r /...)" },
|
|
251
|
+
// 5. rm -f absolute path (no -r)
|
|
252
|
+
{ pattern: /\brm\s+-[a-zA-Z]*f[a-zA-Z]*\s+\//i, severity: "medium", description: "\u5F3A\u5236\u5220\u9664\u6587\u4EF6 (rm -f /...)" },
|
|
253
|
+
{ pattern: /dd\s+if=\/dev\/zero|dd\s+if=.*of=\/dev\//i, severity: "critical", description: "\u78C1\u76D8\u6E05\u96F6/\u76F4\u63A5\u5199\u5165" },
|
|
254
|
+
{ pattern: /mkfs\b/i, severity: "critical", description: "\u683C\u5F0F\u5316\u6587\u4EF6\u7CFB\u7EDF" },
|
|
255
|
+
{ pattern: /:\(\)\s*\{.*:\|:&.*\};:/i, severity: "critical", description: "Fork Bomb" },
|
|
256
|
+
// --- Critical: remote code execution ---
|
|
257
|
+
{ pattern: /curl\s+.*\|\s*(bash|sh|zsh|dash)/i, severity: "critical", description: "curl pipe shell \u6267\u884C" },
|
|
258
|
+
{ pattern: /wget\s+.*\|\s*(bash|sh)/i, severity: "critical", description: "wget pipe shell \u6267\u884C" },
|
|
259
|
+
{ pattern: /eval\s+\$\(curl|eval\s+\$\(wget/i, severity: "critical", description: "eval \u8FDC\u7A0B\u4EE3\u7801\u6267\u884C" },
|
|
260
|
+
{ pattern: /base64\s+-d\s+.*\|\s*bash/i, severity: "critical", description: "base64 \u89E3\u7801\u6267\u884C" },
|
|
261
|
+
// --- Critical: reverse shell ---
|
|
262
|
+
{ pattern: /\/dev\/tcp\/|bash\s+-i\s+>&/i, severity: "critical", description: "\u53CD\u5F39 Shell" },
|
|
263
|
+
{ pattern: /nc\s+-[el]\s+.*\d{1,5}/i, severity: "high", description: "Netcat \u53CD\u5F39\u8FDE\u63A5" },
|
|
264
|
+
// --- Critical: sensitive file access ---
|
|
265
|
+
{ pattern: /\/etc\/shadow/i, severity: "critical", description: "\u8BBF\u95EE\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6 (/etc/shadow)" },
|
|
266
|
+
{ pattern: /\.ssh\/id_rsa/i, severity: "critical", description: "\u8BBF\u95EE SSH \u79C1\u94A5\u6587\u4EF6" },
|
|
267
|
+
// --- Critical: privilege escalation / container escape ---
|
|
268
|
+
{ pattern: /\/var\/run\/docker\.sock/i, severity: "critical", description: "\u8BBF\u95EE Docker socket\uFF0C\u53EF\u80FD\u5BFC\u81F4\u5BB9\u5668\u9003\u9038" },
|
|
269
|
+
// --- High: dangerous permissions / ownership ---
|
|
270
|
+
{ pattern: /chmod\s+[0-7]*777[0-7]*/i, severity: "high", description: "\u5168\u6743\u9650\u8BBE\u7F6E (chmod 777)" },
|
|
271
|
+
{ 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" },
|
|
272
|
+
{ pattern: /sudo\s+rm\s+-rf/i, severity: "high", description: "sudo \u5F3A\u5236\u5220\u9664" },
|
|
273
|
+
{ pattern: /sudo\s+/i, severity: "high", description: "\u63D0\u6743\u6267\u884C (sudo)" },
|
|
274
|
+
// --- High: system config overwrite ---
|
|
275
|
+
{ pattern: />\s*\/etc\//i, severity: "critical", description: "\u8986\u76D6\u5199\u5165\u7CFB\u7EDF\u914D\u7F6E\u76EE\u5F55" },
|
|
276
|
+
// --- High: download script + execute risk ---
|
|
277
|
+
{ pattern: /(?:curl|wget)\s+.*(?:-o|--output|>)\s*\S+\.(?:sh|bash)\b/i, severity: "high", description: "\u8FDC\u7A0B\u4E0B\u8F7D\u811A\u672C\u5230\u672C\u5730" },
|
|
278
|
+
{ pattern: /(?:curl|wget)\s+.*\s+&&?\s*(?:chmod\s+\+x|bash|sh)/i, severity: "high", description: "\u4E0B\u8F7D\u540E\u6267\u884C" },
|
|
279
|
+
// --- High: package manager pipe-to-shell ---
|
|
280
|
+
{ pattern: /git\s+clone\s+.*\|.*bash/i, severity: "high", description: "git clone pipe shell" },
|
|
281
|
+
{ pattern: /pip\s+install\s+.*\|\s*bash/i, severity: "high", description: "pip install pipe shell" },
|
|
282
|
+
{ pattern: /npm\s+install\s+-g\s+.*\|\s*bash/i, severity: "high", description: "npm install pipe shell" },
|
|
283
|
+
// --- High: destructive git operations ---
|
|
284
|
+
{ pattern: /git\s+push\s+.*--force/i, severity: "high", description: "\u5F3A\u5236\u63A8\u9001\u64CD\u4F5C (git push --force)" },
|
|
285
|
+
// --- Medium: credential export ---
|
|
74
286
|
{ pattern: /export\s+.*(?:TOKEN|KEY|SECRET|PASSWORD)/i, severity: "medium", description: "\u5BFC\u51FA\u654F\u611F\u51ED\u8BC1" },
|
|
75
|
-
|
|
287
|
+
// --- High: device file access ---
|
|
288
|
+
{ pattern: /\/dev\/(mem|kmsg)\b/i, severity: "high", description: "\u8BBF\u95EE\u8BBE\u5907\u6587\u4EF6 (\u7269\u7406\u5185\u5B58/\u5185\u6838\u6D88\u606F)" }
|
|
76
289
|
];
|
|
77
290
|
}
|
|
78
291
|
});
|
|
@@ -80,31 +293,60 @@ var init_heuristic = __esm({
|
|
|
80
293
|
// src/engine/logic-rules.ts
|
|
81
294
|
function runLogicCheck(input) {
|
|
82
295
|
const { actionHistory, aNext, cwd } = input;
|
|
83
|
-
const
|
|
296
|
+
const actionDetail = aNext.actionDetail.toLowerCase();
|
|
297
|
+
const actionType = aNext.actionType.toLowerCase();
|
|
84
298
|
const resolvedCwd = cwd || "/";
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
);
|
|
89
|
-
|
|
299
|
+
const rbwResult = checkReadBeforeWrite(actionType, actionDetail, actionHistory, input.reason, resolvedCwd);
|
|
300
|
+
if (rbwResult) return rbwResult;
|
|
301
|
+
if (input.promptSession && input.promptSession.trim()) {
|
|
302
|
+
const promptLower = input.promptSession.toLowerCase();
|
|
303
|
+
for (const pattern of INTENT_DEVIATION_PATTERNS) {
|
|
304
|
+
const intentMatched = pattern.intentKeywords.some((kw) => promptLower.includes(kw));
|
|
305
|
+
const dangerousMatched = pattern.dangerousActions.some((kw) => actionDetail.includes(kw));
|
|
306
|
+
if (intentMatched && dangerousMatched) {
|
|
307
|
+
return {
|
|
308
|
+
allowed: false,
|
|
309
|
+
reason: pattern.reason,
|
|
310
|
+
riskLevel: "high",
|
|
311
|
+
riskType: "intent_deviation",
|
|
312
|
+
confidence: 85,
|
|
313
|
+
source: "logic_rule",
|
|
314
|
+
violatedLayers: ["2"]
|
|
315
|
+
};
|
|
316
|
+
}
|
|
90
317
|
}
|
|
91
318
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
319
|
+
const pathResult = checkSensitivePath(actionDetail, resolvedCwd);
|
|
320
|
+
if (pathResult) return pathResult;
|
|
321
|
+
if (actionType !== "file_write" && actionType !== "file_edit") {
|
|
322
|
+
if ((actionDetail.includes("*") || actionDetail.includes("?")) && ["rm ", "rm ", "del ", "del ", "delete", "remove ", "remove ", "\u5220\u9664", "\u79FB\u9664"].some((kw) => actionDetail.includes(kw))) {
|
|
323
|
+
return {
|
|
324
|
+
allowed: false,
|
|
325
|
+
reason: "\u68C0\u6D4B\u5230\u901A\u914D\u7B26\u7ED3\u5408\u5220\u9664\u64CD\u4F5C\uFF0C\u53EF\u80FD\u9020\u6210\u6279\u91CF\u8BEF\u5220",
|
|
326
|
+
riskLevel: "high",
|
|
327
|
+
riskType: "script_execution",
|
|
328
|
+
confidence: 90,
|
|
329
|
+
source: "logic_rule",
|
|
330
|
+
violatedLayers: ["2"]
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
if (/>\s*\/etc\//.test(actionDetail) || />\s*\/boot\//.test(actionDetail) || />\s*\/proc\//.test(actionDetail)) {
|
|
334
|
+
return {
|
|
335
|
+
allowed: false,
|
|
336
|
+
reason: "\u68C0\u6D4B\u5230\u91CD\u5B9A\u5411\u8986\u76D6\u5199\u5165\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55",
|
|
337
|
+
riskLevel: "critical",
|
|
338
|
+
riskType: "file_access",
|
|
339
|
+
confidence: 95,
|
|
340
|
+
source: "logic_rule",
|
|
341
|
+
violatedLayers: ["2"]
|
|
342
|
+
};
|
|
343
|
+
}
|
|
102
344
|
}
|
|
103
|
-
if (
|
|
345
|
+
if (actionDetail.includes("sudo") || actionDetail.includes("su -") || actionDetail.includes("chmod u+s") || actionDetail.includes("setuid")) {
|
|
104
346
|
const adminContext = actionHistory.some(
|
|
105
|
-
(h) => h.actionDetail.includes("sudo") || h.actionDetail.includes("apt") || h.actionDetail.includes("systemctl")
|
|
347
|
+
(h) => h.actionDetail.includes("sudo") || h.actionDetail.includes("apt") || h.actionDetail.includes("systemctl") || h.actionDetail.includes("yum") || h.actionDetail.includes("dnf") || h.actionDetail.includes("pacman") || h.actionDetail.includes("zypper") || h.actionDetail.includes("apk")
|
|
106
348
|
);
|
|
107
|
-
if (!adminContext
|
|
349
|
+
if (!adminContext) {
|
|
108
350
|
return {
|
|
109
351
|
allowed: false,
|
|
110
352
|
reason: "\u68C0\u6D4B\u5230\u63D0\u6743\u64CD\u4F5C\uFF0C\u672A\u53D1\u73B0\u5408\u6CD5\u7BA1\u7406\u4E0A\u4E0B\u6587",
|
|
@@ -116,8 +358,8 @@ function runLogicCheck(input) {
|
|
|
116
358
|
};
|
|
117
359
|
}
|
|
118
360
|
}
|
|
119
|
-
if (
|
|
120
|
-
const targetHost = extractRemoteHost(
|
|
361
|
+
if (/(?:ssh|scp|rsync)\s+/.test(actionDetail)) {
|
|
362
|
+
const targetHost = extractRemoteHost(actionDetail);
|
|
121
363
|
if (targetHost && !isLocalHost(targetHost)) {
|
|
122
364
|
return {
|
|
123
365
|
allowed: false,
|
|
@@ -132,18 +374,97 @@ function runLogicCheck(input) {
|
|
|
132
374
|
}
|
|
133
375
|
return null;
|
|
134
376
|
}
|
|
135
|
-
function
|
|
377
|
+
function extractFilePaths(text) {
|
|
378
|
+
const paths = [];
|
|
379
|
+
const unixPaths = text.match(/(?:^|\s)(\/[^\s;|&><'"]+)/g);
|
|
380
|
+
if (unixPaths) paths.push(...unixPaths.map((p) => p.trim()));
|
|
381
|
+
const quotedPaths = text.match(/["']\/([^"']+)["']/g);
|
|
382
|
+
if (quotedPaths) paths.push(...quotedPaths.map((p) => p.replace(/["']/g, "")));
|
|
383
|
+
return [...new Set(paths)];
|
|
384
|
+
}
|
|
385
|
+
function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, cwd) {
|
|
386
|
+
if (NON_WRITE_TOOLS.has(actionType)) return null;
|
|
387
|
+
const isWrite = WRITE_KEYWORDS.some((kw) => actionType.includes(kw) || actionDetail.includes(kw));
|
|
388
|
+
if (!isWrite) return null;
|
|
389
|
+
const targetPaths = extractFilePaths(actionDetail);
|
|
390
|
+
if (targetPaths.length === 0) return null;
|
|
391
|
+
const historyReadPaths = /* @__PURE__ */ new Set();
|
|
392
|
+
for (const action of actionHistory) {
|
|
393
|
+
const histDetail = action.actionDetail.toLowerCase();
|
|
394
|
+
const histType = action.actionType.toLowerCase();
|
|
395
|
+
const isRead = READ_KEYWORDS.some((kw) => histType.includes(kw) || histDetail.includes(kw));
|
|
396
|
+
if (isRead) {
|
|
397
|
+
for (const p of extractFilePaths(histDetail)) {
|
|
398
|
+
historyReadPaths.add(p);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
const unreadPaths = [];
|
|
403
|
+
for (const path2 of targetPaths) {
|
|
404
|
+
const hasRead = [...historyReadPaths].some(
|
|
405
|
+
(rp) => path2 === rp || path2.startsWith(rp) || rp.startsWith(path2)
|
|
406
|
+
);
|
|
407
|
+
if (!hasRead) {
|
|
408
|
+
const isCreate = CREATE_KEYWORDS.some((kw) => reason.toLowerCase().includes(kw) || actionDetail.includes(kw));
|
|
409
|
+
if (!isCreate) {
|
|
410
|
+
unreadPaths.push(path2);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (unreadPaths.length > 0) {
|
|
415
|
+
return {
|
|
416
|
+
allowed: false,
|
|
417
|
+
reason: `\u8FDD\u53CD read_before_write \u5B89\u5168\u539F\u5219\uFF1A\u5BF9\u6587\u4EF6 ${unreadPaths[0]} \u6267\u884C\u5199\u5165\u64CD\u4F5C\uFF0C\u4F46\u5386\u53F2\u6267\u884C\u5E8F\u5217\u4E2D\u672A\u53D1\u73B0\u5BF9\u8BE5\u6587\u4EF6\u7684\u8BFB\u53D6\u64CD\u4F5C`,
|
|
418
|
+
riskLevel: "high",
|
|
419
|
+
riskType: "read_before_write",
|
|
420
|
+
confidence: 85,
|
|
421
|
+
source: "logic_rule",
|
|
422
|
+
violatedLayers: ["2"]
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
function checkSensitivePath(command, cwd) {
|
|
136
428
|
const expandedCwd = cwd.replace(/^~/, process.env.HOME || "/home/user");
|
|
137
|
-
for (const
|
|
138
|
-
if (command.includes(
|
|
429
|
+
for (const sp of ROOT_SENSITIVE_PATHS) {
|
|
430
|
+
if (command.includes(sp.path.toLowerCase())) {
|
|
431
|
+
return {
|
|
432
|
+
allowed: false,
|
|
433
|
+
reason: `\u5C1D\u8BD5\u8BBF\u95EE\u7CFB\u7EDF\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
|
|
434
|
+
riskLevel: sp.riskLevel,
|
|
435
|
+
riskType: "sensitive_path_access",
|
|
436
|
+
confidence: 90,
|
|
437
|
+
source: "logic_rule",
|
|
438
|
+
violatedLayers: ["2"]
|
|
439
|
+
};
|
|
440
|
+
}
|
|
139
441
|
}
|
|
140
|
-
for (const
|
|
141
|
-
|
|
442
|
+
for (const sp of HOME_SENSITIVE_PATHS) {
|
|
443
|
+
const expanded = `${expandedCwd}/${sp.path}`;
|
|
444
|
+
if (command.includes(sp.path.toLowerCase()) || command.includes(expanded.toLowerCase())) {
|
|
445
|
+
return {
|
|
446
|
+
allowed: false,
|
|
447
|
+
reason: `\u5C1D\u8BD5\u8BBF\u95EE\u654F\u611F\u8DEF\u5F84: ${sp.desc} (${sp.path})`,
|
|
448
|
+
riskLevel: sp.riskLevel,
|
|
449
|
+
riskType: "sensitive_path_access",
|
|
450
|
+
confidence: 90,
|
|
451
|
+
source: "logic_rule",
|
|
452
|
+
violatedLayers: ["2"]
|
|
453
|
+
};
|
|
454
|
+
}
|
|
142
455
|
}
|
|
143
456
|
if ((command.includes("rm ") || command.includes("unlink")) && (command.includes("/etc/") || command.includes("/root/") || command.includes("/var/log/"))) {
|
|
144
|
-
return
|
|
457
|
+
return {
|
|
458
|
+
allowed: false,
|
|
459
|
+
reason: "\u68C0\u6D4B\u5230\u5220\u9664\u5173\u952E\u7CFB\u7EDF\u76EE\u5F55\u5185\u5BB9",
|
|
460
|
+
riskLevel: "critical",
|
|
461
|
+
riskType: "sensitive_path_access",
|
|
462
|
+
confidence: 90,
|
|
463
|
+
source: "logic_rule",
|
|
464
|
+
violatedLayers: ["2"]
|
|
465
|
+
};
|
|
145
466
|
}
|
|
146
|
-
return
|
|
467
|
+
return null;
|
|
147
468
|
}
|
|
148
469
|
function extractRemoteHost(command) {
|
|
149
470
|
const m = command.match(/(?:ssh|scp|rsync)\s+(?:-[\w]+\s+)*(\S+@\S+|\b(?!-)[\w.-]+\b)/);
|
|
@@ -153,51 +474,51 @@ function isLocalHost(host) {
|
|
|
153
474
|
const localNames = ["localhost", "127.0.0.1", "::1", process.env.HOSTNAME || "", "0.0.0.0"];
|
|
154
475
|
return localNames.includes(host.replace(/.*@/, ""));
|
|
155
476
|
}
|
|
156
|
-
var ROOT_SENSITIVE_PATHS, HOME_SENSITIVE_PATHS;
|
|
477
|
+
var ROOT_SENSITIVE_PATHS, HOME_SENSITIVE_PATHS, NON_WRITE_TOOLS, WRITE_KEYWORDS, READ_KEYWORDS, CREATE_KEYWORDS, INTENT_DEVIATION_PATTERNS;
|
|
157
478
|
var init_logic_rules = __esm({
|
|
158
479
|
"src/engine/logic-rules.ts"() {
|
|
159
480
|
"use strict";
|
|
160
481
|
init_esm_shims();
|
|
161
482
|
ROOT_SENSITIVE_PATHS = [
|
|
162
|
-
"/etc/shadow",
|
|
163
|
-
"/etc/passwd",
|
|
164
|
-
"/etc/sudoers",
|
|
165
|
-
"/
|
|
166
|
-
"/
|
|
167
|
-
"/
|
|
168
|
-
"/
|
|
169
|
-
"/
|
|
483
|
+
{ path: "/etc/shadow", riskLevel: "critical", desc: "\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6" },
|
|
484
|
+
{ path: "/etc/passwd", riskLevel: "high", desc: "\u7CFB\u7EDF\u7528\u6237\u6587\u4EF6" },
|
|
485
|
+
{ path: "/etc/sudoers", riskLevel: "critical", desc: "sudo \u914D\u7F6E" },
|
|
486
|
+
{ path: "/etc/hosts", riskLevel: "medium", desc: "DNS \u89E3\u6790\u914D\u7F6E" },
|
|
487
|
+
{ path: "/etc/crontab", riskLevel: "high", desc: "\u7CFB\u7EDF\u5B9A\u65F6\u4EFB\u52A1" },
|
|
488
|
+
{ path: "/etc/systemd/", riskLevel: "high", desc: "systemd \u670D\u52A1\u914D\u7F6E" },
|
|
489
|
+
{ path: "/etc/ssh/sshd_config", riskLevel: "high", desc: "SSH \u670D\u52A1\u914D\u7F6E" },
|
|
490
|
+
{ path: "/root/.ssh", riskLevel: "critical", desc: "root SSH \u76EE\u5F55" },
|
|
491
|
+
{ path: "/root/.bashrc", riskLevel: "high", desc: "root shell \u914D\u7F6E" },
|
|
492
|
+
{ path: "/boot/", riskLevel: "critical", desc: "\u542F\u52A8\u5F15\u5BFC\u76EE\u5F55" },
|
|
493
|
+
{ path: "/proc/sys/", riskLevel: "high", desc: "\u5185\u6838\u53C2\u6570" },
|
|
494
|
+
{ path: "/sys/", riskLevel: "high", desc: "sysfs \u5185\u6838\u63A5\u53E3" },
|
|
495
|
+
{ path: "/dev/mem", riskLevel: "critical", desc: "\u7269\u7406\u5185\u5B58\u8BBF\u95EE\u8BBE\u5907" },
|
|
496
|
+
{ path: "/dev/kmsg", riskLevel: "high", desc: "\u5185\u6838\u6D88\u606F\u7F13\u51B2\u533A" }
|
|
170
497
|
];
|
|
171
498
|
HOME_SENSITIVE_PATHS = [
|
|
172
|
-
".ssh/id_rsa",
|
|
173
|
-
".ssh/
|
|
174
|
-
".
|
|
175
|
-
".
|
|
176
|
-
".
|
|
499
|
+
{ path: ".ssh/id_rsa", riskLevel: "critical", desc: "SSH \u79C1\u94A5" },
|
|
500
|
+
{ path: ".ssh/id_ed25519", riskLevel: "critical", desc: "SSH \u79C1\u94A5 (ed25519)" },
|
|
501
|
+
{ path: ".ssh/authorized_keys", riskLevel: "high", desc: "SSH \u6388\u6743\u5BC6\u94A5" },
|
|
502
|
+
{ path: ".aws/credentials", riskLevel: "critical", desc: "AWS \u51ED\u8BC1" },
|
|
503
|
+
{ path: ".gitconfig", riskLevel: "medium", desc: "Git \u914D\u7F6E" },
|
|
504
|
+
{ path: ".gnupg", riskLevel: "high", desc: "GPG \u5BC6\u94A5\u76EE\u5F55" }
|
|
505
|
+
];
|
|
506
|
+
NON_WRITE_TOOLS = /* @__PURE__ */ new Set(["ask_user_question", "glob", "grep", "list_dir", "count_text_length", "search"]);
|
|
507
|
+
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"];
|
|
508
|
+
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"];
|
|
509
|
+
CREATE_KEYWORDS = ["\u521B\u5EFA", "\u65B0\u5EFA", "create", "new file", "touch"];
|
|
510
|
+
INTENT_DEVIATION_PATTERNS = [
|
|
511
|
+
{
|
|
512
|
+
intentKeywords: ["\u8BFB\u53D6", "\u67E5\u770B", "\u5206\u6790", "read", "analyze", "view", "list", "\u5217\u51FA"],
|
|
513
|
+
dangerousActions: ["\u5220\u9664", "remove", "delete", "rm ", "\u683C\u5F0F\u5316", "format"],
|
|
514
|
+
reason: "\u539F\u59CB\u610F\u56FE\u4E3A\u8BFB\u53D6/\u5206\u6790\uFF0C\u4F46\u6267\u884C\u4E86\u5220\u9664\u64CD\u4F5C"
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
intentKeywords: ["\u67E5\u8BE2", "\u641C\u7D22", "search", "query", "find"],
|
|
518
|
+
dangerousActions: ["\u4FEE\u6539", "update", "write", "\u5199\u5165", "alter"],
|
|
519
|
+
reason: "\u539F\u59CB\u610F\u56FE\u4E3A\u67E5\u8BE2/\u641C\u7D22\uFF0C\u4F46\u6267\u884C\u4E86\u4FEE\u6539\u64CD\u4F5C"
|
|
520
|
+
}
|
|
177
521
|
];
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
// src/config.ts
|
|
182
|
-
function getLLMConfig() {
|
|
183
|
-
return llmConfig;
|
|
184
|
-
}
|
|
185
|
-
function isLLMAvailable() {
|
|
186
|
-
return llmConfig.apiKey.length > 0;
|
|
187
|
-
}
|
|
188
|
-
var llmConfig;
|
|
189
|
-
var init_config = __esm({
|
|
190
|
-
"src/config.ts"() {
|
|
191
|
-
"use strict";
|
|
192
|
-
init_esm_shims();
|
|
193
|
-
llmConfig = {
|
|
194
|
-
provider: "openai",
|
|
195
|
-
baseUrl: process.env.AGENT_MOSS_LLM_BASE_URL || "https://api.openai.com/v1",
|
|
196
|
-
apiKey: process.env.AGENT_MOSS_LLM_API_KEY || "",
|
|
197
|
-
model: process.env.AGENT_MOSS_LLM_MODEL || "gpt-4o",
|
|
198
|
-
temperature: parseFloat(process.env.AGENT_MOSS_LLM_TEMPERATURE || "0.1"),
|
|
199
|
-
maxTokens: parseInt(process.env.AGENT_MOSS_LLM_MAX_TOKENS || "4096", 10)
|
|
200
|
-
};
|
|
201
522
|
}
|
|
202
523
|
});
|
|
203
524
|
|
|
@@ -205,23 +526,71 @@ var init_config = __esm({
|
|
|
205
526
|
import { readFileSync, readdirSync, existsSync } from "fs";
|
|
206
527
|
import { resolve, dirname } from "path";
|
|
207
528
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
208
|
-
function
|
|
529
|
+
function loadAllSkills() {
|
|
209
530
|
if (cachedSkills) return cachedSkills;
|
|
531
|
+
cachedSkills = {};
|
|
210
532
|
if (!existsSync(SKILLS_DIR)) {
|
|
211
533
|
console.warn("[AgentMoss] Skills directory not found:", SKILLS_DIR);
|
|
212
|
-
cachedSkills = [];
|
|
213
534
|
return cachedSkills;
|
|
214
535
|
}
|
|
215
536
|
const files = readdirSync(SKILLS_DIR).filter((f) => f.endsWith(".md"));
|
|
216
|
-
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
});
|
|
221
|
-
console.log(`[AgentMoss] Loaded ${cachedSkills.length} skills`);
|
|
537
|
+
for (const f of files) {
|
|
538
|
+
const name = f.replace(".md", "");
|
|
539
|
+
cachedSkills[name] = readFileSync(resolve(SKILLS_DIR, f), "utf-8");
|
|
540
|
+
}
|
|
541
|
+
console.log(`[AgentMoss] Loaded ${Object.keys(cachedSkills).length} skills`);
|
|
222
542
|
return cachedSkills;
|
|
223
543
|
}
|
|
224
|
-
|
|
544
|
+
function matchSkills(actionType, actionDetail, reason, maxSkills = 3) {
|
|
545
|
+
const skills = loadAllSkills();
|
|
546
|
+
const combined = `${actionType} ${actionDetail} ${reason}`.toLowerCase();
|
|
547
|
+
const actionDetailLower = actionDetail.toLowerCase();
|
|
548
|
+
const actionTypeLower = actionType.toLowerCase();
|
|
549
|
+
const reasonLower = reason.toLowerCase();
|
|
550
|
+
const scores = {};
|
|
551
|
+
for (const [skillName, keywords] of Object.entries(SKILL_KEYWORD_MAP)) {
|
|
552
|
+
let score = 0;
|
|
553
|
+
for (const kw of keywords) {
|
|
554
|
+
if (combined.includes(kw)) {
|
|
555
|
+
if (kw === actionTypeLower) score += 2;
|
|
556
|
+
else if (actionDetailLower.includes(kw)) score += 1.5;
|
|
557
|
+
else if (reasonLower.includes(kw)) score += 1;
|
|
558
|
+
else score += 0.5;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
if (score > 0) scores[skillName] = score;
|
|
562
|
+
}
|
|
563
|
+
const sorted = Object.entries(scores).sort((a, b) => b[1] - a[1]);
|
|
564
|
+
const top = sorted.slice(0, maxSkills);
|
|
565
|
+
const maxScore = top[0]?.[1] || 1;
|
|
566
|
+
const results = [];
|
|
567
|
+
for (const [name, score] of top) {
|
|
568
|
+
const content = skills[name];
|
|
569
|
+
if (content) {
|
|
570
|
+
results.push({
|
|
571
|
+
skillName: name,
|
|
572
|
+
relevance: Math.min(score / maxScore, 1),
|
|
573
|
+
skillContent: content
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
if (results.length === 0) {
|
|
578
|
+
const fallback = skills["general_tool_risk_guard"];
|
|
579
|
+
if (fallback) {
|
|
580
|
+
results.push({ skillName: "general_tool_risk_guard", relevance: 0.3, skillContent: fallback });
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
return results;
|
|
584
|
+
}
|
|
585
|
+
function formatSkillsForPrompt(skills) {
|
|
586
|
+
if (skills.length === 0) return "(\u672A\u5339\u914D\u5230\u7279\u5B9A\u5B89\u5168\u68C0\u6D4B\u89C4\u5219)";
|
|
587
|
+
return skills.map(
|
|
588
|
+
(s) => `### \u5B89\u5168\u68C0\u6D4B\u89C4\u5219: ${s.skillName} (\u76F8\u5173\u5EA6: ${Math.round(s.relevance * 100)}%)
|
|
589
|
+
|
|
590
|
+
${s.skillContent}`
|
|
591
|
+
).join("\n\n---\n\n");
|
|
592
|
+
}
|
|
593
|
+
var __filename2, __dirname2, SKILLS_DIR, SKILL_KEYWORD_MAP, cachedSkills;
|
|
225
594
|
var init_skill_loader = __esm({
|
|
226
595
|
"src/engine/skill-loader.ts"() {
|
|
227
596
|
"use strict";
|
|
@@ -229,6 +598,20 @@ var init_skill_loader = __esm({
|
|
|
229
598
|
__filename2 = fileURLToPath2(import.meta.url);
|
|
230
599
|
__dirname2 = dirname(__filename2);
|
|
231
600
|
SKILLS_DIR = resolve(__dirname2, "../../agent_moss/skills");
|
|
601
|
+
SKILL_KEYWORD_MAP = {
|
|
602
|
+
"file_access_guard": ["file", "\u6587\u4EF6", "path", "\u8DEF\u5F84", "read", "write", "cat", "open", "access", "\u8BBF\u95EE", "\u8BFB\u5199", "copy", "cp", "mv", "move"],
|
|
603
|
+
"script_execution_guard": ["command", "shell", "bash", "exec", "execute", "\u6267\u884C", "\u547D\u4EE4", "script", "\u811A\u672C", "run", "\u8FD0\u884C", "subprocess", "system", "eval", "compile", "\u5B89\u88C5", "install", "pip", "npm"],
|
|
604
|
+
"data_exfiltration_guard": ["upload", "\u4E0A\u4F20", "send", "\u53D1\u9001", "transfer", "\u4F20\u8F93", "export", "\u5BFC\u51FA", "download", "\u4E0B\u8F7D", "post", "http", "request", "fetch", "curl", "wget", "\u5916\u4F20", "\u6CC4\u9732", "exfil", "network", "\u7F51\u7EDC"],
|
|
605
|
+
"browser_web_access_guard": ["browser", "\u6D4F\u89C8\u5668", "web", "\u7F51\u9875", "url", "navigate", "\u6D4F\u89C8", "scrape", "crawl", "\u722C\u53D6", "click", "screenshot"],
|
|
606
|
+
"email_operation_guard": ["email", "\u90AE\u4EF6", "mail", "smtp", "send_email", "\u53D1\u9001\u90AE\u4EF6", "inbox", "\u6536\u4EF6\u7BB1", "\u9644\u4EF6", "attachment"],
|
|
607
|
+
"general_tool_risk_guard": ["tool", "\u5DE5\u5177", "api", "invoke", "\u8C03\u7528", "function", "\u51FD\u6570"],
|
|
608
|
+
"lateral_movement_guard": ["ssh", "scp", "ftp", "sftp", "rdp", "vnc", "telnet", "\u5185\u7F51", "\u6A2A\u5411", "tunnel", "\u4EE3\u7406", "proxy", "port", "\u7AEF\u53E3", "scan", "\u626B\u63CF", "ping", "traceroute", "nslookup"],
|
|
609
|
+
"persistence_backdoor_guard": ["cron", "crontab", "\u5B9A\u65F6", "scheduled", "startup", "\u542F\u52A8\u9879", "service", "\u670D\u52A1", "daemon", "\u5B88\u62A4", "registry", "\u6CE8\u518C\u8868", "backdoor", "\u540E\u95E8", "persist", "\u6301\u4E45\u5316", "authorized_keys"],
|
|
610
|
+
"resource_exhaustion_guard": ["fork", "bomb", "\u70B8\u5F39", "memory", "\u5185\u5B58", "disk", "\u78C1\u76D8", "cpu", "process", "\u8FDB\u7A0B", "dd", "while true", "\u65E0\u9650\u5FAA\u73AF", "\u8017\u5C3D", "exhaust", "\u8D44\u6E90"],
|
|
611
|
+
"skill_installation_guard": ["skill", "plugin", "\u63D2\u4EF6", "extension", "\u6269\u5C55", "install skill", "\u5B89\u88C5\u63D2\u4EF6", "\u5B89\u88C5\u6280\u80FD", "marketplace", "\u5546\u5E97"],
|
|
612
|
+
"supply_chain_guard": ["dependency", "\u4F9D\u8D56", "package", "\u5305", "npm install", "pip install", "requirements", "import", "library", "\u5E93", "registry", "\u4ED3\u5E93", "supply", "\u4F9B\u5E94\u94FE", "typosquatting"],
|
|
613
|
+
"intent_deviation_guard": ["rm", "del", "delete", "remove", "\u5220\u9664", "format", "\u683C\u5F0F\u5316", "drop", "truncate", "\u6E05\u7A7A", "wipe", "\u64E6\u9664", "\u91CD\u7F6E", "reset", "sudo", "root", "admin", "\u7BA1\u7406\u5458", "privilege", "\u63D0\u6743"]
|
|
614
|
+
};
|
|
232
615
|
cachedSkills = null;
|
|
233
616
|
}
|
|
234
617
|
});
|
|
@@ -267,6 +650,199 @@ var init_template_loader = __esm({
|
|
|
267
650
|
}
|
|
268
651
|
});
|
|
269
652
|
|
|
653
|
+
// src/engine/script-analyzer.ts
|
|
654
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
655
|
+
import { resolve as resolve3, join } from "path";
|
|
656
|
+
function emptyResult() {
|
|
657
|
+
return { scriptPath: "", scriptContent: "", matchedKeywords: [], lineCount: 0, isSuspicious: false, error: "", riskLevel: "", riskReason: "" };
|
|
658
|
+
}
|
|
659
|
+
function extractScriptPath(command) {
|
|
660
|
+
for (const pattern of SCRIPT_EXEC_PATTERNS) {
|
|
661
|
+
const m = command.match(pattern);
|
|
662
|
+
if (m) return m[1];
|
|
663
|
+
}
|
|
664
|
+
return null;
|
|
665
|
+
}
|
|
666
|
+
function scanScriptForKeywords(content) {
|
|
667
|
+
const matched = [];
|
|
668
|
+
for (const { name, pattern } of SUSPICIOUS_PATTERNS) {
|
|
669
|
+
if (pattern.test(content)) matched.push(name);
|
|
670
|
+
}
|
|
671
|
+
return matched;
|
|
672
|
+
}
|
|
673
|
+
function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
|
|
674
|
+
if (matchedKeywords.length === 0) return { level: "", reason: "" };
|
|
675
|
+
const keywordSet = new Set(matchedKeywords);
|
|
676
|
+
for (const rule of KEYWORD_COMBINATION_RISKS) {
|
|
677
|
+
const hitGroup1 = [...rule.group1].some((k) => keywordSet.has(k));
|
|
678
|
+
const hitGroup2 = rule.group2.size === 0 ? true : [...rule.group2].some((k) => keywordSet.has(k));
|
|
679
|
+
if (hitGroup1 && hitGroup2) return { level: rule.level, reason: rule.reason };
|
|
680
|
+
}
|
|
681
|
+
const networkPostKeywords = /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]);
|
|
682
|
+
const hasNetworkPost = [...networkPostKeywords].some((k) => keywordSet.has(k));
|
|
683
|
+
if (hasNetworkPost) {
|
|
684
|
+
const envPatterns = ["$env", "$(env)", "$USER", "$(hostname)", "$(pwd)", "$HOME"];
|
|
685
|
+
const contentLower = scriptContent.toLowerCase();
|
|
686
|
+
for (const p of envPatterns) {
|
|
687
|
+
if (contentLower.includes(p.toLowerCase())) {
|
|
688
|
+
return {
|
|
689
|
+
level: "critical",
|
|
690
|
+
reason: "\u68C0\u6D4B\u5230\u7F51\u7EDC\u6570\u636E\u53D1\u9001 + \u7CFB\u7EDF\u73AF\u5883\u53D8\u91CF\u6536\u96C6\u7EC4\u5408\uFF0C\u6784\u6210 Critical \u7EA7\u522B\u6570\u636E\u5916\u4F20\u98CE\u9669\u3002\u811A\u672C\u53EF\u80FD\u5C06\u7528\u6237\u73AF\u5883\u53D8\u91CF\uFF08\u53EF\u80FD\u5305\u542B API Key\u3001\u5BC6\u94A5\u7B49\u654F\u611F\u4FE1\u606F\uFF09\u53D1\u9001\u5230\u8FDC\u7A0B\u670D\u52A1\u5668\u3002"
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
let bestLevel = "";
|
|
696
|
+
let bestReason = "";
|
|
697
|
+
for (const kw of matchedKeywords) {
|
|
698
|
+
const risk = SINGLE_KEYWORD_RISKS[kw];
|
|
699
|
+
if (risk && (LEVEL_ORDER[risk.level] || 0) > (LEVEL_ORDER[bestLevel] || 0)) {
|
|
700
|
+
bestLevel = risk.level;
|
|
701
|
+
bestReason = risk.reason;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
return { level: bestLevel, reason: bestReason };
|
|
705
|
+
}
|
|
706
|
+
function readScriptContent(scriptPath, maxLines = 500) {
|
|
707
|
+
try {
|
|
708
|
+
const expandedPath = scriptPath.startsWith("~") ? join(process.env.HOME || "/home/user", scriptPath.slice(2)) : resolve3(scriptPath);
|
|
709
|
+
const raw = readFileSync3(expandedPath, "utf-8");
|
|
710
|
+
const lines = raw.split("\n").slice(0, maxLines);
|
|
711
|
+
return { content: lines.join("\n"), lineCount: lines.length, error: "" };
|
|
712
|
+
} catch (e) {
|
|
713
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
714
|
+
return { content: "", lineCount: 0, error: `\u8BFB\u53D6\u811A\u672C\u5931\u8D25: ${msg}` };
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
function analyzeScriptContent(actionType, actionDetail, maxLines = 500) {
|
|
718
|
+
if (actionType.toLowerCase() !== "bash") return emptyResult();
|
|
719
|
+
const scriptPath = extractScriptPath(actionDetail);
|
|
720
|
+
if (!scriptPath) return emptyResult();
|
|
721
|
+
const { content, lineCount, error } = readScriptContent(scriptPath, maxLines);
|
|
722
|
+
if (error) return { ...emptyResult(), scriptPath, error };
|
|
723
|
+
const matchedKeywords = scanScriptForKeywords(content);
|
|
724
|
+
const isSuspicious = matchedKeywords.length > 0;
|
|
725
|
+
let riskLevel = "";
|
|
726
|
+
let riskReason = "";
|
|
727
|
+
if (isSuspicious) {
|
|
728
|
+
const risk = assessKeywordCombinationRisk(matchedKeywords, content);
|
|
729
|
+
riskLevel = risk.level;
|
|
730
|
+
riskReason = risk.reason;
|
|
731
|
+
}
|
|
732
|
+
return { scriptPath, scriptContent: content, matchedKeywords, lineCount, isSuspicious, error: "", riskLevel, riskReason };
|
|
733
|
+
}
|
|
734
|
+
function formatScriptAnalysisForPrompt(result) {
|
|
735
|
+
if (!result.scriptPath) return "";
|
|
736
|
+
if (result.error) return `\u811A\u672C\u5185\u5BB9\u5206\u6790\u5931\u8D25: ${result.error}`;
|
|
737
|
+
if (!result.isSuspicious) return "";
|
|
738
|
+
const keywordsStr = result.matchedKeywords.join(", ");
|
|
739
|
+
let riskHint = "";
|
|
740
|
+
if (result.riskLevel && result.riskReason) {
|
|
741
|
+
riskHint = `
|
|
742
|
+
|
|
743
|
+
\u98CE\u9669\u8BC4\u4F30: ${result.riskLevel.toUpperCase()} \u7EA7\u522B
|
|
744
|
+
\u98CE\u9669\u539F\u56E0: ${result.riskReason}
|
|
745
|
+
\u5EFA\u8BAE: \u6B64\u811A\u672C\u5E94\u88AB\u62D2\u7EDD\uFF08Deny\uFF09\uFF0C\u9664\u975E\u6709\u660E\u786E\u7684\u4E1A\u52A1\u9700\u6C42\u4E14\u5DF2\u786E\u8BA4\u76EE\u6807\u670D\u52A1\u5668\u53EF\u4FE1\u3002`;
|
|
746
|
+
}
|
|
747
|
+
return `\u68C0\u6D4B\u5230\u811A\u672C\u6267\u884C\uFF0C\u4E14\u811A\u672C\u5185\u5BB9\u5305\u542B\u53EF\u7591\u5173\u952E\u8BCD:
|
|
748
|
+
\u811A\u672C\u8DEF\u5F84: ${result.scriptPath}
|
|
749
|
+
\u53EF\u7591\u5173\u952E\u8BCD: ${keywordsStr}
|
|
750
|
+
\u811A\u672C\u884C\u6570: ${result.lineCount}${riskHint}
|
|
751
|
+
|
|
752
|
+
### \u811A\u672C\u5185\u5BB9 ###
|
|
753
|
+
\`\`\`
|
|
754
|
+
${result.scriptContent}
|
|
755
|
+
\`\`\`
|
|
756
|
+
`;
|
|
757
|
+
}
|
|
758
|
+
var SUSPICIOUS_PATTERNS, SCRIPT_EXEC_PATTERNS, KEYWORD_COMBINATION_RISKS, SINGLE_KEYWORD_RISKS, LEVEL_ORDER;
|
|
759
|
+
var init_script_analyzer = __esm({
|
|
760
|
+
"src/engine/script-analyzer.ts"() {
|
|
761
|
+
"use strict";
|
|
762
|
+
init_esm_shims();
|
|
763
|
+
SUSPICIOUS_PATTERNS = [
|
|
764
|
+
// Network exfiltration (curl/wget POST in scripts is almost never legitimate)
|
|
765
|
+
{ name: "curl POST", pattern: /curl\s+(.*\s)?-X\s+POST/i },
|
|
766
|
+
{ name: "curl --data", pattern: /curl\s+(.*\s)?--data/i },
|
|
767
|
+
{ name: "curl -d", pattern: /curl\s+(.*\s)?-d\s/i },
|
|
768
|
+
{ name: "wget --post-data", pattern: /wget\s+(.*\s)?--post-data/i },
|
|
769
|
+
{ name: "wget --post-file", pattern: /wget\s+(.*\s)?--post-file/i },
|
|
770
|
+
// Reverse shell
|
|
771
|
+
{ name: "nc -e", pattern: /\bnc\s+.*-e\b/ },
|
|
772
|
+
{ name: "ncat -e", pattern: /\bncat\s+.*-e\b/ },
|
|
773
|
+
{ name: "/dev/tcp", pattern: /\/dev\/tcp\// },
|
|
774
|
+
{ name: "/dev/udp", pattern: /\/dev\/udp\// },
|
|
775
|
+
// Base64 obfuscation
|
|
776
|
+
{ name: "$(base64", pattern: /\$\(\s*base64/ },
|
|
777
|
+
{ name: "`base64", pattern: /`\s*base64/ },
|
|
778
|
+
{ name: "base64 decode pipe", pattern: /base64\s+(-d|--decode)\s*\|/ },
|
|
779
|
+
// Sensitive file access
|
|
780
|
+
{ name: "/etc/shadow", pattern: /\/etc\/shadow/ },
|
|
781
|
+
{ name: "id_rsa", pattern: /\bid_rsa\b/ },
|
|
782
|
+
{ name: "id_ed25519", pattern: /\bid_ed25519\b/ },
|
|
783
|
+
// Persistence backdoor
|
|
784
|
+
{ name: "crontab install", pattern: /crontab\s+-/ },
|
|
785
|
+
{ name: "authorized_keys", pattern: /authorized_keys/ },
|
|
786
|
+
{ name: "ssh no host key check", pattern: /ssh\s+.*StrictHostKeyChecking\s*=\s*no/ }
|
|
787
|
+
];
|
|
788
|
+
SCRIPT_EXEC_PATTERNS = [
|
|
789
|
+
/bash\s+(\S+\.sh)/,
|
|
790
|
+
/sh\s+(\S+\.sh)/,
|
|
791
|
+
/\.\/(\S+\.sh)/,
|
|
792
|
+
/source\s+(\S+\.sh)/,
|
|
793
|
+
/\.\s+(\S+\.sh)/,
|
|
794
|
+
/zsh\s+(\S+\.sh)/
|
|
795
|
+
];
|
|
796
|
+
KEYWORD_COMBINATION_RISKS = [
|
|
797
|
+
{
|
|
798
|
+
group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
|
|
799
|
+
group2: /* @__PURE__ */ new Set(["$(base64", "`base64", "base64 decode pipe"]),
|
|
800
|
+
level: "critical",
|
|
801
|
+
reason: "\u68C0\u6D4B\u5230\u7F51\u7EDC\u6570\u636E\u53D1\u9001 + Base64\u7F16\u7801\u7EC4\u5408\uFF0C\u6784\u6210 Critical \u7EA7\u522B\u6570\u636E\u5916\u4F20\u98CE\u9669\u3002\u653B\u51FB\u8005\u53EF\u80FD\u5C06\u654F\u611F\u6570\u636E\u7F16\u7801\u540E\u5916\u4F20\u3002"
|
|
802
|
+
},
|
|
803
|
+
{
|
|
804
|
+
group1: /* @__PURE__ */ new Set(["curl POST", "curl --data", "curl -d", "wget --post-data", "wget --post-file"]),
|
|
805
|
+
group2: /* @__PURE__ */ new Set(["/etc/shadow", "id_rsa", "id_ed25519"]),
|
|
806
|
+
level: "critical",
|
|
807
|
+
reason: "\u68C0\u6D4B\u5230\u7F51\u7EDC\u6570\u636E\u53D1\u9001 + \u654F\u611F\u6587\u4EF6\u8BBF\u95EE\u7EC4\u5408\uFF0C\u6784\u6210 Critical \u7EA7\u522B\u51ED\u8BC1\u5916\u4F20\u98CE\u9669\u3002"
|
|
808
|
+
},
|
|
809
|
+
{
|
|
810
|
+
group1: /* @__PURE__ */ new Set(["nc -e", "ncat -e", "/dev/tcp", "/dev/udp"]),
|
|
811
|
+
group2: /* @__PURE__ */ new Set(),
|
|
812
|
+
level: "high",
|
|
813
|
+
reason: "\u68C0\u6D4B\u5230\u53CD\u5F39 Shell \u7279\u5F81\uFF0C\u653B\u51FB\u8005\u53EF\u80FD\u5EFA\u7ACB\u8FDC\u7A0B\u63A7\u5236\u901A\u9053\u3002"
|
|
814
|
+
},
|
|
815
|
+
{
|
|
816
|
+
group1: /* @__PURE__ */ new Set(["crontab install", "authorized_keys", "ssh no host key check"]),
|
|
817
|
+
group2: /* @__PURE__ */ new Set(),
|
|
818
|
+
level: "high",
|
|
819
|
+
reason: "\u68C0\u6D4B\u5230\u6301\u4E45\u5316\u540E\u95E8\u7279\u5F81\uFF0C\u653B\u51FB\u8005\u53EF\u80FD\u5728\u7CFB\u7EDF\u4E2D\u690D\u5165\u540E\u95E8\u3002"
|
|
820
|
+
}
|
|
821
|
+
];
|
|
822
|
+
SINGLE_KEYWORD_RISKS = {
|
|
823
|
+
"curl POST": { level: "high", reason: "\u68C0\u6D4B\u5230 curl POST \u8BF7\u6C42\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
|
|
824
|
+
"curl --data": { level: "high", reason: "\u68C0\u6D4B\u5230 curl --data \u53C2\u6570\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
|
|
825
|
+
"curl -d": { level: "high", reason: "\u68C0\u6D4B\u5230 curl -d \u53C2\u6570\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
|
|
826
|
+
"wget --post-data": { level: "high", reason: "\u68C0\u6D4B\u5230 wget POST \u8BF7\u6C42\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6570\u636E\u3002" },
|
|
827
|
+
"wget --post-file": { level: "high", reason: "\u68C0\u6D4B\u5230 wget \u6587\u4EF6\u4E0A\u4F20\uFF0C\u53EF\u80FD\u5411\u5916\u90E8\u670D\u52A1\u5668\u53D1\u9001\u6587\u4EF6\u3002" },
|
|
828
|
+
"nc -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 nc -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
|
|
829
|
+
"ncat -e": { level: "critical", reason: "\u68C0\u6D4B\u5230 ncat -e \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
|
|
830
|
+
"/dev/tcp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/tcp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
|
|
831
|
+
"/dev/udp": { level: "critical", reason: "\u68C0\u6D4B\u5230 /dev/udp \u53CD\u5F39 Shell \u7279\u5F81\u3002" },
|
|
832
|
+
"$(base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
|
|
833
|
+
"`base64": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u7F16\u7801\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
|
|
834
|
+
"base64 decode pipe": { level: "high", reason: "\u68C0\u6D4B\u5230 Base64 \u89E3\u7801\u540E\u7BA1\u9053\u6267\u884C\uFF0C\u53EF\u80FD\u662F\u6DF7\u6DC6\u9003\u9038\u3002" },
|
|
835
|
+
"/etc/shadow": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE\u7CFB\u7EDF\u5BC6\u7801\u6587\u4EF6\u3002" },
|
|
836
|
+
"id_rsa": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
|
|
837
|
+
"id_ed25519": { level: "critical", reason: "\u68C0\u6D4B\u5230\u8BBF\u95EE SSH \u79C1\u94A5\u3002" },
|
|
838
|
+
"crontab install": { level: "high", reason: "\u68C0\u6D4B\u5230\u4FEE\u6539\u5B9A\u65F6\u4EFB\u52A1\uFF0C\u53EF\u80FD\u662F\u6301\u4E45\u5316\u540E\u95E8\u3002" },
|
|
839
|
+
"authorized_keys": { level: "high", reason: "\u68C0\u6D4B\u5230\u4FEE\u6539 SSH \u6388\u6743\u5BC6\u94A5\uFF0C\u53EF\u80FD\u662F\u6301\u4E45\u5316\u540E\u95E8\u3002" },
|
|
840
|
+
"ssh no host key check": { level: "high", reason: "\u68C0\u6D4B\u5230 SSH \u8DF3\u8FC7\u4E3B\u673A\u5BC6\u94A5\u9A8C\u8BC1\uFF0C\u53EF\u80FD\u662F\u6A2A\u5411\u79FB\u52A8\u3002" }
|
|
841
|
+
};
|
|
842
|
+
LEVEL_ORDER = { low: 1, medium: 2, high: 3, critical: 4 };
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
|
|
270
846
|
// src/engine/llm-analyzer.ts
|
|
271
847
|
import OpenAI from "openai";
|
|
272
848
|
async function runLLMAnalysis(input, previous) {
|
|
@@ -283,15 +859,17 @@ async function runLLMAnalysis(input, previous) {
|
|
|
283
859
|
};
|
|
284
860
|
}
|
|
285
861
|
const config = getLLMConfig();
|
|
286
|
-
const
|
|
862
|
+
const rawDetail = input.aNext.actionDetail.replace(/^terminal-\w+:\s*command=([^,]+).*/, "$1").trim();
|
|
863
|
+
const cacheKey = `${input.aNext.actionType}:${rawDetail || input.aNext.actionDetail}`;
|
|
287
864
|
const cached = llmCache.get(cacheKey);
|
|
288
865
|
if (cached) {
|
|
289
866
|
console.log("[AgentMoss] LLM cache hit");
|
|
290
867
|
return cached;
|
|
291
868
|
}
|
|
292
|
-
const
|
|
869
|
+
const matchedSkills = matchSkills(input.aNext.actionType, input.aNext.actionDetail, input.reason);
|
|
293
870
|
const templates = loadTemplates();
|
|
294
|
-
const
|
|
871
|
+
const scriptAnalysis = analyzeScriptContent(input.aNext.actionType, input.aNext.actionDetail);
|
|
872
|
+
const prompt = buildPrompt(input, previous, matchedSkills, templates, scriptAnalysis);
|
|
295
873
|
const result = await analyzeWithLLM(prompt, config);
|
|
296
874
|
if (llmCache.size >= LLM_CACHE_MAX) {
|
|
297
875
|
const firstKey = llmCache.keys().next().value;
|
|
@@ -300,11 +878,13 @@ async function runLLMAnalysis(input, previous) {
|
|
|
300
878
|
llmCache.set(cacheKey, result);
|
|
301
879
|
return result;
|
|
302
880
|
}
|
|
303
|
-
function buildPrompt(input, previous,
|
|
304
|
-
const
|
|
305
|
-
const historyStr = input.actionHistory.map((h, i) => `${i + 1}.
|
|
881
|
+
function buildPrompt(input, previous, matchedSkills, templates, scriptAnalysis) {
|
|
882
|
+
const skillsText = formatSkillsForPrompt(matchedSkills);
|
|
883
|
+
const historyStr = input.actionHistory.map((h, i) => `${i + 1}. ${h.actionType}: ${h.actionDetail}`).join("\n") || "(\u65E0\u5386\u53F2\u64CD\u4F5C)";
|
|
306
884
|
const templateName = previous.heuristic || previous.logic ? "prompt2" : "prompt1";
|
|
307
885
|
const baseTemplate = templates[templateName] || templates["prompt1"] || "";
|
|
886
|
+
const scriptAnalysisText = scriptAnalysis ? formatScriptAnalysisForPrompt(scriptAnalysis) : "";
|
|
887
|
+
const hintsText = formatHints(previous);
|
|
308
888
|
return `${baseTemplate}
|
|
309
889
|
|
|
310
890
|
## \u5F53\u524D\u64CD\u4F5C\u4E0A\u4E0B\u6587
|
|
@@ -321,7 +901,13 @@ ${historyStr}
|
|
|
321
901
|
\u8BE6\u60C5: ${input.aNext.actionDetail}
|
|
322
902
|
|
|
323
903
|
## \u53EF\u7528\u5B89\u5168 Skill
|
|
324
|
-
${
|
|
904
|
+
${skillsText}
|
|
905
|
+
|
|
906
|
+
## \u524D\u7F6E\u68C0\u6D4B\u7ED3\u679C
|
|
907
|
+
${hintsText}
|
|
908
|
+
${scriptAnalysisText ? `
|
|
909
|
+
## \u811A\u672C\u5185\u5BB9\u5206\u6790
|
|
910
|
+
${scriptAnalysisText}` : ""}
|
|
325
911
|
|
|
326
912
|
\u8BF7\u6839\u636E\u4EE5\u4E0A\u4FE1\u606F\uFF0C\u5BF9\u5373\u5C06\u6267\u884C\u7684\u64CD\u4F5C\u8FDB\u884C\u5B89\u5168\u5224\u65AD\u3002\u4F60\u5FC5\u987B\u8F93\u51FA\u4E25\u683C\u7684 JSON \u683C\u5F0F\uFF0C
|
|
327
913
|
\u4E0D\u8981\u5305\u542B\u4EFB\u4F55\u5176\u4ED6\u6587\u5B57\u3001\u6CE8\u91CA\u6216 markdown \u6807\u8BB0\u3002
|
|
@@ -329,52 +915,92 @@ ${skillContext}
|
|
|
329
915
|
\u8F93\u51FA\u683C\u5F0F:
|
|
330
916
|
{"allowed": true/false, "reason": "\u5224\u65AD\u7406\u7531", "riskLevel": "low|medium|high|critical", "riskType": "\u98CE\u9669\u7C7B\u578B", "confidence": 0-100}`;
|
|
331
917
|
}
|
|
918
|
+
function formatHints(previous) {
|
|
919
|
+
const hints = [];
|
|
920
|
+
if (previous.heuristic) {
|
|
921
|
+
hints.push(
|
|
922
|
+
`\u542F\u53D1\u5F0F\u68C0\u6D4B\u53D1\u73B0\u98CE\u9669 [\u98CE\u9669\u7B49\u7EA7: ${previous.heuristic.riskLevel}]:
|
|
923
|
+
\u7C7B\u578B: ${previous.heuristic.riskType}
|
|
924
|
+
\u539F\u56E0: ${previous.heuristic.reason}`
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
if (previous.logic) {
|
|
928
|
+
hints.push(
|
|
929
|
+
`\u903B\u8F91\u89C4\u5219\u68C0\u6D4B\u53D1\u73B0\u98CE\u9669 [\u98CE\u9669\u7B49\u7EA7: ${previous.logic.riskLevel}]:
|
|
930
|
+
\u7C7B\u578B: ${previous.logic.riskType}
|
|
931
|
+
\u539F\u56E0: ${previous.logic.reason}`
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
return hints.length > 0 ? hints.join("\n\n") : "\u542F\u53D1\u5F0F\u68C0\u6D4B\u548C\u903B\u8F91\u89C4\u5219\u68C0\u6D4B\u5747\u672A\u53D1\u73B0\u98CE\u9669\u3002";
|
|
935
|
+
}
|
|
332
936
|
async function analyzeWithLLM(prompt, config) {
|
|
333
937
|
console.log(`[AgentMoss] LLM analyzing with model: ${config.model}, url: ${config.baseUrl}`);
|
|
938
|
+
const provider = detectProvider(config.baseUrl, config.provider);
|
|
939
|
+
const extraHeaders = getProviderHeaders(provider);
|
|
940
|
+
if (Object.keys(extraHeaders).length > 0) {
|
|
941
|
+
console.log(`[AgentMoss] Provider detected: ${provider}, injecting headers: ${Object.keys(extraHeaders).join(", ")}`);
|
|
942
|
+
}
|
|
334
943
|
const client = new OpenAI({
|
|
335
944
|
baseURL: config.baseUrl,
|
|
336
945
|
apiKey: config.apiKey,
|
|
337
|
-
timeout:
|
|
946
|
+
timeout: (config.timeout || 300) * 1e3,
|
|
947
|
+
defaultHeaders: Object.keys(extraHeaders).length > 0 ? extraHeaders : void 0
|
|
338
948
|
});
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
jsonStr =
|
|
949
|
+
const maxRetries = parseInt(process.env.AGENT_MOSS_LLM_RETRIES || "2", 10);
|
|
950
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
951
|
+
try {
|
|
952
|
+
const response = await client.chat.completions.create({
|
|
953
|
+
model: config.model,
|
|
954
|
+
temperature: config.temperature || 0.1,
|
|
955
|
+
max_tokens: config.maxTokens || 1024,
|
|
956
|
+
messages: [
|
|
957
|
+
{ role: "system", content: "\u4F60\u662F\u4E00\u4E2A AI Agent \u5B89\u5168\u5BA1\u8BA1\u4E13\u5BB6\u3002\u4F60\u7684\u4EFB\u52A1\u662F\u5206\u6790\u5373\u5C06\u6267\u884C\u7684\u547D\u4EE4\u662F\u5426\u5B89\u5168\u3002\u4F60\u5FC5\u987B\u53EA\u8F93\u51FA\u7EAF JSON\uFF0C\u4E0D\u8981\u5305\u542B\u4EFB\u4F55 markdown \u6807\u8BB0\u3001\u4EE3\u7801\u5757\u6216\u5176\u4ED6\u6587\u5B57\u3002" },
|
|
958
|
+
{ role: "user", content: prompt }
|
|
959
|
+
]
|
|
960
|
+
});
|
|
961
|
+
const content = response.choices[0]?.message?.content || "{}";
|
|
962
|
+
console.log(`[AgentMoss] LLM raw response (attempt ${attempt + 1}/${maxRetries + 1}): ${content.substring(0, 300)}`);
|
|
963
|
+
let jsonStr = content.trim();
|
|
964
|
+
if (jsonStr.startsWith("```")) {
|
|
965
|
+
jsonStr = jsonStr.replace(/^```[\w]*\n?/, "").replace(/\n?```$/, "");
|
|
966
|
+
}
|
|
967
|
+
const parsed = JSON.parse(jsonStr);
|
|
968
|
+
return {
|
|
969
|
+
allowed: parsed.allowed !== false,
|
|
970
|
+
reason: parsed.reason || "LLM \u5206\u6790\u5B8C\u6210",
|
|
971
|
+
riskLevel: ["low", "medium", "high", "critical"].includes(parsed.riskLevel) ? parsed.riskLevel : "low",
|
|
972
|
+
riskType: parsed.riskType || "",
|
|
973
|
+
confidence: typeof parsed.confidence === "number" ? Math.min(100, Math.max(0, parsed.confidence)) : 70,
|
|
974
|
+
source: "llm",
|
|
975
|
+
violatedLayers: ["3"]
|
|
976
|
+
};
|
|
977
|
+
} catch (err) {
|
|
978
|
+
if (attempt < maxRetries) {
|
|
979
|
+
console.error(`[AgentMoss] LLM call failed (attempt ${attempt + 1}/${maxRetries + 1}), retrying in 1s:`, err.message);
|
|
980
|
+
await new Promise((resolve4) => setTimeout(resolve4, 1e3));
|
|
981
|
+
} else {
|
|
982
|
+
console.error("[AgentMoss] LLM call failed after all retries:", err.message);
|
|
983
|
+
return {
|
|
984
|
+
allowed: false,
|
|
985
|
+
reason: `LLM \u5206\u6790\u5931\u8D25\uFF0C\u56E0\u65E0\u6CD5\u5B8C\u6210\u6DF1\u5EA6\u8BED\u4E49\u5206\u6790\uFF0C\u6309 fail-closed \u539F\u5219\u62D2\u7EDD (${maxRetries + 1} \u6B21\u5C1D\u8BD5): ${err.message}`,
|
|
986
|
+
riskLevel: "medium",
|
|
987
|
+
riskType: "",
|
|
988
|
+
confidence: 60,
|
|
989
|
+
source: "llm_failure",
|
|
990
|
+
violatedLayers: ["3"]
|
|
991
|
+
};
|
|
992
|
+
}
|
|
354
993
|
}
|
|
355
|
-
const parsed = JSON.parse(jsonStr);
|
|
356
|
-
return {
|
|
357
|
-
allowed: parsed.allowed !== false,
|
|
358
|
-
// default to Allow if missing
|
|
359
|
-
reason: parsed.reason || "LLM \u5206\u6790\u5B8C\u6210",
|
|
360
|
-
riskLevel: ["low", "medium", "high", "critical"].includes(parsed.riskLevel) ? parsed.riskLevel : "low",
|
|
361
|
-
riskType: parsed.riskType || "",
|
|
362
|
-
confidence: typeof parsed.confidence === "number" ? Math.min(100, Math.max(0, parsed.confidence)) : 70,
|
|
363
|
-
source: "llm",
|
|
364
|
-
violatedLayers: ["3"]
|
|
365
|
-
};
|
|
366
|
-
} catch (err) {
|
|
367
|
-
console.error("[AgentMoss] LLM call failed:", err.message);
|
|
368
|
-
return {
|
|
369
|
-
allowed: true,
|
|
370
|
-
reason: `LLM \u8C03\u7528\u5931\u8D25: ${err.message}`,
|
|
371
|
-
riskLevel: "low",
|
|
372
|
-
riskType: "",
|
|
373
|
-
confidence: 30,
|
|
374
|
-
source: "llm",
|
|
375
|
-
violatedLayers: []
|
|
376
|
-
};
|
|
377
994
|
}
|
|
995
|
+
return {
|
|
996
|
+
allowed: false,
|
|
997
|
+
reason: "LLM \u5206\u6790\u5F02\u5E38",
|
|
998
|
+
riskLevel: "medium",
|
|
999
|
+
riskType: "",
|
|
1000
|
+
confidence: 60,
|
|
1001
|
+
source: "llm_failure",
|
|
1002
|
+
violatedLayers: ["3"]
|
|
1003
|
+
};
|
|
378
1004
|
}
|
|
379
1005
|
var llmCache, LLM_CACHE_MAX;
|
|
380
1006
|
var init_llm_analyzer = __esm({
|
|
@@ -384,6 +1010,7 @@ var init_llm_analyzer = __esm({
|
|
|
384
1010
|
init_config();
|
|
385
1011
|
init_skill_loader();
|
|
386
1012
|
init_template_loader();
|
|
1013
|
+
init_script_analyzer();
|
|
387
1014
|
llmCache = /* @__PURE__ */ new Map();
|
|
388
1015
|
LLM_CACHE_MAX = 200;
|
|
389
1016
|
}
|
|
@@ -394,21 +1021,83 @@ var coordinator_exports = {};
|
|
|
394
1021
|
__export(coordinator_exports, {
|
|
395
1022
|
runAnalysis: () => runAnalysis
|
|
396
1023
|
});
|
|
1024
|
+
function extractFirstCommand(command) {
|
|
1025
|
+
let main2 = command.split("|")[0].trim();
|
|
1026
|
+
for (const sep of ["&&", "||"]) {
|
|
1027
|
+
const idx = main2.indexOf(sep);
|
|
1028
|
+
if (idx !== -1) {
|
|
1029
|
+
main2 = main2.substring(0, idx).trim();
|
|
1030
|
+
break;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
return main2;
|
|
1034
|
+
}
|
|
1035
|
+
function extractRawCommand(detail) {
|
|
1036
|
+
const m = detail.match(/command=([^,]+)/);
|
|
1037
|
+
return m ? m[1].trim() : detail;
|
|
1038
|
+
}
|
|
1039
|
+
function matchesAny(cmd, patterns) {
|
|
1040
|
+
return patterns.some((p) => p.test(cmd));
|
|
1041
|
+
}
|
|
397
1042
|
async function runAnalysis(input) {
|
|
398
|
-
const heuristicResult = runHeuristicCheck(input);
|
|
1043
|
+
const heuristicResult = isHeuristicEnabled() ? runHeuristicCheck(input) : null;
|
|
399
1044
|
if (heuristicResult && !heuristicResult.allowed) {
|
|
400
1045
|
return heuristicResult;
|
|
401
1046
|
}
|
|
402
|
-
const
|
|
1047
|
+
const actionType = input.aNext.actionType.toLowerCase();
|
|
1048
|
+
const rawDetail = extractRawCommand(input.aNext.actionDetail);
|
|
1049
|
+
const lowerDetail = rawDetail.toLowerCase();
|
|
1050
|
+
if (FULLY_SAFE_TOOLS.has(actionType)) {
|
|
1051
|
+
return {
|
|
1052
|
+
allowed: true,
|
|
1053
|
+
reason: `\u5B8C\u5168\u5B89\u5168\u5DE5\u5177\uFF0C\u8DF3\u8FC7\u6DF1\u5EA6\u5206\u6790: ${actionType}`,
|
|
1054
|
+
riskLevel: heuristicResult?.riskLevel || "low",
|
|
1055
|
+
riskType: "",
|
|
1056
|
+
confidence: 95,
|
|
1057
|
+
source: "whitelist_bypass",
|
|
1058
|
+
violatedLayers: []
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
let skipLLM = READONLY_SENSITIVE_TOOLS.has(actionType);
|
|
1062
|
+
if (actionType === "bash" || actionType === "executebash" || actionType === "terminal-executebash") {
|
|
1063
|
+
const firstCmd = extractFirstCommand(lowerDetail);
|
|
1064
|
+
if (matchesAny(firstCmd, FULLY_SAFE_BASH_PATTERNS)) {
|
|
1065
|
+
return {
|
|
1066
|
+
allowed: true,
|
|
1067
|
+
reason: `\u5B89\u5168\u7684\u53EA\u8BFB bash \u547D\u4EE4: ${firstCmd}`,
|
|
1068
|
+
riskLevel: "low",
|
|
1069
|
+
riskType: "",
|
|
1070
|
+
confidence: 95,
|
|
1071
|
+
source: "whitelist_bypass",
|
|
1072
|
+
violatedLayers: []
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
if (matchesAny(firstCmd, READONLY_SENSITIVE_BASH_PATTERNS)) {
|
|
1076
|
+
skipLLM = true;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
const logicResult = isLogicRulesEnabled() ? runLogicCheck(input) : null;
|
|
403
1080
|
if (logicResult && !logicResult.allowed) {
|
|
404
1081
|
return logicResult;
|
|
405
1082
|
}
|
|
1083
|
+
if (skipLLM || !isLLMAvailable()) {
|
|
1084
|
+
return {
|
|
1085
|
+
allowed: true,
|
|
1086
|
+
reason: "\u53EA\u8BFB\u64CD\u4F5C\u4E14\u65E0\u903B\u8F91\u89C4\u5219\u8FDD\u89C4\uFF0C\u8DF3\u8FC7 LLM \u5206\u6790",
|
|
1087
|
+
riskLevel: logicResult?.riskLevel || "low",
|
|
1088
|
+
riskType: "",
|
|
1089
|
+
confidence: 85,
|
|
1090
|
+
source: "heuristic_and_logic_only",
|
|
1091
|
+
violatedLayers: []
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
406
1094
|
const llmResult = await runLLMAnalysis(input, {
|
|
407
1095
|
heuristic: heuristicResult,
|
|
408
1096
|
logic: logicResult
|
|
409
1097
|
});
|
|
410
1098
|
return llmResult;
|
|
411
1099
|
}
|
|
1100
|
+
var FULLY_SAFE_TOOLS, READONLY_SENSITIVE_TOOLS, FULLY_SAFE_BASH_PATTERNS, READONLY_SENSITIVE_BASH_PATTERNS;
|
|
412
1101
|
var init_coordinator = __esm({
|
|
413
1102
|
"src/engine/coordinator.ts"() {
|
|
414
1103
|
"use strict";
|
|
@@ -416,6 +1105,59 @@ var init_coordinator = __esm({
|
|
|
416
1105
|
init_heuristic();
|
|
417
1106
|
init_logic_rules();
|
|
418
1107
|
init_llm_analyzer();
|
|
1108
|
+
init_config();
|
|
1109
|
+
FULLY_SAFE_TOOLS = /* @__PURE__ */ new Set([
|
|
1110
|
+
"ask_user_question",
|
|
1111
|
+
"glob",
|
|
1112
|
+
"list_dir",
|
|
1113
|
+
"ls",
|
|
1114
|
+
"count_text_length",
|
|
1115
|
+
"filemgr-globfiles"
|
|
1116
|
+
]);
|
|
1117
|
+
READONLY_SENSITIVE_TOOLS = /* @__PURE__ */ new Set([
|
|
1118
|
+
"read",
|
|
1119
|
+
"file_read",
|
|
1120
|
+
"read_file",
|
|
1121
|
+
"head",
|
|
1122
|
+
"tail",
|
|
1123
|
+
"grep",
|
|
1124
|
+
"filemgr-readfile",
|
|
1125
|
+
"filemgr-grepfiles"
|
|
1126
|
+
]);
|
|
1127
|
+
FULLY_SAFE_BASH_PATTERNS = [
|
|
1128
|
+
/^ls(?:\s|$)/i,
|
|
1129
|
+
/^dir(?:\s|$)/i,
|
|
1130
|
+
/^pwd(?:\s|$)/i,
|
|
1131
|
+
/^which\s+/i,
|
|
1132
|
+
/^whereis\s+/i,
|
|
1133
|
+
/^realpath\s+/i,
|
|
1134
|
+
/^basename\s+/i,
|
|
1135
|
+
/^dirname\s+/i,
|
|
1136
|
+
/^file\s+/i,
|
|
1137
|
+
/^stat\s+/i,
|
|
1138
|
+
/^du(?:\s|$)/i,
|
|
1139
|
+
/^echo\s+/i,
|
|
1140
|
+
/^printf\s+/i,
|
|
1141
|
+
/^type\s+/i,
|
|
1142
|
+
/^command\s+/i
|
|
1143
|
+
];
|
|
1144
|
+
READONLY_SENSITIVE_BASH_PATTERNS = [
|
|
1145
|
+
/^cat\s+/i,
|
|
1146
|
+
/^head(?:\s|$)/i,
|
|
1147
|
+
/^tail(?:\s|$)/i,
|
|
1148
|
+
/^less(?:\s|$)/i,
|
|
1149
|
+
/^more(?:\s|$)/i,
|
|
1150
|
+
/^wc(?:\s|$)/i,
|
|
1151
|
+
/^grep(?:\s|$)/i,
|
|
1152
|
+
/^find\s+/i,
|
|
1153
|
+
/^ag(?:\s|$)/i,
|
|
1154
|
+
/^rg(?:\s|$)/i,
|
|
1155
|
+
/^awk\s+/i,
|
|
1156
|
+
/^sed\s+/i,
|
|
1157
|
+
/^cut\s+/i,
|
|
1158
|
+
/^sort(?:\s|$)/i,
|
|
1159
|
+
/^uniq(?:\s|$)/i
|
|
1160
|
+
];
|
|
419
1161
|
}
|
|
420
1162
|
});
|
|
421
1163
|
|