A branch has been force-pushed. The activity appears under your GitHub username, but you did not make the change. Your browser sessions look familiar, and two-factor authentication is enabled. What should you investigate next?
Start with the credential that authorized the operation. GitHub access can come from an OAuth token, a personal access token, an SSH key, an installed application, or automation. An account name is an important clue, but it does not establish which person or device used that access.
This guide explains how developers and repository owners can establish what happened, contain the relevant access, and recover without overstating the evidence. It focuses on GitHub.com, with Windows and PowerShell examples where local commands are useful. Organization Git audit data requires the appropriate access and plan; the account and repository checks remain useful when that data is unavailable.
Stop ongoing harm while preserving the essentials
If unauthorized activity is continuing, revoke the affected access immediately from a trusted device. Preserve readily available evidence, but do not delay containment to complete the investigation. If teammates are available, evidence collection and containment can proceed in parallel.
Record when the suspicious activity occurred, including the time zone. When comparing logs from different systems, convert their timestamps to UTC so they can be aligned accurately. Capture the repository and branch or tag, recorded actor, and old and new commit identifiers. Save relevant activity links and original log exports in restricted storage. Record when each export was collected and every response action you take. Work from copies when annotating evidence.
For a force push, preserve available copies of the previous history before attempting a repair. Avoid running unfamiliar code, build tasks, or workflows from the affected repository. If a suspicious workflow is running, stopping it may be more urgent than reconstructing the entire timeline.
Then check whether an expected release job, IDE, scheduled script, or teammate action explains the change. Unexpected activity is a reason to investigate; it is not, on its own, proof of credential theft.
Separate the account from the person behind it
The investigation has three distinct questions: what changed, which access path permitted it, and who controlled that access. Evidence that answers one question may leave the others open.
Repository Activity links pushes, merges, force pushes, and branch changes to commits and authenticated users. Use it to establish the repository event and affected history. A commit’s author name or email is a different kind of information and should not be substituted for evidence of who authenticated the push. See Repository activity.
| Evidence | Useful for | Does not establish by itself |
| Repository Activity | Change type, affected branch, associated commits and account | The physical person or device responsible |
| Personal Security log | Account events around the incident window | A complete record of repository reads and writes |
| Organization Git audit events | Recorded Git operations and available authentication metadata | Every way repository content can change |
| Local history and configuration | Commands and credential paths present on a workstation | That no other device or process used the credential |
The personal Security log covers the previous 90 days. Review it for relevant authorization and account changes, then export the useful records. A normal-looking browser session list cannot clear every programmatic credential. See Personal security log.
Treat user-agent strings and country codes as supporting clues. A Windows-flavored Git user agent is consistent with that client family; it does not identify an individual machine. Network location may reflect a VPN, proxy, or cloud service. Correlation becomes persuasive when independent records agree.
Map the access that could have caused the change
Review the account’s sessions, personal access tokens, authorized OAuth apps, GitHub App user authorizations, and SSH authentication keys. Repository and organization owners should separately review app installations, deploy keys, collaborators, teams, and automation.
Keep authorization types distinct. Removing a user’s app authorization and removing an organization’s app installation address different access paths. Likewise, an SSH authentication key grants a different capability from a key used only to sign commits.
For each plausible credential, record its identifier when available, permission scope, accessible repositories, and expected owner or tool. Distinguish observed impact from possible exposure: a credential may have permission to reach repositories for which you have no evidence of activity.
This inventory determines the response. An unexplained credential with write access to one repository calls for different scoping than an integration with broad organization access. Do not assume that the repository where the incident was noticed is the only one affected.
Identify the workstation credential path
GitHub CLI and Git may use different credentials. First inspect the CLI’s authentication state without displaying its token. See GitHub CLI authentication status.
gh auth status –hostname github.com
Do not add –show-token. Review diagnostic output privately before sharing it; usernames, paths, and repository names can also be sensitive.
GitHub CLI gives GH_TOKEN and GITHUB_TOKEN precedence over stored credentials. In PowerShell, check whether either variable exists without printing its value. See GitHub CLI environment variables.
‘GH_TOKEN’,’GITHUB_TOKEN’ | ForEach-Object {
[pscustomobject]@{
Name = $_
Present = Test-Path “Env:$_”
}
}
Next inspect the configured Git credential helpers. Run the command in the relevant trusted repository context when possible, because configuration can vary by location. See Git configuration reference.
git config –show-origin –get-all credential.helper
This lists helper configuration; it does not retrieve a stored password. However, a custom helper can contain sensitive inline configuration, so do not paste the output into a public issue without reviewing it.
If Git Credential Manager is installed, inspect its GitHub accounts and available commands. See Git Credential Manager account management.
git credential-manager github list
git credential-manager github –help
These checks identify plausible credential suppliers. They do not prove that a particular stored credential caused the incident. An SSH remote, IDE, container, CI runner, or another machine may use a different path entirely.
Local shell history and repository configuration can add context. Search the known development folders first, and inspect remote configuration without executing project code. Remember that linked worktrees may use a .git file rather than a .git directory. A search restricted to .git/config will miss those layouts.
No matching command in shell history means only that you did not find one in that history. GUI tools, deleted records, other shells, and remote use remain possible.
Export Git audit evidence before it expires
For eligible GitHub Enterprise Cloud organizations, an authorized owner can retrieve Git events through the audit-log REST API. Git events have a seven-day retention window, so collection is time-sensitive. GraphQL does not return them. GitHub Enterprise Server behavior is version- and configuration-dependent. See Organization audit logs and Git event retention.
There is another important limit: Git events do not include operations initiated through the browser or REST or GraphQL APIs. A browser-based pull-request merge can change a branch without appearing as a corresponding Git push in this dataset. Combine Git events with repository activity and other audit records.
When authentication metadata is present, preserve programmatic_access_type, token_id, hashed_token, and relevant timestamps and repository fields. GitHub documents token metadata for Git events as a public-preview capability, so field availability can change. See Correlating audit events with access tokens.
To correlate Git operations, obtain the Git-event data and filter the exported records for the same hashed_token. Do not assume that a normal audit-log search using hashed_token returns Git events; GitHub documents that limitation explicitly. There is no need to expose the raw token when the relevant hash is already available in the export.
A worked example with deliberately limited conclusions
Imagine an exported clone event and a later push event for the same repository carry the same token hash. Repository Activity also records a force push in the relevant window. This is a hypothetical example, not a reported incident.
The matching hash connects the recorded Git operations to one credential. The repository view establishes the history-changing action. Together, they justify scoping that credential’s activity and revoking its access.
They do not establish how the credential was obtained, which computer held it, or who operated that computer. Those questions require additional evidence. Record the distinction in the incident notes rather than filling the gap with a location guess.
If you lack organization Git logs, continue with repository activity, account records, integration logs, and available endpoint evidence. Ask the repository or organization owner to preserve what they can. “Credential not identified from available evidence” is a valid finding.
Use the Events API only for supplementary context
The Events API can help locate recent activity, but it is not a complete forensic record or a real-time monitoring feed. GitHub documents possible delays of 30 seconds to six hours. Visibility also depends on the authenticated user and permissions. See REST API endpoints for events.
For recent account context, this PowerShell example uses GitHub CLI pagination rather than assuming a fixed number of pages. It outputs timestamps and repository names for returned push events:
$me = gh api user –jq ‘.login’
if ($LASTEXITCODE -ne 0) { throw ‘Cannot identify account.’ }
gh api –paginate “users/$me/events?per_page=100” `
–jq ‘.[] | select(.type == “PushEvent”) | [.created_at, .repo.name] | @tsv’
Pagination retrieves the records the endpoint makes available; it does not recover an unlimited history. Preserve any useful results with the collection time and account used. An empty result cannot rule out unauthorized activity.
Revoke server access and then clear local credentials
Once you have enough evidence to identify questionable access, revoke it at GitHub. Revoked tokens can no longer authenticate Git or API requests. Revoking an OAuth or GitHub App user authorization also revokes the tokens associated with that authorization. See Token expiration and revocation.
If the suspected authorization is GitHub CLI, review it under GitHub’s application settings. The CLI manual states that revoking its authorization invalidates tokens generated by GitHub CLI across your devices. Account for the legitimate workflows this may interrupt. See GitHub CLI logout and server revocation.
Review other plausible paths at the same time: personal access tokens, SSH and deploy keys, installed apps, automation secrets, and unexpected collaborators. Secure account sign-in and recovery settings if account takeover is plausible. A password change alone is not evidence that every programmatic path has been removed.
Local logout is a cleanup step. gh auth logout removes the selected account’s local authentication configuration; it does not revoke the token at GitHub. After server-side revocation, remove the stale entry, replacing YOUR_USERNAME with the account being cleaned up.
gh auth logout –hostname github.com –user YOUR_USERNAME
Use the installed credential manager’s supported logout or removal procedure for its cache, and check its account list afterward. Avoid indiscriminately deleting unrelated credentials.
Verify exactly what was revoked
Verification must match the credential you intended to invalidate. gh auth status tests the authentication configuration available to GitHub CLI. It does not test every token or SSH key on the machine, and an environment variable may select a different credential. See GitHub CLI authentication status and GitHub CLI environment variables.
Record the server-side revocation and its time. If the old credential is still available through its original client and can be tested safely, confirm that authentication is rejected without displaying or copying the secret. Do not retain a compromised token merely to create a test.
A failed request needs interpretation: network errors, permission denials, and invalid authentication are not equivalent. Conversely, a successful operation may have used another credential. If you cannot isolate the old authentication path, document that limit instead of claiming complete verification.
Reauthenticate only the tools still needed, from a trusted environment. If you choose GitHub CLI’s browser flow, use the commands below. See GitHub CLI login.
gh auth login –hostname github.com –git-protocol https –web
gh api user –jq ‘.login’
The second command confirms the API identity in use. It does not independently confirm which credential a subsequent Git push will use. Review that path separately and observe the first legitimate operations after recovery.
Recover the repository and assess downstream impact
Credential containment and repository recovery are separate workstreams. After preserving evidence, identify a known-good state with the repository owner and coordinate the repair with collaborators. A hurried force push can destroy useful context or overwrite legitimate work made during the incident.
Inspect changes to application code, workflow files, release scripts, tags, and deployment configuration. Determine whether suspicious changes reached a build, package, release, or production deployment. Restoring the branch does not repair artifacts already published from it.
If an untrusted workflow ran with access to secrets or a privileged runner, assess those resources as potentially affected. Preserve run records, contain suspicious automation, and rotate downstream credentials where exposure is plausible. GitHub’s Actions security guidance explains why workflow permissions, secrets, and runner trust boundaries matter. See GitHub Actions secure use reference.
Review branch protection or rulesets, including force-push controls, required review, status checks, and bypass permissions. A protection rule needs to apply to the affected branch and actors to be useful; review administrator exceptions as well. See Protected branches and bypass permissions.
If workstation theft remains plausible, investigate the endpoint using trusted security tooling and your incident-response process. A clean scan cannot establish that a token was never copied. Avoid issuing fresh credentials on a machine whose integrity is still in doubt.
Close with a finding the evidence can support
A useful incident summary states the confirmed activity, affected repositories, identified credential or remaining uncertainty, containment actions, recovery work, and evidence gaps. Assign an owner to unresolved follow-up rather than treating uncertainty as closure.
Monitor subsequent repository changes and authorization events against the reviewed set of accounts, tools, and integrations. For teams that need investigations beyond the native retention window, establish ongoing audit collection and a retention policy before the next incident.
The goal is a defensible explanation and a controlled recovery. Establish what GitHub accepted, remove unauthorized access, repair what changed, and state clearly what the available evidence cannot tell you.
Related Readings from WebDigestPro
- When Unknown PHP Files Appear on Your Server covers suspicious files, evidence preservation, and containment of a website compromise.
- When Database Tables Disappear After a Power Outage presents a recovery workflow focused on preserving original files, working from copies, and verifying recovered data.
Subscribe to our newsletter!
+ There are no comments
Add yours