Security tooling
Anvesha typed git add . without looking. One-tenth of a second later, GitHub had already decided that push wasn't going to go through.
Anvesha's connecting the homepage's newsletter form to a real email service, which means a real API key, which — for a first pass, just to get it working locally — she's dropped straight into a file called .env in the project root. It works. She's happy. Out of habit, since she's mid-flow and not thinking hard about it, she runs git add . and commits everything in the folder, .env included, and pushes.
remote: error: GH013: Repository rule violations found
remote: — Push cannot contain secrets
remote: —— Mailgun API Key
remote: locations:
remote: - commit: 8a1b2c3
remote: path: .env:1
remote:
remote: (?) Learn how to resolve a blocked push
remote: https://docs.github.com/code-security/...
The push simply never lands. Not "flagged for later review" — refused, on the spot, before the key ever reaches a public, permanent, forkable history. Anvesha didn't do anything unusual; this is close to the single most common way real secrets leak in the real world, and it very nearly happened here too.
Push protection: the wall that caught it
GitHub maintains a database of roughly 200 known secret formats — AWS keys, Stripe tokens, GitHub PATs, Mailgun keys among them — each with a recognizable shape. Push protection scans every incoming push against that list, before accepting it, and simply rejects anything that matches. It's on for every public repo Accelerate has, free, no configuration needed beyond the toggle already flipped in Settings → Code security.
.env file and it briefly existed in a local commit before the push was rejected. The safe move now isn't just "delete the file and try again" — it's rotating the key at Mailgun first, generating a fresh one, and treating the old one as compromised regardless of whether it ever technically reached GitHub's servers. This is the exact same lesson from Chapter 2's .gitignore callout, now happening for real: once a secret has been typed anywhere near a commit, the file removal isn't the fix, the rotation is.
Dependabot — the same idea, aimed at other people's code
The club's own code isn't the only thing that can carry a vulnerability — every dependency the project installs is code the club is trusting, sight unseen, and those get security patches too.
- Alerts (Settings → Code security → Dependabot alerts: on) — GitHub cross-references every dependency against the public Advisory Database and opens an alert the moment one of them has a known issue.
- Security updates (same page, next toggle) — goes one step further and opens an actual PR bumping the vulnerable package to a patched version, ready to review and merge.
- Version updates, for routine freshness rather than just security:
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule: { interval: weekly }
open-pull-requests-limit: 5
groups:
dev-deps: { dependency-type: development }
- package-ecosystem: github-actions
directory: /
schedule: { interval: weekly }
The second entry matters specifically because of Chapter 17's SHA-pinning discipline — it's how those pinned SHAs actually get refreshed on a schedule, instead of silently going stale forever once pinned.
CodeQL — reading the code itself for known-dangerous patterns
Push protection catches secrets by their shape. CodeQL is a different kind of scanner — it reads the actual logic of the code for patterns known to cause real vulnerabilities: SQL injection, cross-site scripting, path traversal, hardcoded cryptographic keys.
# .github/workflows/codeql.yml
name: codeql
on:
push: { branches: [main] }
pull_request: { branches: [main] }
schedule: [{ cron: "0 3 * * 1" }]
jobs:
analyze:
runs-on: ubuntu-latest
permissions:
security-events: write
strategy:
matrix: { language: [javascript] }
steps:
- uses: actions/checkout@v4
- uses: github/codeql-action/init@v3
with: { languages: ${{ matrix.language }} }
- uses: github/codeql-action/analyze@v3
Findings land in the Security tab under Code scanning. A high-severity finding can be wired into branch protection (Chapter 15) as a required status check, the same way lint already is.
Everything else already sitting quietly underneath the whole story
None of the earlier chapters called this out directly, but every one of them was resting on choices made at the org level, back before the story even started:
- 2FA required, org-wide. Verified beforehand that nobody would be locked out — the club checked the member list for anyone without it before flipping the requirement on, exactly the kind of care Chapter 6's "check before you act" habit describes.
- Base org permission set to
none. Every access grant in this entire story — Anvesha's write access, Ishaan's lack of it — came from an explicit team grant, never from simply being an org member. - Members can't create, delete, or transfer repos. The four repos in this story exist exactly because someone deliberately decided to create them, not by accident.
- OAuth app and fine-grained PAT policies set to require Owner approval. Otherwise, a member authorizing some random third-party tool could hand it read access to everything they can see — including, potentially, the private
recordsrepo.
Reporting a vulnerability, if you ever find one
A SECURITY.md at the repo root tells anyone who spots a real vulnerability exactly where to report it responsibly, rather than posting it publicly as an issue where it could be exploited before it's fixed. For anything sensitive enough to need a private conversation first, GitHub's own private security advisories feature gives a maintainer and a reporter a confidential space to work out a fix before any public disclosure.
.env from the commit and re-pushing now sufficient?.git/ object database, on a machine that could be lost, backed up somewhere, or synced to a cloud drive. Treating "it got blocked" as equivalent to "it's safe" is exactly the gap that turns a near-miss into a real incident later.
Every mechanism in this story — commits, branches, PRs, Actions, security scanning — has been in service of one club: Accelerate specifically. Time to step back from the day-to-day drama and see the whole shape of how it actually fits together, end to end.