code wiki / (root) / nx_compare_gw_gate.nx

nx_compare_gw_gate.nx source

↩ module page · 229 lines · 18116 B

1// nx_compare_gw_gate.nx -- GATE for the /compare gateway decision core (nx_compare_gw_lib). 2// 3// THE LOAD-BEARING TEETH ARE THE NO-LEAK ONES: a board a viewer may not see is NOT in the bytes of the 4// index rendered for that viewer (not-even-listed), and a request for it returns the SAME answer as a 5// request for a board that does not exist (NOTFOUND, never DENY, for an anonymous visitor). Everything 6// else checks a shape; those check the property the operator asked for -- the owner sees all, a client 7// sees a subset, and the subset boundary does not leak the existence of what is above it. 8// 9// Every function is exercised IN-PROCESS so a mutation bite reaches it; the conf and requests are built 10// IN MEMORY so no live file can change what this gate measures. The comment character in a fixture conf is 11// CONSTRUCTED (35) -- a literal hash inside a string is an nx_cc lexer trap. 12// license_tier: ORIGINAL No hw writes (Rule 26). 13import "nx_syscalls.nx" 14import "nx_compare_gw_lib.nx" 15import "nx_gate_verdict.nx" 16 17const CGG_BUF: i64 = 65536 18const CGG_SMALL: i64 = 256 19const CGG_WORD: i64 = 8 20 21func cgg_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 22func cgg_streq(a: *u8, b: *u8) -> i64 { var i: i64 = 0; while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } if b[i] != (0 as u8) { return 0 } return 1 } 23// substring search over a NUL-terminated buffer 24func cgg_has(hay: *u8, needle: *u8) -> i64 { 25 let hn: i64 = cgg_slen(hay) 26 let m: i64 = cgg_slen(needle) 27 if m == 0 { return 1 } 28 var i: i64 = 0 29 while i + m <= hn { 30 var j: i64 = 0 31 var ok: i64 = 1 32 while j < m { if hay[i+j] != needle[j] { ok = 0; j = m } else { j = j + 1 } } 33 if ok == 1 { return 1 } 34 i = i + 1 35 } 36 return 0 37} 38 39func main() -> i64 { 40 let ctr: *i64 = gv_ctr() 41 gv_head("nx_compare_gw -- the /compare access wall: the owner sees all, a client sees a subset, and the subset does not leak" as *u8) 42 43 // ---- the fixture conf, built in memory (comment byte constructed) ---- 44 // default public; two boards named: a public one and an owner-only one. 45 let conf: *u8 = sys_mmap(CGG_BUF) 46 var co: i64 = 0 47 conf[co] = 35 as u8; co = co + 1 48 co = cg_cat(conf, co, " fixture access.conf\ndefault|public\ndomain|folkgames|public\ndomain|synthroom|operator\ndomain|members|member\n" as *u8) 49 conf[co] = 0 as u8 50 let cn: i64 = co 51 52 // ---- cg_access_level: the four words and the fail-closed default ---- 53 gv_check("T1 public reads level 0" as *u8, (cg_access_level("public" as *u8, 0, 6) == CG_LVL_PUBLIC) as i64, ctr) 54 gv_check("T2 viewer reads level 1" as *u8, (cg_access_level("viewer" as *u8, 0, 6) == CG_LVL_VIEWER) as i64, ctr) 55 gv_check("T3 operator reads level 3" as *u8, (cg_access_level("operator" as *u8, 0, 8) == CG_LVL_OPERATOR) as i64, ctr) 56 gv_check("T4 an unknown access word reads OPERATOR, fail-closed, never public" as *u8, (cg_access_level("nonsense" as *u8, 0, 8) == CG_LVL_OPERATOR) as i64, ctr) 57 gv_bite("neg-control-fail-closed a typo locks a board to the owner and does NOT read as public" as *u8, (cg_access_level("pubIic" as *u8, 0, 6) == CG_LVL_OPERATOR) as i64, (cg_access_level("public" as *u8, 0, 6) == CG_LVL_OPERATOR) as i64, ctr) 58 59 // ---- cg_required: a named board wins, else the default ---- 60 gv_check("T5 a board declared public requires level 0" as *u8, (cg_required(conf, cn, "folkgames" as *u8) == CG_LVL_PUBLIC) as i64, ctr) 61 gv_check("T6 a board declared operator requires level 3" as *u8, (cg_required(conf, cn, "synthroom" as *u8) == CG_LVL_OPERATOR) as i64, ctr) 62 gv_check("T7 a board with NO row takes the declared default of public" as *u8, (cg_required(conf, cn, "typography" as *u8) == CG_LVL_PUBLIC) as i64, ctr) 63 // a conf with NO default row falls to operator -- names nothing, exposes nothing 64 let ndf: *u8 = sys_mmap(CGG_BUF) 65 var no: i64 = 0 66 no = cg_cat(ndf, no, "domain|folkgames|public\n" as *u8) 67 ndf[no] = 0 as u8 68 gv_check("T8 a conf that declares no default leaves an unnamed board at OPERATOR, fail-closed" as *u8, (cg_required(ndf, no, "typography" as *u8) == CG_LVL_OPERATOR) as i64, ctr) 69 gv_bite("neg-control-default-fail-closed an unnamed board is operator without a default row and public with one" as *u8, (cg_required(ndf, no, "typography" as *u8) == CG_LVL_OPERATOR) as i64, (cg_required(conf, cn, "typography" as *u8) == CG_LVL_OPERATOR) as i64, ctr) 70 71 // ---- cg_decide: the whole point ---- 72 // owner (3) sees the operator board; a client (1) does not, and is told NOTFOUND, not DENY; an 73 // anonymous visitor (0) is told NOTFOUND for it too; but a signed-in client asking for a MEMBER board 74 // (needs 2) is told DENY because their existence as a viewer is already known. 75 gv_check("T9 owner level 3 SERVES an operator board" as *u8, (cg_decide(3, CG_LVL_OPERATOR, 1) == CG_D_SERVE) as i64, ctr) 76 gv_check("T10 a client level 1 gets NOTFOUND for an operator board, never DENY -- existence not leaked" as *u8, (cg_decide(1, CG_LVL_OPERATOR, 1) == CG_D_NOTFOUND) as i64, ctr) 77 gv_check("T11 an anonymous level 0 gets NOTFOUND for a gated board" as *u8, (cg_decide(0, CG_LVL_VIEWER, 1) == CG_D_NOTFOUND) as i64, ctr) 78 gv_check("T12 a public board is SERVED to everyone including anonymous" as *u8, (cg_decide(0, CG_LVL_PUBLIC, 1) == CG_D_SERVE) as i64, ctr) 79 gv_check("T13 a board that does not exist is NOTFOUND regardless of level" as *u8, (cg_decide(3, CG_LVL_PUBLIC, 0) == CG_D_NOTFOUND) as i64, ctr) 80 // THE NO-LEAK DISCRIMINATOR: for the SAME operator board, an owner SERVES and a client gets the exact 81 // answer a missing board gives. A leaky gate would answer DENY, which confirms the board is there. 82 gv_check("T14 the client's answer for an operator board EQUALS the answer for a missing board" as *u8, (cg_decide(1, CG_LVL_OPERATOR, 1) == cg_decide(1, CG_LVL_PUBLIC, 0)) as i64, ctr) 83 gv_bite("neg-control-no-leak an anonymous client cannot distinguish a hidden board from an absent one, but the owner can serve it" as *u8, (cg_decide(0, CG_LVL_OPERATOR, 1) == CG_D_NOTFOUND) as i64, (cg_decide(3, CG_LVL_OPERATOR, 1) == CG_D_NOTFOUND) as i64, ctr) 84 85 // ---- cg_split: the request path under /compare ---- 86 let dom: *u8 = sys_mmap(CGG_SMALL) 87 let rest: *u8 = sys_mmap(CGG_BUF) 88 let s_idx: i64 = cg_split("/compare" as *u8, 8, dom, CGG_SMALL, rest, CGG_BUF) 89 gv_check("T15 /compare is the index" as *u8, (s_idx == 1) as i64, ctr) 90 let s_idx2: i64 = cg_split("/compare/" as *u8, 9, dom, CGG_SMALL, rest, CGG_BUF) 91 gv_check("T16 /compare/ is also the index" as *u8, (s_idx2 == 1) as i64, ctr) 92 let s_dom: i64 = cg_split("/compare/synthroom" as *u8, 18, dom, CGG_SMALL, rest, CGG_BUF) 93 gv_check("T17 /compare/synthroom names the board and no rest" as *u8, (s_dom == 0) as i64 & cgg_streq(dom, "synthroom" as *u8) & (rest[0] == (0 as u8)) as i64, ctr) 94 let s_file: i64 = cg_split("/compare/synthroom/api.json" as *u8, 27, dom, CGG_SMALL, rest, CGG_BUF) 95 gv_check("T18 /compare/synthroom/api.json names the board and the rest" as *u8, (s_file == 0) as i64 & cgg_streq(dom, "synthroom" as *u8) & cgg_streq(rest, "api.json" as *u8), ctr) 96 let s_bad: i64 = cg_split("/gallery/x" as *u8, 10, dom, CGG_SMALL, rest, CGG_BUF) 97 gv_check("T19 a path outside /compare is refused, not guessed" as *u8, (s_bad == (0 - 1)) as i64, ctr) 98 99 // ---- cg_path_safe: boundary defence ---- 100 gv_check("T20 a plain relative file is safe" as *u8, cg_path_safe("api.json" as *u8, 8), ctr) 101 gv_check("T21 a climbing path is refused" as *u8, (cg_path_safe("../secret" as *u8, 9) == 0) as i64, ctr) 102 gv_check("T22 an absolute path is refused" as *u8, (cg_path_safe("/etc/passwd" as *u8, 11) == 0) as i64, ctr) 103 gv_bite("neg-control-path-safe the traversal check fires on .. and not on a clean name" as *u8, (cg_path_safe("a/../b" as *u8, 6) == 0) as i64, (cg_path_safe("a/b" as *u8, 3) == 0) as i64, ctr) 104 105 // ---- cg_declared: the conf enumerates exactly its domain rows ---- 106 let names: *u8 = sys_mmap(CGG_BUF) 107 let dc: *i64 = sys_mmap(CGG_WORD) as *i64 108 cg_declared(conf, cn, names, CGG_BUF, dc) 109 gv_check("T23 the conf declares exactly its three domain rows" as *u8, (dc[0] == 3) as i64, ctr) 110 111 // ---- the INDEX no-leak, measured on the BYTES ---- 112 // Build the owner's index (all three boards) and the client's index (only the public ones), then prove 113 // the operator board's name is present for the owner and ABSENT from the client's bytes. 114 let cand: *i64 = sys_mmap(4 * CGG_WORD) as *i64 115 cand[0] = ("folkgames" as *u8) as i64 116 cand[1] = ("synthroom" as *u8) as i64 117 cand[2] = ("members" as *u8) as i64 118 let owner_ix: *u8 = sys_mmap(CGG_BUF) 119 var oo: i64 = cg_index_open(owner_ix, CG_LVL_OPERATOR, 0) 120 var oshown: i64 = 0 121 var ci: i64 = 0 122 while ci < 3 { 123 let d: *u8 = cand[ci] as *u8 124 if CG_LVL_OPERATOR >= cg_required(conf, cn, d) { oo = cg_index_row(owner_ix, oo, d, "" as *u8); oshown = oshown + 1 } 125 ci = ci + 1 126 } 127 cg_index_close(owner_ix, oo, oshown) 128 let client_ix: *u8 = sys_mmap(CGG_BUF) 129 var xo: i64 = cg_index_open(client_ix, CG_LVL_VIEWER, 0) 130 var xshown: i64 = 0 131 ci = 0 132 while ci < 3 { 133 let d: *u8 = cand[ci] as *u8 134 if CG_LVL_VIEWER >= cg_required(conf, cn, d) { xo = cg_index_row(client_ix, xo, d, "" as *u8); xshown = xshown + 1 } 135 ci = ci + 1 136 } 137 cg_index_close(client_ix, xo, xshown) 138 gv_check("T24 the owner index shows all three boards" as *u8, (oshown == 3) as i64, ctr) 139 gv_check("T25 the client index shows ONLY the one public board" as *u8, (xshown == 1) as i64, ctr) 140 gv_check("T26 the operator board name IS in the owner bytes" as *u8, cgg_has(owner_ix, "synthroom" as *u8), ctr) 141 gv_check("T27 NOT-EVEN-LISTED: the operator board name is ABSENT from the client bytes" as *u8, (cgg_has(client_ix, "synthroom" as *u8) == 0) as i64, ctr) 142 gv_check("T28 the member board is likewise ABSENT from the level-1 client bytes" as *u8, (cgg_has(client_ix, "members" as *u8) == 0) as i64, ctr) 143 gv_check("T29 the public board IS in the client bytes" as *u8, cgg_has(client_ix, "folkgames" as *u8), ctr) 144 gv_check("T39 the served index head advertises the daily feed (rel=alternate, feed.xml) at every level" as *u8, cgg_has(owner_ix, "rel='alternate'" as *u8) & cgg_has(owner_ix, "href='/compare/feed.xml'" as *u8) & cgg_has(client_ix, "href='/compare/feed.xml'" as *u8), ctr) 145 gv_bite("neg-control-index-leak the hidden board appears in the owner index and not in the client index" as *u8, cgg_has(owner_ix, "synthroom" as *u8), cgg_has(client_ix, "synthroom" as *u8), ctr) 146 147 // ---- request parsers, in memory ---- 148 let path: *u8 = sys_mmap(CGG_SMALL) 149 let pl: i64 = cg_req_path("GET /compare/synthroom?x=1 HTTP/1.1\r\n" as *u8, 37, path, CGG_SMALL) 150 gv_check("T30 the request path is taken up to the query string" as *u8, cgg_streq(path, "/compare/synthroom" as *u8), ctr) 151 let hv: *u8 = sys_mmap(CGG_SMALL) 152 let hl: i64 = cg_req_header("GET / HTTP/1.1\r\nX-Nishi-Session: abc123\r\n\r\n" as *u8, 43, "X-Nishi-Session:" as *u8, hv, CGG_SMALL) 153 gv_check("T31 the session header value is extracted, trimmed of leading space and CR" as *u8, cgg_streq(hv, "abc123" as *u8), ctr) 154 155 // ---- EC47: the FEED no-leak, measured on the BYTES the projector emits ---- 156 let feed: *u8 = sys_mmap(CGG_BUF) 157 var fo: i64 = 0 158 fo = cg_cat(feed, fo, "<?xml version='1.0' encoding='UTF-8'?>\n<rss version='2.0'><channel><title>fixture</title>\n" as *u8) 159 fo = cg_cat(feed, fo, "<item><title>folkgames 2026-09-16</title><link>https://nishifamily.com/compare/folkgames/</link><guid>a</guid></item>\n" as *u8) 160 fo = cg_cat(feed, fo, "<item><title>synthroom 2026-09-16</title><link>https://nishifamily.com/compare/synthroom/</link><guid>b</guid></item>\n" as *u8) 161 fo = cg_cat(feed, fo, "<item><title>members 2026-09-16</title><link>https://nishifamily.com/compare/members/</link><guid>c</guid></item>\n" as *u8) 162 fo = cg_cat(feed, fo, "</channel></rss>\n" as *u8) 163 feed[fo] = 0 as u8 164 let fdom: *u8 = sys_mmap(CGG_SMALL) 165 let fctr: *i64 = sys_mmap(2 * CGG_WORD) as *i64 166 let fdrop: *i64 = ((fctr as i64) + CGG_WORD) as *i64 167 let fowner: *u8 = sys_mmap(CGG_BUF) 168 let fol: i64 = cg_feed_project(feed, fo, CG_LVL_OPERATOR, conf, cn, fowner, CGG_BUF, fdom, CGG_SMALL, fctr, fdrop) 169 if fol >= 0 { fowner[fol] = 0 as u8 } 170 gv_check("T32 the owner's feed keeps all three items and is byte-for-byte the whole fixture" as *u8, (fol == fo) as i64 & (fctr[0] == 3) as i64 & (fdrop[0] == 0) as i64 & cgg_streq(fowner, feed), ctr) 171 let fclient: *u8 = sys_mmap(CGG_BUF) 172 let fcl: i64 = cg_feed_project(feed, fo, CG_LVL_VIEWER, conf, cn, fclient, CGG_BUF, fdom, CGG_SMALL, fctr, fdrop) 173 if fcl >= 0 { fclient[fcl] = 0 as u8 } 174 gv_check("T33 the client's feed keeps exactly the one public item and drops two" as *u8, (fcl > 0) as i64 & (fcl < fo) as i64 & (fctr[0] == 1) as i64 & (fdrop[0] == 2) as i64, ctr) 175 gv_check("T34 NOT-EVEN-LISTED in the feed: synthroom and members are ABSENT from the client bytes, folkgames present" as *u8, cgg_has(fclient, "folkgames" as *u8) & (cgg_has(fclient, "synthroom" as *u8) == 0) as i64 & (cgg_has(fclient, "members" as *u8) == 0) as i64, ctr) 176 gv_check("T35 the client's feed is still one whole document: xml head kept, channel and rss closed" as *u8, cgg_has(fclient, "<?xml version" as *u8) & cgg_has(fclient, "</channel></rss>" as *u8), ctr) 177 let fanon: *u8 = sys_mmap(CGG_BUF) 178 let fan: i64 = cg_feed_project(feed, fo, CG_LVL_PUBLIC, conf, cn, fanon, CGG_BUF, fdom, CGG_SMALL, fctr, fdrop) 179 if fan >= 0 { fanon[fan] = 0 as u8 } 180 gv_check("T36 an anonymous visitor's feed equals the client's here (both see only the public board)" as *u8, (fan == fcl) as i64 & cgg_streq(fanon, fclient), ctr) 181 // an item with no board link is served to NOBODY, the owner included -- an unattributable row is not a public row 182 let feed2: *u8 = sys_mmap(CGG_BUF) 183 var f2: i64 = 0 184 f2 = cg_cat(feed2, f2, "<rss><channel>\n<item><title>no link</title><guid>d</guid></item>\n</channel></rss>\n" as *u8) 185 feed2[f2] = 0 as u8 186 let fout2: *u8 = sys_mmap(CGG_BUF) 187 let f2l: i64 = cg_feed_project(feed2, f2, CG_LVL_OPERATOR, conf, cn, fout2, CGG_BUF, fdom, CGG_SMALL, fctr, fdrop) 188 if f2l >= 0 { fout2[f2l] = 0 as u8 } 189 gv_check("neg-control-unattributable-item an item without a board link is dropped even for the owner, and the document stays closed" as *u8, (f2l > 0) as i64 & (fctr[0] == 0) as i64 & (fdrop[0] == 1) as i64 & (cgg_has(fout2, "no link" as *u8) == 0) as i64 & cgg_has(fout2, "</channel></rss>" as *u8), ctr) 190 // a truncated feed (an item that never closes) is REFUSED, so the daemon can never serve half a feed 191 let feed3: *u8 = sys_mmap(CGG_BUF) 192 var f3: i64 = 0 193 f3 = cg_cat(feed3, f3, "<rss><channel>\n<item><title>open</title><link>https://nishifamily.com/compare/folkgames/</link>\n" as *u8) 194 feed3[f3] = 0 as u8 195 gv_check("T37 an item without its close is REFUSED (-1), never projected" as *u8, (cg_feed_project(feed3, f3, CG_LVL_OPERATOR, conf, cn, fout2, CGG_BUF, fdom, CGG_SMALL, fctr, fdrop) == (0 - 1)) as i64, ctr) 196 // a feed with no items is served whole at every level 197 let feed4: *u8 = sys_mmap(CGG_BUF) 198 var f4: i64 = 0 199 f4 = cg_cat(feed4, f4, "<?xml version='1.0'?>\n<rss><channel><title>empty</title></channel></rss>\n" as *u8) 200 feed4[f4] = 0 as u8 201 let f4l: i64 = cg_feed_project(feed4, f4, CG_LVL_PUBLIC, conf, cn, fout2, CGG_BUF, fdom, CGG_SMALL, fctr, fdrop) 202 if f4l >= 0 { fout2[f4l] = 0 as u8 } 203 gv_check("T38 a feed with no items projects to identical bytes for an anonymous visitor" as *u8, (f4l == f4) as i64 & cgg_streq(fout2, feed4) & (fctr[0] == 0) as i64 & (fdrop[0] == 0) as i64, ctr) 204 // ---- the file served under a board: a directory is its index.html, a full read is refused (2026-09-16) ---- 205 let bf: *u8 = sys_mmap(CGG_SMALL) 206 cg_board_file("" as *u8, 0, bf) 207 gv_check("T40 an empty rest serves the board's own index.html" as *u8, cgg_streq(bf, "index.html" as *u8), ctr) 208 cg_board_file("frontier" as *u8, 1, bf) 209 gv_check("T41 a rest that names a directory serves that directory's index.html (the radar at <board>/frontier)" as *u8, cgg_streq(bf, "frontier/index.html" as *u8), ctr) 210 cg_board_file("frontier/" as *u8, 1, bf) 211 gv_check("T42 a trailing slash on a directory does not double the slash" as *u8, cgg_streq(bf, "frontier/index.html" as *u8), ctr) 212 cg_board_file("api.json" as *u8, 0, bf) 213 gv_check("T43 a rest that names a file is served as itself" as *u8, cgg_streq(bf, "api.json" as *u8), ctr) 214 let bf2: *u8 = sys_mmap(CGG_SMALL) 215 cg_board_file("frontier" as *u8, 1, bf2) 216 let dir_as_self: i64 = cgg_streq(bf2, "frontier" as *u8) 217 cg_board_file("frontier" as *u8, 0, bf2) 218 gv_bite("neg-control-directory-never-read-as-a-file a directory rest is rewritten to its index while the same name as a file is not" as *u8, (dir_as_self == 0) as i64, (cgg_streq(bf2, "frontier" as *u8) == 0) as i64, ctr) 219 gv_check("T44 a read that filled the body reserve is REFUSED, never served truncated" as *u8, (cg_body_verdict(CGG_BUF, CGG_BUF) == CG_BODY_TOO_LARGE) as i64 & (cg_body_verdict(CGG_BUF + 1, CGG_BUF) == CG_BODY_TOO_LARGE) as i64, ctr) 220 gv_check("T45 a read under the reserve is served, including an empty file, and an unreadable one is missing" as *u8, (cg_body_verdict(CGG_BUF - 1, CGG_BUF) == CG_BODY_SERVE) as i64 & (cg_body_verdict(0, CGG_BUF) == CG_BODY_SERVE) as i64 & (cg_body_verdict(0 - 1, CGG_BUF) == CG_BODY_MISSING) as i64, ctr) 221 gv_kv("feed_fixture_bytes" as *u8, fo) 222 gv_kv("feed_owner_bytes" as *u8, fol) 223 gv_kv("feed_client_bytes" as *u8, fcl) 224 225 226 let rc: i64 = gv_verdict("COMPARE-GW" as *u8, ctr, "the owner sees all, a client sees a subset, and a hidden board is indistinguishable from an absent one" as *u8) 227 sys_exit(rc) 228 return rc 229}