Committed Secret Files

A missing .gitignore let a whole .env of live keys get committed to a public repo, so an attacker cloned it and reached staging. Untrack the files, rotate the keys, and add a .gitignore plus a pre-commit hook that blocks secret filenames.

What Is Committed Secret Files?

Committed secret files are a distinct Git failure from hardcoding a secret literal in source code. Instead of one value pasted into a code line, a whole file of secrets, a .env, a private key, or a service-account JSON, gets committed and tracked, usually because the repository has no .gitignore or an incomplete one. Once tracked, the file is part of the repository and its history, so every clone carries a copy and deleting the file later does not undo the exposure. This exercise puts you on both sides. As Bob, an attacker who clones public repositories and greps them for tracked secrets, you clone one of Tavonn's public service repos, list its tracked files, find a committed .env and a service-account key sitting next to the source because there is no .gitignore, open the .env to read the API keys and database URL, and use those keys to reach Tavonn's staging environment. As Alice, a backend developer at Tavonn, a payments-integrations startup, you receive a Security Operations alert that a .env with live keys is in the public repo. You confirm the repository has no .gitignore, so the file was tracked from the first commit, remove the secret files from tracking with git rm --cached, rotate the exposed keys because they must be treated as compromised, add a .gitignore covering .env, keys, and credential files, and install a pre-commit hook that blocks known secret filenames before they can be committed again. The exercise closes with quiz questions on why adding a .gitignore after a commit does not remove the file, how a committed secret file differs from a hardcoded literal, what git rm --cached does, and what a pre-commit hook prevents.

What You'll Learn in Committed Secret Files

Committed Secret Files — Training Steps

  1. Size up the target

    Today Bob is targeting Tavonn, a payments-integrations startup that ships some of its code in the open. He starts on the public repository, sizing up what the company runs before he looks for a way in. A public repo is a gift to someone like Bob: the whole project, every file, downloadable in one command.

  2. Clone the repo

    Bob clones the repository to his own machine so he can read every file it tracks, offline and at his own pace. Whatever the repo tracks comes down with the clone. If a secret file was ever committed, it is in the copy he just pulled.

  3. List the tracked files

    Bob lists everything the repository tracks. He is not looking at what is on disk, he is asking git which files are actually under version control, because those are the ones every clone carries.

  4. Read the secret file

    Bob opens the committed environment file. This is the difference between this leak and a single hardcoded key: it is not one value pasted into a code line, it is an entire file of secrets under version control. Everything the service needs to run is here, in plain text.

  5. Reach the staging environment

    A leaked key is only a theory until it opens something. Bob already has every piece he needs from the clone: the committed .env gave him the API base URL in PUBLIC_API_URL and a live key, and the routes he cloned in src/routes/payments.js define the payment feed at /v1/transactions . He assembles the request and carries the key in an Authorization header the way a real client would. Endpoints were never the secret; they sit in the public source. The key was, and the committed file handed him one, so the server has no reason to reject the request.

  6. The payment data is open

    The server trusted the key and answered. Bob is reading Tavonn's staging payment records from outside the company, with no account of his own.

  7. Knowledge check

    You just watched a public repo hand an outsider the keys to a live environment. Lock in why.

  8. The alert lands

    You own Tavonn's payments-service repo. Overnight, the security team's secret scanner flagged the public repository and emailed you. The finding is blunt: a committed .env with live keys is sitting in the public repo, and there is no .gitignore keeping files like it out.

  9. Tracked, with no .gitignore

    Your first move is to see it for yourself. You list what git is tracking in the repo, and there it is: the .env and the service-account key are under version control, and no .gitignore is in the list. A file present on disk is one thing; a file git is tracking is committed, in the history, and in every clone.

  10. Untrack the secret files

    You stop git from tracking the secret files with git rm --cached , which removes them from the index while leaving them on disk so the service can still run locally. You commit and push that change, and the files disappear from the current repo. It feels like the fix. It is not, and the next step shows why.