code wiki / _hdl_build / nx_email_portal_gate.nx

nx_email_portal_gate.nx source

↩ module page · 226 lines · 16138 B

1// nx_email_portal_gate.nx -- SOVEREIGN in-process referee for the mail.<domain> EMAIL portal daemon 2// (no socket/curl/shell). Arms a fresh realm + invite store, then feeds crafted HTTP request BYTES into 3// the pure router epd_handle and asserts response bytes AND real mailbox side effects in the seg_store: 4// T1 GET / -> 200 + the login/register/mail SPA shell 5// T2 invite register alice -> 200 mnemonic; then login -> token A 6// T3 NEG bad-charset handle -> 400 AND the invite is NOT consumed (proved by T4 using it) 7// T4 register bob with T3's invite -> 200 (the invite survived the refused attempt); login -> token B 8// T5 NEG /mail/inbox with NO session -> 401 9// T6 alice sends bob a message whose subject carries "<script>" -> 200 DELIVERED-LOCAL + bob count +1 10// T7 bob's inbox fragment shows the subject HTML-ESCAPED (&lt;script&gt;), raw <script> ABSENT 11// T8 bob reads the message -> body marker present, escaped 12// T9 ISOLATION: alice's inbox does NOT contain bob's subject (mailbox derives from the session only) 13// T10 alice sends to an EXTERNAL rcpt -> 200 QUEUED-EXTERNAL + relay-queue@<domain> count +1 (honest) 14// T11 bob deletes the message -> inbox fragment no longer lists it; slot reads tombstoned 15// T12 GET /mail/whoami (token A) -> {"handle":"alice","mailbox":"alice@<domain>"} 16// GREEN iff 12/12. Appends knowledge/status/email_portal_gate.log. license_tier: ORIGINAL expect_exit: 0 17import "_hdl_build/nx_email_portal_daemon.nx" 18import "hub/nx_modern_auth_flow.nx" 19import "nx_syscalls.nx" 20 21func g_w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } 22func g_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 23func g_lw(fd: i64, s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(fd, s, n); return 0 } 24func g_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 } 25func g_cat_n(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 g_trunc(path: *u8) -> i64 { let fd: i64 = sys_openat_wr(path, 0x180); if fd >= 0 { sys_close(fd) } return 0 } 27func g_starts(buf: *u8, n: i64, s: *u8) -> i64 { let sn: i64 = g_len(s); if n < sn { return 0 } var i: i64 = 0; while i < sn { if (buf[i] as i64) != (s[i] as i64) { return 0 } i = i + 1 } return 1 } 28func g_contains(hay: *u8, n: i64, needle: *u8) -> i64 { 29 let nn: i64 = g_len(needle); var i: i64 = 0 30 while i + nn <= n { var m: i64 = 1; var j: i64 = 0; while j < nn { if (hay[i + j] as i64) != (needle[j] as i64) { m = 0; j = nn } else { j = j + 1 } } if m == 1 { return 1 } i = i + 1 } 31 return 0 32} 33func g_extract_token(resp: *u8, n: i64, out: *u8) -> i64 { 34 let key: *u8 = "{\"token\":\"" as *u8; let kn: i64 = 10 35 var start: i64 = 0 - 1; var i: i64 = 0 36 while i + kn <= n { var m: i64 = 1; var j: i64 = 0; while j < kn { if (resp[i + j] as i64) != (key[j] as i64) { m = 0; j = kn } else { j = j + 1 } } if m == 1 { start = i + kn; i = n } i = i + 1 } 37 if start < 0 { return 0 } 38 var w: i64 = 0; var k: i64 = start; var done: i64 = 0 39 while done == 0 { if k >= n { done = 1 } if done == 0 { if (resp[k] as i64) == 34 { done = 1 } } if done == 0 { out[w] = resp[k]; w = w + 1; k = k + 1 } } 40 return w 41} 42func g_verdict(label: *u8, ok: i64) -> i64 { g_w(" " as *u8); g_w(label); if ok == 1 { g_w(" PASS\n" as *u8) } else { g_w(" FAIL\n" as *u8) } return ok } 43 44func main() -> i64 { 45 g_w("=== EMAIL PORTAL GATE (mail.<domain> on the CANONICAL Modern Auth; sovereign in-process) ===\n" as *u8) 46 let store_path: *u8 = "/tmp/nx_epd_gate_store.log" as *u8 47 let keys_path: *u8 = "/tmp/nx_epd_gate_keys.log" as *u8 48 let invpath: *u8 = "/tmp/nx_epd_gate_store.log.invites" as *u8 49 g_trunc(store_path); g_trunc(keys_path); g_trunc(invpath) 50 sys_mkdir("/tmp/nx_epd_gate_mail" as *u8, 511) 51 let prefix: *u8 = "/tmp/nx_epd_gate_mail/" as *u8 52 let domain: *u8 = "andelinwest.com" as *u8 53 54 let oprf_seed: *u8 = sys_mmap(32); let akp: *u8 = sys_mmap(32); let akb: *u8 = sys_mmap(33) 55 let edp: *u8 = sys_mmap(32); let edb: *u8 = sys_mmap(32) 56 if nx_uas_server_keys_load_or_init(keys_path, oprf_seed, akp, akb, edp, edb) != NX_UAS_OK { g_w("KEYS FAIL\n" as *u8); sys_exit(1) } 57 let realm: *u8 = "andelinwest_mail" as *u8 58 let realm_n: i64 = g_len(realm) 59 let ctx: *NxAuthContext = sys_mmap(256) as *NxAuthContext 60 if nx_auth_context_init(ctx, realm, realm_n, realm, realm_n, store_path as i64, oprf_seed, edp, edb, 900, 8192, 1, 1, 5, 1) != NX_MAUTH_OK { g_w("CTX FAIL\n" as *u8); sys_exit(1) } 61 62 let req: *u8 = sys_mmap(65536) 63 let out: *u8 = sys_mmap(EPD_OUTCAP) 64 var pass: i64 = 0 65 let rnow: i64 = sys_now_realtime_sec() 66 67 // T1: shell 68 let n1: i64 = g_cat(req, 0, "GET / HTTP/1.1\r\nHost: mail.andelinwest.com\r\n\r\n" as *u8) 69 let o1: i64 = epd_handle(ctx, domain, prefix, req, n1, out) 70 var t1: i64 = 0 71 if g_starts(out, o1, "HTTP/1.1 200" as *u8) == 1 { if g_contains(out, o1, "id=login" as *u8) == 1 { if g_contains(out, o1, "/mail/register" as *u8) == 1 { t1 = 1 } } } 72 pass = pass + g_verdict("T1 GET / -> 200 SPA shell:" as *u8, t1) 73 74 // T2: invite register alice -> mnemonic; login -> token A 75 let inv1: *u8 = sys_mmap(80) 76 let ri1: i64 = inv_issue(invpath, realm, realm_n, 2, 3600, rnow, inv1) 77 var n2: i64 = g_cat(req, 0, "POST /mail/register HTTP/1.1\r\nHost: x\r\n\r\nhandle=alice&pw=alice+long+passphrase&invite=" as *u8) 78 n2 = g_cat_n(req, n2, inv1, NX_INV_TOKEN_HEX) 79 let o2: i64 = epd_handle(ctx, domain, prefix, req, n2, out) 80 var t2: i64 = 0 81 let tokA: *u8 = sys_mmap(256) 82 var tokA_n: i64 = 0 83 if ri1 == NX_INV_OK { if g_starts(out, o2, "HTTP/1.1 200" as *u8) == 1 { if g_contains(out, o2, "mnemonic" as *u8) == 1 { 84 let n2b: i64 = g_cat(req, 0, "POST /mail/login HTTP/1.1\r\nHost: x\r\n\r\nhandle=alice&passphrase=alice+long+passphrase" as *u8) 85 let o2b: i64 = epd_handle(ctx, domain, prefix, req, n2b, out) 86 if g_starts(out, o2b, "HTTP/1.1 200" as *u8) == 1 { tokA_n = g_extract_token(out, o2b, tokA); if tokA_n > 0 { t2 = 1 } } 87 } } } 88 pass = pass + g_verdict("T2 invite register alice + login -> token:" as *u8, t2) 89 90 // T3 NEG: bad-charset handle refused AND the invite survives (proved in T4) 91 let inv2: *u8 = sys_mmap(80) 92 let ri2: i64 = inv_issue(invpath, realm, realm_n, 2, 3600, rnow, inv2) 93 var n3: i64 = g_cat(req, 0, "POST /mail/register HTTP/1.1\r\nHost: x\r\n\r\nhandle=Bad%2F..%2Fguy&pw=whatever+pass&invite=" as *u8) 94 n3 = g_cat_n(req, n3, inv2, NX_INV_TOKEN_HEX) 95 let o3: i64 = epd_handle(ctx, domain, prefix, req, n3, out) 96 var t3: i64 = 0 97 if ri2 == NX_INV_OK { if g_starts(out, o3, "HTTP/1.1 400" as *u8) == 1 { t3 = 1 } } 98 pass = pass + g_verdict("T3 NEG bad-charset handle -> 400:" as *u8, t3) 99 100 // T4: bob registers with the SAME invite (not consumed by the refused attempt) -> token B 101 var n4: i64 = g_cat(req, 0, "POST /mail/register HTTP/1.1\r\nHost: x\r\n\r\nhandle=bob&pw=bob+long+passphrase&invite=" as *u8) 102 n4 = g_cat_n(req, n4, inv2, NX_INV_TOKEN_HEX) 103 let o4: i64 = epd_handle(ctx, domain, prefix, req, n4, out) 104 var t4: i64 = 0 105 let tokB: *u8 = sys_mmap(256) 106 var tokB_n: i64 = 0 107 if g_starts(out, o4, "HTTP/1.1 200" as *u8) == 1 { 108 let n4b: i64 = g_cat(req, 0, "POST /mail/login HTTP/1.1\r\nHost: x\r\n\r\nhandle=bob&passphrase=bob+long+passphrase" as *u8) 109 let o4b: i64 = epd_handle(ctx, domain, prefix, req, n4b, out) 110 if g_starts(out, o4b, "HTTP/1.1 200" as *u8) == 1 { tokB_n = g_extract_token(out, o4b, tokB); if tokB_n > 0 { t4 = 1 } } 111 } 112 pass = pass + g_verdict("T4 invite SURVIVED the refusal; bob registers + logs in:" as *u8, t4) 113 114 // T5 NEG: inbox with no session -> 401 115 let n5: i64 = g_cat(req, 0, "GET /mail/inbox HTTP/1.1\r\nHost: x\r\n\r\n" as *u8) 116 let o5: i64 = epd_handle(ctx, domain, prefix, req, n5, out) 117 var t5: i64 = 0 118 if g_starts(out, o5, "HTTP/1.1 401" as *u8) == 1 { t5 = 1 } 119 pass = pass + g_verdict("T5 NEG no-session /mail/inbox -> 401:" as *u8, t5) 120 121 // T6: alice -> bob, subject carries an injection attempt; bob's mailbox count grows 122 let bobmb: *u8 = "bob@andelinwest.com" as *u8 123 let c0: i64 = nx_mbox_count(prefix, bobmb) 124 var n6: i64 = g_cat(req, 0, "POST /mail/send HTTP/1.1\r\nHost: x\r\nX-Nishi-Session: " as *u8) 125 n6 = g_cat_n(req, n6, tokA, tokA_n) 126 n6 = g_cat(req, n6, "\r\n\r\nto=bob&subject=Hello%20%3Cscript%3Ealert(1)%3C%2Fscript%3E%20zetamarker&body=Meeting%20memo%20omegapayload%20end." as *u8) 127 let o6: i64 = epd_handle(ctx, domain, prefix, req, n6, out) 128 var t6: i64 = 0 129 if g_starts(out, o6, "HTTP/1.1 200" as *u8) == 1 { if g_contains(out, o6, "DELIVERED-LOCAL to=bob@andelinwest.com" as *u8) == 1 { if nx_mbox_count(prefix, bobmb) == c0 + 1 { t6 = 1 } } } 130 pass = pass + g_verdict("T6 alice->bob DELIVERED-LOCAL + count grew:" as *u8, t6) 131 132 // T7: bob's inbox shows the subject ESCAPED; raw <script> absent 133 var n7: i64 = g_cat(req, 0, "GET /mail/inbox HTTP/1.1\r\nHost: x\r\nX-Nishi-Session: " as *u8) 134 n7 = g_cat_n(req, n7, tokB, tokB_n) 135 n7 = g_cat(req, n7, "\r\n\r\n" as *u8) 136 let o7: i64 = epd_handle(ctx, domain, prefix, req, n7, out) 137 var t7: i64 = 0 138 if g_starts(out, o7, "HTTP/1.1 200" as *u8) == 1 { if g_contains(out, o7, "&lt;script&gt;" as *u8) == 1 { if g_contains(out, o7, "<script>alert" as *u8) == 0 { if g_contains(out, o7, "zetamarker" as *u8) == 1 { t7 = 1 } } } } 139 pass = pass + g_verdict("T7 bob inbox: subject escaped, raw <script> absent:" as *u8, t7) 140 141 // T8: bob reads the message (seq = c0) -> escaped body marker 142 var n8: i64 = g_cat(req, 0, "GET /mail/msg?n=" as *u8) 143 var nn8: i64 = 0 144 if c0 == 0 { req[n8] = 48 as u8; nn8 = n8 + 1 } else { var v: i64 = c0; let tb: *u8 = sys_mmap(24); var k: i64 = 0; while v > 0 { tb[k] = (48 + (v % 10)) as u8; v = v / 10; k = k + 1 } var x: i64 = 0; while x < k { req[n8 + x] = tb[k - 1 - x]; x = x + 1 } nn8 = n8 + k } 145 nn8 = g_cat(req, nn8, " HTTP/1.1\r\nHost: x\r\nX-Nishi-Session: " as *u8) 146 nn8 = g_cat_n(req, nn8, tokB, tokB_n) 147 nn8 = g_cat(req, nn8, "\r\n\r\n" as *u8) 148 let o8: i64 = epd_handle(ctx, domain, prefix, req, nn8, out) 149 var t8: i64 = 0 150 if g_starts(out, o8, "HTTP/1.1 200" as *u8) == 1 { if g_contains(out, o8, "omegapayload" as *u8) == 1 { if g_contains(out, o8, "From: alice@andelinwest.com" as *u8) == 1 { t8 = 1 } } } 151 pass = pass + g_verdict("T8 bob reads the message (From: alice, body marker):" as *u8, t8) 152 153 // T9 ISOLATION: alice's inbox lacks bob's message marker 154 var n9: i64 = g_cat(req, 0, "GET /mail/inbox HTTP/1.1\r\nHost: x\r\nX-Nishi-Session: " as *u8) 155 n9 = g_cat_n(req, n9, tokA, tokA_n) 156 n9 = g_cat(req, n9, "\r\n\r\n" as *u8) 157 let o9: i64 = epd_handle(ctx, domain, prefix, req, n9, out) 158 var t9: i64 = 0 159 if g_starts(out, o9, "HTTP/1.1 200" as *u8) == 1 { if g_contains(out, o9, "zetamarker" as *u8) == 0 { t9 = 1 } } 160 pass = pass + g_verdict("T9 ISOLATION alice inbox lacks bob's mail:" as *u8, t9) 161 162 // T10: external rcpt -> honest QUEUED-EXTERNAL + relay queue grows 163 let qmb: *u8 = "relay-queue@andelinwest.com" as *u8 164 let q0: i64 = nx_mbox_count(prefix, qmb) 165 var n10: i64 = g_cat(req, 0, "POST /mail/send HTTP/1.1\r\nHost: x\r\nX-Nishi-Session: " as *u8) 166 n10 = g_cat_n(req, n10, tokA, tokA_n) 167 n10 = g_cat(req, n10, "\r\n\r\nto=friend%40gmail.com&subject=external%20probe&body=outbound%20body" as *u8) 168 let o10: i64 = epd_handle(ctx, domain, prefix, req, n10, out) 169 var t10: i64 = 0 170 if g_starts(out, o10, "HTTP/1.1 200" as *u8) == 1 { if g_contains(out, o10, "QUEUED-EXTERNAL" as *u8) == 1 { if nx_mbox_count(prefix, qmb) == q0 + 1 { t10 = 1 } } } 171 pass = pass + g_verdict("T10 external rcpt -> QUEUED-EXTERNAL (honest) + queue grew:" as *u8, t10) 172 173 // T11: bob deletes the message -> inbox no longer lists it; slot reads tombstoned 174 var n11: i64 = g_cat(req, 0, "POST /mail/del?n=" as *u8) 175 var nn11: i64 = 0 176 if c0 == 0 { req[n11] = 48 as u8; nn11 = n11 + 1 } else { var v2: i64 = c0; let tb2: *u8 = sys_mmap(24); var k2: i64 = 0; while v2 > 0 { tb2[k2] = (48 + (v2 % 10)) as u8; v2 = v2 / 10; k2 = k2 + 1 } var x2: i64 = 0; while x2 < k2 { req[n11 + x2] = tb2[k2 - 1 - x2]; x2 = x2 + 1 } nn11 = n11 + k2 } 177 nn11 = g_cat(req, nn11, " HTTP/1.1\r\nHost: x\r\nX-Nishi-Session: " as *u8) 178 nn11 = g_cat_n(req, nn11, tokB, tokB_n) 179 nn11 = g_cat(req, nn11, "\r\n\r\n" as *u8) 180 let o11: i64 = epd_handle(ctx, domain, prefix, req, nn11, out) 181 var t11: i64 = 0 182 if g_starts(out, o11, "HTTP/1.1 200" as *u8) == 1 { 183 var n11b: i64 = g_cat(req, 0, "GET /mail/inbox HTTP/1.1\r\nHost: x\r\nX-Nishi-Session: " as *u8) 184 n11b = g_cat_n(req, n11b, tokB, tokB_n) 185 n11b = g_cat(req, n11b, "\r\n\r\n" as *u8) 186 let o11b: i64 = epd_handle(ctx, domain, prefix, req, n11b, out) 187 if g_contains(out, o11b, "zetamarker" as *u8) == 0 { t11 = 1 } 188 } 189 pass = pass + g_verdict("T11 delete -> tombstoned, no longer listed:" as *u8, t11) 190 191 // T12: whoami carries the session identity 192 var n12: i64 = g_cat(req, 0, "GET /mail/whoami HTTP/1.1\r\nHost: x\r\nX-Nishi-Session: " as *u8) 193 n12 = g_cat_n(req, n12, tokA, tokA_n) 194 n12 = g_cat(req, n12, "\r\n\r\n" as *u8) 195 let o12: i64 = epd_handle(ctx, domain, prefix, req, n12, out) 196 var t12: i64 = 0 197 if g_starts(out, o12, "HTTP/1.1 200" as *u8) == 1 { if g_contains(out, o12, "\"mailbox\":\"alice@andelinwest.com\"" as *u8) == 1 { t12 = 1 } } 198 pass = pass + g_verdict("T12 whoami -> alice@andelinwest.com:" as *u8, t12) 199 200 g_w("----\nEMAIL-PORTAL rows=12 pass=" as *u8) 201 let db: *u8 = sys_mmap(8); var v3: i64 = pass; var k3: i64 = 0 202 if v3 == 0 { db[0] = 48 as u8; k3 = 1 } else { while v3 > 0 { db[k3] = (48 + (v3 % 10)) as u8; v3 = v3 / 10; k3 = k3 + 1 } } 203 let rv: *u8 = sys_mmap(8); var x3: i64 = 0 204 while x3 < k3 { rv[x3] = db[k3 - 1 - x3]; x3 = x3 + 1 } 205 sys_write(1, rv, k3); g_w("\n" as *u8) 206 let lg: i64 = sys_openat_append("knowledge/status/email_portal_gate.log" as *u8, 0x1a4) 207 if lg >= 0 { 208 g_lw(lg, "EMAIL-PORTAL auth=modern-opaque shell=" as *u8); if t1 == 1 { g_lw(lg, "1" as *u8) } else { g_lw(lg, "0" as *u8) } 209 g_lw(lg, " register_login=" as *u8); if t2 == 1 { g_lw(lg, "1" as *u8) } else { g_lw(lg, "0" as *u8) } 210 g_lw(lg, " bad_handle_400=" as *u8); if t3 == 1 { g_lw(lg, "1" as *u8) } else { g_lw(lg, "0" as *u8) } 211 g_lw(lg, " invite_survives_refusal=" as *u8); if t4 == 1 { g_lw(lg, "1" as *u8) } else { g_lw(lg, "0" as *u8) } 212 g_lw(lg, " deny_no_session=" as *u8); if t5 == 1 { g_lw(lg, "1" as *u8) } else { g_lw(lg, "0" as *u8) } 213 g_lw(lg, " local_delivery=" as *u8); if t6 == 1 { g_lw(lg, "1" as *u8) } else { g_lw(lg, "0" as *u8) } 214 g_lw(lg, " xss_escaped=" as *u8); if t7 == 1 { g_lw(lg, "1" as *u8) } else { g_lw(lg, "0" as *u8) } 215 g_lw(lg, " read_msg=" as *u8); if t8 == 1 { g_lw(lg, "1" as *u8) } else { g_lw(lg, "0" as *u8) } 216 g_lw(lg, " isolation=" as *u8); if t9 == 1 { g_lw(lg, "1" as *u8) } else { g_lw(lg, "0" as *u8) } 217 g_lw(lg, " external_queue_honest=" as *u8); if t10 == 1 { g_lw(lg, "1" as *u8) } else { g_lw(lg, "0" as *u8) } 218 g_lw(lg, " tombstone_delete=" as *u8); if t11 == 1 { g_lw(lg, "1" as *u8) } else { g_lw(lg, "0" as *u8) } 219 g_lw(lg, " whoami=" as *u8); if t12 == 1 { g_lw(lg, "1" as *u8) } else { g_lw(lg, "0" as *u8) } 220 g_lw(lg, " rows=12 pass=" as *u8); sys_write(lg, rv, k3) 221 if pass == 12 { g_lw(lg, " verdict=GREEN\n" as *u8) } else { g_lw(lg, " verdict=RED\n" as *u8) } 222 sys_close(lg) 223 } 224 if pass == 12 { g_w("EMAIL-PORTAL GREEN (Modern Auth gates the mailboxes; per-session isolation; escaped rendering; honest external queue)\n" as *u8); sys_exit(0); return 0 } 225 g_w("EMAIL-PORTAL RED\n" as *u8); sys_exit(1); return 1 226}