Key Insights
The new Force Push Scanner tool scans for secrets in dangling commits on GitHub that remain exposed after certain force push operations. Run the following command to scan your GitHub repositories:
Zero-Commit Force Push operations: where developers attempt to erase mistakes by resetting their git history without pushing new commits.
There are tens of millions of dangling commits publicly accessible based on this dataset.
Building on Truffle Security’s research into Deleted Data on GitHub from last summer and new research analyzing years of GH Archive data, we’ve developed the Force Push Scanner: a tool that scans for secrets in dangling commits on GitHub exposed by force pushes.
Let’s say a developer tries hard to delete a commit from their history. This post gives an overview of how to access this history with a new tool we’ve just released: the Force Push Scanner. We explore how it operates at scale across years of GitHub activity, and reveal how many sensitive commits are still floating in the public domain, waiting to be rediscovered.
A big thank you to Sharon Brizinov for participating in Truffle Security’s CFP and discovering some key insights that enabled the creation of the Force Push Scanner.
Some Context
Summer of 2024
Last summer, we posted Anyone can Access Deleted and Private Repository Data on GitHub. Our main takeaway was that most GitHub users are unaware that the data they considered “deleted” persists on GitHub’s servers. That historical data, including sensitive secrets, remains accessible if someone knows the specific Git object’s SHA-1 hash.
We released an experimental submodule on TruffleHog that politely tries 65,536 Short SHA-1 commit hash values to identify that “deleted” data. It’s thorough but on the slow side.
Our original post left a breadcrumb for researchers: the GH Archive captures all public GitHub events. If you mine that dataset, you don’t have to test all 65,536+ commit hashes; you could determine exactly which commit hashes to use to access the “deleted” data.

New GH Archive Research
Enter Sharon. As part of Truffle’s CFP, Sharon Brizinov pitched the Truffle team on digging into GH Archive to identify force push events and then scan them for secrets. We released his research last week on the blog; he found hundreds of live secrets and made $25k in bounties!
The golden nugget of information in Sharon’s research was identifying a type of PushEvent
that’s likely used to cover up a leaked secret: a zero-commit force push.
Let's briefly dig into force pushing to understand what a zero-commit force push is.
What is a force push?
When you run a normal git push
, Git checks that the remote branch is an ancestor of your local branch. If someone else has pushed new commits, your push stops with a “non‑fast‑forward” error, so you don’t accidentally wipe out their work.
git push --force
(or the safer git push --force-with-lease
) tells the server, “move the branch pointer to exactly the commit I have, even if that discards remote history.” The branch ref is updated, and any commits that are no longer reachable from any branch or tag become dangling commits. Importantly, if you know the commit hash, you can still access these dangling commits on GitHub.
When is it appropriate to force push?
Force pushing rewrites shared history, so use it sparingly and never on a protected or shared‑release branch without team consent. Here are a few legitimate cases:
Scrubbing sensitive data – After rewriting history with tools such as
git filter-repo
or BFG Repo‑Cleaner to remove secrets, you must force push to replace the contaminated history on the server.Tidying a personal feature branch – Before opening a PR, you might squash or re‑order commits and then force push to your own branch.
Undoing a bad merge or commit series – You can reset the branch tip to a known‑good commit and force push, provided teammates haven’t based work on the bad commits.
Always default to using --force-with-lease
; it aborts if someone pushed new work since your last fetch, reducing the risk of clobbering their changes.
What is a zero‑commit force push?
Suppose you realize the last two commits on feature/login-refactor
were a mistake. Then you run:
git reset --hard HEAD~2
# move branch pointer back two commits
git push --force-with-lease

