Files
wuguanganandClaude Opus 5 8510e1de28 feat: 读密钥文件的 Read/Grep/Glob 拦一下
`RiskClassifier` 里 readOnlyTools 是无条件早退的(注释写明理由:Read 一个讲
`rm -rf` 的 README 不该触发审批)。但「读密钥文件」恰恰是要拦的那件事——
共用密钥交给 AI 的契约是 `set -a; source`,值只进子进程环境;改用 Read
之后文件内容就进了 ~/.claude/projects/*.jsonl(0644、永久、每轮重发给 API)。

判断**只放在 readOnlyTools 那个分支里,不放在 classify 开头**。放开头的话,
约定用法里那条 source 命令每次都会弹审批,正常流程当场堵死。Bash 走下面的
模式匹配,`\.env\b` 已经把它定成 dangerous(记录不拦截),不动。

写清楚它挡不住什么:python -c 读、软链、base64 转一道都绕得过,
而且 hubctl 超时是 fail-open——app 没开着的时候这道闸是关的。
它是一次提醒,不是安全边界,README 和注释都按这个写。

路径常量抄在 HubIPC 里(那个模块刻意不依赖 HubProjects,hubctl 要小而快)。
抄就会漂,漂了的症状是静默的——改个文件名,闸就再也命不中而界面毫无变化。
所以加了 SensitivePathsAgreementTests,在同时看得见两个模块的测试目录里
把两边的真实路径对一遍。

625 个测试全过。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 12:48:30 +08:00

33 lines
1.5 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Foundation
/// Hub 自己存密钥的那几个位置。
///
/// ## 为什么常量抄在这里而不是从 HubProjects 引
///
/// `HubIPC` 刻意不依赖 `HubProjects`(见 `Package.swift` 的注释):
/// `hubctl` 每次工具调用都要跑一遍,必须小而快,多链一个库就多一份启动开销。
///
/// 抄常量的代价是会漂。所以 `HubUITests/SensitivePathsAgreementTests`
/// 把两边的真实路径拿来对了一遍 —— 那个测试目录同时看得见两个模块。
/// 改了 `EnvMaterializer.defaultRoot` 或者哪个 `.dat` 的名字,那条测试会红。
public enum SensitivePaths {
/// 相对家目录的前缀。
static let prefixes = [
// 共用密钥物化出来的 .env(对应 EnvMaterializer.defaultRoot)
".vibe-foreman/env/",
// 两个库的密文本体(对应 SharedSecretStore / CredentialStore 的 defaultURL)
"Library/Application Support/claude-hub/shared-secrets.dat",
"Library/Application Support/claude-hub/credentials.dat",
]
/// 这段文本里有没有指向密钥文件的路径。
///
/// 用后缀匹配而不是拼上真实家目录再比:`~` 有没有展开、有没有走软链、
/// 是不是 `/Users/xxx` 还是 `/private/...`,取决于调用方怎么写的,
/// 一个都不能假设。而这几个前缀本身已经足够独特,误伤概率可以忽略。
public static func matches(_ text: String) -> Bool {
prefixes.contains { text.contains($0) }
}
}