Skip to content

fix(oauth): app group links via internal route (empty groups + access-gate bypass)#66

Merged
BK1031 merged 3 commits into
mainfrom
bk1031/fix-oauth-app-group-links
Jun 2, 2026
Merged

fix(oauth): app group links via internal route (empty groups + access-gate bypass)#66
BK1031 merged 3 commits into
mainfrom
bk1031/fix-oauth-app-group-links

Conversation

@BK1031

@BK1031 BK1031 commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

The bug

The oauth service resolved application→group links by calling the public /applications/:id/groups, which requires a bearer it doesn't carry → the call 401'd and getAppGroupLinks returned nil for every client. That one failure caused two security-relevant symptoms (surfaced wiring ArgoCD SSO):

  1. Empty groups claim for all third-party clients → ArgoCD users all landed in role:readonly.
  2. Access gate silently bypassed — nil links → len(required)==0 → app treated as open → required-group gating did nothing.

Plus a latent field bug: oauth parsed json:"group_id", but the endpoint returns GroupWithRequired with the id as json:"id".

Fix (part 1): read links via an internal route

  • core: new internal GET /core/applications/client/:clientID/groups (unauthenticated, service-to-service, like the other /core/* routes), resolving links by client_id.
  • oauth: call that instead of the auth-gated public endpoint; parse the group id from json:"id".

Fix (part 2): fail closed on core errors

Even with part 1, a transient core failure still failed open (nil links → gate "open"). Now errors propagate instead of collapsing to nil/empty:

  • getAppGroupLinks / getEntityGroups / FilteredGroups / BuildTokenClaims / BuildIDTokenClaims return errors.
  • CheckAccessGate propagates fetch errors; handlers map ErrAccessDenied403 access_denied and any other error → 502 (deny, never "open").
  • token / refresh / first-party-login handlers abort issuance on claim-build errors rather than mint a token with missing groups.
  • Audit (/core/entity/logins) stays best-effort — a logging hiccup shouldn't block a valid login.
  • sentinel client: 5s timeout + 2 retries, GET-only (non-idempotent POSTs like token mint / login record are never retried).

Impact / deploy

Affects every third-party OAuth client since the group features shipped. Needs a Sentinel release + infra image bump to take effect.

Verify after deploy

  • A non-member hitting an app with a required group → access_denied at authorize.
  • A member of a linked group → that group (by name) appears in /oauth/userinfo and the id_token.
  • With core unreachable, logins to gated apps fail (deny) rather than slip through.

BK1031 added 3 commits June 2, 2026 15:33
…ps + access-gate bypass)

The oauth service fetched application group links from the public
/applications/:id/groups endpoint, which requires a bearer it doesn't
carry — so the call 401'd and getAppGroupLinks always returned nil. That
silently (a) left the groups claim empty for every third-party client and
(b) disabled CheckAccessGate (nil links -> no required groups -> open),
letting users bypass required-group gating.

- core: add internal GET /core/applications/client/:clientID/groups
  (unauthenticated, service-to-service) resolving links by client_id
- oauth: call the internal route instead of the auth-gated public one;
  fix applicationGroupLink to parse the group id from json "id" (the
  response is GroupWithRequired, which has "id", not "group_id")
Thread errors through the group/identity lookups instead of collapsing
them to nil/empty, so a failed core call can't silently bypass the access
gate or mint a token with missing claims:

- getAppGroupLinks / getEntityGroups / FilteredGroups / BuildTokenClaims /
  BuildIDTokenClaims now return errors
- CheckAccessGate propagates fetch errors; handlers map ErrAccessDenied to
  403 access_denied and any other error to 502 (deny, never 'open')
- token/refresh/login handlers abort issuance on claim-build errors
- sentinel client: 5s timeout + 2 retries on idempotent GETs only (POSTs
  like token mint / login record are never retried)
Apply the same resty client hardening as oauth to the core and discord
service-to-service clients: 5s timeout, 2 retries on idempotent GETs only
(POSTs are never retried to avoid double-applying non-idempotent writes).
@BK1031 BK1031 merged commit e39e8b5 into main Jun 2, 2026
12 checks passed
@BK1031 BK1031 deleted the bk1031/fix-oauth-app-group-links branch June 2, 2026 22:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant