Secrets that stay secret
The dangerous part is not storage. It's that a secret in git history is still there after you delete the file — and .gitignore won't save you.
Most guidance on secrets is about where to put them. The failures I have actually seen are about where they have already been.
Git remembers
Deleting a file removes it from the working tree, not from history. Every previous commit still contains it, and anyone with the repository can retrieve it with one command.
Adding the path to .gitignore does nothing for a file that is already tracked —
.gitignore only prevents *untracked* files being added. This is the single most common
misunderstanding in this area, and it produces a specific false sense of security: the
file disappears from git status, so it feels handled.
git rm --cached untracks it going forward. Removing it from history needs a rewrite,
and if the repository has been pushed, a force-push and a conversation with anyone who
has cloned it.
Assume anything committed is compromised
If a credential reached a remote — especially a public one — treat it as disclosed and rotate it. Automated scanners crawl public repositories continuously for exactly this, and the window between push and exploitation is measured in minutes for high-value keys.
Rewriting history is worth doing to stop further exposure. It is not a substitute for rotation, because you cannot know who already has a copy.
Rotate first, rewrite second. History rewriting limits future exposure; it does nothing about the past.
What actually belongs in the repository
Encrypted credentials are fine to commit — that is the point of them. Rails' encrypted credentials file is safe in version control precisely because the key is not.
The key is not. It goes in the environment, or a secrets manager, and it is the one file
your .gitignore genuinely must cover.
Environment variables are the workable default for most deployments: outside the repository, settable per environment, supported by every platform. They are not perfect — they appear in process listings and crash dumps — but the threat model for a typical application makes them a reasonable choice.
The quieter leaks
Secrets escape through channels people do not think of as storage.
Logs, when a parameter filter does not cover a new field name. Rails filters common names by default; a field called something unusual will be logged in full.
Error reporting, which by design captures request context. Check that its scrubbing configuration matches your parameter filters, because they are configured separately and drift apart.
Client-side bundles, where a key intended for a server ends up shipped to a browser because it was needed "just for this one call".
And build artefacts — container images carry every layer. A secret copied in and deleted in a later layer is still present in the image, retrievable by anyone who can pull it.
Practical baseline
Scan the repository, including history, before making it public. Use a build-time
secrets mechanism rather than copying files into an image. Keep the ignore file honest,
and verify it actually covers what you think — git check-ignore -v answers in one
command, and it answers "no" more often than people expect.