nx_brane.nx source
↩ module page · 240 lines · 8486 B
1// nx_brane.nx -- capability-token cell membrane.
2//
3// Per [[feedback-naming-discipline-no-industry-competitor-overlap]]:
4// "nx_brane" replaces the "container" semantic in cardinal naming.
5// A brane is the cell's membrane -- the boundary that controls what
6// crosses (capability tokens for IO, network egress, file system
7// access, peer messages, etc).
8//
9// THE CORE SECURITY PRIMITIVE. Every IO operation a cell attempts
10// goes through brane capability checks. No capability token = no
11// crossing. This is what makes the substrate's per-cell isolation
12// real (not just a convention).
13//
14// Per Captain Moroni doctrine: every IO is a potential attack vector.
15// Default is DENY; capabilities are explicitly granted at cell
16// instantiation. Granted capabilities can expire (auto-revoke).
17//
18// Composes:
19// nx_cell -- each cell has its own brane
20// nx_restriction -- IO-boundary gate composes brane checks with
21// methyl + pamp + crispr verdicts
22// nx_xenocell -- hostile foreign agent's brane has zero
23// legit capabilities by construction
24// nx_provenance_chain -- capability-check decisions are transforms
25//
26// V1 ships:
27// - sealed enum NxCapKind (10 capability classes)
28// - struct NxCapToken (kind + scope + expires_us + originator)
29// - struct NxBrane with capability_tokens ring
30// - grant + revoke + check + count_active verbs
31//
32// Gap list (V1 honest perf verdict):
33// - capabilities are flat; no hierarchical scopes (V2 adds nesting)
34// - no automatic refresh on near-expiry (caller polls)
35// - cap_check is O(n) linear scan (V2 hash-index if N > ~32)
36
37import "nx_syscalls.nx"
38import "nx_tier.nx"
39
40// ===== Sealed enum: NxCapKind =====================================
41
42const NX_CAP_FILE_READ: nx_int = 0
43const NX_CAP_FILE_WRITE: nx_int = 1
44const NX_CAP_NET_EGRESS: nx_int = 2
45const NX_CAP_NET_INGRESS: nx_int = 3
46const NX_CAP_PEER_MESSAGE: nx_int = 4
47const NX_CAP_HARDWARE_IO: nx_int = 5 // GPIO/SPI/etc on MCU
48const NX_CAP_KERNEL_SYSCALL: nx_int = 6 // mmap/etc privileged
49const NX_CAP_SIGN_AS_SELF: nx_int = 7 // use cell's methyl key
50const NX_CAP_DAC_OUTPUT: nx_int = 8 // silicon DAC emit
51const NX_CAP_DRONE_ACTUATE: nx_int = 9 // physical drone control
52 // (defensive-purpose only
53 // per Captain Moroni)
54const NX_CAP_N_KINDS: nx_int = 10
55
56// ===== Sealed enum: NxBraneVerdict ================================
57
58const NX_BR_OK: nx_int = 0
59const NX_BR_ERR_FULL: nx_int = 1
60const NX_BR_ERR_BAD_KIND: nx_int = 2
61const NX_BR_DENIED: nx_int = 3 // no matching capability
62const NX_BR_EXPIRED: nx_int = 4 // matching cap is expired
63const NX_BR_GRANTED: nx_int = 5 // check succeeded
64
65// ===== Struct: NxCapToken =========================================
66//
67// kind: NX_CAP_* enum. scope: caller-defined narrowing (e.g., file
68// path hash for FILE_READ; peer_id for PEER_MESSAGE). 0 = full scope.
69// expires_us: absolute time; 0 = never expires (use sparingly).
70
71struct NxCapToken {
72 kind: nx_int,
73 scope: nx_size,
74 expires_us: nx_size,
75 granted_us: nx_size,
76 grant_count: nx_int,
77}
78
79// ===== Struct: NxBrane ============================================
80
81struct NxBrane {
82 tokens: *NxCapToken,
83 capacity: nx_size,
84 count: nx_size,
85 cell_id: nx_int, // which cell owns this brane
86}
87
88const NX_BR_TOKEN_BYTES: nx_size = 40
89
90// ===== Validators ================================================
91
92func nx_cap_kind_is_valid(k: nx_int) -> nx_int {
93 if k < 0 { return 0 }
94 if k >= NX_CAP_N_KINDS { return 0 }
95 return 1
96}
97
98// ===== nx_brane_new ===============================================
99
100func nx_brane_new(cell_id: nx_int, capacity: nx_size) -> *NxBrane {
101 let b: *NxBrane = (sys_mmap(32)) as *NxBrane
102 let bytes: nx_size = capacity * NX_BR_TOKEN_BYTES
103 b.tokens = (sys_mmap(bytes)) as *NxCapToken
104 b.capacity = capacity
105 b.count = 0
106 b.cell_id = cell_id
107 return b
108}
109
110func _brane_at(b: *NxBrane, idx: nx_size) -> *NxCapToken {
111 return (b.tokens as i64 + (idx as i64) * NX_BR_TOKEN_BYTES) as *NxCapToken
112}
113
114// ===== nx_brane_grant =============================================
115//
116// Grant a capability. Returns OK or FULL/BAD_KIND. Duplicates are
117// allowed (operator may grant same kind+scope twice with different
118// expiry); check returns the first match.
119
120func nx_brane_grant(b: *NxBrane,
121 kind: nx_int,
122 scope: nx_size,
123 expires_us: nx_size,
124 now_us: nx_size) -> nx_int {
125 if nx_cap_kind_is_valid(kind) == 0 { return NX_BR_ERR_BAD_KIND }
126 if b.count >= b.capacity { return NX_BR_ERR_FULL }
127 let t: *NxCapToken = _brane_at(b, b.count)
128 t.kind = kind
129 t.scope = scope
130 t.expires_us = expires_us
131 t.granted_us = now_us
132 t.grant_count = 1
133 b.count = b.count + 1
134 return NX_BR_OK
135}
136
137// ===== nx_brane_check =============================================
138//
139// Predicate check: does this brane hold a non-expired token matching
140// (kind, scope)? Returns NX_BR_GRANTED, NX_BR_DENIED, or NX_BR_EXPIRED
141// (the latter when a matching token exists but is past expires_us).
142//
143// scope=0 in the token means "any scope" (full grant); otherwise
144// scope must match exactly. Substrate convention: prefer tokens with
145// narrow scope over full grants.
146
147func nx_brane_check(b: *NxBrane,
148 kind: nx_int,
149 scope: nx_size,
150 now_us: nx_size) -> nx_int {
151 var found_expired: nx_int = 0
152 var i: nx_size = 0
153 while i < b.count {
154 let t: *NxCapToken = _brane_at(b, i)
155 if t.kind == kind {
156 let scope_matches: nx_int = 0
157 var matches: nx_int = 0
158 if t.scope == 0 { matches = 1 }
159 if t.scope == scope { matches = 1 }
160 if matches == 1 {
161 if t.expires_us == 0 { return NX_BR_GRANTED }
162 if now_us < t.expires_us { return NX_BR_GRANTED }
163 found_expired = 1
164 }
165 }
166 i = i + 1
167 }
168 if found_expired == 1 { return NX_BR_EXPIRED }
169 return NX_BR_DENIED
170}
171
172// ===== nx_brane_revoke ============================================
173//
174// Revoke ALL tokens matching (kind, scope). Returns the number of
175// tokens removed (0 if none found). Compaction: revoked tokens are
176// zero'd and remaining ones slide down to keep count tight.
177
178func nx_brane_revoke(b: *NxBrane,
179 kind: nx_int,
180 scope: nx_size) -> nx_int {
181 var removed: nx_int = 0
182 var write_idx: nx_size = 0
183 var read_idx: nx_size = 0
184 while read_idx < b.count {
185 let t: *NxCapToken = _brane_at(b, read_idx)
186 var keep: nx_int = 1
187 if t.kind == kind {
188 if t.scope == scope { keep = 0 }
189 }
190 if keep == 1 {
191 if write_idx != read_idx {
192 let dst: *NxCapToken = _brane_at(b, write_idx)
193 dst.kind = t.kind
194 dst.scope = t.scope
195 dst.expires_us = t.expires_us
196 dst.granted_us = t.granted_us
197 dst.grant_count = t.grant_count
198 }
199 write_idx = write_idx + 1
200 } else {
201 removed = removed + 1
202 }
203 read_idx = read_idx + 1
204 }
205 b.count = write_idx
206 return removed
207}
208
209// ===== nx_brane_revoke_all ========================================
210//
211// Revoke ALL tokens. Used in cell-termination + lysis CAPABILITY_REVOKE
212// mechanism. Returns the count removed.
213
214func nx_brane_revoke_all(b: *NxBrane) -> nx_int {
215 let removed: nx_int = b.count as i64
216 b.count = 0
217 return removed
218}
219
220// ===== nx_brane_count_active ======================================
221
222func nx_brane_count_active(b: *NxBrane, now_us: nx_size) -> nx_int {
223 var active: nx_int = 0
224 var i: nx_size = 0
225 while i < b.count {
226 let t: *NxCapToken = _brane_at(b, i)
227 if t.expires_us == 0 { active = active + 1 }
228 if t.expires_us != 0 {
229 if now_us < t.expires_us { active = active + 1 }
230 }
231 i = i + 1
232 }
233 return active
234}
235
236// ===== nx_brane_token_count =======================================
237
238func nx_brane_token_count(b: *NxBrane) -> nx_size {
239 return b.count
240}