一个用 Rust 实现的边界代理,挡在不可信 AI agent 和外网之间。Agent 持有的只是无价值的哨兵占位符,Hush 在出站请求中将其替换为真实 API key,并只允许访问白名单内的 host。Agent 全程接触不到真 key。
Agent(不可信,只持有哨兵)
│ 唯一出口
▼
Hush:host 白名单 + 哨兵→真 key + HTTPS MITM
▼
外网(OpenAI、Anthropic、……)
- Agent 用无意义的哨兵字符串作为 API key 发请求
- Hush 通过 TLS MITM 拦截,校验 host 是否在白名单内
- 将请求 header 和 query 中的哨兵替换为真 key
- 以真 TLS 转发到上游
- 响应原样透传(包括流式/SSE)
cargo build --releasecp secrets.example.json secrets.json
# 编辑 secrets.json,填入真实 key 和哨兵
chmod 600 secrets.json# 首次启动时 CA 会自动生成(如果不存在)
./target/release/hush run --config ./secrets.json --listen 127.0.0.1:8080# 将 CA 证书装入 agent 信任库后:
curl -x http://127.0.0.1:8080 \
https://api.openai.com/v1/models \
-H "Authorization: Bearer nbx-你的哨兵"hush run --config <PATH> [OPTIONS]
选项:
--config <PATH> secrets.json 路径(必填)
--listen <ADDR> 监听地址 [默认: 127.0.0.1:8080]
--ca-cert <PATH> CA 证书 PEM [默认: /etc/hush/ca-public/ca.crt]
--ca-key <PATH> CA 私钥 PEM [默认: /etc/hush/ca-private/ca.key]
--log-level <LEVEL> trace|debug|info|warn|error [默认: info]
CA 文件首次启动时自动生成(如果不存在)。
hush ca generate [OPTIONS]
选项:
--out <DIR> 输出目录 [默认: ./ca]
--cn <NAME> CA CommonName [默认: "Hush Boundary CA"]
--days <N> 有效天数 [默认: 3650]
{
"allow_hosts": ["api.openai.com", "*.anthropic.com"],
"secrets": [
{
"sentinel": "nbx-唯一哨兵串",
"real": "sk-真实API Key",
"allowed_hosts": ["api.openai.com"]
},
{
"sentinel": "nbx-另一个哨兵",
"real": "sk-ant-真实Anthropic Key",
"allowed_hosts": ["*.anthropic.com"]
}
]
}Host 规则(allow_hosts 与 allowed_hosts 共用):
- 精确:
"api.openai.com"(大小写不敏感) - 子域通配:
"*.openai.com"匹配api.openai.com、a.b.openai.com,不匹配openai.com或evil-openai.com - 通配:
"*"— 允许所有 host,需要allow_wildcard_all = true
安全护栏:
- 凭证级
allowed_hosts禁止"*"(防止真 key 被发送到任意 host) - 全局
"*"必须显式设置allow_wildcard_all = true
编辑 secrets.json 保存后自动生效(inotify,50ms 去抖)。或发送 SIGHUP:
kill -HUP $(pgrep hush) # 宿主
docker kill -s HUP hush # 容器双通道感知:inotify 监听父目录(应对编辑器原子写 rename 换 inode),SIGHUP 作为容器下的可靠兜底。
原子替换:用 ArcSwap 整体换表,读侧无锁,进行中的请求持有旧快照不受影响。
坏配置不致命:解析失败保留旧表 + 打 warn,服务继续运行。
推荐做法(主程序驱动):改完 secrets.json 后主动给 Hush 发 SIGHUP,不依赖 inotify 碰运气:
# 容器
docker kill -s HUP hush
# 宿主
kill -HUP $(pgrep hush)写文件原子写:先写临时文件 → rename 覆盖,避免 Hush 读到半成品:
# 写到临时文件
echo '...' > secrets.json.tmp
# 原子覆盖
mv secrets.json.tmp secrets.json
# 触发重载
kill -HUP $(pgrep hush)# 拉取
docker pull ghcr.io/anthropics/hush:v0.1.0
# 运行
docker run --rm \
-v ./secrets.json:/etc/hush/secrets.json:ro \
-v ./ca:/etc/hush/ca:ro \
-p 8080:8080 \
ghcr.io/anthropics/hush:v0.1.0 \
run --config /etc/hush/secrets.json \
--listen 0.0.0.0:8080 \
--ca-cert /etc/hush/ca/ca.crt \
--ca-key /etc/hush/ca/ca.key多架构镜像:linux/amd64、linux/arm64。
在 GitHub Actions 页面手动触发,填入版本号(如 v0.1.0)。
自动构建:
x86_64和aarch64二进制包 → 附到 GitHub Release- 多架构 Docker 镜像 → 推送到 GHCR
# 装入系统信任库
sudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain ./ca/ca.crt
# 验证
security find-certificate -c "Hush Boundary CA" /Library/Keychains/System.keychain
# 删除
sudo security delete-certificate -c "Hush Boundary CA" /Library/Keychains/System.keychain# 单元测试
cargo test
# 手动端到端
cargo run -- run --config ./secrets.json --listen 127.0.0.1:8080 --log-level debug
# 测试白名单拦截(应返回 403)
curl -k -x http://127.0.0.1:8080 https://evil.example.com/
# 测试哨兵替换
curl -k -x http://127.0.0.1:8080 \
https://api.openai.com/v1/models \
-H "Authorization: Bearer nbx-你的哨兵"- 真 key 只存在于进程内存和配置文件中,绝不写日志
- 哨兵日志只记前 6 位 + 长度
- CA 私钥权限
0600 - 程序只读配置文件,绝不写入
- 程序不修改 iptables、DNS、网络配置 — 由部署层负责
MIT