capsec_core/root.rs
1//! The capability root — the single point where ambient authority enters the system.
2//!
3//! [`CapRoot`] is the factory for all capability tokens. It exists so that capability
4//! creation is explicit and traceable — grep for `capsec::root()` to find every point
5//! where authority enters your application.
6//!
7//! # Singleton
8//!
9//! Only one `CapRoot` can exist per process. [`root()`] panics if called twice;
10//! [`try_root()`] returns `None` on the second call. This ensures a single point
11//! of authority even in large applications.
12//!
13//! # Testing
14//!
15//! `test_root()` bypasses the singleton check and is available in debug/test builds
16//! (`#[cfg(debug_assertions)]`). It cannot be enabled in release builds.
17
18use crate::cap::Cap;
19use crate::permission::*;
20use std::sync::atomic::{AtomicBool, Ordering};
21
22static ROOT_CREATED: AtomicBool = AtomicBool::new(false);
23
24/// The root of all capabilities. Only one can exist per process.
25///
26/// `CapRoot` has full ambient authority — it can [`grant`](CapRoot::grant) any
27/// permission. It exists to make authority explicit: every capability in your
28/// program traces back to a `CapRoot::grant` call.
29///
30/// # Example
31///
32/// ```rust,ignore
33/// # use capsec_core::root::test_root;
34/// # use capsec_core::permission::FsRead;
35/// let root = test_root();
36/// let fs_cap = root.grant::<FsRead>();
37/// ```
38#[must_use = "CapRoot is a singleton — discarding it means no capabilities can be granted"]
39pub struct CapRoot {
40 _private: (),
41}
42
43/// Creates the singleton capability root. Panics if called more than once.
44///
45/// Use [`try_root`] for a non-panicking alternative. In tests, use `test_root`.
46pub fn root() -> CapRoot {
47 if ROOT_CREATED.swap(true, Ordering::SeqCst) {
48 panic!("capsec::root() called more than once — use try_root() or test_root() instead");
49 }
50 CapRoot { _private: () }
51}
52
53/// Creates the singleton capability root, returning `None` if already created.
54///
55/// Non-panicking alternative to [`root()`].
56pub fn try_root() -> Option<CapRoot> {
57 if ROOT_CREATED.swap(true, Ordering::SeqCst) {
58 None
59 } else {
60 Some(CapRoot { _private: () })
61 }
62}
63
64/// Creates a capability root for testing. Bypasses the singleton check.
65///
66/// Available in debug/test builds only (`#[cfg(debug_assertions)]`).
67/// Cannot be enabled in release builds — there is no feature flag.
68/// Can be called multiple times without panicking — essential for parallel tests.
69#[cfg(debug_assertions)]
70pub fn test_root() -> CapRoot {
71 CapRoot { _private: () }
72}
73
74impl CapRoot {
75 /// Grants a capability token for permission `P`.
76 ///
77 /// The returned `Cap<P>` is a zero-sized proof that the holder has permission `P`.
78 /// This is the only way to obtain a capability token.
79 ///
80 /// # Example
81 ///
82 /// ```rust,ignore
83 /// # use capsec_core::root::test_root;
84 /// # use capsec_core::permission::{FsRead, NetConnect};
85 /// let root = test_root();
86 ///
87 /// // Individual capabilities:
88 /// let fs_cap = root.grant::<FsRead>();
89 /// let net_cap = root.grant::<NetConnect>();
90 ///
91 /// // Or bundle multiple permissions in one token:
92 /// let combo = root.grant::<(FsRead, NetConnect)>();
93 /// ```
94 pub fn grant<P: Permission>(&self) -> Cap<P> {
95 Cap::new()
96 }
97
98 /// Grants a `Cap<FsRead>` for filesystem read access.
99 pub fn fs_read(&self) -> Cap<FsRead> {
100 self.grant()
101 }
102
103 /// Grants a `Cap<FsWrite>` for filesystem write access.
104 pub fn fs_write(&self) -> Cap<FsWrite> {
105 self.grant()
106 }
107
108 /// Grants a `Cap<FsAll>` for full filesystem access.
109 pub fn fs_all(&self) -> Cap<FsAll> {
110 self.grant()
111 }
112
113 /// Grants a `Cap<NetConnect>` for outbound network connections.
114 pub fn net_connect(&self) -> Cap<NetConnect> {
115 self.grant()
116 }
117
118 /// Grants a `Cap<NetBind>` for binding network listeners.
119 pub fn net_bind(&self) -> Cap<NetBind> {
120 self.grant()
121 }
122
123 /// Grants a `Cap<NetAll>` for full network access.
124 pub fn net_all(&self) -> Cap<NetAll> {
125 self.grant()
126 }
127
128 /// Grants a `Cap<EnvRead>` for reading environment variables.
129 pub fn env_read(&self) -> Cap<EnvRead> {
130 self.grant()
131 }
132
133 /// Grants a `Cap<EnvWrite>` for writing environment variables.
134 pub fn env_write(&self) -> Cap<EnvWrite> {
135 self.grant()
136 }
137
138 /// Grants a `Cap<Spawn>` for subprocess execution.
139 pub fn spawn(&self) -> Cap<Spawn> {
140 self.grant()
141 }
142
143 /// Grants a `Cap<Ambient>` with full ambient authority.
144 pub fn ambient(&self) -> Cap<Ambient> {
145 self.grant()
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152 use crate::has::Has;
153
154 #[test]
155 fn test_root_works() {
156 let root = test_root();
157 let _cap = root.grant::<FsRead>();
158 }
159
160 #[test]
161 fn test_root_can_be_called_multiple_times() {
162 let _r1 = test_root();
163 let _r2 = test_root();
164 let _r3 = test_root();
165 }
166
167 #[test]
168 fn grant_produces_zst() {
169 let root = test_root();
170 let cap = root.grant::<FsRead>();
171 assert_eq!(std::mem::size_of_val(&cap), 0);
172 }
173
174 #[test]
175 fn convenience_methods_return_correct_types() {
176 let root = test_root();
177 fn check_fs_read(_: &impl Has<FsRead>) {}
178 fn check_fs_write(_: &impl Has<FsWrite>) {}
179 fn check_fs_all(_: &impl Has<FsAll>) {}
180 fn check_net_connect(_: &impl Has<NetConnect>) {}
181 fn check_net_bind(_: &impl Has<NetBind>) {}
182 fn check_net_all(_: &impl Has<NetAll>) {}
183 fn check_env_read(_: &impl Has<EnvRead>) {}
184 fn check_env_write(_: &impl Has<EnvWrite>) {}
185 fn check_spawn(_: &impl Has<Spawn>) {}
186 fn check_ambient(_: &impl Has<Ambient>) {}
187
188 check_fs_read(&root.fs_read());
189 check_fs_write(&root.fs_write());
190 check_fs_all(&root.fs_all());
191 check_net_connect(&root.net_connect());
192 check_net_bind(&root.net_bind());
193 check_net_all(&root.net_all());
194 check_env_read(&root.env_read());
195 check_env_write(&root.env_write());
196 check_spawn(&root.spawn());
197 check_ambient(&root.ambient());
198 }
199
200 #[test]
201 fn convenience_equivalent_to_grant() {
202 let root = test_root();
203 // Both produce ZSTs of the same type
204 let _a: Cap<FsRead> = root.fs_read();
205 let _b: Cap<FsRead> = root.grant();
206 assert_eq!(std::mem::size_of_val(&_a), std::mem::size_of_val(&_b));
207 }
208}