nx_tools_api_exec_gate.nx source
↩ module page · 256 lines · 16447 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 "ping\t_offc/nx_tool_ping.elf\tGREEN\n" (+ a blocked row + a comment) to the real allowlist file.
30func eg_write_conf() -> i64 {
31 let fd: i64 = __syscall(257, 0 - 100, EG_CONF, 0x241, 0x1a4, 0, 0) // O_WRONLY|O_CREAT|O_TRUNC, 0644
32 if fd < 0 { return 0 - 1 }
33 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
34 var n: i64 = 0; while body[n]!=(0 as u8){n=n+1}
35 sys_write(fd, body, n); sys_close(fd)
36 return 0
37}
38
39// build a POST /mcp tools/call request with params.name = `tool` and params._cap = `tok` (tlen bytes). Returns len.
40func eg_build_call(req: *u8, tool: *u8, tok: *u8, tlen: i64) -> i64 {
41 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)
42 o = eg_cat(req, o, "{\"jsonrpc\":\"2.0\",\"id\":5,\"method\":\"tools/call\",\"params\":{\"name\":\"" as *u8)
43 o = eg_cat(req, o, tool)
44 o = eg_cat(req, o, "\",\"_cap\":\"" as *u8)
45 o = eg_catb(req, o, tok, tlen)
46 o = eg_cat(req, o, "\"}}" as *u8)
47 return o
48}
49
50// like eg_build_call but adds params.arguments.argv = `argvj` (a JSON string-array literal), to drive the
51// multi-arg tools/call path (params.arguments.argv -> native argv -> child).
52func eg_build_call_argv(req: *u8, tool: *u8, tok: *u8, tlen: i64, argvj: *u8) -> i64 {
53 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)
54 o = eg_cat(req, o, "{\"jsonrpc\":\"2.0\",\"id\":5,\"method\":\"tools/call\",\"params\":{\"name\":\"" as *u8)
55 o = eg_cat(req, o, tool)
56 o = eg_cat(req, o, "\",\"_cap\":\"" as *u8)
57 o = eg_catb(req, o, tok, tlen)
58 o = eg_cat(req, o, "\",\"arguments\":{\"argv\":" as *u8)
59 o = eg_cat(req, o, argvj)
60 o = eg_cat(req, o, "}}}" as *u8)
61 return o
62}
63
64// like eg_build_call but presents the cap via the OAuth `Authorization: Bearer <cap>` HEADER (no _cap/X-Nishi-Cap) --
65// exercises the stock-MCP-client interop path.
66func eg_build_call_bearer(req: *u8, tool: *u8, tok: *u8, tlen: i64) -> i64 {
67 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)
68 o = eg_catb(req, o, tok, tlen)
69 o = eg_cat(req, o, "\r\n\r\n" as *u8)
70 o = eg_cat(req, o, "{\"jsonrpc\":\"2.0\",\"id\":7,\"method\":\"tools/call\",\"params\":{\"name\":\"" as *u8)
71 o = eg_cat(req, o, tool)
72 o = eg_cat(req, o, "\"}}" as *u8)
73 return o
74}
75
76func main() -> i64 {
77 gw("=== nx_tools_api_exec_gate (R2: /mcp tools/call is REAL execution, cap+allowlist gated) ===\n" as *u8)
78 let TP: *u8 = "knowledge/toolreg-test-exec-" as *u8
79 tool_register_pfx(TP, "ping" as *u8, "no-op smoke-test organ" as *u8, "ping" as *u8, "gate-proven" as *u8)
80 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)
81 tool_register_pfx(TP, "argecho" as *u8, "multi-arg witness organ (echoes argv[1..])" as *u8, "argecho <args...>" as *u8, "gate-proven" as *u8)
82 if eg_write_conf() != 0 { gw("FAIL: could not write tool_allowlist.conf\n" as *u8); sys_exit(1); return 1 }
83
84 let secret: *u8 = TA_CAP_SECRET
85 let slen: i64 = ta_slen(TA_CAP_SECRET)
86 let now: i64 = sys_now_realtime_sec()
87 let exp: i64 = now + 3600
88 let out: *u8 = sys_mmap(1048576)
89 let req: *u8 = sys_mmap(4096)
90 let tok: *u8 = sys_mmap(1024)
91
92 var pass: i64 = 0
93 var tot: i64 = 0
94
95 // T1: valid cap for "ping" + ping is GREEN-allowlisted -> EXECUTES, real stdout returned, isError:false
96 tot = tot + 1
97 let tl1: i64 = capt_issue(secret, slen, "ping" as *u8, 4, exp, 1001, tok, 1024)
98 let rn1: i64 = eg_build_call(req, "ping" as *u8, tok, tl1)
99 let on1: i64 = ta_handle_pfx(TP, req, rn1, out)
100 var t1: i64 = 0
101 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 } } }
102 if t1 == 1 { pass = pass + 1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
103 gw("T1 cap-authorized + GREEN-allowlisted 'ping' -> real fork+capture: 'NX_TOOL_PING_OK', exit_code 0\n" as *u8)
104
105 // T2 NEG (no ambient authority): tools/call WITHOUT any capability -> -32001, nothing executed
106 tot = tot + 1
107 var o2: i64 = eg_cat(req, 0, "POST /mcp HTTP/1.1\r\nHost: x\r\n\r\n" as *u8)
108 o2 = eg_cat(req, o2, "{\"jsonrpc\":\"2.0\",\"id\":6,\"method\":\"tools/call\",\"params\":{\"name\":\"ping\"}}" as *u8)
109 let on2: i64 = ta_handle_pfx(TP, req, o2, out)
110 var t2: i64 = 0
111 if eg_has(out, on2, "-32001" as *u8) == 1 { if eg_has(out, on2, "NX_TOOL_PING_OK" as *u8) == 0 { t2 = 1 } }
112 if t2 == 1 { pass = pass + 1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
113 gw("T2 no capability -> -32001, organ NOT executed (no ambient authority)\n" as *u8)
114
115 // T3 NEG (fail-closed allowlist): valid cap for 'nx_http_probe' but its row is RED -> tool error, not run
116 tot = tot + 1
117 let tl3: i64 = capt_issue(secret, slen, "nx_http_probe" as *u8, 13, exp, 1003, tok, 1024)
118 let rn3: i64 = eg_build_call(req, "nx_http_probe" as *u8, tok, tl3)
119 let on3: i64 = ta_handle_pfx(TP, req, rn3, out)
120 var t3: i64 = 0
121 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 } }
122 if t3 == 1 { pass = pass + 1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
123 gw("T3 cap-valid but non-GREEN allowlist row -> tool error, NOT executed (defense in depth)\n" as *u8)
124
125 // T4: MULTI-ARG tools/call -- params.arguments.argv is parsed into a native argv and delivered to the child
126 // as real argv[1..]. Proves the FULL arg-passing path end-to-end: parse -> cap-verify -> tea_run_argv ->
127 // resolve -> fork+execve(argecho, [path,alpha,bravo,charlie]) -> child echoes them -> captured. This is the
128 // exceed that makes multi-arg organs (e.g. nx_mgmt_client <url> call GET <path> <tok>) callable over MCP.
129 tot = tot + 1
130 let tl4: i64 = capt_issue(secret, slen, "argecho" as *u8, 7, exp, 1004, tok, 1024)
131 let rn4: i64 = eg_build_call_argv(req, "argecho" as *u8, tok, tl4, "[\"alpha\",\"bravo\",\"charlie\"]" as *u8)
132 let on4: i64 = ta_handle_pfx(TP, req, rn4, out)
133 var t4: i64 = 0
134 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 } } } } }
135 if t4 == 1 { pass = pass + 1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
136 gw("T4 multi-arg tools/call: params.arguments.argv -> child argv (alpha/bravo/charlie echoed) -- arg-passing exceed\n" as *u8)
137
138 // T5: GET /api/cap/status is PUBLIC (no cap) and honestly reports the INSECURE placeholder when no keyfile is
139 // provisioned (the gate CWD has none) -- the bootstrap self-check that prevents a blind mint against a stale key.
140 tot = tot + 1
141 let o5: i64 = eg_cat(req, 0, "GET /api/cap/status HTTP/1.1\r\nHost: x\r\n\r\n" as *u8)
142 let on5: i64 = ta_handle_pfx(TP, req, o5, out)
143 var t5: i64 = 0
144 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 } } }
145 if t5 == 1 { pass = pass + 1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
146 gw("T5 GET /api/cap/status (public) honestly reports insecure_placeholder when no keyfile is provisioned\n" as *u8)
147
148 // T6: ta_cap_provisioned detection -- real keyfile -> secure; absent OR a file literally holding the placeholder
149 // -> insecure. Proves the status readout flips correctly once nx_cap_keygen writes a real key on the server.
150 let KP: *u8 = "/tmp/eg_capkey.key" as *u8
151 __syscall(87, KP, 0, 0, 0, 0, 0) // ensure absent
152 tot = tot + 1
153 var t6: i64 = 0
154 if ta_cap_provisioned_from(KP) == 0 {
155 eg_write_file(KP, "a-real-256-bit-secret-not-the-placeholder-000000" as *u8)
156 if ta_cap_provisioned_from(KP) == 1 {
157 eg_write_file(KP, TA_CAP_SECRET)
158 if ta_cap_provisioned_from(KP) == 0 { t6 = 1 }
159 }
160 }
161 __syscall(87, KP, 0, 0, 0, 0, 0) // cleanup
162 if t6 == 1 { pass = pass + 1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
163 gw("T6 ta_cap_provisioned: real keyfile -> secure; absent/placeholder-in-file -> insecure (status flips on provisioning)\n" as *u8)
164
165 // T7: OAuth 2.1 interop -- present the SAME ocap via `Authorization: Bearer <cap>` (not X-Nishi-Cap/_cap) -> EXECUTES.
166 tot = tot + 1
167 let tl7: i64 = capt_issue(secret, slen, "ping" as *u8, 4, exp, 1007, tok, 1024)
168 let rn7: i64 = eg_build_call_bearer(req, "ping" as *u8, tok, tl7)
169 let on7: i64 = ta_handle_pfx(TP, req, rn7, out)
170 var t7: i64 = 0
171 if eg_has(out, on7, "NX_TOOL_PING_OK" as *u8) == 1 { if eg_has(out, on7, "\"isError\":false" as *u8) == 1 { t7 = 1 } }
172 if t7 == 1 { pass = pass + 1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
173 gw("T7 OAuth Bearer: 'Authorization: Bearer <cap>' authorizes tools/call (stock-MCP-client interop)\n" as *u8)
174
175 // T8: RFC 9728 discovery -- GET /.well-known/oauth-protected-resource advertises bearer-in-header auth + the AS.
176 tot = tot + 1
177 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)
178 let on8: i64 = ta_handle_pfx(TP, req, o8, out)
179 var t8: i64 = 0
180 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 } } }
181 if t8 == 1 { pass = pass + 1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
182 gw("T8 RFC9728 GET /.well-known/oauth-protected-resource advertises bearer-in-header auth\n" as *u8)
183
184 // T9 (R4 per-client consent): delegate a NARROWER cap WITH a client id -> attenuated cap + consent_recorded.
185 tot = tot + 1
186 let ptl: i64 = capt_issue(secret, slen, "ping,argecho" as *u8, 12, exp, 1009, tok, 1024)
187 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)
188 o9 = eg_cat(req, o9, "{\"_cap\":\"" as *u8)
189 o9 = eg_catb(req, o9, tok, ptl)
190 o9 = eg_cat(req, o9, "\",\"allow\":\"ping\",\"nonce\":42,\"client\":\"gate-test-client\"}" as *u8)
191 let on9: i64 = ta_handle_pfx(TP, req, o9, out)
192 var t9r: i64 = 0
193 if eg_has(out, on9, "\"consent_recorded\":true" as *u8) == 1 { if eg_has(out, on9, "\"cap\":\"" as *u8) == 1 { t9r = 1 } }
194 if t9r == 1 { pass = pass + 1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
195 gw("T9 R4 per-client consent: delegate w/ client id -> attenuated cap + consent_recorded\n" as *u8)
196
197 // T10 (R4): the append-only consent ledger records the client id (auditable).
198 tot = tot + 1
199 let o10: i64 = eg_cat(req, 0, "GET /api/cap/consent-log HTTP/1.1\r\nHost: x\r\n\r\n" as *u8)
200 let on10: i64 = ta_handle_pfx(TP, req, o10, out)
201 var t10: i64 = 0
202 if eg_has(out, on10, "gate-test-client" as *u8) == 1 { t10 = 1 }
203 if t10 == 1 { pass = pass + 1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
204 gw("T10 R4 consent ledger: GET /api/cap/consent-log records the client id (auditable)\n" as *u8)
205
206 // T11 (rung 24): ASYNC JOB LANE -- "_async":"1" returns JOB-STARTED
207 // immediately; the detached worker lands .out then the ATOMIC .done
208 // marker (tmp+rename, written LAST); the echoed argv proves the worker
209 // ran the same cap+allowlist-gated pinned dispatch the sync lane runs.
210 tot = tot + 1
211 let tl11: i64 = capt_issue(secret, slen, "argecho" as *u8, 7, exp, 1011, tok, 1024)
212 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)
213 o11 = eg_cat(req, o11, "{\"jsonrpc\":\"2.0\",\"id\":9,\"method\":\"tools/call\",\"params\":{\"name\":\"argecho\",\"_cap\":\"" as *u8)
214 o11 = eg_catb(req, o11, tok, tl11)
215 o11 = eg_cat(req, o11, "\",\"arguments\":{\"argv\":[\"async_alpha\",\"async_bravo\"],\"_async\":\"1\"}}}" as *u8)
216 let on11: i64 = ta_handle_pfx(TP, req, o11, out)
217 var t11: i64 = 0
218 if eg_has(out, on11, "JOB-STARTED id=" as *u8) == 1 { if eg_has(out, on11, "\"state\":\"RUNNING\"" as *u8) == 1 {
219 let idp: i64 = ta_indexof(out, on11, "JOB-STARTED id=" as *u8) + 15
220 let dpath: *u8 = sys_mmap(256)
221 let opath: *u8 = sys_mmap(256)
222 var dq: i64 = eg_cat(dpath, 0, "_jobs/job_" as *u8)
223 var oq: i64 = eg_cat(opath, 0, "_jobs/job_" as *u8)
224 var di: i64 = idp
225 var digo: i64 = 1
226 while digo == 1 {
227 let dc: i64 = out[di] as i64
228 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 }
229 }
230 dq = eg_cat(dpath, dq, ".done" as *u8)
231 oq = eg_cat(opath, oq, ".out" as *u8)
232 dpath[dq] = 0 as u8
233 opath[oq] = 0 as u8
234 var polls: i64 = 0
235 var seen: i64 = 0
236 while polls < 100 {
237 let dfd: i64 = sys_openat_rd(dpath)
238 if dfd >= 0 { sys_close(dfd); seen = 1; polls = 100 } else { sys_sleep_ms(50); polls = polls + 1 }
239 }
240 if seen == 1 {
241 let dl: *i64 = sys_mmap(16) as *i64
242 let db2: *u8 = sys_read_file(dpath, dl)
243 let ol: *i64 = sys_mmap(16) as *i64
244 let ob2: *u8 = sys_read_file(opath, ol)
245 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 } } }
246 }
247 __syscall(87, dpath, 0, 0, 0, 0, 0)
248 __syscall(87, opath, 0, 0, 0, 0, 0)
249 } }
250 if t11 == 1 { pass = pass + 1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
251 gw("T11 async job: JOB-STARTED immediate, detached worker lands .out + atomic .done, argv echoed\n" as *u8)
252
253 gw("\n=== nx_tools_api_exec_gate " as *u8); gn(pass); gw("/" as *u8); gn(tot); gw(" ===\n" as *u8)
254 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 }
255 gw("TOOLS-API-EXEC RED\n" as *u8); sys_exit(1); return 1
256}