code wiki / (root) / nx_tool_http_backend.nx

nx_tool_http_backend.nx source

↩ module page · 204 lines · 10233 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_connect.nx" // bounded connect: a raw sys_connect hangs ~127s on a black-holed host 13const TB_MAGIC_262144: i64 = 262144 14const TB_MAGIC_8192: i64 = 8192 15const TB_MAGIC_16384: i64 = 16384 16const TB_MAGIC_1048576: i64 = 1048576 17 18const TB_CONF: *u8 = "knowledge/tool_backends.conf" as *u8 // server CWD-relative (like tool_allowlist.conf) 19const THB_OK: i64 = 1 // fetched -> body in out[0..outlen) 20const THB_NOROUTE: i64 = 0 // tool not an HTTP backend -> caller falls back to fork-exec 21const THB_ERR: i64 = 0 - 1 // declared but malformed/unreachable 22 23func thb_hex(v: i64) -> i64 { if v < 10 { return 48 + v } return 55 + v } // 0-9 then A-F 24func thb_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 25func 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 } 26func 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 } 27 28// idx-th TAB-field of line buf[ls,le) -> out2[0]=off,out2[1]=len; 1 if present. 29func thb_field(buf: *u8, ls: i64, le: i64, idx: i64, out2: *i64) -> i64 { 30 var fi: i64 = 0; var s: i64 = ls; var i: i64 = ls 31 while i <= le { 32 var sep: i64 = 0 33 if i == le { sep = 1 } else { if buf[i] == (9 as u8) { sep = 1 } } 34 if sep == 1 { if fi == idx { out2[0] = s; out2[1] = i - s; return 1 } fi = fi + 1; s = i + 1 } 35 i = i + 1 36 } 37 return 0 38} 39func thb_atoi(buf: *u8, off: i64, len: i64) -> i64 { 40 var v: i64 = 0; var i: i64 = 0 41 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 } 42 return v 43} 44// url-encode src[0..slen) into dst; RFC3986 unreserved kept, else %XX. Returns length. Forecloses injection. 45func thb_urlenc(src: *u8, slen: i64, dst: *u8) -> i64 { 46 var o: i64 = 0; var i: i64 = 0 47 while i < slen { 48 let c: i64 = src[i] as i64 49 var keep: i64 = 0 50 if c >= 48 { if c <= 57 { keep = 1 } } 51 if c >= 65 { if c <= 90 { keep = 1 } } 52 if c >= 97 { if c <= 122 { keep = 1 } } 53 if c == 45 { keep = 1 } 54 if c == 46 { keep = 1 } 55 if c == 95 { keep = 1 } 56 if c == 126 { keep = 1 } 57 if keep == 1 { dst[o] = c as u8; o = o + 1 } else { 58 let hi: i64 = thb_hex((c >> 4) & 15) 59 let lo: i64 = thb_hex(c & 15) 60 dst[o] = 37 as u8; o = o + 1 // '%' 61 dst[o] = hi as u8; o = o + 1 62 dst[o] = lo as u8; o = o + 1 63 } 64 i = i + 1 65 } 66 return o 67} 68 69// ---- minimal self-contained loopback HTTP client (only nx_syscalls) ---- 70func thb_sockaddr(addr: *u8, port: i64, a: i64, b: i64, c: i64, d: i64) -> i64 { 71 addr[0] = 2 as u8; addr[1] = 0 as u8 // AF_INET 72 addr[2] = ((port / 256) & 255) as u8 // port hi (network byte order) 73 addr[3] = (port & 255) as u8 // port lo 74 addr[4] = a as u8; addr[5] = b as u8; addr[6] = c as u8; addr[7] = d as u8 75 var i: i64 = 8; while i < 16 { addr[i] = 0 as u8; i = i + 1 } 76 return 0 77} 78func thb_parse_ip(s: *u8, slen: i64, out4: *i64) -> i64 { 79 var oct: i64 = 0; var val: i64 = 0; var i: i64 = 0 80 while i < slen { 81 let ch: i64 = s[i] as i64 82 if ch == 46 { if oct < 4 { out4[oct] = val } oct = oct + 1; val = 0 } else { if ch >= 48 { if ch <= 57 { val = val * 10 + (ch - 48) } } } 83 i = i + 1 84 } 85 if oct < 4 { out4[oct] = val; oct = oct + 1 } 86 if oct == 4 { return 1 } 87 return 0 88} 89// connect host[0..hostlen]:port, send req[0..reqn], drain response into out (cap). Bytes read, or <=0 on failure. 90func thb_fetch(host: *u8, hostlen: i64, port: i64, req: *u8, reqn: i64, out: *u8, cap: i64) -> i64 { 91 let ip: *i64 = sys_mmap(64) as *i64 92 if thb_parse_ip(host, hostlen, ip) != 1 { return 0 - 1 } 93 let fd: i64 = sys_socket(2, 1, 0) // AF_INET, SOCK_STREAM 94 if fd < 0 { return 0 - 1 } 95 let addr: *u8 = sys_mmap(16) 96 thb_sockaddr(addr, port, ip[0], ip[1], ip[2], ip[3]) 97 if nx_connect_bounded(fd, addr, 16, NX_CONN_DEFAULT_MS) < 0 { sys_close(fd); return 0 - 1 } 98 var off: i64 = 0 99 while off < reqn { let w: i64 = sys_write(fd, ((req as i64) + off) as *u8, reqn - off); if w <= 0 { off = reqn } else { off = off + w } } 100 var total: i64 = 0; var go: i64 = 1 101 while go == 1 { 102 if total >= cap { go = 0 } else { 103 let r: i64 = sys_read(fd, ((out as i64) + total) as *u8, cap - total) 104 if r <= 0 { go = 0 } else { total = total + r } 105 } 106 } 107 sys_close(fd) 108 return total 109} 110 111// resolve `name` in `conf`; if it is an HTTP backend, fetch it with argv[1] as the url-encoded param value and 112// write the response BODY (after CRLFCRLF) into out. argv[1..] are the parsed args (argv[0] reserved), argc = count. 113// bounded config read: a config file never needs sys_read_file's 4GB reservation (rule 21). Reads up to 256KB into 114// a small mmap so calling this per-request doesn't accumulate 4GB reservations that starve fork() on a small VM. 115func thb_read_conf(path: *u8, out_len: *i64) -> *u8 { 116 out_len[0] = 0 117 let fd: i64 = sys_openat_rd(path) 118 if fd < 0 { return 0 as *u8 } 119 let cap: i64 = TB_MAGIC_262144 120 let buf: *u8 = sys_mmap(cap + 16) 121 var total: i64 = 0 122 var go: i64 = 1 123 while go == 1 { 124 if total >= cap { go = 0 } else { 125 let r: i64 = sys_read(fd, ((buf as i64) + total) as *u8, cap - total) 126 if r <= 0 { go = 0 } else { total = total + r } 127 } 128 } 129 sys_close(fd) 130 let term: *u8 = ((buf as i64) + total) as *u8 131 term[0] = 0 as u8 132 out_len[0] = total 133 return buf 134} 135 136func thb_dispatch_from(conf: *u8, name: *u8, nlen: i64, argv: *i64, argc: i64, out: *u8, cap: i64, outlen: *i64) -> i64 { 137 let szp: *i64 = sys_mmap(16) as *i64 138 let buf: *u8 = thb_read_conf(conf, szp) 139 if (buf as i64) == 0 { return THB_NOROUTE } 140 let n: i64 = szp[0] 141 var f_host: i64 = 0; var l_host: i64 = 0; var v_port: i64 = 0 142 var f_path: i64 = 0; var l_path: i64 = 0; var f_param: i64 = 0; var l_param: i64 = 0 143 var found: i64 = 0 144 var ls: i64 = 0; var i: i64 = 0 145 while i <= n { 146 var eol: i64 = 0 147 if i == n { eol = 1 } else { if buf[i] == (10 as u8) { eol = 1 } } 148 if eol == 1 { 149 if found == 0 { 150 let le: i64 = i 151 if le > ls { if buf[ls] != (35 as u8) { 152 let f0: *i64 = sys_mmap(16) as *i64 153 if thb_field(buf, ls, le, 0, f0) == 1 { 154 if f0[1] == nlen { 155 var m: i64 = 1; var c: i64 = 0 156 while c < nlen { if buf[f0[0] + c] != name[c] { m = 0 } c = c + 1 } 157 if m == 1 { 158 let ff: *i64 = sys_mmap(16) as *i64 159 if thb_field(buf, ls, le, 2, ff) == 1 { f_host = ff[0]; l_host = ff[1] } 160 if thb_field(buf, ls, le, 3, ff) == 1 { v_port = thb_atoi(buf, ff[0], ff[1]) } 161 if thb_field(buf, ls, le, 4, ff) == 1 { f_path = ff[0]; l_path = ff[1] } 162 if thb_field(buf, ls, le, 5, ff) == 1 { f_param = ff[0]; l_param = ff[1] } 163 found = 1 164 } 165 } 166 } 167 } } 168 } 169 ls = i + 1 170 } 171 i = i + 1 172 } 173 if found == 0 { return THB_NOROUTE } 174 if l_host <= 0 { return THB_ERR } 175 if v_port <= 0 { return THB_ERR } 176 let enc: *u8 = sys_mmap(TB_MAGIC_8192); var enclen: i64 = 0 177 if argc >= 1 { let a: *u8 = argv[1] as *u8; enclen = thb_urlenc(a, thb_slen(a), enc) } 178 let req: *u8 = sys_mmap(TB_MAGIC_16384) 179 var o: i64 = thb_cat(req, 0, "GET " as *u8) 180 o = thb_catb(req, o, ((buf as i64) + f_path) as *u8, l_path) 181 o = thb_catb(req, o, "?" as *u8, 1) 182 o = thb_catb(req, o, ((buf as i64) + f_param) as *u8, l_param) 183 o = thb_catb(req, o, "=" as *u8, 1) 184 o = thb_catb(req, o, enc, enclen) 185 o = thb_cat(req, o, " HTTP/1.0\r\nHost: nishifamily.com\r\nConnection: close\r\n\r\n" as *u8) 186 let rcap: i64 = TB_MAGIC_1048576 187 let resp: *u8 = sys_mmap(rcap) 188 let hostp: *u8 = ((buf as i64) + f_host) as *u8 189 let rn: i64 = thb_fetch(hostp, l_host, v_port, req, o, resp, rcap) 190 if rn <= 0 { return THB_ERR } 191 var bo: i64 = 0 - 1; var j: i64 = 0 192 while j + 3 < rn { if resp[j] == (13 as u8) { if resp[j + 1] == (10 as u8) { if resp[j + 2] == (13 as u8) { if resp[j + 3] == (10 as u8) { bo = j + 4; j = rn } } } } j = j + 1 } 193 var start: i64 = 0 194 if bo >= 0 { start = bo } 195 var blen: i64 = rn - start 196 if blen > cap { blen = cap } 197 var k: i64 = 0 198 while k < blen { out[k] = resp[start + k]; k = k + 1 } 199 outlen[0] = blen 200 return THB_OK 201} 202func thb_dispatch(name: *u8, nlen: i64, argv: *i64, argc: i64, out: *u8, cap: i64, outlen: *i64) -> i64 { 203 return thb_dispatch_from(TB_CONF, name, nlen, argv, argc, out, cap, outlen) 204}