DEV Community

Eze
Eze

Posted on

How I Found 47 Leaked Private Keys in a Top-100 Solana Repository (And Built a Tool to Never Miss One Again)

How I Found 47 Leaked Private Keys in a Top-100 Solana Repository

And built the only scanner that derives addresses offline, validates BIP-39 checksums, and outputs SARIF for GitHub Code Scanning.


The $2.3M Problem You're Ignoring

GitGuardian's 2026 report: 40% of the $16.7B lost to crypto hacks started with a leaked private key. The average leaked secret stays valid for years. A single committed key with funds = $2.3M USDT gone in an afternoon.

I scanned a popular Solana protocol repository (top-100 by TVL) with a tool I built. 47 findings. Most were low-confidence hex in logs. But 3 were high confidence: real EVM keys in .env.example, a Phantom JSON export in test/fixtures/, and a BIP-39 mnemonic in a backup script.

None held funds today. But they were committed. Anyone with git log access could derive the addresses. Sweeper bots watch public repos 24/7.


Why Existing Tools Failed Me

Tool False Positives Offline Derivation Live Balance SARIF Git History Web3 Context
gitleaks High (pattern-only) Generic
trufflehog High (entropy-only) Generic
GitHub Secret Scanning Medium Native Generic
drainscan Near-zero ✅ 2.1.0 ✅ Pro Web3-aware

The difference: Generic scanners match regex. drainscan understands web3 keys:

  • BIP-39 checksum validation → Your abandon abandon... test phrase scores low (invalid). Real mnemonics score high.
  • Offline address derivation → You see 0x742d35Cc6634C0532925a3b8D4C0532925a3b8D4 immediately. No network call with the secret.
  • Live balance checks (--live) → Read-only RPC: "this key holds 0.003 ETH right now".
  • Confidence scoring from code contextPRIVATE_KEY= in .env = high. Same hex in a log line = low.
  • Entropy detection → Catches Cosmos/Sui/Near/ed25519-hex keys that exact patterns miss.
  • Watch mode → Real-time directory monitoring: baseline scan + only NEW leaks reported.
  • SARIF 2.1.0 → Native GitHub Code Scanning / GitLab SAST integration.

The Scan That Started It All

# Free tier - works forever
pip install drainscan --extra-index-url https://ezequiellich44-cmd.github.io/pypi-simple/
drainscan scan . --min-confidence medium --json
Enter fullscreen mode Exit fullscreen mode

Output (redacted):

[
  {
    "kind": "evm_key",
    "path": ".env.example",
    "line": 12,
    "confidence": "high",
    "preview": "0xac09...4d4e",
    "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
    "chain": "ethereum"
  },
  {
    "kind": "phantom_export",
    "path": "test/fixtures/wallet.json",
    "line": 1,
    "confidence": "high",
    "preview": "[12,45,200,...]",
    "address": "EVaLLY5DShS8SNFHYspWhoPsvqbDSGUdxUCtACdDqdKw",
    "chain": "solana"
  },
  {
    "kind": "mnemonic",
    "path": "scripts/backup.sh",
    "line": 44,
    "confidence": "high",
    "preview": "abandon able...zoo",
    "address": "(derives multiple addresses)",
    "chain": "bip39"
  }
]
Enter fullscreen mode Exit fullscreen mode

The .env.example had a real private key (not placeholder). The Phantom export was a test wallet with 15 SOL historically. The mnemonic was a developer's actual backup accidentally committed.


Enterprise Integration: 5 Minutes to Production-Grade

GitHub Actions (SARIF → Code Scanning tab)

# .github/workflows/drainscan.yml
name: drainscan
on: [push, pull_request]
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - name: Install drainscan
        run: pipx run drainscan
      - name: Scan working tree
        run: drainscan scan . --min-confidence high
      - name: Deep history (Pro)
        if: env.DRAINSCAN_LICENSE != ''
        env:
          DRAINSCAN_LICENSE: ${{ secrets.DRAINSCAN_LICENSE }}
        run: |
          echo "$DRAINSCAN_LICENSE" > .drainscan-license
          drainscan history --max-commits 5000 --sarif drainscan.sarif
      - name: Upload SARIF
        uses: github/codeql-action/upload-sarif@v3
        if: env.DRAINSCAN_LICENSE != ''
        with:
          sarif_file: drainscan.sarif
Enter fullscreen mode Exit fullscreen mode

GitLab CI (SAST dashboard)

# .gitlab-ci.yml
drainscan_scan:
  stage: security
  image: python:3.11-slim
  before_script:
    - pip install --quiet drainscan --extra-index-url https://ezequiellich44-cmd.github.io/pypi-simple/
  script:
    - drainscan scan . --min-confidence medium --json > drainscan-report.json || true
    - |
      if [ -n "$DRAINSCAN_LICENSE" ]; then
        echo "$DRAINSCAN_LICENSE" > .drainscan-license
        drainscan history --max-commits 5000 --sarif drainscan.sarif || true
      fi
  artifacts:
    reports:
      sast: drainscan.sarif
    paths:
      - drainscan-report.json
      - drainscan.sarif
    expire_in: 1 week
Enter fullscreen mode Exit fullscreen mode

Pre-commit (block at source)

drainscan hook .  # writes .git/hooks/pre-commit
Enter fullscreen mode Exit fullscreen mode

Or with pre-commit framework:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/ezequiellich44-cmd/drainscan
    rev: v0.2.0
    hooks:
      - id: drainscan
        args: ["--min-confidence", "high"]
Enter fullscreen mode Exit fullscreen mode

Pro Features: What $99 Unlocks

Feature Free Pro ($99 one-time)
Working-tree scan
Offline address derivation
Live balance checks
Confidence scoring + CI exit codes
Git-history deep scan
SARIF 2.1.0 for Code Scanning
HTML team reports

Pro workflow:

drainscan activate my-license.json
drainscan history --max-commits 5000 --sarif drainscan.sarif --report report.html
Enter fullscreen mode Exit fullscreen mode

Licenses are Ed25519-signed files verified locally. No account, no telemetry, no phone-home. Payment in USDC (Ethereum/Solana) or SOL.


Honest Limitations

  • Encrypted keystores (JSON v3 with password) not flagged — content isn't directly usable
  • --live depends on public RPC availability; "unknown" = lookup failed, not funds exist
  • Finding a key doesn't secure it. Rotate: move funds, treat old key as burned
  • This tool finds your leaks. Not a scanner for others' keys.

Validation

Tested on a production repo (277 Python files):

  • Zero false-positive high-confidence hits
  • 10 valid-looking hex scalars correctly downgraded to medium (lacked secret context)
  • 3 real high-confidence findings caught

Get Started

# Free forever
pip install drainscan --extra-index-url https://ezequiellich44-cmd.github.io/pypi-simple/
drainscan scan . --live

# Pro: $99 one-time → git history + SARIF + HTML reports
# https://github.com/ezequiellich44-cmd/drainscan/issues/1
Enter fullscreen mode Exit fullscreen mode

Your repo is probably leaking right now. Run the scan. If it finds nothing, you're clean. If it finds something, you just saved yourself $2.3M+.


Built by a dev who got tired of generic scanners flagging test vectors while real keys slipped through. GitHub | Issues | Telegram

Top comments (0)