vigil_types/lease.rs
1//! SecretLease:短期凭据租约。
2//!
3//! 不变量(AGENTS.md §4):
4//!
5//! 1. **本类型不得承载任何真实 secret 值**。所有字段要么是 alias(`secret://...`)、
6//! 要么是 id(`lease_id` / `session_id` / `server_id` 等)、要么是 metadata
7//! (`expires_at` / `injection_method`)。真实值由 `vigil-lease` crate 在运行时
8//! 从 OS Keychain 取出,以 `lease_id → value` 的方式短期缓存。
9//! 2. 因为字段本身就不该含 secret 值,serde `Serialize` 的默认行为(序列化全部字段)
10//! 对**本类型**是安全的 —— 但这只在不变量 §1 成立时成立。测试
11//! `secret_lease_serialization_surface_is_stable_and_bounded` 通过断言
12//! 序列化出的字段数固定,强制未来新增字段时必须人工评审是否违反 §1。
13//! 3. `Debug` 手写实现为最小脱敏集;`Display` 手写实现为纯 alias 形式。未定义自动
14//! 派生,以防 `#[derive(Debug)]` 等宏在未来被意外恢复。
15//!
16//! I00 只声明类型,运行时解析与注入在 I06 实现。
17
18use serde::{Deserialize, Serialize};
19
20/// 一次 secret 使用的短期租约元数据。
21#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
22pub struct SecretLease {
23 /// 唯一 id。
24 pub lease_id: String,
25 /// 指向 secret 的 alias(`secret://github/repo-write` 形式)。
26 pub secret_ref: String,
27 /// 绑定 session —— 其它 session 不能复用此 lease。
28 pub bound_session_id: String,
29 /// 绑定 server —— 其它 server 不能复用。
30 pub bound_server_id: String,
31 /// 绑定工具名 —— 其它工具不能复用。
32 pub bound_tool_name: String,
33 /// 若由审批签发,关联审批 id。
34 pub approval_id: Option<String>,
35 /// 注入方式。
36 pub injection_method: InjectionMethod,
37 /// 到期时间(Unix epoch 秒);到期后 `vigil-lease` 必须主动撤销。
38 pub expires_at: i64,
39}
40
41/// 凭据注入方式。优先级参见主方案 §5.5。
42#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
43#[non_exhaustive]
44#[serde(rename_all = "PascalCase")]
45pub enum InjectionMethod {
46 /// Gateway 内部在 HTTP header 上注入。
47 HttpHeader,
48 /// 子进程环境变量(env_clear 后仅此 lease)。
49 ChildEnv,
50 /// pipe / fd 注入。
51 Pipe,
52 /// 临时文件(最后手段,需在进程结束时抹除)。
53 TempFile,
54}
55
56// 手写 Debug:本类型不存真实 secret 值(值在 vigil-lease 运行时缓存),
57// 但为了不被未来派生宏意外替换,且与 AGENTS.md §4 "secrets never in logs/UI" 在
58// 类型层形成双重保险,这里把能打印的字段收紧到最窄集合。
59// 面向用户可见的 Display 另行给出并进一步脱敏(**只露 alias**,不含 lease_id/时间)。
60impl std::fmt::Debug for SecretLease {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 // 只输出:lease_id(可审计关联)、secret_ref(alias,设计上非敏感)、
63 // injection_method(非敏感)、expires_at(时间戳)。
64 // 明确不打印 bound_* / approval_id —— 它们对日志追踪不是必要项,
65 // 若需要请改调 audit payload 专用的 redacted serializer。
66 f.debug_struct("SecretLease")
67 .field("lease_id", &self.lease_id)
68 .field("secret_ref", &self.secret_ref)
69 .field("injection_method", &self.injection_method)
70 .field("expires_at", &self.expires_at)
71 .finish_non_exhaustive()
72 }
73}
74
75// 手写 Display:作为 UI / 日志字符串插值的安全默认,只露 alias。
76// 这是对 AGENTS.md §4 的类型层守卫 —— 即使未来有人派生 thiserror 的 `{0}` 插值,
77// 也只会拿到 `secret://...` 这样的 alias,而非任何关联上下文。
78impl std::fmt::Display for SecretLease {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 write!(f, "SecretLease({})", self.secret_ref)
81 }
82}