Skip to content
Insights · Engineering

Name the secret the history holds

Name the commit that still holds the secret. Show a fresh clone still has it, or that history was rewritten. Revoking the live value is a different proof.

Scroll
EngineeringOct 20, 202612 min readBy Salman Naqvi, Founder & CEO
Name the secret the history holds

A secret was committed. It may already have been revoked, and the file at the tip of the branch may already be clean. Tonight, three things, in a repository you or the client can clone. Name the commit that still contains the secret, by the hash a fresh clone can still resolve. Say whether the live credential was revoked, and do not record that revoke as this proof. Then show that a fresh clone still contains the old value, or write that the history was rewritten and name one older clone you checked, or write that you could not check it. If the name, the revoke, or the clone requires the laptop that made the commit, the history is still theirs.

This is the value a commit still holds. It is not the afternoon you revoke the live credential and prove a call with the old value fails. That afternoon — list it, revoke it without the person who minted it, show the old value fail — is revoke the key they minted. A revoked key can sit in a commit forever and still fail on the wire. A key that fails on the wire can sit in a commit and be the next person's copy. The revoke article already asks you to write where a copy might remain. A sentence that says "history" is not a hash. It is also not the license text a dependency added, which is name the license the build added. A license is permission. A secret is a credential. It is not the list of hosts the running process calls. Name every host the build calls is that list. A host can be clean while the repository that configures it is not.

GitHub's own page on removing sensitive data draws the line the revoke does not draw. Sensitive data can be removed from the history of a repository if you can coordinate with everyone who has cloned it and you are willing to manage the side effects. If the data is a secret, such as a password, a token, or a credential, the first step is to revoke or rotate it. Once it is revoked or rotated, it can no longer be used for access, and that may be sufficient. Rewriting the history may not be warranted. The page then says what a rewrite is not. If you only rewrite history and force-push, the commits with the sensitive data may still be accessible in any clone or fork, directly via their SHA-1 hashes in cached views on GitHub, and through any pull request that references them. You cannot remove the data from another person's clone. They have to clean that clone. GitHub Support will not help remove data when the risk can be mitigated by rotating the credential (GitHub, Removing sensitive data from a repository). A new commit that deletes the file from the tip is not, on that page, the removal. The removal they describe rewrites history, changes the hashes of the bad commit and of every commit after it, and still leaves every clone you did not touch.

How often a later commit cleans the tip, and how often anyone rewrites the history, was measured on public GitHub in a paper that is not a vendor's dashboard. Michael Meli and Bradley Reaves, at North Carolina State University, and Matthew R. McNiece, at North Carolina State and Cisco, presented the work at the Network and Distributed System Security Symposium in 2019. They examined billions of files with two approaches: a nearly six-month scan of real-time public commits through the GitHub Search API, and a public snapshot covering 13% of open-source repositories. They looked at private-key files and 11 platforms whose API keys have a distinctive format. The abstract's result is that leakage affected over 100,000 repositories, and that thousands of new, unique secrets were leaked every day. They say the detectors were built to be conservative, and that the work is not exhaustive (Meli, McNiece, and Reaves, How Bad Can It Git?, NDSS 2019). Public repositories in 2018 and 2019 are not your client's private history. Over 100,000 repositories is not a count of secrets, and "thousands" per day is not an integer you should invent. 13% is the share of open-source repositories in the snapshot, not a share of those repositories that leaked.

The removal study is the part that matches a clean tip. Starting on 4 April 2018, they monitored secrets found through the Search API. For the first 24 hours they checked hourly, and after that daily, whether the repository, the file, and the secret still existed on the head of the default branch. About 6% of detected secrets were removed in the first hour. At the end of the first day, over 12% were gone. After 16 days, only 19% were gone. They also say 19% were removed at some point in roughly two weeks, most of those in the first 24 hours, which means 81% were not removed. 6 is not 12. 12 is not 19. 19 and 81 are the pair that adds to 100. "About 6%" and "over 12%" are their hedges. 16 days and "roughly two weeks" are their two phrases for the same 19%. Those checks asked whether the secret was still on the head. They did not, by themselves, ask whether an older commit still contained it.

The history check is a separate sentence, and it is the one this page is for. They looked at users who removed the secret while keeping the repository, which they had already established was the common case, because files and secrets disappeared faster than repositories did. For every such case they queried the GitHub Commits API. If the history had been rewritten, the commit they had discovered would no longer be accessible. They found that none of the monitored repositories had their history rewritten, so the secrets remained reachable through git history. "None" is those repositories, the ones that cleaned the head and kept the repo, in the window they monitored. It is not a finding about every repository on GitHub, and it is not a finding about a private repository in 2026. It is a finding that a commit which removes a secret from the head is not a rewrite. In absolute terms they say 19% of their results "amounts of tens of thousands of secrets," and they call that a lower bound on secrets that were sensitive. "Tens of thousands" is their phrase. Do not replace it with an integer they did not print. Do not borrow a later vendor's count of new secrets in 2024 and average it with this 19%. The validity figures, and the public-versus-private shares, are already on the revoke article. They measure whether a secret still works. They do not measure whether a commit was rewritten.

