nx_tools_api_exec_gate.nx source
↩ module page · 283 lines · 18365 B
1// nx_tools_api_exec_gate.nx -- GATE for R2: the /mcp tools/call STUB is now REAL execution. Drives the live
2// pure router ta_handle_pfx IN-PROCESS (no socket) and proves, end-to-end, that a capability-authorized
3// tools/call for a GREEN-allowlisted tool actually FORKS the organ and returns its real stdout -- with the
4// two never-brick negative controls that make this safe: (T2) no capability -> JSON-RPC -32001 (nothing
5// runs), (T3) a valid capability for a tool that is NOT on the GREEN execution allowlist -> tool-level error,
6// NOT executed. Composes R0 (nx_tool_run) + R1 (nx_tool_exec_allow) + the cap-token ocap layer.
7//
8// PREREQ: build nx_tool_ping FIRST (produces _offc/nx_tool_ping.elf, the allowlisted target). This gate
9// writes the real allowlist file (tool_allowlist.conf) in the server CWD, since ta_mcp_call resolves through
10// the production TEA_CONF path -- proving the ACTUAL wired path, not a test shim.
11// license_tier: ORIGINAL expect_exit: 0
12import "nx_tools_api.nx" // ta_handle_pfx + tea_* + capt_* (transitive) + sys_*
13import "nx_gate.nx" // gw / gn
14
15const EG_CONF: *u8 = "tool_allowlist.conf" as *u8
16const EG_PINGELF: *u8 = "_offc/nx_tool_ping.elf" as *u8
17
18func eg_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 }
19func eg_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 }
20func eg_has(out: *u8, n: i64, needle: *u8) -> i64 { if ta_indexof(out, n, needle) >= 0 { return 1 } return 0 }
21func eg_write_file(path: *u8, content: *u8) -> i64 {
22 let fd: i64 = __syscall(257, 0 - 100, path, 0x241, 0x1a4, 0, 0) // O_WRONLY|O_CREAT|O_TRUNC, 0644
23 if fd < 0 { return 0 - 1 }
24 var n: i64 = 0; while content[n] != (0 as u8) { n = n + 1 }
25 sys_write(fd, content, n); sys_close(fd)
26 return 0
27}
28
29// write exactly n bytes (eg_write_file is NUL-terminated; the allowlist snapshot is arbitrary bytes).
30func eg_write_file_n(path: *u8, content: *u8, n: i64) -> i64 {
31 let fd: i64 = __syscall(257, 0 - 100, path, 0x241, 0x1a4, 0, 0) // O_WRONLY|O_CREAT|O_TRUNC, 0644
32 if fd < 0 { return 0 - 1 }
33 sys_write(fd, content, n); sys_close(fd)
34 return 0
35}
36
37// write "ping\t_offc/nx_tool_ping.elf\tGREEN\n" (+ a blocked row + a comment) to the real allowlist file.
38func eg_write_conf() -> i64 {
39 let fd: i64 = __syscall(257, 0 - 100, EG_CONF, 0x241, 0x1a4, 0, 0) // O_WRONLY|O_CREAT|O_TRUNC, 0644
40 if fd < 0 { return 0 - 1 }
41 let body: *u8 = "# nx execution allowlist (written by nx_tools_api_exec_gate)\nping\t_offc/nx_tool_ping.elf\tGREEN\nargecho\t_offc/nx_tool_argecho.elf\tGREEN\nnx_http_probe\t_offc/nx_http_probe.elf\tRED\n" as *u8
42 var n: i64 = 0; while body[n]!=(0 as u8){n=n+1}
43 sys_write(fd, body, n); sys_close(fd)
44 return 0
45}
46
47// build a POST /mcp tools/call request with params.name = `tool` and params._cap = `tok` (tlen bytes). Returns len.
48func eg_build_call(req: *u8, tool: *u8, tok: *u8, tlen: i64) -> i64 {
49 var o: i64 = eg_cat(req, 0, "POST /mcp HTTP/1.1\r\nHost: x\r\nContent-Type: application/json\r\n\r\n" as *u8)
50 o = eg_cat(req, o, "{\"jsonrpc\":\"2.0\",\"id\":5,\"method\":\"tools/call\",\"params\":{\"name\":\"" as *u8)
51 o = eg_cat(req, o, tool)
52 o = eg_cat(req, o, "\",\"_cap\":\"" as *u8)
53 o = eg_catb(req, o, tok, tlen)
54 o = eg_cat(req, o, "\"}}" as *u8)
55 return o
56}
57
58// like eg_build_call but adds params.arguments.argv = `argvj` (a JSON string-array literal), to drive the
59// multi-arg tools/call path (params.arguments.argv -> native argv -> child).
60func eg_build_call_argv(req: *u8, tool: *u8, tok: *u8, tlen: i64, argvj: *u8) -> i64 {
61 var o: i64 = eg_cat(req, 0, "POST /mcp HTTP/1.1\r\nHost: x\r\nContent-Type: application/json\r\n\r\n" as *u8)
62 o = eg_cat(req, o, "{\"jsonrpc\":\"2.0\",\"id\":5,\"method\":\"tools/call\",\"params\":{\"name\":\"" as *u8)
63 o = eg_cat(req, o, tool)
64 o = eg_cat(req, o, "\",\"_cap\":\"" as *u8)
65 o = eg_catb(req, o, tok, tlen)
66 o = eg_cat(req, o, "\",\"arguments\":{\"argv\":" as *u8)
67 o = eg_cat(req, o, argvj)
68 o = eg_cat(req, o, "}}}" as *u8)
69 return o
70}
71
72// like eg_build_call but presents the cap via the OAuth `Authorization: Bearer <cap>` HEADER (no _cap/X-Nishi-Cap) --
73// exercises the stock-MCP-client interop path.
74func eg_build_call_bearer(req: *u8, tool: *u8, tok: *u8, tlen: i64) -> i64 {
75 var o: i64 = eg_cat(req, 0, "POST /mcp HTTP/1.1\r\nHost: x\r\nContent-Type: application/json\r\nAuthorization: Bearer " as *u8)
76 o = eg_catb(req, o, tok, tlen)
77 o = eg_cat(req, o, "\r\n\r\n" as *u8)
78 o = eg_cat(req, o, "{\"jsonrpc\":\"2.0\",\"id\":7,\"method\":\"tools/call\",\"params\":{\"name\":\"" as *u8)
79 o = eg_cat(req, o, tool)
80 o = eg_cat(req, o, "\"}}" as *u8)
81 return o
82}
83
84func main() -> i64 {
85 gw("=== nx_tools_api_exec_gate (R2: /mcp tools/call is REAL execution, cap+allowlist gated) ===\n" as *u8)
86 // NEVER-BRICK GUARD (2026-08-19): this gate O_TRUNCs tool_allowlist.conf in its CWD. Run with
87 // CWD=nishihost, its 4-row fixture REPLACED the production execution allowlist at 13:38 and every
88 // MCP tool refused for 2h20m (the 2026-08-06 nx_cap_grant_e2e_gate incident repeated, by a runner
89 // that does not consume knowledge/gate_effectful.conf). This gate REQUIRES an unprovisioned
90 // scratch CWD BY ITS OWN DESIGN -- T5 asserts insecure_placeholder -- so a real
91 // tools_cap_secret.key in the CWD is mechanical proof of a production misrun: REFUSE.
92 let kchk: i64 = sys_openat_rd("tools_cap_secret.key" as *u8)
93 if kchk >= 0 {
94 sys_close(kchk)
95 gw("TOOLS-API-EXEC REFUSED effectful-in-production: CWD holds a real tools_cap_secret.key and this gate overwrites tool_allowlist.conf. Run it from a scratch CWD (/tmp/<gate>/ with _offc/nx_tool_ping.elf + _offc/nx_tool_argecho.elf copied in). exit 3 = SKIP, not a verdict\n" as *u8)
96 sys_exit(3); return 3
97 }
98 // SNAPSHOT the allowlist about to be overwritten; RESTORED at the verdict exits below
99 // (idempotent-gate law: the fixture must not outlive the gate).
100 let egsz: *i64 = sys_mmap(16) as *i64
101 let egsnap: *u8 = sys_read_file(EG_CONF, egsz)
102 let TP: *u8 = "knowledge/toolreg-test-exec-" as *u8
103 tool_register_pfx(TP, "ping" as *u8, "no-op smoke-test organ" as *u8, "ping" as *u8, "gate-proven" as *u8)
104 tool_register_pfx(TP, "nx_http_probe" as *u8, "sovereign HTTP GET probe" as *u8, "nx_http_probe <path>" as *u8, "gate-proven" as *u8)
105 tool_register_pfx(TP, "argecho" as *u8, "multi-arg witness organ (echoes argv[1..])" as *u8, "argecho <args...>" as *u8, "gate-proven" as *u8)
106 if eg_write_conf() != 0 { gw("FAIL: could not write tool_allowlist.conf\n" as *u8); sys_exit(1); return 1 }
107
108 let secret: *u8 = TA_CAP_SECRET
109 let slen: i64 = ta_slen(TA_CAP_SECRET)
110 let now: i64 = sys_now_realtime_sec()
111 let exp: i64 = now + 3600
112 let out: *u8 = sys_mmap(1048576)
113 let req: *u8 = sys_mmap(4096)
114 let tok: *u8 = sys_mmap(1024)
115
116 var pass: i64 = 0
117 var tot: i64 = 0
118
119 // T1: valid cap for "ping" + ping is GREEN-allowlisted -> EXECUTES, real stdout returned, isError:false
120 tot = tot + 1
121 let tl1: i64 = capt_issue(secret, slen, "ping" as *u8, 4, exp, 1001, tok, 1024)
122 let rn1: i64 = eg_build_call(req, "ping" as *u8, tok, tl1)
123 let on1: i64 = ta_handle_pfx(TP, req, rn1, out)
124 var t1: i64 = 0
125 if eg_has(out, on1, "NX_TOOL_PING_OK" as *u8) == 1 { if eg_has(out, on1, "\"isError\":false" as *u8) == 1 { if eg_has(out, on1, "\"exit_code\":0" as *u8) == 1 { t1 = 1 } } }
126 if t1 == 1 { pass = pass + 1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
127 gw("T1 cap-authorized + GREEN-allowlisted 'ping' -> real fork+capture: 'NX_TOOL_PING_OK', exit_code 0\n" as *u8)
128
129 // T2 NEG (no ambient authority): tools/call WITHOUT any capability -> -32001, nothing executed
130 tot = tot + 1
131 var o2: i64 = eg_cat(req, 0, "POST /mcp HTTP/1.1\r\nHost: x\r\n\r\n" as *u8)
132 o2 = eg_cat(req, o2, "{\"jsonrpc\":\"2.0\",\"id\":6,\"method\":\"tools/call\",\"params\":{\"name\":\"ping\"}}" as *u8)
133 let on2: i64 = ta_handle_pfx(TP, req, o2, out)
134 var t2: i64 = 0
135 if eg_has(out, on2, "-32001" as *u8) == 1 { if eg_has(out, on2, "NX_TOOL_PING_OK" as *u8) == 0 { t2 = 1 } }
136 if t2 == 1 { pass = pass + 1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
137 gw("T2 no capability -> -32001, organ NOT executed (no ambient authority)\n" as *u8)
138
139 // T3 NEG (fail-closed allowlist): valid cap for 'nx_http_probe' but its row is RED -> tool error, not run
140 tot = tot + 1
141 let tl3: i64 = capt_issue(secret, slen, "nx_http_probe" as *u8, 13, exp, 1003, tok, 1024)
142 let rn3: i64 = eg_build_call(req, "nx_http_probe" as *u8, tok, tl3)
143 let on3: i64 = ta_handle_pfx(TP, req, rn3, out)
144 var t3: i64 = 0
145 if eg_has(out, on3, "not on the GREEN execution allowlist" as *u8) == 1 { if eg_has(out, on3, "\"isError\":true" as *u8) == 1 { t3 = 1 } }
146 if t3 == 1 { pass = pass + 1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
147 gw("T3 cap-valid but non-GREEN allowlist row -> tool error, NOT executed (defense in depth)\n" as *u8)
148
149 // T4: MULTI-ARG tools/call -- params.arguments.argv is parsed into a native argv and delivered to the child
150 // as real argv[1..]. Proves the FULL arg-passing path end-to-end: parse -> cap-verify -> tea_run_argv ->
151 // resolve -> fork+execve(argecho, [path,alpha,bravo,charlie]) -> child echoes them -> captured. This is the
152 // exceed that makes multi-arg organs (e.g. nx_mgmt_client <url> call GET <path> <tok>) callable over MCP.
153 tot = tot + 1
154 let tl4: i64 = capt_issue(secret, slen, "argecho" as *u8, 7, exp, 1004, tok, 1024)
155 let rn4: i64 = eg_build_call_argv(req, "argecho" as *u8, tok, tl4, "[\"alpha\",\"bravo\",\"charlie\"]" as *u8)
156 let on4: i64 = ta_handle_pfx(TP, req, rn4, out)
157 var t4: i64 = 0
158 if eg_has(out, on4, "NX_TOOL_ARGECHO_OK" as *u8) == 1 { if eg_has(out, on4, "alpha" as *u8) == 1 { if eg_has(out, on4, "bravo" as *u8) == 1 { if eg_has(out, on4, "charlie" as *u8) == 1 { if eg_has(out, on4, "\"isError\":false" as *u8) == 1 { t4 = 1 } } } } }
159 if t4 == 1 { pass = pass + 1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
160 gw("T4 multi-arg tools/call: params.arguments.argv -> child argv (alpha/bravo/charlie echoed) -- arg-passing exceed\n" as *u8)
161
162 // T5: GET /api/cap/status is PUBLIC (no cap) and honestly reports the INSECURE placeholder when no keyfile is
163 // provisioned (the gate CWD has none) -- the bootstrap self-check that prevents a blind mint against a stale key.
164 tot = tot + 1
165 let o5: i64 = eg_cat(req, 0, "GET /api/cap/status HTTP/1.1\r\nHost: x\r\n\r\n" as *u8)
166 let on5: i64 = ta_handle_pfx(TP, req, o5, out)
167 var t5: i64 = 0
168 if eg_has(out, on5, "200 OK" as *u8) == 1 { if eg_has(out, on5, "insecure_placeholder" as *u8) == 1 { if eg_has(out, on5, "\"secure\":false" as *u8) == 1 { t5 = 1 } } }
169 if t5 == 1 { pass = pass + 1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
170 gw("T5 GET /api/cap/status (public) honestly reports insecure_placeholder when no keyfile is provisioned\n" as *u8)
171
172 // T6: ta_cap_provisioned detection -- real keyfile -> secure; absent OR a file literally holding the placeholder
173 // -> insecure. Proves the status readout flips correctly once nx_cap_keygen writes a real key on the server.
174 let KP: *u8 = "/tmp/eg_capkey.key" as *u8
175 __syscall(87, KP, 0, 0, 0, 0, 0) // ensure absent
176 tot = tot + 1
177 var t6: i64 = 0
178 if ta_cap_provisioned_from(KP) == 0 {
179 eg_write_file(KP, "a-real-256-bit-secret-not-the-placeholder-000000" as *u8)
180 if ta_cap_provisioned_from(KP) == 1 {
181 eg_write_file(KP, TA_CAP_SECRET)
182 if ta_cap_provisioned_from(KP) == 0 { t6 = 1 }
183 }
184 }
185 __syscall(87, KP, 0, 0, 0, 0, 0) // cleanup
186 if t6 == 1 { pass = pass + 1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
187 gw("T6 ta_cap_provisioned: real keyfile -> secure; absent/placeholder-in-file -> insecure (status flips on provisioning)\n" as *u8)
188
189 // T7: OAuth 2.1 interop -- present the SAME ocap via `Authorization: Bearer <cap>` (not X-Nishi-Cap/_cap) -> EXECUTES.
190 tot = tot + 1
191 let tl7: i64 = capt_issue(secret, slen, "ping" as *u8, 4, exp, 1007, tok, 1024)
192 let rn7: i64 = eg_build_call_bearer(req, "ping" as *u8, tok, tl7)
193 let on7: i64 = ta_handle_pfx(TP, req, rn7, out)
194 var t7: i64 = 0
195 if eg_has(out, on7, "NX_TOOL_PING_OK" as *u8) == 1 { if eg_has(out, on7, "\"isError\":false" as *u8) == 1 { t7 = 1 } }
196 if t7 == 1 { pass = pass + 1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
197 gw("T7 OAuth Bearer: 'Authorization: Bearer <cap>' authorizes tools/call (stock-MCP-client interop)\n" as *u8)
198
199 // T8: RFC 9728 discovery -- GET /.well-known/oauth-protected-resource advertises bearer-in-header auth + the AS.
200 tot = tot + 1
201 let o8: i64 = eg_cat(req, 0, "GET /.well-known/oauth-protected-resource HTTP/1.1\r\nHost: x\r\n\r\n" as *u8)
202 let on8: i64 = ta_handle_pfx(TP, req, o8, out)
203 var t8: i64 = 0
204 if eg_has(out, on8, "200 OK" as *u8) == 1 { if eg_has(out, on8, "bearer_methods_supported" as *u8) == 1 { if eg_has(out, on8, "authorization_servers" as *u8) == 1 { t8 = 1 } } }
205 if t8 == 1 { pass = pass + 1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
206 gw("T8 RFC9728 GET /.well-known/oauth-protected-resource advertises bearer-in-header auth\n" as *u8)
207
208 // T9 (R4 per-client consent): delegate a NARROWER cap WITH a client id -> attenuated cap + consent_recorded.
209 tot = tot + 1
210 let ptl: i64 = capt_issue(secret, slen, "ping,argecho" as *u8, 12, exp, 1009, tok, 1024)
211 var o9: i64 = eg_cat(req, 0, "POST /api/cap/issue HTTP/1.1\r\nHost: x\r\nContent-Type: application/json\r\n\r\n" as *u8)
212 o9 = eg_cat(req, o9, "{\"_cap\":\"" as *u8)
213 o9 = eg_catb(req, o9, tok, ptl)
214 o9 = eg_cat(req, o9, "\",\"allow\":\"ping\",\"nonce\":42,\"client\":\"gate-test-client\"}" as *u8)
215 let on9: i64 = ta_handle_pfx(TP, req, o9, out)
216 var t9r: i64 = 0
217 if eg_has(out, on9, "\"consent_recorded\":true" as *u8) == 1 { if eg_has(out, on9, "\"cap\":\"" as *u8) == 1 { t9r = 1 } }
218 if t9r == 1 { pass = pass + 1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
219 gw("T9 R4 per-client consent: delegate w/ client id -> attenuated cap + consent_recorded\n" as *u8)
220
221 // T10 (R4): the append-only consent ledger records the client id (auditable).
222 tot = tot + 1
223 let o10: i64 = eg_cat(req, 0, "GET /api/cap/consent-log HTTP/1.1\r\nHost: x\r\n\r\n" as *u8)
224 let on10: i64 = ta_handle_pfx(TP, req, o10, out)
225 var t10: i64 = 0
226 if eg_has(out, on10, "gate-test-client" as *u8) == 1 { t10 = 1 }
227 if t10 == 1 { pass = pass + 1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
228 gw("T10 R4 consent ledger: GET /api/cap/consent-log records the client id (auditable)\n" as *u8)
229
230 // T11 (rung 24): ASYNC JOB LANE -- "_async":"1" returns JOB-STARTED
231 // immediately; the detached worker lands .out then the ATOMIC .done
232 // marker (tmp+rename, written LAST); the echoed argv proves the worker
233 // ran the same cap+allowlist-gated pinned dispatch the sync lane runs.
234 tot = tot + 1
235 let tl11: i64 = capt_issue(secret, slen, "argecho" as *u8, 7, exp, 1011, tok, 1024)
236 var o11: i64 = eg_cat(req, 0, "POST /mcp HTTP/1.1\r\nHost: x\r\nContent-Type: application/json\r\n\r\n" as *u8)
237 o11 = eg_cat(req, o11, "{\"jsonrpc\":\"2.0\",\"id\":9,\"method\":\"tools/call\",\"params\":{\"name\":\"argecho\",\"_cap\":\"" as *u8)
238 o11 = eg_catb(req, o11, tok, tl11)
239 o11 = eg_cat(req, o11, "\",\"arguments\":{\"argv\":[\"async_alpha\",\"async_bravo\"],\"_async\":\"1\"}}}" as *u8)
240 let on11: i64 = ta_handle_pfx(TP, req, o11, out)
241 var t11: i64 = 0
242 if eg_has(out, on11, "JOB-STARTED id=" as *u8) == 1 { if eg_has(out, on11, "\"state\":\"RUNNING\"" as *u8) == 1 {
243 let idp: i64 = ta_indexof(out, on11, "JOB-STARTED id=" as *u8) + 15
244 let dpath: *u8 = sys_mmap(256)
245 let opath: *u8 = sys_mmap(256)
246 var dq: i64 = eg_cat(dpath, 0, "_jobs/job_" as *u8)
247 var oq: i64 = eg_cat(opath, 0, "_jobs/job_" as *u8)
248 var di: i64 = idp
249 var digo: i64 = 1
250 while digo == 1 {
251 let dc: i64 = out[di] as i64
252 if dc >= 48 { if dc <= 57 { dpath[dq] = out[di]; opath[oq] = out[di]; dq = dq + 1; oq = oq + 1; di = di + 1 } else { digo = 0 } } else { digo = 0 }
253 }
254 dq = eg_cat(dpath, dq, ".done" as *u8)
255 oq = eg_cat(opath, oq, ".out" as *u8)
256 dpath[dq] = 0 as u8
257 opath[oq] = 0 as u8
258 var polls: i64 = 0
259 var seen: i64 = 0
260 while polls < 100 {
261 let dfd: i64 = sys_openat_rd(dpath)
262 if dfd >= 0 { sys_close(dfd); seen = 1; polls = 100 } else { sys_sleep_ms(50); polls = polls + 1 }
263 }
264 if seen == 1 {
265 let dl: *i64 = sys_mmap(16) as *i64
266 let db2: *u8 = sys_read_file(dpath, dl)
267 let ol: *i64 = sys_mmap(16) as *i64
268 let ob2: *u8 = sys_read_file(opath, ol)
269 if eg_has(db2, dl[0], "exit=0" as *u8) == 1 { if eg_has(ob2, ol[0], "async_alpha" as *u8) == 1 { if eg_has(ob2, ol[0], "async_bravo" as *u8) == 1 { t11 = 1 } } }
270 }
271 __syscall(87, dpath, 0, 0, 0, 0, 0)
272 __syscall(87, opath, 0, 0, 0, 0, 0)
273 } }
274 if t11 == 1 { pass = pass + 1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
275 gw("T11 async job: JOB-STARTED immediate, detached worker lands .out + atomic .done, argv echoed\n" as *u8)
276
277 gw("\n=== nx_tools_api_exec_gate " as *u8); gn(pass); gw("/" as *u8); gn(tot); gw(" ===\n" as *u8)
278 // RESTORE the pre-run allowlist (or remove the fixture if none existed) BEFORE the verdict:
279 // this exact fixture WAS the production allowlist for 2h20m on 2026-08-19.
280 if egsz[0] > 0 { eg_write_file_n(EG_CONF, egsnap, egsz[0]) } else { __syscall(87, EG_CONF, 0, 0, 0, 0, 0) }
281 if pass == tot { gw("TOOLS-API-EXEC GREEN -- /mcp tools/call now RUNS vetted organs and returns real output; the stub is gone, ocap + allowlist both enforced\n" as *u8); sys_exit(0); return 0 }
282 gw("TOOLS-API-EXEC RED\n" as *u8); sys_exit(1); return 1
283}