code wiki / _hdl_build / nx_inventory_gate.nx

nx_inventory_gate.nx source

↩ module page · 162 lines · 9083 B

1// nx_inventory_gate.nx -- proves nx_inventory_serve's teeth IN-PROCESS. No socket, no curl, no shell. 2// 3// WHY THIS EXISTS: the first version of this daemon was tested by a shell script driving curl -- the 4// break-glass shape rule 29 forbids -- and that suite was BLIND TWICE OVER: 5// 1. it printed only "Refused." without the REASON, so four SSRF negative tests went green while the 6// url decoder was completely broken. ***A GUARD THAT REFUSES EVERYTHING PASSES EVERY NEGATIVE 7// TEST.*** Only a POSITIVE control (an input that MUST be allowed) could see it. 8// 2. once auth landed, every route 401s before reaching the guard, so the SSRF checks could not run 9// over HTTP AT ALL. A test that the product's own security improvement disables is not a test. 10// The daemon's router is a PURE FUNCTION for exactly this reason: request bytes in, response bytes out. 11// So this gate calls iv_url_ok / iv_urldec / iv_handle directly and asserts WHICH RULE FIRED. 12// 13// Fixtures live under /tmp; production surfaces are never read or written. 14// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0 15import "nx_syscalls.nx" 16import "nx_gate_verdict.nx" 17import "nx_inventory_serve.nx" 18 19const IG_BUF: i64 = 4096 20const IG_SMALL: i64 = 64 21 22// assert iv_url_ok's VERDICT *and* its REASON CODE. Asserting only "refused" is what made the shell 23// suite blind: every url was being refused for the WRONG reason and it could not tell. 24func ig_url(name: *u8, url: *u8, want_ok: i64, want_why: i64, ctr: *i64) -> i64 { 25 let w: *i64 = sys_mmap(IG_SMALL) as *i64 26 var n: i64 = 0 27 while url[n] != (0 as u8) { n = n + 1 } 28 let got: i64 = iv_url_ok(url, n, w) 29 var pass: i64 = 0 30 if got == want_ok { if want_ok == 1 { pass = 1 } else { if w[0] == want_why { pass = 1 } } } 31 gv_check(name, pass, ctr) 32 return pass 33} 34 35// decode a query value out of a REAL request line and compare byte-for-byte. 36func ig_dec(name: *u8, reqline: *u8, want: *u8, ctr: *i64) -> i64 { 37 var rn: i64 = 0 38 while reqline[rn] != (0 as u8) { rn = rn + 1 } 39 // find "url=" the same way the router does 40 var q: i64 = 0 41 var found: i64 = 0 42 var i: i64 = 0 43 while i < rn { 44 if found == 0 { if iv_lit_at(reqline, rn, i, "url=" as *u8) > 0 { q = i + 4; found = 1 } } 45 i = i + 1 46 } 47 let dst: *u8 = sys_mmap(IG_BUF) 48 var got: i64 = 0 49 if found == 1 { got = iv_urldec(reqline, rn, q, dst, IG_BUF - 2) } 50 var wn: i64 = 0 51 while want[wn] != (0 as u8) { wn = wn + 1 } 52 var pass: i64 = 0 53 if got == wn { 54 pass = 1 55 var k: i64 = 0 56 while k < wn { if dst[k] != want[k] { pass = 0; k = wn } else { k = k + 1 } } 57 } 58 gv_check(name, pass, ctr) 59 return pass 60} 61 62func ig_contains(h: *u8, hn: i64, needle: *u8) -> i64 { 63 var i: i64 = 0 64 while i < hn { if iv_lit_at(h, hn, i, needle) > 0 { return 1 } i = i + 1 } 65 return 0 66} 67 68func main(argc: i64, argv: *i64) -> i64 { 69 gv_head("=== nx_inventory_gate -- SSRF guard + url decoder + auth wall, driven IN-PROCESS ===" as *u8) 70 let ctr: *i64 = gv_ctr() 71 72 // ---- T1..T2 THE BITE: the guard must REFUSE the estate and ALLOW the open web. Both directions, 73 // because a guard that refuses everything passes every negative test. 74 let lo: *i64 = sys_mmap(IG_SMALL) as *i64 75 let pu: *i64 = sys_mmap(IG_SMALL) as *i64 76 // ⚠POLARITY: gv_bite speaks in "DID THE GUARD FIRE?" (bad must be 1, good must be 0) while 77 // iv_url_ok speaks in "IS THIS ALLOWED?" (1 = allowed). Opposite senses -- so the results are 78 // INVERTED here. The first cut passed them straight through and the gate correctly reported 79 // "[VACUOUS: did not fire on the bad input][FALSE-POSITIVE: fired on the good input]", i.e. the 80 // instrument caught my own contract assumption before it could certify anything. 81 let bad_ok: i64 = iv_url_ok("https://127.0.0.1/api/health" as *u8, 28, lo) 82 let good_ok: i64 = iv_url_ok("https://example.com/" as *u8, 20, pu) 83 gv_bite("T1 BITE: loopback REFUSED and a public https url ALLOWED" as *u8, 1 - bad_ok, 1 - good_ok, ctr) 84 85 // ---- T3..T9 each rule asserted BY REASON CODE, not merely "refused" 86 ig_url("T3 loopback -> reason 6" as *u8, "https://127.0.0.1/x" as *u8, 0, 6, ctr) 87 ig_url("T4 rfc1918 10/8 -> reason 6" as *u8, "https://10.0.0.5/x" as *u8, 0, 6, ctr) 88 ig_url("T5 rfc1918 192.168 -> reason 6" as *u8, "https://192.168.8.240/api" as *u8, 0, 6, ctr) 89 ig_url("T6 link-local 169.254 -> reason 6" as *u8, "https://169.254.1.1/x" as *u8, 0, 6, ctr) 90 ig_url("T7 plain http -> reason 3" as *u8, "http://example.com/" as *u8, 0, 3, ctr) 91 ig_url("T8 userinfo @ -> reason 5" as *u8, "https://evil@127.0.0.1/" as *u8, 0, 5, ctr) 92 ig_url("T9 ipv6 literal -> reason 6" as *u8, "https://[::1]/x" as *u8, 0, 6, ctr) 93 94 // ---- T10..T11 THE CALIBRATION PAIR: only 172.16-31 is private. A guard that blocks all of 172/8 95 // is over-broad and would look identical on a refuse-only test suite. 96 ig_url("T10 172.20 IS private -> reason 6" as *u8, "https://172.20.1.1/x" as *u8, 0, 6, ctr) 97 ig_url("T11 172.200 is PUBLIC -> ALLOWED" as *u8, "https://172.200.1.1/x" as *u8, 1, 0, ctr) 98 99 // ---- T12..T14 THE DECODER REGRESSION. This exact defect shipped twice: without the SPACE 100 // terminator the decode swallows " HTTP/1.1\r\nHost:..." and every url then refuses as 101 // "control bytes" -- which every negative test happily accepts. 102 ig_dec("T12 decode STOPS at the space ending the request-line URI" as *u8, 103 "GET /inventory?url=https%3A%2F%2Fexample.com%2F HTTP/1.1\r\nHost: x\r\n\r\n" as *u8, 104 "https://example.com/" as *u8, ctr) 105 ig_dec("T13 decode STOPS at &" as *u8, 106 "GET /inventory?url=https%3A%2F%2Fa.com%2F&z=1 HTTP/1.1\r\n" as *u8, 107 "https://a.com/" as *u8, ctr) 108 ig_dec("T14 decode STOPS at #" as *u8, 109 "GET /inventory?url=https%3A%2F%2Fb.com%2F#frag HTTP/1.1\r\n" as *u8, 110 "https://b.com/" as *u8, ctr) 111 112 // ---- T15..T18 THE AUTH WALL, driven through the PURE ROUTER with a /tmp-only realm. 113 let keysf: *u8 = "/tmp/nx_invgate_keys.bin" as *u8 114 let storef: *u8 = "/tmp/nx_invgate_store.log" as *u8 115 let realm: *u8 = "invgate" as *u8 116 let oprf: *u8 = sys_mmap(32) 117 let akp: *u8 = sys_mmap(32) 118 let akb: *u8 = sys_mmap(33) 119 let edp: *u8 = sys_mmap(32) 120 let edb: *u8 = sys_mmap(32) 121 var armed: i64 = 0 122 if nx_uas_server_keys_load_or_init(keysf, oprf, akp, akb, edp, edb) == NX_UAS_OK { armed = 1 } 123 // gv_need: "I COULD NOT LOOK" is not "IT IS BROKEN" -- if the realm cannot arm in this environment 124 // the auth teeth are UNOBSERVABLE, not failing, and the gate must say so rather than invent a pass. 125 gv_need("T15 realm armable (auth teeth observable)" as *u8, armed, ctr) 126 if armed == 1 { 127 let ctx: *NxAuthContext = sys_mmap(256) as *NxAuthContext 128 var okctx: i64 = 0 129 if nx_auth_context_init(ctx, realm, 7, realm, 7, storef as i64, oprf, edp, edb, IV_SESS_TTL, IV_MAGIC_8192, 1, 1, 5, 1) == NX_MAUTH_OK { okctx = 1 } 130 gv_check("T16 auth context init" as *u8, okctx, ctr) 131 if okctx == 1 { 132 let page: *u8 = sys_mmap(IV_PAGECAP) 133 let scan: *u8 = sys_mmap(IV_OUTCAP) 134 let body: *u8 = sys_mmap(IV_OUTCAP) 135 let out: *u8 = sys_mmap(IV_OUTCAP) 136 // POSITIVE CONTROL: health must be served WITHOUT a session, or the wall is just refusing 137 // everything and the 401s below prove nothing. 138 let hreq: *u8 = "GET /inventory/health HTTP/1.1\r\nHost: x\r\n\r\n" as *u8 139 var hn: i64 = 0 140 while hreq[hn] != (0 as u8) { hn = hn + 1 } 141 let ho: i64 = iv_handle(ctx, hreq, hn, 1, page, scan, body, out) 142 gv_check("T17 POSITIVE CONTROL: /inventory/health 200 with NO session" as *u8, 143 ig_contains(out, ho, "200 OK" as *u8), ctr) 144 // the SSRF-capable route must 401 AND must not have fetched 145 let freq: *u8 = "GET /inventory?url=https%3A%2F%2Fexample.com%2F HTTP/1.1\r\nHost: x\r\n\r\n" as *u8 146 var fn2: i64 = 0 147 while freq[fn2] != (0 as u8) { fn2 = fn2 + 1 } 148 let fo: i64 = iv_handle(ctx, freq, fn2, 2, page, scan, body, out) 149 gv_check("T18 fetch route 401 without a session" as *u8, 150 ig_contains(out, fo, "401 Unauthorized" as *u8), ctr) 151 var nofetch: i64 = 1 152 if ig_contains(out, fo, "bytes fetched" as *u8) == 1 { nofetch = 0 } 153 gv_check("T19 CRITICAL: no fetch happened for the denied request" as *u8, nofetch, ctr) 154 var nocookie: i64 = 1 155 if ig_contains(out, fo, "Set-Cookie" as *u8) == 1 { nocookie = 0 } 156 gv_check("T20 no Set-Cookie on deny (charter C1)" as *u8, nocookie, ctr) 157 } 158 } 159 160 return gv_verdict("nx_inventory_gate" as *u8, ctr, 161 "SSRF guard asserted BY REASON CODE with a positive control; decoder regression pinned; auth wall driven through the pure router in-process (no curl, no shell)" as *u8) 162}