code wiki / _hdl_build / nx_hr_visitor.nx

nx_hr_visitor.nx source

↩ module page · 65 lines · 3830 B

1// nx_hr_visitor.nx -- NISHI HR, visitor module (S-class HR rung 3): the "front desk" -- WELCOME the visitors we 2// want business from, and BLOCK/kick the ones we want gone, by BEHAVIOR (incl. bots). Unlike the team/user side 3// (deny-by-default), the public site is ALLOW-by-default (NEUTRAL) -- visitors are welcome until behavior says 4// otherwise. Verdicts: 0=NEUTRAL (serve normally), 1=WELCOME (a wanted/business visitor -> VIP path), 2=BLOCK 5// (hostile/abusive/bot -> 403/drop). PRIORITY (security wins): blocklist > abusive-rate > bot-UA > welcomelist > 6// neutral -- so a WELCOMED visitor who turns abusive is still BLOCKED. Thresholds are DATA-DRIVEN (CLAUDE.md #11). 7// HONEST: UA bot-detection is a first-tier heuristic (declared crawlers + lazy clients); the RATE limb catches 8// UA-spoofing abuse; deeper signals (JA3, challenge, per-path anomaly) are later rungs. license_tier: ORIGINAL 9import "nx_hr.nx" // hr_read / hr_line_end / hr_field_end / hr_eqrange (reused) 10import "nx_syscalls.nx" 11const HV_MAGIC_262144: i64 = 262144 12 13const HV_NEUTRAL: i64 = 0 14const HV_WELCOME: i64 = 1 15const HV_BLOCK: i64 = 2 16const HV_BLOCK_RPM: i64 = 600 // requests/minute over the window above which a visitor is "abusive" (config, not magic) 17 18// substring: needle in hay (both NUL-terminated)? 1/0. 19func hv_contains(hay: *u8, needle: *u8) -> i64 { 20 var hn: i64=0; while hay[hn]!=(0 as u8){hn=hn+1} 21 var nn: i64=0; while needle[nn]!=(0 as u8){nn=nn+1} 22 if nn==0 { return 0 } 23 var i: i64=0 24 while i + nn <= hn { var j: i64=0; var m: i64=1; while j<nn { if hay[i+j]!=needle[j] {m=0;j=nn} else {j=j+1} } if m==1 { return 1 } i=i+1 } 25 return 0 26} 27// first-tier bot signal: the user-agent declares/looks automated. (Honest heuristic; the rate limb backs it up.) 28func hv_is_bot(ua: *u8) -> i64 { 29 if hv_contains(ua, "bot" as *u8)==1 { return 1 } 30 if hv_contains(ua, "Bot" as *u8)==1 { return 1 } 31 if hv_contains(ua, "crawl" as *u8)==1 { return 1 } 32 if hv_contains(ua, "spider" as *u8)==1 { return 1 } 33 if hv_contains(ua, "scrapy" as *u8)==1 { return 1 } 34 if hv_contains(ua, "curl" as *u8)==1 { return 1 } 35 if hv_contains(ua, "wget" as *u8)==1 { return 1 } 36 if hv_contains(ua, "python" as *u8)==1 { return 1 } 37 if hv_contains(ua, "Go-http" as *u8)==1 { return 1 } 38 return 0 39} 40// behavior: is the request rate abusive? req_count over window_sec, projected to req/min, vs HV_BLOCK_RPM. 41func hv_rate_abusive(req_count: i64, window_sec: i64) -> i64 { 42 if window_sec <= 0 { return 0 } 43 if (req_count * 60) > (HV_BLOCK_RPM * window_sec) { return 1 } 44 return 0 45} 46// is `id` listed in `path` (one id per line, exact field0 match)? -- the manual blocklist / welcomelist. 47func hv_listed(path: *u8, id: *u8, idlen: i64) -> i64 { 48 let buf: *u8 = sys_mmap(HV_MAGIC_262144); let n: i64 = hr_read(path, buf, HV_MAGIC_262144) 49 var i: i64 = 0 50 while i < n { 51 let le: i64 = hr_line_end(buf, i, n) 52 let f0e: i64 = hr_field_end(buf, i, le) 53 if hr_eqrange(buf, i, f0e, id, idlen) == 1 { return 1 } 54 i = le + 1 55 } 56 return 0 57} 58// THE FRONT-DESK DECISION. Security-priority order; returns HV_NEUTRAL / HV_WELCOME / HV_BLOCK. 59func hv_classify(id: *u8, idlen: i64, req_count: i64, window_sec: i64, ua: *u8, block_path: *u8, welcome_path: *u8) -> i64 { 60 if hv_listed(block_path, id, idlen) == 1 { return HV_BLOCK } // explicitly kicked 61 if hv_rate_abusive(req_count, window_sec) == 1 { return HV_BLOCK } // abusive flood (catches UA-spoofers too) 62 if hv_is_bot(ua) == 1 { return HV_BLOCK } // declared/obvious bot 63 if hv_listed(welcome_path, id, idlen) == 1 { return HV_WELCOME } // a wanted/business visitor 64 return HV_NEUTRAL // allow-by-default (public site) 65}