fix(security): reject consent scopes outside the original request
The /consent POST handler trusted the scope values submitted in the form, so a forged consent submission could approve (and persist consent for) scopes that were never part of the originating authorization request — a scope-escalation vector. Intersect the submitted scopes with the originally requested set stored in the session before saving consent and completing the flow. Refs: porchlight-a03 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c52778326e
commit
cdde3e3754
2 changed files with 28 additions and 2 deletions
|
|
@ -223,6 +223,30 @@ async def test_partial_consent_filters_scopes(client: AsyncClient) -> None:
|
|||
assert set(consent.scopes) == {"openid", "profile"}
|
||||
|
||||
|
||||
async def test_consent_rejects_scopes_outside_request(client: AsyncClient) -> None:
|
||||
"""A forged consent POST cannot approve scopes that were not requested."""
|
||||
app = client._transport.app # type: ignore[union-attr]
|
||||
_register_test_rp(app)
|
||||
await _create_test_user(app)
|
||||
|
||||
# Only openid + profile were requested; "email" is forged into the POST.
|
||||
await _login_and_start_auth(client, scope="openid profile")
|
||||
token = await get_csrf_token(client)
|
||||
res = await client.post(
|
||||
"/consent",
|
||||
data={"action": "allow", "scope": ["openid", "profile", "email"]},
|
||||
headers={"X-CSRF-Token": token},
|
||||
follow_redirects=False,
|
||||
)
|
||||
assert res.status_code == 303
|
||||
|
||||
consent_repo = app.state.consent_repo
|
||||
consent = await consent_repo.get_consent("lusab-consent", "consent-rp")
|
||||
assert consent is not None
|
||||
assert "email" not in consent.scopes
|
||||
assert set(consent.scopes) == {"openid", "profile"}
|
||||
|
||||
|
||||
# -- Test helpers --
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue