Repository navigation
Conversation
Protected handlers now receive the validated CallerIdentity through a require_permission dependency instead of discarding it. Submission routes require simulation:submit, polling routes require simulation:read, and the temporary Stage 12 routes require stage12:operate. A permission may come from the permissions claim or the space-delimited scope claim. SIMULATION_ENTRYPOINT_ENFORCE_PERMISSIONS accepts exactly 0 or 1 and defaults to 0. In log-only mode a missing permission emits simulation_entry_permission_missing with the permission and the token's azp, then allows the request. With 1 the request is rejected with 403. Disabled authentication has no identity to check and stays allowed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The deployment workflow passes each environment's SIMULATION_ENTRYPOINT_ENFORCE_PERMISSIONS variable to the entrypoint. An unset or empty value deploys 0, and any value other than 0 or 1 fails before Cloud Run changes. Every authenticated deployment test run receives the same value. When it is 1, a new check confirms that the deployed test client's valid token is rejected on a stage12:operate route; otherwise the check skips. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Describe the route permissions, the log-only and enforced modes, the Auth0 RBAC settings and client grants required before enforcement, and the rollout and rollback steps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 12, 2026
Draft
Draft
Open
A route permission now comes from the RBAC permissions claim for every token, but from the space-delimited scope claim only when the token was issued to a machine client (gty is client-credentials or the subject ends in @Clients). Without RBAC on the Auth0 API, a user token's scope carries any API scope the user's application requested, so it is not proof of a grant. This matters once a public sign-in application exists in the same tenant. CallerIdentity now parses gty, and the verified-token tests use a realistic client-credentials token. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CallerIdentity typed gty as a string, so a token whose gty was a list failed validation and was rejected with 403 even in log-only mode, where main had ignored the claim. It is now read as a tuple of grant types: a string or a list of strings, with any other shape ignored. The claim only decides whether scope counts, so ignoring a malformed value can only narrow what a token grants. New tests send signed tokens through JWTDecoder: user tokens with scope but no permissions are rejected under enforcement whatever their grant type, list-valued and malformed gty claims still authenticate, and a listed client-credentials grant still counts scope. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Beta never promotes its candidate revision, so a beta enforcement value applies only to the candidate that the deployment tests call, and log-only events from beta cannot reveal a missing API v1 grant. The rollout steps now watch prod for those events, note that no deployment test sends API v1's own token, and name the production-only workflow that can redeploy the entrypoint for a faster rollback. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CallerIdentity allowed population by field name, so a verified token with no gty but a top-level grant_types claim counted as a client-credentials token and had its scope trusted, and a subject claim could stand in for sub (an @Clients subject marks a machine client). Nothing builds CallerIdentity by field name, so drop populate_by_name: sub, iss, aud, exp, iat, azp and gty are now read only by those claim names. Tests sign tokens that carry grant_types (with a user sub) or subject in place of the registered claims, and check that neither grants scope permissions, while a registered @Clients sub still does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #759
Same-Repository Draft PR Check
PolicyEngine/policyengine-sim-api, not a personal fork.Summary
CallerIdentity, which used to be parsed and discarded.simulation:submit:POST /simulate/economy/comparisonandPOST /simulate/economy/budget-window.simulation:read:GET /jobs/{job_id}andGET /budget-window-jobs/{batch_job_id}.stage12:operate: both/internal/stage12/*routes.permissionsclaim on any token. The space-delimitedscopeclaim also counts, but only on client-credentials (machine) tokens, identified bygty(a string or a list) or an@clientssubject; agtyof any other shape is ignored rather than rejecting the token. Claims are read only by their registered JWT names, so agrant_typesorsubjectclaim cannot stand in forgtyorsub. Without RBAC, Auth0 copies requested API scopes into a user token'sscope, so a user token'sscopeis ignored. Public routes are unchanged.SIMULATION_ENTRYPOINT_ENFORCE_PERMISSIONS, strictly0or1, defaulting to0:0(log-only): emitssimulation_entry_permission_missingwith the permission andazpclient ID, never tokens, and lets the request through.1: answers 403.SIMULATION_ENTRYPOINT_ENFORCE_PERMISSIONS; unset means0. The deployed auth tests receive it too.1. It confirms the deployed test client, which must not holdstage12:operate, gets 403 from a Stage 12 route.Before setting enforcement to
1: on the shared entrypoint API in Auth0, enable RBAC and "Add Permissions in the Access Token". Then grant:simulation:submitandsimulation:readto the API v1 client and to the deployed test client (but notstage12:operateto the test client);stage12:operateto the Stage 12 operator client.Wait one access-token lifetime with no log-only events before flipping, because cached tokens don't carry new grants. The API v1 client uses a single
GATEWAY_AUTH_AUDIENCEfor both the old gateway and the entrypoint.Testing
uv run pytest tests/(entry project): 233 passed. Before this change there were 158. The new tests cover:permissionsvsscopeclaims, including near misses, andscopebeing ignored on user tokens, including signed user tokens with string, list and malformedgty, and tokens whosegrant_typesorsubjectclaims try to stand in forgtyorsub;ruff format --check src tests authenticated_testsis clean.ruff check srcwas already failing onmainwith 4 findings and now has 11. The 7 new ones are all B008, the sameDepends(...)-as-default pattern as the existingDepends(_bearer).main.Related:
🤖 Generated with Claude Code