diff --git a/docs/agentforce-plan.md b/docs/agentforce-plan.md index 7590ae6..dd970fd 100644 --- a/docs/agentforce-plan.md +++ b/docs/agentforce-plan.md @@ -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). ---