DEV Community

Sharjeel Zubair
Sharjeel Zubair

Posted on

I Pushed Our Backend Repo to a Public GitHub by Accident. Here's What Happened in 47 Minutes

TL;DR: Leaking real source code (not just .env files) exposes your business logic, internal APIs, hardcoded secrets buried deep in commits, and your auth flow. Bots will find it within minutes. Here's a timeline of what actually happens and how to respond.

The Mistake

I was helping a friend split their monolith into two repos. One was meant to be open-sourced (a CLI tool), the other was their actual backend — payment processing, user data, the works.

I ran:

gh repo create their-org/backend --public --source=. --push
Enter fullscreen mode Exit fullscreen mode

Notice the --public. I meant --private. The CLI didn't ask for confirmation. The repo went live at 14:03.

We caught it at 14:50. Forty-seven minutes. Here's what happened in that window.

Minute 0–2: GitHub's Indexers Wake Up

Within seconds, GitHub's search index picked it up. The repo was indexed and searchable by keyword. This matters because a lot of secret-scanning bots don't crawl GitHub directly — they query the search API for patterns like AWS_SECRET_ACCESS_KEY or Bearer eyJ.

GitHub's own secret scanning ran almost immediately. We later got two automated emails from AWS and Stripe saying our keys had been auto-revoked because GitHub notified them. That's the only reason this story isn't worse.

Minute 2–10: The Bots Arrive

We checked the repo's traffic graph after the fact. In the first 10 minutes there were clones from 14 unique IPs. None of them were us.

These are scrapers. They don't read your code — they git clone --mirror, then run tools like trufflehog and gitleaks against the entire history. Not just the current commit.

That's the part most people miss. You can git rm a secret and push, but if it was ever committed, it's in the pack files. A scanner will find it:

trufflehog git file://./backend --only-verified
Enter fullscreen mode Exit fullscreen mode

In our case, an old commit from 2022 had a hardcoded Postgres connection string for a now-defunct staging DB. The bot found it. The DB was offline so nothing happened, but the credential is now in someone's database forever.

Minute 10–30: Business Logic Exposure

This is the part nobody talks about. Even if you have zero secrets in your code, leaking the source itself is bad.

What we exposed:

  • Webhook signature verification logic. Anyone could now see exactly how we validated Stripe webhooks and look for bugs in our implementation.
  • Rate limiting rules. The exact thresholds and the redis keys we used.
  • Internal API routes that weren't documented anywhere public, including admin endpoints behind a "secret" path.
  • The promo code generation algorithm. It was deterministic based on user ID. Oops.

Here's a sanitized version of what was sitting in utils/promo.js:

function generatePromoCode(userId, campaign) {
  const hash = crypto
    .createHmac('sha256', PROMO_SECRET)
    .update(`${userId}:${campaign}`)
    .digest('hex');
  return hash.slice(0, 8).toUpperCase();
}
Enter fullscreen mode Exit fullscreen mode

PROMO_SECRET was in env, fine. But the algorithm being public means an attacker who later gets just the secret (from a different leak, a former employee, anything) can generate unlimited promo codes for every user.

Security through obscurity is not security — but losing obscurity does shift your threat model. You should know it shifted.

Minute 30–47: The Discovery and Panic

A teammate noticed the repo was public when they got a GitHub notification ("Your repo is now visible to..."). The next 17 minutes were chaos. Here's the order we did things in, which in retrospect was mostly right:

  1. Make the repo private immediately. Don't delete it yet — you'll want the audit trail.
  2. Rotate every secret in the codebase, even ones not flagged. Database passwords, API keys, JWT signing keys, webhook secrets, OAuth client secrets.
  3. Invalidate all active sessions. If your JWT signing key changed, this happens automatically. If you use server-side sessions, flush them.
  4. Check audit logs on AWS, Stripe, GitHub, your DB. Look for unusual API calls in the leak window and the next 24 hours.
  5. Then you can delete the repo or scrub history if you want — but assume the code is already cloned.

What I Wish We Had

A pre-push hook that screams when you're about to push to a public remote. Something like:

#!/bin/sh
# .git/hooks/pre-push
remote_url=$(git remote get-url "$1")
visibility=$(gh repo view "$remote_url" --json visibility -q .visibility 2>/dev/null)

if [ "$visibility" = "PUBLIC" ]; then
  echo "⚠️  You are pushing to a PUBLIC repo: $remote_url"
  echo "Type 'yes-public' to continue:"
  read confirmation
  [ "$confirmation" = "yes-public" ] || exit 1
fi
Enter fullscreen mode Exit fullscreen mode

Also worth setting up:

  • GitHub org policies that require admin approval to create public repos.
  • Pre-commit secret scanning with gitleaks so secrets never enter history in the first place.
  • Branch protection even on main, so a force-push can't accidentally publish private branches.

The Takeaway

Leaking source code is not a binary "did you leak secrets or not" event. The real damage is:

  1. Historical secrets in old commits you forgot about.
  2. Business logic that informs future attacks even after you rotate.
  3. Internal endpoints and assumptions about what's "hidden."

Assume any public push, even for 60 seconds, has been mirrored. Rotate everything. Audit everything. And put a hook on your machine before you trust yourself with --public.

Top comments (0)