Learning from the Exaforce frontline: Detecting GitHub OAuth Token Compromise

A dormant OAuth token, issued 95 days earlier during a routine application install, was used to exfiltrate 472 repositories. This post examines the detection mechanism, the underlying credential model that enabled it, and the controls that reduce this class of risk.

Kavita Varadarajan

Kavita Varadarajan

Joseph Odyn

Joseph Odyn

Shang Li

Shang Li

Executive Summary

A few weeks ago, Exaforce's detection engine identified a GitHub user downloading repositories as ZIP archives from more than two dozen European cities within seconds of each other, on an account whose engineering organization operates exclusively out of Taiwan. The activity traced back to a GitHub OAuth token issued during a GitHub Desktop installation roughly three months earlier, which had been unused until the point of compromise.

By the conclusion of the investigation, the token had been used to exfiltrate 472 of the 638 repositories accessible to the account, via a distributed residential proxy and VPN network structured to make automated, scripted access resemble ordinary browser traffic from distinct individuals.

This blog highlights the risks involved in some of Github’s default configurations, the power of the Exaforce detection platform, and outlines how to proactively prevent similar breaches in your environment. 

Key Takeaways:

  • GitHub has no native detection for this kind of OAuth token abuse, left alone, it would have gone unnoticed. Exaforce caught it out of the box by correlating five independent anomaly signals into one high-confidence finding within minutes.
  • GitHub OAuth tokens, both classic OAuth App tokens and GitHub App refresh tokens, are long-lived by design and with no native way to shorten that lifespan. The only fix is proactively revoking and re-authenticating on your own schedule, which Exaforce can automate.
  • Preventative controls such as expiring tokens over tools like GitHub Desktop, dormant-token flagging, tighter default repo access, can meaningfully reduce the odds of an attack like this succeeding. 

Anatomy of the Attack

  1. Token issuance. An engineer installed GitHub Desktop from the official distribution site and completed first-time setup. The standard browser-based OAuth flow completed in under a minute, issuing a token scoped to repo, user, and workflow, which was stored in macOS Keychain. GitHub Desktop was opened once more immediately afterward and was not used again. The token remained valid and unused, a pattern consistent with routine developer tooling setup across most engineering organizations.
  2. Dormancy period. For the following three months, account activity remained consistent with the established baseline: Taiwan-based infrastructure, macOS tooling, no anomalies. The token itself saw no further use during this period.
  3. Exfiltration onset. On 2026-06-19 exfiltration began with an initial burst of 76 repository downloads within a 15-second window.Using the dormant OAuth token, an automated client began issuing repo.download_zip requests. Within a single session, requests originated near-simultaneously from Belgium, the United Kingdom, Norway, Finland, Spain, Germany, and numerous other European locations, all under the same GitHub session ID and the same truncated Windows user-agent string, a pattern inconsistent with any single human operator.
  4. Detection. Exaforce generated a correlated finding combining five distinct anomaly signals into a single high-confidence alert (detailed below). The alert was built based on a baseline of consistent, single-geography history of 397 prior events, all originating from Taiwan-based infrastructure and macOS tooling (git, Chrome, GitHub CLI), with no prior activity from Europe.
  5. Continued exfiltration. Activity continued, ultimately reaching 472 distinct repositories out of the 638 accessible to the account. 
  6. Response. Containment was performed via a combination of the Exaforce MDR team   and the  platform's SOAR capabilities. The customer followed with manual remediation: disabling the Google Workspace account, revoking the GitHub OAuth grant, exposed secrets were rotated, and containing the affected device. EDR was deployed shortly afterward specifically to support forensic analysis, as the host had no endpoint telemetry prior to the incident. Forensic review found no malware or persistence mechanisms on the device, indicating no active compromise at the time of investigation.

What You Should Know About GitHub OAuth Tokens

This case turned on a detail that is not widely understood and one we will talk about in more details in a follow-up blog: classic GitHub OAuth App tokens - the type GitHub Desktop generates - carry no fixed expiration date, by design. GitHub supports two distinct integration models, and their credential lifecycles differ substantially:

  • Classic OAuth Apps (tokens prefixed gho_) represent the older, simpler model, used by GitHub Desktop, many CLIs, and earlier third-party integrations. Once authorized, the token remains valid indefinitely, there is no 30-day, 90-day, or 1-year expiration cycle. GitHub's only safeguard is a dormancy check: a token that goes entirely unused for a full year is automatically revoked. Any token that sees use, or that has been dormant for less than a year, remains valid.
  • GitHub Apps (tokens prefixed ghu_) represent the newer, more granular model. Here, the application developer may opt in to token expiration; when enabled, the default lifespan is 8 hours, backed by a 6-month refresh token.That refresh token is itself long-lived, so a stolen one can keep minting fresh 8-hour access tokens for months without re-authentication. So, while this model narrows the exposure window, it doesn't eliminate the long-lived credential; it just moves it one level down.

