code wiki / _hdl_build / nx_cms_api_exceed_gate.nx

nx_cms_api_exceed_gate.nx source

↩ module page · 185 lines · 10274 B

1// nx_cms_api_exceed_gate.nx -- W-RE-004 head-to-head EXCEED (rest-graphql-api, INJECTION-SAFETY axis). 2// Runs BOTH serializers on the SAME corpus of field values (benign + quote-injection): 3// OURS = cms_api_render_obj (nx_json_emit, RFC 8259 escaping) 4// INCUMBENT = naive string-concat JSON (no escaping) -- the CWE-116 failure mode (improper output 5// neutralization -> JSON injection / structure breakout). 6// A serialization is CORRECT iff the output stays well-formed: exactly the expected number of UNESCAPED 7// double-quotes (4 for one key+value object). A value containing a `"` breaks the naive concat (extra 8// unescaped quotes) but ours escapes it. Verdict COMPUTED by nx_cms_exceed. license_tier: ORIGINAL 9import "nx_cms_exceed.nx" 10import "nx_cms_api.nx" 11import "nx_syscalls.nx" 12import "nx_gate_verdict.nx" // D001 base class so nx_gate_rollup can judge this gate 13 14func ax_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 15// Same writer, arbitrary fd. ax_w is hard-wired to fd 1, which is exactly how the failure path came to 16// write only to stdout and leave the LOG tail sitting on the previous success. 17func ax_w2(fd: i64, s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(fd,s,n); return 0 } 18 19// ---- ONE EXIT, REACHABLE FROM ANY DEPTH 2026-08-07 ------------------------------------------- 20// Twin of the defect fixed in nx_access_wall_exceed_gate the same day, and left unfixed there would 21// have been the partial job: this log was APPEND-ON-SUCCESS-ONLY, so a failing run left the previous 22// SUCCESS as the last line forever, and nx_gate_rollup anchors on the LAST line. 23// ★★★★★★A LOG WRITTEN ONLY ON SUCCESS CANNOT EVER REPORT A FAILURE: ITS LAST LINE IS A STANDING PASS. 24// The success path here exits from three levels of nesting, so the contract is a HELPER rather than a 25// rewritten tail -- reachable from any depth, and it also satisfies D001 by inheriting nx_gate_verdict 26// instead of rolling its own word. The escape allow_own_verdict=yes was available and is the wrong 27// answer: it ships an UNREADABLE gate, which is the defect being fixed. 28func ax_finish(ok: i64) -> i64 { 29 let lf: i64 = sys_openat_append("knowledge/status/cms_api_exceed.log" as *u8, 0x1a4) 30 if lf >= 0 { 31 if ok == 0 { ax_w2(lf, "CMSEXCEED feature=rest-graphql-api axis=injection-safety verdict=NOT-MEASURED (gate FAILED this run; no EXCEED claim recorded)\ 32" as *u8) } 33 if ok == 1 { ax_w2(lf, "CMS-API-EXCEED verdict=GREEN\ 34" as *u8) } 35 if ok == 0 { ax_w2(lf, "CMS-API-EXCEED verdict=RED\ 36" as *u8) } 37 sys_close(lf) 38 } 39 let ctr: *i64 = gv_ctr() 40 gv_check("REST/GraphQL injection-safety EXCEED measured on the same corpus as the incumbent" as *u8, ok, ctr) 41 let rc: i64 = gv_verdict("CMS-API-EXCEED" as *u8, ctr, "REST/GraphQL API injection-safety measured ours vs incumbent on one corpus; recorded only when measured" as *u8) 42 sys_exit(rc) 43 return rc 44} 45func ax_num(v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m;sys_write(1,"-" as *u8,1)}; let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48;k=1}; while m>0{t[k]=48+(m%10);m=m/10;k=k+1}; var i: i64=0; while i<k{bb[i]=t[k-1-i];i=i+1}; sys_write(1,bb,k); return 0 } 46func ax_cat(dst: *u8, off: i64, s: *u8) -> i64 { var o: i64=off; var k: i64=0; while s[k]!=(0 as u8){dst[o]=s[k];o=o+1;k=k+1} return o } 47func ax_catnum(dst: *u8, off: i64, v: i64) -> i64 { 48 var o: i64=off; let t: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m;dst[o]=45 as u8;o=o+1}; var k: i64=0 49 if m==0 {t[0]=48 as u8;k=1}; while m>0 {t[k]=(48+(m%10)) as u8; m=m/10; k=k+1} 50 var i: i64=0; while i<k {dst[o]=t[k-1-i]; o=o+1; i=i+1} return o 51} 52func ax_row(id: i64, ok: i64, what: *u8) -> i64 { 53 ax_w("AXROW " as *u8); ax_num(id); ax_w(" " as *u8) 54 if ok==1 { ax_w("PASS " as *u8) } else { ax_w("FAIL " as *u8) } 55 ax_w(what); ax_w("\n" as *u8) 56 return ok 57} 58 59// count double-quotes (0x22) that are NOT escaped (not immediately preceded by a backslash) 60func ax_unescaped_quotes(buf: *u8, n: i64) -> i64 { 61 var c: i64 = 0 62 var i: i64 = 0 63 while i < n { 64 if (buf[i] as i64) == 0x22 { 65 if i == 0 { c = c + 1 } else { if (buf[i-1] as i64) != 0x5C { c = c + 1 } } 66 } 67 i = i + 1 68 } 69 return c 70} 71 72// incumbent: naive concat {"key":"<raw value>"} with NO escaping (CWE-116) 73func ax_naive_render(key: *u8, val: *u8, vl: i64, out: *u8) -> i64 { 74 var o: i64 = 0 75 out[o]=123 as u8; o=o+1 // { 76 out[o]=34 as u8; o=o+1 // " 77 var k: i64=0; while key[k]!=(0 as u8){out[o]=key[k];o=o+1;k=k+1} 78 out[o]=34 as u8; o=o+1; out[o]=58 as u8; o=o+1; out[o]=34 as u8; o=o+1 // ":" 79 var j: i64=0; while j<vl{out[o]=val[j];o=o+1;j=j+1} 80 out[o]=34 as u8; o=o+1; out[o]=125 as u8; o=o+1 // "} 81 return o 82} 83 84// build a one-field store @body\n<value>\n@z\nx\n (value may contain quotes) 85func ax_build_store(val: *u8, vl: i64, out: *u8) -> i64 { 86 var o: i64 = ax_cat(out, 0, "@body\n" as *u8) 87 var j: i64=0; while j<vl { out[o]=val[j]; o=o+1; j=j+1 } 88 o = ax_cat(out, o, "\n@z\nx\n" as *u8) 89 return o 90} 91 92func main() -> i64 { 93 var pass: i64 = 0 94 var rows: i64 = 0 95 96 // corpus: parallel arrays of (value ptr, value len). benign first, then quote-injection. 97 let vp: *i64 = sys_mmap(8 * 16) as *i64 98 let vl: *i64 = sys_mmap(8 * 16) as *i64 99 var nc: i64 = 0 100 let b0: *u8 = "Hello world" as *u8; vp[nc]=b0 as i64; vl[nc]=11; nc=nc+1 101 let b1: *u8 = "estate planning" as *u8; vp[nc]=b1 as i64; vl[nc]=15; nc=nc+1 102 // injections (contain unescaped quotes that break naive concat) 103 let j0: *u8 = "x\",\"role\":\"admin" as *u8; vp[nc]=j0 as i64 104 var l0: i64=0; while j0[l0]!=(0 as u8){l0=l0+1}; vl[nc]=l0; nc=nc+1 105 let j1: *u8 = "a\"b" as *u8; vp[nc]=j1 as i64 106 var l1: i64=0; while j1[l1]!=(0 as u8){l1=l1+1}; vl[nc]=l1; nc=nc+1 107 let j2: *u8 = "\"" as *u8; vp[nc]=j2 as i64; vl[nc]=1; nc=nc+1 108 109 let names: *i64 = sys_mmap(32) as *i64 110 names[0] = "body" as *u8 as i64 111 let store: *u8 = sys_mmap(4096) 112 let oout: *u8 = sys_mmap(4096) 113 let nout: *u8 = sys_mmap(4096) 114 115 var our_correct: i64 = 0 116 var inc_correct: i64 = 0 117 var n_inj: i64 = 0 118 var inj_our: i64 = 0 119 var inj_inc: i64 = 0 120 var i: i64 = 0 121 while i < nc { 122 let v: *u8 = (vp[i]) as *u8 123 let ln: i64 = vl[i] 124 let sn: i64 = ax_build_store(v, ln, store) 125 let on: i64 = cms_api_render_obj(store, sn, names, 1, oout, 4096) 126 let inn: i64 = ax_naive_render("body" as *u8, v, ln, nout) 127 var our_v: i64 = 0; if ax_unescaped_quotes(oout, on) == 4 { our_v = 1 } 128 var inc_v: i64 = 0; if ax_unescaped_quotes(nout, inn) == 4 { inc_v = 1 } 129 // ground truth: a correct serializer is ALWAYS well-formed (count 4) for ANY value 130 if our_v == 1 { our_correct = our_correct + 1 } 131 if inc_v == 1 { inc_correct = inc_correct + 1 } 132 // an "injection" value is one that contains a raw quote 133 var has_q: i64 = 0 134 var q: i64 = 0; while q < ln { if (v[q] as i64) == 0x22 { has_q = 1; q = ln } else { q = q + 1 } } 135 if has_q == 1 { 136 n_inj = n_inj + 1 137 if our_v == 1 { inj_our = inj_our + 1 } 138 if inc_v == 1 { inj_inc = inj_inc + 1 } 139 } 140 i = i + 1 141 } 142 143 let verdict: i64 = xcd_verdict(our_correct, inc_correct) 144 ax_w("HEAD-TO-HEAD rest-graphql-api injection-safety: ours=" as *u8); ax_num(our_correct); ax_w("/" as *u8); ax_num(nc) 145 ax_w(" incumbent=" as *u8); ax_num(inc_correct); ax_w("/" as *u8); ax_num(nc) 146 ax_w(" injections-safe ours=" as *u8); ax_num(inj_our); ax_w("/" as *u8); ax_num(n_inj) 147 ax_w(" incumbent=" as *u8); ax_num(inj_inc); ax_w("/" as *u8); ax_num(n_inj) 148 ax_w(" verdict=" as *u8); ax_w(xcd_vname(verdict)); ax_w("\n" as *u8) 149 150 var ok: i64 = 0; if our_correct == nc { ok = 1 } 151 rows=rows+1; pass=pass+ax_row(0, ok, "ours fully correct (well-formed JSON for every value incl injections)" as *u8) 152 ok = 0; if inc_correct < nc { ok = 1 } 153 rows=rows+1; pass=pass+ax_row(1, ok, "incumbent naive concat fails (injection breaks structure = CWE-116)" as *u8) 154 ok = 0; if verdict == XCD_AHEAD { ok = 1 } 155 rows=rows+1; pass=pass+ax_row(2, ok, "measured verdict = AHEAD" as *u8) 156 ok = 0; if inj_our == n_inj { if inj_inc == 0 { if n_inj > 0 { ok = 1 } } } 157 rows=rows+1; pass=pass+ax_row(3, ok, "injection-safety axis: ours safe on all, incumbent safe on 0" as *u8) 158 ok = 0; if xcd_referee_ok(XCD_AHEAD, our_correct, nc) == 1 { ok = 1 } 159 rows=rows+1; pass=pass+ax_row(4, ok, "referee accepts honest AHEAD claim" as *u8) 160 ok = 0; if xcd_referee_ok(XCD_AHEAD, nc - 1, nc) == 0 { ok = 1 } 161 rows=rows+1; pass=pass+ax_row(5, ok, "referee rejects overclaim (anti-false-green)" as *u8) 162 163 ax_w("CMS-API-EXCEED-GATE rows=" as *u8); ax_num(rows); ax_w(" pass=" as *u8); ax_num(pass); ax_w("\n" as *u8) 164 165 if pass == rows { 166 if verdict == XCD_AHEAD { 167 if xcd_referee_ok(XCD_AHEAD, our_correct, nc) == 1 { 168 let line: *u8 = sys_mmap(256) 169 var lo: i64 = ax_cat(line, 0, "CMSEXCEED feature=rest-graphql-api axis=injection-safety cwe=CWE-116 ours=" as *u8) 170 lo = ax_catnum(line, lo, our_correct); lo = ax_cat(line, lo, "/" as *u8); lo = ax_catnum(line, lo, nc) 171 lo = ax_cat(line, lo, " incumbent=" as *u8); lo = ax_catnum(line, lo, inc_correct); lo = ax_cat(line, lo, "/" as *u8); lo = ax_catnum(line, lo, nc) 172 lo = ax_cat(line, lo, " injections-safe=" as *u8); lo = ax_catnum(line, lo, inj_our); lo = ax_cat(line, lo, "/" as *u8); lo = ax_catnum(line, lo, n_inj) 173 lo = ax_cat(line, lo, " verdict=AHEAD\n" as *u8) 174 let gf: i64 = sys_openat_append("knowledge/status/cms_api_exceed.log" as *u8, 0x1a4) 175 if gf >= 0 { sys_write(gf, line, lo); sys_close(gf) } 176 ax_w("CMS-API-EXCEED-GATE verdict=AHEAD -- measured EXCEED recorded\n" as *u8) 177 ax_finish(1) 178 return 0 179 } 180 } 181 } 182 ax_w("CMS-API-EXCEED-GATE verdict=NOT-RECORDED (no fake-green)\n" as *u8) 183 ax_finish(0) 184 return 1 185}