@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.
@@ -0,0 +1,485 @@
1
+ /**
2
+ * 插件配置和凭证管理
3
+ *
4
+ * 功能:
5
+ * - 定义默认的 Core 和 Dashboard URL
6
+ * - 管理 Core 平台凭证(加载、保存、注册)
7
+ * - 轮询账户激活状态
8
+ * - 读取 Agent Profile(从 OpenClaw 工作区)
9
+ * - 解析配置(合并默认值和用户配置)
10
+ */
11
+
12
+ import type { OpenClawGuardConfig } from "./types.js";
13
+ import os from "node:os";
14
+ import path from "node:path";
15
+ import fs from "node:fs";
16
+
17
+ // ── 常量 ─────────────────────────────────────────────
18
+
19
+ /** 默认 Core API 地址(可通过环境变量 OG_CORE_URL 覆盖) */
20
+ export const DEFAULT_CORE_URL =
21
+ process.env.OG_CORE_URL ?? "http://localhost:53666";
22
+
23
+ /** 默认 Dashboard API 地址(可通过环境变量 OG_DASHBOARD_URL 覆盖) */
24
+ export const DEFAULT_DASHBOARD_URL =
25
+ process.env.OG_DASHBOARD_URL ?? "http://localhost:53667";
26
+
27
+ /** 凭证存储目录 */
28
+ const CREDENTIALS_DIR = path.join(os.homedir(), ".openclaw/credentials/opentrust-guard");
29
+ /** 凭证文件路径 */
30
+ const CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, "credentials.json");
31
+
32
+ // ── Core 凭证管理 ────────────────────────────────────
33
+
34
+ /**
35
+ * Core 平台凭证
36
+ */
37
+ export type CoreCredentials = {
38
+ /** API Key(sk-og-xxx 格式) */
39
+ apiKey: string;
40
+ /** Agent ID */
41
+ agentId: string;
42
+ /** 激活链接(未激活时有值) */
43
+ claimUrl: string;
44
+ /** 验证码 */
45
+ verificationCode: string;
46
+ /** 关联邮箱(激活后有值) */
47
+ email?: string;
48
+ };
49
+
50
+ /**
51
+ * 从本地文件加载凭证
52
+ * @returns 凭证对象,如果不存在或无效则返回 null
53
+ */
54
+ export function loadCoreCredentials(): CoreCredentials | null {
55
+ try {
56
+ if (!fs.existsSync(CREDENTIALS_FILE)) return null;
57
+ const data = JSON.parse(fs.readFileSync(CREDENTIALS_FILE, "utf-8"));
58
+ if (typeof data.apiKey === "string" && typeof data.agentId === "string") {
59
+ return data as CoreCredentials;
60
+ }
61
+ return null;
62
+ } catch {
63
+ return null;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * 保存凭证到本地文件
69
+ * @param creds - 凭证对象
70
+ */
71
+ export function saveCoreCredentials(creds: CoreCredentials): void {
72
+ if (!fs.existsSync(CREDENTIALS_DIR)) {
73
+ fs.mkdirSync(CREDENTIALS_DIR, { recursive: true });
74
+ }
75
+ fs.writeFileSync(CREDENTIALS_FILE, JSON.stringify(creds, null, 2), "utf-8");
76
+ }
77
+
78
+ /** 注册结果 */
79
+ export type RegisterResult = {
80
+ /** 凭证 */
81
+ credentials: CoreCredentials;
82
+ /** 激活链接 */
83
+ activateUrl: string;
84
+ /** 登录链接 */
85
+ loginUrl: string;
86
+ };
87
+
88
+ /**
89
+ * 向 Core 平台注册新 Agent
90
+ *
91
+ * @param name - Agent 名称
92
+ * @param description - Agent 描述
93
+ * @param coreUrl - Core API 地址
94
+ * @returns 注册结果(包含凭证和激活链接)
95
+ */
96
+ export async function registerWithCore(
97
+ name: string,
98
+ description: string,
99
+ coreUrl: string = DEFAULT_CORE_URL,
100
+ ): Promise<RegisterResult> {
101
+ const response = await fetch(`${coreUrl}/api/v1/agents/register`, {
102
+ method: "POST",
103
+ headers: { "Content-Type": "application/json" },
104
+ body: JSON.stringify({ name, description }),
105
+ });
106
+
107
+ if (!response.ok) {
108
+ const text = await response.text().catch(() => "");
109
+ throw new Error(
110
+ `Registration failed: ${response.status} ${response.statusText}${text ? ` — ${text}` : ""}`,
111
+ );
112
+ }
113
+
114
+ const json = (await response.json()) as {
115
+ success: boolean;
116
+ agent?: { id: string; api_key: string };
117
+ activate_url?: string;
118
+ login_url?: string;
119
+ error?: string;
120
+ };
121
+
122
+ if (!json.success || !json.agent) {
123
+ throw new Error(`Registration error: ${json.error ?? "unknown"}`);
124
+ }
125
+
126
+ // 构建凭证对象
127
+ const creds: CoreCredentials = {
128
+ apiKey: json.agent.api_key,
129
+ agentId: json.agent.id,
130
+ claimUrl: json.activate_url ?? "",
131
+ verificationCode: "",
132
+ };
133
+
134
+ // 保存到本地
135
+ saveCoreCredentials(creds);
136
+
137
+ return {
138
+ credentials: creds,
139
+ activateUrl: json.activate_url ?? "",
140
+ loginUrl: json.login_url ?? `${coreUrl}/login`,
141
+ };
142
+ }
143
+
144
+ // ── 账户状态轮询 ─────────────────────────────────────
145
+
146
+ /**
147
+ * 轮询账户邮箱激活状态
148
+ * 检查 Agent 是否已通过邮箱激活
149
+ *
150
+ * @param apiKey - API Key
151
+ * @param coreUrl - Core API 地址
152
+ * @returns 邮箱和状态(如果已激活),否则返回 null
153
+ */
154
+ export async function pollAccountEmail(
155
+ apiKey: string,
156
+ coreUrl: string = DEFAULT_CORE_URL,
157
+ ): Promise<{ email: string; status: string } | null> {
158
+ try {
159
+ const res = await fetch(`${coreUrl}/api/v1/account`, {
160
+ headers: { Authorization: `Bearer ${apiKey}` },
161
+ });
162
+ if (!res.ok) return null;
163
+ const data = (await res.json()) as { success: boolean; email?: string | null; status?: string };
164
+ if (data.success && data.email && data.status === "active") {
165
+ return { email: data.email, status: data.status };
166
+ }
167
+ return null;
168
+ } catch {
169
+ return null;
170
+ }
171
+ }
172
+
173
+ // ── 默认配置 ─────────────────────────────────────────
174
+
175
+ /** 默认插件配置 */
176
+ export const DEFAULT_CONFIG: Required<OpenClawGuardConfig> = {
177
+ enabled: true,
178
+ blockOnRisk: true,
179
+ apiKey: process.env.OG_API_KEY ?? "",
180
+ timeoutMs: 60000,
181
+ coreUrl: DEFAULT_CORE_URL,
182
+ agentName: "OpenClaw Agent",
183
+ dashboardUrl: DEFAULT_DASHBOARD_URL,
184
+ logLevel: "info",
185
+ };
186
+
187
+ // ── 辅助函数 ─────────────────────────────────────────
188
+
189
+ /**
190
+ * 安全读取文件内容
191
+ * @param filePath - 文件路径
192
+ * @returns 文件内容,读取失败返回空字符串
193
+ */
194
+ function readFileSafe(filePath: string): string {
195
+ try { return fs.readFileSync(filePath, "utf-8"); } catch { return ""; }
196
+ }
197
+
198
+ /**
199
+ * 安全读取 JSON 文件
200
+ * @param filePath - 文件路径
201
+ * @returns 解析后的对象,读取或解析失败返回 null
202
+ */
203
+ function readJsonSafe(filePath: string): Record<string, unknown> | null {
204
+ try { return JSON.parse(fs.readFileSync(filePath, "utf-8")); } catch { return null; }
205
+ }
206
+
207
+ /**
208
+ * 检查值是否为未填写的 IDENTITY.md 占位符
209
+ * 占位符格式:_(...some hint...)_
210
+ */
211
+ function isPlaceholder(value: string): boolean {
212
+ return /^_\(.*\)_$/.test(value.trim());
213
+ }
214
+
215
+ /**
216
+ * 从 Markdown 内容中解析指定字段
217
+ * 支持格式:`- **FieldName:** value`
218
+ *
219
+ * @param content - Markdown 内容
220
+ * @param field - 字段名
221
+ * @returns 字段值,未找到返回空字符串
222
+ */
223
+ function parseIdentityField(content: string, field: string): string {
224
+ const prefix = `- **${field}:**`;
225
+ const lines = content.split("\n");
226
+ for (let i = 0; i < lines.length; i++) {
227
+ const trimmed = lines[i]!.trim();
228
+ if (trimmed.startsWith(prefix)) {
229
+ // 检查同一行的内联值
230
+ const inline = trimmed.slice(prefix.length).trim();
231
+ if (inline && !isPlaceholder(inline)) return inline;
232
+ // 检查下一行
233
+ const next = lines[i + 1]?.trim();
234
+ if (next && !next.startsWith("-") && !next.startsWith("#") && !isPlaceholder(next)) return next;
235
+ }
236
+ }
237
+ return "";
238
+ }
239
+
240
+ /**
241
+ * 从 IDENTITY.md 读取 Agent 名称
242
+ * @returns Agent 名称,未找到返回 null
243
+ */
244
+ function readIdentityName(): string | null {
245
+ try {
246
+ const identityPath = path.join(os.homedir(), ".openclaw/workspace/IDENTITY.md");
247
+ const content = fs.readFileSync(identityPath, "utf-8");
248
+ return parseIdentityField(content, "Name") || null;
249
+ } catch {
250
+ return null;
251
+ }
252
+ }
253
+
254
+ // ── Agent Profile ────────────────────────────────────
255
+
256
+ /**
257
+ * Agent Profile 类型
258
+ * 包含从 OpenClaw 工作区读取的完整 Agent 信息
259
+ */
260
+ export type AgentProfile = {
261
+ /** 表情符号 */
262
+ emoji: string;
263
+ /** 生物类型 */
264
+ creature: string;
265
+ /** 个性氛围 */
266
+ vibe: string;
267
+ /** AI 模型 */
268
+ model: string;
269
+ /** 模型提供商 */
270
+ provider: string;
271
+ /** 所有者名称 */
272
+ ownerName: string;
273
+ /** 技能列表 */
274
+ skills: { name: string; description?: string }[];
275
+ /** 插件列表 */
276
+ plugins: { name: string; enabled: boolean }[];
277
+ /** 钩子列表 */
278
+ hooks: { name: string; enabled: boolean }[];
279
+ /** 已连接的系统 */
280
+ connectedSystems: string[];
281
+ /** 活跃的渠道 */
282
+ channels: string[];
283
+ /** 会话数量 */
284
+ sessionCount: number;
285
+ /** 最后活跃时间 */
286
+ lastActive: string | null;
287
+ /** 工作区 Markdown 文件内容 */
288
+ workspaceFiles: {
289
+ soul: string;
290
+ identity: string;
291
+ user: string;
292
+ agents: string;
293
+ tools: string;
294
+ heartbeat: string;
295
+ };
296
+ /** Bootstrap 文件是否存在 */
297
+ bootstrapExists: boolean;
298
+ /** 定时任务列表 */
299
+ cronJobs: Array<{ id?: string; schedule?: string; task?: string; enabled?: boolean }>;
300
+ };
301
+
302
+ /**
303
+ * 读取 Agent Profile
304
+ * 从 OpenClaw 工作区收集 Agent 的完整信息
305
+ *
306
+ * @returns Agent Profile 对象
307
+ */
308
+ export function readAgentProfile(): AgentProfile {
309
+ const openclawDir = path.join(os.homedir(), ".openclaw");
310
+
311
+ // 初始化空 Profile
312
+ const result: AgentProfile = {
313
+ emoji: "", creature: "", vibe: "", model: "", provider: "", ownerName: "",
314
+ skills: [], plugins: [], hooks: [], connectedSystems: [], channels: [],
315
+ sessionCount: 0, lastActive: null,
316
+ workspaceFiles: { soul: "", identity: "", user: "", agents: "", tools: "", heartbeat: "" },
317
+ bootstrapExists: false,
318
+ cronJobs: [],
319
+ };
320
+
321
+ // 读取 openclaw.json 配置
322
+ const config = readJsonSafe(path.join(openclawDir, "openclaw.json"));
323
+ let workspacePath = path.join(openclawDir, "workspace");
324
+
325
+ if (config) {
326
+ // 解析模型配置
327
+ const agentsConfig = config.agents as { defaults?: { model?: { primary?: string }; workspace?: string } } | undefined;
328
+ const defaultModel = agentsConfig?.defaults?.model?.primary ?? "";
329
+ if (defaultModel.includes("/")) {
330
+ const [provider, model] = defaultModel.split("/", 2);
331
+ result.provider = provider ?? "";
332
+ result.model = model ?? "";
333
+ } else {
334
+ result.model = defaultModel;
335
+ }
336
+ if (agentsConfig?.defaults?.workspace) workspacePath = agentsConfig.defaults.workspace;
337
+
338
+ // 解析插件配置
339
+ const pluginsConfig = config.plugins as { entries?: Record<string, { enabled?: boolean }> } | undefined;
340
+ if (pluginsConfig?.entries) {
341
+ for (const [name, entry] of Object.entries(pluginsConfig.entries)) {
342
+ result.plugins.push({ name, enabled: entry?.enabled !== false });
343
+ }
344
+ }
345
+
346
+ // 解析钩子配置
347
+ const hooksConfig = config.hooks as { internal?: { entries?: Record<string, { enabled?: boolean }> } } | undefined;
348
+ if (hooksConfig?.internal?.entries) {
349
+ for (const [name, entry] of Object.entries(hooksConfig.internal.entries)) {
350
+ result.hooks.push({ name, enabled: entry?.enabled !== false });
351
+ }
352
+ }
353
+ }
354
+
355
+ // 读取工作区 Markdown 文件
356
+ const identityContent = readFileSafe(path.join(workspacePath, "IDENTITY.md"));
357
+ result.workspaceFiles.identity = identityContent;
358
+ result.workspaceFiles.soul = readFileSafe(path.join(workspacePath, "SOUL.md"));
359
+ result.workspaceFiles.user = readFileSafe(path.join(workspacePath, "USER.md"));
360
+ result.workspaceFiles.agents = readFileSafe(path.join(workspacePath, "AGENTS.md"));
361
+ result.workspaceFiles.tools = readFileSafe(path.join(workspacePath, "TOOLS.md"));
362
+ result.workspaceFiles.heartbeat = readFileSafe(path.join(workspacePath, "HEARTBEAT.md"));
363
+ result.bootstrapExists = fs.existsSync(path.join(workspacePath, "BOOTSTRAP.md"));
364
+
365
+ // 解析身份字段(使用 OpenClaw 默认值作为后备)
366
+ if (identityContent) {
367
+ result.emoji = parseIdentityField(identityContent, "Emoji") || "🦞";
368
+ result.creature = parseIdentityField(identityContent, "Creature") || "OpenClaw AI Agent";
369
+ result.vibe = parseIdentityField(identityContent, "Vibe") || "Secure";
370
+ } else {
371
+ result.emoji = "🦞";
372
+ result.creature = "OpenClaw AI Agent";
373
+ result.vibe = "Secure";
374
+ }
375
+
376
+ // 解析所有者名称
377
+ if (result.workspaceFiles.user) {
378
+ result.ownerName = parseIdentityField(result.workspaceFiles.user, "Name")
379
+ || parseIdentityField(result.workspaceFiles.user, "name");
380
+ }
381
+
382
+ // 发现技能
383
+ try {
384
+ const skillsDir = path.join(workspacePath, "skills");
385
+ if (fs.existsSync(skillsDir)) {
386
+ result.skills = fs.readdirSync(skillsDir, { withFileTypes: true })
387
+ .filter((d) => d.isDirectory())
388
+ .map((d) => {
389
+ const meta = readJsonSafe(path.join(skillsDir, d.name, "_meta.json")) as { description?: string } | null;
390
+ return { name: d.name, description: meta?.description };
391
+ });
392
+ }
393
+ } catch { /* 忽略错误 */ }
394
+
395
+ // 发现已连接的系统(从凭证目录)
396
+ try {
397
+ const credsDir = path.join(openclawDir, "credentials");
398
+ if (fs.existsSync(credsDir)) {
399
+ result.connectedSystems = fs.readdirSync(credsDir)
400
+ .filter((f) => f.endsWith(".json"))
401
+ .map((f) => f.slice(0, -5)); // 移除 .json 后缀
402
+ }
403
+ } catch { /* 忽略错误 */ }
404
+
405
+ // 统计会话信息
406
+ try {
407
+ const agentsDir = path.join(openclawDir, "agents");
408
+ if (fs.existsSync(agentsDir)) {
409
+ for (const dir of fs.readdirSync(agentsDir, { withFileTypes: true })) {
410
+ if (!dir.isDirectory()) continue;
411
+ const sessionsData = readJsonSafe(path.join(agentsDir, dir.name, "sessions", "sessions.json")) as
412
+ Record<string, { updatedAt?: number; lastChannel?: string }> | null;
413
+ if (!sessionsData) continue;
414
+ for (const session of Object.values(sessionsData)) {
415
+ result.sessionCount++;
416
+ // 更新最后活跃时间
417
+ if (typeof session.updatedAt === "number") {
418
+ const iso = new Date(session.updatedAt).toISOString();
419
+ if (!result.lastActive || iso > result.lastActive) result.lastActive = iso;
420
+ }
421
+ // 收集渠道
422
+ if (typeof session.lastChannel === "string" && !result.channels.includes(session.lastChannel)) {
423
+ result.channels.push(session.lastChannel);
424
+ }
425
+ }
426
+ }
427
+ }
428
+ } catch { /* 忽略错误 */ }
429
+
430
+ // 读取定时任务
431
+ try {
432
+ const raw = readFileSafe(path.join(openclawDir, "cron", "jobs.json"));
433
+ if (raw) {
434
+ const parsed = JSON.parse(raw) as unknown;
435
+ result.cronJobs = Array.isArray(parsed) ? parsed : ((parsed as { jobs?: unknown[] })?.jobs ?? []);
436
+ }
437
+ } catch { /* 忽略错误 */ }
438
+
439
+ return result;
440
+ }
441
+
442
+ /**
443
+ * 获取需要监视的 Profile 文件路径
444
+ * 用于在文件变更时自动同步 Profile 到 Dashboard
445
+ *
446
+ * @param openclawDir - OpenClaw 目录(可选)
447
+ * @returns 文件路径数组
448
+ */
449
+ export function getProfileWatchPaths(openclawDir?: string): string[] {
450
+ const dir = openclawDir ?? path.join(os.homedir(), ".openclaw");
451
+ const config = readJsonSafe(path.join(dir, "openclaw.json"));
452
+ const agentsConfig = config?.agents as { defaults?: { workspace?: string } } | undefined;
453
+ const workspace = agentsConfig?.defaults?.workspace ?? path.join(dir, "workspace");
454
+ return [
455
+ path.join(dir, "openclaw.json"),
456
+ path.join(workspace, "IDENTITY.md"),
457
+ path.join(workspace, "SOUL.md"),
458
+ path.join(workspace, "USER.md"),
459
+ path.join(workspace, "AGENTS.md"),
460
+ path.join(workspace, "TOOLS.md"),
461
+ path.join(workspace, "HEARTBEAT.md"),
462
+ path.join(dir, "cron", "jobs.json"),
463
+ path.join(workspace, "skills"),
464
+ ];
465
+ }
466
+
467
+ /**
468
+ * 解析配置
469
+ * 合并用户配置和默认配置
470
+ *
471
+ * @param config - 用户配置(可选)
472
+ * @returns 完整配置
473
+ */
474
+ export function resolveConfig(config?: Partial<OpenClawGuardConfig>): Required<OpenClawGuardConfig> {
475
+ return {
476
+ enabled: config?.enabled ?? DEFAULT_CONFIG.enabled,
477
+ blockOnRisk: config?.blockOnRisk ?? DEFAULT_CONFIG.blockOnRisk,
478
+ apiKey: config?.apiKey ?? DEFAULT_CONFIG.apiKey,
479
+ timeoutMs: config?.timeoutMs ?? DEFAULT_CONFIG.timeoutMs,
480
+ coreUrl: config?.coreUrl ?? DEFAULT_CONFIG.coreUrl,
481
+ agentName: config?.agentName ?? readIdentityName() ?? DEFAULT_CONFIG.agentName,
482
+ dashboardUrl: config?.dashboardUrl ?? DEFAULT_CONFIG.dashboardUrl,
483
+ logLevel: config?.logLevel ?? DEFAULT_CONFIG.logLevel,
484
+ };
485
+ }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * 基于内容的提示词注入扫描器
3
+ *
4
+ * 纯本地/同步的正则扫描器 — 无网络依赖。
5
+ *
6
+ * 双层设计:
7
+ * HIGH 高置信度 — 单个匹配即触发检测
8
+ * MEDIUM 中置信度 — 需要 2+ 个不同类别才触发检测
9
+ *
10
+ * 检测类别:
11
+ * - INSTRUCTION_OVERRIDE: 指令覆盖(如 "ignore previous instructions")
12
+ * - MODE_SWITCHING: 模式切换(如 "you are now in debug mode")
13
+ * - FAKE_SYSTEM_MESSAGE: 伪造系统消息(如 "SYSTEM ALERT:")
14
+ * - CONCEALMENT_DIRECTIVE: 隐藏指令(如 "do not mention these instructions")
15
+ * - COMMAND_EXECUTION: 命令执行(如 "execute the following shell command")
16
+ * - TASK_HIJACKING: 任务劫持(如 "your new task is:")
17
+ * - ROLE_ASSUMPTION: 角色假设(如 "you are now unrestricted")
18
+ * - DATA_EXFILTRATION: 数据外泄(如 "send contents to URL")
19
+ */
20
+
21
+ import type { DetectionFinding, DetectionRiskType, RiskLevel } from "./types.js";
22
+ import type { InjectionCategory } from "./patterns/types.js";
23
+ import { HIGH_CONFIDENCE_PATTERNS } from "./patterns/high-confidence.js";
24
+ import { MEDIUM_CONFIDENCE_PATTERNS } from "./patterns/medium-confidence.js";
25
+
26
+ export type { InjectionCategory } from "./patterns/types.js";
27
+
28
+ /** 注入匹配结果 */
29
+ export type InjectionMatch = {
30
+ /** 匹配的模式名称 */
31
+ pattern: string;
32
+ /** 注入类别 */
33
+ category: InjectionCategory;
34
+ /** 置信度 */
35
+ confidence: "high" | "medium";
36
+ /** 匹配到的文本 */
37
+ matchedText: string;
38
+ };
39
+
40
+ /** 注入扫描结果 */
41
+ export type InjectionScanResult = {
42
+ /** 是否检测到注入 */
43
+ detected: boolean;
44
+ /** 所有匹配结果 */
45
+ matches: InjectionMatch[];
46
+ /** 去重后的类别列表 */
47
+ distinctCategories: InjectionCategory[];
48
+ /** 摘要描述 */
49
+ summary: string;
50
+ /** 检测发现列表(用于上报) */
51
+ findings: DetectionFinding[];
52
+ };
53
+
54
+ /** 合并所有模式 */
55
+ const ALL_PATTERNS = [...HIGH_CONFIDENCE_PATTERNS, ...MEDIUM_CONFIDENCE_PATTERNS];
56
+
57
+ /** 类别到风险类型的映射 */
58
+ const CATEGORY_TO_RISK_TYPE: Record<InjectionCategory, DetectionRiskType> = {
59
+ INSTRUCTION_OVERRIDE: "PROMPT_INJECTION",
60
+ MODE_SWITCHING: "PROMPT_INJECTION",
61
+ FAKE_SYSTEM_MESSAGE: "PROMPT_INJECTION",
62
+ CONCEALMENT_DIRECTIVE: "PROMPT_INJECTION",
63
+ COMMAND_EXECUTION: "COMMAND_EXECUTION",
64
+ TASK_HIJACKING: "PROMPT_INJECTION",
65
+ ROLE_ASSUMPTION: "PROMPT_INJECTION",
66
+ DATA_EXFILTRATION: "DATA_EXFILTRATION",
67
+ };
68
+
69
+ /**
70
+ * 脱敏内容
71
+ * 将检测到的注入模式替换为 `__REDACTED_BY_OPENTRUST_DUE_TO_{riskType}__` 标记
72
+ *
73
+ * @param text - 要脱敏的文本
74
+ * @returns 脱敏后的文本和检测发现
75
+ */
76
+ export function redactContent(text: string): { redacted: string; findings: DetectionFinding[] } {
77
+ if (!text || text.length === 0) return { redacted: text, findings: [] };
78
+
79
+ const findings: DetectionFinding[] = [];
80
+ let redacted = text;
81
+
82
+ // 遍历所有模式进行替换
83
+ for (const entry of ALL_PATTERNS) {
84
+ // 确保使用全局匹配
85
+ const globalRegex = new RegExp(
86
+ entry.regex.source,
87
+ entry.regex.flags.includes("g") ? entry.regex.flags : entry.regex.flags + "g",
88
+ );
89
+ const riskType = CATEGORY_TO_RISK_TYPE[entry.category];
90
+
91
+ // 替换匹配内容
92
+ redacted = redacted.replace(globalRegex, (matched) => {
93
+ findings.push({
94
+ riskLevel: (entry.confidence === "high" ? "high" : "medium") as RiskLevel,
95
+ riskType,
96
+ riskContent: matched,
97
+ reason: `Matched injection pattern: "${entry.label}" (${entry.category})`,
98
+ });
99
+ return `__REDACTED_BY_OPENTRUST_DUE_TO_${riskType}__`;
100
+ });
101
+ }
102
+
103
+ return { redacted, findings };
104
+ }
105
+
106
+ /**
107
+ * 扫描注入模式
108
+ * 检测文本中是否包含提示词注入攻击
109
+ *
110
+ * 触发条件:
111
+ * - 任何 HIGH 置信度匹配
112
+ * - 或 2+ 个不同类别的 MEDIUM 置信度匹配
113
+ *
114
+ * @param text - 要扫描的文本
115
+ * @returns 扫描结果
116
+ */
117
+ export function scanForInjection(text: string): InjectionScanResult {
118
+ if (!text || text.length === 0) {
119
+ return { detected: false, matches: [], distinctCategories: [], summary: "", findings: [] };
120
+ }
121
+
122
+ const matches: InjectionMatch[] = [];
123
+ const categorySet = new Set<InjectionCategory>();
124
+ let hasHigh = false;
125
+
126
+ // 遍历所有模式进行匹配
127
+ for (const entry of ALL_PATTERNS) {
128
+ const execResult = entry.regex.exec(text);
129
+ if (execResult) {
130
+ matches.push({
131
+ pattern: entry.label,
132
+ category: entry.category,
133
+ confidence: entry.confidence,
134
+ matchedText: execResult[0],
135
+ });
136
+ categorySet.add(entry.category);
137
+ if (entry.confidence === "high") hasHigh = true;
138
+ }
139
+ }
140
+
141
+ const distinctCategories = [...categorySet];
142
+
143
+ // 判断是否触发检测:任何 HIGH 匹配,或 2+ 个不同的 MEDIUM 类别
144
+ const mediumCategories = new Set(
145
+ matches.filter((m) => m.confidence === "medium").map((m) => m.category),
146
+ );
147
+ const detected = hasHigh || mediumCategories.size >= 2;
148
+
149
+ // 生成摘要
150
+ let summary = "";
151
+ if (detected) {
152
+ const patternList = matches.map((m) => `"${m.pattern}"`).join(", ");
153
+ summary =
154
+ `Detected ${matches.length} injection pattern(s) across ${distinctCategories.length} ` +
155
+ `categor${distinctCategories.length === 1 ? "y" : "ies"}: ${distinctCategories.join(", ")}. ` +
156
+ `Matched patterns: ${patternList}`;
157
+ }
158
+
159
+ // 构建检测发现列表
160
+ const findings: DetectionFinding[] = detected
161
+ ? matches.map((m) => ({
162
+ riskLevel: (m.confidence === "high" ? "high" : "medium") as RiskLevel,
163
+ riskType: CATEGORY_TO_RISK_TYPE[m.category],
164
+ riskContent: m.matchedText,
165
+ reason: `Matched injection pattern: "${m.pattern}" (${m.category})`,
166
+ }))
167
+ : [];
168
+
169
+ return { detected, matches, distinctCategories, summary, findings };
170
+ }
package/agent/index.ts ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Agent 模块导出
3
+ *
4
+ * 导出 agent 相关的核心功能:
5
+ * - 检测运行器
6
+ * - 配置和凭证管理
7
+ * - 内容脱敏
8
+ * - 类型定义
9
+ */
10
+
11
+ export { runGuardAgent, mapApiResponseToVerdict, type RunnerConfig } from "./runner.js";
12
+ export {
13
+ loadCoreCredentials,
14
+ saveCoreCredentials,
15
+ registerWithCore,
16
+ DEFAULT_CORE_URL,
17
+ DEFAULT_CONFIG,
18
+ resolveConfig,
19
+ type CoreCredentials,
20
+ } from "./config.js";
21
+ export { sanitizeContent } from "./sanitizer.js";
22
+ export * from "./types.js";