Agent安全审计与Agent安全基线(1)

ooolllddd 2753 字 发布于 2026-07-19


Agent安全基线的内容较多,可以放到下一部分讲讲,这里只是简单实现Agent的身份审批验证相关思路

在Agent 在执行 file.read、exec、message.send、http.request、db.query 等工具时,已经不只是生成文本,而是在真实环境中执行操作。这些操作可能读取本地文件、修改代码、调用外部服务、发送消息,甚至触发部署或审批流程。

例如企业Dify平台,运行一个Claw类型的Agent,都可能存在权限问题,以root运行或者任意文件可写

因此,Agent运行时必须明确回答几个问题:

  1. 后续审计、追踪、审批和权限判断应该基于哪一组身份信息?
  2. 谁最初触发了这次 Agent 执行?
  3. 当前实际执行工具调用的是哪个 Agent 或子 Agent?
  4. Agent 是否是在代表某个用户执行操作?
  5. 工具调用发生在哪个会话、哪次执行,以及哪个宿主运行时中?

为了便于统一管理,需要一个统一的控制面,功能包括

  1. 接入不同 Agent:让 LangChain、OpenClaw、MCP、自研 Agent 都能把事件上报进来。
  2. 收集身份上下文:知道是谁触发、哪个 Agent 执行、在哪个会话、哪次执行里调用了工具。
  3. 记录工具调用:记录调用了什么工具、访问了什么资源、做了什么动作、结果是什么。
  4. 生成审计日志:把不同框架的调用记录统一成可查询、可追踪的审计日志。
  5. 为后续管控预留能力:以后可以加授权判断、人工审批、风险告警、策略管理。

控制面主要在Agent注册、请求进入,工具调用前后采集事件并生成审计日志,授权,审批和风险判断可作为后续拓展,授权由企业同意认证系统来控制

实现方案-框架映射

不同框架不需要使用完全相同的字段名,但需要能映射到本文定义的身份实体、执行上下文和审计事件。

  • LangChain / LangGraph:将 Runtime context、thread、run、tool call 和 callbacks / middleware 中的信息映射到运行时身份上下文。
  • Claude Tool Use:将请求方应用作为宿主运行时,将 tool_use.id 映射为工具调用标识;会话、执行实例和用户身份需要由应用层补齐。
  • MCP:将 MCP client、server、transport、OAuth token 或 HTTP Header 中的身份信息映射到调用方、执行环境和工具调用上下文。
  • Cursor-like IDE Agent:将 IDE 登录用户、workspace、session、run、tool dispatcher 和 approval 状态映射到身份上下文和审计事件。
  • OpenAI Agents SDK:将 RunContext、tool call、session / thread 等运行信息映射到执行上下文。
  • 自研 Agent:不要求固定 envelope 结构,但必须在工具边界传递运行时身份上下文,并生成审计事件。

实现方案-身份管理

一次 Agent 执行中至少需要区分以下角色:

  • 最初发起者:最早触发这次执行的一方,可以是用户、系统任务、Webhook、Cron 或另一个服务。
  • 实际执行者:当前真正执行工具调用的一方,通常是主 Agent 或某个子 Agent。
  • 被代表用户:Agent 代表其执行操作的用户,可选。例如企业应用中由 Agent 代用户调用 API。
  • 直接委托者:把任务委托给当前执行者的一方,可选。例如主 Agent 调用子 Agent。

实现方案-审计事件

每次工具调用至少需要记录以下信息:

  • 发生时间:工具调用发生的时间,用于排序、排查和合规审计。
  • 执行环境:工具调用发生在哪个宿主运行时中。
  • 工具信息:被调用的工具名称和本次工具调用标识。
  • 身份信息:最初发起者、实际执行者、被代表用户和直接委托者。
  • 执行上下文:会话、执行实例和追踪链路。
  • 操作对象:被访问或修改的资源,以及执行的动作。
  • 决策结果:本次调用是允许、拒绝,还是需要人工审批。
  • 决策原因:策略命中、审批结果或系统判断的说明。

审计日志不应保存完整敏感内容、token 或 secret。

LangChain

首期只需控制面提供两个能力:

  • 注册 Agent:记录 Agent 是谁、归谁管、默认能使用哪些工具。
  • 记录审计事件:记录谁在什么上下文中调用了什么工具、访问了什么资源、结果是什么。
创建 Agent
  -> 向控制面注册 Agent 身份、owner、workspace 和可用工具

调用 Agent
  -> 宿主应用认证用户
  -> 生成身份上下文
  -> 通过 CallbackHandler 或 contextvars 传入 LangChain / LangGraph

