1. 沙箱隔离:别让 Agent 拿着服务器的钥匙到处跑
最常见的错误:给 Agent 分配和开发者账号一样的权限。Agent 需要读文件、执行 Shell、调用 API → 它就自动继承了你所有的 IAM 角色。一次 Agent 被攻破 = 整个基础设施沦陷。
# ❌ 危险示范:Agent 拥有全部权限
tools = [
FileReadTool(), # 可以读取任何文件,包括 .env
ShellTool(), # 可以执行任何 shell 命令
APIClientTool(api_key=os.environ["SECRET_KEY"]) # 密钥直接暴露给 LLM
]
# ✅ 安全方案:工具级权限控制 + 审计日志
from goclaw import Agent, ToolPolicy
policy = ToolPolicy()
# 文件访问:只读 + 仅限项目目录
policy.add_rule("file_read",
allowed_paths=["/app/src/", "/app/config/"],
denied_paths=["/app/.env", "/app/secrets/"],
audit=True
)
# Shell:禁止危险命令
policy.add_rule("shell",
allowed_commands=["git", "npm", "pytest", "docker-compose"],
denied_patterns=["rm -rf", "curl.*|nc ", "ssh ", "chmod 777"],
audit=True
)
# API 密钥:通过密钥管理器引用,LLM 永远看不到真实密钥
policy.add_rule("api_call",
require_secret_manager=True, # 从 Vault/SSM 解析,不暴露给 LLM
allowed_endpoints=["api.stripe.com", "api.github.com"],
audit=True
)
agent = Agent(
model="claude-sonnet-4",
tools=[FileReadTool(policy=policy), ShellTool(policy=policy)],
isolation="gvisor", # 在 gVisor 沙箱中运行,与宿主机内核隔离
)
result = agent.run("部署最新版本")
# 每次工具调用都有日志:谁、做了什么、用了什么参数、返回了什么
为什么这个漏洞这么普遍? 教程只教你怎么让 Agent "能干活",不教你怎么让它"安全干活"。Vercel 事件之所以发生,就是因为一个 Agent 拥有了它根本不需要的环境变量访问权限。
数据支撑:GitHub 上的 goclaw(2,901★)专门为了这个原因做了多租户隔离和5层安全防护。
2. 工具投毒:别信任 Agent 调用工具后返回的数据
AI Agent 调用工具,工具返回数据——但如果工具返回的数据被污染,用于操控 Agent 下一个决策呢?
这就是工具投毒(Tool Poisoning),比想象中常见。第三方 MCP 服务器、外部 API、甚至你自己的向量数据库,都可能返回经过精心构造的内容来影响 Agent 行为。
# ❌ 易受攻击:工具输出直接喂给 Agent
def search_codebase(query: str) -> str:
results = vector_db.similarity_search(query, k=10)
# 攻击者可能在向量数据库中植入提示词注入载荷
return "\n".join([r.content for r in results])
# 这些内容会被嵌入下一个 LLM 提示词,没有任何过滤
# ✅ 安全方案:输出净化 + Schema 验证 + 注入检测
import re
def sanitize_tool_output(raw_output: str, max_length: int = 8000) -> str:
"""移除工具输出中的潜在提示词注入模式。"""
injection_patterns = [
r"<\|system\|.*?\|>", # 角色混淆
r'<script[^>]*>.*?</script>', # XSS 向量
r'^IGNORE ALL PREVIOUS.*', # 直接覆盖尝试
r'^You are now.*?:', # 角色重分配
r'\n(?:system|assistant|user):', # 轮次混淆
]
cleaned = raw_output[:max_length]
for pattern in injection_patterns:
cleaned = re.sub(pattern, "[已过滤]", cleaned, flags=re.IGNORECASE | re.DOTALL)
if cleaned.count("[已过滤]") > 3:
logger.warning(f"工具输出中检测到疑似注入攻击:{cleaned[:200]}")
return cleaned
def safe_search(query: str) -> str:
results = vector_db.similarity_search(query, k=10)
raw = "\n".join([r.content for r in results])
return sanitize_tool_output(raw)
agent.register_tool(
"search_codebase",
handler=safe_search,
output_validator=sanitize_tool_output,
rate_limit={"max_calls_per_minute": 30},
requires_confirmation=True # 可疑查询标记为需要人工审核
)
为什么重要:Lovable 安全事件中,安全研究人员发现被操纵的上下文可以将 Agent 操作重定向到非预期目标。如果你的检索器返回污染内容,本质上等于把 Agent 控制权交给了攻击者。
3. 密钥管理:永远不要让 LLM 看到任何密钥
生产代码中见到最多的模式:Agent 直接持有 API 密钥。一旦你把 api_key=os.environ["OPENAI_KEY"] 传给 Agent 的工具定义,这个密钥就进入了 LLM 的上下文窗口。取决于你的服务商的日志策略,它可能被记录在审计日志、训练数据、甚至在提示词注入攻击中被窃取。
import os
from abc import abstractmethod
# ✅ 安全方案:密钥管理器模式 —— 密钥永远不接触 LLM 上下文
class SecretBackedTool:
"""需要密钥但永远不暴露给 LLM 的工具基类。"""
def __init__(self, secret_name: str, secret_manager: str = "aws-ssm"):
self.secret_name = secret_name
self.secret_manager = secret_manager
def _resolve_secret(self) -> str:
"""在运行时从密钥管理器解析。绝不存储在上下文中。"""
if self.secret_manager == "aws-ssm":
import boto3
ssm = boto3.client('ssm')
return ssm.get_parameter(Name=self.secret_name, WithDecryption=True)['Parameter']['Value']
elif self.secret_manager == "hashicorp-vault":
import hvac
client = hvac.Client()
return client.secrets.kv.v2.read_secret_version(
path=self.secret_name
)['data']['data']['value']
elif self.secret_manager == "env":
return os.environ[self.secret_name]
@abstractmethod
def execute(self, **kwargs):
pass
def __call__(self, *args, **kwargs):
# 密钥解析在执行时发生,不在注册时发生
resolved = self._resolve_secret()
return self.execute(secret=resolved, **kwargs)
class StripeTool(SecretBackedTool):
"""示例:Stripe API 工具,零密钥暴露。"""
def __init__(self):
super().__init__(secret_name="/prod/stripe/api-key")
def execute(self, secret: str, action: str, amount: int) -> dict:
import stripe
stripe.api_key = secret
if action == "charge":
return stripe.Charge.create(amount=amount, currency="usd", source="tok_visa")
elif action == "refund":
return stripe.Refund.create(charge=kwargs.get("charge_id"))
return {"status": "ok"}
# ✅ 注册时工具定义中不包含任何密钥
agent.register_tool("stripe", StripeTool())
# LLM 看到的是:工具名、参数 Schema、返回类型
# LLM 看不到:实际的 API 密钥
核心原则:密钥在执行时解析,不在注册时解析。LLM 上下文永远不包含原始凭证。
4. Agent 间认证:多 Agent 系统需要 mTLS
当你在运行多个协作的 AI Agent(比如:一个写代码,一个审查代码,一个部署)时,你面临一个跨 Agent 信任问题。如果没有认证,被攻陷的 Agent 可以冒充其他 Agent 执行未授权操作。
# ✅ mTLS 风格的 Agent 认证
import hashlib, hmac, time, json
class AgentAuth:
"""多 Agent 系统的轻量级双向认证。"""
def __init__(self, agent_id: str, signing_key: str):
self.agent_id = agent_id
self.signing_key = signing_key.encode()
def sign(self, payload: dict, nonce: str = None) -> dict:
"""使用 HMAC 签名跨 Agent 消息。"""
nonce = nonce or f"{time.time_ns()}"
data = json.dumps(payload, sort_keys=True) + nonce
signature = hmac.new(
self.signing_key, data.encode(), hashlib.sha256
).hexdigest()
return {
**payload,
"_auth": {
"agent_id": self.agent_id,
"nonce": nonce,
"signature": signature,
"ts": time.time()
}
}
def verify(self, signed_payload: dict) -> bool:
"""验证来自其他 Agent 的消息。"""
auth = signed_payload.get("_auth", {})
if not auth:
return False
# 拒绝过期消息(5分钟窗口)
if abs(time.time() - auth.get("ts", 0)) > 300:
return False
payload = {k: v for k, v in signed_payload.items() if k != "_auth"}
expected_sig = self.sign(payload, nonce=auth["nonce"])["_auth"]["signature"]
return hmac.compare_digest(expected_sig, auth["signature"])
# Agent A(代码编写者)向 Agent B(审查者)进行身份认证
auth_a = AgentAuth("code-writer", signing_key="shared-secret-xyz")
message = auth_a.sign({
"action": "review_code",
"file": "/app/src/deploy.py",
"commit": "a3f8c2d"
})
# Agent B 在接受任务前先验证
auth_b = AgentAuth("code-reviewer", signing_key="shared-secret-xyz")
if not auth_b.verify(message):
raise PermissionError(
f"Agent {message['_auth']['agent_id']} 身份验证失败"
)
# 这防止了:攻击者攻陷一个 Agent 后冒充代码编写者
现实背景:Anthropic 在2026年4月23日发布的 Claude Code 质量问题事后分析报告中指出,多 Agent 编排缺乏适当认证是导致不可预测行为的关键因素之一。当 Agent 可以互相冒充时,你根本做不到问责。
5. 审计日志:你修复不了看不见的东西
每个生产级 AI Agent 部署都需要全面、防篡改的审计日志。不是简单记录"Agent 做了什么",而是记录"确切的提示词是什么、调用了哪些工具、工具返回了什么、最终决策是什么"。
import json, hashlib
from datetime import datetime
from pathlib import Path
class AgentAuditLogger:
"""AI Agent 操作的防篡改审计日志。"""
def __init__(self, log_path: str = "/var/log/agent-audit.jsonl"):
self.log_path = Path(log_path)
def log(self, event_type: str, data: dict, context: dict):
"""写入不可变审计条目。"""
entry = {
"ts": datetime.utcnow().isoformat() + "Z",
"type": event_type,
"data_hash": hashlib.sha256(
json.dumps(data, sort_keys=True).encode()
).hexdigest(),
"data": data,
"context": {
"model": context.get("model"),
"session_id": context.get("session_id"),
"user_id": context.get("user_id"),
},
"prev_hash": self._last_hash(),
}
# 追加写入,不可修改
with open(self.log_path, "a") as f:
f.write(json.dumps(entry) + "\n")
def _last_hash(self) -> str:
"""获取最后一条记录的哈希值,用于链式完整性校验。"""
if not self.log_path.exists():
return "genesis"
with open(self.log_path) as f:
lines = f.readlines()
if not lines:
return "genesis"
return json.loads(lines[-1])["data_hash"]
def get_session_log(self, session_id: str):
"""获取某个会话的完整审计轨迹(用于事后分析)。"""
events = []
with open(self.log_path) as f:
for line in f:
entry = json.loads(line)
if entry.get("context", {}).get("session_id") == session_id:
events.append(entry)
return events
def detect_anomalies(self, session_id: str):
"""检测会话中的可疑模式。"""
events = self.get_session_log(session_id)
anomalies = []
for e in events:
# 检测高频工具调用(可能的循环/DoS)
if e["type"] == "tool_call":
window = [
x for x in events
if abs(float(x.get("ts", 0).replace("Z", "").replace("T", " ")) -
float(e.get("ts", 0).replace("Z", "").replace("T", " "))) < 60
]
if len(window) > 20:
anomalies.append(f"高频调用:60秒内{len(window)}次")
# 检测密钥访问模式
if e["type"] == "tool_output" and "key" in str(e["data"]).lower():
anomalies.append("检测到疑似密钥访问")
return anomalies
# 在 Agent 中使用
audit = AgentAuditLogger()
agent = Agent(
model="claude-sonnet-4",
tools=[...],
callbacks={
"on_tool_call": lambda tool, params: audit.log("tool_call", {"tool": tool, "params": params}, ctx),
"on_tool_output": lambda tool, output: audit.log(
"tool_output",
{"tool": tool, "output_hash": hashlib.sha256(str(output).encode()).hexdigest()},
ctx
),
"on_decision": lambda decision: audit.log("decision", {"decision": decision}, ctx),
}
)
行业终于开始重视了
2026年4月的 Vercel 和 Lovable 安全事件是警钟。但即使在这些事件之前,社区已经在讨论:
- Hacker News:《人们并不渴望自动化》(35票)——讨论正在从"Agent 能做到吗?"转向"Agent 值得信任吗?"
- Reddit r/artificial:Anthropic 告诉联邦法院模型一旦部署就无法控制——责任归属讨论正在迫使各组织认真对待 Agent 安全
- GitHub:NVIDIA 的 safety-for-agentic-ai 蓝图和 goclaw 的5层安全模型表明业界正在行动
多 Agent 安全问题不是理论上的。OpenHands(71,915★)和 MetaGPT(67,368★)是 GitHub 上 Star 数最高的 AI 项目之二——但它们默认都没有生产级安全防护。
今天就能做的5件事
- 审计你当前 Agent 的工具权限——有多少工具拥有不受限制的权限?
- 给所有返回外部/用户生成内容的工具加上输出净化
- 把密钥迁移到管理器(AWS SSM、Vault 或简单的引用方案)
- 如果运行多个 Agent,立即实现跨 Agent 认证
- 从现在开始记录所有操作——你无法修复看不见的漏洞
"原型 Agent"和"生产级 Agent"之间的差距,主要就是安全差距。工具已存在,方案已验证。现在要做的就是抢在下一个安全事件之前把它们用上。
你在生产环境中遇到过 AI Agent 安全问题吗?欢迎在评论区分享你的经历和解决方案。
相关阅读:
Top comments (0)