Skip to content

Back to the catalog

Threat Hunter's Guide to GitHub (Datadog Security Labs)
securitylabs.datadoghq.comwriteup

Threat Hunter's Guide to GitHub (Datadog Security Labs)

Datadog Security Labs walks through hunting for GitHub organization compromise using GitHub audit logs, covering the full attack chain from stolen PATs/OAuth tokens and device-code phishing (initial access) through secrets-listing enumeration, private-repo/tree discovery, and bulk collection via git.clone or repo.download_zip. It provides copy-pasteable queries (with fields like hashed_token, programmatic_access_type, external_identity_nameid) mapped to MITRE ATT&CK techniques and calls out GitHub audit-log quirks (missing IP on failed auth, inconsistent visibility fields, misleading token permission scopes) that shape accurate detections.

TL;DR Practical query cookbook for detecting compromised GitHub accounts/tokens, secret enumeration, and source-code exfiltration via audit logs.
Document

What it is

A Datadog Security Labs threat-hunting guide (third in a series after Snowflake and Salesforce) providing verbatim GitHub audit-log queries mapped to MITRE tactics. The summary names the attack chain and the log quirks; what it cannot carry is the actual query text and the per-field/per-threshold rules that make the queries work. Those are below.

What to do

The queries use GitHub audit-log field syntax (portable to any SIEM ingesting these logs, not Datadog-only).

Initial access — login from new device/geo (T1078):

source:github
action:user.sign_in_from_unrecognized_device_and_location
actor_is_bot:false

These events carry country code and user agent but no token type and no source IP.

Credential access — new OAuth app authorized (T1528):

source:github
action:oauth_authorization.create

oauth_application_name and integration never appear in the same event; a populated integration means a user-to-server token was granted.

Discovery — secrets enumeration (T1552): match api.request against four routes (secret-scanning/alerts, actions/secrets, actions/organization-secrets, environments/:environment/secrets) with status_code:[200 TO 299], group by actor, count distinct repo over 1h. None of these expose a secret value — only that a listing occurred.

Discovery — private repo/tree walking (T1526):

source:github
action:api.request
route:("/repositories/:repository_id/git/trees"
OR /repositories/:repository_id/git/trees/*
OR /repositories/:repository_id/contents*)
-status_code>=300
-(public_repo:true OR repository_public:true OR visibility:public)

For the org-repo-listing query, flag actors paging through query_string "page" values >= ~5 or in a sequential run; surface type=private in query_string separately.

Collection: action:git.clone repository_public:false and action:repo.download_zip public_repo:false. For ZIP downloads, focus on many distinct repos in under five minutes — dozens of pulls of the same repo indicate a retry loop, not theft.

The field rules that matter

These constraints, not stated in the summary, determine whether a query is correct:

  • Repo visibility field depends on event type. Three fields express privacy — public_repo, repository_public, visibility — and for api.request events only public_repo applies; the others populate in other event types. A wrong field silently returns nothing.
  • hashed_token is the only stable pivot. application_name / oauth_application_name are inconsistent across event types; group by hashed_token to follow one credential.
  • Token permission fields lie about scope. They describe the token's permissions in its owning org, not yours — an internet-wide scanner with admin in its own org shows admin in your log while only reading your public repos.
  • Failed requests drop external identity and source IP, removing geolocation right when triaging suspicious auth.
  • /user/repos and EMU-only routes: those log entries exist only under GitHub Enterprise Managed Users.

To find whether private repo names/IDs have leaked, hunt route:*repositories* status_code>=400 against non-public repos, then exclude known employees by building a list of actors carrying an external_identity_nameid (present only with SSO).

Caveats

Every query needs baselining — the guide repeatedly states thresholds must be calibrated to org size (a large org may alert only at hundreds of collection attempts per 10 min). Clone/ZIP activity is inherently hard to separate from normal engineering; benign causes called out include new-laptop setup, onboarding, dev-env rebuilds, and CI runners misconfigured to use a human's PAT instead of a GitHub App.

Organisations named here

  • T1078
  • T1213
  • T1526
  • T1528
  • T1552

Technologies named here

  • GitHub
  • Datadog Cloud SIEM
  • Datadog Code Threat Detection
  • GitHub Enterprise Managed Users
  • GitHub integration
  • OAuth
  • SIEM
  • GitHub Actions
  • MITRE ATT&CK

Techniques named here

  • Valid Accounts
  • Steal Application Access Token
  • Unsecured Credentials
  • Cloud Service Discovery
  • Data from Information Repositories
  • Device code phishing
  • Leaked token abuse

Also named here

  • MITRE ATT&CK