The Ghost in the Repo: Dormant GitHub Accounts Spawning Reconnaissance Campaigns
Just caught the Datadog Security Labs report regarding systematic enumeration of corporate GitHub orgs. It seems attackers are pivoting away from purely automated scripts to using "ghost" accounts—dormant profiles that are years old—to blend in with normal traffic.
The TTPs involve scraping the GitHub API using compromised Personal Access Tokens (PATs) or hijacked OAuth apps. What makes this tricky is they are spoofing user-agents to mimic legitimate Git CLI tools, bypassing basic fingerprinting. They aren't just looking for code; they are mapping the org structure and identifying high-value targets.
If you haven't audited your org members lately, now is the time. Here is a quick Python snippet to identify users who haven't pushed code in over a year but still have access:
import os
from github import Github
g = Github(os.getenv('GITHUB_TOKEN'))
org = g.get_organization("YOUR_ORG_NAME")
print(f"{'User': 365 days
is_dormant = last_event != "Never" and (datetime.datetime.now() - last_event.replace(tzinfo=None)).days > 365
print(f"{member.login:<20} | {str(last_event):<25} | {'CHECK THIS' if is_dormant else 'Active'}")
Has anyone else noticed an uptick in weird user-agent strings in their GitHub audit logs recently, or are we just lucky today?
We started flagging this after noticing a correlation between dormant accounts and high-frequency GET requests on the /repos and /orgs endpoints. Our detection logic focuses on request volume from accounts that historically have low activity.
If you're ingesting logs into a SIEM, try a query similar to this KQL to spot the anomaly:
GitHubAuditLogs
| where Action == "org.read"
| summarize count() by Actor, bin(TimeGenerated, 1h)
| where count_ > 50 // Threshold depends on org size
| join kind=inner (GitHubAuditLogs | where TimeGenerated < ago(30d) | distinct Actor) on Actor
| project Actor, count_, TimeGenerated
Most of the time, the "Actor" turns out to be a former employee whose OAuth token was never revoked during offboarding.
We enforce strict expiration on all PATs and mandate SAML SSO for org access. Even if an attacker has a valid PAT, if their IdP session is revoked, they can't access the org resources. This "ghost" issue is exactly why we removed all outside collaborators and moved everyone to a central IdP.
However, this highlights the massive risk of legacy service accounts. If you have automation repos, ensure your bot tokens don't have read:org permissions unless absolutely necessary.
You can audit your current token scope locally:
gh auth status
gh api user/memberships/orgs | jq '.[] | select(.role=="admin") | .organization.login'
Lock those scopes down; attackers love enumeration tokens.
Verified Access Required
To maintain the integrity of our intelligence feeds, only verified partners and security professionals can post replies.
Request Access