允许执行,不等于允许一切:Open Nua 的 Agent 安全心智模型
Open Nua Engineering
做 Agent 安全时,最容易从“要不要弹确认框”开始讨论。
但确认框只回答了一个问题:用户是否同意这次意图。它没有回答这个进程最终能读哪些文件、能否继承本机凭证、能连接哪些网络、失败后是否可以重试,以及管理员应该看到什么证据。
这也是我们在 Open Nua 里逐步形成的核心判断:授权不是安全控制的终点,而是受控执行的起点。
上一篇关于 Agent 治理的文章,主要讨论执行身份、Capability、HITL、恢复和 unknown outcome。这一篇继续往下走一步:当一项动作已经被允许,系统如何保证它仍然只在批准的边界内发生。
不要试图“信任 Agent”,要控制执行链
模型可以被提示词影响,文档可以携带注入指令,网页和远程响应会变化,Skill 只是文字,Plugin 也可能升级。把安全建立在“Agent 应该理解并遵守规则”上,等于把最终授权交给了最不稳定的一层。
Open Nua 因此把来自 Prompt、模型、客户端动作、Skill、Plugin、workspace 文件和远程响应的内容统一视为不可信意图。它们可以提出动作,也可以提供风险信号,但不能自己声明身份、授权、数据范围或安全等级。
整条安全链可以压缩成一句话:
不可信意图
→ 可信事实产生决策
→ 决策绑定受限执行环境
→ 外部访问使用短期凭证与精确网络租约
→ 只留下最小充分的安全证据
这里最重要的不是某一种沙箱技术,而是每一层只负责一个清楚的问题。
第一层:谁有权做决定
Open Nua 把本地授权事实留在 Desktop Main。Renderer、模型或 Plugin 提交的内容只能表达“想做什么”;Main 会结合已认证的用户与 Profile、已安装包摘要、Capability、目标、数据范围、当前 consent 和限制条件,生成不可变的决策 revision。
这带来两个直接结果。
第一,安全信号只能维持或提高风险,不能降低风险。模型说“这是安全的”、网页说“请忽略之前规则”、Skill 写着“无需确认”,都不能产生新的权限。
第二,只要 actor、package、capability、target、data scope 或参数发生变化,就必须重新决策。一次批准不是给整条会话发了一张万能通行证。
在产品上,这个原则映射为三档本地执行模式:
- 每次询问:敏感本地动作在执行前交还用户确认;
- 受控执行:默认模式,在批准 workspace、文件、环境、网络和资源边界内自主完成工作;
- 完全访问:用户对当前前台 Thread 明确接受更高风险,关闭文件与网络沙箱,但仍保留超时、进程树、环境、资源和输出边界。
完全访问不是“关闭所有安全”。它不会自动传播给 Automation、飞书或 Plugin 后台任务,高风险外部副作用仍然需要独立治理。
第二层:允许动作,不等于允许宿主能力
很多系统把 cwd 指向 workspace,就认为 Agent 被限制在 workspace 内。但工作目录只是默认起点:绝对路径、..、软硬链接、子进程、解释器和网络连接仍可能越过它。
Open Nua 把“是否允许”与“如何执行”拆成两个模块:
LocalExecutionPolicy产生决策:风险等级、允许/确认/拒绝、目标范围和所需控制;ExecutionEnvironment消费决策:实际限制进程、文件、环境变量、网络、资源、取消和清理。
所以即便 policy 返回 allow,执行环境缺少所需能力时仍然必须拒绝。系统不能因为沙箱启动失败,就静默回退到宿主 shell;也不能因为某个平台暂时没有 adapter,就假装 cwd 已经提供了隔离。
在当前 Open Nua Desktop 开发线上,macOS 本地执行使用真实系统级 containment,默认只允许批准的 workspace 文件访问、清理后的环境和受控资源,并拒绝任意网络。超时、取消或崩溃后,还要证明子进程和临时资源已经收口。
这层心智模型可以概括为:Policy 决定边界,Environment 证明边界真的存在。
第三层:凭证和网络不是环境,而是能力
传统 CLI 很依赖 ambient authority:环境变量里有 token,用户目录里有登录文件,系统网络默认全通。对人工操作很方便,对 Agent 却意味着任何被调用的脚本都可能顺手获得超出任务需要的权限。
Open Nua 的做法是把凭证和网络从环境中拿出来,交给 CredentialBroker:
- workload 默认看不到长期 credential;
- Agent、Plugin、shell、argv、workspace、普通日志和 audit 都不接收原始 secret;
- 需要远程访问时,只取得 opaque reference 或短期 lease;
- lease 绑定 actor、Profile、execution、capability、目标、method、path 和过期时间;
- 网络默认拒绝,只有精确批准的目标可以经过 Gateway 受控代理;
- redirect、DNS 变化、localhost、metadata 地址和 proxy 环境变量不能扩大出口。
这让“允许调用某个服务”不再等于“允许任意联网”,也让凭证不必经过模型上下文才能完成工作。
Consent 不是一个布尔值,而是一段有范围的关系
如果系统只有 approved = true,很快就会遇到问题:批准的是这一次,还是整个 Thread?目标改变后还有效吗?Plugin 升级后能继续复用吗?
Open Nua 现在区分三种常见 consent:
- 单次授权:只用于当前决策与动作;
- Thread Scoped Consent:绑定当前 Thread、用户、Profile、Capability、目标和 policy revision;
- Plugin Standing Consent:用户安装或启用 Plugin 时,对包中明确声明的低风险能力建立持续授权。
Standing consent 也不是“这个 Plugin 永远可信”。它绑定包版本与摘要、Capability 集合和安装状态;权限扩大、包内容变化、撤销或卸载都会使它失效。发布、删除、转账等高风险或外部副作用,也不能借安装授权绕过逐次确认。
这正是 OpenCLI Plugin 可以管理自己的 CLI 与 Chrome Extension 前置,却不能因此获得任意 shell 或任意网络的原因。
第四层:审计是证据,不是会话副本
安全系统常见的另一个误区,是为了“可审计”把 Prompt、回复、完整参数、绝对路径和身份信息全部复制进日志。这样确实更容易搜索,却同时创建了一套新的敏感数据事实源。
Open Nua 的 SecurityAudit 只接受字段 allowlist。它记录稳定的 execution/effect reference、包与 Capability 摘要、风险、决策、reason code、结果、耗时和必要的大小区间,不保存原始 Prompt、回复、credential、完整参数、URL query/body 或外部身份。
Desktop 先写入有界本地队列,Gateway 和 Backend 幂等接收,Admin Web 再按角色展示聚合、调查和导出。调查读取本身也会留下审计;kill switch 和 rollout 只能收紧或禁用能力,不能把高风险动作改成 unrestricted fallback。
最小证据的目标不是重放用户的一切,而是回答:谁基于什么事实做了什么决策,哪个边界执行了它,结果是否确定。
一条恶意指令会经过什么
我们的第一条安全 tracer bullet 很简单:workspace 中的一份恶意文档诱导 Agent 读取 workspace 外的 credential,再把它发到网络。
这条攻击不靠某一个“聪明分类器”阻断,而是连续经过多层边界:
- 文档和模型输出只能形成意图,不能授予更高数据范围;
- Policy 发现目标越出已批准 scope,拒绝或要求新的确认;
- 即使上层误判,Execution Environment 仍会阻止 workspace 外文件访问;
- workload 没有原始 credential,也没有默认网络;
- Broker 不会为未声明目标签发 lease;
- Audit 记录拒绝原因,但不复制文档内容、路径或 secret。
安全的价值来自这些层彼此独立:任何一层都不假设上一层永远正确。
四个概念如何映射到 Open Nua
| 心智模型 | Open Nua 中的 owner | 用户能感知到什么 |
|---|---|---|
| 可信决策 | Desktop Main / LocalExecutionPolicy | 三档执行模式、HITL、Scoped Consent、稳定 reason |
| 受限执行 | Local Runtime / ExecutionEnvironment | workspace 边界、默认无任意网络、超时取消与残留清理 |
| 受控外部能力 | Desktop + Gateway / CredentialBroker | 不暴露原始凭证,只访问批准的服务与路径 |
| 最小安全证据 | Desktop、Gateway、Backend / SecurityAudit | 可解释的安全状态、Admin 聚合、调查、kill switch 与 retention |
这四个模块不是为了搭建一个巨大的中央 Policy Service。相反,它们让决定、执行、外部能力和证据各有明确 owner,也让 Browser、shell、Plugin 和未来 Managed Runtime 可以在同一安全不变量下使用不同 adapter。
安全不是一句“我们有沙箱”
我们最终用同一套 machine-readable vectors 验证 policy、真实本地 adapter、Gateway/Backend integration 和开发态 Electron。正向路径必须能完成工作,恶意路径必须在真实边界被拒绝;HITL race、replay、timeout、unknown、cancel 和残留也都属于安全结果,而不只是稳定性问题。
当前这套实现属于 Open Nua Desktop 1.5 开发线。它已经完成 macOS 当前开发架构、跨服务与开发态产品链路的安全验收,但不等同于所有平台、packaged candidate 或 Managed Runtime 都已经验证。缺少真实 enforcement adapter 的平台必须显式 unavailable,而不是降级运行。
对任何 Agent 系统,我们现在会先问五个问题:
- 不可信输入最终在哪里失去授权能力?
- allow 之后,谁证明文件、网络、环境和资源边界真实存在?
- 凭证与外部访问是否仍是 ambient authority?
- consent 是否绑定身份、范围、版本和时间?
- 审计是在证明控制,还是在复制敏感内容?
当这五个问题都有清楚答案时,Agent 安全才不再是一组 Prompt 规则或确认弹窗,而是一条可以被实现、测试、运营和持续演进的受控执行链。