Skip to content

Validate the OAuth state parameter on the web-flow callback - #3424

Closed
carfeii wants to merge 1 commit into
gitify-app:mainfrom
carfeii:fix/oauth-web-flow-state-validation
Closed

carfeii wants to merge 1 commit into
gitify-app:mainfrom
carfeii:fix/oauth-web-flow-state-validation

Conversation

@carfeii

@carfeii carfeii commented Oct 16, 2026

Copy link
Copy Markdown

performGitHubWebOAuth() never passed a state option to getWebFlowAuthorizationUrl(), and handleCallback() never checked one on the gitify://oauth callback either, so any authorization code arriving through that custom protocol was accepted and exchanged for a token regardless of where it came from. An attacker who completes GitHub's OAuth authorize step for their own account can get the resulting code delivered to a victim's running Gitify instance via a crafted gitify://oauth?code=...&type=oauth link (custom protocol handlers can be triggered from a webpage, email, or another local app), silently binding the victim's client to the attacker's GitHub account instead of the victim's own.

This generates a per-attempt random state value, passes it through to the authorization URL, and requires the callback's state to match before treating a code as belonging to that login attempt. A callback with a missing or mismatched state is logged and ignored rather than accepted, so a still-pending legitimate login isn't torn down by an attacker's delivery attempt.

Updated the existing web-flow success test to supply the now-required state on its simulated callback, and added a regression test confirming a mismatched or missing state does not resolve the login. Full test suite run clean (10/10) in an isolated environment.

performGitHubWebOAuth() never passed a state option to
getWebFlowAuthorizationUrl(), and handleCallback() never checked one on
the gitify://oauth callback either, so any authorization code arriving
through that custom protocol was accepted and exchanged for a token
regardless of where it came from. An attacker who completes GitHub's
OAuth authorize step for their own account can get the resulting code
delivered to a victim's running Gitify instance via a crafted
gitify://oauth?code=...&type=oauth link (custom protocol handlers can
be triggered from a webpage, email, or another local app), silently
binding the victim's client to the attacker's GitHub account instead of
the victim's own.

Generate a per-attempt random state value, pass it through to the
authorization URL, and require the callback's state to match before
treating a code as belonging to that login attempt. A callback with a
missing or mismatched state is logged and ignored rather than
accepted, so a still-pending legitimate login isn't torn down by an
attacker's delivery attempt.

Updated the existing web-flow success test to supply the now-required
state on its simulated callback, and added a regression test confirming
a mismatched or missing state does not resolve the login.
@setchy

setchy commented Oct 17, 2026

Copy link
Copy Markdown
Member

Thanks for flagging @carfeii. I've used this as a base to fix a few other edge cases in the OAuth state handling. Appreciate it!

@setchy setchy closed this Oct 17, 2026
@setchy

setchy commented Oct 17, 2026

Copy link
Copy Markdown
Member

Actually, we might be able to remove this oauth method completely. #3449

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants