跳到主要内容

Guard

Aidy 内置了 Guard 检测能力,通过静态配置 plugins.guard.* 与 route 级 plugin_config.guard 配置。

启用条件

Guard 插件只有在同时满足以下条件时,才会被挂进请求链路:

  1. 静态配置 plugins.guard.enable = true
  2. 当前 route 配置了 plugin_config.guard
  3. detect_chat_requestdetect_chat_responsedetect_embeddings_request 中至少有一个 mode != disabled

如果以上任一条件不满足,Guard 不会进入当前 route 的请求链路。

另外,Guard 当前只挂在 chat 与 embeddings 的 forward hook 上。因此即使路由启用了 Guard,models、options 与 unknown 请求也不会进入 Guard 插件。

静态配置

check_service_url

用于指定 Guard 检测服务的 URL,例如 http://localhost:8080。对应静态配置 plugins.guard.check_service_url

备注

检测服务直连检测模型 API,如对应检测 API URL 为 http://localhost:8080/v1/chat/responses,则此处应配置为 http://localhost:8080

timeout

检测服务请求超时时间,默认 30s。对应静态配置 plugins.guard.timeout_ms

当检测服务响应超时时,将跳过 guard 检测流程

redis

用于异步会话封禁的 Redis 连接地址,例如 redis://127.0.0.1:6379。对应静态配置 plugins.guard.redis

若未配置,async_session_block 模式将不可用,降级为 log_only

路由配置

路由配置用于指定特定路由的 Guard 检测配置。请参见 路由配置 的 Guard 节。

需要注意的是,以下情况都属于“Guard 已经进入链路后发生降级或跳过”,不属于“未启用”:

  • 某个 detect 的 policy_chain_json 非法,导致该 detect 在运行时被降级为 disabled
  • Guard detect 命中 tenant.detect_rate_limit_cpm 限额
  • Guard 检测请求超时或检测服务异常,最终走 bypass / skip 逻辑

输出

检测结果将与请求/响应日志一同输出,并汇总到 ext_fields.guard

请求日志

ext_fields.guard 使用以下 proto:

说明:

  • requestresponse 按是否执行到对应阶段决定是否出现
  • 两者的字段语义与之前请求日志中的 guard 字段保持一致,只是存储位置变为 ext_fields.guard

request / response 的常见字段包括:

  • status
  • action
  • effect_action
  • error
  • not_detect_reason
  • chatembeddings
  • chat_stream

当检测到违规请求时,路由级别的检测模式有以下四种:

  • disabled:关闭该检测项
  • block:同步检测,命中后拦截或代答
  • log_only:异步检测,仅记录,不修改请求链路行为
  • async_session_block:异步检测 + 会话封禁(详见下文)

CyberGuard 的 action 会细分为以下行为:

  • block_action:拦截模式(intercept)
  • override_action:代答模式(substitute)
  • modify_action:脱敏模式(desensitize)
  • alert_action:仅记录(log_only)
  • pass_action:通过(pass)

脱敏还原

Guard 在 v2 chat IR 链路中支持由 CyberGuard modify_action 驱动的脱敏还原闭环。

说明:

  • Aidy 不提供独立的脱敏还原静态配置开关。
  • Aidy 不新增 route 级 GuardPluginConfig 开关。
  • 只有当 CyberGuard 检测结果显式返回 modify_action 时,Aidy 才会执行请求脱敏与响应还原。
  • 如果没有 modify_action,Guard 仅执行正常检测,不会额外改写请求或响应。

请求阶段:

  • Guard 会复制 RequestIR,仅改写参与 Guard 检测的 canonical 文本字段。
  • 上游 LLM 实际看到的是替换占位符后的脱敏请求。
  • Aidy 会保存一个仅驻留在请求上下文内的 RestorePlan,用于后续响应还原。

响应阶段:

  • 非流式响应会在 ResponseIR 层还原后再进入协议 assembler。
  • 流式响应会在 StreamEventIR 层按 item / tool call 维度跨 chunk 还原。
  • 因此客户端看到的是已还原的文本,而 Guard 响应检测也会基于还原后的内容执行。

日志与观测:

  • Aidy 不会把 RestorePlan.Values 明细写入日志。
  • 请求日志仅记录 ext_fields.guard.desensitize 摘要,例如 request_modifiedrestore_itemsrestored_responserestore_error
  • Aidy 不会新增顶层 upstream_request_body / upstream_response_body 字段。

