# OAuth scope not bound to grant

Scope checks skipped or consent attributed to wrong origin, letting a token or grant exceed what was approved

_Area: Web authentication and account takeover. Seen in 2 disclosed reports; latest 2026-07-20. Updated 2026-11-01._

## How it works
A permission decision is made against something that is not the actual grant. Either the scope check is gated on a claim that may be absent, so a missing claim silently means "no scopes to enforce" and enforcement is skipped [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-vgj4-345g-jcf8.md); or a consent is recorded against a shared origin rather than the specific app, so one approval covers every app on that origin [E3](https://bugbountylibrary.com/r/blog%3Ablog-easysiem-com-application-security-case-study-i-browser-anomaly-with-facebook-apps-1500usd.md); or an app holds far broader admin-consented permissions than its purpose, so anyone who can mint a token for it inherits tenant-wide rights [E1](https://bugbountylibrary.com/r/blog%3Awiz-io-blog-midnight-blizzard-microsoft-breach-analysis-and-best-practices.md). The broken assumption is that the token/grant carries enough identity to bind scope to the right principal.

## Where it shows up
- Middleware that loads scopes only when a claim (e.g. `ClientID`) is non-empty, then treats empty scopes as "allow" [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-vgj4-345g-jcf8.md)
- Token issuance paths that differ: refresh token sets a claim, access token omits it [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-vgj4-345g-jcf8.md)
- Platforms hosting third-party code in iframes on one shared origin, with browser permission prompts [E3](https://bugbountylibrary.com/r/blog%3Ablog-easysiem-com-application-security-case-study-i-browser-anomaly-with-facebook-apps-1500usd.md)
- Service principals or OAuth apps with tenant-wide admin consent (Directory.ReadWrite.All, AppRoleAssignment.ReadWrite.All, full_access_as_app) [E1](https://bugbountylibrary.com/r/blog%3Awiz-io-blog-midnight-blizzard-microsoft-breach-analysis-and-best-practices.md)
- Legacy or non-production tenants federated/consented into production [E1](https://bugbountylibrary.com/r/blog%3Awiz-io-blog-midnight-blizzard-microsoft-breach-analysis-and-best-practices.md)

## How to review
State the invariant: every authorization decision must be evaluated against the scope actually granted to the specific client and user, and absence of scope data must fail closed. Check:
- Does the verifier deny when the scope claim is missing, rather than skip? [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-vgj4-345g-jcf8.md)
- Do all issuance paths (access, refresh, exchange) set the same identifying claims? [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-vgj4-345g-jcf8.md)
- Is consent keyed to the embedded app origin, not the top-level host? [E3](https://bugbountylibrary.com/r/blog%3Ablog-easysiem-com-application-security-case-study-i-browser-anomaly-with-facebook-apps-1500usd.md)
- Enumerate service principals with tenant-wide admin consent and confirm each is justified and its secrets are protected by MFA-gated accounts [E1](https://bugbountylibrary.com/r/blog%3Awiz-io-blog-midnight-blizzard-microsoft-breach-analysis-and-best-practices.md)

## How finders spotted it
- Compared documented scope model and the "insufficient scope" error against the middleware comment, noticing issuance omitted ClientID while refresh included it [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-vgj4-345g-jcf8.md)
- Read the Chromium doc on deprecating permissions in cross-origin iframes, then tested the same embed in Firefox vs Chrome and saw the prompt origin differ [E3](https://bugbountylibrary.com/r/blog%3Ablog-easysiem-com-application-security-case-study-i-browser-anomaly-with-facebook-apps-1500usd.md)
- Reconstructed the chain from disclosure, inferring which Graph permissions each step required [E1](https://bugbountylibrary.com/r/blog%3Awiz-io-blog-midnight-blizzard-microsoft-breach-analysis-and-best-practices.md)

## Variants seen
- Missing claim causes scope enforcement to be skipped entirely [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-vgj4-345g-jcf8.md)
- Consent attributed to shared top-level origin, so one grant applies to all apps [E3](https://bugbountylibrary.com/r/blog%3Ablog-easysiem-com-application-security-case-study-i-browser-anomaly-with-facebook-apps-1500usd.md)
- Over-consented app whose secret can be minted from a weakly protected tenant [E1](https://bugbountylibrary.com/r/blog%3Awiz-io-blog-midnight-blizzard-microsoft-breach-analysis-and-best-practices.md)

## Impact and severity
E2 lets low-scope tokens reach file, share and admin APIs; admin users' tokens reach Admin.Read/Write [E2](https://bugbountylibrary.com/r/ghsa%3AGHSA-vgj4-345g-jcf8.md). E3 lets any app silently capture camera/mic after another app's grant, bounty $1500 [E3](https://bugbountylibrary.com/r/blog%3Ablog-easysiem-com-application-security-case-study-i-browser-anomaly-with-facebook-apps-1500usd.md). E1 escalated to Global Admin and tenant-wide mailbox read, severe because a legacy MFA-less tenant chained into production consent [E1](https://bugbountylibrary.com/r/blog%3Awiz-io-blog-midnight-blizzard-microsoft-breach-analysis-and-best-practices.md).

## Evidence
- E1: https://bugbountylibrary.com/r/blog%3Awiz-io-blog-midnight-blizzard-microsoft-breach-analysis-and-best-practices.md
- E2: https://bugbountylibrary.com/r/ghsa%3AGHSA-vgj4-345g-jcf8.md
- E3: https://bugbountylibrary.com/r/blog%3Ablog-easysiem-com-application-security-case-study-i-browser-anomaly-with-facebook-apps-1500usd.md
