code wiki / _hdl_build / nx_mgmt_tools_register_gate.nx

nx_mgmt_tools_register_gate.nx source

↩ module page · 206 lines · 12353 B

1// nx_mgmt_tools_register_gate.nx -- prove POST /api/tools/register is fail-closed + idempotent. In-process 2// referee (no socket): crafts request BYTES, feeds the pure handler ma_do_tools_register. Auth is the SAME 3// ma_level_of gate as every other write route, so this focuses on the NEW logic: 4// T1 missing name -> 400 5// T2 path-escape elf basename ("../x") -> 400 invalid basename (sanitize refuses) 6// T3 no confirm=yes -> 400 7// T4 IDEMPOTENT: a name already in tool_allowlist.conf -> ALREADY-REGISTERED, file UNCHANGED (no dup row) 8// T5 elf not a real ELF under nishihost -> 400 (never register a phantom tool) 9// (The positive append is proven LIVE post-deploy -- its ELF path is the NAS-absolute nishihost dir.) 10// Runs FS under /tmp. license_tier: ORIGINAL expect_exit: 0 11import "nx_mgmt_api.nx" 12import "nx_syscalls.nx" 13 14// sized: fixture args-column scratch; matches MA_ARGSDEC_CAP order (pinned args are short strings) 15const GT_ARGCAP: i64 = 4096 16 17func w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } 18func pn(v: i64) -> i64 { 19 let b: *u8 = sys_mmap(32) as *u8 20 var x: i64 = v; if x < 0 { sys_write(1,"-" as *u8,1); x = 0 - x } 21 var i: i64 = 31 22 if x == 0 { b[i] = 48 as u8; i = i - 1 } 23 while x > 0 { b[i] = (48 + x % 10) as u8; x = x / 10; i = i - 1 } 24 sys_write(1, (b as i64 + i + 1) as *u8, 31 - i) 25 return 0 26} 27func glen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 28func gcat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8){d[o+i]=s[i]; i=i+1} return o+i } 29func gcatn(d: *u8, o: i64, s: *u8, n: i64) -> i64 { var i: i64=0; while i<n {d[o+i]=s[i]; i=i+1} return o+n } 30func gcontains(hay: *u8, n: i64, needle: *u8) -> i64 { 31 let nn: i64 = glen(needle) 32 var i: i64 = 0 33 while i + nn <= n { 34 var m: i64 = 1; var j: i64 = 0 35 while j < nn { if (hay[i+j] as i64) != (needle[j] as i64) { m = 0; j = nn } else { j = j + 1 } } 36 if m == 1 { return 1 } 37 i = i + 1 38 } 39 return 0 40} 41func mkreq(dst: *u8, line: *u8, bod: *u8) -> i64 { 42 let bn: i64 = glen(bod) 43 var o: i64 = gcat(dst, 0, line) 44 o = gcat(dst, o, " HTTP/1.1\r\nHost: x\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: " as *u8) 45 let t: *u8 = sys_mmap(24); var m: i64 = bn; var k: i64 = 0 46 if m == 0 { t[0]=48 as u8; k=1 } 47 while m > 0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 } 48 var z: i64 = 0 49 while z < k { dst[o]=t[k-1-z]; o=o+1; z=z+1 } 50 o = gcat(dst, o, "\r\n\r\n" as *u8) 51 o = gcatn(dst, o, bod, bn) 52 return o 53} 54func wfile(path: *u8, buf: *u8, n: i64) -> i64 { 55 let fd: i64 = sys_openat_wr(path, 0x1a4); if fd < 0 { return 0 - 1 } 56 sys_write(fd, buf, n); sys_close(fd); return 0 57} 58func fsize(path: *u8) -> i64 { 59 let szp: *i64 = sys_mmap(16) as *i64 60 let b: *u8 = sys_read_file(path, szp) 61 if (b as i64) == 0 { return 0 - 1 } 62 return szp[0] 63} 64 65func main() -> i64 { 66 w("=== nx_mgmt_tools_register_gate -- POST /api/tools/register, fail-closed + idempotent ===\n" as *u8) 67 sys_mkdir("/tmp/nx_treg_gt" as *u8, 0x1ed) 68 sys_chdir("/tmp/nx_treg_gt" as *u8) 69 let out: *u8 = sys_mmap(262144) 70 let req: *u8 = sys_mmap(262144) 71 var fails: i64 = 0 72 73 // seed an allowlist with one existing tool (for the idempotency test). 74 let seed: *u8 = "existingtool\t/x/y.elf\tGREEN\n" as *u8 75 wfile("tool_allowlist.conf" as *u8, seed, glen(seed)) 76 let base_sz: i64 = fsize("tool_allowlist.conf" as *u8) 77 78 // T1 missing name 79 let n1: i64 = mkreq(req, "POST /api/tools/register" as *u8, "elf=nx_x.elf&confirm=yes" as *u8) 80 let r1: i64 = ma_do_tools_register(req, n1, out) 81 var t1: i64 = 1 82 if gcontains(out, r1, "400" as *u8) == 0 { t1 = 0 } 83 if gcontains(out, r1, "missing name" as *u8) == 0 { t1 = 0 } 84 if t1 == 1 { w("T1 PASS missing name -> 400\n" as *u8) } else { fails = fails + 1; w("T1 FAIL\n" as *u8) } 85 86 // T2 path-escape elf basename 87 let n2: i64 = mkreq(req, "POST /api/tools/register" as *u8, "name=evil&elf=../etc/passwd&confirm=yes" as *u8) 88 let r2: i64 = ma_do_tools_register(req, n2, out) 89 var t2: i64 = 1 90 if gcontains(out, r2, "400" as *u8) == 0 { t2 = 0 } 91 if gcontains(out, r2, "invalid elf path" as *u8) == 0 { t2 = 0 } 92 if t2 == 1 { w("T2 PASS path-escape basename -> 400\n" as *u8) } else { fails = fails + 1; w("T2 FAIL\n" as *u8); sys_write(1, out, r2); w("\n" as *u8) } 93 94 // T3 no confirm 95 let n3: i64 = mkreq(req, "POST /api/tools/register" as *u8, "name=newtool&elf=nx_x.elf" as *u8) 96 let r3: i64 = ma_do_tools_register(req, n3, out) 97 var t3: i64 = 1 98 if gcontains(out, r3, "400" as *u8) == 0 { t3 = 0 } 99 if gcontains(out, r3, "confirm=yes" as *u8) == 0 { t3 = 0 } 100 if t3 == 1 { w("T3 PASS no-confirm -> 400\n" as *u8) } else { fails = fails + 1; w("T3 FAIL\n" as *u8); sys_write(1, out, r3); w("\n" as *u8) } 101 102 // T4 IDEMPOTENT: register a name already present -> ALREADY, allowlist file UNCHANGED 103 let n4: i64 = mkreq(req, "POST /api/tools/register" as *u8, "name=existingtool&elf=nx_x.elf&confirm=yes" as *u8) 104 let r4: i64 = ma_do_tools_register(req, n4, out) 105 var t4: i64 = 1 106 if gcontains(out, r4, "ALREADY-REGISTERED" as *u8) == 0 { t4 = 0 } 107 if fsize("tool_allowlist.conf" as *u8) != base_sz { t4 = 0 } // no dup row appended 108 if t4 == 1 { w("T4 PASS idempotent (already-registered, no dup row)\n" as *u8) } else { fails = fails + 1; w("T4 FAIL\n" as *u8); sys_write(1, out, r4); w("\n" as *u8) } 109 110 // T5 elf not a real ELF under nishihost (the NAS path is absent locally) -> 400 111 let n5: i64 = mkreq(req, "POST /api/tools/register" as *u8, "name=newtool&elf=nx_definitely_absent.elf&confirm=yes" as *u8) 112 let r5: i64 = ma_do_tools_register(req, n5, out) 113 var t5: i64 = 1 114 if gcontains(out, r5, "400" as *u8) == 0 { t5 = 0 } 115 if gcontains(out, r5, "not found or not a valid ELF" as *u8) == 0 { t5 = 0 } 116 if fsize("tool_allowlist.conf" as *u8) != base_sz { t5 = 0 } // nothing appended for a phantom elf 117 if t5 == 1 { w("T5 PASS phantom-elf -> 400, allowlist untouched\n" as *u8) } else { fails = fails + 1; w("T5 FAIL\n" as *u8); sys_write(1, out, r5); w("\n" as *u8) } 118 119 // T6 UPDATE CAN NEVER CREATE: update=yes on an unknown name -> 400, allowlist untouched 120 let n6: i64 = mkreq(req, "POST /api/tools/register" as *u8, "name=ghosttool&elf=nx_x.elf&confirm=yes&update=yes" as *u8) 121 let r6: i64 = ma_do_tools_register(req, n6, out) 122 var t6: i64 = 1 123 if gcontains(out, r6, "400" as *u8) == 0 { t6 = 0 } 124 if gcontains(out, r6, "update target not registered" as *u8) == 0 { t6 = 0 } 125 if fsize("tool_allowlist.conf" as *u8) != base_sz { t6 = 0 } 126 if t6 == 1 { w("T6 PASS update-on-unknown -> 400, allowlist untouched\n" as *u8) } else { fails = fails + 1; w("T6 FAIL\n" as *u8); sys_write(1, out, r6); w("\n" as *u8) } 127 128 // T7 UNIT: md_allow_update_row REPLACES the existing row (new elf visible, no dup, .prev banked) 129 var t7: i64 = 1 130 if md_allow_update_row("existingtool" as *u8, "/new/path.elf" as *u8, 0 as *u8, 0) != 1 { t7 = 0 } 131 let szp7: *i64 = sys_mmap(16) as *i64 132 let c7: *u8 = sys_read_file("tool_allowlist.conf" as *u8, szp7) 133 if (c7 as i64) == 0 { t7 = 0 } else { 134 if gcontains(c7, szp7[0], "/new/path.elf" as *u8) == 0 { t7 = 0 } 135 if gcontains(c7, szp7[0], "/x/y.elf" as *u8) == 1 { t7 = 0 } 136 } 137 if fsize("tool_allowlist.conf.prev" as *u8) <= 0 { t7 = 0 } 138 if t7 == 1 { w("T7 PASS update-row replaces (new elf in, old row out, .prev banked)\n" as *u8) } else { fails = fails + 1; w("T7 FAIL\n" as *u8) } 139 140 // T8 UNIT liar-killer: update-row on a missing name REFUSES, file byte-count unchanged 141 let pre8: i64 = fsize("tool_allowlist.conf" as *u8) 142 var t8: i64 = 1 143 if md_allow_update_row("neverexisted" as *u8, "/new/path2.elf" as *u8, 0 as *u8, 0) != 0 { t8 = 0 } 144 if fsize("tool_allowlist.conf" as *u8) != pre8 { t8 = 0 } 145 if t8 == 1 { w("T8 PASS update-row not-found refuses, file untouched\n" as *u8) } else { fails = fails + 1; w("T8 FAIL\n" as *u8) } 146 147 // T9 UNIT tri-state (seq722 root fix): ma_schema_has_name on empty file -> 0; seeded row -> 1; 148 // prefix and absent names -> 0 (the \t terminator is load-bearing). 149 sys_mkdir("knowledge" as *u8, 0x1ed) 150 wfile("knowledge/tool_schemas.conf" as *u8, "" as *u8, 0) 151 var t9: i64 = 1 152 if ma_schema_has_name("existingtool" as *u8) != 0 { t9 = 0 } 153 let sseed: *u8 = "othertool\tsome title\t0\t1\t0\t1\tdesc\n" as *u8 154 wfile("knowledge/tool_schemas.conf" as *u8, sseed, glen(sseed)) 155 if ma_schema_has_name("othertool" as *u8) != 1 { t9 = 0 } 156 if ma_schema_has_name("other" as *u8) != 0 { t9 = 0 } 157 if ma_schema_has_name("existingtool" as *u8) != 0 { t9 = 0 } 158 if t9 == 1 { w("T9 PASS schema-has-name tri-state (empty->0, seeded->1, prefix/absent->0)\n" as *u8) } else { fails = fails + 1; w("T9 FAIL\n" as *u8) } 159 160 // T10 UPSERT: append DECODES %20 + SCRUBS %09 (row-injection foreclosed); append-if-absent guard 161 // skips the second write byte-exact (no dup rows, curated rows safe). 162 var t10: i64 = 1 163 let tb: *u8 = "hello%20world%09x" as *u8 164 if ma_schema_append_row("existingtool" as *u8, tb, 0, glen(tb)) != 1 { t10 = 0 } 165 let szp10: *i64 = sys_mmap(16) as *i64 166 let c10: *u8 = sys_read_file("knowledge/tool_schemas.conf" as *u8, szp10) 167 if (c10 as i64) == 0 { t10 = 0 } else { 168 if gcontains(c10, szp10[0], "existingtool\thello world x\t0\t1\t0\t1\t" as *u8) == 0 { t10 = 0 } 169 if gcontains(c10, szp10[0], "%20" as *u8) == 1 { t10 = 0 } 170 } 171 if ma_schema_has_name("existingtool" as *u8) != 1 { t10 = 0 } 172 let pre10: i64 = fsize("knowledge/tool_schemas.conf" as *u8) 173 if ma_schema_has_name("existingtool" as *u8) == 0 { ma_schema_append_row("existingtool" as *u8, tb, 0, glen(tb)) } 174 if fsize("knowledge/tool_schemas.conf" as *u8) != pre10 { t10 = 0 } 175 if t10 == 1 { w("T10 PASS schema upsert (decode+scrub, append-if-absent no-dup)\n" as *u8) } else { fails = fails + 1; w("T10 FAIL\n" as *u8) } 176 177 // T11 UNIT (seq1281): md_allow_get_args returns the pinned-args column exactly; no-args row + absent name -> 0 178 let pseed: *u8 = "pinnedtool\t/x/p.elf\tGREEN\tknowledge/store alpha bravo\nplaintool\t/x/q.elf\tGREEN\n" as *u8 179 wfile("tool_allowlist.conf" as *u8, pseed, glen(pseed)) 180 var t11: i64 = 1 181 let agot: *u8 = sys_mmap(GT_ARGCAP) 182 let agn: i64 = md_allow_get_args("pinnedtool" as *u8, agot, GT_ARGCAP - 1) 183 if agn <= 0 { t11 = 0 } else { 184 if gcontains(agot, agn, "knowledge/store alpha bravo" as *u8) == 0 { t11 = 0 } 185 } 186 if md_allow_get_args("plaintool" as *u8, agot, GT_ARGCAP - 1) != 0 { t11 = 0 } 187 if md_allow_get_args("neverwas" as *u8, agot, GT_ARGCAP - 1) != 0 { t11 = 0 } 188 if t11 == 1 { w("T11 PASS get-args (pinned col exact, no-args/absent -> 0)\n" as *u8) } else { fails = fails + 1; w("T11 FAIL\n" as *u8) } 189 190 // T12 PRESERVE COMPOSE (seq1281): update WITHOUT args (via the get-args fetch the handler now does) 191 // -> pinned args survive byte-exact; an explicit clear still drops them. 192 var t12: i64 = 1 193 let pres: *u8 = sys_mmap(GT_ARGCAP) 194 let prn: i64 = md_allow_get_args("pinnedtool" as *u8, pres, GT_ARGCAP - 1) 195 if md_allow_update_row("pinnedtool" as *u8, "/x/p2.elf" as *u8, pres, prn) != 1 { t12 = 0 } 196 if md_allow_get_args("pinnedtool" as *u8, agot, GT_ARGCAP - 1) <= 0 { t12 = 0 } else { 197 if gcontains(agot, glen(agot), "knowledge/store alpha bravo" as *u8) == 0 { t12 = 0 } 198 } 199 if md_allow_update_row("pinnedtool" as *u8, "/x/p3.elf" as *u8, 0 as *u8, 0) != 1 { t12 = 0 } 200 if md_allow_get_args("pinnedtool" as *u8, agot, GT_ARGCAP - 1) != 0 { t12 = 0 } 201 if t12 == 1 { w("T12 PASS preserve-compose (omit -> pinned survive; explicit clear -> dropped)\n" as *u8) } else { fails = fails + 1; w("T12 FAIL\n" as *u8) } 202 203 if fails == 0 { w("MGMT-TOOLS-REGISTER-GATE 12/12 GREEN -- fail-closed (name/path/confirm/phantom-elf) + idempotent (no dup rows) + update verb (replace-atomic, can-never-create) + schema upsert (decode+scrub, append-if-absent) + pinned-args preserve (omit-safe, explicit-clear)\n" as *u8); sys_exit(0); return 0 } 204 w("MGMT-TOOLS-REGISTER-GATE RED fails=" as *u8); pn(fails); w("\n" as *u8) 205 sys_exit(1); return 1 206}