code wiki / (root) / nx_tool_http_backend_candidate_t314.nx

nx_tool_http_backend_candidate_t314.nx source

↩ module page · 284 lines · 13736 B

1// nx_tool_http_backend.nx -- HTTP-BACKEND execution mode for the sovereign agent tools API. A tools/call whose 2// tool name is declared in tool_backends.conf is an ALREADY-RUNNING sovereign HTTP service (search on :18456, 3// doc, ...), reached IN-PROCESS over LOOPBACK -- ONE general, data-driven mechanism, NOT a per-tool shim binary. 4// This is the second execution kind alongside fork-exec (nx_tool_exec_allow): a tool is EITHER a vetted ELF OR a 5// registered HTTP backend. SECURITY: host/port/path are fixed in the operator-curated registry; the caller supplies 6// ONLY the url-encoded param value (argv[1]) -> no SSRF, no path/header injection. FAIL-CLOSED: no row -> THB_NOROUTE 7// (the caller then falls back to fork-exec, which also fail-closes). Self-contained loopback client (only nx_syscalls; 8// sockaddr/parse-ip/read-loop replicate the proven nx_tool_run / nx_http_proxy patterns) -> minimal blast radius in 9// the crown-jewel daemon. Row (TAB): <name>\t<method>\t<host>\t<port>\t<path>\t<param> 10// license_tier: ORIGINAL 11import "nx_syscalls.nx" 12import "nx_fetch_deadline_candidate_t314.nx" 13import "nx_http_response_complete_candidate_t314.nx" 14import "nx_deadline_lib.nx" 15import "nx_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host 16const TB_MAGIC_262144: i64 = 262144 17const TB_MAGIC_8192: i64 = 8192 18const TB_MAGIC_16384: i64 = 16384 19const TB_MAGIC_1048576: i64 = 1048576 20 21const TB_CONF: *u8 = "knowledge/tool_backends.conf" as *u8 // server CWD-relative (like tool_allowlist.conf) 22const THB_OK: i64 = 1 // fetched -> body in out[0..outlen) 23const THB_NOROUTE: i64 = 0 // tool not an HTTP backend -> caller falls back to fork-exec 24const THB_ERR: i64 = 0 - 1 // declared but malformed/unreachable 25 26func thb_hex(v: i64) -> i64 { if v < 10 { return 48 + v } return 55 + v } // 0-9 then A-F 27func thb_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 28func thb_catb(d: *u8, o: i64, s: *u8, n: i64) -> i64 { var i: i64 = 0; while i < n { d[o + i] = s[i]; i = i + 1 } return o + n } 29func thb_cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { d[o + i] = s[i]; i = i + 1 } return o + i } 30 31// idx-th TAB-field of line buf[ls,le) -> out2[0]=off,out2[1]=len; 1 if present. 32func thb_field(buf: *u8, ls: i64, le: i64, idx: i64, out2: *i64) -> i64 { 33 var fi: i64 = 0; var s: i64 = ls; var i: i64 = ls 34 while i <= le { 35 var sep: i64 = 0 36 if i == le { sep = 1 } else { if buf[i] == (9 as u8) { sep = 1 } } 37 if sep == 1 { if fi == idx { out2[0] = s; out2[1] = i - s; return 1 } fi = fi + 1; s = i + 1 } 38 i = i + 1 39 } 40 return 0 41} 42func thb_atoi(buf: *u8, off: i64, len: i64) -> i64 { 43 var v: i64 = 0; var i: i64 = 0 44 while i < len { let c: i64 = buf[off + i] as i64; if c >= 48 { if c <= 57 { v = v * 10 + (c - 48) } } i = i + 1 } 45 return v 46} 47// url-encode src[0..slen) into dst; RFC3986 unreserved kept, else %XX. Returns length. Forecloses injection. 48func thb_urlenc(src: *u8, slen: i64, dst: *u8) -> i64 { 49 var o: i64 = 0; var i: i64 = 0 50 while i < slen { 51 let c: i64 = src[i] as i64 52 var keep: i64 = 0 53 if c >= 48 { if c <= 57 { keep = 1 } } 54 if c >= 65 { if c <= 90 { keep = 1 } } 55 if c >= 97 { if c <= 122 { keep = 1 } } 56 if c == 45 { keep = 1 } 57 if c == 46 { keep = 1 } 58 if c == 95 { keep = 1 } 59 if c == 126 { keep = 1 } 60 if keep == 1 { dst[o] = c as u8; o = o + 1 } else { 61 let hi: i64 = thb_hex((c >> 4) & 15) 62 let lo: i64 = thb_hex(c & 15) 63 dst[o] = 37 as u8; o = o + 1 // '%' 64 dst[o] = hi as u8; o = o + 1 65 dst[o] = lo as u8; o = o + 1 66 } 67 i = i + 1 68 } 69 return o 70} 71 72// ---- minimal self-contained loopback HTTP client (only nx_syscalls) ---- 73func thb_sockaddr(addr: *u8, port: i64, a: i64, b: i64, c: i64, d: i64) -> i64 { 74 addr[0] = 2 as u8; addr[1] = 0 as u8 // AF_INET 75 addr[2] = ((port / 256) & 255) as u8 // port hi (network byte order) 76 addr[3] = (port & 255) as u8 // port lo 77 addr[4] = a as u8; addr[5] = b as u8; addr[6] = c as u8; addr[7] = d as u8 78 var i: i64 = 8; while i < 16 { addr[i] = 0 as u8; i = i + 1 } 79 return 0 80} 81func thb_parse_ip(s:*u8,n:i64,out4:*i64)->i64 { 82 var oct:i64=0;var value:i64=0;var digits:i64=0;var i:i64=0 83 while i<n { 84 let c:i64=s[i] as i64 85 if c==46 { if digits==0 || oct>=3 { return 0 } out4[oct]=value;oct=oct+1;value=0;digits=0 } 86 else { if c<48 || c>57 { return 0 } value=value*10+c-48;if value>255 { return 0 } digits=digits+1 } 87 i=i+1 88 } 89 if oct!=3 || digits==0 { return 0 } out4[3]=value;return 1 90} 91// connect host[0..hostlen]:port, send req[0..reqn], drain response into out (cap). Bytes read, or <=0 on failure. 92struct NxHttpBackendEvidence { 93 state:i64 94 stage:i64 95 http_status:i64 96 framing:i64 97 sent_bytes:i64 98 received_bytes:i64 99 body_bytes:i64 100 begin_ms:i64 101 end_ms:i64 102 deadline_ms:i64 103} 104const THB_STAGE_CONNECT:i64=1 105const THB_STAGE_SEND:i64=2 106const THB_STAGE_READ:i64=3 107const THB_STAGE_FRAME:i64=4 108const THB_STAGE_STATUS:i64=5 109const THB_TIMEOUT:i64=0-2 110const THB_CAPACITY:i64=0-3 111const THB_FRAMING:i64=0-4 112const THB_UPSTREAM:i64=0-5 113func thb_evidence_init(e:*NxHttpBackendEvidence,deadline:i64)->i64 { 114 e.state=THB_ERR;e.stage=0;e.http_status=0;e.framing=0;e.sent_bytes=0;e.received_bytes=0;e.body_bytes=0 115 e.begin_ms=sys_now_ms();e.end_ms=e.begin_ms;e.deadline_ms=deadline;return 0 116} 117func thb_fetch_result(host:*u8,hostlen:i64,port:i64,req:*u8,reqn:i64,out:*u8,cap:i64,deadline:i64,e:*NxHttpBackendEvidence)->i64 { 118 thb_evidence_init(e,deadline) 119 if cap<0 || reqn<0 { return THB_ERR } 120 if port<1 || port>65535 { return THB_ERR } 121 if deadline<=e.begin_ms { e.state=THB_TIMEOUT;return e.state } 122 var a:i64=0;var b:i64=0;var c:i64=0;var d:i64=0 123 // Existing parser uses a four-word output; keep it in an owned, fallible allocation. 124 let ip:*i64=sys_mmap_try(4*__size_of(i64)) as *i64 125 if (ip as i64)<=0 { return THB_ERR } 126 let parsed:i64=thb_parse_ip(host,hostlen,ip) 127 if parsed==1 { a=ip[0];b=ip[1];c=ip[2];d=ip[3] } 128 sys_munmap_direct(ip as *u8,4*__size_of(i64)) 129 if parsed!=1 { return THB_ERR } 130 let fd:i64=sys_socket(2,1,0) 131 if fd<0 { return THB_ERR } 132 let addr:*u8=sys_mmap_try(16) 133 if (addr as i64)<=0 { sys_close(fd);return THB_ERR } 134 thb_sockaddr(addr,port,a,b,c,d) 135 e.stage=THB_STAGE_CONNECT 136 let left:i64=dl_wait_ms(deadline,sys_now_ms(),NX_CONN_DEFAULT_MS) 137 var connected:i64=0-1 138 if left>0 { connected=nx_connect_bounded(fd,addr,16,left) } 139 sys_munmap_direct(addr,16) 140 if connected<0 { sys_close(fd);e.end_ms=sys_now_ms();if e.end_ms>=deadline { e.state=THB_TIMEOUT } return e.state } 141 let flags:i64=nx_fcntl(fd,NX_F_GETFL,0) 142 if flags<0 { sys_close(fd);return THB_ERR } 143 if nx_fcntl(fd,NX_F_SETFL,flags|NX_O_NONBLOCK)<0 { sys_close(fd);return THB_ERR } 144 e.stage=THB_STAGE_SEND 145 var sent:i64=0 146 let wr:i64=fb_send_until(fd,req,reqn,deadline,&sent) 147 e.sent_bytes=sent 148 if wr!=FB_OK { sys_close(fd);e.end_ms=sys_now_ms();if wr==FB_TIMEOUT { e.state=THB_TIMEOUT } return e.state } 149 e.stage=THB_STAGE_READ 150 var got:i64=0 151 let rd:i64=fb_receive_until(fd,out,cap,deadline,&got) 152 sys_close(fd);e.received_bytes=got;e.end_ms=sys_now_ms() 153 if rd==FB_TIMEOUT { e.state=THB_TIMEOUT;return e.state } 154 if rd==FB_OVERFLOW { e.state=THB_CAPACITY;return e.state } 155 if rd!=FB_OK { return e.state } 156 e.state=THB_OK;return e.state 157} 158func thb_fetch(host:*u8,hostlen:i64,port:i64,req:*u8,reqn:i64,out:*u8,cap:i64)->i64 { 159 var e:NxHttpBackendEvidence 160 let now:i64=sys_now_ms() 161 let budget:i64=dl_wait_ms(dl_scan_request(req,reqn),now,FB_DEFAULT_TIMEOUT_MS) 162 let r:i64=thb_fetch_result(host,hostlen,port,req,reqn,out,cap,now+budget,&e) 163 if r!=THB_OK { return THB_ERR } 164 return e.received_bytes 165} 166func thb_body_result(raw:*u8,n:i64,out:*u8,cap:i64,e:*NxHttpBackendEvidence)->i64 { 167 e.stage=THB_STAGE_FRAME 168 var code:i64=0;var bl:i64=0 169 let r:i64=nx_http_response_complete(raw,n,out,cap,&code,&bl) 170 e.framing=r;e.http_status=code;e.body_bytes=0 171 if r!=NX_HTTP_RESP_OK { e.state=THB_FRAMING;return e.state } 172 e.stage=THB_STAGE_STATUS 173 if code<200 || code>=300 { e.state=THB_UPSTREAM;return e.state } 174 e.body_bytes=bl;e.state=THB_OK;return e.state 175} 176 177// resolve `name` in `conf`; if it is an HTTP backend, fetch it with argv[1] as the url-encoded param value and 178// write the response BODY (after CRLFCRLF) into out. argv[1..] are the parsed args (argv[0] reserved), argc = count. 179// bounded config read: a config file never needs sys_read_file's 4GB reservation (rule 21). Reads up to 256KB into 180// a small mmap so calling this per-request doesn't accumulate 4GB reservations that starve fork() on a small VM. 181func thb_read_conf(path: *u8, out_len: *i64) -> *u8 { 182 out_len[0] = 0 183 let fd: i64 = sys_openat_rd(path) 184 if fd < 0 { return 0 as *u8 } 185 let cap: i64 = TB_MAGIC_262144 186 let buf: *u8 = sys_mmap(cap + 16) 187 var total: i64 = 0 188 var go: i64 = 1 189 while go == 1 { 190 if total >= cap { go = 0 } else { 191 let r: i64 = sys_read(fd, ((buf as i64) + total) as *u8, cap - total) 192 if r <= 0 { go = 0 } else { total = total + r } 193 } 194 } 195 sys_close(fd) 196 let term: *u8 = ((buf as i64) + total) as *u8 197 term[0] = 0 as u8 198 out_len[0] = total 199 return buf 200} 201 202func thb_dispatch_from_result(conf: *u8, name: *u8, nlen: i64, argv: *i64, argc: i64, out: *u8, cap: i64, outlen: *i64, deadline:i64, wire_cap:i64, ev:*NxHttpBackendEvidence) -> i64 { 203 outlen[0]=0;thb_evidence_init(ev,deadline) 204 if wire_cap<=0 || cap<0 { return THB_ERR } 205 if deadline<=sys_now_ms() { ev.state=THB_TIMEOUT;return THB_TIMEOUT } 206 let szp: *i64 = sys_mmap(16) as *i64 207 let buf: *u8 = thb_read_conf(conf, szp) 208 if (buf as i64) == 0 { return THB_NOROUTE } 209 let n: i64 = szp[0] 210 var f_host: i64 = 0; var l_host: i64 = 0; var v_port: i64 = 0 211 var f_path: i64 = 0; var l_path: i64 = 0; var f_param: i64 = 0; var l_param: i64 = 0 212 var found: i64 = 0 213 var ls: i64 = 0; var i: i64 = 0 214 while i <= n { 215 var eol: i64 = 0 216 if i == n { eol = 1 } else { if buf[i] == (10 as u8) { eol = 1 } } 217 if eol == 1 { 218 if found == 0 { 219 let le: i64 = i 220 if le > ls { if buf[ls] != (35 as u8) { 221 let f0: *i64 = sys_mmap(16) as *i64 222 if thb_field(buf, ls, le, 0, f0) == 1 { 223 if f0[1] == nlen { 224 var m: i64 = 1; var c: i64 = 0 225 while c < nlen { if buf[f0[0] + c] != name[c] { m = 0 } c = c + 1 } 226 if m == 1 { 227 let ff: *i64 = sys_mmap(16) as *i64 228 if thb_field(buf, ls, le, 2, ff) == 1 { f_host = ff[0]; l_host = ff[1] } 229 if thb_field(buf, ls, le, 3, ff) == 1 { v_port = thb_atoi(buf, ff[0], ff[1]) } 230 if thb_field(buf, ls, le, 4, ff) == 1 { f_path = ff[0]; l_path = ff[1] } 231 if thb_field(buf, ls, le, 5, ff) == 1 { f_param = ff[0]; l_param = ff[1] } 232 found = 1 233 } 234 } 235 } 236 } } 237 } 238 ls = i + 1 239 } 240 i = i + 1 241 } 242 if found == 0 { return THB_NOROUTE } 243 if l_host <= 0 { return THB_ERR } 244 if v_port <= 0 { return THB_ERR } 245 outlen[0]=0 246 var arg:*u8="" as *u8 247 if argc>=1 { arg=argv[1] as *u8 } 248 let an:i64=thb_slen(arg) 249 if an>(9223372036854775807-1)/3 { return THB_ERR } 250 let enc_cap:i64=an*3+1 251 let enc:*u8=sys_mmap_try(enc_cap) 252 if (enc as i64)<=0 { return THB_ERR } 253 let enclen:i64=thb_urlenc(arg,an,enc) 254 let suffix:*u8=" HTTP/1.0\r\nHost: nishifamily.com\r\nConnection: close\r\n\r\n" as *u8 255 let fixed:i64=thb_slen(suffix)+6+l_path+l_param 256 if enc_cap>9223372036854775807-fixed { sys_munmap_direct(enc,enc_cap);return THB_ERR } 257 let reqcap:i64=enc_cap+fixed 258 let req:*u8=sys_mmap_try(reqcap) 259 if (req as i64)<=0 { sys_munmap_direct(enc,enc_cap);return THB_ERR } 260 var o:i64=thb_cat(req,0,"GET " as *u8) 261 o=thb_catb(req,o,buf+f_path,l_path);o=thb_cat(req,o,"?" as *u8) 262 o=thb_catb(req,o,buf+f_param,l_param);o=thb_cat(req,o,"=" as *u8) 263 o=thb_catb(req,o,enc,enclen);o=thb_cat(req,o,suffix) 264 sys_munmap_direct(enc,enc_cap) 265 let resp:*u8=sys_mmap_try(wire_cap) 266 if (resp as i64)<=0 { sys_munmap_direct(req,reqcap);return THB_ERR } 267 let fr:i64=thb_fetch_result(buf+f_host,l_host,v_port,req,o,resp,wire_cap,deadline,ev) 268 sys_munmap_direct(req,reqcap) 269 var result:i64=fr 270 if fr==THB_OK { result=thb_body_result(resp,ev.received_bytes,out,cap,ev);if result==THB_OK { outlen[0]=ev.body_bytes } } 271 sys_munmap_direct(resp,wire_cap) 272 return result 273} 274 275func thb_dispatch(name: *u8, nlen: i64, argv: *i64, argc: i64, out: *u8, cap: i64, outlen: *i64) -> i64 { 276 return thb_dispatch_from(TB_CONF, name, nlen, argv, argc, out, cap, outlen) 277} 278 279func thb_dispatch_from(conf:*u8,name:*u8,nlen:i64,argv:*i64,argc:i64,out:*u8,cap:i64,outlen:*i64)->i64 { 280 var ev:NxHttpBackendEvidence 281 let rc:i64=thb_dispatch_from_result(conf,name,nlen,argv,argc,out,cap,outlen,sys_now_ms()+FB_DEFAULT_TIMEOUT_MS,TB_MAGIC_1048576,&ev) 282 if rc==THB_OK || rc==THB_NOROUTE { return rc } 283 return THB_ERR 284}