nx_http_loopback_gate.nx source
↩ module page · 76 lines · 4571 B
1// nx_http_loopback_gate.nx -- gates the sovereign HTTP client with a LIVE loopback GET against :8080.
2//
3// WHAT CHANGED, AND WHY (2026-08-01). This gate used to assert that
4// :8080/blocklist answers 200 with the blocklist body. It has been RED since
5// the topology changed: :8080 is redirect.elf, a plain-HTTP -> HTTPS
6// redirector, and it 301s EVERY path unconditionally. Proven by hand with a
7// raw socket carrying no Accept headers at all -- same 301 -- so the failure
8// was never about the request we build.
9//
10// The old expectation was not just stale, it was backwards: serving that page
11// in cleartext on :8080 is the thing we do NOT want. So the gate now asserts
12// the redirect is present AND correct -- 301 with a Location that is https://
13// and carries the requested path. That turns a broken liveness check into a
14// real transport-security check: if someone ever makes :8080 serve content
15// directly, this goes RED.
16//
17// It still gates what it always gated -- nx_http_client builds a request that
18// a live server accepts and answers -- because T1 needs real bytes back.
19//
20// license_tier: ORIGINAL
21import "nx_http_client.nx"
22import "nx_gate.nx"
23
24func hlg_atoi3(buf: *u8, off: i64) -> i64 { var v: i64=0; var i: i64=0; while i<3 { let d: i64=buf[off+i] as i64; if d>=48 { if d<=57 { v=v*10+(d-48) } } i=i+1 } return v }
25func hlg_contains(buf: *u8, n: i64, needle: *u8) -> i64 {
26 var nl: i64=0; while needle[nl]!=(0 as u8){nl=nl+1}
27 if nl==0 { return 1 }
28 var i: i64=0
29 while i+nl<=n { var m: i64=1; var j: i64=0; while j<nl { if buf[i+j]!=needle[j] { m=0; j=nl } else { j=j+1 } } if m==1 { return 1 } i=i+1 }
30 return 0
31}
32
33func main() -> i64 {
34 gw("=== nx_http_loopback_gate: live GET :8080 -> 301 to https (gates nx_http_client + the cleartext fence) ===\n" as *u8)
35 var pass: i64=0; var tot: i64=0
36 let addr: *u8 = sys_mmap(16)
37 nx_http_client_sockaddr_ipv4(addr, 127, 0, 0, 1, 8080)
38 let cap: i64 = 4194304
39 let buf: *u8 = sys_mmap(cap)
40 let verdict: *i64 = sys_mmap(8) as *i64
41 let n: i64 = nx_http_client_get(addr, "/blocklist" as *u8, 10, "localhost" as *u8, 9, buf, cap, verdict)
42
43 // T1 -- the request we BUILD is one a live server accepts and answers.
44 // This is the half that actually exercises nx_http_client, including the
45 // Accept/Accept-Encoding block now derived from nx_codec_caps.
46 tot=tot+1; if n > 0 { pass=pass+1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
47 gw("T1 GET returns bytes (n=" as *u8); gn(n); gw(")\n" as *u8)
48
49 var status: i64=0; if n > 12 { status = hlg_atoi3(buf, 9) }
50 tot=tot+1; if status==301 { pass=pass+1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
51 gw("T2 status == 301 -- cleartext :8080 REDIRECTS, never serves (got " as *u8); gn(status); gw(")\n" as *u8)
52
53 // T3 -- the redirect must go to https, not merely somewhere. A 301 to
54 // another http:// URL would satisfy T2 while leaving the fence open.
55 tot=tot+1; if hlg_contains(buf, n, "Location: https://" as *u8)==1 { pass=pass+1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
56 gw("T3 Location is https:// (the fence, not just any redirect)\n" as *u8)
57
58 // T4 -- the redirect PRESERVES the path, so a bookmark survives the hop.
59 tot=tot+1; if hlg_contains(buf, n, "/blocklist" as *u8)==1 { pass=pass+1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
60 gw("T4 Location carries the requested path\n" as *u8)
61
62 // T5 NEG -- non-vacuity. The body must NOT carry the page content; if
63 // :8080 ever starts serving it directly, T2/T3 could still pass on a
64 // stale header while the content leaks in cleartext below it.
65 tot=tot+1; if hlg_contains(buf, n, "Do-not-recover" as *u8)==0 { pass=pass+1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
66 gw("T5 NEG body does NOT carry the page in cleartext\n" as *u8)
67
68 // T6 NEG -- the status reader is proven able to report something other
69 // than what we want, so T2 is not passing on a constant.
70 tot=tot+1; if status != 200 { pass=pass+1; gw(" [PASS] " as *u8) } else { gw(" [FAIL] " as *u8) }
71 gw("T6 NEG status is not 200 -- the reader discriminates\n" as *u8)
72
73 gw("\n=== nx_http_loopback_gate " as *u8); gn(pass); gw("/" as *u8); gn(tot); gw(" ===\n" as *u8)
74 if pass==tot { gw("HTTP-LOOPBACK GREEN -- live GET over the sovereign http client; cleartext :8080 proven to 301 to https with the path preserved and NO content served\n" as *u8); sys_exit(0); return 0 }
75 gw("HTTP-LOOPBACK RED\n" as *u8); sys_exit(1); return 1
76}