The sensitivity estimate is a third object, and it is not the removal rate. They manually reviewed a random sample of 240 candidate secrets, split evenly between Amazon AWS keys and RSA keys, so 120 of each. Two of the three authors coded each one. 88.8% of the judgments agreed, with a Cohen's kappa of 0.753. Disagreements went to the third author, who had not seen the earlier codes, and then to a consensus. They excluded 5 secrets they could not class as sensitive or not, and 4 that were not valid secrets. From that sample they estimated that 89.10% of all discovered secrets were sensitive, 93.74% of API secrets, and 76.24% of asymmetric keys. The estimate treats the AWS review as representative of API keys and the RSA review as representative of asymmetric keys. 88.8 is agreement between raters. It is not the 89.10 sensitivity estimate. 89.10 is not 93.74, and 93.74 is not 76.24. A different cut of the RSA keys says nearly 50% of all occurrences came from the most common unique 0.1% of keys, which they describe as keys with multiple owners and likely test keys. Nearly 50 is not 89.10. The BigQuery half of the study is licensed repositories only, which they treat as a more mature slice. A licensed public snapshot is not a private client repository, and a test key in the common 0.1% is not proof that the value in your commit is a test.

Do the three proofs on one real secret, in a clone, without printing the value into a ticket. First, the commit. Search the history, not the working tree of the latest commit. Record the full hash that still contains the secret, the path, and the date of that commit. A scanner that reads only the files on the current branch head is the check Meli ran for the 6%, the 12%, and the 19%. It will report a clean repository that fails this proof. Second, the live credential. Confirm it was revoked or rotated, which is the other article, or write that it was not and go do that before you talk about history. A rewrite of a secret that still works is the GitHub page's warning in the other order: they say rotation may be enough, and they also say support will not purge history when rotation addresses the risk. Rotation without a look at the commit is the revoke article stopping early. A look at the commit without rotation leaves a working credential in a place you just advertised by naming the hash. Third, the clone. Clone again, to an empty directory, and show that the hash is still reachable and still contains the value. If you rewrote history, the fresh clone must not contain it, and you still have to name one older clone — a teammate's laptop, a fork, a CI cache — or write that you could not. The GitHub page's list is the reason: force-push does not edit someone else's disk, a fork, a pull request, or a cached view addressed by the old hash.

Three moves feel like the third proof and are a second problem. Adding the file to `.gitignore` after the commit. The ignore file changes what the next commit adds. It does not change the commit you already made. Deleting the file in a new commit and merging it, which is the head-cleaning Meli measured, including the finding that none of those repositories had rewritten history. Pasting the secret into a chatbot so it can tell you whether the string "looks real," which puts the credential in a vendor you did not have this morning and does not name a hash. The diff you were supposed to open before the change shipped is read their change before it ships. A diff of the latest commit, read carefully, still does not show a secret that was added three merges ago and deleted on the tip.

Searching a repository is a product shape, and it is not this proof. Haystack is a search layer across documents, data lakes, and repositories, with indexing as new data lands. The shape is that a repository can be something you query. That case is not a scan of git history for a credential, and it is not a rewrite. Nothing in the description is a headcount, a duration, or a result figure. A search box that returns the current file, and cannot return an older commit, is the head check again. Agency work of this kind is not published here as a case study with a count of secrets found.

There is a version of this where hiring the history check is the wrong purchase. The work is not sold, so there is no client repository and no commit a build could have made. The client asked to meet the people who will write the code. The room only has a slide. Or the buyer wants a template policy and does not have a clone to make. A policy that names a rewrite and has never resolved a hash is a brochure. The criteria for choosing a firm, when that is the actual decision, are the ones on the page for a top saas development company. They do not rank anyone, including us. Or the buyer wants a legal opinion on whether the commit was a disclosure. This page will not give them one.

When the value is already in a commit you cannot see from the tip, the missing object is specific. A clone you or the client can make, a hash, a statement of whether the live credential was revoked, and a second clone that still shows the value or a written limit on the clones you could not reach. A white label software development partner that expects this will put the repository in an organization the client can administer before the first push, because a history you cannot clone is a history you cannot name. One that can only "clean up" from a laptop it takes home is telling you who holds the older copy. The accounts and the repository that have to be in the client's name at the end are the handover you must receive. The handover lists the repository with its real history. It does not perform tonight's hash. If the next slice is one capability, an AI integration sprint is the shape where the acceptance sentence can require the three proofs on one deliberate test value, in a repository you can delete afterward. If you are not sure which repositories the current work already lives in, a two-week AI readiness assessment maps them. The findings are yours. They are not a count of secrets, and they are not the 19%.

A session your product issued to a person, and a secret sitting in a commit, fail in different places. Ending the session is end the session they still hold. A passage's position inside a prompt is where the supporting passage sat. Do not record a logout, or a model's answer, as a hash. Before you tell the client the repository is clean, do the three proofs on one real value. Name the commit. Say whether the live credential was revoked. Show a fresh clone, or write down the clone you could not open. A green scan of the latest file is the head. The history is the page you can defend.

Find this useful? Tell Google to show you more of it.

Let's put AI to work in your business.

A 30-minute call. You bring the workflow or the roadmap — we'll tell you what's feasible, what it costs, and what we'd build first.

Book a call→