code wiki / _hdl_build / nx_adnet_submit_gate.nx
nx_adnet_submit_gate.nx source
↩ module page · 133 lines · 7129 B
1// nx_adnet_submit_gate.nx -- GATE for the self-serve intake surface (nx_adnet_submit). Proves: the GET form
2// renders zero-JS with all fields; a VALID POST stages a row (read-back verified) and shows confirmation; a
3// malicious POST (external img / javascript: click / bad slug) is REJECTED, re-populates the form ESCAPED,
4// and stages NOTHING (load-bearing negative control on the live journal); reflected XSS in a field stays
5// escaped. expect_exit: 0 license_tier: ORIGINAL
6import "nx_syscalls.nx"
7import "_hdl_build/nx_adnet_submit.nx"
8import "nx_gate_verdict.nx"
9
10func fg_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
11func fg_putn(v: i64) -> i64 { var m: i64 = v; if m < 0 { fg_puts("-" as *u8); m = 0 - m } let t: *u8 = sys_mmap(24); var k: i64 = 0; 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 } let o: *u8 = sys_mmap(24); var i: i64 = 0; while i < k { o[i] = t[k - 1 - i]; i = i + 1 } sys_write(1, o, k); return 0 }
12
13func fg_has(hay: *u8, n: i64, needle: *u8) -> i64 {
14 var nl: i64 = 0
15 while needle[nl] != (0 as u8) { nl = nl + 1 }
16 if nl == 0 { return 0 }
17 var i: i64 = 0
18 while i + nl <= n {
19 var j: i64 = 0
20 var eq: i64 = 1
21 while j < nl { if hay[i + j] != needle[j] { eq = 0; break } j = j + 1 }
22 if eq == 1 { return 1 }
23 i = i + 1
24 }
25 return 0
26}
27
28func fg_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
29
30func fg_readn(path: *u8, box: *i64) -> *u8 {
31 box[0] = 0
32 return sys_read_file(path, box)
33}
34
35func main() -> i64 {
36 fg_puts("=== nx_adnet_submit_gate ===\n" as *u8)
37 var pass: i64 = 0
38 var fail: i64 = 0
39 let out: *u8 = sys_mmap(65536)
40 let subs: *u8 = "/tmp/adnet_submit_test.log" as *u8
41 let f0: i64 = sys_openat_wr(subs, 0x1A4)
42 if f0 >= 0 { sys_close(f0) }
43
44 // T1 GET form zero-JS with all fields
45 let w0: i64 = asf_form_page(out, 65536)
46 var t1: i64 = 1
47 if w0 <= 0 { t1 = 0 }
48 if fg_has(out, w0, "name=\"slug\"" as *u8) == 0 { t1 = 0 }
49 if fg_has(out, w0, "name=\"business\"" as *u8) == 0 { t1 = 0 }
50 if fg_has(out, w0, "name=\"img\"" as *u8) == 0 { t1 = 0 }
51 if fg_has(out, w0, "name=\"click\"" as *u8) == 0 { t1 = 0 }
52 if fg_has(out, w0, "name=\"section\"" as *u8) == 0 { t1 = 0 }
53 if fg_has(out, w0, "<script" as *u8) == 1 { t1 = 0 }
54 // empty-form fields must render EMPTY (value="") -- guards the ""-literal alias-pool trap that leaked a
55 // stray pooled vhost string ("andelinwest.com") into every field. slug field value must be immediately "".
56 if fg_has(out, w0, "name=\"slug\" value=\"\"" as *u8) == 0 { t1 = 0 }
57 if fg_has(out, w0, "andelinwest" as *u8) == 1 { t1 = 0 }
58 if t1 == 1 { pass = pass + 1 } else { fail = fail + 1; fg_puts("T1 FAIL form render / empty-field alias leak\n" as *u8) }
59
60 // T2 valid POST stages + confirms
61 let good: *u8 = "slug=acme-spring&business=Acme+Co&img=%2Fsynth%2Facme.png&click=https%3A%2F%2Facme.example§ion=home" as *u8
62 let w1: i64 = asf_post(good, fg_slen(good), subs, out, 65536)
63 var t2: i64 = 1
64 if w1 <= 0 { t2 = 0 }
65 if fg_has(out, w1, "Submitted for review" as *u8) == 0 { t2 = 0 }
66 if fg_has(out, w1, "acme-spring" as *u8) == 0 { t2 = 0 }
67 let box: *i64 = sys_mmap(8) as *i64
68 let back1: *u8 = fg_readn(subs, box)
69 if (back1 as i64) == 0 { t2 = 0 } else {
70 if fg_has(back1, box[0], "acme-spring" as *u8) == 0 { t2 = 0 }
71 if fg_has(back1, box[0], "Acme Co" as *u8) == 0 { t2 = 0 }
72 if fg_has(back1, box[0], "/synth/acme.png" as *u8) == 0 { t2 = 0 }
73 }
74 if t2 == 1 { pass = pass + 1 } else { fail = fail + 1; fg_puts("T2 FAIL valid submit\n" as *u8) }
75
76 // T3 external img REJECTED + nothing staged
77 let badimg: *u8 = "slug=evilimg&business=Bad&img=https%3A%2F%2Fevil.example%2Fx.png&click=https%3A%2F%2Fok.example§ion=home" as *u8
78 let w2: i64 = asf_post(badimg, fg_slen(badimg), subs, out, 65536)
79 var t3: i64 = 1
80 if fg_has(out, w2, "Rejected" as *u8) == 0 { t3 = 0 }
81 let back2: *u8 = fg_readn(subs, box)
82 if (back2 as i64) != 0 { if fg_has(back2, box[0], "evilimg" as *u8) == 1 { t3 = 0 } }
83 if t3 == 1 { pass = pass + 1 } else { fail = fail + 1; fg_puts("T3 FAIL external img not rejected\n" as *u8) }
84
85 // T4 javascript: click REJECTED + nothing staged
86 let badclick: *u8 = "slug=evilclick&business=Bad&img=%2Fsynth%2Fb.png&click=javascript%3Aalert(1)§ion=home" as *u8
87 let w3: i64 = asf_post(badclick, fg_slen(badclick), subs, out, 65536)
88 var t4: i64 = 1
89 if fg_has(out, w3, "Rejected" as *u8) == 0 { t4 = 0 }
90 let back3: *u8 = fg_readn(subs, box)
91 if (back3 as i64) != 0 { if fg_has(back3, box[0], "evilclick" as *u8) == 1 { t4 = 0 } }
92 if t4 == 1 { pass = pass + 1 } else { fail = fail + 1; fg_puts("T4 FAIL javascript click not rejected\n" as *u8) }
93
94 // T5 bad slug (uppercase/space) REJECTED
95 let badslug: *u8 = "slug=Bad+Slug&business=Co&img=%2Fsynth%2Fc.png&click=%2Fx§ion=home" as *u8
96 let w4: i64 = asf_post(badslug, fg_slen(badslug), subs, out, 65536)
97 var t5: i64 = 1
98 if fg_has(out, w4, "Rejected" as *u8) == 0 { t5 = 0 }
99 if t5 == 1 { pass = pass + 1 } else { fail = fail + 1; fg_puts("T5 FAIL bad slug not rejected\n" as *u8) }
100
101 // T6 reflected XSS in business field stays escaped in the re-populated form (rejected via external img,
102 // so the form re-renders with the submitted business value -> must be escaped, raw tag absent).
103 let xss: *u8 = "slug=okslug&business=%3Cscript%3Ealert(1)%3C%2Fscript%3E&img=https%3A%2F%2Fevil%2Fx.png&click=%2Fx§ion=home" as *u8
104 let w5b: i64 = asf_post(xss, fg_slen(xss), subs, out, 65536)
105 var t6: i64 = 1
106 if fg_has(out, w5b, "<script>" as *u8) == 0 { t6 = 0 }
107 if fg_has(out, w5b, "<script>alert" as *u8) == 1 { t6 = 0 }
108 if t6 == 1 { pass = pass + 1 } else { fail = fail + 1; fg_puts("T6 FAIL reflected xss not escaped\n" as *u8) }
109
110 fg_puts("pass=" as *u8)
111 fg_putn(pass)
112 fg_puts(" fail=" as *u8)
113 fg_putn(fail)
114 fg_puts("\n" as *u8)
115
116 let log: *u8 = sys_mmap(256)
117 var lo: i64 = 0
118 if fail == 0 { lo = ad_cat(log, lo, "ADNETSUBMIT authored=organ verdict=GREEN" as *u8) } else { lo = ad_cat(log, lo, "ADNETSUBMIT authored=organ verdict=RED" as *u8) }
119 log[lo] = 10 as u8
120 lo = lo + 1
121 let fd: i64 = sys_openat_wr("knowledge/status/adnet_submit.log" as *u8, 0x1A4)
122 if fd >= 0 { sys_write(fd, log, lo); sys_close(fd) }
123
124 // MIGRATED onto nx_gate_verdict by nx_gate_dry_apply (D001, minimal form): every check
125 // row above is untouched, so the PASS/FAIL vector cannot change; only the hand-rolled
126 // verdict emission is replaced by the ONE shared base class. Proven by nx_gate_migrate verify.
127 let ctr__dry: *i64 = gv_ctr()
128 ctr__dry[0] = pass
129 ctr__dry[1] = pass + fail
130 let rc__dry: i64 = gv_verdict("ADNET-SUBMIT-GATE" as *u8, ctr__dry, "teeth unchanged; verdict emission migrated onto the shared base class" as *u8)
131 sys_exit(rc__dry)
132 return rc__dry
133}