code wiki / (root) / nx_http_loopback_gate.nx

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}