🐎 第7章 无为而治:Headless 模式与 CI/CD 集成
📄 一句话概括

headless 的安全问题和交互式不一样:没人看着,所以「默认权限」实际上就是「全部权限」。 五条原则:最小权限、密钥不入库、容器隔离、成本防护、审计留痕。 一份最简配置真跑下来只过了两条。

📝 本章和前面几章的关系

前面几节把流水线搭起来了,这一节补最重要的一课:这些权限开出去,出了事怎么兜。 这一节有一个真跑的检查器:拿一份能跑的最简配置,五条原则逐条查。

一、五条原则各自的落点

最小权限:只给它干活必需的那几项。落在 permissions 段和允许工具列表上。 密钥不入库:密钥只能从环境或密钥库来。落在 secrets 上。 容器隔离:跑在一次性环境里,跑完就丢。落在运行环境的选择上。

成本防护:有上限,超了就停。落在轮数上限和超时上。 审计留痕:每次跑了什么、改了什么都留档。落在日志和产物上。 五条都能落到具体的配置项上——落不到配置项的原则,等于没写。

二、真跑:一份配置过了几条

演示拿一份「能跑」的最简配置逐条查:过了两条,没过三条。 过的两条是容器隔离和审计留痕——因为工作流本来就跑在托管环境上,本来就有输出文件。 没过的是最小权限、密钥、成本防护——这三条恰好都是「不写就沿用默认值」的项。

「密钥不入库」这条没过的原因值得看一眼:它没把密钥写进文件,而是 echo 到了日志里。 一样是泄露,只是换了个地方。而日志通常比文件留得更久、看的人更多。

三、每一条缺了会怎样

缺最小权限:令牌能改代码、能删分支。一次误操作直接进仓库。 缺密钥管理:密钥被打进日志。日志谁都能看,而且留得久。 缺容器隔离:跑在常驻机器上。上一次的残留会影响这一次。

缺成本防护:没有上限,跑不完还一直跑,账单出来才知道。 缺审计:只剩一行退出码。出事回溯时什么都看不到。 五条里有三条的后果很难撤回:进了日志、进了仓库、账单已经产生。

四、两条能立刻做的改动

第一条:删掉那行 echo。调试用的打印留在配置里,是最常见的泄露来源。 第二条:把默认权限从「能写」改成「只读」,需要写的地方单独开。 两条加起来不超过五行,但它们挡掉的是一整类事故。

这一条和 6.8 节那句是同一个意思:安全不能靠自觉,要靠「做不到」。

五、为什么 headless 的安全更难过

交互式有人看着:它要做危险动作,你能看见、能打断、能事后撤销。 无人值守这三道都没有。所以只能靠「一开始就不给它那个权限」。 还有一条更微妙的:headless 的输出会进日志、进评论、进别的地方。

所以 「泄露」的范围比本地大得多——本地跑错只脏了你的屏幕,CI 里跑错留的是永久记录。

六、动手 Demo:五条安全原则,一份配置过了几条

headless 的安全问题和交互式不一样:没人看着,所以「默认权限」就是「全部权限」。 五条原则写成五条检查,拿一份能跑的最简配置逐条查。

checks = [
    (u"最小权限", lambda t: u"permissions:" in t),
    (u"密钥不入库", lambda t: re.search(r"echo[^\n]*(API_KEY|TOKEN|SECRET)", t) is None),
    (u"成本防护", lambda t: u"--max-turns" in t or u"timeout-minutes" in t),
]

实跑输出:

--------------------------------------------------------------------
五条安全原则,一份配置过了几条
--------------------------------------------------------------------

  headless 的安全问题和交互式不一样:没人看着,所以「默认权限」就是「全部权限」。
  五条原则:最小权限、密钥不入库、容器隔离、成本防护、审计留痕。

  ① 五条原则各自的落点
  --------------------------------------------------------------------
| 原则 | 要求 | 落在哪 |
|---|---|---|
| 最小权限 | 只给它干活必需的那几项 | permissions 段、--allowed-tools |
| 密钥不入库 | 密钥只能从环境或密钥库来 | secrets、credentials() |
| 容器隔离 | 跑在一次性环境里,跑完就丢 | runs-on、docker run --rm |
| 成本防护 | 有上限,超了就停 | --max-turns、timeout-minutes |
| 审计留痕 | 每次跑了什么、改了什么都留档 | 日志上传、产物保存 |


  ② 真跑:拿一份最简配置逐条查
  --------------------------------------------------------------------
