@opentrust/guards 7.3.3
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/agent/behavior-detector.ts +421 -0
- package/agent/config.ts +485 -0
- package/agent/content-injection-scanner.ts +170 -0
- package/agent/index.ts +22 -0
- package/agent/patterns/high-confidence.ts +119 -0
- package/agent/patterns/medium-confidence.ts +93 -0
- package/agent/patterns/types.ts +40 -0
- package/agent/runner.ts +261 -0
- package/agent/sanitizer.ts +153 -0
- package/agent/types.ts +283 -0
- package/index.ts +63 -0
- package/memory/index.ts +7 -0
- package/memory/store.ts +293 -0
- package/openclaw.plugin.json +44 -0
- package/package.json +55 -0
- package/platform-client/index.ts +241 -0
- package/platform-client/types.ts +132 -0
- package/plugin/commands.ts +151 -0
- package/plugin/hooks.ts +206 -0
- package/plugin/lifecycle.ts +252 -0
- package/plugin/state.ts +76 -0
- package/scripts/postinstall.mjs +135 -0
- package/tsconfig.json +28 -0
package/agent/types.ts
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenTrust Guards 插件类型定义
|
|
3
|
+
*
|
|
4
|
+
* 包含:
|
|
5
|
+
* - 配置类型
|
|
6
|
+
* - 分析相关类型
|
|
7
|
+
* - 行为检测相关类型(与 core/src/types.ts 保持同步)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// ── 配置类型 ─────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 插件配置
|
|
14
|
+
*/
|
|
15
|
+
export type OpenClawGuardConfig = {
|
|
16
|
+
/** 是否启用插件 */
|
|
17
|
+
enabled?: boolean;
|
|
18
|
+
/** 检测到风险时是否阻断执行 */
|
|
19
|
+
blockOnRisk?: boolean;
|
|
20
|
+
/** Core 平台 API Key(sk-og-xxx 格式),为空时自动注册 */
|
|
21
|
+
apiKey?: string;
|
|
22
|
+
/** 请求超时时间(毫秒) */
|
|
23
|
+
timeoutMs?: number;
|
|
24
|
+
/** Core API 地址(用于注册和行为检测) */
|
|
25
|
+
coreUrl?: string;
|
|
26
|
+
/** 注册时使用的 Agent 名称 */
|
|
27
|
+
agentName?: string;
|
|
28
|
+
/** Dashboard API 地址(用于观测数据上报) */
|
|
29
|
+
dashboardUrl?: string;
|
|
30
|
+
/** 日志级别 */
|
|
31
|
+
logLevel?: "debug" | "info" | "warn" | "error";
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// ── 分析类型 ─────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 分析目标
|
|
38
|
+
* 表示要分析的内容及其元数据
|
|
39
|
+
*/
|
|
40
|
+
export type AnalysisTarget = {
|
|
41
|
+
/** 内容类型 */
|
|
42
|
+
type: "message" | "tool_call" | "tool_result";
|
|
43
|
+
/** 内容文本 */
|
|
44
|
+
content: string;
|
|
45
|
+
/** 工具名称(如果是 tool_call 或 tool_result) */
|
|
46
|
+
toolName?: string;
|
|
47
|
+
/** 工具参数 */
|
|
48
|
+
toolParams?: Record<string, unknown>;
|
|
49
|
+
/** 附加元数据 */
|
|
50
|
+
metadata?: Record<string, unknown>;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 检测发现项
|
|
55
|
+
* 表示单个可疑内容发现
|
|
56
|
+
*/
|
|
57
|
+
export type Finding = {
|
|
58
|
+
/** 可疑内容 */
|
|
59
|
+
suspiciousContent: string;
|
|
60
|
+
/** 可疑原因 */
|
|
61
|
+
reason: string;
|
|
62
|
+
/** 置信度(0-1) */
|
|
63
|
+
confidence: number;
|
|
64
|
+
/** 在原文中的位置 */
|
|
65
|
+
location?: { start: number; end: number };
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 分析判定结果
|
|
70
|
+
*/
|
|
71
|
+
export type AnalysisVerdict = {
|
|
72
|
+
/** 是否为注入攻击 */
|
|
73
|
+
isInjection: boolean;
|
|
74
|
+
/** 置信度(0-1) */
|
|
75
|
+
confidence: number;
|
|
76
|
+
/** 判定原因 */
|
|
77
|
+
reason: string;
|
|
78
|
+
/** 所有发现项 */
|
|
79
|
+
findings: Finding[];
|
|
80
|
+
/** 分析的块数 */
|
|
81
|
+
chunksAnalyzed: number;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* OpenTrust API 响应格式
|
|
86
|
+
*/
|
|
87
|
+
export type OpenTrustApiResponse = {
|
|
88
|
+
/** 请求是否成功 */
|
|
89
|
+
ok: boolean;
|
|
90
|
+
/** 判定结果 */
|
|
91
|
+
verdict: {
|
|
92
|
+
isInjection: boolean;
|
|
93
|
+
confidence: number;
|
|
94
|
+
reason: string;
|
|
95
|
+
findings: Array<{ suspiciousContent: string; reason: string; confidence: number }>;
|
|
96
|
+
};
|
|
97
|
+
/** 错误信息 */
|
|
98
|
+
error?: string;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// ── 分析日志类型 ─────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 分析日志条目
|
|
105
|
+
* 用于记录每次分析的详细信息
|
|
106
|
+
*/
|
|
107
|
+
export type AnalysisLogEntry = {
|
|
108
|
+
/** 日志 ID */
|
|
109
|
+
id: number;
|
|
110
|
+
/** 时间戳 */
|
|
111
|
+
timestamp: string;
|
|
112
|
+
/** 目标类型 */
|
|
113
|
+
targetType: string;
|
|
114
|
+
/** 内容长度 */
|
|
115
|
+
contentLength: number;
|
|
116
|
+
/** 分析的块数 */
|
|
117
|
+
chunksAnalyzed: number;
|
|
118
|
+
/** 判定结果 */
|
|
119
|
+
verdict: AnalysisVerdict;
|
|
120
|
+
/** 分析耗时(毫秒) */
|
|
121
|
+
durationMs: number;
|
|
122
|
+
/** 是否被阻断 */
|
|
123
|
+
blocked: boolean;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// ── 日志器类型 ───────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* 日志器接口
|
|
130
|
+
* 与 OpenClaw 的日志 API 兼容
|
|
131
|
+
*/
|
|
132
|
+
export type Logger = {
|
|
133
|
+
info: (msg: string) => void;
|
|
134
|
+
warn: (msg: string) => void;
|
|
135
|
+
error: (msg: string) => void;
|
|
136
|
+
debug?: (msg: string) => void;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// ── 脱敏类型 ─────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* 脱敏结果
|
|
143
|
+
*/
|
|
144
|
+
export type SanitizeResult = {
|
|
145
|
+
/** 脱敏后的文本 */
|
|
146
|
+
sanitized: string;
|
|
147
|
+
/** 各类别的脱敏数量 */
|
|
148
|
+
redactions: Record<string, number>;
|
|
149
|
+
/** 总脱敏数量 */
|
|
150
|
+
totalRedactions: number;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// ── 行为检测类型(与 core/src/types.ts 保持同步)────
|
|
154
|
+
|
|
155
|
+
/** 风险级别 */
|
|
156
|
+
export type RiskLevel = "no_risk" | "low" | "medium" | "high" | "critical";
|
|
157
|
+
|
|
158
|
+
/** 评估动作 */
|
|
159
|
+
export type AssessAction = "allow" | "alert" | "block";
|
|
160
|
+
|
|
161
|
+
/** 检测风险类型 */
|
|
162
|
+
export type DetectionRiskType = "PROMPT_INJECTION" | "DATA_EXFILTRATION" | "COMMAND_EXECUTION";
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 检测发现
|
|
166
|
+
* 表示单个风险发现
|
|
167
|
+
*/
|
|
168
|
+
export type DetectionFinding = {
|
|
169
|
+
/** 风险级别 */
|
|
170
|
+
riskLevel: RiskLevel;
|
|
171
|
+
/** 风险类型 */
|
|
172
|
+
riskType: DetectionRiskType;
|
|
173
|
+
/** 风险内容 */
|
|
174
|
+
riskContent: string;
|
|
175
|
+
/** 原因说明 */
|
|
176
|
+
reason: string;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 工具链条目
|
|
181
|
+
* 记录单个已完成的 tool call
|
|
182
|
+
*/
|
|
183
|
+
export type ToolChainEntry = {
|
|
184
|
+
/** 序号 */
|
|
185
|
+
seq: number;
|
|
186
|
+
/** 工具名称 */
|
|
187
|
+
toolName: string;
|
|
188
|
+
/** 脱敏后的参数 */
|
|
189
|
+
sanitizedParams: Record<string, string>;
|
|
190
|
+
/** 执行结果 */
|
|
191
|
+
outcome: "success" | "error" | "timeout";
|
|
192
|
+
/** 执行耗时(毫秒) */
|
|
193
|
+
durationMs: number;
|
|
194
|
+
/** 结果类别 */
|
|
195
|
+
resultCategory: "text_small" | "text_large" | "binary" | "empty" | "error";
|
|
196
|
+
/** 结果大小(字节) */
|
|
197
|
+
resultSizeBytes: number;
|
|
198
|
+
/** 数据来源(用于追踪数据流) */
|
|
199
|
+
dataFlowFrom?: string;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* 待评估的工具调用
|
|
204
|
+
*/
|
|
205
|
+
export type PendingToolCall = {
|
|
206
|
+
/** 工具名称 */
|
|
207
|
+
toolName: string;
|
|
208
|
+
/** 参数 */
|
|
209
|
+
params: Record<string, string>;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* 内容注入发现
|
|
214
|
+
* 记录扫描到的注入模式
|
|
215
|
+
*/
|
|
216
|
+
export type ContentInjectionFinding = {
|
|
217
|
+
/** 注入类别 */
|
|
218
|
+
category: string;
|
|
219
|
+
/** 置信度 */
|
|
220
|
+
confidence: "high" | "medium";
|
|
221
|
+
/** 匹配到的文本 */
|
|
222
|
+
matchedText: string;
|
|
223
|
+
/** 匹配的模式名称 */
|
|
224
|
+
pattern: string;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* 行为评估请求
|
|
229
|
+
* 发送给 Core API 进行风险评估
|
|
230
|
+
*/
|
|
231
|
+
export type BehaviorAssessRequest = {
|
|
232
|
+
/** Agent ID */
|
|
233
|
+
agentId: string;
|
|
234
|
+
/** 会话 Key */
|
|
235
|
+
sessionKey: string;
|
|
236
|
+
/** 运行 ID */
|
|
237
|
+
runId: string;
|
|
238
|
+
/** 用户意图 */
|
|
239
|
+
userIntent: string;
|
|
240
|
+
/** 已完成的工具链 */
|
|
241
|
+
toolChain: ToolChainEntry[];
|
|
242
|
+
/** 待评估的工具调用 */
|
|
243
|
+
pendingTool?: PendingToolCall;
|
|
244
|
+
/** 内容注入发现 */
|
|
245
|
+
contentFindings?: ContentInjectionFinding[];
|
|
246
|
+
/** 上下文信息 */
|
|
247
|
+
context: {
|
|
248
|
+
/** 消息历史长度 */
|
|
249
|
+
messageHistoryLength: number;
|
|
250
|
+
/** 最近的用户消息 */
|
|
251
|
+
recentUserMessages: string[];
|
|
252
|
+
};
|
|
253
|
+
/** 元数据 */
|
|
254
|
+
meta: {
|
|
255
|
+
/** 插件版本 */
|
|
256
|
+
pluginVersion: string;
|
|
257
|
+
/** 客户端时间戳 */
|
|
258
|
+
clientTimestamp: string;
|
|
259
|
+
};
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* 行为评估响应
|
|
264
|
+
* Core API 返回的风险评估结果
|
|
265
|
+
*/
|
|
266
|
+
export type BehaviorAssessResponse = {
|
|
267
|
+
/** 行为 ID */
|
|
268
|
+
behaviorId: string;
|
|
269
|
+
/** 风险级别 */
|
|
270
|
+
riskLevel: RiskLevel;
|
|
271
|
+
/** 异常类型列表 */
|
|
272
|
+
anomalyTypes: string[];
|
|
273
|
+
/** 置信度(0-1) */
|
|
274
|
+
confidence: number;
|
|
275
|
+
/** 建议动作 */
|
|
276
|
+
action: AssessAction;
|
|
277
|
+
/** 解释说明 */
|
|
278
|
+
explanation: string;
|
|
279
|
+
/** 受影响的工具序号 */
|
|
280
|
+
affectedTools: number[];
|
|
281
|
+
/** 检测发现列表 */
|
|
282
|
+
findings?: DetectionFinding[];
|
|
283
|
+
};
|
package/index.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenTrust Guard — OpenClaw 安全插件
|
|
3
|
+
*
|
|
4
|
+
* 精简入口文件 — 将所有逻辑委托给 plugin/lifecycle 模块处理。
|
|
5
|
+
*
|
|
6
|
+
* 功能概述:
|
|
7
|
+
* - 为 OpenClaw AI Agent 提供运行时安全防护
|
|
8
|
+
* - 行为异常检测:分析 tool call 链,识别可疑模式
|
|
9
|
+
* - 内容注入扫描:检测工具返回结果中的提示词注入攻击
|
|
10
|
+
* - 自动注册到 Core 平台获取 API Key
|
|
11
|
+
* - 向 Dashboard 上报观测数据
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
15
|
+
import type { Logger } from "./agent/types.js";
|
|
16
|
+
import { createState } from "./plugin/state.js";
|
|
17
|
+
import { register, unregister } from "./plugin/lifecycle.js";
|
|
18
|
+
|
|
19
|
+
/** 日志前缀,用于区分来自本插件的日志消息 */
|
|
20
|
+
const LOG_PREFIX = "[opentrust-guard]";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 创建带前缀的日志器
|
|
24
|
+
* 包装 OpenClaw 提供的 logger,在每条消息前添加插件标识
|
|
25
|
+
*/
|
|
26
|
+
function createLogger(base: Logger): Logger {
|
|
27
|
+
return {
|
|
28
|
+
info: (msg: string) => base.info(`${LOG_PREFIX} ${msg}`),
|
|
29
|
+
warn: (msg: string) => base.warn(`${LOG_PREFIX} ${msg}`),
|
|
30
|
+
error: (msg: string) => base.error(`${LOG_PREFIX} ${msg}`),
|
|
31
|
+
debug: (msg: string) => base.debug?.(`${LOG_PREFIX} ${msg}`),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** 插件全局状态(凭证、检测器、客户端等) */
|
|
36
|
+
const state = createState();
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* OpenClaw 插件定义
|
|
40
|
+
*
|
|
41
|
+
* @property id - 插件唯一标识符
|
|
42
|
+
* @property name - 插件显示名称
|
|
43
|
+
* @property description - 插件功能描述
|
|
44
|
+
* @property register - 插件加载时调用,初始化所有功能
|
|
45
|
+
* @property unregister - 插件卸载时调用,清理资源
|
|
46
|
+
*/
|
|
47
|
+
const openClawGuardPlugin = {
|
|
48
|
+
id: "opentrust-guard",
|
|
49
|
+
name: "OpenTrust Guard",
|
|
50
|
+
description: "Behavioral anomaly detection for OpenClaw agents",
|
|
51
|
+
|
|
52
|
+
/** 插件注册回调 — 初始化行为检测器、凭证、钩子等 */
|
|
53
|
+
register(api: OpenClawPluginApi) {
|
|
54
|
+
register(api, createLogger(api.logger), state);
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
/** 插件注销回调 — 清理定时器、文件监视器等资源 */
|
|
58
|
+
async unregister() {
|
|
59
|
+
unregister(state);
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export default openClawGuardPlugin;
|
package/memory/index.ts
ADDED
package/memory/store.ts
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSONL 文件存储 — 用于分析日志记录
|
|
3
|
+
*
|
|
4
|
+
* 特点:
|
|
5
|
+
* - 无外部依赖
|
|
6
|
+
* - 仅追加模式(Append-only)
|
|
7
|
+
* - 支持分析日志、反馈记录、工具调用日志
|
|
8
|
+
*
|
|
9
|
+
* 文件格式:每行一个 JSON 对象(JSONL)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { AnalysisVerdict, AnalysisLogEntry, Logger } from "../agent/types.js";
|
|
13
|
+
import fs from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
|
|
16
|
+
/** 分析记录行 */
|
|
17
|
+
type AnalysisRow = {
|
|
18
|
+
/** 记录 ID */
|
|
19
|
+
id: number;
|
|
20
|
+
/** 时间戳 */
|
|
21
|
+
timestamp: string;
|
|
22
|
+
/** 目标类型 */
|
|
23
|
+
targetType: string;
|
|
24
|
+
/** 内容长度 */
|
|
25
|
+
contentLength: number;
|
|
26
|
+
/** 分析的块数 */
|
|
27
|
+
chunksAnalyzed: number;
|
|
28
|
+
/** 判定结果 */
|
|
29
|
+
verdict: AnalysisVerdict;
|
|
30
|
+
/** 分析耗时(毫秒) */
|
|
31
|
+
durationMs: number;
|
|
32
|
+
/** 是否被阻断 */
|
|
33
|
+
blocked: boolean;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** 反馈记录行 */
|
|
37
|
+
type FeedbackRow = {
|
|
38
|
+
/** 记录 ID */
|
|
39
|
+
id: number;
|
|
40
|
+
/** 时间戳 */
|
|
41
|
+
timestamp: string;
|
|
42
|
+
/** 关联的分析 ID */
|
|
43
|
+
analysisId: number | null;
|
|
44
|
+
/** 反馈类型 */
|
|
45
|
+
feedbackType: string;
|
|
46
|
+
/** 反馈原因 */
|
|
47
|
+
reason: string | null;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 从 JSONL 文件读取所有行
|
|
52
|
+
*
|
|
53
|
+
* @param filePath - 文件路径
|
|
54
|
+
* @returns 解析后的对象数组
|
|
55
|
+
*/
|
|
56
|
+
function readLines<T>(filePath: string): T[] {
|
|
57
|
+
if (!fs.existsSync(filePath)) return [];
|
|
58
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
59
|
+
const results: T[] = [];
|
|
60
|
+
for (const line of content.split("\n")) {
|
|
61
|
+
if (!line.trim()) continue;
|
|
62
|
+
try { results.push(JSON.parse(line) as T); } catch { /* 跳过格式错误的行 */ }
|
|
63
|
+
}
|
|
64
|
+
return results;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 获取记录中的最大 ID
|
|
69
|
+
*
|
|
70
|
+
* @param rows - 记录数组
|
|
71
|
+
* @returns 最大 ID
|
|
72
|
+
*/
|
|
73
|
+
function getMaxId<T extends { id: number }>(rows: T[]): number {
|
|
74
|
+
let max = 0;
|
|
75
|
+
for (const row of rows) if (row.id > max) max = row.id;
|
|
76
|
+
return max;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 分析日志存储类
|
|
81
|
+
*
|
|
82
|
+
* 管理三类日志:
|
|
83
|
+
* - 分析日志:记录每次内容分析的详情
|
|
84
|
+
* - 反馈日志:记录用户反馈(误报/漏报)
|
|
85
|
+
* - 工具调用日志:作为 Dashboard 不可用时的后备存储
|
|
86
|
+
*/
|
|
87
|
+
export class AnalysisStore {
|
|
88
|
+
/** 分析日志文件路径 */
|
|
89
|
+
private analysisFile: string;
|
|
90
|
+
/** 反馈日志文件路径 */
|
|
91
|
+
private feedbackFile: string;
|
|
92
|
+
/** 下一个分析 ID */
|
|
93
|
+
private nextAnalysisId: number;
|
|
94
|
+
/** 下一个反馈 ID */
|
|
95
|
+
private nextFeedbackId: number;
|
|
96
|
+
/** 日志器 */
|
|
97
|
+
private log: Logger;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 创建分析存储
|
|
101
|
+
*
|
|
102
|
+
* @param logPath - 日志目录路径
|
|
103
|
+
* @param log - 日志器
|
|
104
|
+
*/
|
|
105
|
+
constructor(logPath: string, log: Logger) {
|
|
106
|
+
this.log = log;
|
|
107
|
+
|
|
108
|
+
// 确保日志目录存在
|
|
109
|
+
if (!fs.existsSync(logPath)) fs.mkdirSync(logPath, { recursive: true });
|
|
110
|
+
|
|
111
|
+
// 初始化文件路径
|
|
112
|
+
this.analysisFile = path.join(logPath, "opentrust-guard-analyses.jsonl");
|
|
113
|
+
this.feedbackFile = path.join(logPath, "opentrust-guard-feedback.jsonl");
|
|
114
|
+
|
|
115
|
+
// 加载已有数据,确定下一个 ID
|
|
116
|
+
const analyses = readLines<AnalysisRow>(this.analysisFile);
|
|
117
|
+
const feedback = readLines<FeedbackRow>(this.feedbackFile);
|
|
118
|
+
this.nextAnalysisId = getMaxId(analyses) + 1;
|
|
119
|
+
this.nextFeedbackId = getMaxId(feedback) + 1;
|
|
120
|
+
|
|
121
|
+
this.log.info(`Analysis store initialized at ${logPath}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 记录分析日志
|
|
126
|
+
*
|
|
127
|
+
* @param entry - 分析条目
|
|
128
|
+
* @returns 分配的 ID
|
|
129
|
+
*/
|
|
130
|
+
logAnalysis(entry: {
|
|
131
|
+
targetType: string;
|
|
132
|
+
contentLength: number;
|
|
133
|
+
chunksAnalyzed: number;
|
|
134
|
+
verdict: AnalysisVerdict;
|
|
135
|
+
durationMs: number;
|
|
136
|
+
blocked: boolean;
|
|
137
|
+
}): number {
|
|
138
|
+
const id = this.nextAnalysisId++;
|
|
139
|
+
const row: AnalysisRow = { id, timestamp: new Date().toISOString(), ...entry };
|
|
140
|
+
fs.appendFileSync(this.analysisFile, JSON.stringify(row) + "\n", "utf-8");
|
|
141
|
+
return id;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* 获取最近的分析日志
|
|
146
|
+
*
|
|
147
|
+
* @param limit - 返回数量限制
|
|
148
|
+
* @returns 分析日志列表(按时间倒序)
|
|
149
|
+
*/
|
|
150
|
+
getRecentLogs(limit = 20): AnalysisLogEntry[] {
|
|
151
|
+
const rows = readLines<AnalysisRow>(this.analysisFile);
|
|
152
|
+
rows.sort((a, b) => (b.timestamp > a.timestamp ? 1 : b.timestamp < a.timestamp ? -1 : 0));
|
|
153
|
+
return rows.slice(0, limit);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* 获取指定时间窗口内的阻断数量
|
|
158
|
+
*
|
|
159
|
+
* @param windowHours - 时间窗口(小时)
|
|
160
|
+
* @returns 阻断数量
|
|
161
|
+
*/
|
|
162
|
+
getBlockedCount(windowHours = 24): number {
|
|
163
|
+
const cutoff = new Date(Date.now() - windowHours * 3600_000).toISOString();
|
|
164
|
+
return readLines<AnalysisRow>(this.analysisFile).filter((r) => r.blocked && r.timestamp >= cutoff).length;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* 获取统计信息
|
|
169
|
+
*
|
|
170
|
+
* @returns 统计数据
|
|
171
|
+
*/
|
|
172
|
+
getStats(): { totalAnalyses: number; totalBlocked: number; blockedLast24h: number; avgDurationMs: number } {
|
|
173
|
+
const rows = readLines<AnalysisRow>(this.analysisFile);
|
|
174
|
+
let totalBlocked = 0, totalDuration = 0;
|
|
175
|
+
for (const row of rows) { if (row.blocked) totalBlocked++; totalDuration += row.durationMs; }
|
|
176
|
+
return {
|
|
177
|
+
totalAnalyses: rows.length,
|
|
178
|
+
totalBlocked,
|
|
179
|
+
blockedLast24h: this.getBlockedCount(24),
|
|
180
|
+
avgDurationMs: rows.length > 0 ? Math.round(totalDuration / rows.length) : 0,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 获取最近的检测记录(仅包含检测到注入的)
|
|
186
|
+
*
|
|
187
|
+
* @param limit - 返回数量限制
|
|
188
|
+
* @returns 检测记录列表
|
|
189
|
+
*/
|
|
190
|
+
getRecentDetections(limit = 10): AnalysisLogEntry[] {
|
|
191
|
+
const rows = readLines<AnalysisRow>(this.analysisFile).filter((r) => r.verdict.isInjection);
|
|
192
|
+
rows.sort((a, b) => (b.timestamp > a.timestamp ? 1 : b.timestamp < a.timestamp ? -1 : 0));
|
|
193
|
+
return rows.slice(0, limit);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* 记录用户反馈
|
|
198
|
+
*
|
|
199
|
+
* @param entry - 反馈条目
|
|
200
|
+
* @returns 分配的 ID
|
|
201
|
+
*/
|
|
202
|
+
logFeedback(entry: {
|
|
203
|
+
analysisId?: number;
|
|
204
|
+
feedbackType: "false_positive" | "missed_detection";
|
|
205
|
+
reason?: string;
|
|
206
|
+
}): number {
|
|
207
|
+
const id = this.nextFeedbackId++;
|
|
208
|
+
const row: FeedbackRow = {
|
|
209
|
+
id,
|
|
210
|
+
timestamp: new Date().toISOString(),
|
|
211
|
+
analysisId: entry.analysisId ?? null,
|
|
212
|
+
feedbackType: entry.feedbackType,
|
|
213
|
+
reason: entry.reason ?? null,
|
|
214
|
+
};
|
|
215
|
+
fs.appendFileSync(this.feedbackFile, JSON.stringify(row) + "\n", "utf-8");
|
|
216
|
+
return id;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* 获取反馈统计
|
|
221
|
+
*
|
|
222
|
+
* @returns 反馈统计数据
|
|
223
|
+
*/
|
|
224
|
+
getFeedbackStats(): { falsePositives: number; missedDetections: number } {
|
|
225
|
+
const rows = readLines<FeedbackRow>(this.feedbackFile);
|
|
226
|
+
let falsePositives = 0, missedDetections = 0;
|
|
227
|
+
for (const row of rows) {
|
|
228
|
+
if (row.feedbackType === "false_positive") falsePositives++;
|
|
229
|
+
if (row.feedbackType === "missed_detection") missedDetections++;
|
|
230
|
+
}
|
|
231
|
+
return { falsePositives, missedDetections };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ── 工具调用日志(Dashboard 不可用时的后备存储)────
|
|
235
|
+
|
|
236
|
+
/** 工具调用日志文件路径 */
|
|
237
|
+
private get toolCallFile(): string {
|
|
238
|
+
return path.join(path.dirname(this.analysisFile), "opentrust-guard-tool-calls.jsonl");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* 记录工具调用
|
|
243
|
+
* 当 Dashboard 不可用时,本地保存工具调用记录
|
|
244
|
+
*
|
|
245
|
+
* @param entry - 工具调用条目
|
|
246
|
+
*/
|
|
247
|
+
logToolCall(entry: {
|
|
248
|
+
agentId: string;
|
|
249
|
+
toolName: string;
|
|
250
|
+
params?: Record<string, unknown>;
|
|
251
|
+
phase: "before" | "after";
|
|
252
|
+
result?: unknown;
|
|
253
|
+
error?: string;
|
|
254
|
+
durationMs?: number;
|
|
255
|
+
blocked?: boolean;
|
|
256
|
+
blockReason?: string;
|
|
257
|
+
}): void {
|
|
258
|
+
fs.appendFileSync(this.toolCallFile, JSON.stringify({ timestamp: new Date().toISOString(), ...entry }) + "\n", "utf-8");
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* 获取最近的工具调用记录
|
|
263
|
+
*
|
|
264
|
+
* @param limit - 返回数量限制
|
|
265
|
+
* @returns 工具调用记录列表
|
|
266
|
+
*/
|
|
267
|
+
getRecentToolCalls(limit = 50): Array<Record<string, unknown>> {
|
|
268
|
+
const rows = readLines<Record<string, unknown>>(this.toolCallFile);
|
|
269
|
+
rows.sort((a, b) => {
|
|
270
|
+
const ta = (a.timestamp as string) || "";
|
|
271
|
+
const tb = (b.timestamp as string) || "";
|
|
272
|
+
return tb > ta ? 1 : tb < ta ? -1 : 0;
|
|
273
|
+
});
|
|
274
|
+
return rows.slice(0, limit);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* 关闭存储
|
|
279
|
+
* JSONL 存储无需关闭操作
|
|
280
|
+
*/
|
|
281
|
+
close(): void { /* no-op for JSONL store */ }
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* 创建分析存储实例
|
|
286
|
+
*
|
|
287
|
+
* @param logPath - 日志目录路径
|
|
288
|
+
* @param log - 日志器
|
|
289
|
+
* @returns 分析存储实例
|
|
290
|
+
*/
|
|
291
|
+
export function createAnalysisStore(logPath: string, log: Logger): AnalysisStore {
|
|
292
|
+
return new AnalysisStore(logPath, log);
|
|
293
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "opentrust-guard",
|
|
3
|
+
"name": "OpenTrust Guard",
|
|
4
|
+
"description": "AI security guard for OpenClaw agents: prompt injection detection, credential scanning, and behavioral monitoring.",
|
|
5
|
+
"version": "7.3.3",
|
|
6
|
+
"configSchema": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"enabled": {
|
|
11
|
+
"type": "boolean",
|
|
12
|
+
"description": "Enable or disable the guard (default: true)"
|
|
13
|
+
},
|
|
14
|
+
"blockOnRisk": {
|
|
15
|
+
"type": "boolean",
|
|
16
|
+
"description": "Block tool calls when behavioral risk is detected (default: true)"
|
|
17
|
+
},
|
|
18
|
+
"apiKey": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"description": "API key (optional — auto-registered if missing)"
|
|
21
|
+
},
|
|
22
|
+
"timeoutMs": {
|
|
23
|
+
"type": "number",
|
|
24
|
+
"description": "Timeout for cloud assessment in milliseconds (default: 60000)"
|
|
25
|
+
},
|
|
26
|
+
"agentName": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"description": "Agent name used during registration (default: OpenClaw Agent)"
|
|
29
|
+
},
|
|
30
|
+
"coreUrl": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"description": "Core API URL (default: http://localhost:53666)"
|
|
33
|
+
},
|
|
34
|
+
"dashboardUrl": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "Dashboard URL for observation reporting (default: http://localhost:53667)"
|
|
37
|
+
},
|
|
38
|
+
"logLevel": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"description": "Log level: debug, info, warn, error (default: info)"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|