nx_cap_revoke_gate.nx source
↩ module page · 60 lines · 3753 B
1// nx_cap_revoke_gate.nx -- proves REVOCATION over R0's ta_handle: a valid cap invokes; after POST /api/cap/revoke
2// (presenting it) the SAME cap is refused (reason 5 = revoked). Completes issue->delegate->verify->REVOKE. The gate
3// writes cap_revoked.list in CWD and unlinks it at both ends (self-cleaning). license_tier: ORIGINAL
4import "nx_tools_api.nx"
5import "nx_gate.nx"
6
7func rv_has(o: *u8, n: i64, ndl: *u8) -> i64 { if ta_indexof(o, n, ndl) >= 0 { return 1 } return 0 }
8func rv_mark(cond: i64, p: *i64, t: *i64, lbl: *u8) -> i64 {
9 t[0] = t[0] + 1
10 if cond == 1 { p[0] = p[0] + 1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
11 gw(lbl); gw("\n" as *u8); return 0
12}
13func rv_unlink(path: *u8) -> i64 { __syscall(263, AT_FDCWD, path, 0, 0, 0, 0) return 0 }
14func rv_call(cap: *u8, caplen: i64, name: *u8, out: *u8) -> i64 {
15 var o: i64 = ta_cat(out, 0, "POST /mcp HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":{\"name\":\"" as *u8)
16 o = ta_cat(out, o, name); o = ta_cat(out, o, "\",\"_cap\":\"" as *u8); o = ta_catb(out, o, cap, caplen); o = ta_cat(out, o, "\"}}" as *u8)
17 return o
18}
19func rv_revoke_req(cap: *u8, caplen: i64, out: *u8) -> i64 {
20 var o: i64 = ta_cat(out, 0, "POST /api/cap/revoke HTTP/1.1\r\nHost: x\r\nX-Nishi-Cap: " as *u8)
21 o = ta_catb(out, o, cap, caplen); o = ta_cat(out, o, "\r\nConnection: close\r\n\r\n{}" as *u8)
22 return o
23}
24
25func main() -> i64 {
26 gw("=== nx_cap_revoke_gate: revocation denylist -- a leaked cap is KILLED before expiry (issue->verify->REVOKE) ===\n" as *u8)
27 let TP: *u8 = "knowledge/toolreg-test-rev-" as *u8
28 let secret: *u8 = TA_CAP_SECRET
29 let slen: i64 = ta_slen(TA_CAP_SECRET)
30 let p: *i64 = sys_mmap(16) as *i64; p[0] = 0
31 let t: *i64 = sys_mmap(16) as *i64; t[0] = 0
32 let out: *u8 = sys_mmap(1048576)
33 let req: *u8 = sys_mmap(4096)
34 rv_unlink("cap_revoked.list" as *u8) // start clean
35
36 let capb: *u8 = sys_mmap(1024)
37 let cap: i64 = capt_issue(secret, slen, "nx_a" as *u8, 4, 9999999999, 4242, capb, 1024)
38
39 // R1: before revocation -> AUTHORIZED (post-R2-exec-rung: the "capability-authorized ..." tool result
40 // is only reachable when the cap VERIFIED; execution itself is the separate exec-allowlist layer)
41 let r1: i64 = rv_call(capb, cap, "nx_a" as *u8, req); let n1: i64 = ta_handle_pfx(TP, req, r1, out)
42 var t1: i64 = 0; if rv_has(out, n1, "capability-authorized" as *u8) == 1 { if rv_has(out, n1, "-32001" as *u8) == 0 { t1 = 1 } }
43 rv_mark(t1, p, t, "R1 before revoke: valid cap -> AUTHORIZED (not -32001)" as *u8)
44
45 // R2: revoke it (present the cap) -> 200 revoked_nonce
46 let rr: i64 = rv_revoke_req(capb, cap, req); let nr: i64 = ta_handle_pfx(TP, req, rr, out)
47 var t2: i64 = 0; if rv_has(out, nr, "revoked_nonce" as *u8) == 1 { t2 = 1 }
48 rv_mark(t2, p, t, "R2 revoke (present the cap) -> 200 revoked_nonce (denylisted)" as *u8)
49
50 // R3: SAME cap now -> DENIED (revoked; reason 5), NOT invoked
51 let r3: i64 = rv_call(capb, cap, "nx_a" as *u8, req); let n3: i64 = ta_handle_pfx(TP, req, r3, out)
52 var t3: i64 = 0; if rv_has(out, n3, "\"reason\":5" as *u8) == 1 { if rv_has(out, n3, "invoked nx_a" as *u8) == 0 { t3 = 1 } }
53 rv_mark(t3, p, t, "R3 after revoke: SAME cap -> DENIED (reason 5 = revoked, not invoked)" as *u8)
54
55 rv_unlink("cap_revoked.list" as *u8) // cleanup
56
57 gw("\n=== nx_cap_revoke_gate " as *u8); gn(p[0]); gw("/" as *u8); gn(t[0]); gw(" ===\n" as *u8)
58 if p[0] == t[0] { gw("CAP-REVOKE GREEN -- a leaked capability is killed before expiry (revocation completes the lifecycle)\n" as *u8); sys_exit(0); return 0 }
59 gw("CAP-REVOKE RED\n" as *u8); sys_exit(1); return 1
60}