| 原则 | 结果 | 配置里 |
|---|:---:|:---:|
| 最小权限 | 没过 | 缺 |
| 密钥不入库 | 没过 | 缺 |
| 容器隔离 | 过 | 有 |
| 成本防护 | 没过 | 缺 |
| 审计留痕 | 过 | 有 |

  五条只过了 2 条。这份配置能跑,但它把权限、成本和痕迹全留成了默认值。
  「密钥不入库」这条没过,原因值得看一眼:它没把密钥写进文件,而是 echo 到了日志里。
  一样是泄露,只是换了个地方——而日志通常比文件留得更久、看的人更多。

  ③ 每一条缺了会怎样
  --------------------------------------------------------------------
| 缺哪条 | 直接后果 | 最坏情况 |
|---|---|---|
| 缺最小权限 | 令牌能改代码、能删分支 | 一次误操作直接进仓库 |
| 缺密钥管理 | 密钥被打进日志 | 日志谁都能看,而且留得久 |
| 缺容器隔离 | 跑在常驻机器上 | 上一次的残留会影响这一次 |
| 缺成本防护 | 没有上限 | 跑不完还一直跑,账单才知道 |
| 缺审计 | 只有一行退出码 | 出事回溯时什么都看不到 |

  五条里有三条的后果是很难撤回的:进了日志、进了仓库、账单已经产生。
  所以安全这件事在 headless 里不是优化项,是准入条件。

  ④ 两条能立刻做的改动
  --------------------------------------------------------------------
  第一条:删掉那行 echo。调试用的打印留在配置里,是最常见的泄露来源。
  第二条:把默认权限从「能写」改成「只读」,需要写的地方单独开。
  两条加起来不超过五行,但它们挡掉的是一整类事故。
  ! 检查器是文本扫描,只认关键字,不是真的语义检查——它的作用是提醒,不是把关。

①五条原则都能落到具体配置项上:最小权限落 permissions、密钥落 secrets、容器隔离落运行环境、 成本防护落轮数上限和超时、审计留痕落日志和产物。 落不到配置项的原则,等于没写。 ②一份能跑的最简配置只过了两条:容器隔离和审计留痕(因为本来就跑在托管环境上、本来就有输出文件)。 没过的是最小权限、密钥、成本防护——这三条恰好都是「不写就沿用默认值」的项。 ③「密钥不入库」没过,原因值得看:它没把密钥写进文件,而是 echo 到了日志里。 一样是泄露,只是换了个地方,而日志留得更久、看的人更多。 ④五条里三条的后果很难撤回:进了日志、进了仓库、账单已经产生。所以安全不是优化项,是准入条件。

30 秒实验

  1. 把 echo 那行删掉、加上 permissions 段,重跑一遍。过了几条?还剩哪一条没过? 它为什么必须靠「跑一次看账单」才能发现?
  2. 给检查器加第六条「容器跑完即销毁」,重跑一遍。这一条能用文本扫描查出来吗?查不出来说明什么?
  3. 把工作流改成「先在本地跑,再把结果贴到 PR」,重跑一遍。审计留痕这一条还成立吗?

常见误区

  1. 不写 permissions,用平台默认权限。默认往往比你要的大。
  2. 把密钥 echo 到日志里调试。这和写进文件一样是泄露,而且日志留得更久。
  3. 给「审查」任务开写代码的权限。它能自己改,审查结论就不独立了。
  4. 不设成本上限。跑不完还一直跑,账单出来才知道。
  5. 以为容器隔离是「部署的事」。它决定了上一次的残留会不会影响这一次。
自测三问(点开对答案) 1. 五条安全原则是什么? —— 最小权限、密钥不入库、容器隔离、成本防护、审计留痕。 2. 为什么「密钥 echo 到日志」和「写进文件」一样严重? —— 因为都是把密钥暴露到了不该出现的地方。日志通常留得更久、看的人更多。 3. headless 的安全为什么比交互式更难过? —— 交互式有人看着,能看见、能打断、能撤销;无人值守这三道都没有,只能一开始就不给那个权限。

安全兜住了,下一节往上走一层:不用命令行,直接在程序里调。

相关笔记