行为 - 拦截模式

拦截模式对应 CyberGuard 返回 block_action(且路由 mode=block)。

Chat - 请求风险

当检测到请求存在风险时,直接拦截并返回 HTTP 412 + error JSON(message 来自 CyberGuard,若为空则使用默认提示):

HTTP/1.1 412 Precondition Failed
Content-Type: application/json

{"error":{"code":null,"message":"[来源于 CyberGuard 返回]您的请求包含不安全内容,无法生成回复","param":null,"type":"security_guard_error"}}

Chat - 响应风险

  • 非流式:返回 HTTP 512 + error JSON(结构同上)。
  • 流式:在触发风险后插入 refusal chunk(delta.refusal),并以 finish_reason=refusal 结束,随后输出 [DONE];不会继续转发后续上游内容。

示例(两段 refusal chunk + 完成信号):

{"id":"chatcmpl-123","object":"chat.completion.chunk","created":1694268190,"model":"gpt-4o-mini","system_fingerprint":"fp_44709d6fcb","choices":[{"index":0,"delta":{"refusal":"I'm sorry, I cannot assist with that request."},"logprobs":null,"finish_reason":null}]}
{"id":"chatcmpl-123","object":"chat.completion.chunk","created":1694268190,"model":"gpt-4o-mini","system_fingerprint":"fp_44709d6fcb","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"refusal"}]}

Embeddings - 请求风险

Embeddings 的拦截逻辑始终返回 HTTP 412 + error JSON(同上)。

行为 - 代答模式

代答模式对应 CyberGuard 返回 override_action(且路由 mode=block)。Chat 将返回模拟输出(from-security-guard),Embeddings 仍按拦截处理。

Chat - 请求风险

当请求触发风险时,返回代答内容(HTTP 200),格式与 OpenAI 兼容:

  • 非流式object=chat.completionmodel=from-security-guard
  • 流式:输出 chat.completion.chunkfinish_reason=stop,随后 [DONE]

Chat - 响应风险

当响应触发风险时,返回代答内容(同上),并终止后续上游内容的转发。

Embeddings - 请求风险

即便 CyberGuard 返回 override_action,Embeddings 仍按拦截处理,返回 HTTP 412 + error JSON。

行为 - 异步会话封禁

异步会话封禁模式对应路由配置 mode=async_session_block(proto 值 4)。

该模式适用于以下场景:当前请求不应被阻塞,但若检测到不安全内容,需要封禁该会话(session),使得后续来自同一 session 的请求在进入上游前被直接拦截。

工作流程

  1. 请求到达后,Guard 先查询 Redis 是否存在该 session 的封禁 key
  2. 若已封禁 → 直接返回拦截/代答响应,不再调用 CyberGuard
  3. 若未封禁 → 正常转发到上游,同时异步调用 CyberGuard 检测
  4. 异步检测完成且 CyberGuard 返回 block_actionoverride_action → 将 session 封禁 key 写入 Redis
  5. 后续来自同一 session 的请求在步骤 1 即被拦截

适用阶段

async_session_block 模式同时支持 detect_chat_requestdetect_chat_response

  • 请求阶段:异步检测请求内容,命中后封禁 session
  • 响应阶段:异步检测响应内容,命中后封禁 session

Session ID 来源

Session ID 由 session 插件提供,解析优先级:

  1. session 插件从请求 header 中提取(需在路由 plugin_config 中配置 session 插件)
  2. RequestIR.Context.ConversationID 作为 fallback

若无法获取 session ID,async_session_block 模式将降级为 log_only 行为(请求正常放行,不会写入 Redis 封禁 key)。

Redis 依赖

  • Session 封禁功能使用静态配置 plugins.guard.redis 作为 Redis 连接
  • 若未配置 plugins.guard.redis,session 封禁功能不可用,降级为 log_only
  • 封禁 key 格式:guard:session-block:{sessionID}
  • 封禁 TTL:由 session_block_ttl_seconds 配置,默认 86400 秒(24 小时)

封禁触发条件

只有 CyberGuard 返回 block_actionoverride_action 时才会触发 session 封禁。以下 action 不会触发封禁:

  • pass_action:安全通过
  • alert_action:仅告警
  • modify_action:脱敏

降级行为

条件降级行为
无 session ID降级为 log_only,请求正常放行
Redis 不可用降级为 log_only,无法查询/写入封禁 key
CyberGuard 检测超时或异常跳过检测,不封禁 session