Part 1 of 3 in Git Tales Series
Overview
In my recent bug hunting, I stumbled across something that perfectly shows why "deleted" doesn't always mean "gone." I discovered a particularly critical vulnerability that highlights a common oversight among developers, the misunderstanding of Git history. The target codebase had undergone multiple revisions, and one configuration file "appsettings.json" had been deleted in a recent commit.
Curious, I ran a quick scan of the Git history using tools like git log
and git diff
. To my surprise, the deleted file had once contained Shopify Admin API keys — plaintext credentials capable of granting administrative access to a Shopify store.
This wasn’t a file accidentally left in the final commit. It was a deleted file that had been committed earlier, still lingering in the repository’s history. This sort of vulnerability is often overlooked because many developers assume that once a file is deleted and pushed, it's gone. Git, however, retains every commit unless explicitly scrubbed.
Impact of the Vulnerability
The leaked Admin API token for Shopify was still valid when discovered and belonged to a store that had gone live in January 2025. The potential impact was significant and included:
Full Administrative Access 🔓
Read, update, and delete orders, products, customers, inventory, and themes.
Manage discount codes and price rules.
Add malicious ScriptTags to inject JavaScript into the storefront.
Data Breach Risk 📊
- Exposed customer PII (names, emails, order history).
- Violated data protection regulations like the GDPR.
Disruption of Store Operations 🛠
- Unauthorized configuration changes
- Loss or corruption of data
- Deletion of products or categories
In short, a valid Admin API key, even for a development store, has access to production-level functionality. This creates an enormous attack surface.
Proof of Concept (PoC) and Bounty
To responsibly demonstrate the impact, I conducted a limited set of read-only API calls using curl
Get store information
curl -X GET \
-H "X-Shopify-Access-Token: " \
https://.myshopify.com/admin/api/2024-01/shop.json
List all products
curl -X GET \
-H "X-Shopify-Access-Token: " \
https://.myshopify.com/admin/api/2024-01/products.json
View access scopes
curl -X GET \
-H "X-Shopify-Access-Token: " \
https://.myshopify.com/admin/oauth/access_scopes.json
The last API call (access_scopes.json
) confirmed that the token had read permissions across major endpoints as shown in the diagram below.
Responsible Disclosure ✅
The vulnerability was reported via the appropriate bug bounty platform. The organization responded quickly, revoked the token, and awarded a bounty for the finding.
Properly Remove Secrets from Git History
Many developers know how to delete a file from a repo, but far fewer understand that Git never forgets. A simple git rm
followed by a commit only removes the file from the working directory — not from history.
Step-by-Step: Clean Your Git History 🧼
Option 1: Using BFG Repo-Cleaner
# Delete a file from all history
bfg --delete-files config.json
# Remove secrets from history using a list
bfg --replace-text passwords.txt
Option 2: Using git filter-branch
Kindly note this method is slower but more flexible.
git filter-branch --force --index-filter \
"git rm --cached --ignore-unmatch config.json" \
--prune-empty --tag-name-filter cat -- --all
Important
- Force-push after cleaning. Commands below:
git push origin --force --all
git push origin --force --tags
- Rotate any secrets found in history. Very Important!!!!!!!!!
- Re-clone the repository to avoid residuals.
- Notify all collaborators so they can do the same.
Conclusion
This finding is a prime example of how code history can betray your security efforts. Developers often overlook deleted files, unaware that a sensitive credential committed even once can remain accessible unless explicitly purged.
Key Takeaways
Deleting a file doesn't delete it from Git history.
Admin API keys in Shopify offer massive control and should be treated like passwords.
Always audit code and Git history before pushing public repositories or publishing code.
Use automated scanners during CI/CD to catch secrets before they go live.
This is just the beginning. In the next parts of the Git Tales series, we’ll dive into:
🧩 Part 2: How Service account keys can be your demons in the cloud.
📝 Part 3: How committed Github tokens can expose your private repos.
Until then, treat your Git repo like an open diary — someone might be reading.