P12 — Gateway MCP-scope enforcement
The S3 gateway (crates/fula-cli) must PARSE and ENFORCE the scoped MCP-S3 JWT minted by the pinning-webui (P11/P11.1). The claim shape is a cross-service contract (reference verifier: pinning-service-mcp/pinning-webui/server/mcpTokens.ts::verifyMcpToken).
Token shape
HS256 JWT (shared JWT_SECRET); payload carries token_use:mcp_s3, 64-hex sub, iss/aud/exp/nbf/jti, and:
mcp: { v:1, scopes:[ { bucket:fula-ai-workspace, prefix:ai/, perms:[read,write,list] } ] }
Long-lived storage JWTs share the secret but carry scope:storage:* and NONE of token_use/mcp/exp/typ.
What the gateway must do
- Detect MCP via the
token_use claim (the decoder discards the JOSE header).
- Fail-closed: a token flagged
token_use:mcp_s3 but with a missing/invalid/v!=1 mcp claim is REFUSED — it must never fall through to broad storage access. (Hardening: a token carrying mcp WITHOUT token_use:mcp_s3 is also refused as ambiguous.)
- Fence the MCP token to its single
bucket + ai/ prefix + perms on every S3 data operation; unknown perm strings are ignored (forward-compat, never a grant).
- Normal storage tokens must behave byte-identically (no scope ⇒ enforcement is a no-op).
- Cross-user isolation is unchanged (already from
(hashed_user_id, bucket) + JWT sub); P12 adds the intra-user bucket/prefix/perm fence.
Revocation seam (jti)
An MCP jti deny-list (sibling of the existing key revocation), env-gated and mockable; fail-CLOSED on write when the source is enabled-but-unreachable, open on read. Live HTTP polling defaults OFF (deferred to the test-server deploy). Scope enforcement is the must-have.
Out of scope for P12 (noted)
typ/iss/aud/nbf enforcement is deferred (the decoder already pins HS256 so none fails; exp is covered; token_use+mcp.v defeat token-confusion).
Tracked-in: branch p12-gateway-scope → PR into feat/fula-mcp.
P12 — Gateway MCP-scope enforcement
The S3 gateway (
crates/fula-cli) must PARSE and ENFORCE the scoped MCP-S3 JWT minted by the pinning-webui (P11/P11.1). The claim shape is a cross-service contract (reference verifier:pinning-service-mcp/pinning-webui/server/mcpTokens.ts::verifyMcpToken).Token shape
HS256 JWT (shared
JWT_SECRET); payload carriestoken_use:mcp_s3, 64-hexsub,iss/aud/exp/nbf/jti, and:mcp: { v:1, scopes:[ { bucket:fula-ai-workspace, prefix:ai/, perms:[read,write,list] } ] }Long-lived storage JWTs share the secret but carry
scope:storage:*and NONE oftoken_use/mcp/exp/typ.What the gateway must do
token_useclaim (the decoder discards the JOSE header).token_use:mcp_s3but with a missing/invalid/v!=1mcpclaim is REFUSED — it must never fall through to broad storage access. (Hardening: a token carryingmcpWITHOUTtoken_use:mcp_s3is also refused as ambiguous.)bucket+ai/prefix +permson every S3 data operation; unknown perm strings are ignored (forward-compat, never a grant).(hashed_user_id, bucket)+ JWTsub); P12 adds the intra-user bucket/prefix/perm fence.Revocation seam (jti)
An MCP
jtideny-list (sibling of the existing key revocation), env-gated and mockable; fail-CLOSED on write when the source is enabled-but-unreachable, open on read. Live HTTP polling defaults OFF (deferred to the test-server deploy). Scope enforcement is the must-have.Out of scope for P12 (noted)
typ/iss/aud/nbfenforcement is deferred (the decoder already pins HS256 so none fails;expis covered;token_use+mcp.vdefeat token-confusion).Tracked-in: branch
p12-gateway-scope→ PR intofeat/fula-mcp.