code wiki / (root) / nx_cap_grant_e2e_gate.nx

nx_cap_grant_e2e_gate.nx source

↩ module page · 201 lines · 13316 B

1// nx_cap_grant_e2e_gate.nx -- THE CAPSTONE: proves the ENTIRE production MCP-grant path end-to-end, offline, with a 2// REAL CSPRNG keyfile on disk (NOT the placeholder). Composes every piece that ships to the NAS in the exact wiring 3// a granted mcp__nishi__* call hits in production: 4// nx_cap_keygen (ck_provision writes tools_cap_secret.key) -> the SERVER's own loader (ta_load_cap_secret reads it) 5// -> mint a token against that same on-disk key (capt_issue) -> present it via the X-Nishi-Cap HEADER on a real 6// POST /mcp tools/call -> ta_handle_pfx verifies vs the loaded keyfile + runs the GREEN-allowlisted organ with the 7// parsed argv -> real fork+capture. The ONLY thing not exercised here is the network/edge relay + the deploy. 8// license_tier: ORIGINAL expect_exit: 0 9import "nx_tools_api.nx" // ta_handle_pfx + ta_load_cap_secret + ta_cap_provisioned + tool_register_pfx + capt_issue/capt_slen (transitive) + sys_* 10import "nx_cap_keygen.nx" // ck_provision (real CSPRNG keyfile provisioning) + CK_OK 11import "nx_gate.nx" // gw / gn 12 13// ★★★★★★A GATE THAT WRITES TO THE PATH IT TESTS DESTROYS THE THING IT MEASURES. 14// 15// THIS GATE CAUSED A SEV-9 (2026-08-06). Every path below is CWD-RELATIVE **by design** -- they must 16// match what the code under test resolves (nx_tools_api TA_CAP_KEYFILE, nx_tool_exec_allow TEA_CONF). 17// That is correct for fidelity and catastrophic for safety: run from `nishihost`, main() opens by 18// UNLINKING tools_cap_secret.key (syscall 87) and then overwrites tool_allowlist.conf with a two-row 19// fixture. Measured consequence, in this order: 20// key unlinked -> nx_tools_api fell back to the FORGEABLE BAKED PLACEHOLDER (the fail-open SEV-9) 21// key regenerated -> EVERY outstanding cap in the estate died at once (admin, .mcp.json, all scoped) 22// allowlist written -> 750 rows -> 2; the restore came from a 3-day-old .prev, losing ~96 registrations 23// cap_revoked.list unlinked -> the revocation SSOT silently vanished 24// The gate reported GREEN 5/5 the whole time. It proved the grant chain works while deleting the 25// secret that makes a grant mean anything. 26// 27// ★★THE FIX IS NOT DISCIPLINE, IT IS CONSTRUCTION. "Only run this from a scratch dir" is a promise; 28// promises are not mechanisms, and the one moment nobody is careful is right after a recovery -- which 29// is exactly when someone runs the cap gates. So main() now CHDIRs into a private sandbox BEFORE any 30// destructive syscall, which makes every relative path below resolve inside it. The gate keeps its 31// fidelity (same relative names, real fork+exec) and becomes structurally unable to reach production. 32// The one path that must survive the move -- the witness ELF -- is resolved to an ABSOLUTE path from 33// the original CWD first, so the exec leg still exercises the real binary. 34const GE_KEYFILE: *u8 = "tools_cap_secret.key" as *u8 // MUST equal nx_tools_api TA_CAP_KEYFILE (CWD-relative) 35const GE_CONF: *u8 = "tool_allowlist.conf" as *u8 // MUST equal nx_tool_exec_allow TEA_CONF 36const GE_REVOKED: *u8 = "cap_revoked.list" as *u8 // cleared for determinism 37const GE_SANDBOX: *u8 = "/tmp/nx_capgrant_e2e" as *u8 // every relative path above resolves in HERE 38 39// Resolve the witness ELF against the ORIGINAL cwd, make the sandbox, and move into it. 40// Returns 1 on success. Any failure => the caller MUST refuse to run: continuing would mean running 41// the destructive fixtures in whatever directory we happen to be standing in. 42// ⚠A LITERAL SYSCALL NUMBER IN `__syscall` IS TRANSLATED (rv64->x86), SO IT IS NOT THE CALL YOU WROTE. 43// My first attempt used `__syscall(80, ...)` for chdir and it silently was not chdir -- the gate refused, 44// fail-closed, which is the only reason this was visible at all. nx_syscalls' own sys_chdir documents the 45// fix: force the number through a RUNTIME variable so the translation is skipped. getcwd has no helper, 46// so it is written here in the same shape rather than invented in a new one. 47// ★MATCH THE KNOWN-GOOD CALL SHAPE BEFORE IMPROVING ON IT -- a hand-rolled equivalent that LOOKS identical 48// can be semantically different, and here the difference was silent. 49func ge_exists(p: *u8) -> i64 { 50 let fd: i64 = sys_openat_rd(p) 51 if fd < 0 { return 0 } 52 sys_close(fd) 53 return 1 54} 55 56func ge_getcwd(buf: *u8, cap: i64) -> i64 { 57 let nbox: *i64 = sys_mmap(16) as *i64 58 nbox[0] = 79 // x86_64 getcwd, forced runtime (see sys_chdir) 59 return __syscall(nbox[0], buf as i64, cap, 0, 0, 0, 0) 60} 61 62func ge_sandbox_enter(argecho_abs: *u8) -> i64 { 63 let cwd: *u8 = sys_mmap(4096) 64 if ge_getcwd(cwd, 4000) < 0 { return 0 } 65 var n: i64 = 0 66 while cwd[n] != (0 as u8) { n = n + 1 } 67 if n <= 0 { return 0 } 68 // ⚠THE WITNESS ELF LIVES IN A DIFFERENT PLACE IN EACH TREE: `_offc/` in the build tree, the ROOT in 69 // nishihost. The original fixture hardcoded `_offc/...`, which is why it only ever worked from one of 70 // them. Probe both and use whichever actually exists, rather than assuming a layout. 71 var p: i64 = 0 72 while p < n { argecho_abs[p] = cwd[p]; p = p + 1 } 73 let root_end: i64 = p 74 p = ge_cat(argecho_abs, p, "/_offc/nx_tool_argecho.elf" as *u8) 75 argecho_abs[p] = 0 as u8 76 if ge_exists(argecho_abs) == 0 { 77 p = ge_cat(argecho_abs, root_end, "/nx_tool_argecho.elf" as *u8) 78 argecho_abs[p] = 0 as u8 79 } 80 sys_mkdir(GE_SANDBOX, 0x1ed) // 0755, matching the known-good gate 81 if sys_chdir(GE_SANDBOX) != 0 { return 0 } // <- the actual containment 82 sys_mkdir("knowledge" as *u8, 0x1ed) // tool_register_pfx writes under here 83 return 1 84} 85 86func ge_cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8){ d[o+i]=s[i]; i=i+1 } return o+i } 87func ge_catb(d: *u8, o: i64, s: *u8, n: i64) -> i64 { var i: i64=0; while i<n { d[o+i]=s[i]; i=i+1 } return o+n } 88func ge_has(out: *u8, n: i64, needle: *u8) -> i64 { if ta_indexof(out, n, needle) >= 0 { return 1 } return 0 } 89func ge_eqn(a: *u8, b: *u8, n: i64) -> i64 { var i: i64=0; while i<n { if a[i]!=b[i] { return 0 } i=i+1 } return 1 } 90func ge_write_file(path: *u8, content: *u8) -> i64 { 91 let fd: i64 = __syscall(257, 0 - 100, path, 0x241, 0x1a4, 0, 0) // O_WRONLY|O_CREAT|O_TRUNC, 0644 92 if fd < 0 { return 0 - 1 } 93 var n: i64 = 0; while content[n] != (0 as u8) { n = n + 1 } 94 sys_write(fd, content, n); sys_close(fd) 95 return 0 96} 97// build a POST /mcp tools/call presenting the cap via the X-Nishi-Cap HEADER (the production .mcp.json form) with 98// params.name=tool and params.arguments.argv=argvj (a JSON string-array literal). 99func ge_build_call_hdr(req: *u8, tool: *u8, tok: *u8, tlen: i64, argvj: *u8) -> i64 { 100 var o: i64 = ge_cat(req, 0, "POST /mcp HTTP/1.1\r\nHost: x\r\nContent-Type: application/json\r\nX-Nishi-Cap: " as *u8) 101 o = ge_catb(req, o, tok, tlen) 102 o = ge_cat(req, o, "\r\n\r\n{\"jsonrpc\":\"2.0\",\"id\":9,\"method\":\"tools/call\",\"params\":{\"name\":\"" as *u8) 103 o = ge_cat(req, o, tool) 104 o = ge_cat(req, o, "\",\"arguments\":{\"argv\":" as *u8) 105 o = ge_cat(req, o, argvj) 106 o = ge_cat(req, o, "}}}" as *u8) 107 return o 108} 109func ge_expect(cond: i64, pass: *i64, tot: *i64, label: *u8) -> i64 { 110 tot[0] = tot[0] + 1 111 if cond == 1 { pass[0] = pass[0] + 1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) } 112 gw(label); gw("\n" as *u8) 113 return 0 114} 115 116func main() -> i64 { 117 gw("=== nx_cap_grant_e2e_gate: FULL production MCP grant with a REAL CSPRNG keyfile (keygen->load->mint->call->exec) ===\n" as *u8) 118 119 // CONTAINMENT FIRST -- before ANY destructive syscall. See the header: this gate unlinks the cap 120 // signing key and overwrites the tool allowlist, and it must do so at CWD-relative paths to stay 121 // faithful to what the code under test resolves. The sandbox is what makes that safe. 122 let argecho_abs: *u8 = sys_mmap(4096) 123 if ge_sandbox_enter(argecho_abs) != 1 { 124 gw("REFUSED: could not enter " as *u8); gw(GE_SANDBOX) 125 gw(" -- this gate UNLINKS tools_cap_secret.key and OVERWRITES tool_allowlist.conf at\n" as *u8) 126 gw(" CWD-relative paths. Running it in the live directory caused a SEV-9 on 2026-08-06.\n" as *u8) 127 gw(" Refusing rather than running destructive fixtures wherever we happen to stand.\n" as *u8) 128 sys_exit(3) 129 return 3 130 } 131 gw(" [sandbox] cwd -> " as *u8); gw(GE_SANDBOX); gw(" (production paths are now unreachable)\n" as *u8) 132 133 __syscall(87, GE_KEYFILE, 0, 0, 0, 0, 0) // clean slate (deterministic) -- sandbox-local 134 __syscall(87, GE_REVOKED, 0, 0, 0, 0, 0) 135 136 let TP: *u8 = "knowledge/toolreg-test-e2e-" as *u8 137 tool_register_pfx(TP, "argecho" as *u8, "multi-arg witness" as *u8, "argecho <args...>" as *u8, "gate-proven" as *u8) 138 // The witness ELF is referenced ABSOLUTELY so the exec leg still runs the real binary from inside 139 // the sandbox -- fidelity preserved, blast radius removed. 140 let confbuf: *u8 = sys_mmap(8192) 141 var co: i64 = ge_cat(confbuf, 0, "# grant e2e (SANDBOX FIXTURE -- never the live allowlist)\nargecho\t" as *u8) 142 co = ge_cat(confbuf, co, argecho_abs) 143 co = ge_cat(confbuf, co, "\tGREEN\n" as *u8) 144 confbuf[co] = 0 as u8 145 if ge_write_file(GE_CONF, confbuf) != 0 { gw("FAIL: cannot write allowlist\n" as *u8); sys_exit(1); return 1 } 146 147 let pbox: *i64 = sys_mmap(16) as *i64; pbox[0] = 0 148 let tbox: *i64 = sys_mmap(16) as *i64; tbox[0] = 0 149 let out: *u8 = sys_mmap(1048576) 150 let req: *u8 = sys_mmap(8192) 151 152 // T1: provision a REAL keyfile at the exact path the server reads 153 let kbuf: *u8 = sys_mmap(128) 154 let klp: *i64 = sys_mmap(16) as *i64 155 let rc: i64 = ck_provision(GE_KEYFILE, 1, kbuf, klp) 156 var t1: i64 = 0 157 if rc == CK_OK { if klp[0] == 64 { t1 = 1 } } 158 ge_expect(t1, pbox, tbox, "T1 nx_cap_keygen wrote a real 256-bit keyfile at tools_cap_secret.key" as *u8) 159 160 // T2: the SERVER now reports provisioned (its own /api/cap/status probe flips) 161 ge_expect(ta_cap_provisioned(), pbox, tbox, "T2 server /api/cap/status would report provisioned (real key loaded)" as *u8) 162 163 // T3: the SERVER's loader reads back EXACTLY the bytes keygen wrote (the load seam) 164 let slb: *i64 = sys_mmap(16) as *i64 165 let sload: *u8 = ta_load_cap_secret(slb) 166 var t3: i64 = 0 167 if slb[0] == 64 { if ge_eqn(sload, kbuf, 64) == 1 { t3 = 1 } } 168 ge_expect(t3, pbox, tbox, "T3 server ta_load_cap_secret reads back the exact 64 keyfile bytes (load seam)" as *u8) 169 170 // T4 (CAPSTONE): mint a cap against the ON-DISK key, present via X-Nishi-Cap HEADER, call argecho with argv -> 171 // the server verifies against the loaded keyfile and forks the organ with the args. The whole grant, end to end. 172 let tok: *u8 = sys_mmap(1024) 173 let tn: i64 = capt_issue(kbuf, klp[0], "argecho" as *u8, 7, 9999999999, 90001, tok, 1024) 174 let rn: i64 = ge_build_call_hdr(req, "argecho" as *u8, tok, tn, "[\"one\",\"two\",\"three\"]" as *u8) 175 let on: i64 = ta_handle_pfx(TP, req, rn, out) 176 var t4: i64 = 0 177 if ge_has(out, on, "NX_TOOL_ARGECHO_OK" as *u8) == 1 { if ge_has(out, on, "one" as *u8) == 1 { if ge_has(out, on, "two" as *u8) == 1 { if ge_has(out, on, "three" as *u8) == 1 { if ge_has(out, on, "\"isError\":false" as *u8) == 1 { t4 = 1 } } } } } 178 ge_expect(t4, pbox, tbox, "T4 CAPSTONE: real-key token via X-Nishi-Cap header -> verified vs keyfile -> argecho ran with argv" as *u8) 179 180 // T5 (neg): a token minted against a DIFFERENT key is DENIED even though the server has a real keyfile -- proving 181 // the server truly verifies against the ON-DISK key, not the placeholder and not "anything". 182 let k2s: *u8 = "a-totally-different-signing-key-do-not-match-000000" as *u8 183 let tok2: *u8 = sys_mmap(1024) 184 let tn2: i64 = capt_issue(k2s, capt_slen(k2s), "argecho" as *u8, 7, 9999999999, 90002, tok2, 1024) 185 let rn2: i64 = ge_build_call_hdr(req, "argecho" as *u8, tok2, tn2, "[\"one\"]" as *u8) 186 let on2: i64 = ta_handle_pfx(TP, req, rn2, out) 187 var t5: i64 = 0 188 if ge_has(out, on2, "-32001" as *u8) == 1 { if ge_has(out, on2, "NX_TOOL_ARGECHO_OK" as *u8) == 0 { t5 = 1 } } 189 ge_expect(t5, pbox, tbox, "T5 neg: token minted with a WRONG key -> -32001, organ NOT run (server verifies vs the keyfile)" as *u8) 190 191 __syscall(87, GE_KEYFILE, 0, 0, 0, 0, 0) // never leave a secret behind 192 __syscall(87, GE_REVOKED, 0, 0, 0, 0, 0) 193 194 gw("\n=== nx_cap_grant_e2e_gate " as *u8); gn(pbox[0]); gw("/" as *u8); gn(tbox[0]); gw(" ===\n" as *u8) 195 // D001: emit the CANONICAL `verdict=` anchor alongside the human line. Without it nx_gate_green 196 // cannot judge this gate and no harness.jrnl frame is recorded -- so its flake and erosion are 197 // invisible, which is exactly how a gate that was quietly destroying production still read GREEN. 198 // ★A GATE WHOSE VERDICT ONLY A HUMAN CAN READ IS UNMONITORED BY CONSTRUCTION. 199 if pbox[0] == tbox[0] { gw("GRANT-E2E GREEN verdict=GREEN -- the full production MCP grant works offline with a real CSPRNG keyfile; only deploy + NAS allowlist paths remain\n" as *u8); sys_exit(0); return 0 } 200 gw("GRANT-E2E RED verdict=RED\n" as *u8); sys_exit(1); return 1 201}