Skip to content

Commit b3235b5

Browse files
os-zhuangclaude
andauthored
ci: expand adr-anchors to the full authz/security ADR set — 8 → 15 (#4575 follow-up) (#4594)
The seed anchors covered only #3723's blast radius. This is the bounded sweep promised in that PR: audit ADR-0057/0066/0068/0090/0091/0095/0105 for other load-bearing realizations — spots where a reasonable engineer could "fix" the code and be reverting a decision — and register them. Seven files added, each with its reversal story (see the changeset): the posture ladder (capability-derived, never role-read — #2836), grant validity (resolution-time, never a cleanup job), the tenant wall (no shared compiler with business RLS), the org-admin auto-grant (no_bypass under wall-less postures — F2), invitation placement (issuance dry-run; acceptance runs system context), position flatness (no `parent` — the retired ADR-0057 D5 mistake), and the permission evaluator (wildcard-derived superuser bypass, no stored boolean; scope depth). Plus ADR-0068 on the existing resolve-authz-context anchor: `platform_admin` is derived from an unscoped grant — the classic "add an is_admin column" reversal target. Zero code changes: every file already cited its governing ADRs, so the sweep only registers what must not be silently un-cited. All 15 pass; negative- tested by stripping ADR-0091 from grant-validity.ts. Claude-Session: https://claude.ai/code/session_0186LhwkUBupmLJUUAMda5hU Co-authored-by: Claude <noreply@anthropic.com>
1 parent ff17642 commit b3235b5

2 files changed

Lines changed: 55 additions & 2 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
---
3+
4+
Tooling-only: expand `scripts/adr-anchors.json` from 8 to 15 anchors — the bounded authz/security ADR sweep promised in #4575. Releases nothing.
5+
6+
The first 8 anchors covered only #3723's blast radius. This sweep audited the decisions in ADR-0057 / 0066 / 0068 / 0090 / 0091 / 0095 / 0105 for other load-bearing realizations — places where a reasonable engineer could "fix" the code and be reverting a decision. Seven files added, each with its reversal story:
7+
8+
- `posture-ladder.ts` (0095) — posture derives from capability grants; re-reading the better-auth role reopens the #2836 dual-track class.
9+
- `grant-validity.ts` (0091) — window enforcement lives at resolution time; "optimize it into a cleanup job" is the banned move (ADR-0049).
10+
- `tenant-layer.ts` (0095 D1) — Layer 0 shares no compiler/merge/bypass with business RLS; "deduplicate into the RLS compiler" would let a Layer-1 change weaken tenant isolation.
11+
- `auto-org-admin-grant.ts` (0105) — wall-less postures get `organization_admin_no_bypass`; "why two sets?" collapses into an environment-wide superuser (the F2 finding).
12+
- `invitation-placement.ts` (0105 D8) — issuance dry-runs the gate; "acceptance re-checks anyway" is false (acceptance runs under system context) and skipping it is an escalation hole.
13+
- `position.zod.ts` (0090 D3) — positions are flat; adding `parent` is the exact mistake ADR-0057 D5 retired.
14+
- `permission-evaluator.ts` (0066 D2 + 0057 D1) — superuser bypass derives from the wildcard grant, no stored boolean and no role fast-path.
15+
16+
Plus one extension: the existing `resolve-authz-context.ts` anchor gains ADR-0068 (`platform_admin` is derived from an unscoped grant — no trusted stored boolean, the classic "add an `is_admin` column" reversal target).
17+
18+
All 15 pass as-is — every anchored file already cited its governing ADRs — so this changes zero code, only registers what must not be silently un-cited. Negative-tested by stripping `ADR-0091` from `grant-validity.ts` (fails, printing the invariant).

scripts/adr-anchors.json

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@
2828
},
2929
{
3030
"file": "packages/core/src/security/resolve-authz-context.ts",
31-
"adrs": ["ADR-0095"],
32-
"invariant": "No enforcement-time code path may consult the better-auth role directly. `mapMembershipRole` is a grant-PROVISIONING concern here; posture and adjudication run off the resulting capability grants."
31+
"adrs": ["ADR-0068", "ADR-0095"],
32+
"invariant": "No enforcement-time code path may consult the better-auth role directly`mapMembershipRole` is a grant-PROVISIONING concern here; posture and adjudication run off the resulting capability grants (ADR-0095 D3). `platform_admin` is DERIVED from an unscoped `admin_full_access` user grant — there is no trusted stored boolean (ADR-0068 D2)."
3333
},
3434
{
3535
"file": "packages/plugins/plugin-security/src/delegated-admin-gate.ts",
@@ -40,6 +40,41 @@
4040
"file": "packages/plugins/plugin-auth/src/invitation-role-cap.ts",
4141
"adrs": ["ADR-0105"],
4242
"invariant": "An invitation may add a person, never authority above the issuer's own grade. An issuer below admin grade may invite as plain `member` only."
43+
},
44+
{
45+
"file": "packages/core/src/security/posture-ladder.ts",
46+
"adrs": ["ADR-0095"],
47+
"invariant": "Posture DERIVES from held capability grants, never from a better-auth role (D3) — re-reading the role here reopens the #2836 dual-track class. The ladder is monotonic: each rung's visible set contains the rung below's, one rung maps to exactly one injection rule, and EXTERNAL is deny-by-default (explicit shares only; OWD never widens it)."
48+
},
49+
{
50+
"file": "packages/core/src/security/grant-validity.ts",
51+
"adrs": ["ADR-0091"],
52+
"invariant": "A grant row outside its validity window must not resolve, and correctness lives at RESOLUTION time — never in a cleanup job (ADR-0049: no unenforced security properties). Half-open `[from, until)` in UTC; absent bounds are unbounded; a present-but-unparseable bound disables the grant, fail closed."
53+
},
54+
{
55+
"file": "packages/plugins/plugin-security/src/tenant-layer.ts",
56+
"adrs": ["ADR-0095"],
57+
"invariant": "Layer 0 (the tenant wall) is an independent, always-first, AND-composed filter sharing no compiler, no merge step and no bypass bit with business RLS (D1). Merging it into the RLS compiler would let a Layer-1 change weaken tenant isolation and the superuser business-RLS bypass cross the wall."
58+
},
59+
{
60+
"file": "packages/plugins/plugin-security/src/auto-org-admin-grant.ts",
61+
"adrs": ["ADR-0105"],
62+
"invariant": "The one provisioning bridge from an admin-grade membership to the `organization_admin` capability grant — idempotent, and revoked on demotion. Under a wall-less posture it hands out `organization_admin_no_bypass` instead: blanket record visibility must be granted deliberately, never as a side effect of a membership role (the ADR-0105 F2 finding)."
63+
},
64+
{
65+
"file": "packages/plugins/plugin-security/src/invitation-placement.ts",
66+
"adrs": ["ADR-0105"],
67+
"invariant": "Placement intent is authorized at ISSUANCE by dry-running `DelegatedAdminGate` against the very `sys_user_position` rows acceptance would write — an invitation can never place what its issuer could not have assigned directly. Acceptance runs under system context, so skipping the issuance check ('acceptance re-checks anyway') would be an escalation hole, not a simplification."
68+
},
69+
{
70+
"file": "packages/spec/src/identity/position.zod.ts",
71+
"adrs": ["ADR-0090"],
72+
"invariant": "Positions are deliberately FLAT — no `parent`, no hierarchy (D3). Visibility rollup lives on the business-unit tree; adding a second tree here is the mistake ADR-0057 D5 retired and D3 bans, and the word 'role' is reserved-forbidden in this vocabulary."
73+
},
74+
{
75+
"file": "packages/plugins/plugin-security/src/permission-evaluator.ts",
76+
"adrs": ["ADR-0057", "ADR-0066"],
77+
"invariant": "The superuser bypass derives solely from a resolved set carrying the `objects['*']` wildcard with `viewAllRecords`/`modifyAllRecords` (ADR-0066 D2) — no stored boolean, no role fast-path. Scope depth (own/unit/unit_and_below/org) resolves to an effective access depth per operation class (ADR-0057 D1); an unenforceable depth is a compile error, never silent fail-open (ADR-0049)."
4378
}
4479
]
4580
}

0 commit comments

Comments
 (0)