@kenhkl/agent-moss 0.12.5 → 0.12.7
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 +644 -18
- package/dist/cli.js.map +1 -1
- package/dist/engine/coordinator.d.ts +3 -1
- package/dist/engine/coordinator.js +603 -2
- package/dist/engine/coordinator.js.map +1 -1
- package/dist/rules/l1_rules.json +21 -0
- package/dist/server.js +612 -17
- package/dist/server.js.map +1 -1
- package/dist/static/index.html +158 -7
- package/package.json +1 -1
|
@@ -51,6 +51,8 @@ interface AnalysisInput {
|
|
|
51
51
|
* 测试可 import 此常量直接断言"无 agentId 时白名单不含某 skill",避免走 L3
|
|
52
52
|
* LLM 调用导致测试脆弱(依赖 LLM 401 fail_open 兜底)。 */
|
|
53
53
|
declare const TRUSTED_SKILLS_BASE: Set<string>;
|
|
54
|
+
/** 供测试隔离:重置 brain 单例 */
|
|
55
|
+
declare function _resetBrainForTest(): void;
|
|
54
56
|
/**
|
|
55
57
|
* Three-layer defense coordinator.
|
|
56
58
|
* Layer 1 → fast-pass check → Layer 2 → Layer 3.
|
|
@@ -58,4 +60,4 @@ declare const TRUSTED_SKILLS_BASE: Set<string>;
|
|
|
58
60
|
*/
|
|
59
61
|
declare function runAnalysis(input: AnalysisInput): Promise<SecurityJudgment>;
|
|
60
62
|
|
|
61
|
-
export { type AnalysisInput, TRUSTED_SKILLS_BASE, runAnalysis };
|
|
63
|
+
export { type AnalysisInput, TRUSTED_SKILLS_BASE, _resetBrainForTest, runAnalysis };
|
|
@@ -126,6 +126,7 @@ __export(config_exports, {
|
|
|
126
126
|
getProviderHeaders: () => getProviderHeaders,
|
|
127
127
|
getRetryConfig: () => getRetryConfig,
|
|
128
128
|
getTimeoutConfig: () => getTimeoutConfig,
|
|
129
|
+
isBrainEnabled: () => isBrainEnabled,
|
|
129
130
|
isHeuristicEnabled: () => isHeuristicEnabled,
|
|
130
131
|
isLLMAvailable: () => isLLMAvailable,
|
|
131
132
|
isLogicRulesEnabled: () => isLogicRulesEnabled,
|
|
@@ -245,11 +246,11 @@ function isLLMAvailable() {
|
|
|
245
246
|
}
|
|
246
247
|
async function probeLLMConnection(timeoutMs = 8e3) {
|
|
247
248
|
if (!isLLMAvailable()) return false;
|
|
248
|
-
const
|
|
249
|
+
const OpenAI3 = (await import("openai")).default;
|
|
249
250
|
const config = getLLMConfig();
|
|
250
251
|
const provider = detectProvider(config.baseUrl, config.provider);
|
|
251
252
|
const extraHeaders = getProviderHeaders(provider);
|
|
252
|
-
const client = new
|
|
253
|
+
const client = new OpenAI3({
|
|
253
254
|
baseURL: config.baseUrl,
|
|
254
255
|
apiKey: config.apiKey,
|
|
255
256
|
timeout: timeoutMs,
|
|
@@ -307,6 +308,9 @@ function getProviderHeaders(provider) {
|
|
|
307
308
|
function isHeuristicEnabled() {
|
|
308
309
|
return process.env.AGENT_MOSS_DISABLE_HEURISTIC !== "1";
|
|
309
310
|
}
|
|
311
|
+
function isBrainEnabled() {
|
|
312
|
+
return process.env.AGENT_MOSS_BRAIN_ENABLED === "1";
|
|
313
|
+
}
|
|
310
314
|
function isLogicRulesEnabled() {
|
|
311
315
|
return process.env.AGENT_MOSS_DISABLE_LOGIC_RULES !== "1";
|
|
312
316
|
}
|
|
@@ -3930,6 +3934,533 @@ async function analyzeWithLLM(prompt, config) {
|
|
|
3930
3934
|
// src/engine/coordinator.ts
|
|
3931
3935
|
init_config();
|
|
3932
3936
|
init_agents();
|
|
3937
|
+
|
|
3938
|
+
// src/brain/index.ts
|
|
3939
|
+
init_esm_shims();
|
|
3940
|
+
|
|
3941
|
+
// src/brain/rule-store.ts
|
|
3942
|
+
init_esm_shims();
|
|
3943
|
+
import { existsSync as existsSync9, readFileSync as readFileSync10, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
3944
|
+
import { join as join5 } from "path";
|
|
3945
|
+
import { homedir as homedir3 } from "os";
|
|
3946
|
+
var DEFAULT_RULES_PATH = join5(homedir3(), ".agentmoss", "brain", "rules.json");
|
|
3947
|
+
var ENV_RULES_PATH = "AGENT_MOSS_BRAIN_RULES_PATH";
|
|
3948
|
+
var STATUS_DRAFT = "draft";
|
|
3949
|
+
var STATUS_APPROVED = "approved";
|
|
3950
|
+
var STATUS_ACTIVE = "active";
|
|
3951
|
+
var STATUS_DISPUTED = "disputed";
|
|
3952
|
+
var STATUS_STALE = "stale";
|
|
3953
|
+
var STATUS_ARCHIVED = "archived";
|
|
3954
|
+
var STATUS_REJECTED = "rejected";
|
|
3955
|
+
function getRulesPath() {
|
|
3956
|
+
const envPath = process.env[ENV_RULES_PATH];
|
|
3957
|
+
if (envPath) return envPath;
|
|
3958
|
+
return DEFAULT_RULES_PATH;
|
|
3959
|
+
}
|
|
3960
|
+
function uuid() {
|
|
3961
|
+
try {
|
|
3962
|
+
return crypto.randomUUID();
|
|
3963
|
+
} catch {
|
|
3964
|
+
return `rule_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
3965
|
+
}
|
|
3966
|
+
}
|
|
3967
|
+
var BrainRuleStore = class {
|
|
3968
|
+
path;
|
|
3969
|
+
data;
|
|
3970
|
+
constructor(path3) {
|
|
3971
|
+
this.path = path3 || getRulesPath();
|
|
3972
|
+
this.data = this.load();
|
|
3973
|
+
}
|
|
3974
|
+
load() {
|
|
3975
|
+
if (!existsSync9(this.path)) {
|
|
3976
|
+
return { version: 1, rules: [], pattern_candidates: [] };
|
|
3977
|
+
}
|
|
3978
|
+
try {
|
|
3979
|
+
const raw = JSON.parse(readFileSync10(this.path, "utf-8"));
|
|
3980
|
+
return {
|
|
3981
|
+
version: raw.version || 1,
|
|
3982
|
+
rules: raw.rules || [],
|
|
3983
|
+
pattern_candidates: raw.pattern_candidates || []
|
|
3984
|
+
};
|
|
3985
|
+
} catch {
|
|
3986
|
+
return { version: 1, rules: [], pattern_candidates: [] };
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3989
|
+
save() {
|
|
3990
|
+
const dir = this.path.substring(0, this.path.lastIndexOf("/"));
|
|
3991
|
+
if (dir) mkdirSync3(dir, { recursive: true });
|
|
3992
|
+
writeFileSync3(this.path, JSON.stringify(this.data, null, 2), "utf-8");
|
|
3993
|
+
}
|
|
3994
|
+
addRule(rule) {
|
|
3995
|
+
const now = Date.now();
|
|
3996
|
+
const full = {
|
|
3997
|
+
id: rule.id || uuid(),
|
|
3998
|
+
layer: rule.layer,
|
|
3999
|
+
ruleType: rule.ruleType || "deny_pattern",
|
|
4000
|
+
pattern: rule.pattern,
|
|
4001
|
+
scope: rule.scope || "all",
|
|
4002
|
+
source: rule.source || "llm_extracted",
|
|
4003
|
+
sourceEventIds: rule.sourceEventIds || [],
|
|
4004
|
+
confidence: rule.confidence || 0,
|
|
4005
|
+
status: rule.status || STATUS_DRAFT,
|
|
4006
|
+
createdAt: rule.createdAt || now,
|
|
4007
|
+
lastTriggeredAt: rule.lastTriggeredAt || 0,
|
|
4008
|
+
triggerCount: rule.triggerCount || 0,
|
|
4009
|
+
disputedCount: rule.disputedCount || 0,
|
|
4010
|
+
explanation: rule.explanation || "",
|
|
4011
|
+
originNode: rule.originNode || "local"
|
|
4012
|
+
};
|
|
4013
|
+
this.data.rules.push(full);
|
|
4014
|
+
this.save();
|
|
4015
|
+
return full.id;
|
|
4016
|
+
}
|
|
4017
|
+
approveRule(ruleId) {
|
|
4018
|
+
const rule = this.getRule(ruleId);
|
|
4019
|
+
if (!rule) throw new Error(`\u89C4\u5219\u4E0D\u5B58\u5728: ${ruleId}`);
|
|
4020
|
+
if (rule.status !== STATUS_DRAFT) throw new Error(`\u53EA\u6709 draft \u72B6\u6001\u53EF\u5BA1\u6838\uFF0C\u5F53\u524D: ${rule.status}`);
|
|
4021
|
+
rule.status = STATUS_ACTIVE;
|
|
4022
|
+
this.save();
|
|
4023
|
+
}
|
|
4024
|
+
rejectRule(ruleId) {
|
|
4025
|
+
const rule = this.getRule(ruleId);
|
|
4026
|
+
if (!rule) throw new Error(`\u89C4\u5219\u4E0D\u5B58\u5728: ${ruleId}`);
|
|
4027
|
+
if (rule.status !== STATUS_DRAFT) throw new Error(`\u53EA\u6709 draft \u72B6\u6001\u53EF\u62D2\u7EDD\uFF0C\u5F53\u524D: ${rule.status}`);
|
|
4028
|
+
rule.status = STATUS_REJECTED;
|
|
4029
|
+
this.save();
|
|
4030
|
+
}
|
|
4031
|
+
getRule(ruleId) {
|
|
4032
|
+
return this.data.rules.find((r) => r.id === ruleId);
|
|
4033
|
+
}
|
|
4034
|
+
getActiveRules(layer) {
|
|
4035
|
+
return this.data.rules.filter((r) => r.status === STATUS_ACTIVE && (layer === void 0 || r.layer === layer));
|
|
4036
|
+
}
|
|
4037
|
+
getDraftRules() {
|
|
4038
|
+
return this.data.rules.filter((r) => r.status === STATUS_DRAFT).sort((a, b) => b.createdAt - a.createdAt);
|
|
4039
|
+
}
|
|
4040
|
+
recordTrigger(ruleId) {
|
|
4041
|
+
const rule = this.getRule(ruleId);
|
|
4042
|
+
if (!rule) return;
|
|
4043
|
+
rule.triggerCount += 1;
|
|
4044
|
+
rule.lastTriggeredAt = Date.now();
|
|
4045
|
+
this.save();
|
|
4046
|
+
}
|
|
4047
|
+
// --- pattern_candidates ---
|
|
4048
|
+
addPatternCandidate(pattern, layer, scope, sourceEventId, confidence) {
|
|
4049
|
+
const now = Date.now();
|
|
4050
|
+
const existing = this.data.pattern_candidates.find(
|
|
4051
|
+
(c) => c.pattern === pattern && c.layer === layer && c.scope === scope
|
|
4052
|
+
);
|
|
4053
|
+
if (existing) {
|
|
4054
|
+
if (existing.reviewStatus === "rejected") return 0;
|
|
4055
|
+
existing.occurrenceCount += 1;
|
|
4056
|
+
existing.lastSeenAt = now;
|
|
4057
|
+
existing.sourceEventIds.unshift(sourceEventId);
|
|
4058
|
+
this.save();
|
|
4059
|
+
return existing.occurrenceCount;
|
|
4060
|
+
}
|
|
4061
|
+
this.data.pattern_candidates.push({
|
|
4062
|
+
pattern,
|
|
4063
|
+
layer,
|
|
4064
|
+
scope,
|
|
4065
|
+
sourceEventIds: [sourceEventId],
|
|
4066
|
+
confidence,
|
|
4067
|
+
occurrenceCount: 1,
|
|
4068
|
+
firstSeenAt: now,
|
|
4069
|
+
lastSeenAt: now,
|
|
4070
|
+
reviewStatus: "pending",
|
|
4071
|
+
reviewReason: "",
|
|
4072
|
+
reviewedAt: 0
|
|
4073
|
+
});
|
|
4074
|
+
this.save();
|
|
4075
|
+
return 1;
|
|
4076
|
+
}
|
|
4077
|
+
getPatternCandidates(minOccurrences = 3) {
|
|
4078
|
+
return this.data.pattern_candidates.filter((c) => c.occurrenceCount >= minOccurrences && c.reviewStatus !== "rejected").sort((a, b) => b.occurrenceCount - a.occurrenceCount);
|
|
4079
|
+
}
|
|
4080
|
+
getPendingReviewCandidates(minOccurrences = 3) {
|
|
4081
|
+
return this.data.pattern_candidates.filter((c) => c.occurrenceCount >= minOccurrences && c.reviewStatus === "pending").sort((a, b) => b.occurrenceCount - a.occurrenceCount);
|
|
4082
|
+
}
|
|
4083
|
+
isExcludedPattern(pattern, layer, scope) {
|
|
4084
|
+
const c = this.data.pattern_candidates.find(
|
|
4085
|
+
(c2) => c2.pattern === pattern && c2.layer === layer && c2.scope === scope
|
|
4086
|
+
);
|
|
4087
|
+
return !!c && c.reviewStatus === "rejected";
|
|
4088
|
+
}
|
|
4089
|
+
setPatternReviewStatus(pattern, layer, scope, status, reason = "") {
|
|
4090
|
+
const c = this.data.pattern_candidates.find(
|
|
4091
|
+
(c2) => c2.pattern === pattern && c2.layer === layer && c2.scope === scope
|
|
4092
|
+
);
|
|
4093
|
+
if (c) {
|
|
4094
|
+
c.reviewStatus = status;
|
|
4095
|
+
c.reviewReason = reason;
|
|
4096
|
+
c.reviewedAt = Date.now();
|
|
4097
|
+
this.save();
|
|
4098
|
+
}
|
|
4099
|
+
}
|
|
4100
|
+
removePatternCandidate(pattern, layer, scope) {
|
|
4101
|
+
this.data.pattern_candidates = this.data.pattern_candidates.filter(
|
|
4102
|
+
(c) => !(c.pattern === pattern && c.layer === layer && c.scope === scope)
|
|
4103
|
+
);
|
|
4104
|
+
this.save();
|
|
4105
|
+
}
|
|
4106
|
+
getStats() {
|
|
4107
|
+
const count = (status) => this.data.rules.filter((r) => r.status === status).length;
|
|
4108
|
+
return {
|
|
4109
|
+
draft: count(STATUS_DRAFT),
|
|
4110
|
+
approved: count(STATUS_APPROVED),
|
|
4111
|
+
active: count(STATUS_ACTIVE),
|
|
4112
|
+
disputed: count(STATUS_DISPUTED),
|
|
4113
|
+
stale: count(STATUS_STALE),
|
|
4114
|
+
archived: count(STATUS_ARCHIVED),
|
|
4115
|
+
rejected: count(STATUS_REJECTED),
|
|
4116
|
+
total: this.data.rules.length,
|
|
4117
|
+
by_layer: {
|
|
4118
|
+
1: this.data.rules.filter((r) => r.layer === 1 && r.status === STATUS_ACTIVE).length,
|
|
4119
|
+
2: this.data.rules.filter((r) => r.layer === 2 && r.status === STATUS_ACTIVE).length
|
|
4120
|
+
},
|
|
4121
|
+
pattern_candidates: this.data.pattern_candidates.length
|
|
4122
|
+
};
|
|
4123
|
+
}
|
|
4124
|
+
};
|
|
4125
|
+
|
|
4126
|
+
// src/brain/pattern-extractor.ts
|
|
4127
|
+
init_esm_shims();
|
|
4128
|
+
var PatternExtractor = class {
|
|
4129
|
+
ruleStore;
|
|
4130
|
+
minConfidence;
|
|
4131
|
+
minOccurrences;
|
|
4132
|
+
builtinPatterns;
|
|
4133
|
+
reviewFn;
|
|
4134
|
+
constructor(ruleStore, opts) {
|
|
4135
|
+
this.ruleStore = ruleStore;
|
|
4136
|
+
this.minConfidence = opts?.minConfidence ?? 0.9;
|
|
4137
|
+
this.minOccurrences = opts?.minOccurrences ?? 3;
|
|
4138
|
+
this.builtinPatterns = opts?.builtinPatterns ?? [];
|
|
4139
|
+
this.reviewFn = opts?.review ?? null;
|
|
4140
|
+
}
|
|
4141
|
+
async extract(event, llmResult) {
|
|
4142
|
+
const result = { rulesCreated: 0, patternsRecorded: 0, skippedReason: "", deduped: [] };
|
|
4143
|
+
const confidence = llmResult.confidence ?? 0;
|
|
4144
|
+
const reason = llmResult.reason ?? "";
|
|
4145
|
+
const riskType = llmResult.riskType ?? "";
|
|
4146
|
+
const actionDetail = llmResult.actionDetail ?? event.actionDetail ?? "";
|
|
4147
|
+
const eventId = event.eventId ?? "";
|
|
4148
|
+
if (confidence < this.minConfidence) {
|
|
4149
|
+
result.skippedReason = `confidence ${confidence} < ${this.minConfidence}`;
|
|
4150
|
+
return result;
|
|
4151
|
+
}
|
|
4152
|
+
const patterns = this.extractPatterns(actionDetail, reason, riskType);
|
|
4153
|
+
if (patterns.length === 0) {
|
|
4154
|
+
result.skippedReason = "no extractable pattern";
|
|
4155
|
+
return result;
|
|
4156
|
+
}
|
|
4157
|
+
for (const p of patterns) {
|
|
4158
|
+
if (this.isCoveredByBuiltin(actionDetail)) {
|
|
4159
|
+
result.deduped.push(p.pattern);
|
|
4160
|
+
continue;
|
|
4161
|
+
}
|
|
4162
|
+
const count = this.ruleStore.addPatternCandidate(
|
|
4163
|
+
p.pattern,
|
|
4164
|
+
p.layer,
|
|
4165
|
+
p.scope,
|
|
4166
|
+
eventId,
|
|
4167
|
+
confidence
|
|
4168
|
+
);
|
|
4169
|
+
if (count === 0) {
|
|
4170
|
+
result.deduped.push(p.pattern);
|
|
4171
|
+
continue;
|
|
4172
|
+
}
|
|
4173
|
+
result.patternsRecorded += 1;
|
|
4174
|
+
if (count >= this.minOccurrences) {
|
|
4175
|
+
const candidates = this.ruleStore.getPatternCandidates(this.minOccurrences);
|
|
4176
|
+
const matched = candidates.find((c) => c.pattern === p.pattern && c.layer === p.layer);
|
|
4177
|
+
if (!matched) continue;
|
|
4178
|
+
if (this.reviewFn) {
|
|
4179
|
+
let review;
|
|
4180
|
+
try {
|
|
4181
|
+
review = await this.reviewFn({
|
|
4182
|
+
pattern: p.pattern,
|
|
4183
|
+
layer: p.layer,
|
|
4184
|
+
ruleType: p.ruleType,
|
|
4185
|
+
actionDetail,
|
|
4186
|
+
reason
|
|
4187
|
+
});
|
|
4188
|
+
} catch {
|
|
4189
|
+
review = null;
|
|
4190
|
+
}
|
|
4191
|
+
if (review === null) {
|
|
4192
|
+
continue;
|
|
4193
|
+
}
|
|
4194
|
+
if (!review.shouldKeep) {
|
|
4195
|
+
this.ruleStore.setPatternReviewStatus(
|
|
4196
|
+
p.pattern,
|
|
4197
|
+
p.layer,
|
|
4198
|
+
p.scope,
|
|
4199
|
+
"rejected",
|
|
4200
|
+
review.reason || "LLM \u5BA1\u6838\u62D2\u7EDD"
|
|
4201
|
+
);
|
|
4202
|
+
result.deduped.push(p.pattern);
|
|
4203
|
+
continue;
|
|
4204
|
+
}
|
|
4205
|
+
this.ruleStore.setPatternReviewStatus(
|
|
4206
|
+
p.pattern,
|
|
4207
|
+
p.layer,
|
|
4208
|
+
p.scope,
|
|
4209
|
+
"approved",
|
|
4210
|
+
review.reason || "LLM \u5BA1\u6838\u901A\u8FC7"
|
|
4211
|
+
);
|
|
4212
|
+
}
|
|
4213
|
+
this.ruleStore.addRule({
|
|
4214
|
+
layer: p.layer,
|
|
4215
|
+
ruleType: p.ruleType,
|
|
4216
|
+
pattern: p.pattern,
|
|
4217
|
+
scope: p.scope,
|
|
4218
|
+
source: "llm_extracted",
|
|
4219
|
+
sourceEventIds: eventId ? [eventId] : [],
|
|
4220
|
+
confidence: matched.confidence,
|
|
4221
|
+
status: STATUS_DRAFT,
|
|
4222
|
+
explanation: `\u4ECE ${count} \u6B21 LLM Deny \u4E2D\u63D0\u53D6(\u7ECF\u5BA1\u6838): ${reason.slice(0, 200)}`
|
|
4223
|
+
});
|
|
4224
|
+
this.ruleStore.removePatternCandidate(p.pattern, p.layer, p.scope);
|
|
4225
|
+
result.rulesCreated += 1;
|
|
4226
|
+
}
|
|
4227
|
+
}
|
|
4228
|
+
return result;
|
|
4229
|
+
}
|
|
4230
|
+
extractPatterns(actionDetail, reason, riskType) {
|
|
4231
|
+
const patterns = [];
|
|
4232
|
+
const detailLower = actionDetail.toLowerCase();
|
|
4233
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4234
|
+
const sensitivePathRe = /(\/(?:etc|root)(?:\/[^\s;|&"'<>`$]*)?|(?:[^\s;|&"'<>`$]*)?\/\.ssh\/[^\s;|&"'<>`$]*|(?:[^\s;|&"'<>`$]*)?\/\.aws\/[^\s;|&"'<>`$]*|\S+\.(?:pem|key|p12|pfx|id_rsa|authorized_keys)\b)/;
|
|
4235
|
+
const m = detailLower.match(sensitivePathRe);
|
|
4236
|
+
if (m) {
|
|
4237
|
+
const gen = this.generalizePath(m[1]);
|
|
4238
|
+
if (!seen.has(gen)) {
|
|
4239
|
+
seen.add(gen);
|
|
4240
|
+
patterns.push({ pattern: gen, layer: 1, scope: "openat", ruleType: "deny_path" });
|
|
4241
|
+
}
|
|
4242
|
+
}
|
|
4243
|
+
const dangerousCmdRe = /\b(rm\s+-[a-zA-Z]*r[a-zA-Z]*f|rm\s+--recursive|chmod\s+777|curl\s+[^|]*\|\s*(?:ba)?sh|wget\s+[^|]*\|\s*(?:ba)?sh|mkfs\.|dd\s+if=|>\s*\/dev\/sd)\b/;
|
|
4244
|
+
const cmdM = detailLower.match(dangerousCmdRe);
|
|
4245
|
+
if (cmdM) {
|
|
4246
|
+
const gen = this.generalizeCommand(cmdM[1]);
|
|
4247
|
+
const seenKey = `cmd:${gen}`;
|
|
4248
|
+
if (!seen.has(seenKey)) {
|
|
4249
|
+
seen.add(seenKey);
|
|
4250
|
+
patterns.push({ pattern: gen, layer: 1, scope: "execve", ruleType: "deny_command" });
|
|
4251
|
+
}
|
|
4252
|
+
}
|
|
4253
|
+
const reasonLower = (reason || "").toLowerCase();
|
|
4254
|
+
if (reasonLower.includes("inject") || reasonLower.includes("\u6570\u636E\u6CE8\u5165")) {
|
|
4255
|
+
patterns.push({
|
|
4256
|
+
pattern: "BEHAVIOR_CHAIN:external_read\u2192sensitive_write",
|
|
4257
|
+
layer: 2,
|
|
4258
|
+
scope: "all",
|
|
4259
|
+
ruleType: "deny_pattern"
|
|
4260
|
+
});
|
|
4261
|
+
}
|
|
4262
|
+
if (reasonLower.includes("reconnaissance") || reasonLower.includes("\u4FA6\u5BDF") || reasonLower.includes("\u63D0\u6743")) {
|
|
4263
|
+
patterns.push({
|
|
4264
|
+
pattern: "BEHAVIOR_CHAIN:multi_config_read\u2192privilege_action",
|
|
4265
|
+
layer: 2,
|
|
4266
|
+
scope: "all",
|
|
4267
|
+
ruleType: "deny_pattern"
|
|
4268
|
+
});
|
|
4269
|
+
}
|
|
4270
|
+
const pathPrefix = `(?:^|[\\s"'(=])`;
|
|
4271
|
+
const pathBody = "(\\/(?:[a-zA-Z0-9_.-]+\\/)+[a-zA-Z0-9_.-]+)";
|
|
4272
|
+
const pathSuffix = `(?=[\\s"')\uFF0C\u3002\uFF09\uFF1B;]|$)`;
|
|
4273
|
+
for (const src of [detailLower, reasonLower]) {
|
|
4274
|
+
const pathRe = new RegExp(pathPrefix + pathBody + pathSuffix, "g");
|
|
4275
|
+
let mm;
|
|
4276
|
+
while ((mm = pathRe.exec(src)) !== null) {
|
|
4277
|
+
const path3 = mm[1];
|
|
4278
|
+
if (path3.split("/").length < 3) continue;
|
|
4279
|
+
if (["/dev/null", "/dev/zero", "/dev/random", "/dev/urandom"].includes(path3)) continue;
|
|
4280
|
+
const gen = this.generalizePath(path3);
|
|
4281
|
+
if (seen.has(gen)) continue;
|
|
4282
|
+
seen.add(gen);
|
|
4283
|
+
patterns.push({ pattern: gen, layer: 1, scope: "openat", ruleType: "deny_path" });
|
|
4284
|
+
}
|
|
4285
|
+
}
|
|
4286
|
+
return patterns;
|
|
4287
|
+
}
|
|
4288
|
+
isCoveredByBuiltin(actionDetail) {
|
|
4289
|
+
if (this.builtinPatterns.length === 0 || !actionDetail) return false;
|
|
4290
|
+
for (const pat of this.builtinPatterns) {
|
|
4291
|
+
try {
|
|
4292
|
+
if (new RegExp(pat, "i").test(actionDetail)) return true;
|
|
4293
|
+
} catch {
|
|
4294
|
+
continue;
|
|
4295
|
+
}
|
|
4296
|
+
}
|
|
4297
|
+
return false;
|
|
4298
|
+
}
|
|
4299
|
+
generalizePath(path3) {
|
|
4300
|
+
const p = path3.toLowerCase();
|
|
4301
|
+
if (/\/etc\/(shadow|passwd|sudoers|security|ssh)/.test(p)) {
|
|
4302
|
+
return "(?:^|[^a-z0-9_-])etc/(?:shadow|passwd|sudoers|security/|ssh/)";
|
|
4303
|
+
}
|
|
4304
|
+
if (p.includes("/etc/")) {
|
|
4305
|
+
return "(?:^|[^a-z0-9_-])etc/";
|
|
4306
|
+
}
|
|
4307
|
+
if (p.includes("/root/")) {
|
|
4308
|
+
return "(?:^|[^a-z0-9_-])root/\\.(?:ssh|gnupg|aws|config)/";
|
|
4309
|
+
}
|
|
4310
|
+
if (p.includes(".ssh/")) {
|
|
4311
|
+
return "(?:^|[^a-z0-9_-])[\\w./-]*/\\.ssh/";
|
|
4312
|
+
}
|
|
4313
|
+
if (p.includes(".aws/")) {
|
|
4314
|
+
return "(?:^|[^a-z0-9_-])[\\w./-]*/\\.aws/";
|
|
4315
|
+
}
|
|
4316
|
+
if (/\.(pem|key|p12|pfx|id_rsa|authorized_keys)\b/.test(p)) {
|
|
4317
|
+
return "[\\w./-]*\\.(?:pem|key|p12|pfx|id_rsa|authorized_keys)(?:$|[^a-z0-9])";
|
|
4318
|
+
}
|
|
4319
|
+
const escaped = p.replace(/^\//, "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4320
|
+
return `(?:^|[^a-z0-9_-])${escaped}(?:/|\\b)`;
|
|
4321
|
+
}
|
|
4322
|
+
generalizeCommand(cmd) {
|
|
4323
|
+
if (cmd.includes("rm") && cmd.includes("r") && cmd.includes("f")) {
|
|
4324
|
+
return "rm\\s+(?:-[a-zA-Z]*r[a-zA-Z]*f|--recursive)\\s+/";
|
|
4325
|
+
}
|
|
4326
|
+
if (cmd.includes("chmod")) return "chmod\\s+777";
|
|
4327
|
+
if (cmd.includes("curl") || cmd.includes("wget")) {
|
|
4328
|
+
return "(?:curl|wget)\\s+[^|]*\\|\\s*(?:ba)?sh";
|
|
4329
|
+
}
|
|
4330
|
+
if (cmd.includes("mkfs")) return "mkfs\\.";
|
|
4331
|
+
if (cmd.includes("dd")) return "dd\\s+if=";
|
|
4332
|
+
return cmd.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4333
|
+
}
|
|
4334
|
+
};
|
|
4335
|
+
|
|
4336
|
+
// src/brain/reviewer.ts
|
|
4337
|
+
init_esm_shims();
|
|
4338
|
+
init_config();
|
|
4339
|
+
import OpenAI2 from "openai";
|
|
4340
|
+
var REVIEW_PROMPT = `\u4F60\u662F AgentMoss \u5B89\u5168\u89C4\u5219\u5BA1\u6838\u5668\u3002\u5224\u65AD\u4E0B\u5217"\u4ECE LLM \u62D2\u7EDD\u7ED3\u679C\u4E2D\u63D0\u53D6\u7684\u5B89\u5168\u6A21\u5F0F"\u662F\u5426\u503C\u5F97\u6C89\u6DC0\u4E3A\u672C\u5730\u89C4\u5219\u3002
|
|
4341
|
+
|
|
4342
|
+
## \u63D0\u53D6\u7684\u6A21\u5F0F\uFF08\u5019\u9009\uFF09
|
|
4343
|
+
- \u6A21\u5F0F: {pattern}
|
|
4344
|
+
- \u6240\u5C5E\u5C42: Layer {layer}\uFF081=\u7279\u5F81\u5339\u914D\uFF0C2=\u884C\u4E3A\u94FE\uFF09
|
|
4345
|
+
- \u7C7B\u578B: {ruleType}
|
|
4346
|
+
|
|
4347
|
+
## \u539F\u59CB\u88AB\u62D2\u7EDD\u7684\u64CD\u4F5C
|
|
4348
|
+
{actionDetail}
|
|
4349
|
+
|
|
4350
|
+
## LLM \u5F53\u65F6\u7684\u62D2\u7EDD\u7406\u7531
|
|
4351
|
+
{reason}
|
|
4352
|
+
|
|
4353
|
+
## \u5224\u65AD\u51C6\u5219
|
|
4354
|
+
1. **\u662F\u5426\u8FC7\u9AD8\u6CDB\u5316**\uFF1A\u8FD9\u6761\u6A21\u5F0F\u662F\u5426\u4F1A\u8BEF\u62E6\u6B63\u5E38\u7684\u5408\u6CD5\u64CD\u4F5C\uFF1F
|
|
4355
|
+
2. **\u662F\u5426\u7B26\u5408\u654F\u611F\u7279\u5F81**\uFF1A\u6307\u5411\u7684\u8DEF\u5F84/\u547D\u4EE4\u662F\u5426\u786E\u5B9E\u654F\u611F\uFF08\u51ED\u636E/\u7CFB\u7EDF\u6587\u4EF6/\u6076\u610F\u547D\u4EE4/\u5916\u4F20\uFF09\uFF1F
|
|
4356
|
+
3. **\u4E0E\u4E09\u5C42\u89C4\u5219\u91CD\u590D**\uFF1A\u662F\u5426\u5DF2\u88AB\u73B0\u6709 L1/L2 \u89C4\u5219\u8986\u76D6\uFF1F
|
|
4357
|
+
4. **\u662F\u4E0D\u662F\u771F\u5B9E\u653B\u51FB\u6A21\u5F0F**\uFF1A\u662F\u5426\u4EE3\u8868\u4E00\u79CD\u8FDE\u7EED\u7684\u653B\u51FB\u884C\u4E3A\u6A21\u5F0F\uFF1F
|
|
4358
|
+
|
|
4359
|
+
## \u8F93\u51FA JSON\uFF08\u53EA\u8F93\u51FA JSON\uFF0C\u65E0\u5176\u4ED6\u6587\u5B57\uFF09
|
|
4360
|
+
{"should_keep": true/false, "confidence": 0-100, "reason": "\u7B80\u77ED\u7406\u7531\uFF08\u4E2D\u6587\uFF09", "suggested_rule": "\u82E5\u4FDD\u7559\u5219\u8F93\u51FA\u5EFA\u8BAE\u6B63\u5219\uFF0C\u5426\u5219\u7A7A\u4E32", "duplicate_of": "\u82E5\u91CD\u590D\u5219\u8BF4\u660E\u662F\u54EA\u4E2A\uFF0C\u5426\u5219\u7A7A\u4E32"}`;
|
|
4361
|
+
function createBrainReviewer(timeoutMs = 1e4) {
|
|
4362
|
+
return async (opts) => {
|
|
4363
|
+
if (process.env.AGENT_MOSS_BRAIN_REVIEW === "0") return null;
|
|
4364
|
+
if (!isLLMAvailable()) return null;
|
|
4365
|
+
const config = getLLMConfig();
|
|
4366
|
+
const provider = detectProvider(config.baseUrl, config.provider);
|
|
4367
|
+
const extraHeaders = getProviderHeaders(provider);
|
|
4368
|
+
const client = new OpenAI2({
|
|
4369
|
+
baseURL: config.baseUrl,
|
|
4370
|
+
apiKey: config.apiKey,
|
|
4371
|
+
timeout: timeoutMs,
|
|
4372
|
+
maxRetries: 1,
|
|
4373
|
+
defaultHeaders: Object.keys(extraHeaders).length > 0 ? extraHeaders : void 0
|
|
4374
|
+
});
|
|
4375
|
+
const prompt = REVIEW_PROMPT.replace("{pattern}", opts.pattern).replace("{layer}", String(opts.layer)).replace("{ruleType}", opts.ruleType).replace("{actionDetail}", (opts.actionDetail || "").slice(0, 400)).replace("{reason}", (opts.reason || "").slice(0, 400));
|
|
4376
|
+
try {
|
|
4377
|
+
const response = await client.chat.completions.create({
|
|
4378
|
+
model: config.model,
|
|
4379
|
+
temperature: config.temperature || 0.1,
|
|
4380
|
+
max_tokens: 256,
|
|
4381
|
+
messages: [
|
|
4382
|
+
{ role: "system", content: "\u53EA\u8F93\u51FA\u7EAF JSON\uFF0C\u4E0D\u8981\u5305\u542B markdown \u6807\u8BB0\u3001\u4EE3\u7801\u5757\u6216\u4EFB\u4F55\u5176\u4ED6\u6587\u5B57\u3002" },
|
|
4383
|
+
{ role: "user", content: prompt }
|
|
4384
|
+
]
|
|
4385
|
+
});
|
|
4386
|
+
const content = response.choices[0]?.message?.content || "";
|
|
4387
|
+
const parsed = parseReview(content);
|
|
4388
|
+
return { shouldKeep: parsed.shouldKeep, reason: parsed.reason };
|
|
4389
|
+
} catch (e) {
|
|
4390
|
+
console.warn(`[AgentMoss] Brain \u5BA1\u6838 LLM \u8C03\u7528\u5931\u8D25\uFF08\u964D\u7EA7\u4FDD\u6301\u5019\u9009\uFF09: ${e.message}`);
|
|
4391
|
+
return null;
|
|
4392
|
+
}
|
|
4393
|
+
};
|
|
4394
|
+
}
|
|
4395
|
+
function parseReview(content) {
|
|
4396
|
+
let text = content.trim();
|
|
4397
|
+
if (text.startsWith("```")) {
|
|
4398
|
+
text = text.replace(/^```[a-zA-Z]*\n?/, "").replace(/\n?```$/, "");
|
|
4399
|
+
}
|
|
4400
|
+
const match = text.match(/\{[\s\S]*\}/);
|
|
4401
|
+
if (!match) throw new Error(`\u5BA1\u6838\u54CD\u5E94\u65E0 JSON \u5BF9\u8C61: ${text.slice(0, 200)}`);
|
|
4402
|
+
const data = JSON.parse(match[0]);
|
|
4403
|
+
if (!("should_keep" in data) && !("shouldKeep" in data)) {
|
|
4404
|
+
throw new Error(`\u5BA1\u6838\u54CD\u5E94\u7F3A should_keep: ${text.slice(0, 200)}`);
|
|
4405
|
+
}
|
|
4406
|
+
let shouldKeep = data.shouldKeep ?? data.should_keep;
|
|
4407
|
+
if (typeof shouldKeep === "string") {
|
|
4408
|
+
shouldKeep = ["true", "1", "\u662F", "yes"].includes(shouldKeep.trim().toLowerCase());
|
|
4409
|
+
}
|
|
4410
|
+
return {
|
|
4411
|
+
shouldKeep: !!shouldKeep,
|
|
4412
|
+
confidence: Number(data.confidence ?? 0) || 0,
|
|
4413
|
+
reason: String(data.reason ?? data.suggested_rule ?? "") || ""
|
|
4414
|
+
};
|
|
4415
|
+
}
|
|
4416
|
+
|
|
4417
|
+
// src/brain/index.ts
|
|
4418
|
+
function checkBrainRules(store, opts) {
|
|
4419
|
+
if (!store) return null;
|
|
4420
|
+
const rules = store.getActiveRules(opts.layer);
|
|
4421
|
+
if (rules.length === 0) return null;
|
|
4422
|
+
const combined = `${opts.actionType} ${opts.actionDetail}`;
|
|
4423
|
+
for (const rule of rules) {
|
|
4424
|
+
try {
|
|
4425
|
+
if (new RegExp(rule.pattern, "i").test(combined)) {
|
|
4426
|
+
if (opts.layer === 2 && opts.actionHistory) {
|
|
4427
|
+
if (!checkLayer2Context(rule.pattern, opts.actionHistory, opts.actionDetail)) {
|
|
4428
|
+
continue;
|
|
4429
|
+
}
|
|
4430
|
+
}
|
|
4431
|
+
store.recordTrigger(rule.id);
|
|
4432
|
+
return {
|
|
4433
|
+
ruleId: rule.id,
|
|
4434
|
+
pattern: rule.pattern,
|
|
4435
|
+
explanation: rule.explanation,
|
|
4436
|
+
confidence: rule.confidence
|
|
4437
|
+
};
|
|
4438
|
+
}
|
|
4439
|
+
} catch {
|
|
4440
|
+
continue;
|
|
4441
|
+
}
|
|
4442
|
+
}
|
|
4443
|
+
return null;
|
|
4444
|
+
}
|
|
4445
|
+
function checkLayer2Context(pattern, actionHistory, actionDetail) {
|
|
4446
|
+
if (pattern.startsWith("BEHAVIOR_CHAIN:")) {
|
|
4447
|
+
const chainType = pattern.replace("BEHAVIOR_CHAIN:", "");
|
|
4448
|
+
if (chainType === "external_read\u2192sensitive_write") {
|
|
4449
|
+
const hasExternalRead = actionHistory.slice(-5).some((h) => /curl|wget|http/i.test(h.actionDetail ?? ""));
|
|
4450
|
+
const isSensitiveWrite = /\.ssh|\.gnupg|authorized_keys|bashrc|profile/i.test(actionDetail);
|
|
4451
|
+
return hasExternalRead && isSensitiveWrite;
|
|
4452
|
+
}
|
|
4453
|
+
if (chainType === "multi_config_read\u2192privilege_action") {
|
|
4454
|
+
const configReads = actionHistory.slice(-5).filter((h) => /\/etc\/|config|\.conf|\.yaml|\.json/i.test(h.actionDetail ?? "")).length;
|
|
4455
|
+
const isPrivilege = /sudo|su |chmod|chown/i.test(actionDetail);
|
|
4456
|
+
return configReads >= 2 && isPrivilege;
|
|
4457
|
+
}
|
|
4458
|
+
return false;
|
|
4459
|
+
}
|
|
4460
|
+
return true;
|
|
4461
|
+
}
|
|
4462
|
+
|
|
4463
|
+
// src/engine/coordinator.ts
|
|
3933
4464
|
var FULLY_SAFE_TOOLS = /* @__PURE__ */ new Set([
|
|
3934
4465
|
"ask_user_question",
|
|
3935
4466
|
"glob",
|
|
@@ -4014,6 +4545,23 @@ function extractRawCommand(detail) {
|
|
|
4014
4545
|
function matchesAny(cmd, patterns) {
|
|
4015
4546
|
return patterns.some((p) => p.test(cmd));
|
|
4016
4547
|
}
|
|
4548
|
+
var _brainStore = null;
|
|
4549
|
+
var _brainExtractor = null;
|
|
4550
|
+
var _brainInitialized = false;
|
|
4551
|
+
function getBrainStore() {
|
|
4552
|
+
if (!isBrainEnabled()) return null;
|
|
4553
|
+
if (!_brainInitialized) {
|
|
4554
|
+
_brainStore = new BrainRuleStore();
|
|
4555
|
+
_brainExtractor = new PatternExtractor(_brainStore, { review: createBrainReviewer() });
|
|
4556
|
+
_brainInitialized = true;
|
|
4557
|
+
}
|
|
4558
|
+
return _brainStore;
|
|
4559
|
+
}
|
|
4560
|
+
function _resetBrainForTest() {
|
|
4561
|
+
_brainStore = null;
|
|
4562
|
+
_brainExtractor = null;
|
|
4563
|
+
_brainInitialized = false;
|
|
4564
|
+
}
|
|
4017
4565
|
async function runAnalysis(input) {
|
|
4018
4566
|
const heuristicResult = isHeuristicEnabled() ? runHeuristicCheck(input) : null;
|
|
4019
4567
|
const isInlineScriptCmd = isInlineScriptCommand(input.aNext.actionDetail.toLowerCase());
|
|
@@ -4046,6 +4594,25 @@ async function runAnalysis(input) {
|
|
|
4046
4594
|
}
|
|
4047
4595
|
}
|
|
4048
4596
|
}
|
|
4597
|
+
const brainStore = getBrainStore();
|
|
4598
|
+
if (brainStore) {
|
|
4599
|
+
const brainL1 = checkBrainRules(brainStore, {
|
|
4600
|
+
layer: 1,
|
|
4601
|
+
actionType: input.aNext.actionType,
|
|
4602
|
+
actionDetail: input.aNext.actionDetail
|
|
4603
|
+
});
|
|
4604
|
+
if (brainL1) {
|
|
4605
|
+
return {
|
|
4606
|
+
allowed: false,
|
|
4607
|
+
reason: `[Brain L1] ${brainL1.explanation}`,
|
|
4608
|
+
riskLevel: "high",
|
|
4609
|
+
riskType: "brain_rule",
|
|
4610
|
+
confidence: Math.round(brainL1.confidence * 100),
|
|
4611
|
+
source: "brain_rule",
|
|
4612
|
+
violatedLayers: ["brain_L1"]
|
|
4613
|
+
};
|
|
4614
|
+
}
|
|
4615
|
+
}
|
|
4049
4616
|
const actionType = input.aNext.actionType.toLowerCase();
|
|
4050
4617
|
const rawDetail = extractRawCommand(input.aNext.actionDetail);
|
|
4051
4618
|
const lowerDetail = rawDetail.toLowerCase();
|
|
@@ -4171,6 +4738,25 @@ async function runAnalysis(input) {
|
|
|
4171
4738
|
}
|
|
4172
4739
|
}
|
|
4173
4740
|
}
|
|
4741
|
+
if (brainStore) {
|
|
4742
|
+
const brainL2 = checkBrainRules(brainStore, {
|
|
4743
|
+
layer: 2,
|
|
4744
|
+
actionType: input.aNext.actionType,
|
|
4745
|
+
actionDetail: input.aNext.actionDetail,
|
|
4746
|
+
actionHistory: input.actionHistory
|
|
4747
|
+
});
|
|
4748
|
+
if (brainL2) {
|
|
4749
|
+
return {
|
|
4750
|
+
allowed: false,
|
|
4751
|
+
reason: `[Brain L2] ${brainL2.explanation}`,
|
|
4752
|
+
riskLevel: "high",
|
|
4753
|
+
riskType: "brain_rule",
|
|
4754
|
+
confidence: Math.round(brainL2.confidence * 100),
|
|
4755
|
+
source: "brain_rule",
|
|
4756
|
+
violatedLayers: ["brain_L2"]
|
|
4757
|
+
};
|
|
4758
|
+
}
|
|
4759
|
+
}
|
|
4174
4760
|
if (skipLLM || !isLLMAvailable()) {
|
|
4175
4761
|
return {
|
|
4176
4762
|
allowed: true,
|
|
@@ -4237,10 +4823,25 @@ async function runAnalysis(input) {
|
|
|
4237
4823
|
violatedLayers: ["3"]
|
|
4238
4824
|
};
|
|
4239
4825
|
}
|
|
4826
|
+
if (!llmResult.allowed && llmResult.confidence && llmResult.confidence >= 90 && _brainExtractor) {
|
|
4827
|
+
try {
|
|
4828
|
+
await _brainExtractor.extract(
|
|
4829
|
+
{ actionDetail: input.aNext.actionDetail, eventId: "" },
|
|
4830
|
+
{
|
|
4831
|
+
confidence: llmResult.confidence / 100,
|
|
4832
|
+
reason: llmResult.reason || "",
|
|
4833
|
+
riskType: llmResult.riskType || "",
|
|
4834
|
+
actionDetail: input.aNext.actionDetail
|
|
4835
|
+
}
|
|
4836
|
+
);
|
|
4837
|
+
} catch {
|
|
4838
|
+
}
|
|
4839
|
+
}
|
|
4240
4840
|
return llmResult;
|
|
4241
4841
|
}
|
|
4242
4842
|
export {
|
|
4243
4843
|
TRUSTED_SKILLS_BASE,
|
|
4844
|
+
_resetBrainForTest,
|
|
4244
4845
|
runAnalysis
|
|
4245
4846
|
};
|
|
4246
4847
|
//# sourceMappingURL=coordinator.js.map
|