code wiki / _hdl_build / nx_cms_blocks_gate.nx
nx_cms_blocks_gate.nx source
↩ module page · 449 lines · 19481 B
1// nx_cms_blocks_gate.nx -- CMS C11 GATE (re-runnable, evidence-driven): BLOCK EDITOR + UPLOADS at
2// the wire (the Gutenberg + media-library class). Proves: blocks render through the TEAM-AUTHORED
3// _pe_blockr (TEMPLATE_TABLE, emit12) in document order; rich blocks sanitized ON WRITE; image
4// uploads are REAL multipart (nx_multipart) and pass only when magic bytes MATCH the extension,
5// size is within the site's upload.conf cap (config DATA), and the name is traversal-proof; served
6// uploads come back BYTE-IDENTICAL with the right Content-Type; block ops are session+CSRF guarded.
7// Appends "CMSGATE row=nx_cms_blocks ... verdict=PASS|FAIL". Exit 0 iff all rows pass.
8// license_tier: ORIGINAL
9import "nx_cms_store.nx"
10import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host
11import "nx_sha256.nx"
12import "nx_syscalls.nx"
13import "_hdl_build/nx_kill_portable.nx"
14
15const KB_PORT: i64 = 8085
16const KB_SITE: *u8 = "/tmp/_cms_blocks_site"
17
18func kb_w(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
19func kb_len(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
20func kb_cat(dst: *u8, off: i64, s: *u8) -> i64 {
21 var o: i64 = off
22 var k: i64 = 0
23 while s[k] != (0 as u8) { dst[o] = s[k]; o = o + 1; k = k + 1 }
24 return o
25}
26func kb_catn(dst: *u8, off: i64, s: *u8, n: i64) -> i64 {
27 var o: i64 = off
28 var k: i64 = 0
29 while k < n { dst[o] = s[k]; o = o + 1; k = k + 1 }
30 return o
31}
32func kb_index(hay: *u8, n: i64, needle: *u8) -> i64 {
33 let nl: i64 = kb_len(needle)
34 var i: i64 = 0
35 while i + nl <= n {
36 var q: i64 = 0
37 var ok: i64 = 1
38 while q < nl { if (hay[i+q] as i64) != (needle[q] as i64) { ok = 0; q = nl } q = q + 1 }
39 if ok == 1 { return i }
40 i = i + 1
41 }
42 return 0 - 1
43}
44func kb_has(hay: *u8, n: i64, needle: *u8) -> i64 { if kb_index(hay, n, needle) >= 0 { return 1 } return 0 }
45
46func kb_http(req: *u8, rl: i64, resp: *u8, cap: i64) -> i64 {
47 let fd: i64 = sys_socket(2, 1, 0)
48 if fd < 0 { return 0 - 1 }
49 let addr: *u8 = sys_mmap(16)
50 addr[0] = 2 as u8; addr[1] = 0 as u8
51 addr[2] = ((KB_PORT >> 8) & 0xff) as u8
52 addr[3] = (KB_PORT & 0xff) as u8
53 addr[4] = 127 as u8; addr[5] = 0 as u8; addr[6] = 0 as u8; addr[7] = 1 as u8
54 var zi: i64 = 8
55 while zi < 16 { addr[zi] = 0 as u8; zi = zi + 1 }
56 if nx_connect_bounded(fd, addr, 16, NX_CONN_DEFAULT_MS) < 0 { sys_close(fd); return 0 - 1 }
57 var off: i64 = 0
58 var go: i64 = 1
59 while go == 1 {
60 go = 0
61 if off < rl {
62 let w: i64 = sys_write(fd, (req + off) as *u8, rl - off)
63 if w > 0 { off = off + w; go = 1 }
64 }
65 }
66 sys_set_socket_timeout(fd, 10)
67 var total: i64 = 0
68 go = 1
69 while go == 1 {
70 go = 0
71 if total < cap {
72 let r: i64 = sys_read(fd, (resp + total) as *u8, cap - total)
73 if r > 0 { total = total + r; go = 1 }
74 }
75 }
76 sys_close(fd)
77 return total
78}
79
80func kb_post(req: *u8, path: *u8, cookie: *u8, body: *u8) -> i64 {
81 var o: i64 = kb_cat(req, 0, "POST " as *u8)
82 o = kb_cat(req, o, path)
83 o = kb_cat(req, o, " HTTP/1.1\r\nHost: gate\r\nContent-Type: application/x-www-form-urlencoded\r\n" as *u8)
84 if (cookie[0] as i64) != 0 {
85 o = kb_cat(req, o, "Cookie: nsess=" as *u8)
86 o = kb_catn(req, o, cookie, 32)
87 o = kb_cat(req, o, "\r\n" as *u8)
88 }
89 o = kb_cat(req, o, "Content-Length: " as *u8)
90 let bl: i64 = kb_len(body)
91 let t: *u8 = sys_mmap(24)
92 var m: i64 = bl
93 var k: i64 = 0
94 if m == 0 { t[0] = 48 as u8; k = 1 }
95 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
96 var i: i64 = 0
97 while i < k { req[o] = t[k-1-i]; o = o + 1; i = i + 1 }
98 o = kb_cat(req, o, "\r\n\r\n" as *u8)
99 o = kb_cat(req, o, body)
100 return o
101}
102// multipart POST: binary-safe body of blen bytes with the gate's fixed boundary
103func kb_post_mp(req: *u8, path: *u8, cookie: *u8, body: *u8, blen: i64) -> i64 {
104 var o: i64 = kb_cat(req, 0, "POST " as *u8)
105 o = kb_cat(req, o, path)
106 o = kb_cat(req, o, " HTTP/1.1\r\nHost: gate\r\nContent-Type: multipart/form-data; boundary=----nxgate\r\n" as *u8)
107 if (cookie[0] as i64) != 0 {
108 o = kb_cat(req, o, "Cookie: nsess=" as *u8)
109 o = kb_catn(req, o, cookie, 32)
110 o = kb_cat(req, o, "\r\n" as *u8)
111 }
112 o = kb_cat(req, o, "Content-Length: " as *u8)
113 let t: *u8 = sys_mmap(24)
114 var m: i64 = blen
115 var k: i64 = 0
116 if m == 0 { t[0] = 48 as u8; k = 1 }
117 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
118 var i: i64 = 0
119 while i < k { req[o] = t[k-1-i]; o = o + 1; i = i + 1 }
120 o = kb_cat(req, o, "\r\n\r\n" as *u8)
121 o = kb_catn(req, o, body, blen)
122 return o
123}
124func kb_get(req: *u8, path: *u8, cookie: *u8) -> i64 {
125 var o: i64 = kb_cat(req, 0, "GET " as *u8)
126 o = kb_cat(req, o, path)
127 o = kb_cat(req, o, " HTTP/1.1\r\nHost: gate\r\n" as *u8)
128 if (cookie[0] as i64) != 0 {
129 o = kb_cat(req, o, "Cookie: nsess=" as *u8)
130 o = kb_catn(req, o, cookie, 32)
131 o = kb_cat(req, o, "\r\n" as *u8)
132 }
133 o = kb_cat(req, o, "\r\n" as *u8)
134 return o
135}
136
137// build a multipart body: csrf part + file part (fname, payload). returns body length.
138func kb_mp_body(out: *u8, csrf: *u8, fname: *u8, pay: *u8, plen: i64) -> i64 {
139 var o: i64 = kb_cat(out, 0, "------nxgate\r\nContent-Disposition: form-data; name=\"csrf\"\r\n\r\n" as *u8)
140 o = kb_catn(out, o, csrf, 32)
141 o = kb_cat(out, o, "\r\n------nxgate\r\nContent-Disposition: form-data; name=\"file\"; filename=\"" as *u8)
142 o = kb_cat(out, o, fname)
143 o = kb_cat(out, o, "\"\r\nContent-Type: application/octet-stream\r\n\r\n" as *u8)
144 o = kb_catn(out, o, pay, plen)
145 o = kb_cat(out, o, "\r\n------nxgate--\r\n" as *u8)
146 return o
147}
148
149func kb_row(id: i64, ok: i64, what: *u8) -> i64 {
150 kb_w("BLOCKROW " as *u8)
151 let d: *u8 = sys_mmap(8); d[0] = (48 + id/10) as u8; d[1] = (48 + id%10) as u8; d[2] = 32 as u8; d[3] = 0 as u8
152 kb_w(d)
153 if ok == 1 { kb_w("PASS " as *u8) }
154 if ok == 0 { kb_w("FAIL " as *u8) }
155 kb_w(what); kb_w("\n" as *u8)
156 return ok
157}
158
159func kb_mkdir(path: *u8, mode: i64) -> i64 {
160 let nb: *i64 = sys_mmap(8) as *i64
161 nb[0] = 258
162 return __syscall(nb[0], 0 - 100, path, mode, 0, 0, 0)
163}
164func kb_writefile(path: *u8, buf: *u8, n: i64) -> i64 {
165 let fd: i64 = sys_openat_wr(path, 0x1a4)
166 if fd < 0 { return 0 }
167 sys_write(fd, buf, n)
168 sys_close(fd)
169 return 1
170}
171
172func main() -> i64 {
173 // ---- fixture site: template WITH {{blocks_html}}, andelinwest content, small upload cap ----
174 kb_mkdir(KB_SITE, 0x1ed)
175 let tpl: *u8 = "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"utf-8\"><title>{{hero_title}}</title></head><body><h1>{{hero_title}}</h1><main>{{blocks_html}}</main></body></html>" as *u8
176 kb_writefile("/tmp/_cms_blocks_site/template.html" as *u8, tpl, kb_len(tpl))
177 cst_copy("knowledge/cms/andelinwest/content.txt" as *u8, "/tmp/_cms_blocks_site/content.txt" as *u8)
178 sys_renameat("/tmp/_cms_blocks_site/draft.txt" as *u8, "/tmp/_cms_blocks_site/draft.stale" as *u8)
179 let conf: *u8 = "max_bytes=1000\n" as *u8
180 kb_writefile("/tmp/_cms_blocks_site/upload.conf" as *u8, conf, kb_len(conf))
181 let dig: *u8 = sys_mmap(32)
182 sha256_digest("blocks-gate-pw-1" as *u8, 16, dig)
183 let hexs: *u8 = sys_mmap(80)
184 let hxc: *u8 = "0123456789abcdef" as *u8
185 var i: i64 = 0
186 while i < 32 {
187 hexs[i*2] = hxc[((dig[i] as i64) >> 4) & 15]
188 hexs[i*2+1] = hxc[(dig[i] as i64) & 15]
189 i = i + 1
190 }
191 kb_writefile("/tmp/_cms_blocks_site/admin_pw.sha256" as *u8, hexs, 64)
192
193 // ---- rebuild + boot the admin ----
194 let bpid: i64 = sys_fork()
195 if bpid == 0 {
196 let dn: i64 = sys_openat_wr("/dev/null" as *u8, 0x1a4)
197 if dn >= 0 { sys_dup3(dn, 1, 0); sys_dup3(dn, 2, 0) }
198 let bargv: *i64 = sys_mmap(32) as *i64
199 bargv[0] = "_offc/nx_sov_build_run.elf" as *u8 as i64
200 bargv[1] = "nx_cms_admin" as *u8 as i64
201 bargv[2] = 0
202 let benvp: *i64 = sys_mmap(16) as *i64
203 benvp[0] = 0
204 sys_execve("_offc/nx_sov_build_run.elf" as *u8, bargv, benvp); sys_exit(127)
205 }
206 let bst: *i64 = sys_mmap(16) as *i64
207 sys_wait4(bpid, bst, 0)
208 // CALLER-GAP FIX 2026-08-01 (seq1267 class): the runner emits buildroot/_build/<t>.sov.elf under
209 // the no-/tmp doctrine while this probed /tmp. Proven by control: _build copy 208962B, /tmp absent.
210 var bchk: i64 = sys_openat_rd("buildroot/_build/nx_cms_admin.sov.elf" as *u8)
211 var badm: *u8 = "buildroot/_build/nx_cms_admin.sov.elf" as *u8
212 if bchk < 0 { bchk = sys_openat_rd("/tmp/nx_cms_admin.sov.elf" as *u8); badm = "/tmp/nx_cms_admin.sov.elf" as *u8 }
213 if bchk < 0 { kb_w("CMSGATE row=nx_cms_blocks ADMIN-BUILD-MISSING (probed buildroot/_build AND /tmp)\n" as *u8); sys_exit(1) }
214 sys_close(bchk)
215 let pid: i64 = sys_fork()
216 if pid == 0 {
217 let lg: i64 = sys_openat_wr("/tmp/_cms_admin_blocks_gate.log" as *u8, 0x1a4)
218 if lg >= 0 { sys_dup3(lg, 1, 0); sys_dup3(lg, 2, 0) }
219 let argvv: *i64 = sys_mmap(8*6) as *i64
220 argvv[0] = badm as i64
221 argvv[1] = "8085" as *u8 as i64
222 argvv[2] = KB_SITE as i64
223 argvv[3] = "99" as *u8 as i64
224 argvv[4] = 0
225 let envp: *i64 = sys_mmap(16) as *i64
226 envp[0] = 0
227 sys_execve(badm, argvv, envp)
228 sys_exit(127)
229 }
230
231 let req: *u8 = sys_mmap(262144)
232 let resp: *u8 = sys_mmap(524288)
233 let nocookie: *u8 = sys_mmap(8)
234 nocookie[0] = 0 as u8
235 let sess: *u8 = sys_mmap(64)
236 sess[0] = 0 as u8
237 let csrf: *u8 = sys_mmap(64)
238 csrf[0] = 0 as u8
239
240 // K0: boot + login + csrf
241 var rn: i64 = 0 - 1
242 var tries: i64 = 0
243 while tries < 50 {
244 let rl0: i64 = kb_get(req, "/" as *u8, nocookie)
245 rn = kb_http(req, rl0, resp, 524287)
246 if rn > 0 { tries = 50 }
247 if rn <= 0 { sys_sleep_ms(100); tries = tries + 1 }
248 }
249 var pass: i64 = 0
250 var rows: i64 = 0
251 var ok: i64 = 0
252 var rl: i64 = kb_post(req, "/admin/login" as *u8, nocookie, "pw=blocks-gate-pw-1" as *u8)
253 rn = kb_http(req, rl, resp, 524287)
254 if rn > 0 { if kb_has(resp, rn, "303" as *u8) == 1 {
255 let sp: i64 = kb_index(resp, rn, "nsess=" as *u8)
256 if sp >= 0 {
257 var m2: i64 = 0
258 while m2 < 32 { sess[m2] = resp[sp+6+m2]; m2 = m2 + 1 }
259 sess[32] = 0 as u8
260 rl = kb_get(req, "/admin" as *u8, sess)
261 rn = kb_http(req, rl, resp, 524287)
262 let cp: i64 = kb_index(resp, rn, "name=\"csrf\" value=\"" as *u8)
263 if cp >= 0 {
264 var m3: i64 = 0
265 while m3 < 32 { csrf[m3] = resp[cp+19+m3]; m3 = m3 + 1 }
266 csrf[32] = 0 as u8
267 ok = 1
268 }
269 }
270 } }
271 rows = rows + 1; pass = pass + kb_row(0, ok, "boot + login + csrf" as *u8)
272
273 // K1: blocks editor reachable (session)
274 rl = kb_get(req, "/admin/blocks" as *u8, sess)
275 rn = kb_http(req, rl, resp, 524287)
276 ok = 0
277 if rn > 0 { if kb_has(resp, rn, "Block Editor" as *u8) == 1 { ok = 1 } }
278 rows = rows + 1; pass = pass + kb_row(1, ok, "block editor page (session)" as *u8)
279
280 // K2: add h2 block -> live page renders <h2> via the TEAM renderer
281 let body2: *u8 = sys_mmap(4096)
282 var b2: i64 = kb_cat(body2, 0, "csrf=" as *u8)
283 b2 = kb_catn(body2, b2, csrf, 32)
284 b2 = kb_cat(body2, b2, "&op=add&kind=2&payload=Our+Services" as *u8)
285 body2[b2] = 0 as u8
286 rl = kb_post(req, "/admin/blocks" as *u8, sess, body2)
287 rn = kb_http(req, rl, resp, 524287)
288 ok = 0
289 if rn > 0 { if kb_has(resp, rn, "303" as *u8) == 1 {
290 let rl2: i64 = kb_get(req, "/" as *u8, nocookie)
291 let rn2: i64 = kb_http(req, rl2, resp, 524287)
292 if rn2 > 0 { if kb_has(resp, rn2, "<h2>Our Services</h2>" as *u8) == 1 { ok = 1 } }
293 } }
294 rows = rows + 1; pass = pass + kb_row(2, ok, "h2 block live via team renderer" as *u8)
295
296 // K3: rich block sanitized on write (script gone, <b> kept)
297 let body3: *u8 = sys_mmap(4096)
298 var b3: i64 = kb_cat(body3, 0, "csrf=" as *u8)
299 b3 = kb_catn(body3, b3, csrf, 32)
300 b3 = kb_cat(body3, b3, "&op=add&kind=1&payload=%3Cscript%3Ealert%283%29%3C%2Fscript%3E%3Cb%3Ebold+stays%3C%2Fb%3E" as *u8)
301 body3[b3] = 0 as u8
302 rl = kb_post(req, "/admin/blocks" as *u8, sess, body3)
303 rn = kb_http(req, rl, resp, 524287)
304 ok = 0
305 if rn > 0 { if kb_has(resp, rn, "303" as *u8) == 1 {
306 let rl3: i64 = kb_get(req, "/" as *u8, nocookie)
307 let rn3: i64 = kb_http(req, rl3, resp, 524287)
308 if rn3 > 0 { if kb_has(resp, rn3, "<b>bold stays</b>" as *u8) == 1 { if kb_has(resp, rn3, "<script" as *u8) == 0 { ok = 1 } } }
309 } }
310 rows = rows + 1; pass = pass + kb_row(3, ok, "rich block sanitized on write" as *u8)
311
312 // K4: REAL multipart upload (valid PNG magic) -> served back byte-identical + image/png
313 let png: *u8 = sys_mmap(256)
314 png[0] = 0x89 as u8; png[1] = 0x50 as u8; png[2] = 0x4e as u8; png[3] = 0x47 as u8
315 var pi2: i64 = 4
316 while pi2 < 100 { png[pi2] = ((pi2 * 7 + 3) & 0xff) as u8; pi2 = pi2 + 1 }
317 let mpb: *u8 = sys_mmap(8192)
318 var mpn: i64 = kb_mp_body(mpb, csrf, "logo.png" as *u8, png, 100)
319 rl = kb_post_mp(req, "/admin/upload" as *u8, sess, mpb, mpn)
320 rn = kb_http(req, rl, resp, 524287)
321 ok = 0
322 if rn > 0 { if kb_has(resp, rn, "303" as *u8) == 1 {
323 let rl4: i64 = kb_get(req, "/uploads/logo.png" as *u8, nocookie)
324 let rn4: i64 = kb_http(req, rl4, resp, 524287)
325 if rn4 > 0 { if kb_has(resp, rn4, "200 OK" as *u8) == 1 { if kb_has(resp, rn4, "image/png" as *u8) == 1 {
326 let bs: i64 = kb_index(resp, rn4, "\r\n\r\n" as *u8)
327 if bs >= 0 { if rn4 - bs - 4 == 100 {
328 var eq: i64 = 1
329 var q4: i64 = 0
330 while q4 < 100 { if resp[bs+4+q4] != png[q4] { eq = 0; q4 = 100 } q4 = q4 + 1 }
331 if eq == 1 { ok = 1 }
332 } }
333 } } }
334 } }
335 rows = rows + 1; pass = pass + kb_row(4, ok, "multipart upload + byte-identical serve" as *u8)
336
337 // K5: fake .png (bad magic) refused 400, nothing served
338 let fake: *u8 = sys_mmap(128)
339 var fi: i64 = 0
340 while fi < 50 { fake[fi] = (65 + (fi % 20)) as u8; fi = fi + 1 }
341 mpn = kb_mp_body(mpb, csrf, "evil.png" as *u8, fake, 50)
342 rl = kb_post_mp(req, "/admin/upload" as *u8, sess, mpb, mpn)
343 rn = kb_http(req, rl, resp, 524287)
344 ok = 0
345 if rn > 0 { if kb_has(resp, rn, "400" as *u8) == 1 {
346 let rl5: i64 = kb_get(req, "/uploads/evil.png" as *u8, nocookie)
347 let rn5: i64 = kb_http(req, rl5, resp, 524287)
348 if rn5 > 0 { if kb_has(resp, rn5, "404" as *u8) == 1 { ok = 1 } }
349 } }
350 rows = rows + 1; pass = pass + kb_row(5, ok, "magic-mismatch refused, nothing stored" as *u8)
351
352 // K6: oversize (valid magic, 1500B > conf 1000) refused 413
353 let big: *u8 = sys_mmap(2048)
354 big[0] = 0x89 as u8; big[1] = 0x50 as u8; big[2] = 0x4e as u8; big[3] = 0x47 as u8
355 var gi: i64 = 4
356 while gi < 1500 { big[gi] = (gi & 0xff) as u8; gi = gi + 1 }
357 mpn = kb_mp_body(mpb, csrf, "big.png" as *u8, big, 1500)
358 rl = kb_post_mp(req, "/admin/upload" as *u8, sess, mpb, mpn)
359 rn = kb_http(req, rl, resp, 524287)
360 ok = 0
361 if rn > 0 { if kb_has(resp, rn, "413" as *u8) == 1 { ok = 1 } }
362 rows = rows + 1; pass = pass + kb_row(6, ok, "size cap from upload.conf enforced 413" as *u8)
363
364 // K7: traversal filename refused 400
365 mpn = kb_mp_body(mpb, csrf, "../x.png" as *u8, png, 100)
366 rl = kb_post_mp(req, "/admin/upload" as *u8, sess, mpb, mpn)
367 rn = kb_http(req, rl, resp, 524287)
368 ok = 0
369 if rn > 0 { if kb_has(resp, rn, "400" as *u8) == 1 { ok = 1 } }
370 rows = rows + 1; pass = pass + kb_row(7, ok, "traversal filename refused 400" as *u8)
371
372 // K8: img block (src|alt) renders attr-escaped via the team renderer
373 let body8: *u8 = sys_mmap(4096)
374 var b8: i64 = kb_cat(body8, 0, "csrf=" as *u8)
375 b8 = kb_catn(body8, b8, csrf, 32)
376 b8 = kb_cat(body8, b8, "&op=add&kind=5&payload=%2Fuploads%2Flogo.png%7CTeam+logo" as *u8)
377 body8[b8] = 0 as u8
378 rl = kb_post(req, "/admin/blocks" as *u8, sess, body8)
379 rn = kb_http(req, rl, resp, 524287)
380 ok = 0
381 if rn > 0 { if kb_has(resp, rn, "303" as *u8) == 1 {
382 let rl8: i64 = kb_get(req, "/" as *u8, nocookie)
383 let rn8: i64 = kb_http(req, rl8, resp, 524287)
384 if rn8 > 0 { if kb_has(resp, rn8, "<img src=\"/uploads/logo.png\" alt=\"Team logo\">" as *u8) == 1 { ok = 1 } }
385 } }
386 rows = rows + 1; pass = pass + kb_row(8, ok, "img block renders src|alt" as *u8)
387
388 // K9: delete block 0 -> h2 gone, img remains (order + targeting proven)
389 let body9: *u8 = sys_mmap(4096)
390 var b9: i64 = kb_cat(body9, 0, "csrf=" as *u8)
391 b9 = kb_catn(body9, b9, csrf, 32)
392 b9 = kb_cat(body9, b9, "&op=del&idx=0" as *u8)
393 body9[b9] = 0 as u8
394 rl = kb_post(req, "/admin/blocks" as *u8, sess, body9)
395 rn = kb_http(req, rl, resp, 524287)
396 ok = 0
397 if rn > 0 { if kb_has(resp, rn, "303" as *u8) == 1 {
398 let rl9: i64 = kb_get(req, "/" as *u8, nocookie)
399 let rn9: i64 = kb_http(req, rl9, resp, 524287)
400 if rn9 > 0 { if kb_has(resp, rn9, "<h2>Our Services</h2>" as *u8) == 0 { if kb_has(resp, rn9, "Team logo" as *u8) == 1 { ok = 1 } } }
401 } }
402 rows = rows + 1; pass = pass + kb_row(9, ok, "delete targets block 0, others intact" as *u8)
403
404 // K10: unauth block op refused
405 rl = kb_post(req, "/admin/blocks" as *u8, nocookie, "csrf=00000000000000000000000000000000&op=add&kind=2&payload=PWNED" as *u8)
406 rn = kb_http(req, rl, resp, 524287)
407 ok = 0
408 if rn > 0 { if kb_has(resp, rn, "403" as *u8) == 1 { ok = 1 } }
409 rows = rows + 1; pass = pass + kb_row(10, ok, "unauth block op refused 403" as *u8)
410
411 // ---- teardown ----
412 nxk_kill(pid, 9)
413 let stbuf: *i64 = sys_mmap(16) as *i64
414 sys_wait4(pid, stbuf, 0)
415
416 var verdict: *u8 = "FAIL" as *u8
417 if pass == rows { verdict = "PASS" as *u8 }
418 let line: *u8 = sys_mmap(512)
419 var lo: i64 = kb_cat(line, 0, "CMSGATE row=nx_cms_blocks rows=" as *u8)
420 let t2: *u8 = sys_mmap(24)
421 var mm: i64 = rows
422 var kk: i64 = 0
423 if mm == 0 { t2[0] = 48 as u8; kk = 1 }
424 while mm > 0 { t2[kk] = (48 + (mm % 10)) as u8; mm = mm / 10; kk = kk + 1 }
425 var ii: i64 = 0
426 while ii < kk { line[lo] = t2[kk-1-ii]; lo = lo + 1; ii = ii + 1 }
427 lo = kb_cat(line, lo, " pass=" as *u8)
428 mm = pass; kk = 0
429 if mm == 0 { t2[0] = 48 as u8; kk = 1 }
430 while mm > 0 { t2[kk] = (48 + (mm % 10)) as u8; mm = mm / 10; kk = kk + 1 }
431 ii = 0
432 while ii < kk { line[lo] = t2[kk-1-ii]; lo = lo + 1; ii = ii + 1 }
433 lo = kb_cat(line, lo, " verdict=" as *u8)
434 lo = kb_cat(line, lo, verdict)
435 lo = kb_cat(line, lo, " epoch=" as *u8)
436 mm = sys_now_realtime_sec(); kk = 0
437 if mm == 0 { t2[0] = 48 as u8; kk = 1 }
438 while mm > 0 { t2[kk] = (48 + (mm % 10)) as u8; mm = mm / 10; kk = kk + 1 }
439 ii = 0
440 while ii < kk { line[lo] = t2[kk-1-ii]; lo = lo + 1; ii = ii + 1 }
441 line[lo] = 10 as u8
442 lo = lo + 1
443 sys_write(1, line, lo)
444 let gf: i64 = sys_openat_append("knowledge/status/cms_gate.log" as *u8, 0x1a4)
445 if gf >= 0 { sys_write(gf, line, lo); sys_close(gf) }
446 if pass == rows { sys_exit(0) }
447 sys_exit(1)
448 return 1
449}