跳到主要内容《Agent Runtime 工程化》第七章 权限与安全边界 | 极客日志编程语言Agent Runtime工程化安全陈堂会
《Agent Runtime 工程化》第七章 权限与安全边界
能力越大,边界越要清楚。本章谈权限和安全。 Agent 的能力来自工具,风险也来自工具。一个不会调用工具的模型,最多说错话;一个能执行命令、写文件、联网、访问数据库的 Agent,可能造成真实损失。Runtime 的安全目标不是把 Agent 绑住,而是让它在明确边界内做事;越过边界时,停下来请人确认,或者直接拒绝。 安全不是最后加的一层“确认弹窗”。它应该
站点编辑4 浏览

书名:《Agent Runtime 工程化:从工具调用循环到可恢复执行系统》
作者:陈堂会
平台:极客日志(https://zeeklog.com)
X:@Megick_com
联系邮箱:[email protected]
章节:第七章 权限与安全边界
第七章 权限与安全边界
能力越大,边界越要清楚。本章谈权限和安全。
Agent 的能力来自工具,风险也来自工具。一个不会调用工具的模型,最多说错话;一个能执行命令、写文件、联网、访问数据库的 Agent,可能造成真实损失。Runtime 的安全目标不是把 Agent 绑住,而是让它在明确边界内做事;越过边界时,停下来请人确认,或者直接拒绝。
安全不是最后加的一层'确认弹窗'。它应该贯穿工具定义、输入校验、执行策略、上下文注入、审计记录和恢复执行。很多事故不是因为系统没有任何防护,而是防护散落在 UI、工具函数和 prompt 里,彼此没有统一语义。

图 7-1:工具风险和权限矩阵。风险来自工具能力、输入参数、资源范围和执行环境的组合,而不是工具名称本身。
7.1 权限系统先回答三件事
设计 Agent Runtime 的权限系统,先回答三件事。
第一,谁要做事?通常是某个 run、某个 step、某个 tool call。权限记录不能只写'Agent 请求执行命令',要有 runId、stepId、toolCallId。
第二,要做什么?包括工具名、参数、资源、预期副作用。run_command("npm install") 和 run_command("ls src") 不能因为工具名相同就被归为一类。
第三,谁批准或拒绝?交互模式下是用户,非交互模式下是 policy engine,企业环境里还可能是组织策略或管理员预授权。
可以定义统一决策对象:
type PermissionDecision = {
callId: string;
toolName: string;
risk: RiskAssessment;
decision: "allow" | | ;
: ;
: | | ;
?: ;
};
= {
: | | | | ;
: [];
: [];
: | | ;
: [];
};
"ask"
"deny"
reason
string
decidedBy
"policy"
"user"
"system"
expiresAt
string
type
RiskAssessment
level
"read"
"write"
"network"
"install"
"destructive"
resources
string
sideEffects
string
confidence
"high"
"medium"
"low"
reasons
string
confidence 很有用。命令分析器无法确定行为时,应该提高风险等级,而不是乐观放行。
7.2 安全从 workspace 开始
最基本的边界是 workspace。读写文件工具必须把路径解析到工作区内,禁止 ../ 逃逸,谨慎处理符号链接,避免访问用户主目录、SSH key、浏览器 profile、系统 credential 等敏感路径。
路径检查不要只做字符串前缀。应先解析绝对路径,再判断相对关系:
import fs from "node:fs/promises";
import path from "node:path";
async function resolveWorkspacePath(root: string, inputPath: string) {
const rootReal = await fs.realpath(root);
const resolved = path.resolve(rootReal, inputPath);
const relative = path.relative(rootReal, resolved);
if (relative.startsWith("..") || path.isAbsolute(relative)) {
throw new Error("Path escapes workspace");
}
return resolved;
}
如果工具会跟随符号链接读取真实文件,还要检查 realpath 后的位置是否仍在 workspace 内。对写入工具,则建议默认禁止写入符号链接目标,除非用户明确允许。
workspace 边界还包括忽略目录。.git、node_modules、构建产物、大型缓存、密钥文件、数据库文件,默认不应进入模型上下文。需要访问时,应由工具策略显式允许。
7.3 Shell Policy 要保守、可解释
Shell 是 Agent Runtime 中最危险也最有用的工具。它能快速读项目、跑测试、生成文件,也能删除数据、泄露密钥、安装恶意依赖。
| 等级 | 示例 | 策略 |
|---|
| safe read | pwd, ls, rg, cat, sed, wc | 可自动执行,限制路径和输出 |
| write | touch, mv, cp, apply_patch | 需要 diff 或 preview |
| network | curl, wget, package manager | 需要域名、方法和用途说明 |
| install | npm install, pip install | 需要包名、版本、lockfile 说明 |
| destructive | rm, git reset --hard, drop table | 默认拒绝或强确认 |
命令风险不能只看第一个词。python script.py 可能读文件,也可能删库;node -e 可能只是打印,也可能联网;npm test 通常是测试,但测试脚本可以做任何事。工程上先做保守分类,再逐步扩大自动允许范围。
type CommandRisk = {
level: "safe_read" | "write" | "network" | "install" | "destructive";
reasons: string[];
requiresApproval: boolean;
};
function classifyCommand(command: string): CommandRisk {
const text = command.trim();
const reasons: string[] = [];
if (/\b(rm|unlink|mkfs|shutdown|reboot)\b/.test(text)) {
reasons.push("contains destructive command");
return { level: "destructive", reasons, requiresApproval: true };
}
if (/\b(curl|wget|ssh|scp|nc)\b/.test(text)) {
reasons.push("contains network command");
return { level: "network", reasons, requiresApproval: true };
}
if (/\b(npm|pnpm|yarn|pip|brew)\s+(install|add)\b/.test(text)) {
reasons.push("installs dependencies");
return { level: "install", reasons, requiresApproval: true };
}
if (/[>|;&]/.test(text)) {
reasons.push("contains shell operator");
return { level: "write", reasons, requiresApproval: true };
}
if (/^(pwd|ls|find|rg|cat|sed|wc)\b/.test(text)) {
reasons.push("known read-only command");
return { level: "safe_read", reasons, requiresApproval: false };
}
reasons.push("unknown command");
return { level: "write", reasons, requiresApproval: true };
}
这不是完美安全沙箱。它是策略入口。真正的执行还应配合工作目录限制、环境变量过滤、超时、输出上限、进程树清理和审计日志。
7.4 Secret Scanning 不只扫文件
工具结果、日志、文件片段进入模型上下文前,应经过 secret scanning。至少检查:
- API key;
- OAuth token;
- 私钥;
.env;
- cloud credential;
- 数据库连接串;
- 内部域名和个人信息。
不要只扫描 read_file。rg 搜索结果、测试日志、错误栈、配置打印、命令 stdout/stderr 都可能带出 secret。
发现 secret 后,不要把原文注入模型。可以替换成 [REDACTED_SECRET],并在 trace metadata 中记录'已脱敏'。
type RedactionResult = {
text: string;
findings: {
kind: "api_key" | "private_key" | "token" | "database_url" | "unknown";
location: string;
replacement: string;
}[];
};
如果任务必须处理 secret,比如修改部署配置,应请求用户明确授权,并尽量让处理发生在本地工具中,而不是把 secret 发送给模型。
7.5 供应链风险从安装命令开始
coding agent 很容易倾向'缺包就装'。但安装依赖是供应链入口。Runtime 至少应要求:
- 展示包名和版本;
- 识别 package manager;
- 优先使用 lockfile;
- 对陌生包提高审批等级;
- 对 install scripts 保持警惕;
- 必要时使用
--ignore-scripts;
- 记录 registry、lockfile diff 和执行环境。
这不是过度谨慎。Agent 自动安装依赖后,项目安全边界就从你的代码扩展到第三方包及其安装脚本。
{
"toolName": "run_command",
"command": "pnpm add zod",
"riskLevel": "install",
"preview": {
"packageManager": "pnpm",
"packages": ["zod"],
"registry": "default",
"lockfileMayChange": true,
"installScripts": "unknown"
}
}
用户看到这份 preview,才知道自己批准的不是'跑一个命令',而是'改变依赖图'。
7.6 MCP 工具的信任问题
MCP 让工具接入变容易,也让工具信任问题变复杂。一个 Server 暴露的工具描述不应被 runtime 无条件信任。规范中的工具行为注解适合帮助 UI 和模型理解工具,但不应替代本地安全策略。
MCP 工具审批建议按 Server 信任级别分层:
| Server 类型 | 默认策略 |
|---|
| 官方可信、本地只读 | 可自动执行只读工具 |
| 团队内部、有审计 | 按工具风险等级审批 |
| 第三方远程 | 默认需要确认 |
| 未知来源 | 只允许 discovery,调用需显式授权 |
同时,MCP 的 tools/list 结果进入模型上下文前也要做筛选。不要把几十个不相关工具全部暴露给模型,否则既浪费 token,也增加误调用概率。
MCP Server 断开或工具列表变化时,也应写入 trace。权限系统必须知道'这次调用基于哪个工具列表、哪个 schema、哪个 Server 信任等级'。
7.7 审批记录是系统事实
用户审批不能只当 UI 事件。对 runtime 来说,它是一条系统事实,每次都应写入 audit log:
{
"runId": "run_123",
"stepId": "step_7",
"toolCallId": "call_9",
"toolName": "run_command",
"riskLevel": "destructive",
"preview": "rm -rf build",
"decision": "denied",
"actor": "user",
"timestamp": "2026-08-12T12:00:00+08:00",
"expiresAt": null
}
恢复执行时,这些记录尤其重要。已经被拒绝的危险工具,不能因为进程重启就再次自动执行。已经批准的一次性操作,也不应无限期复用授权。
| 类型 | 生命周期 | 示例 |
|---|
| one-shot approval | 只对当前 tool call 有效 | 批准一次 apply_patch |
| scoped approval | 对限定范围和时间有效 | 本 run 内允许 read_file 读取 src/** |
不要默认把用户一次批准扩展成永久授权。授权过宽,会让后续 resume 和自动重试变危险。
7.8 非交互模式必须有 policy
CI、后台任务、定时任务不能等待用户点按钮。非交互模式必须有明确 policy,而不是默认全放行。
{
"mode": "non_interactive",
"workspace": {
"read": ["src/**", "package.json", "README.md"],
"write": []
},
"tools": {
"list_files": "auto",
"read_file": "auto",
"search_code": "auto",
"run_command.safe_read": "auto",
"run_command.write": "deny",
"run_command.network": "deny",
"run_command.install": "deny",
"run_command.destructive": "deny"
}
}
非交互模式的策略要进入 run metadata。否则同一个任务在本地和 CI 跑出不同结果时,很难解释差异。
7.9 沙箱不是单一开关
| 层级 | 作用 |
|---|
| 路径白名单 | 控制文件读写范围 |
| 命令策略 | 控制 shell 能力 |
| 环境变量过滤 | 避免 secret 被子进程读取 |
| 网络策略 | 控制外部访问 |
| 进程隔离 | 限制不可信代码 |
| 容器或 VM | 隔离文件系统和系统调用 |
| 审计日志 | 事后追踪和恢复依据 |
入门项目可以先实现路径白名单、命令策略和审计日志。生产系统再逐步引入容器、网络隔离、资源配额和组织策略。
关键是不要在 UI 上写'sandbox enabled'就以为安全。你要能说清楚:它隔离了什么,没有隔离什么,失败时怎样处理。
7.10 本章的实现路径
第一步,统一 RiskAssessment 和 PermissionDecision 类型。所有工具调用都先生成风险评估,再执行。
第二步,给文件工具加 workspace 解析、realpath 检查、忽略目录和 secret redaction。
第三步,给 shell 工具加命令分类、工作目录限制、环境变量过滤、超时和输出上限。
第四步,给写入、联网、安装、破坏性操作生成 preview。交互模式询问用户,非交互模式走 policy。
第五步,把所有审批、拒绝、policy 自动裁决写入 audit log,并让 checkpoint 引用这些记录。
第六步,为 MCP Server 增加信任等级和工具暴露筛选。
7.11 验收标准
完成本章后,你应该能解释为什么 Agent 不能直接执行任意 shell;能为 MCP 工具设计权限审批策略;能区分 schema 校验、权限策略和用户确认;能保证恢复执行后不会重复越权。
| 用例 | 期望结果 |
|---|
| 读取 workspace 外路径 | 拒绝,返回 PERMISSION_DENIED |
读取 .env | 默认拒绝或脱敏后返回 |
执行 ls src | 自动允许,限制输出长度 |
执行 curl https://example.com | 标记 network,要求确认 |
执行 npm install left-pad | 标记 install,展示依赖和 lockfile 风险 |
执行 git reset --hard | 默认拒绝或强确认 |
| 用户拒绝高风险工具后 resume | 不自动重放该工具 |
安全边界做得好,用户才敢把 Agent 放进真实工程目录。它不是'少做事',而是让每一次做事都有依据、边界和后路。
系列文章目录
相关免费在线工具
- 编码工具包
Base32/Base58 编解码、JSON 片段 URL 安全转义等,与 URL/HTML/Base64 工具互补。 在线工具,编码工具包在线工具,online
- 打乱字符串
确保字符串 url、文件名和 id 安全。 在线工具,打乱字符串在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online