No new commits accompany this push, so GitHub creates a PushEvent
whose commits
array is empty. The force push event appears in sources like GH Archive, but it shows that the branch ref moved (3 commits back in this case), rather than adding new objects (hence the commits: []
).
This was really insightful work by Sharon!
What does the Force Push Scanner do?
The Force Push Scanner has one goal: surface secrets in your GitHub repositories that you didn’t know were accessible. Identifying the dangling commits and scanning occurs in 3 steps.
Query GH Archive for Force Push events
GH Archive is a massive dataset containing 26Tb of public GitHub event data from 2015 to today. It’s publicly available to anyone on BigQuery. We’re only interested in PushEvent
data containing zero commits commit: []
. These are our Force Push events.
If you’re unfamiliar with BigQuery, you might be shocked to learn that querying 26Tb of data costs about $170 (at the time of this research). Instead of asking every user of this tool to spend that much to build the same dataset, we’re maintaining a ~2GB dataset that contains only the Force Push events from GH Archive.
How to Access the Dataset
Option 1: Download the Pre-built SQLite Database (Recommended)
Option 2: Query the Public BigQuery Table (Advanced)
This Big Query table is free to query (unless you’ve already used your GCP allotment of free query bandwidth this month). Here’s a query to get you started:
You’re welcome to query the GH Archive Big Query dataset directly, but it can get expensive.
2. Identify Dangling Commits
The core logic of the Force Push Scanner is identifying dangling commits based on the Force Push event log.
A (simplified) Force Push event in GH Archive looks like this:
The key attributes that concern us are the repository name
and before
values.
The before
value represents the previous commit at the tip of the branch before the force push. It may no longer exist on the branch after the push. In our dataset, this value often represents the commit hash of a dangling commit (but not always).
Our next step is to (a) identify if the before
commit is a dangling commit, and (b) if so, walk the commit history to identify all dangling parent commits of the before
commit. This provides us with the most extensive set of dangling commit data to scan for secrets.
In the tool, after cloning the repo, we run two git
commands to answer the questions above.
git fetch origin <before>
This command will fetch the dangling commit from GitHub’s servers and relevant commit objects to complete their history.
git rev-list <before>
This command returns a list of all commits in reverse chronological order that are reachable by following the parent links from the before
commit. We then iterate through the list to identify the first parent commit in any branch included in our normal git clone <repo>
commit history.
Sometimes, the before
commit itself is included in the existing commit history. These aren’t useful for scanning because TruffleHog would normally scan them.
Sometimes, we only identify one dangling commit (the before
commit), and sometimes we find a whole commit history of hundreds of dangling commits.
The most interesting result is this error in response to the git fetch origin <before>
command.

This error indicates the commit object is no longer available on GitHub's servers. This could be due to routine garbage collection or, in some cases, a manual deletion request processed by GitHub support.
In the CLI output from the Force Push Scanner, we print the following statement: “This commit was likely manually removed from the repository network.”

Scan the Dangling Data using TruffleHog
This part is the simplest. We run the following TruffleHog command to scan only dangling commits.
Importantly, the before
value is passed into the --branch
flag and the first non-dangling commit (derived from git rev-list <before>
) is passed in the --since-commit
flag.
How large a problem is this?
We surveyed 1,500 repositories from the dataset to answer the following question: For every zero-commit force push event, how many unique* dangling commits can we discover?
The answer: 3.68 unique dangling commits.
On average, each of the 15 million zero-commit force push events will lead researchers to discover ~3.5 unique dangling commits. This implies tens of millions of dangling commits are still accessible on GitHub.The volume of unintentionally retained historical data represents a severe and underappreciated security exposure across open-source projects.
*Unique meaning we deduplicated dangling commits within repositories, to ensure that multiple force push events from a single repo don’t inflate the results.
Conclusion
Rewriting history on GitHub doesn't make the old commits disappear. Our research shows that tens of millions of these dangling commits persist, creating a vast and unmonitored attack surface. The Force Push Scanner makes this forgotten data discoverable, but detection is only the first step. Use what you find not just to address past exposures, but to champion the preventative guardrails, like pre-commit hooks and CI pipeline checks, that stop secrets from becoming a permanent part of your repository’s hidden history in the first place.