Skip to content

Commit 683acf2

Browse files
committed
docs(adr-0069): record ruling C′'s enforcement and the API-key admission seam
A dated status note names what PR #23440 and PR #23508 enforce on main: the closed posture vocabulary and its scopes, the held outcome of the shared key-admission seam, every key-admitting seam asking the owner's posture, and the forced reset's revocation, on by default. The seam summary gains the API-key admission seam as its sixth entry, so "each row names exactly one of these seams" stays true for D3. Claude-Session: https://claude.ai/code/session_01XgS6gBdsbPNicv9ELY6ne5 Co-authored-by: Claude <noreply@anthropic.com>
1 parent 34b740f commit 683acf2

1 file changed

Lines changed: 8 additions & 0 deletions

File tree

‎docs/adr/0069-enterprise-authentication-hardening.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ Plus admin-facing surface: a `sys_user` list-view filter for locked accounts + a
169169
3. **Hono auth-route middleware** (in `plugin-auth` route registration, *before* delegating to better-auth) — IP allowlist, rate-limit pre-check.
170170
4. **Session-validation path** (resolve-execution-context / `customSession`) — MFA step-up gate, password-expiry gate, idle-timeout, locked-account rejection.
171171
5. **`bindAuthSettings` -> `applyConfigPatch`** — the existing channel that turns every new setting into live config without restart.
172+
6. **API-key admission** (`resolveApiKeyAdmission` in `packages/core/src/security/api-key.ts`, fed by `userAuthGates`) — the key half of D3's amended note: an owner's `account`-scope posture holds the key (`held`).
172173

173174
Each row in D1-D6 names exactly one of these seams. No setting is introduced without one.
174175

@@ -189,6 +190,13 @@ Each row in D1-D6 names exactly one of these seams. No setting is introduced wit
189190
| **P2/P3** (D6) | 🟡 partial | Generic OIDC RP wired (`genericOAuth`/`sso`); admin OIDC **trust-list settings UI** still env/`sys_sso_provider`-only. |
190191
| **P3** (SAML, broader social) | 🟡 partial | `@better-auth/sso` present (SAML now better-auth-native — see Addendum); broader settings-driven social providers pending. |
191192

193+
**Status (2026-10-14): ruling C′ is enforced.** D3's amended note holds on `main` through PR [#23440](https://github.com/objectstack-ai/objectstack/pull/23440) (`0f059078ec`) and PR [#23508](https://github.com/objectstack-ai/objectstack/pull/23508) (`daafae6d6d`):
194+
195+
- **The posture vocabulary is closed.** `AuthGateCodeSchema` (`@objectstack/spec/kernel`) is the closed set of codes an `authGate` may carry: `PASSWORD_EXPIRED`, `PASSWORD_RESET_REQUIRED` and `MFA_REQUIRED`. `AUTH_GATE_SCOPES`, typed total over it, declares each code's scope once: `MFA_REQUIRED` holds the **account** (every credential of the user); `PASSWORD_EXPIRED` and `PASSWORD_RESET_REQUIRED` hold the **credential** (password sign-in alone).
196+
- **An API key answers as the session does** (seam 6). `resolveApiKeyAdmission` asks `userAuthGates` for the key's owner and returns the `held` outcome on an `account`-scope posture; the door refuses the key with the posture's code. Nothing is written: the same key is admitted again once the posture clears. A `credential`-scope posture leaves the key admitted.
197+
- **Every seam that admits a key asks.** PR #23508 made `userAuthGates` a required input of `resolveAuthzContext`, `resolveApiKeyAdmission` and `resolveApiKeyPrincipal`, which closed the two seams that still admitted a held owner's key: the `@objectstack/cloud-connection` install-local resolver and the `@objectstack/mcp` stdio face. The runtime dispatcher, the REST data API, the settings, storage-download, datasource-admin and share-link routes pass the source as well. `resolveApiKeyPrincipal` answers a held owner's key with no principal.
198+
- **The forced reset revokes, on by default.** An administrator's forced password reset (D7) carries `revokeSessionsAndKeys`: it ends every session of the user (the D4 mechanism) and revokes every API key they own. It is on unless the administrator passes a literal `false`. Unlike the hold, this is a revocation: a revoked key stays revoked after the password change.
199+
192200
## Out of scope / deferred
193201

194202
- **SCIM / directory provisioning & bulk import** — separate ADR (provisioning, not authentication); tracked in #2246.

0 commit comments

Comments
 (0)