Fold in Gemini (round-3, third model family) auth findings

- Gemini-BLOCK-1: AllowedOAuthScopes strictly filtering a V2 pre-token Lambda's output
  is unverified -> fatal Phase-0a check (§6 #8a); if it fails, pre-token fail-closed
  becomes the primary boundary.
- Gemini-BLOCK-2: Cognito access tokens carry client_id/scopes, not native aud -> align
  §1h + facade/server checks to client_id allow-list / scope-prefix audience proxy.
- Gemini-Q: 0a spike must run on production-equivalent Enterprise Grid + real licenses.
- Gemini-NIT (path-corrected): project memory is a private store at ~/.claude/.../memory/,
  not the repo and not Gemini's own ~/.gemini path.
Three model families now converge: structural plan sound; only open risk is the auth
token-mint mechanism, fully spike-gated in Phase 0a.
This commit is contained in:
Adam Moussa 2026-06-11 13:22:55 -04:00
parent 70987522b2
commit 9cf124a24b

View file

@ -144,8 +144,13 @@ credential that requests **only its tier's scopes**. Mechanism (specified, testa
Finance client → `finance:read`; Lauren Exec client → `ops:read ops:tasks gmail:self calendar:self` (**never
finance**). Cognito's token endpoint **cannot issue a scope outside a client's `AllowedOAuthScopes`** — so even
though Lauren-the-person holds `finance:read` (via `-finance@`), the Exec client can never obtain a token
carrying it. This is the boundary — it does NOT depend on the pre-token Lambda, and it is independent of
design.md §2.3's group→scope union.
carrying it. This is the intended boundary — independent of design.md §2.3's group→scope union.
**CAVEAT (Gemini round-3 BLOCK — UNVERIFIED, fatal Phase-0a check):** this rests on Cognito **strictly
filtering the issued token's scopes to the client's `AllowedOAuthScopes` even when a pre-token V2 Lambda
runs**. In some Cognito configs a V2 pre-token Lambda can *return scopes exceeding* the client's allow-list — if
so, the app-client control is soft and the pre-token Lambda becomes load-bearing after all. Verify "issued
scopes ⊆ `AllowedOAuthScopes` regardless of Lambda output" as a **fatal** 0a gate item (§6 #8); if it does NOT
hold, the pre-token fail-closed suppression below is promoted from backstop to primary and gets the heavier test.
- **AMENDS design.md §2.3 (BLOCK-1 / F2).** §2.3 describes a plain **union** of a user's group-scopes (and even
shows Lauren's token *with* `finance:read`); the substrate is ambiguous on per-audience subsetting. This plan
**amends** §2.3: group→scope mapping still defines what a user *may* hold, but the **issued token is bounded by
@ -219,6 +224,22 @@ and FIX is addressed below; this revision supersedes the pre-audit text wherever
flip-point clarified as operational re-announce. **Q1** fallback-scope honesty + **Q2** freshness-bound
mechanism documented.
**Round 3 (Gemini `scanner`, third model family — 2026-06-11).** Triangulated the auth nerve all three families
flagged; two sharp BLOCKs folded in:
- **Gemini-BLOCK-1:** `AllowedOAuthScopes` strictly filtering a V2 pre-token Lambda's output is **unverified** —
added as a fatal Phase-0a check (§6 #8a); if it doesn't hold, pre-token fail-closed becomes the primary boundary.
(Sharpens BLOCK-1: neither layer is *independently* guaranteed until this interaction is proven.)
- **Gemini-BLOCK-2:** Cognito access tokens carry `client_id`/scopes, **not a native `aud`** — aligned §1h and the
facade/server checks to a **`client_id` allow-list / scope-prefix audience proxy** (§0.1, §1h, §6 #8c).
- **Gemini-Q:** the 0a spike must run on a **production-equivalent Enterprise Grid org with real licenses**, not a
Dev/non-Grid Slack (false-positive risk) — §6 #6.
- **Gemini-NIT (path-corrected):** project memory is a private store at `~/.claude/projects/.../memory/`, distinct
from repo READMEs — §4. (Gemini cited its own `~/.gemini/...` path; corrected — a cross-family infra-fact
assertion to verify, not adopt, per `feedback_fable_review_gates`.)
Gemini APPROVED the rest: B1/B5 parallel-run dual-feed + Bolt fallback sound; §1a identity segregation coherently
integrated with the token-layer mechanism. **All three families (Claude/Fable, GPT-4.1, Gemini) now converge: the
structural plan is sound; the only open risk is the auth token-mint mechanism, fully spike-gated in Phase 0a.**
**Cross-family review (GPT-4.1 `cross_reviewer`, 2026-06-11) — auth/trifecta sections.** Run per the mandatory
cross-review gate for auth/IAM design (Fable is Claude-family, not a substitute). Findings folded in:
- **CR-1 (BLOCK):** validate `aud` at the edge **AND** server-side (defense in depth) — the per-tier authorizer
@ -436,9 +457,11 @@ real home:
deprecating each bot (design.md §6, §7.3 parity gate).
**Core security tests (authoritative, transport-agnostic, in `sh-mcp`, design.md §7.3):**
**audience-binding rejection at the per-tier facade AND re-validated server-side** (an `aud=sh-mcp-ops` token
rejected by the `sh-mcp-finance` gateway authorizer *and* by the finance server itself — B3/CR-1, defense in
depth); per-tool **server-side** scope enforcement; **per-agent credential mints only its tier's scopes** (a
**audience-binding rejection at the per-tier facade AND re-validated server-side** — bound on the chosen
audience proxy (**`client_id` allow-list per gateway, or resource-server scope-prefix**, since Cognito access
tokens carry `client_id`/scopes, **not a native `aud` claim** unless injected via pre-token V2 — Gemini round-3):
an ops-tier token is rejected by the `sh-mcp-finance` gateway authorizer *and* by the finance server itself
(B3/CR-1, defense in depth); per-tool **server-side** scope enforcement; **per-agent credential mints only its tier's scopes** (a
token obtained via the Lauren Exec app client never contains `finance:read`, even though the person holds it —
B4); **pre-token fails closed on a cross-audience scope** (CR-3); **a finance-audience token cannot reach a
Gmail/Calendar tool** (CR-6); **backend rejects a token whose `sub` is not a valid Google Workspace user** (CR-2);
@ -694,9 +717,12 @@ MCP layer) are unchanged — those stay locked.
**Repo READMEs & memory (F6 — global-instruction obligations, were omitted):**
- `sh-mcp` README updated to **OpenAPI-first** (servers expose OpenAPI; MCP adapter deferred).
- `sh-agentforce` README created (agent metadata repo, `cd-sfdx`, scratch-org flow).
- **Project memory:** create `project_sh_agentforce` before the repo-creating conversation ends; update
`project_sh_mcp` for the transport/auth changes; add **cross-project references** `sh-mcp` ↔ `sh-agentforce`
↔ `project_seahaven_slack_bot` ↔ `project_exec_aide` (each side links the other).
- **Project memory (PRIVATE memory store, NOT repo files — Gemini round-3 NIT, path-corrected):** these live in
`~/.claude/projects/-Users-adammoussa-Documents-repositories/memory/` (the Sea Haven memory location — **not**
any repo, and not Gemini's own `~/.gemini/...` path, which it incorrectly cited). Create `project_sh_agentforce`
before the repo-creating conversation ends; update `project_sh_mcp` for the transport/auth changes; add
**cross-project references** `sh-mcp` ↔ `sh-agentforce` ↔ `project_seahaven_slack_bot` ↔ `project_exec_aide`.
Keep this distinct from the repo-side READMEs above.
**Monitoring (N2 — ALARM-state-only convention, design.md alarm preferences):**
- Each per-tier **API Gateway facade**: 5xx-rate, auth-failure-spike, and **wrong-audience-token** alarms (the
@ -804,15 +830,20 @@ Groups to reconcile the two control planes (G11, recommend SCIM).
must run as scripted per-user sessions instead of batch — decide before Phase 1.
6. **(Slack plan + licensing, Q1/Q2/G15)** Confirm Sea Haven's Slack plan supports **Agentforce Employee Agents**,
and whether the all-staff Ops agent needs a provisioned Agentforce **user + license per employee** (prices G9,
feeds G11 SCIM scope).
feeds G11 SCIM scope). **Run this on a production-equivalent Enterprise Grid org with real Agentforce licenses
(Gemini round-3) — a Developer Org / non-Grid Slack can false-positive on Employee Agent support.**
7. **(Agent variables, Q3/G15)** Confirm Agentforce exposes **`$User.GoogleGroups`** and **`$Session.Channel`** to
agent instructions. If not, design an alternate enforcement for Lauren Exec's DM-only and per-scope gating
(e.g. a context Apex action that returns channel + group facts) before Phase 1/3.
8. **(Cognito `aud`/authorizer mechanism, BLOCK-1 — on the 0a/0b gate)** Confirm the **pre-token V2** access-token
customization trigger is available on our Cognito plan, and pick + prove the concrete per-tier rejection
mechanism: **scope-prefix check**, **`client_id` allow-list per gateway**, or **custom `aud` via pre-token V2**.
The B3 edge rejection and B4 fail-closed both depend on this; Cognito access tokens do not carry a per-
resource-server `aud` by default. Do this in the 0a throwaway kit before 0b builds the real facades.
8. **(Cognito token-mint mechanism, BLOCK-1 + Gemini round-3 — FATAL on the 0a gate)** Three things to prove in
the 0a throwaway kit before 0b builds real facades:
(a) **Confirm `AllowedOAuthScopes` strictly filters the issued token** — i.e. issued scopes ⊆ the client's
allow-list **even when a pre-token V2 Lambda runs** (Gemini BLOCK). If a V2 Lambda can widen beyond the
allow-list, the app-client control is soft → promote pre-token fail-closed suppression to primary.
(b) **Confirm the `pre-token V2` trigger is available** on our Cognito plan (it may be a paid feature).
(c) **Pick + prove the per-tier rejection mechanism** — `client_id` allow-list per gateway, resource-server
scope-prefix, or custom `aud` via V2 — since Cognito access tokens carry no native per-resource-server `aud`.
The B3 edge rejection, B4 boundary, and §1h audience tests all depend on (a)–(c).
---