模块六:安全与权限 | 前置依赖:第 17 课 | 预计学习时间:50 分钟
学习目标
完成本课后,你将能够:
- 解释 CYBER_RISK_INSTRUCTION 的设计意图与 Safeguards 团队的所有权模式
- 描述双用途工具(dual-use tools)的授权判断逻辑
- 理解 Undercover 模式的三层激活条件与死代码消除机制
- 说明 Undercover 指令如何防止内部信息泄露到公开仓库
19.1 CYBER_RISK_INSTRUCTION:安全红线定义
文件位置与所有权
constants/cyberRiskInstruction.ts 是一个只有 24 行的小文件,但它承载着极其重要的安全职责。文件头部的注释明确标识了所有权:
/**
* CYBER_RISK_INSTRUCTION
*
* IMPORTANT: DO NOT MODIFY THIS INSTRUCTION WITHOUT SAFEGUARDS TEAM REVIEW
*
* This instruction is owned by the Safeguards team and has been carefully
* crafted and evaluated to balance security utility with safety.
*
* If you need to modify this instruction:
* 1. Contact the Safeguards team (David Forsythe, Kyla Guru)
* 2. Ensure proper evaluation of the changes
* 3. Get explicit approval before merging
*/
这是 Claude Code 代码库中少数由非工程团队直接管理的代码之一。Safeguards 团队负责评估每一次修改对安全边界的影响。
指令内容分析
export const CYBER_RISK_INSTRUCTION = `IMPORTANT: Assist with authorized security
testing, defensive security, CTF challenges, and educational contexts. Refuse
requests for destructive techniques, DoS attacks, mass targeting, supply chain
compromise, or detection evasion for malicious purposes. Dual-use security tools
(C2 frameworks, credential testing, exploit development) require clear
authorization context: pentesting engagements, CTF competitions, security
research, or defensive use cases.`
这段指令定义了三个层次的行为边界:
行为边界的三层结构
┌─────────────────────────────────────────────────┐
│ 允许(Always Allow) │
│ - 授权的安全测试 (authorized security testing) │
│ - 防御性安全 (defensive security) │
│ - CTF 挑战 (CTF challenges) │
│ - 教育场景 (educational contexts) │
├─────────────────────────────────────────────────┤
│ 条件允许(Conditional) │
│ - C2 框架 (C2 frameworks) │
│ - 凭据测试 (credential testing) │
│ - 漏洞开发 (exploit development) │
│ ↳ 需要明确的授权上下文: │
│ 渗透测试合同 / CTF 竞赛 / │
│ 安全研究 / 防御用途 │
├─────────────────────────────────────────────────┤
│ 禁止(Always Refuse) │
│ - 破坏性技术 (destructive techniques) │
│ - DoS 攻击 │
│ - 大规模目标攻击 (mass targeting) │
│ - 供应链攻击 (supply chain compromise) │
│ - 恶意目的的检测规避 (detection evasion) │
└─────────────────────────────────────────────────┘
双用途工具的判断逻辑
中间层"条件允许"是设计中最微妙的部分。同样一个 C2 框架(如 Cobalt Strike),在渗透测试中是合法工具,在未授权场景中就是攻击工具。Claude Code 的判断依据是授权上下文(authorization context):
- 渗透测试合约(pentesting engagements)— 有明确的商业授权
- CTF 竞赛(CTF competitions)— 在受控环境中
- 安全研究(security research)— 学术或防御研究
- 防御用途(defensive use cases)— 蓝队工作
这段指令被注入到 Claude 的系统提示中,由模型在推理时自行判断上下文是否满足条件。工程代码不做硬编码的规则匹配 — 这是一个纯粹的提示工程决策。
设计哲学
这条指令的简洁是刻意的。它不试图枚举所有可能的攻击类型(那将是一个不断过时的列表),而是定义了原则性边界:
- 有授权 + 防御目的 = 允许
- 无授权 + 攻击目的 = 拒绝
- 工具本身中性,上下文决定判断
另一个值得注意的细节是文件末尾的注释:
* Claude: Do not edit this file unless explicitly asked to do so by the user.
这是对 Claude 自身的指令 — 防止 AI 在代码修改过程中意外更改安全边界。
19.2 Undercover 模式:身份隐藏机制
问题背景
Anthropic 的内部工程师(ant 用户)使用 Claude Code 为公开的开源项目贡献代码。如果提交信息中包含 "Claude Code"、内部模型代号(如 "Capybara"、"Tengu")或内部项目名称,就会泄露 Anthropic 的内部信息到公共代码仓库。
Undercover 模式就是为解决这个问题而设计的。
文件结构
utils/undercover.ts 包含三个导出函数:
utils/undercover.ts
├── isUndercover() → 判断是否激活
├── getUndercoverInstructions() → 获取注入的提示内容
└── shouldShowUndercoverAutoNotice() → 是否显示首次激活提示
激活条件的三层门控
isUndercover() 决策树:
process.env.USER_TYPE === 'ant' ?
├── NO → return false(外部构建,永远不激活)
│
└── YES → CLAUDE_CODE_UNDERCOVER=1 ?
├── YES → return true(强制开启)
│
└── NO → getRepoClassCached() !== 'internal' ?
├── YES → return true(非内部仓库,自动开启)
│
└── NO → return false(内部仓库,关闭)
关键代码:
export function isUndercover(): boolean {
if (process.env.USER_TYPE === 'ant') {
if (isEnvTruthy(process.env.CLAUDE_CODE_UNDERCOVER)) return true
// Auto: active unless we've positively confirmed we're in an allowlisted
// internal repo. 'external', 'none', and null (check not yet run) all
// resolve to ON. The check is primed in setup.ts; only 'internal' → OFF.
return getRepoClassCached() !== 'internal'
}
return false
}
安全默认值设计(Safe Default)
注意一个关键的安全设计决策:默认是开启的。
getRepoClassCached() 的返回值:
- 'internal' → 确认是内部仓库 → 关闭 undercover
- 'external' → 确认是外部仓库 → 开启 undercover
- 'none' → 不在 git 仓库中 → 开启 undercover(安全默认)
- null → 检查尚未运行 → 开启 undercover(安全默认)
只有明确确认是内部仓库时才关闭。在任何不确定的情况下(不在 git 仓库、检查未完成、外部仓库),都保持开启。这是因为泄露内部信息的后果远大于在内部仓库中多加一层保护的成本。
没有强制关闭
源码注释中明确说明:
* - There is NO force-OFF. This guards against model codename leaks — if
* we're not confident we're in an internal repo, we stay undercover.
CLAUDE_CODE_UNDERCOVER=1 可以强制开启,但没有 CLAUDE_CODE_UNDERCOVER=0 的强制关闭功能。这是一个单向安全阀 — 只能让它更严格,不能让它更宽松。
注入的提示内容
当 undercover 模式激活时,getUndercoverInstructions() 返回一段详细的指令:
export function getUndercoverInstructions(): string {
if (process.env.USER_TYPE === 'ant') {
return `## UNDERCOVER MODE — CRITICAL
You are operating UNDERCOVER in a PUBLIC/OPEN-SOURCE repository. Your commit
messages, PR titles, and PR bodies MUST NOT contain ANY Anthropic-internal
information. Do not blow your cover.
NEVER include in commit messages or PR descriptions:
- Internal model codenames (animal names like Capybara, Tengu, etc.)
- Unreleased model version numbers (e.g., opus-4-7, sonnet-4-8)
- Internal repo or project names (e.g., claude-cli-internal, anthropics/…)
- Internal tooling, Slack channels, or short links (e.g., go/cc, #claude-code-…)
- The phrase "Claude Code" or any mention that you are an AI
- Any hint of what model or version you are
- Co-Authored-By lines or any other attribution
Write commit messages as a human developer would — describe only what the code
change does.`
}
return ''
}
禁止泄露的信息类型
| 类别 | 示例 | 风险 |
|---|---|---|
| 内部模型代号 | Capybara, Tengu | 泄露未发布模型信息 |
| 未发布版本号 | opus-4-7, sonnet-4-8 | 泄露产品路线图 |
| 内部仓库名 | claude-cli-internal | 泄露内部项目结构 |
| 内部工具 | go/cc, #claude-code-... | 泄露内部基础设施 |
| AI 身份 | "Claude Code", "AI generated" | 泄露 AI 使用情况 |
| 归属信息 | Co-Authored-By 行 | 泄露 AI 参与 |
19.3 死代码消除(Dead Code Elimination)
构建时优化
Undercover 模式的所有逻辑都被 process.env.USER_TYPE === 'ant' 门控包裹。源码注释解释了这个设计:
* All code paths are gated on process.env.USER_TYPE === 'ant'. Since USER_TYPE is
* a build-time --define, the bundler constant-folds these checks and dead-code-
* eliminates the ant-only branches from external builds. In external builds every
* function in this file reduces to a trivial return.
在构建流程中:
┌─────────────────────────────────────────┐
│ 源码(所有分支都存在) │
│ │
│ if (USER_TYPE === 'ant') { │
│ // undercover 逻辑 │
│ } │
│ return false │
└──────────────┬──────────────────────────┘
│
┌──────────┴──────────┐
│ │
▼ ▼
┌─────────┐ ┌──────────┐
│ ant 构建 │ │ 外部构建 │
│ │ │ │
│ 保留全部 │ │ 常量折叠 │
│ 分支逻辑 │ │ USER_TYPE │
│ │ │ !== 'ant' │
│ │ │ → false │
│ │ │ → 删除分支 │
└─────────┘ └──────────┘
外部构建的用户永远看不到 undercover 相关的代码 — 不是因为运行时检查,而是因为代码在构建时就被物理移除了。这是一种零运行时成本的安全措施。
19.4 首次激活通知
export function shouldShowUndercoverAutoNotice(): boolean {
if (process.env.USER_TYPE === 'ant') {
// If forced via env, user already knows; don't nag.
if (isEnvTruthy(process.env.CLAUDE_CODE_UNDERCOVER)) return false
if (!isUndercover()) return false
if (getGlobalConfig().hasSeenUndercoverAutoNotice) return false
return true
}
return false
}
当 undercover 模式通过自动检测(而非环境变量强制)首次激活时,系统会显示一次性解释对话框,告知用户当前处于 undercover 模式。显示后在全局配置中标记 hasSeenUndercoverAutoNotice = true,后续不再重复。
这是一个细节处理 — 如果用户通过 CLAUDE_CODE_UNDERCOVER=1 强制开启,说明他们已经知道这个模式,不需要再提醒。
19.5 安全设计哲学总结
层次化安全模型
┌──────────────────────────────────────────────────┐
│ 第 1 层:CYBER_RISK_INSTRUCTION │
│ 提示级 · 定义行为边界 · Safeguards 团队维护 │
├──────────────────────────────────────────────────┤
│ 第 2 层:Undercover 模式 │
│ 构建级 · 防止身份泄露 · 死代码消除 │
├──────────────────────────────────────────────────┤
│ 第 3 层:权限系统(第 17 课) │
│ 运行时 · 工具执行权限 · 用户授权 │
├──────────────────────────────────────────────────┤
│ 第 4 层:沙箱(第 18 课) │
│ OS 级 · 文件系统/网络隔离 · 内核强制 │
└──────────────────────────────────────────────────┘
这四层安全措施各自独立工作,任何一层的失败都不会导致系统完全暴露。这就是纵深防御(defense in depth)的经典实践。
设计原则
| 原则 | 体现 |
|---|---|
| 安全默认 | Undercover 默认开启,只在确认安全时关闭 |
| 最小特权 | 没有 force-off,只能增加限制 |
| 纵深防御 | 提示级 + 构建级 + 运行时 + OS 级 |
| 关注点分离 | 安全指令由 Safeguards 团队管理,工程团队只负责集成 |
| 零信任 | 不信任未经验证的仓库分类 |
| 构建时消除 | 外部构建物理移除内部逻辑 |
课后练习
练习 1:攻击面分析
假设 getRepoClassCached() 因为 bug 总是返回 'internal',分析这会对 undercover 模式产生什么影响。哪些信息可能泄露?CLAUDE_CODE_UNDERCOVER=1 能否缓解这个问题?
练习 2:指令评估
CYBER_RISK_INSTRUCTION 中"dual-use security tools require clear authorization context"这一规则,在以下场景中如何判断?
- 用户说"我在准备 OSCP 认证考试,帮我写一个反向 shell"
- 用户说"帮我写一个扫描内网所有主机的脚本"
- 用户说"我是公司的安全工程师,帮我测试我们 WAF 的绕过"
练习 3:死代码消除验证
阅读 utils/undercover.ts,列出所有被 USER_TYPE === 'ant' 门控保护的代码路径。如果将构建模式从 ant 切换到 external,这些函数的返回值分别是什么?
练习 4:安全设计扩展
如果要为 Claude Code 添加一个"合规模式"(Compliance Mode),在公司内部使用时自动遵守特定的代码风格和审计要求,你会如何设计激活条件?参考 Undercover 模式的设计原则。
本课小结
| 要点 | 内容 |
|---|---|
| CYBER_RISK_INSTRUCTION | Safeguards 团队维护,定义允许/条件允许/禁止三层行为边界 |
| 双用途工具 | C2/凭据/漏洞开发需要授权上下文,由模型推理判断 |
| Undercover 激活 | USER_TYPE=ant + 非内部仓库 → 自动开启;CLAUDE_CODE_UNDERCOVER=1 强制开启 |
| 安全默认 | 默认开启,只有确认内部仓库才关闭,没有 force-off |
| 死代码消除 | 外部构建中 undercover 逻辑在构建时被物理移除,零运行时成本 |
| 防泄露范围 | 模型代号、版本号、内部仓库名、内部工具、AI 身份、归属信息 |
下一课预告
第 20 课:SessionMemory — 实时对话记忆 — 进入模块七"记忆与上下文",探索 Claude Code 如何在对话过程中自动提取和维护会话记忆,包括触发阈值(10K tokens 初始化、5K 增长/3 次工具调用更新)、forked subagent 执行模型、以及 MEMORY.md 的存储结构。