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 (<script>), 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, "<script>" 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}