Stop Committing Secrets to Your Git Repositories

It’s a good idea to commit regularly when developing software. I find that when I’m trying something new, I might end up making a mess of the code. Having a recent commit means that I have a safe place I can reset everything to if needed. It also means that I’m free to try different ways to achieve a goal without having to worry about remembering exactly what I’ve done if things go wrong.

That said, there are occasionally some changes that I’d like in a safe zone which either aren’t ready or shouldn’t be in a commit. One example would be renaming a few existing variables or methods in preparation for doing something bigger. The changes are trivial and feel too small for a meaningful commit. However, redoing the same changes on each new attempt at solving the same problem would become tedious.

We can squash work-in-progress commits into a single commit when the task is done, but there is another way. We can create a safe zone by staging file changes instead of committing. I’ve taken this approach a few times. While it works, it does have one downside: it’s far too easy to forget exactly what has been staged and end up committing changes that shouldn’t have been included. These might include user or service credentials, sensitive files, or build outputs; it’s reasonable that these might be needed for testing, but they don’t belong in source control.

Once committed, they become part of the repository’s history. A natural reaction might be to delete them and make another commit. However, this simply removes them from that point onwards. Sensitive data would still be there if someone knew where to look; unnecessary binaries would likewise add to the data size which becomes important when downloading the repository. To remove these unintended changes, we need to edit the commits that they were made in.

Amending the Most Recent Commit

The easiest commit to modify is the most recent one. There have been plenty of times where I’ve hit the Commit button only to realise that I’ve committed more than I intended to. Image 1 shows the UI for making commits in Git Extensions. I’ve highlighted a checkbox with an arrow: when this is checked, any staged changes will be melded into the previous commit. The process would look like this:

  • Delete anything that shouldn’t be in the most recent commit.

  • Stage the change (or changes).

  • Click the Amend Commit checkbox (highlighted in Image 1 with an arrow).

  • Click Commit.

Image 1: The UI for making commits in Git Extensions

After making the commit, these changes will update the (previously) most recent commit and we can go about our day pretending that the accident never happened.

But what if the commit that we need to change is further back?

Amending Historical Commits

It’s possible to amend a commit that is deeper in the repository’s history too. The process is similar, but a little more complex. While there are a few ways to do this, we’ll go over just one. Let’s imagine we have a repository that looks like Image 2.

Image 2: Initial repository state

We have a commit (402ebad) that has secret credentials included by accident. We can’t amend the commit as we did above because it isn’t the most recent one on the branch. Instead, let’s remove the credentials and commit that change. Our repository will look like Image 3.

Image 3: The repository includes a commit to remove the credentials

We can now do an interactive rebase on the last three commits using the following command:

git rebase -i HEAD~3

As described in the on-screen notes, we are free to re-order the commits; they will be performed in the order that they appear in the list. Let’s move the commit that removes the credentials (f7668d0) so that it gets applied immediately after where our passwords were added (402ebad). Then, let’s change the command for commit f7668d0 to s (or squash). The commit will be squashed into the previous one, effectively erasing the credentials. The command list will look like Image 4.

When reading the list, it’s important to remember that the oldest commit is shown at the top of the list; the order is reversed in Git Extensions (as shown in Image 3), where the newest commit is shown at the top.

Image 4: The commits have been reordered so f7668d0 will be squashed into 402ebad

As usual we have a chance to alter the commit message. I want to do so in this example (as shown in Image 5) but this generally won’t be the case in real-world scenarios.

Image 5: Editing the commit message for the new commit

Image 6 shows the results of our interactive rebase. The commit history looks like that of the original repository state. However, commits 402ebad and f7668d0 have been rewritten as a new commit 7c21822, which no longer includes the credentials.

Image 6: The secret credentials have been removed from the repository

It’s worth noting at this point that if this branch had been pushed to a remote while it contained secrets, it would be a good idea to change any related passwords or API keys.

Summary

Accidents happen and commits can sometimes include material that isn’t suitable for source control – this can include credentials, build output binaries, and unrelated notes.

If they are in the most recent commit, you can fix it by removing them and amending the last commit on the branch. If they’re further back in the repository’s history, you can still fix it by removing them and committing the change. You can then use an interactive rebase to apply that commit just after where they were introduced, squashing the two commits together.

By doing this, you can rewrite the commit so that it doesn’t include sensitive or unnecessary data. And the best part is, the only difficulty you’ll have is trying to not feel like a superhero with your ability to change history.