germankovacevic-lab/agent-audit-gate

GitHub: germankovacevic-lab/agent-audit-gate

该插件为 AI 智能体的出站消息提供了一道由高级审核智能体把关的自动化审计闸口,用于拦截并审查草稿以防止信息泄露和提示词注入。

Stars: 2 | Forks: 0

# Agent Audit Gate *(内部代号为 **Switchboard** —— 这就是为什么插件 ID、账本文件和 `SWITCHBOARD_` 环境变量都带有该名称的原因。)* 一个 [OpenClaw](https://openclaw.ai) 插件,可在 AI 智能体自动向第三方通过 1:1 频道发送的任何内容前,放置一个 **AI 参与闭环的审计闸口(AI-in-the-loop audit gate)** —— 审核者是一个**高级智能体,而非人类**(仅在必要时才向上呈递给您)。 这是一个模式的**参考实现**,而非开箱即用的产品。它特意设计得小巧且易读:阅读源码、汲取思路并进行调整 —— 审计策略和频道均可通过环境变量配置,因此常见用例无需 Fork。 ## 问题所在 如果您将基于 LLM 的智能体部署在公共消息频道上(例如具有“开放私信”策略、会自动回复任何人的 WhatsApp 线路),您将面临两个典型的故障模式: 1. **信息泄露** —— 模型泄露了内部上下文(隐私数据、项目、联系人、您的系统提示词、您运行的模型/工具),仅仅因为别人问了。 2. **提示词注入** —— 陌生人的消息被当作指令而非数据处理,导致智能体做出了不该做的事情。 您通常*无法*阻止模型**生成**回复 —— 在您想要干预时,草稿通常已经生成了。因此,与其让它直接发出去,Switchboard 选择**拦截**草稿,并将其路由到一个独立的**审核者**会话中,由后者根据您的规则进行审计,然后再**故意**将其释放。 操作者自己的号码和智能体自身的线路是豁免的:它们会正常通过。 ## 不同之处 人类参与的审批闸口是一个成熟的模式 —— 暂停操作以待签字批准并无新奇之处。这里的转折在于,审核者是一个**高级 AI 智能体,而非人类**:即 AI 参与闭环。草稿被拦截后,由*第二个*智能体(拥有更多上下文且专注于安全的智能体)在释放前对其进行审计。这正是其能够扩展的原因:无需人类阅读每一条消息,且高级审核者拥有频道会话所缺乏的上下文(操作者数据、项目边界、什么算作泄露)。 我在 OpenClaw 生态系统(ClawHub、文档)中找不到其他审核出站**频道消息**的插件。Lobster 及类似工具会拦截工作流的副作用;而本插件拦截的是陌生人可以触发的实际消息 —— 即离开机器并出现在某人手机上的文字。 ## 工作原理 ``` third-party 1:1 DM │ ▼ message_received ──► ledger: pending (reviewer NOT woken yet) │ ▼ agent auto-drafts a reply │ ▼ message_sending ──► HELD (draft is NOT sent) │ ▼ wake reviewer ◄── carries the inbound + the draft │ ▼ reviewer audits ──┬──► RELEASE → deliberate send → answered └──► DROP → dropped (nothing sent) ``` 两个边缘 I/O 钩子(`priority: 100`)。逻辑位于 `src/handlers.ts` 中(可测试);`index.ts` 仅用于连接这些钩子。 - **`message_received`**(`handleInbound`) —— 来自第三方的 1:1 私信(默认为 WhatsApp;通过 `SWITCHBOARD_CHANNELS` 支持任何频道)将以 `pending`(待处理)状态写入账本。它本身**不会**唤醒审核者;唤醒操作会与草稿(下一步)一起到达,因此审核者会同时看到入站消息和草稿。 - **`message_sending`**(`handleSending`): - **对第三方的自动回复**(存在 `ctx.senderId`)→ 草稿被**拦截**(`held`),`notifyAudit({ phone, inbound, draft })` 会实时唤醒**审核者**会话,并且发送操作被**取消**(草稿不会离开本机)。 - **主动发送**(您的 `message send`,无 `ctx.senderId`)→ **始终放行**;如果目标是第三方,则会记录为 `answered`。这是尽力而为的策略 —— 它绝不会取消或中断主动发送。 关键区分点:**自动回复携带 `ctx.senderId`**;而主动发送则不携带。 ### “拦截”生命周期 账本是仅追加的,每个事件占一行。每个对话的实时状态是**派生**出来的(`deriveThreads()`):以每个电话号码的最后一条事件为准。 ``` inbound → pending (opens / reopens the thread) draft → held (auto-reply held, awaiting audit) release → answered (reviewer audited and released deliberately) drop → dropped (reviewer audited and chose not to reply) ``` (`suppressed` 是没有审计步骤的纯抑制操作的遗留状态。) 在已关闭的对话之后出现的新入站消息会将其作为 `pending` **重新开启**。 ### 审计通知 —— `src/notify.ts` `notifyAudit` 使用审计提示词(`buildAuditText`:谁写的、他们说了什么、频道草拟了什么,以及如何释放)唤醒审核者会话。该提示词是一个**通用的参考清单** —— 请根据您自己的部署调整策略措辞。 - **首选:** `POST /hooks/wake`(环回接口、实时、事件驱动、无需轮询)。 - **备用:** `enqueueNextTurnInjection`(被动模式,在审核者下一轮对话时提取)。 ### 第三方检测 —— `src/verified.ts` `isThirdParty()` 会将任何**不**在操作者/智能体白名单中的号码视为第三方。白名单由配置驱动(见下文)。未知/为空的发送者也会被视为第三方 —— **默认采用故障安全机制**。 ## 安装 这是一个 OpenClaw 插件。直接从此代码库安装: ``` openclaw plugins install git:github.com/germankovacevic-lab/agent-audit-gate ``` 或者克隆并进行本地开发链接: ``` git clone https://github.com/germankovacevic-lab/agent-audit-gate openclaw plugins install --link ./agent-audit-gate ``` 然后设置下方的环境变量并重启网关。 ## 配置 所有特定于部署的值均从环境变量中读取;没有硬编码任何真实的号码、名称或路径。 | 环境变量 | 用途 | 默认值 | | --------------------------- | ----------------------------------------------------------------------- | ---------------------------------------- | | `SWITCHBOARD_OWN_NUMBERS` | 操作者/智能体自身号码(数字)的逗号分隔白名单。**不**在此列表中的号码视为第三方。 | *(为空 → 所有人均被视为第三方)* | | `SWITCHBOARD_STORE` | 仅追加的 JSONL 账本路径。 | `~/.switchboard/switchboard.jsonl` | | `SWITCHBOARD_AUDIT_SESSION` | 审核被拦截草稿的审核者会话的 Session key。 | `agent:main:main` | | `SWITCHBOARD_CONFIG_PATH` | 网关配置文件,只读,用于获取环回钩子 token。 | `~/.openclaw/openclaw.json` | | `SWITCHBOARD_DEBUG_LOG` | 可选的调试日志路径。**未设置 = 无日志**(因此任何第三方数据都不会触及磁盘)。 | *(未设置 → 禁用)* | | `SWITCHBOARD_CHANNELS` | 插件拦截的 1:1 消息频道的逗号分隔列表。 | `whatsapp` | | `SWITCHBOARD_AUDIT_POLICY` | 覆盖审核者应用于被拦截草稿的审计清单。 | *(内置通用闸口)* | | `SWITCHBOARD_RELEASE_CHANNEL` | 审计文本中释放命令里显示的频道。默认为第一个 `SWITCHBOARD_CHANNELS` 条目,否则为 `whatsapp`。 | `whatsapp` | | `SWITCHBOARD_STALE_MS` | 超过该时间后,`held`/`pending`(已拦截/待处理)的线程将由[陈旧线程清理](#safety-net--stale-thread-sweep)程序报告。 | `900000` (15 分钟) | 示例: ``` export SWITCHBOARD_OWN_NUMBERS="15551230001,15551230002" export SWITCHBOARD_STORE="$HOME/.switchboard/switchboard.jsonl" ``` 在 OpenClaw 网关中,使用 `plugins.entries.switchboard.enabled = true` 以及 `plugins.load.paths` 中的插件路径来启用该插件。 它**不需要** `allowConversationAccess`:它仅使用边缘 I/O 钩子 `message_received` / `message_sending`,这些不需要对话访问权限。 ## 安全网 —— 陈旧线程清理程序 实时唤醒(及其被动的 `enqueueNextTurnInjection` 备用方案)可能*同时*失败 —— 网关宕机、钩子配置错误、没有审核者回合。发生这种情况时,草稿会永远停留在 `held` 状态(或入站消息永远停留在 `pending` 状态),而且无人知晓。`src/sweep.ts` 填补了这一空白:它是一个**纯粹的、只读的**函数,用于查找卡住超过阈值的对话,以便您可以对其发出警报。 ``` import { findStaleThreads, buildStaleAlert } from "./src/sweep.ts"; // Run this on YOUR scheduler (cron, a heartbeat tick — the plugin starts no timers itself). const stale = await findStaleThreads(); // held/pending older than SWITCHBOARD_STALE_MS (default 15m) if (stale.length) { // buildStaleAlert returns a string only — nothing is written to disk. await notifyOperator(buildStaleAlert(stale)); } ``` 关闭状态(`answered`/`dropped`/`suppressed`/`bot_loop`)永远不会被报告;以每个对话的最后一条事件为准,因此已释放的草稿会自动消失。请确保报警路径独立于其所备份的唤醒路径 —— 如果您唤醒审核者来传递它,您就重新引入了本该解决的单一故障点。 ## 测试 ``` node --test test/*.test.ts ``` 覆盖范围:`verified`(第三方检测 + 配置驱动的白名单)、`capture` (账本 + `held`/`dropped` + `lastInboundText`)、`dedup`(洪水/回声抑制)、 `notify`(`buildAuditText` + `resolveWakeEndpoint` + 向环回测试服务器发出的真实 POST 请求)、 `handlers`(两个带有可注入依赖项的钩子的粘合逻辑)以及 `sweep`(陈旧线程检测 + 报警,使用固定时钟和临时账本)。 ## 已知局限 / 未包含内容 - **配置级别的故障安全:** 在开放 DM 策略下,如果插件宕机,自动回复会恢复。请将其与基础的底层白名单配对使用(插件宕机 = 保持静默,而非泄露)。 - **未捆绑调度程序:** 陈旧线程安全网([见下文](#safety-net--stale-thread-sweep)) 作为纯函数而非运行中的计时器提供 —— 您需要将其连接到您自己的调度程序(cron、心跳机制等)。根据设计,该插件本身不会启动后台计时器。 这是一个**参考实现**。它如实地展示了该模式;它并不是一个经过加固且提供支持的产品。阅读它、Fork 它、改造它。 ## 许可证 MIT —— 见 [LICENSE](./LICENSE)。 ## 关于 由 **[AgentNeo](https://agneo.app)** 构建并维护 —— 我们构建具有严格操作规范的 AI 智能体,并开源在现实世界中运行它们所需的安全模式。 联系方式:[gk@agneo.app](mailto:gk@agneo.app)
标签:AI代理, MITM代理, Retryablehttp, Streamlit, 提示词注入防护, 时序数据库, 消息审计, 自动化攻击, 访问控制