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}