code wiki / _hdl_build / nx_rebac_gate.nx
nx_rebac_gate.nx source
↩ module page · 133 lines · 12363 B
1// nx_rebac_gate.nx -- ADVERSARIAL GATE for the sovereign ReBAC plane (nx_rebac). The CRITIC + ADVERSARY: a
2// legit-access/attack matrix where every escalation, revocation-bypass, cross-scope leak, block-bypass, and
3// userset-cycle MUST resolve correctly (deny-by-default). Green ONLY if all ~28 checks pass. license_tier: ORIGINAL
4// expect_exit: 0
5import "nx_rebac.nx"
6import "nx_gate_verdict.nx" // THE BASE CLASS: gv_ctr/gv_check/gv_verdict make declared == executed
7
8// ONE TOOTH PER CHECK, COUNTED BY THE BASE CLASS (migrated 2026-08-31). THE DEFECT THIS CLOSES: the
9// old ck() set a single okp[0] boolean and the gate ended `verdict=GREEN (28 checks: ...)` with the 28
10// HAND-TYPED IN PROSE. nx_swcompare_evidence.ev_parse_pass looks for `pass=N/M`, `passed N/M` or
11// `PASS=N FAIL=K`; this gate emitted NONE of them, so the evidence layer recorded pass=0/0 and the
12// authz board carried a GREEN on a tooth counter saying ZERO TEETH RAN. A hand-typed 28 is the same
13// fabricated-constant class as a counter emitted but never incremented -- add a check and the prose
14// silently lies. gv_ctr/gv_check make declared == executed BY CONSTRUCTION: the denominator IS the
15// teeth that ran. The ALLOW/DENY diagnostic is KEPT and printed BEFORE the verdict, because a gate
16// that reports a boolean cannot say why.
17func ck(label: *u8, got: i64, want: i64, ctr: *i64) -> i64 {
18 var ok: i64 = 0
19 if got == want { ok = 1 }
20 rb_p(" REBAC got=" as *u8)
21 if got == 1 { rb_p("ALLOW" as *u8) } else { rb_p("DENY " as *u8) }
22 rb_p(" want=" as *u8); rb_pn(want); rb_p(" " as *u8)
23 return gv_check(label, ok, ctr)
24}
25
26func main() -> i64 {
27 rb_p("=== NX-REBAC ADVERSARIAL GATE (Zanzibar/ReBAC: tuple check + userset rewrite + tombstone + block + HR override) ===\n" as *u8)
28 let ctr: *i64 = gv_ctr() // [0]=pass [1]=total [2]=preconditions-missing; the counter IS the denominator
29
30 // fresh store prefix (own the isolation; sys_now_us keeps commits time-ordered for latest-wins)
31 let prefix: *u8 = sys_mmap(128); var po: i64 = 0
32 po = rb_cat(prefix, po, "/tmp/rebac_" as *u8); po = rb_catn(prefix, po, sys_now_us()); po = rb_cat(prefix, po, "_" as *u8); prefix[po] = 0 as u8
33
34 // ---- SETUP (grants) ----
35 // group:elders owned by alice, bob is a member; doc:budget owned by alice, SHARED with elders#member (userset);
36 // tenant:andelinwest admin=alice member=bob; alice<->bob mutual friends.
37 rb_put(prefix, "group:elders" as *u8, "owner" as *u8, "user:alice" as *u8, "system" as *u8, 1)
38 rb_put(prefix, "group:elders" as *u8, "member" as *u8, "user:bob" as *u8, "user:alice" as *u8, 1)
39 rb_put(prefix, "doc:budget" as *u8, "owner" as *u8, "user:alice" as *u8, "system" as *u8, 1)
40 rb_put(prefix, "doc:budget" as *u8, "viewer" as *u8, "group:elders#member" as *u8, "user:alice" as *u8, 1)
41 rb_put(prefix, "tenant:andelinwest" as *u8, "admin" as *u8, "user:alice" as *u8, "system" as *u8, 1)
42 rb_put(prefix, "tenant:andelinwest" as *u8, "member" as *u8, "user:bob" as *u8, "user:alice" as *u8, 1)
43 rb_put(prefix, "user:alice" as *u8, "friend" as *u8, "user:bob" as *u8, "user:bob" as *u8, 1)
44 rb_put(prefix, "user:bob" as *u8, "friend" as *u8, "user:alice" as *u8, "user:alice" as *u8, 1)
45
46 // ---- ALLOW (legitimate access) ----
47 ck("A1 alice manage doc:budget (owner)" as *u8, rb_check(prefix, "user:alice" as *u8, "manage" as *u8, "doc:budget" as *u8, 0), 1, ctr)
48 ck("A2 bob read doc:budget (member->elders#member->viewer USERSET)" as *u8, rb_check(prefix, "user:bob" as *u8, "read" as *u8, "doc:budget" as *u8, 0), 1, ctr)
49 ck("A3 alice read doc:budget (owner->read rewrite)" as *u8, rb_check(prefix, "user:alice" as *u8, "read" as *u8, "doc:budget" as *u8, 0), 1, ctr)
50 ck("A4 bob view group:elders (member)" as *u8, rb_check(prefix, "user:bob" as *u8, "view" as *u8, "group:elders" as *u8, 0), 1, ctr)
51 ck("A5 alice manage group:elders (owner)" as *u8, rb_check(prefix, "user:alice" as *u8, "manage" as *u8, "group:elders" as *u8, 0), 1, ctr)
52 ck("A6 bob read tenant:andelinwest (member)" as *u8, rb_check(prefix, "user:bob" as *u8, "read" as *u8, "tenant:andelinwest" as *u8, 0), 1, ctr)
53 ck("A7 alice friend bob (mutual)" as *u8, rb_check(prefix, "user:alice" as *u8, "friend" as *u8, "user:bob" as *u8, 0), 1, ctr)
54
55 // ENUMERATORS (the surface lists a caller's circle from these): alice's friends include bob; bob's groups include elders
56 let el: *i64 = sys_mmap(8 * 64) as *i64
57 let enf: i64 = rb_list_subjects(prefix, "user:alice" as *u8, "friend" as *u8, el, 64)
58 var friend_listed: i64 = 0
59 if enf >= 1 { if rb_in_list(el, enf, "user:bob" as *u8) == 1 { friend_listed = 1 } }
60 ck("E1 rb_list_subjects: alice friends include bob" as *u8, friend_listed, 1, ctr)
61 let eg: *i64 = sys_mmap(8 * 64) as *i64
62 let eng: i64 = rb_list_objects_for_sub(prefix, "user:bob" as *u8, "member" as *u8, eg, 64)
63 var group_listed: i64 = 0
64 if eng >= 1 { if rb_in_list(eg, eng, "group:elders" as *u8) == 1 { group_listed = 1 } }
65 ck("E2 rb_list_objects_for_sub: bob groups include elders" as *u8, group_listed, 1, ctr)
66
67 // ---- DENY (adversary: no grant / wrong relation / escalation) ----
68 ck("D1 carol read doc:budget (not a member)" as *u8, rb_check(prefix, "user:carol" as *u8, "read" as *u8, "doc:budget" as *u8, 0), 0, ctr)
69 ck("D2 bob WRITE doc:budget (only viewer, not editor)" as *u8, rb_check(prefix, "user:bob" as *u8, "write" as *u8, "doc:budget" as *u8, 0), 0, ctr)
70 ck("D3 mallory read tenant:andelinwest (no grant)" as *u8, rb_check(prefix, "user:mallory" as *u8, "read" as *u8, "tenant:andelinwest" as *u8, 0), 0, ctr)
71 ck("D4 bob MANAGE group:elders (member != admin ESCALATION)" as *u8, rb_check(prefix, "user:bob" as *u8, "manage" as *u8, "group:elders" as *u8, 0), 0, ctr)
72 ck("D5 carol friend alice (not friends)" as *u8, rb_check(prefix, "user:carol" as *u8, "friend" as *u8, "user:alice" as *u8, 0), 0, ctr)
73
74 // ---- REVOCATION (tombstone must WIN; scoped, not global) ----
75 // T1 defriend: tombstone both directions -> no longer friends
76 rb_put(prefix, "user:alice" as *u8, "friend" as *u8, "user:bob" as *u8, "user:alice" as *u8, 0)
77 rb_put(prefix, "user:bob" as *u8, "friend" as *u8, "user:alice" as *u8, "user:bob" as *u8, 0)
78 ck("T1 defriended: alice friend bob (tombstone wins)" as *u8, rb_check(prefix, "user:alice" as *u8, "friend" as *u8, "user:bob" as *u8, 0), 0, ctr)
79 // T2 leave group: bob leaves elders -> loses doc VIA the group, but KEEPS the independent tenant membership
80 rb_put(prefix, "group:elders" as *u8, "member" as *u8, "user:bob" as *u8, "user:bob" as *u8, 0)
81 ck("T2a left-group: bob read doc:budget (userset re-eval DENIES)" as *u8, rb_check(prefix, "user:bob" as *u8, "read" as *u8, "doc:budget" as *u8, 0), 0, ctr)
82 ck("T2b scoped: bob read tenant:andelinwest STILL allowed" as *u8, rb_check(prefix, "user:bob" as *u8, "read" as *u8, "tenant:andelinwest" as *u8, 0), 1, ctr)
83 // T3 re-grant then unshare: re-join proves re-grant works; unshare the doc-userset kills it again
84 rb_put(prefix, "group:elders" as *u8, "member" as *u8, "user:bob" as *u8, "user:alice" as *u8, 1)
85 ck("T3a re-joined: bob read doc:budget allowed again (re-grant)" as *u8, rb_check(prefix, "user:bob" as *u8, "read" as *u8, "doc:budget" as *u8, 0), 1, ctr)
86 rb_put(prefix, "doc:budget" as *u8, "viewer" as *u8, "group:elders#member" as *u8, "user:alice" as *u8, 0)
87 ck("T3b unshared: bob read doc:budget DENIED (userset tombstone)" as *u8, rb_check(prefix, "user:bob" as *u8, "read" as *u8, "doc:budget" as *u8, 0), 0, ctr)
88
89 // ---- BLOCK (consent axis: blocks OUTRANK any relation) ----
90 rb_put(prefix, "user:bob" as *u8, "blocked" as *u8, "user:mallory" as *u8, "user:bob" as *u8, 1)
91 ck("B1 mallory friend bob after BLOCK (blocks outrank)" as *u8, rb_check(prefix, "user:mallory" as *u8, "friend" as *u8, "user:bob" as *u8, 0), 0, ctr)
92 ck("B2 mallory view bob after BLOCK" as *u8, rb_check(prefix, "user:mallory" as *u8, "view" as *u8, "user:bob" as *u8, 0), 0, ctr)
93
94 // ---- HR OPERATOR OVERRIDE (the tie to Nishi HR level) ----
95 ck("H1 mallory read tenant via HR operator lvl 3 (god-in-realm)" as *u8, rb_check_hr(prefix, "user:mallory" as *u8, "read" as *u8, "tenant:andelinwest" as *u8, 3), 1, ctr)
96 ck("H2 mallory read tenant via HR member lvl 1 (no tuple -> deny)" as *u8, rb_check_hr(prefix, "user:mallory" as *u8, "read" as *u8, "tenant:andelinwest" as *u8, 1), 0, ctr)
97
98 // ---- WRITE AUTHORITY (a member cannot grant; unclaimed object bootstraps; then closes) ----
99 ck("W1 bob may_grant doc:budget (viewer, not manage -> NO)" as *u8, rb_may_grant(prefix, "user:bob" as *u8, "doc:budget" as *u8), 0, ctr)
100 ck("W2 alice may_grant doc:budget (owner -> YES)" as *u8, rb_may_grant(prefix, "user:alice" as *u8, "doc:budget" as *u8), 1, ctr)
101 ck("W3a carol may_grant group:new (UNCLAIMED -> bootstrap YES)" as *u8, rb_may_grant(prefix, "user:carol" as *u8, "group:new" as *u8), 1, ctr)
102 rb_put(prefix, "group:new" as *u8, "owner" as *u8, "user:carol" as *u8, "user:carol" as *u8, 1)
103 ck("W3b mallory may_grant group:new (now CLAIMED by carol -> NO)" as *u8, rb_may_grant(prefix, "user:mallory" as *u8, "group:new" as *u8), 0, ctr)
104
105 // ---- NESTED USERSET (positive, multi-level): leads subgroup nested in elders, doc shared with elders ----
106 rb_put(prefix, "group:leads" as *u8, "member" as *u8, "user:carol" as *u8, "user:carol" as *u8, 1)
107 rb_put(prefix, "group:elders" as *u8, "member" as *u8, "group:leads#member" as *u8, "user:alice" as *u8, 1)
108 rb_put(prefix, "doc:plan" as *u8, "owner" as *u8, "user:alice" as *u8, "system" as *u8, 1)
109 rb_put(prefix, "doc:plan" as *u8, "viewer" as *u8, "group:elders#member" as *u8, "user:alice" as *u8, 1)
110 ck("U1 carol read doc:plan (carol->leads->elders->doc NESTED userset)" as *u8, rb_check(prefix, "user:carol" as *u8, "read" as *u8, "doc:plan" as *u8, 0), 1, ctr)
111
112 // ---- CYCLE TERMINATION (adversary DoS: mutual group membership must NOT hang) ----
113 rb_put(prefix, "group:ca" as *u8, "member" as *u8, "group:cb#member" as *u8, "user:alice" as *u8, 1)
114 rb_put(prefix, "group:cb" as *u8, "member" as *u8, "group:ca#member" as *u8, "user:alice" as *u8, 1)
115 ck("C1 cyclic groups TERMINATE (mallory read group:ca -> bounded DENY, no hang)" as *u8, rb_check(prefix, "user:mallory" as *u8, "read" as *u8, "group:ca" as *u8, 0), 0, ctr)
116
117 // ---- CROSS-SURFACE (rb_check2, two stores): a GROUP in store2 grants an OBJECT owned in store1 ----
118 let p2: *u8 = sys_mmap(128); var p2o: i64 = 0
119 p2o = rb_cat(p2, p2o, "/tmp/rebac2_" as *u8); p2o = rb_catn(p2, p2o, sys_now_us()); p2o = rb_cat(p2, p2o, "_" as *u8); p2[p2o] = 0 as u8
120 rb_put(p2, "group:choir" as *u8, "member" as *u8, "user:zoe" as *u8, "user:lead" as *u8, 1) // store2 = the "relate" surface
121 rb_put(prefix, "doc:hymnal" as *u8, "owner" as *u8, "user:lead" as *u8, "system" as *u8, 1) // store1 = the "office" surface
122 rb_put(prefix, "doc:hymnal" as *u8, "viewer" as *u8, "group:choir#member" as *u8, "user:lead" as *u8, 1)
123 ck("X1 CROSS-SURFACE: zoe (store2 group member) reads doc in store1" as *u8, rb_check2(prefix, p2, "user:zoe" as *u8, "read" as *u8, "doc:hymnal" as *u8, 0), 1, ctr)
124 ck("X2 non-member frank DENIED cross-surface" as *u8, rb_check2(prefix, p2, "user:frank" as *u8, "read" as *u8, "doc:hymnal" as *u8, 0), 0, ctr)
125 rb_put(p2, "group:choir" as *u8, "member" as *u8, "user:zoe" as *u8, "user:lead" as *u8, 0) // revoke in store2
126 ck("X3 revoke store2 group membership -> zoe LOSES the store1 doc" as *u8, rb_check2(prefix, p2, "user:zoe" as *u8, "read" as *u8, "doc:hymnal" as *u8, 0), 0, ctr)
127
128 // THE VERDICT NOTE NO LONGER RECITES THE TEETH. gv_verdict prints `passed <p>/<t>` from the
129 // counter that actually ran them, so a check added tomorrow cannot make this line lie -- the old
130 // note's hand-typed "28 checks" was a fabricated constant wearing the shape of a measurement, and
131 // it was the ONLY place this gate ever stated a denominator.
132 return gv_verdict("REBAC-GATE" as *u8, ctr, "userset rewrites + nested groups + tombstone revocation + scoped-not-global + blocks-outrank + HR operator override + write-authority-no-escalation + cycle-termination" as *u8)
133}