本节在全册位置:智能体会调工具、发消息、读数据,安全是硬要求。要点:密钥不进状态、敏感工具加 interrupt、输入做校验、日志脱敏、权限按工具分级。我们把安全当成状态建模阶段就要考虑的事,而非事后补丁。
密钥放环境变量/密钥管理,绝不写进状态(否则落检查点)。写类工具(发信、改库)前置 interrupt 人工确认。用户输入经校验防注入。审计日志保留脱敏后的状态历史以满足合规。类比金融:密钥像印鉴,权限像授权矩阵,审计像留痕。这三条任一条漏了,都是上线事故。
import os # 正确:节点内读,状态只留非敏感标记 from langgraph.types import interrupt def safe_send(s): ok = interrupt({"to": s["to"]}) # 人确认 if ok == "allow": return _send(s["to"], os.environ["SEND_TOKEN"]) return {}
# 输入校验:在入口节点拦掉危险内容 import re def guard(s): if re.search(r"ignore previous", s["msg"]): return {"msg": ""} return {} # 校验是最后一道闸,不能只靠模型自觉
背景:某节点把 token 写回状态,检查点库被读到,密钥外泄。
操作:改为节点内读环境变量,状态只留「已发送」标记。
结果:检查点不含任何密钥,脱敏到位。
解读:安全设计要在状态建模阶段就考虑,而非事后补,因为状态已经落盘了。
变式:敏感字段在落盘前由检查点层统一脱敏,比逐个节点手写更可靠。

密钥放环境变量或密钥管理,绝不写进状态,否则落检查点被读到就泄露。
写类工具前置 interrupt 人工确认,用户输入经校验防注入,审计日志保留脱敏历史。
密钥像印鉴、权限像授权矩阵、审计像留痕,三条任一条漏了都是上线事故。
某节点把 token 写回状态,检查点库被读到密钥外泄;安全设计要在状态建模阶段就考虑,而非事后补,因为状态已经落盘,补救成本极高。
工具不是都能随意调,按敏感度分级,高危工具前置人工闸或二次校验。
# 按角色放行工具:权限在节点内判断,不在状态泄露 ROLE_TOOLS = {"reader": ["search"], "operator": ["search", "send"], "admin": ["*"]} def guard_tool(s, tool_name): role = s["role"] allowed = ROLE_TOOLS.get(role, []) if tool_name not in allowed and "*" not in allowed: return Command(goto=END, update={"denied": tool_name}) return {} # 放行 # 越权调用在进入工具前就被拦下,不依赖模型自觉
| 工具 | 权限级 | 控制 |
|---|---|---|
| 检索 | 读 | 直接 |
| 发信 | 写 | 人工闸 |
| 改库 | 高危 | 人工 + 审计 |
⚠️ 常见坑:把权限判断写进 prompt 让模型「自觉」,模型偶尔会越权;权限是刚性闸门,必须在代码层拦截,prompt 只做软引导。
💡 关键直觉:安全闸要落在「调用发生之前」的代码路径上,而不是「调用之后」的日志里。
用户输入是安全的第一入口,校验要分层,单靠一层总会有漏洞。三层结构各管一段:
| 层次 | 位置 | 拦截什么 | 实现 |
|---|---|---|---|
| 结构层 | 入口节点 | 类型、长度、格式 | Pydantic 校验 |
| 语义层 | 业务节点 | 非法命令、越权请求 | 规则匹配、白名单 |
| 输出层 | 写操作前 | 敏感内容误发 | 二次确认、脱敏 |
# 结构层:入口就拦掉超长与非法输入 from pydantic import BaseModel, Field class Req(BaseModel): msg: str = Field(max_length=1000) # 超长直接拒绝 # 语义层:白名单放行,其余拒绝 ALLOWED = {"query", "summarize"} def guard(s): if s["action"] not in ALLOWED: return {"denied": s["action"]} return {}
三层的顺序不能乱:先结构后语义,最后输出把关。模型生成的自由文本也要过输出层,别以为生成侧就不会出危险内容——写操作前的二次确认与脱敏,是图式编排里最容易做到却最常被省略的一层。
合规要求「可审计」,落到实现就是动作日志的字段设计。一份合格的审计条目至少包含五要素:
{ "action": "send_mail", # 动作名 "actor": "user-42", # 谁触发 "thread": "t-1001", # 哪次运行 "decision": "approved", # 人工决策结果 "ts": "2026-08-26T10:00:00Z" # 时间 }
设计要点:敏感字段在落日志前脱敏(邮件正文只记长度或摘要);日志只增不删,配合检查点天然留痕;查询按 thread 或 actor 建索引,出问题能快速拉出整条链路。审计不是给开发看的,是给合规和法务看的——字段命名和口径要让他们能看懂,别只有代码注释。
不同行业对智能体应用的要求不同,但常见要求基本可以映射到工程动作上:
| 合规要求 | 工程实现 | 对应章节 |
|---|---|---|
| 数据留痕 | 检查点 + 动作日志 | 第四章 |
| 人工把关 | 写操作 interrupt | 4.4 |
| 权限控制 | 工具分级 + 角色白名单 | 5.3、本节 |
| 脱敏 | 检查点/日志脱敏 | 4.2、本节 |
| 可回滚 | 检查点回退 | 5.5 |
对照这张表做一次差距分析,缺哪块补哪块。合规不是一次评审的事,每次加工具、改状态、动审计逻辑,都要回看这张表是否仍然成立。把合规当作持续约束,而不是上线前的临时冲刺。
「密钥不进状态」是原则,落地时还有几处细节容易漏。第一,节点闭包里引用环境变量时,别把值打印进日志或异常信息里,报错信息里出现 token 也是泄露。第二,多环境配置用环境变量注入,代码里不写默认值,避免把测试密钥带上线。第三,定期轮换密钥后,旧的检查点里若曾存过历史字段,要在轮换时一并清理。第四,工具函数的参数永远不要允许把密钥作为参数传入——模型一旦知道这个工具能传密钥,就有概率把它拼进调用里。把密钥当作和业务数据完全隔离的一层对待,安全防线才算闭环。