code wiki / _hdl_build / nx_cleanwatch_serve.nx

nx_cleanwatch_serve.nx source

↩ module page · 189 lines · 14079 B

1// nx_cleanwatch_serve.nx -- HANDS-ON, REAL: the operator enters ANY site; we fetch it over our own sovereign TLS, 2// harvest its ACTUAL resource URLs, and run nx_web_filter over them -- showing the real ad/tracker/miner/popunder 3// requests THAT page makes that we'd block. Real evidence on real sites, not a canned list. Bind 0.0.0.0 -> WSL2 4// forwards Windows localhost (same path as nx_browser_view_serve). Run from nxc2 root (certdata is relative). 5// Honest scope: static fetch shows resources in the delivered HTML; URLs the page builds via JS at runtime need 6// the JS-exec sniffer layer (noted in the report). Run: nx_cleanwatch_serve [port] license_tier: ORIGINAL 7import "nx_syscalls.nx" 8import "nx_http_server.nx" 9import "nx_x509_trust_store.nx" 10import "nx_trust_store_load_from_certdata.nx" 11import "nx_https_fetch_follow.nx" 12import "nx_media_signal.nx" 13import "nx_web_filter.nx" 14import "nx_video_sniff.nx" // vs_extract_scripts + (transitively) js_run_source_keep / js_pending_url 15const K_MAGIC_1048576: i64 = 1048576 16const K_MAGIC_4194304: i64 = 4194304 17const K_MAGIC_262144: i64 = 262144 18const K_MAGIC_4096: i64 = 4096 19const K_MAGIC_4095: i64 = 4095 20const K_MAGIC_8100: i64 = 8100 21const K_MAGIC_16384: i64 = 16384 22const K_MAGIC_65536: i64 = 65536 23 24func cw_slen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n } 25func cw_put(dst: *u8, off: i64, s: *u8) -> i64 { var i: i64=0; while s[i]!=(0 as u8){dst[off]=s[i];off=off+1;i=i+1} return off } 26func cw_putn(dst: *u8, off: i64, v: i64) -> i64 { var m: i64=v; if m==0 { dst[off]=48 as u8; return off+1 } let t: *u8=sys_mmap(24); var k: i64=0; while m>0 { t[k]=(48+(m%10)) as u8; m=m/10; k=k+1 } var j: i64=0; while j<k { dst[off]=t[k-1-j]; off=off+1; j=j+1 } return off } 27func cw_datoi(s: *u8) -> i64 { var v: i64=0; var i: i64=0; while (s[i]&0xff)>=48 { if (s[i]&0xff)>57 { i=i } else { v=v*10+((s[i]&0xff)-48); i=i+1 } if (s[i]&0xff)<48 { i=i } } return v } 28func cw_hexval(c: i64) -> i64 { if c>=48 { if c<=57 { return c-48 } } if c>=97 { if c<=102 { return c-87 } } if c>=65 { if c<=70 { return c-55 } } return 0 } 29func cw_dstarts(p: *u8, plen: i64, pre: *u8) -> i64 { var i: i64=0; while pre[i]!=(0 as u8) { if i>=plen { return 0 } if (p[i]&0xff)!=(pre[i]&0xff) { return 0 } i=i+1 } return 1 } 30// HTML-escape s into dst (prevent the displayed URL from injecting markup) 31func cw_esc(dst: *u8, off: i64, s: *u8, sl: i64) -> i64 { 32 var i: i64=0 33 while i<sl { let c: i64=s[i]&0xff 34 if c==60 { off=cw_put(dst,off,"&lt;" as *u8) } else { if c==62 { off=cw_put(dst,off,"&gt;" as *u8) } else { if c==38 { off=cw_put(dst,off,"&amp;" as *u8) } else { if c==34 { off=cw_put(dst,off,"&quot;" as *u8) } else { dst[off]=c as u8; off=off+1 } } } } 35 i=i+1 } 36 return off 37} 38// parse ?url= (param-boundary aware) + percent-decode into out; returns length 39func cw_query_url(q: *u8, qlen: i64, out: *u8, cap: i64) -> i64 { 40 var start: i64 = 0-1; var si: i64=0 41 while si<qlen { var isp: i64=0; if si==0 { isp=1 } else { let pc: i64=q[si-1]&0xff; if pc==38 { isp=1 } if pc==63 { isp=1 } } 42 if isp==1 { if (si+4)<=qlen { if (q[si]&0xff)==117 { if (q[si+1]&0xff)==114 { if (q[si+2]&0xff)==108 { if (q[si+3]&0xff)==61 { start=si; si=qlen } } } } } } si=si+1 } 43 if start<0 { out[0]=0 as u8; return 0 } 44 var i: i64=start+4; var o: i64=0; var go: i64=1 45 while go==1 { if i>=qlen { go=0 } else { let c: i64=q[i]&0xff 46 if c==38 { go=0 } else { if c==37 { if (i+2)<qlen { let hi: i64=cw_hexval(q[i+1]&0xff); let lo: i64=cw_hexval(q[i+2]&0xff); if o<(cap-1) { out[o]=(hi*16+lo) as u8; o=o+1 } i=i+3 } else { i=i+1 } } else { if c==43 { if o<(cap-1){out[o]=32 as u8;o=o+1} i=i+1 } else { if o<(cap-1){out[o]=c as u8;o=o+1} i=i+1 } } } } } 47 out[o]=0 as u8; return o 48} 49// is byte a URL-terminator? 50func cw_delim(c: i64) -> i64 { if c==34{return 1} if c==39{return 1} if c==32{return 1} if c==60{return 1} if c==62{return 1} if c==41{return 1} if c==125{return 1} if c==93{return 1} if c==92{return 1} if c==10{return 1} if c==9{return 1} if c==13{return 1} return 0 } 51func cw_contains(hay: *u8, hl: i64, needle: *u8, nl: i64) -> i64 { if nl==0 {return 1} if nl>hl {return 0} var i: i64=0; let last: i64=hl-nl; while i<=last { var j: i64=0; var m: i64=1; while j<nl { if (hay[i+j]&0xff)!=(needle[j]&0xff){m=0;j=nl} else {j=j+1} } if m==1 {return 1} i=i+1 } return 0 } 52 53func send_html(cfd: i64, body: *u8, blen: i64) -> i64 { 54 let resp: *u8 = sys_mmap(blen + 512) 55 var o: i64 = cw_put(resp, 0, "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: " as *u8) 56 o = cw_putn(resp, o, blen); o = cw_put(resp, o, "\r\nConnection: close\r\n\r\n" as *u8) 57 var i: i64=0; while i<blen { resp[o]=body[i]; o=o+1; i=i+1 } 58 return nx_http_server_send_response(cfd, resp, o) 59} 60func cw_head(page: *u8, o: i64) -> i64 { 61 return cw_put(page, o, "<!doctype html><html><head><meta charset=utf-8><title>Nishi Clean-Watch</title><style>body{font-family:system-ui,sans-serif;max-width:900px;margin:2rem auto;padding:0 1rem;background:#0f1115;color:#e8e8ea;line-height:1.5}h1{margin:0 0 .2rem}.sub{color:#9aa;font-size:.92rem}.card{background:#171a21;border:1px solid #2a2f3a;border-radius:10px;padding:1rem 1.2rem;margin:1.2rem 0}ul{list-style:none;padding:0}li{padding:.25rem 0;border-bottom:1px solid #21252e;font-size:.9rem}code{color:#bcd;word-break:break-all}.blk{color:#ff6b6b}.ok{color:#51cf66}.cat{color:#889;font-size:.82rem}input{background:#0c0e12;color:#eee;border:1px solid #333;border-radius:6px;padding:.5rem;width:60%}button{background:#2b6cff;color:#fff;border:0;border-radius:6px;padding:.55rem 1rem;cursor:pointer}a{color:#7ab7ff}.tot{font-weight:600;margin:.6rem 0}video{width:100%;border-radius:8px;background:#000}</style></head><body>" as *u8) 62} 63 64func cw_home(page: *u8) -> i64 { 65 var o: i64 = cw_head(page, 0) 66 o = cw_put(page, o, "<h1>Nishi Clean-Watch</h1><div class=sub>Enter any site you browse to. We fetch it over our own sovereign TLS and run the <code>nx_web_filter</code> engine over its <b>actual</b> resources &mdash; showing the real ad / tracker / crypto-miner / popunder requests that page makes that we'd block to keep you safe.</div>" as *u8) 67 o = cw_put(page, o, "<div class=card><form action=/analyze><input name=url placeholder=\"https://example.com\" autofocus> <button>Check what we&#39;d block</button></form></div>" as *u8) 68 o = cw_put(page, o, "<div class=card><h2>Clean player</h2><div class=sub>Clean-watch on a legal test stream (the video, nothing around it):</div><video controls preload=metadata src=\"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4\"></video></div>" as *u8) 69 o = cw_put(page, o, "</body></html>" as *u8) 70 return o 71} 72 73func cw_analyze(cfd: i64, t: *i64, store: *TrustStore, url: *u8, ulen: i64) -> i64 { 74 let page: *u8 = sys_mmap(K_MAGIC_1048576) 75 var o: i64 = cw_head(page, 0) 76 o = cw_put(page, o, "<h1>Protection report</h1><div class=sub>for <code>" as *u8); o = cw_esc(page, o, url, ulen); o = cw_put(page, o, "</code></div>" as *u8) 77 // fetch the page over sovereign TLS 78 let cap: i64 = K_MAGIC_4194304 79 let html: *u8 = sys_mmap(cap) 80 let status: *i64 = sys_mmap(8) as *i64 81 let hn: i64 = nx_https_fetch_follow(url, store, html, cap, 6, status) 82 if hn <= 0 { 83 o = cw_put(page, o, "<div class=card><b class=blk>Fetch failed</b> (status "); o = cw_putn(page, o, status[0]); o = cw_put(page, o, ") &mdash; the site may block non-browser clients or need JS. <a href=/>&larr; try another</a></div></body></html>" as *u8) 84 return send_html(cfd, page, o) 85 } 86 // harvest every http(s):// URL, filter each, render blocked ones (deduped) 87 let seen: *u8 = sys_mmap(K_MAGIC_262144); var seen_o: i64 = 0 88 let ub: *u8 = sys_mmap(K_MAGIC_4096) 89 var total: i64 = 0; var blk: i64 = 0 90 let cat: *i64 = sys_mmap(8) as *i64 91 o = cw_put(page, o, "<div class=card><ul>" as *u8) 92 var i: i64 = 0 93 while i < hn { 94 var hit: i64 = 0 95 if cw_dstarts(((html as i64)+i) as *u8, hn-i, "https://" as *u8) == 1 { hit = 8 } 96 if hit == 0 { if cw_dstarts(((html as i64)+i) as *u8, hn-i, "http://" as *u8) == 1 { hit = 7 } } 97 if hit > 0 { 98 var k: i64 = 0; var j: i64 = i 99 var go: i64 = 1 100 while go == 1 { if j>=hn { go=0 } else { let c: i64=html[j]&0xff; if cw_delim(c)==1 { go=0 } else { if k<K_MAGIC_4095 { ub[k]=c as u8; k=k+1 } j=j+1 } } } 101 ub[k]=0 as u8 102 if k > 10 { 103 total = total + 1 104 if wf_should_block(t, ub, k, cat) == 1 { 105 if cw_contains(seen, seen_o, ub, k) == 0 { 106 if (seen_o+k+1) < K_MAGIC_262144 { var a: i64=0; while a<k { seen[seen_o]=ub[a]; seen_o=seen_o+1; a=a+1 } seen[seen_o]=10 as u8; seen_o=seen_o+1 } 107 blk = blk + 1 108 o = cw_put(page, o, "<li><b class=blk>BLOCK</b> <span class=cat>[" as *u8); o = cw_put(page, o, wf_cat_name(cat[0])); o = cw_put(page, o, "]</span> <code>" as *u8); o = cw_esc(page, o, ub, k); o = cw_put(page, o, "</code></li>" as *u8) 109 } 110 } 111 } 112 i = j 113 } else { i = i + 1 } 114 } 115 // PASS 2: JS-EXEC -- run the page's inline scripts on our engine, filter the fetch/XHR URLs they BUILD at 116 // runtime (the ones not present as literals in the static HTML). Fetches PEND (no real network); we read them. 117 let scripts: *u8 = sys_mmap(K_MAGIC_262144) 118 let slen: i64 = vs_extract_scripts(html, hn, scripts, K_MAGIC_262144) 119 if slen > 0 { 120 let outbox: *i64 = sys_mmap(16) as *i64 121 if js_run_source_keep(scripts, slen, outbox) == 0 { 122 let genv: *i64 = (outbox[1]) as *i64 123 if (genv as i64) != 0 { 124 var ji: i64 = 0 125 var jgo: i64 = 1 126 while jgo == 1 { 127 if ji >= K_MAGIC_4096 { jgo = 0 } 128 else { 129 let jul: i64 = js_pending_url(genv, ji, ub, K_MAGIC_4095) 130 if jul == 0 { jgo = 0 } 131 else { 132 total = total + 1 133 if wf_should_block(t, ub, jul, cat) == 1 { 134 if cw_contains(seen, seen_o, ub, jul) == 0 { 135 if (seen_o+jul+1) < K_MAGIC_262144 { var a2: i64=0; while a2<jul { seen[seen_o]=ub[a2]; seen_o=seen_o+1; a2=a2+1 } seen[seen_o]=10 as u8; seen_o=seen_o+1 } 136 blk = blk + 1 137 o = cw_put(page, o, "<li><b class=blk>BLOCK</b> <span class=cat>[js&middot;" as *u8); o = cw_put(page, o, wf_cat_name(cat[0])); o = cw_put(page, o, "]</span> <code>" as *u8); o = cw_esc(page, o, ub, jul); o = cw_put(page, o, "</code></li>" as *u8) 138 } 139 } 140 ji = ji + 1 141 } 142 } 143 } 144 } 145 } 146 } 147 o = cw_put(page, o, "</ul>" as *u8) 148 if blk == 0 { o = cw_put(page, o, "<div class=tot><b class=ok>No known malicious/ad requests found</b> in the delivered HTML.</div>" as *u8) } 149 else { o = cw_put(page, o, "<div class=tot>Would <span class=blk>BLOCK "); o = cw_putn(page, o, blk); o = cw_put(page, o, "</span> requests (of " as *u8); o = cw_putn(page, o, total); o = cw_put(page, o, " external URLs scanned) to keep you safe.</div>" as *u8) } 150 o = cw_put(page, o, "<div class=sub>Fetched " as *u8); o = cw_putn(page, o, hn); o = cw_put(page, o, " bytes. Note: this scans resources in the delivered HTML; URLs the page builds via JavaScript at runtime need the JS-exec sniffer layer (also ours). <a href=/>&larr; test another site</a></div></div></body></html>" as *u8) 151 return send_html(cfd, page, o) 152} 153 154func main(argc: i64, argv: *i64) -> i64 { 155 var port: i64 = K_MAGIC_8100 156 if argc >= 2 { port = cw_datoi(argv[1] as *u8) } 157 let r: i64 = nx_trust_store_load_from_certdata("data/mozilla_certdata.txt" as *u8, 512, K_MAGIC_4194304) 158 if r <= 0 { sys_write(2, "CLEANWATCH certdata load failed (run from nxc2 root)\n" as *u8, 52); sys_exit(1); return 1 } 159 let store: *TrustStore = r as *TrustStore 160 let t: *i64 = wf_new(); wf_seed(t) 161 let addr: *u8 = sys_mmap(16) 162 nx_http_server_addr_any(addr, port) 163 let vb: *i64 = sys_mmap(8) as *i64 164 let lfd: i64 = nx_http_server_listen(addr, 64, vb) 165 if lfd < 0 { sys_write(2, "CLEANWATCH listen failed\n" as *u8, 25); sys_exit(1); return 1 } 166 let banner: *u8 = sys_mmap(96); var bo: i64 = cw_put(banner, 0, "nx_cleanwatch_serve: http://localhost:" as *u8); bo = cw_putn(banner, bo, port); bo = cw_put(banner, bo, "/\n" as *u8); sys_write(1, banner, bo) 167 var running: i64 = 1 168 while running == 1 { 169 let cfd: i64 = nx_http_server_accept_one(lfd, vb) 170 if cfd >= 0 { 171 let req: *u8 = sys_mmap(K_MAGIC_16384) 172 let om: *i64=sys_mmap(8) as *i64; let opo: *i64=sys_mmap(8) as *i64; let opl: *i64=sys_mmap(8) as *i64; let ocl: *i64=sys_mmap(8) as *i64; let obo: *i64=sys_mmap(8) as *i64; let orn: *i64=sys_mmap(8) as *i64 173 let rc: i64 = nx_http_server_read_request(cfd, req, K_MAGIC_16384, om, opo, opl, ocl, obo, orn) 174 if rc == NXS_OK { 175 let path: *u8 = ((req as i64)+opo[0]) as *u8 176 let plen: i64 = opl[0] 177 if cw_dstarts(path, plen, "/analyze" as *u8) == 1 { 178 let ub: *u8 = sys_mmap(K_MAGIC_4096) 179 let un: i64 = cw_query_url(path, plen, ub, K_MAGIC_4096) 180 if un > 0 { cw_analyze(cfd, t, store, ub, un) } 181 else { let p2: *u8 = sys_mmap(K_MAGIC_4096); let n2: i64 = cw_home(p2); send_html(cfd, p2, n2) } 182 } else { 183 let p: *u8 = sys_mmap(K_MAGIC_65536); let n: i64 = cw_home(p); send_html(cfd, p, n) 184 } 185 } else { sys_close(cfd) } 186 } 187 } 188 return 0 189}