GitHub Desktop was built on the classic OAuth App model, so the shorter-lived GitHub App lifecycle was never available to it. This is why the token issued in March remained fully usable in June: at 95 days of dormancy, it was well within GitHub's one-year automatic-revocation threshold. Unlike short-lived cloud credentials (e.g. an AWS STS token issued through an SSO session, which requires periodic re-authentication) this class of GitHub credential has no meaningful lifecycle unless the specific application it belongs to was built with one. Even the GitHub App model doesn't remove long-lived credentials, it just narrows which one is exposed at any given moment, from an access token to a refresh token.

There is no native mechanism available to shorten either of these token lifespans. GitHub does not provide a native setting to expire any of this token class on a fixed schedule, as one might rotate a database credential. Managing that interval is left entirely to the organization. Teams must instead build their own detection for dormant or stale tokens and revoke individual grants - including forcing re-authentication on a narrower interval than a refresh token's default six months. These detection and management capabilities are now a native feature of the Exaforce platform. 

One alternative remediative option is to move to a credential type that supports expiration natively, though this is more constrained than it sounds. GitHub Desktop supports only its browser-based OAuth flow; GitHub's documentation states explicitly that username/password or token-based sign-in is not supported in Desktop, so there is no configuration change that alters its credential type. Obtaining an expiring credential requires moving the workflow to the command line and authenticating with a fine-grained personal access token instead, which carries a mandatory expiration (up to 366 days, or shorter under an organizational policy) and can be scoped to specific repositories rather than the account's full reach. 

Another alternative worth ruling out; SSH keys are sometimes proposed as an alternative, but they do not address the underlying issue: like classic OAuth App tokens, SSH keys carry no expiration by default, and a stolen key can still be used to clone every repository the account can access. SSH changes the mechanism of access, not the underlying risk itself.

Given that neither GitHub nor GitHub Desktop can shorten these credential lifespans on their own, the most practical path for most teams is to manage this themselves: track dormant tokens, enforce re-authentication on a shorter cycle, and revoke grants that no longer need access - the kind of ongoing detection and management Exaforce is built to handle.

Detecting OAuth Token Compromise

Exaforce identified this activity as a single, high-confidence finding built from five independent anomaly signals, all correlating to the same session, out of the box:

  • ASN anomaly - traffic from 25+ autonomous systems never previously observed on this account
  • Impossible travel - geographically incoherent request sequences implying travel speeds in the millions of km/h
  • Location anomaly - a departure from three months of exclusively Taiwan-based activity
  • Rate anomaly - 76 repository downloads within a 15-second window
  • User-agent/location anomaly - a Windows-flavored, truncated user-agent string replacing a consistent macOS/Chrome baseline

No individual signal here would have been sufficient on its own. In isolation, a location anomaly or an unfamiliar ASN is prone to false positives since legitimate travel and VPN usage regularly produce similar patterns. What elevated this from a routine anomaly to a high confidence finding was the concurrent presence of multiple signals on a single session. 

This was layered with an additional correlation; the platform tied GitHub session activity to Identity Provider events on a shared timeline, and established that bulk download activity began before the corresponding IdP authentication events occurred, a strong indicator of automated credential replay rather than an authenticated human session. This multi-signal, cross-source model is what allowed the finding to reach high confidence immediately, which in turn is what allowed for quick remediation, rather than waiting on manual triage.

By contrast, reproducing this outcome in a traditional SIEM would require substantially more engineering effort than it may first appear. A location-anomaly rule alone requires resolving a basic ambiguity: whether to baseline the user, the account, or the specific credential. GitHub OAuth tokens don't map cleanly onto any of those, since they represent a sub-identity attached to a user, and multiple tokens may be active concurrently on the same account. Baselines are also notoriously cumbersome to maintain: an overly narrow one fails to distinguish a genuine anomaly from routine VPN use, while an overly broad one generates excessive false positives. Building baselines and correlating signals like these in a traditional SIEM typically requires weeks to months of rule development and tuning, along with deep expertise in each token, authentication, and user model - a feat only the most advanced and well-resourced security teams can sustain. 

