code wiki / (root) / nx_brane.nx

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}