ryantylerross/sentinel
GitHub: ryantylerross/sentinel
Sentinel 是一个零信任飞地的带外边界验证器,通过三态出口探针定期验证网络隔离控制措施是否有效,采用失败即关闭策略确保仅在明确确认阻挡时才判定通过。
Stars: 0 | Forks: 0
# Sentinel
Sentinel 是 **Faraday** 飞地的一个带外边界探针 —— 这是一个由
Terraform 定义、采用零信任的环境(一个被严格锁定的 VPC),旨在
容纳不受信任的 AI 工作负载。该工作负载是用户提供的输入,不属于
设计的一部分;无论它是什么,飞地都假设其带有恶意。
**Sentinel 是一个验证器,而不是执行器。** 它从飞地*外部*定期运行,
并检查安全控制措施是否有效。它永远不会出现在实时数据路径中,也
无法实时检查真实流量。一次通过的运行是控制措施有效运行的*证据* ——
其本身并不提供保护。
因为它是一个验证器,其核心规则是:**当仅仅是未能执行检查时,绝不报告该控制措施有效。**
出口探针是三态的 —— 只有当目标被明确阻挡时才是*通过 (pass)*;可到达的
目标是一个**泄漏 (leak)**,而未能确定(DNS 失败、无路由、探针主机故障)的
目标状态是**不确定 (inconclusive)**,这两种情况都会导致运行失败(失败即关闭)。
当前构建完全针对 mock 运行,零运行时依赖(
仅使用标准库)。Live 的实现代码已编写但并未指向真实
目标(目前尚未部署)。请参阅
[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) 了解设计原理。
## 目录
- [Sentinel 为什么存在](#why-sentinel-exists)
- [两道边界](#the-two-boundaries)
- [安装](#install)
- [用法](#usage)
- [退出码](#exit-codes)
- [配置](#configuration)
- [测试](#testing)
- [投入生产](#going-live)
- [许可证](#license)
## Sentinel 为什么存在
飞地将其工作负载视为带有恶意,无论它具体是什么,因此
安全问题在于**控制可能离开飞地的数据**。存在两个
独立的风险:
1. **网络渗出** —— 内部的某个进程试图向
外部发送数据。通过*出口限制*解决:如果没有通向外的路由,无论意图如何,都无法离开。
2. **合法输出中的违规内容** —— 即使通过被允许的通道,
模型的响应也可能包含机密或违禁内容。通过
*输出防护栏*(output guardrail)解决,该防护栏目前尚不存在且不在本次讨论范围内。
Sentinel 不会实时防御任何一种风险。它只探测防御
措施是否存在并发挥作用。
## 两道边界
这两项控制措施截然不同,并且会独立失效。在任何地方将它们混为一谈都是
设计错误。
| | 出口限制 | 输出防护栏 |
| -------------- | -------------------------------------- | -------------------------------------- |
| OSI 层 | L3/L4(地址,端口) | L7(内容本身) |
| 控制措施 | 数据*去向* | 允许离开的*内容* |
| 执行者 | GCP VPC firewall egress 规则 / Private Google Access / default-deny DNS (Terraform) | 虚拟机外部的路径内执行器 |
| 目前是否存在? | 是 —— Sentinel 对其进行探测 | 否 —— 不在本次构建范围内 |
| Sentinel 的职责 | 探测出口是否已被封锁 | 待其存在后进行验证 |
封锁网络对于允许通道上的不良内容毫无作用;
清洗内容对于意外的网络路由也无济于事。
Sentinel 的出口检查只有在从特定视点运行时才有意义,
该视点必须受限于真实逃脱进程将面临的相同网络限制 —— 在
GCP 上,即飞地 VPC 内部的一个 GCE/GKE 工作负载,没有外部 IP,并
受相同的 VPC firewall egress 规则和 Cloud NAT 状态的约束。如果从具有
更宽松外出路由的地方(特权主机、不同的子网)运行,“已阻挡”的结果将
毫无意义。
## 安装
```
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
```
运行时没有任何第三方依赖。`pytest` 仅通过
可选的 `[dev]` 扩展安装。
## 用法
```
sentinel --config config.mock.json # text report
sentinel --config config.mock.json --json # JSON report
python -m sentinel --config config.mock.json # equivalent module form
sentinel --version
```
`--config` 默认为 `config.mock.json`,并基于工作目录进行解析。
报告示例:
```
Sentinel probe report [mode=mock] => PASS
(verifier, not enforcer — evidence the controls hold, not protection)
(MOCK — results are SIMULATED in-process; this exercises the logic, not a live GCP network or a real model surface)
[PASS] egress_containment — L3/L4 network egress
all 2 target(s) blocked; egress containment holds
- malicious.example.com:443: blocked (mock: simulated block)
- exfil.example.net:80: blocked (mock: simulated block)
[PASS] output_guardrail — L7 output guardrail (mock-fixture verification)
2 case(s) behaved as expected (mock-fixture self-check; not a live enforcer)
- probe:benign-greeting: expected_blocked=False observed=False [ok] findings=[]
- probe:adversarial-exfil: expected_blocked=True observed=True [ok] findings=['aws_access_key_id']
```
在 `live` 模式下,如果 Sentinel 既无法到达也无法排除某个目标,则会被报告为
`INCONCLUSIVE` 并导致运行失败 —— 该探针绝不会将“无法判断”升级为
“已限制”。
## 退出码
CLI 返回对 CI 友好的退出码。当出现任何
非零结果时使 pipeline 失败以进行拦截。
| 代码 | 含义 |
| ---- | -------------------------------------------------- |
| `0` | 所有检查均通过 |
| `1` | 探针检查失败 —— 边界发生泄漏或状态不确定 |
| `2` | 配置或使用错误 |
## 配置
`config.py` 即为授权边界:它决定了允许 Sentinel 探测的内容,
并且它是失败即关闭的 —— 任何格式错误、范围过大或无法识别的
内容都会被拒绝,而不是进行猜测。
```
{
"version": 1,
"mode": "mock",
"egress_targets": ["host:port", "..."],
"model_endpoint": "https://..."
}
```
- `mode` —— `mock` 或 `live`。选择要使用的实现。
- `egress_targets` —— 显式的 `host:port` 允许列表。通配符和
全量 CIDR 形式(`*`,`0.0.0.0/0`)将被拒绝;“探测所有内容”绝不会被
授权。
- `model_endpoint` —— 在 live 模式下是必需的,在 mock
模式下会被忽略(但仍会进行验证)。它是一个探针目标,因此它会经过
相同的边界:它必须是一个不包含凭据的 `http(s)` URL,并且
它**不得**指向
GCP 元数据服务器(`169.254.169.254` / `metadata.google.internal`)、
环回地址或链路本地地址 —— 因为元数据服务器会分发
工作负载的 service-account token,是经典的 SSRF 渗出跳板。
合法的内部 GCP 主机(RFC1918、Private Service Connect、内部 load
balancer、`*.run.app`)是被允许的。
- 以 `_` 开头的键(如 `_comment`)是允许的并被忽略。任何其他
未知键都会引发错误。
`config.mock.json` 针对 mock 实现进行离线运行。
`config.live.example.json` 是真实目标的模板 —— 将其复制到
`config.local.json`(已被 gitignored)并填入相应的值。
## 测试
```
pytest -q
```
测试套件**不会发起真实的网络调用**:live 实现的判定
映射和输入处理是通过注入的标准库
模拟对象(通过 monkeypatch 替换的 `socket` / `urllib`)离线执行的,绝不会指向已部署的目标。它
也独立于工作目录。
## 投入生产
当部署了真实目标后,从 mock 切换到 live 是配置上的
更改,而不是重写:
1. 将 `mode` 设置为 `live`,并在被 gitignored 的
配置文件中提供真实的 `model_endpoint`(受上述端点防护限制 —— 不得包含元数据/环回主机)。
2. 从邻近飞地的视点运行 Sentinel —— 即飞地 VPC 内部的一个 GCE/GKE 工作负载,
无外部 IP —— 使其出口尝试面临真实的
VPC firewall / Cloud NAT 限制。发生泄漏会导致运行失败;探针
既无法到达也无法排除的目标会被报告为 `INCONCLUSIVE` 并同样导致运行失败
(失败即关闭)。
3. 一旦路径内的输出防护栏存在,它就会作为验证对象替换内置的
`OutputGuardrail` fixture,并且防护栏探针
用例将变为真实的对抗性 prompt。
Live 的代码路径已经存在,因此这只是线路连接问题,而不是新的
架构。请参阅 [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)。
## 许可证
在 MIT 许可证下发布。请参阅 [LICENSE](LICENSE)。
标签:ECS, Go语言, Terraform, 出站流量控制, 程序破解, 网络安全, 边界验证, 逆向工具, 隐私保护, 零信任架构