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}