CrowdSec Repository Theft – Former Employee GitHub OAuth Token Abuse

Threat Overview

CrowdSec’s final incident reporting describes theft of approximately 170 private GitHub repositories using a GitHub OAuth credential associated with a former employee. CrowdSec links the credential exposure to the earlier TanStack supply-chain compromise. The repository download occurred on May 22, 2026 in roughly nine minutes. CrowdSec says production infrastructure, databases, code, and build pipelines were not modified. The incident demonstrates how a supply-chain compromise can persist through an unrevoked identity token and later become source-code theft.

References

Impacted Systems

The confirmed victim is CrowdSec. The broader exposure class is organizations using GitHub OAuth credentials, developer workstations, and source-control integrations where tokens can survive employee departure or endpoint compromise. CrowdSec also reports an exposed AWS token was tested later.

Why this matters

This is a concrete example of token persistence outliving employment and converting a software-supply-chain compromise into large-scale private repository theft.

Exploitation Status

Confirmed CrowdSec incident. CrowdSec attributes the original TanStack compromise to TeamPCP/UNC6780 and identifies the repository downloader as diencracked; independent legal attribution remains unresolved.

What this hunt looks for

OAuth and application-consent changes, unusual identity activity, new sign-in sources, risky sign-ins, and downstream AWS credential use. Direct GitHub repository-download detection requires GitHub audit telemetry.

Required logs

SecurityAlert, Entra AuditLogs and SigninLogs, plus AWSCloudTrail where available. GitHub audit logs are required for direct repository-access detection and are not assumed to be present.

First Pass – GitHub/OAuth Security Alerts

SecurityAlert
| where TimeGenerated >= ago(90d)
| where AlertName has_any ("GitHub","OAuth","token","credential")
| project TimeGenerated,AlertName,AlertSeverity,CompromisedEntity,Description,SystemAlertId
| order by TimeGenerated desc

OAuth and Application Consent Changes

AuditLogs
| where TimeGenerated >= ago(90d)
| where OperationName has_any ("Consent to application","Add OAuth2PermissionGrant","Add app role assignment","Update application","Add service principal")
| project TimeGenerated,OperationName,Result,InitiatedBy,TargetResources,AdditionalDetails
| order by TimeGenerated desc

Successful Sign-In Inventory

SigninLogs
| where TimeGenerated >= ago(90d)
| where ResultType == 0
| summarize FirstSeen=min(TimeGenerated),LastSeen=max(TimeGenerated),IPs=make_set(IPAddress,20),Apps=make_set(AppDisplayName,20) by UserPrincipalName
| order by LastSeen desc

New Source IPs

let Known=SigninLogs
| where TimeGenerated between (ago(90d)..ago(7d))
| summarize by UserPrincipalName,IPAddress;
SigninLogs
| where TimeGenerated >= ago(7d)
| join kind=leftanti Known on UserPrincipalName,IPAddress
| project TimeGenerated,UserPrincipalName,IPAddress,AppDisplayName,ClientAppUsed,ResultType,LocationDetails
| order by TimeGenerated desc

Risky Sign-Ins

SigninLogs
| where TimeGenerated >= ago(90d)
| where RiskLevelDuringSignIn !in~ ("none","hidden","")
| project TimeGenerated,UserPrincipalName,IPAddress,AppDisplayName,RiskLevelDuringSignIn,RiskState,RiskDetail,LocationDetails
| order by TimeGenerated desc

AWS Credential Use by Access Key

AWSCloudTrail
| where TimeGenerated >= ago(90d)
| extend AccessKey=tostring(UserIdentityAccessKeyId)
| summarize FirstSeen=min(TimeGenerated),LastSeen=max(TimeGenerated),SourceIPs=make_set(SourceIpAddress,50),Events=make_set(EventName,50) by AccessKey
| order by LastSeen desc

High-Volume AWS API Activity

AWSCloudTrail
| where TimeGenerated >= ago(90d)
| extend AccessKey=tostring(UserIdentityAccessKeyId)
| summarize Calls=count(),Services=dcount(EventSource),SourceIPs=dcount(SourceIpAddress),Events=make_set(EventName,50) by AccessKey,bin(TimeGenerated,1h)
| where Calls >= 100
| order by Calls desc

Detection Notes

Direct GitHub repository-download detection requires GitHub audit logs. Entra sign-in data does not prove GitHub token use. AWS queries are downstream credential-abuse pivots only. This hunt intentionally avoids inventing GitHub table names not confirmed in the environment.

Leave a comment