This is the exact gap Exaforce's detection platform is built to close, UEBA baselining and profiling come built in, layered under a multi-tiered detection engine that aggregates low-fidelity anomalies into high-fidelity findings, freeing security teams to focus on novel attacks and deep hunting. Since this attack Exaforce has also added Dormant Token Reactivation as an out of the box detection, to even more thoroughly detect this pattern.

Response Actions

This case illustrates the value of revoking an OAuth app token automatically at the point a high-confidence detection fires. The following actions belong in any response runbook for this scenario:

  1. Revoke the OAuth grant and any active sessions for the affected identity immediately. This includes the GitHub session, the Google Workspace session, and any downstream SSO tokens derived from either. Note that GitHub exposes a dedicated endpoint for this: POST https://api.github.com/credentials/revoke, which takes a token value and immediately expires it. Notably, the endpoint is unauthenticated by design, no login or proof of ownership is required, specifically so that a leaked credential can be killed the moment it's found, by whoever finds it. Exaforce now packages this as an out-of-the-box response action: the platform can call this endpoint automatically the moment a high-confidence finding fires, rather than requiring a team to script and wire up the API call themselves during an active incident.
  2. Secret-scan every repository the token accessed, prioritizing infrastructure, deployment, and platform code, where credentials with the greatest blast radius tend to reside. Rotate any credentials identified. These actions can also be automated by the Exaforce SOAR platform.

Preventative Controls

The following controls could have caught this activity earlier, or prevented it outright. No single one closes the underlying gap completely as long-lived, broadly scoped, network-unrestricted OAuth tokens are a structural feature of GitHub's credential model and not a one-off misconfiguration. However, together they reduce how often a scenario like this can occur, and how far it can get when it does.

  1. IP allowlisting for private repositories, paired with mandatory VPN/ZTNA access. If a stolen token can only be used from approved network ranges, a distributed residential-proxy pool ceases to function as a viable attack path. The tradeoff is meaningful: this requires every engineer to route through a VPN or ZTNA solution, which introduces friction that many organizations and individual engineers resist, and not every customer has this infrastructure in place today.
  2. Standardize on fine-grained personal access tokens for CLI-based git access, and discourage GitHub Desktop for users with access to sensitive repositories. This is more actionable than it may appear: GitHub Desktop cannot be configured to use an expiring credential, since it supports only its own browser OAuth flow. Fine-grained PATs, in contrast, carry a mandatory expiration and can be scoped to specific repositories, and organization owners can enforce a maximum lifetime policy today without custom automation. This control will not prevent every engineer from installing Desktop, but it provides security teams with a concrete, enforceable standard in place of a token type that cannot be time-boxed at all.
  3. Dormant-token risk flagging. Surfacing OAuth tokens that were created and never subsequently used, or unused for 60+ days, as a standing risk item allows teams to revoke this class of credential proactively before it is weaponized. This control would have flagged the specific token in this case months before the attack occurred. Note that dormant tokens are a risk rule available in Exaforce, and dormant tokens being reactivated are now an out of the box detection. 
  4. EDR deployment on developer endpoints, prioritized by access level. This does not prevent token theft directly, but it addresses the largest gap that prevented this investigation from reaching a conclusive root cause. 
  5. Default repository permissions that do not grant organization-wide read access. Reducing standing access to only what is required limits the reach of any single compromised credential, regardless of how it was obtained.

The Takeaway

This attack succeeded because of a structural gap in GitHub Desktop's credential model: the OAuth App token it issues never expires on a schedule, only on a one-year dormancy backstop, and no configuration change inside Desktop closes that gap. It applies to any organization using GitHub Desktop, a CLI, or another classic OAuth integration, not just this one.Because that gap can't be closed at the source, it has to be covered by detection. While possible in a traditional SIEM, correlating signals like these (ASN, impossible travel, location, rate, user-agent, cross-source IdP timing) requires constructing sophisticated baselines, and coupling it with cross data source evaluation.

Detection also isn't enough alone, it needs to be paired with preventative controls that reduce how often this scenario occurs at all. Exaforce closes this loop end to end — highlighting preventative actions through Risk Rules, correlating the signals into high-confidence findings out of the box, and then acting on it automatically through SOAR-driven containment, rather than requiring a team to build, tune, and staff each piece themselves.

Related posts

The dream SOC team.
Working with you 24/7.

Detection, triage, investigation, and response covered by four Exabots running on a unified, real-time view of your environment. Operate the platform yourself, or have Exaforce run it for you.