code wiki / (root) / nx_html_render_test.nx

nx_html_render_test.nx source

↩ module page · 202 lines · 7742 B

1// nx_html_render_test.nx -- smoke + XSS-attack-vector tests. 2// 3// Exercises every context + every escape rule + the bounds-check 4// + the known-XSS attack-string corpus. 5// 6// expect_exit: 0 7// 8// license_tier: ORIGINAL 9 10import "nx_syscalls_x86_64.nx" 11import "nx_html_render.nx" 12 13func bytes_eq(a: *u8, b: *u8, n: i64) -> i64 { 14 var i: i64 = 0 15 while i < n { 16 if a[i] != b[i] { return 0 } 17 i = i + 1 18 } 19 return 1 20} 21 22// Helper: render `escape(src, ctx)` into a fresh buffer + verify it 23// matches expected. Returns 0 on match, error-code-with-base on miss. 24func check_escape(src: *u8, src_n: i64, ctx: i64, 25 expected: *u8, expected_n: i64, 26 err_base: i64) -> i64 { 27 let buf: *u8 = sys_mmap(1024) 28 var off: i64 = 0 29 let rc: i64 = nx_html_escape(buf, &off, 1024, src, src_n, ctx) 30 if rc != NXH_OK { return err_base + 90 } 31 if off != expected_n { return err_base + 91 } 32 if bytes_eq(buf, expected, expected_n) != 1 { return err_base + 92 } 33 return 0 34} 35 36func main() -> i64 { 37 // ---- Context-enum validity gate ---- 38 if nxh_ctx_is_valid(NXH_CTX_TEXT) != 1 { return 1 } 39 if nxh_ctx_is_valid(NXH_CTX_RAW) != 1 { return 2 } 40 if nxh_ctx_is_valid(NXH_CTX_N) != 0 { return 3 } 41 if nxh_ctx_is_valid(-1) != 0 { return 4 } 42 43 if nxh_node_is_valid(NXH_NODE_ELEMENT) != 1 { return 5 } 44 if nxh_node_is_valid(NXH_NODE_N) != 0 { return 6 } 45 if nxh_verdict_is_valid(NXH_OK) != 1 { return 7 } 46 if nxh_verdict_is_valid(NXH_VERDICT_N) != 0 { return 8 } 47 48 // ---- TEXT context: < > & escape ---- 49 var rc: i64 = check_escape( 50 "hello <world> & friends" as *u8, 23, NXH_CTX_TEXT, 51 "hello &lt;world&gt; &amp; friends" as *u8, 33, 100) 52 if rc != 0 { return rc } 53 54 // ---- ATTR_DQ context: & < > " ---- 55 rc = check_escape( 56 "a&b<c>d\"e" as *u8, 9, NXH_CTX_ATTR_DQ, 57 "a&amp;b&lt;c&gt;d&quot;e" as *u8, 24, 200) 58 if rc != 0 { return rc } 59 60 // ---- ATTR_SQ context: & < > ' ---- 61 rc = check_escape( 62 "a&b'c" as *u8, 5, NXH_CTX_ATTR_SQ, 63 "a&amp;b&#x27;c" as *u8, 14, 300) 64 if rc != 0 { return rc } 65 66 // ---- URL context: %HH-encode unsafe bytes ---- 67 rc = check_escape( 68 "hello world" as *u8, 11, NXH_CTX_URL, 69 "hello%20world" as *u8, 13, 400) 70 if rc != 0 { return rc } 71 72 rc = check_escape( 73 "a<b" as *u8, 3, NXH_CTX_URL, 74 "a%3Cb" as *u8, 5, 410) 75 if rc != 0 { return rc } 76 77 // URL: safe punctuation (/ : ? = & #) passes through 78 rc = check_escape( 79 "http://x/y?a=b&c=d" as *u8, 18, NXH_CTX_URL, 80 "http://x/y?a=b&c=d" as *u8, 18, 420) 81 if rc != 0 { return rc } 82 83 // ---- RAW context: verbatim copy (caller-asserted-safe) ---- 84 rc = check_escape( 85 "<b>bold</b>" as *u8, 11, NXH_CTX_RAW, 86 "<b>bold</b>" as *u8, 11, 500) 87 if rc != 0 { return rc } 88 89 // ---- COMMENT context: rejects `--` ---- 90 let buf: *u8 = sys_mmap(256) 91 var off: i64 = 0 92 let rc_c: i64 = nx_html_escape(buf, &off, 256, 93 "ok -- bad" as *u8, 9, NXH_CTX_COMMENT) 94 if rc_c != NXH_BAD_INPUT { return 600 } 95 96 // COMMENT: clean text passes 97 off = 0 98 let rc_c2: i64 = nx_html_escape(buf, &off, 256, 99 "all good" as *u8, 8, NXH_CTX_COMMENT) 100 if rc_c2 != NXH_OK { return 610 } 101 if off != 8 { return 611 } 102 103 // ---- CDATA context: rejects `]]>` ---- 104 off = 0 105 let rc_cd: i64 = nx_html_escape(buf, &off, 256, 106 "ok ]]> bad" as *u8, 10, NXH_CTX_CDATA) 107 if rc_cd != NXH_BAD_INPUT { return 700 } 108 109 // ---- BAD_CTX path ---- 110 off = 0 111 let rc_bc: i64 = nx_html_escape(buf, &off, 256, 112 "x" as *u8, 1, 99) 113 if rc_bc != NXH_BAD_CTX { return 800 } 114 115 // ---- OOM_BUFFER path ---- 116 let small: *u8 = sys_mmap(4) 117 off = 0 118 let rc_oom: i64 = nx_html_escape(small, &off, 4, 119 "abcdefghij" as *u8, 10, NXH_CTX_TEXT) 120 if rc_oom != NXH_OOM_BUFFER { return 810 } 121 122 // ---- Known XSS attack vectors -- all must be NEUTRALIZED ---- 123 124 // 1. <script>alert(1)</script> in TEXT context 125 let xss1_src: *u8 = "<script>alert(1)</script>" as *u8 126 let xss1_exp: *u8 = "&lt;script&gt;alert(1)&lt;/script&gt;" as *u8 127 rc = check_escape(xss1_src, 25, NXH_CTX_TEXT, xss1_exp, 37, 900) 128 if rc != 0 { return rc } 129 130 // 2. attribute-breakout via " in ATTR_DQ 131 let xss2_src: *u8 = "x\" onerror=alert(1) x=\"" as *u8 132 let xss2_exp: *u8 = "x&quot; onerror=alert(1) x=&quot;" as *u8 133 rc = check_escape(xss2_src, 23, NXH_CTX_ATTR_DQ, xss2_exp, 33, 1000) 134 if rc != 0 { return rc } 135 136 // 3. javascript:alert(1) -- URL escape encodes `(` and `)` as %28 137 // %29, but the colon + alnum survive (URL context is permissive on 138 // path punctuation). Protection against javascript: scheme MUST 139 // happen at scheme-allow-list layer (queued: nx_url_scheme_ 140 // allowlist). nx_html_render's job is byte-level context escape. 141 let xss3_src: *u8 = "javascript:alert(1)" as *u8 142 let xss3_exp: *u8 = "javascript:alert%281%29" as *u8 143 rc = check_escape(xss3_src, 19, NXH_CTX_URL, xss3_exp, 23, 1100) 144 if rc != 0 { return rc } 145 146 // 4. Open-tag injection in URL value: `<svg onload=...>` byte-encoded. 147 let xss4_src: *u8 = "<svg onload=alert(1)>" as *u8 148 // < -> %3C, space -> %20, > -> %3E, ( -> %28, ) -> %29 149 let xss4_exp: *u8 = "%3Csvg%20onload=alert%281%29%3E" as *u8 150 rc = check_escape(xss4_src, 21, NXH_CTX_URL, xss4_exp, 31, 1200) 151 if rc != 0 { return rc } 152 153 // 5. Single-quote breakout in ATTR_SQ 154 let xss5_src: *u8 = "x' onerror='alert(1)" as *u8 155 let xss5_exp: *u8 = "x&#x27; onerror=&#x27;alert(1)" as *u8 156 rc = check_escape(xss5_src, 20, NXH_CTX_ATTR_SQ, xss5_exp, 30, 1300) 157 if rc != 0 { return rc } 158 159 // ---- Element-emission flow ---- 160 let ebuf: *u8 = sys_mmap(512) 161 var eoff: i64 = 0 162 if nx_html_open_tag(ebuf, &eoff, 512, "div" as *u8, 3) != NXH_OK { return 1400 } 163 if nx_html_attr(ebuf, &eoff, 512, 164 "class" as *u8, 5, 165 "card x>y" as *u8, 8) != NXH_OK { return 1401 } 166 if nx_html_close_open_tag(ebuf, &eoff, 512) != NXH_OK { return 1402 } 167 if nx_html_text(ebuf, &eoff, 512, "hello <world>" as *u8, 13) != NXH_OK { return 1403 } 168 if nx_html_end_tag(ebuf, &eoff, 512, "div" as *u8, 3) != NXH_OK { return 1404 } 169 170 // Expected: <div class="card x&gt;y">hello &lt;world&gt;</div> 171 let exp: *u8 = "<div class=\"card x&gt;y\">hello &lt;world&gt;</div>" as *u8 172 if eoff != 50 { return 1410 } 173 if bytes_eq(ebuf, exp, 50) != 1 { return 1411 } 174 175 // ---- BAD_INPUT: invalid tag name ---- 176 eoff = 0 177 if nx_html_open_tag(ebuf, &eoff, 512, "di<v" as *u8, 4) != NXH_BAD_INPUT { return 1500 } 178 179 // BAD_INPUT: attribute name with quote 180 eoff = 0 181 if nx_html_open_tag(ebuf, &eoff, 512, "div" as *u8, 3) != NXH_OK { return 1510 } 182 if nx_html_attr(ebuf, &eoff, 512, 183 "cls\"" as *u8, 4, 184 "x" as *u8, 1) != NXH_BAD_INPUT { return 1511 } 185 186 // ---- URL-attr emission ---- 187 eoff = 0 188 if nx_html_open_tag(ebuf, &eoff, 512, "a" as *u8, 1) != NXH_OK { return 1600 } 189 if nx_html_attr_url(ebuf, &eoff, 512, 190 "href" as *u8, 4, 191 "/audit?ts=1" as *u8, 11) != NXH_OK { return 1601 } 192 if nx_html_close_open_tag(ebuf, &eoff, 512) != NXH_OK { return 1602 } 193 if nx_html_text(ebuf, &eoff, 512, "open" as *u8, 4) != NXH_OK { return 1603 } 194 if nx_html_end_tag(ebuf, &eoff, 512, "a" as *u8, 1) != NXH_OK { return 1604 } 195 196 // Expected: <a href="/audit?ts=1">open</a> 197 let exp_a: *u8 = "<a href=\"/audit?ts=1\">open</a>" as *u8 198 if eoff != 30 { return 1610 } 199 if bytes_eq(ebuf, exp_a, 30) != 1 { return 1611 } 200 201 return 0 202}