https://github.com/tg12/phantomcreds
Automated detection and tracking of credential-harvesting and unsafe credential-storage repos on GitHub. Scores suspicious repos daily, captures evidence, and files issues only when fixable.
https://github.com/tg12/phantomcreds
appsec automated-scanning credential-harvesting credential-security github-actions osint python security security-research threat-detection
Last synced: 13 days ago
JSON representation
Automated detection and tracking of credential-harvesting and unsafe credential-storage repos on GitHub. Scores suspicious repos daily, captures evidence, and files issues only when fixable.
- Host: GitHub
- URL: https://github.com/tg12/phantomcreds
- Owner: tg12
- License: other
- Created: 2026-05-18T10:15:06.000Z (26 days ago)
- Default Branch: main
- Last Pushed: 2026-05-26T07:35:09.000Z (18 days ago)
- Last Synced: 2026-05-26T09:29:27.188Z (18 days ago)
- Topics: appsec, automated-scanning, credential-harvesting, credential-security, github-actions, osint, python, security, security-research, threat-detection
- Language: Python
- Homepage: https://labs.jamessawyer.co.uk/ai-slop-intelligence-dashboards/
- Size: 439 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
phantomcreds
Automated detection and tracking of credential-harvesting and unsafe credential-storage repos on GitHub
A JS Labs project -
part of the AI Slop Intelligence initiative.
Runs every day. Scores suspicious repos. Captures evidence. Files issues only when the code looks fixable.
---
## Why this exists
The counterintuitive move here is restraint.
The easy version of this project is a giant crawler that flags every repo mentioning `token`, `cookie`, or `OAuth`. That path loses trust immediately because the maintenance tax becomes larger than the signal. Legitimate software stores tokens. Legitimate tools proxy requests. Legitimate integrations use OAuth callbacks.
The useful version is narrower: detect repos whose docs and code jointly suggest credential harvesting, unsafe persistence, replay posture, or exposed management surfaces. Record the evidence every day. File issues only when the target still looks like a maintainable software project rather than an overt abuse kit.
That is what **phantomcreds** does.
It is built around one premise: operator trust is the product. If the scanner cannot explain *why* a repo was flagged and *which lines* created that judgment, it is not finished.
---
## What it does
**phantomcreds** runs a daily GitHub Actions job that:
1. Searches GitHub repositories for posture phrases such as `multi-account`, `no API key needed`, `auth file`, `shared subscription`, session reuse, provider relays, and imported browser-auth language
2. Searches code across Go, Python, JavaScript, and TypeScript for credential-risk fingerprints such as token or session persistence, raw `Authorization` forwarding, management auth bypass wrappers, wildcard management exposure, callback listeners bound to `0.0.0.0`, and committed secret-bearing `.env`, `.netrc`, `.pypirc`, Docker auth config, Terraform credential, private-key, service-account, and connection-string material
3. Fetches targeted high-signal files plus a bounded sweep of broadly text-like repo files directly from the GitHub API
4. Scores each repo against a repo-level evidence model that prefers multi-family matches over single-query noise, then biases toward recently pushed non-archived non-fork repos
5. Writes append-only ledgers to this repo:
- [`data/repos.jsonl`](data/repos.jsonl) for per-repo scan outcomes
- [`data/findings.jsonl`](data/findings.jsonl) for concrete findings with evidence
6. Updates the README dashboard automatically
7. Opens or updates one issue per target repo **only** when the findings are specific and fixable
8. Leaves overt abuse-oriented repos as `report_only` records instead of spamming them with issues
No servers. No database. No dashboard backend.
---
## Detection model
The scanner combines four evidence classes:
| Evidence class | What it means |
|---|---|
| Harvest posture | README or description markets shared subscriptions, relays, auth-file import, or "no API key needed" positioning |
| Credential persistence | Code writes token-like material to local auth files or serialized session stores |
| Direct secret exposure | Current repo files appear to contain committed cloud, model-provider, CI, package-registry, webhook, SSH, service-account, registry-auth, Terraform, or database-connection credentials; evidence is redacted in stored findings and issue bodies |
| Unsafe exposure | Callback listeners bind broadly, management routes use wildcard CORS, or auth bypass wrappers weaken the control plane |
| Centralized leakage | Request logging or telemetry paths appear to forward raw credential-bearing headers |
Not every hit is issue-worthy.
The product rule is deliberate:
- `file_issue`: concrete technical defect with defensible evidence and a plausible maintainer remediation path
- `report_only`: repo posture looks overtly abusive, or the scan can defend the risk but issue filing is unlikely to improve behavior
- `watch`: suspicious signals exist, but the evidence is not strong enough for automated external action
This is the main maintenance-tax control. It avoids treating every suspicious repo as a workflow target.
---
## Code smell and maintenance tax
Three uncomfortable truths drive the design:
1. The biggest failure mode is not false negatives. It is false-positive automation with weak evidence. That destroys the product faster than missing a repo.
2. Repo families matter more than individual repos. Once one credential-harvesting codebase is confirmed, the next high-leverage step is searching for reused paths and symbol names across derivatives.
3. The project should prefer append-only evidence over complicated state machines. Daily JSONL ledgers and deterministic README updates are lower-maintenance than a bespoke datastore.
### Devil's-advocate view
The comfortable answer is "scan everything and file everything."
Why that loses:
- GitHub code search is rate-limited and noisy.
- Most repositories that mention tokens are normal software.
- Bulk issue creation on overt abuse repos creates work without changing outcomes.
- A complex crawler increases breakage surface and lowers operator confidence.
The winning move is smaller:
- search-first discovery
- multi-language query families
- targeted file fetches
- repo-level scoring
- one issue per repo at most
- explicit `report_only` for abuse-heavy cases
That is less dramatic and more durable.
---
## What you will regret not knowing later
- Which repo families cloned the same unsafe credential paths
- Which findings were recurring but never issue-worthy
- Which wording in README posture was a leading indicator before the code confirmed it
The data model is structured so those questions can be answered from the ledger later without redesigning the project.
---
## Three questions to ask next
1. Which clone-family fingerprints should graduate from "interesting" to "hard finding" after recurring across multiple repos?
2. Which issue classes actually lead to maintainer response, and which are operational dead ends that should stay `report_only`?
3. At what scale does GitHub Search API noise justify adding a local corpus or scheduled seed list?
---
## Live dashboard
| Date | Scanned | Flagged | High Risk | Issue-Worthy | Report Only | New High Risk |
|------|---------|---------|-----------|--------------|-------------|---------------|
| 2026-05-30 | 31 | 18 | 12 | 16 | 8 | 1 |
| 2026-05-29 | 41 | 19 | 14 | 17 | 9 | 2 |
| 2026-05-27 | 36 | 17 | 13 | 14 | 5 | 0 |
| 2026-05-26 | 34 | 19 | 13 | 16 | 6 | 0 |
| 2026-05-25 | 39 | 20 | 11 | 17 | 5 | 0 |
| 2026-05-24 | 33 | 17 | 11 | 14 | 5 | 1 |
| 2026-05-23 | 33 | 18 | 14 | 17 | 9 | 0 |
| 2026-05-22 | 34 | 15 | 13 | 14 | 7 | 0 |
| 2026-05-21 | 33 | 18 | 12 | 17 | 7 | 0 |
| 2026-05-20 | 30 | 14 | 11 | 14 | 5 | 0 |
| 2026-05-19 | 55 | 21 | 14 | 16 | 7 | 1 |
| 2026-05-18 | 115 | 69 | 45 | 46 | 28 | 45 |
---
## Highest-risk repos today
| Repo | Score | Findings | Action | Stars | Updated |
|------|-------|----------|--------|-------|---------|
| jeffnash/CLIProxyAPI | 1.000 | 7 | report_only | 0 | 2026-05-30 |
| leic4u/CLIProxyAPIPlus | 1.000 | 7 | report_only | 3 | 2026-05-29 |
| lzt404/CLIProxyAPI-RUM | 1.000 | 7 | report_only | 1 | 2026-05-29 |
| fxzer/CLIProxyAPI | 1.000 | 7 | report_only | 0 | 2026-05-27 |
| router-for-me/CLIProxyAPI | 1.000 | 7 | report_only | 35434 | 2026-05-30 |
| 6enta0/CPAplus | 1.000 | 6 | file_issue | 16 | 2026-05-27 |
| kdjahdiel-code/c-pipe-engine | 1.000 | 6 | file_issue | 0 | 2026-05-29 |
| rituprodhan-ops/c-channel-engine | 1.000 | 6 | file_issue | 0 | 2026-05-29 |
| BlueSkyXN/CPA-Core-LTS | 1.000 | 6 | report_only | 2 | 2026-05-29 |
| Zeuyel/Proxy-me | 1.000 | 6 | report_only | 1 | 2026-05-29 |
| kittors/CliRelay | 1.000 | 5 | file_issue | 764 | 2026-05-30 |
| Tatsumaki123123/cliproxyapi | 1.000 | 5 | file_issue | 0 | 2026-05-28 |
| SylphAI-Inc/AdalFlow | 0.560 | 2 | file_issue | 4154 | 2026-05-30 |
| Wei-Shaw/claude-relay-service | 0.410 | 2 | report_only | 11923 | 2026-05-30 |
| kunish/wheel | 0.390 | 2 | watch | 1 | 2026-04-29 |
| vlad-levchenko/claude-skills | 0.390 | 2 | watch | 0 | 2026-05-30 |
| winstonwilliamsiii/BBBot | 0.350 | 1 | file_issue | 2 | 2026-05-29 |
| PlanExeOrg/PlanExe | 0.200 | 1 | file_issue | 381 | 2026-05-28 |
---
## Data format
**repos.jsonl** - one row per scanned repo per run:
```json
{
"full_name": "owner/repo",
"composite": 0.82,
"classification": "high_risk",
"action": "file_issue",
"finding_count": 4,
"issue_worthy_count": 3,
"stars": 431,
"scan_date": "2026-05-18",
"created_at": "2026-04-29T20:14:00Z",
"updated_at": "2026-05-18T08:42:11Z",
"discovery_sources": ["auth-bypass", "callback-exposure", "shared-subscription-posture"],
"finding_types": ["callback_exposure", "credential_persistence", "management_auth_bypass"]
}
```
**findings.jsonl** - one row per concrete finding:
```json
{
"repo_full_name": "owner/repo",
"finding_type": "exposed_secret",
"title": "Secret-bearing credential material appears committed in current repository files",
"severity": "high",
"confidence": "confirmed",
"summary": "Current repository files appear to contain committed cloud, model-provider, CI, package-registry, webhook, SSH, or service-account credential material. Evidence is redacted in the report output.",
"issue_worthy": true,
"scan_date": "2026-05-18",
"evidence": [
".env:1 - OPENAI_API_KEY=[REDACTED:sk-pro...3456]",
"deploy/id_rsa:1 - [REDACTED:-----BEGIN OPENSSH PRIVATE KEY-----]"
]
}
```
---
## Setup
### 1. Create or fork the repo
This repo commits its own ledgers back to `main` after each successful scan.
### 2. Add a GitHub PAT secret
Create a **classic** Personal Access Token with scopes:
- `public_repo`
- `read:user`
Add it as `GH_TOKEN` under:
**Settings -> Secrets and variables -> Actions -> New repository secret**
### 3. Enable Actions
The workflow runs at **07:00 UK time daily** using the `Europe/London` clock:
- `06:00 UTC` during British Summer Time
- `07:00 UTC` during Greenwich Mean Time
GitHub cron is UTC-only, so the workflow triggers at both UTC hours and only proceeds when local London time is `07`.
Manual trigger:
**Actions -> Daily Phantomcreds Scan -> Run workflow**
### 4. Run locally
Safe local test run:
```bash
git clone https://github.com/YOUR_USERNAME/phantomcreds.git
cd phantomcreds
python -m venv venv && source venv/bin/activate
pip install -e .[dev]
PHANTOMCREDS_LOCAL_MODE=1 GH_TOKEN=ghp_your_token phantomcreds
```
This uses the same scan logic locally but:
- disables external GitHub issue creation by default
- does not rewrite the main `README.md`
- writes results under `.local/phantomcreds/`
- keeps the same GitHub API fetch, heuristic scoring, and issue-decision logic as the hosted run
Production-style local run:
```bash
GH_TOKEN=ghp_your_token \
PHANTOMCREDS_NOTIFY_EXTERNAL=1 \
PHANTOMCREDS_UPDATE_README=1 \
phantomcreds
```
Useful local overrides:
- `PHANTOMCREDS_OUTPUT_DIR=/tmp/phantomcreds-run`
- `PHANTOMCREDS_NOTIFY_EXTERNAL=0|1`
- `PHANTOMCREDS_UPDATE_README=0|1`
- `PHANTOMCREDS_REPORTS_FILE=/tmp/repos.jsonl`
- `PHANTOMCREDS_FINDINGS_FILE=/tmp/findings.jsonl`
- `PHANTOMCREDS_README_PATH=/tmp/README.md`
Operational difference from GitHub Actions:
- same discovery, fetch, scoring, and notification code paths
- no scheduler wrapper
- no Actions step summary unless `GITHUB_STEP_SUMMARY` is set
- local mode is the safer way to test scanner changes before allowing external issue creation
---
## False positives and exclusions
If a repo is repeatedly benign but matches the search posture, add it to [`data/allowlist.txt`](data/allowlist.txt), one `owner/repo` per line. Allowlisted repos are skipped entirely in future runs.
The scanner also applies built-in context filters before raising secret findings:
- redacted evidence snippets are ignored
- test, fixture, and docs paths are not treated as live secret exposure
- template files such as `.env.example` remain non-issues when they contain placeholders, but still raise findings if they contain real credential material
- Docker auth evidence must decode to printable `user:password` material before it is treated as a committed secret
- credential-persistence findings require nearby write or serialization behavior, not just words like `session` or `cookie`
This is a repo-level scanner. It does not store individual user identities, and it does not attempt attribution beyond public repository content.