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}