Dangling commits get "removed" with a force-push — but they never really leave. shadowGIT finds them.
- Why shadowGIT?
- Features
- Installation
- Quick Start
- Usage
- Output Examples
- How It Works
- Requirements
- Contributing
- Disclaimer
- License
When a developer accidentally commits a secret and "fixes" it with a git push --force, the
old commit is orphaned — it's no longer on any branch, so it disappears from the normal UI.
But GitHub keeps that object around and still serves it by its SHA. Anyone who can recover the
SHA can read the "deleted" code, secrets and all.
shadowGIT automates the recovery of those dangling commits from public activity feeds so you can find leaked credentials before an attacker does.
| 🔎 Repository Scanning | Scan a specific repository for dangling commits |
| 👤 User Activity Analysis | Sweep every public repo a GitHub user has pushed to |
| 🔗 Direct Commit Links | Prints a ready-to-open URL for each finding |
| 📊 JSON Output | Machine-readable output for pipelines and tooling |
| 🚀 Fast & Dependency-light | Pure Python + requests, no auth required for public repos |
| 🔒 Security Focused | Purpose-built to surface accidentally exposed secrets |
Install straight from PyPI with pipx (recommended — it keeps the CLI
isolated in its own environment and on your PATH):
pipx install shadowgitOther installation methods
Using pip:
pip install shadowgitFrom source (for development):
git clone https://github.com/0xCardinal/shadowGIT.git
cd shadowGIT
pipx install -e . # editable installVerify the install:
shadowGIT --help# Scan a single repository
shadowGIT github -r owner/reposhadowGIT github [-r owner/repo | -u username] [--json]
| Command | Description |
|---|---|
shadowGIT github -r owner/repo |
Scan a specific repository |
shadowGIT github -u username |
Scan all public push activity for a user |
shadowGIT github -r owner/repo --json |
Repository scan with JSON output |
shadowGIT github -u username --json |
User scan with JSON output |
[+] Scanning GitHub repo: owner/repo
[!] Found dangling commit: 0ae67fe748e0b6ca52066e76611f4237a0ace744
-> https://github.com/owner/repo/commit/0ae67fe748e0b6ca52066e76611f4237a0ace744
{
"repository": "owner/repo",
"commit_sha": "0ae67fe748e0b6ca52066e76611f4237a0ace744",
"author": {
"name": "Author Name",
"email": "author@example.com"
},
"message": "Commit message",
"secrets_found": [],
"url": "https://github.com/owner/repo/commit/0ae67fe748e0b6ca52066e76611f4237a0ace744"
} push events (GitHub API) candidate SHAs branch_commits page
┌──────────────────────────┐ ┌────────────────┐ ┌──────────────────────┐
│ PushEvent → before, head │ ─▶ │ dedupe & queue │ ──▶ │ spoofed-commit marker? │ ─▶ 🚩 dangling
└──────────────────────────┘ └────────────────┘ └──────────────────────┘
- Collect activity — shadowGIT reads a repository's (or user's) public events from the GitHub
API. Each
PushEventexposes thebeforeandheadcommit SHAs of the push. - Derive candidates — a force-pushed-away commit shows up as either the old
headof one push or thebeforeof a later push, so shadowGIT gathers and de-duplicates both. - Check reachability — for every candidate SHA it requests GitHub's
branch_commitspage. When GitHub renders its spoofed commit warning, the commit exists in the repo but belongs to no branch's history — a dangling commit. - Report — findings are printed with a direct commit URL, and (with
--json) enriched with author and message metadata pulled from the commits API.
- Python 3.11 or higher
- GitHub API access — no authentication required for public repositories
Issues and pull requests are welcome! Please open an issue on the issue tracker to report bugs or propose features.
shadowGIT is intended for authorized security research, auditing, and educational use only. Only scan repositories and accounts you own or have explicit permission to assess. You are responsible for complying with all applicable laws and the terms of service of any platform you use.
Released under the MIT License — see LICENSE for details.
Built with ❤️ by 0xCardinal