调用工具
  -> CallbackHandler 拦截工具调用
  -> 工具调用前上报 tool_call_started
  -> 执行真实工具
  -> 工具调用后上报 tool_call_finished 或 tool_call_failed

Agent 创建与注册

Agent 创建完成后,向控制面注册一次。注册接口建议幂等,避免服务重启或配置重载时重复写入。

tools = [read_file, run_shell]

agent = create_agent(
    model="gpt-5-nano",
    tools=tools,
)

# 也可以在注册的时候,由控制面提供 agent_id
control_plane.register_agent({
    "agent_id": "agent-code-review",
    "runtime": "langchain",
    "owner": "team-a",
    "workspace": "repo-frontend",
    "tools": [tool.name for tool in tools],
})

CallbackHandler 采集审计事件

CallbackHandler 适合记录 tool start / end / error,不侵入工具实现。

class GovernanceCallbackHandler(BaseCallbackHandler):
    def __init__(self, identity_context):
        self.context = identity_context
        self.tool_names = {}

    def on_tool_start(self, serialized, input_str, *, run_id, parent_run_id=None, **kwargs):
        tool_call_id = str(run_id)
        tool_name = serialized.get("name")
        self.tool_names[tool_call_id] = tool_name

        control_plane.audit_event({
            "type": "tool_call_started",
            "tool": tool_name,
            "tool_call_id": tool_call_id,
            "parent_run_id": str(parent_run_id) if parent_run_id else None,
            "context": self.context,
            "input_summary": input_str[:200],
        })

    def on_tool_end(self, output, *, run_id, **kwargs):
        tool_call_id = str(run_id)

        control_plane.audit_event({
            "type": "tool_call_finished",
            "tool": self.tool_names.get(tool_call_id),
            "tool_call_id": tool_call_id,
            "context": self.context,
            "status": "success",
        })

    def on_tool_error(self, error, *, run_id, **kwargs):
        tool_call_id = str(run_id)

        control_plane.audit_event({
            "type": "tool_call_failed",
            "tool": self.tool_names.get(tool_call_id),
            "tool_call_id": tool_call_id,
            "context": self.context,
            "status": "error",
            "error_type": type(error).__name__,
        })        

# 调用 Agent 时,由宿主应用生成身份上下文,并通过 callback 带入本次执行:
identity_context = {
    "user_id": "user-123",
    "agent_id": "agent-code-review",
    "runtime": "langchain",
    "session_id": "session-1",
    "run_id": "run-1",
    "trace_id": "trace-1",
    "workspace": "repo-frontend",
    "trust_level": "authenticated_user",
}

agent.invoke(
    {"messages": messages},
    config={"callbacks": [GovernanceCallbackHandler(identity_context)]},
)

Tool Wrapper / Middleware 进行权限控制

如果后续需要在工具执行前做强授权或人工审批,建议放在 tool wrapper、middleware 或 LangGraph node 边界。

def governed_tool(tool_name, real_tool):
    def wrapper(*args, runtime_context, **kwargs):
        decision = control_plane.authorize({
            "context": runtime_context,
            "tool": tool_name,
            "params_summary": summarize(args, kwargs),
        })

        if decision["result"] == "deny":
            raise PermissionError(decision["reason"])

        return real_tool(*args, **kwargs)

    return wrapper

OpenClaw

创建 / 加载 Agent
  -> 注册 Agent 身份、owner、workspace 和可用工具

请求进入
  -> OpenClaw runtime 生成身份上下文
  -> hook event 携带上下文

调用工具
  -> before_tool_call 上报 tool_call_started
  -> 执行真实工具
  -> after_tool_call 上报 tool_call_finished 或 tool_call_failed

Agent注册

优先在插件启动或 runtime 启动时注册。

async function registerAgent(agentInfo) {
  await controlPlane.registerAgent({
    agent_id: agentInfo.agentId,
    runtime: "openclaw",
    owner: agentInfo.owner,
    workspace: agentInfo.workspace,
    tools: agentInfo.tools,
  });
}

Plugin Hook 采集审计事件

export default definePluginEntry({
  id: "governance",
  name: "Governance",
  register(api) {
    api.on("before_tool_call", async (event) => {
      auditQueue.enqueue({
        type: "tool_call_started",
        tool: event.toolName,
        tool_call_id: event.toolCallId,
        context: event.context,
        params_summary: summarizeParams(event.params),
      });
    });

    api.on("after_tool_call", async (event) => {
      auditQueue.enqueue({
        type: event.error ? "tool_call_failed" : "tool_call_finished",
        tool: event.toolName,
        tool_call_id: event.toolCallId,
        context: event.context,
        status: event.error ? "error" : "success",
      });
    });
  },
});
此作者没有提供个人介绍。
最后更新于 2026-08-20