Pre-commit hooks allow you to run arbitrary code on your commit before it actually gets committed. 🤯
pre-commit
is perfect for blocking yourself from committing hardcoded secrets.
I think even the biggest proponents of checking code for hardcoded secrets will still, every now and then, accidentally commit a hardcoded secret and push it up to GitHub. For most of us, it’s really not a matter of if, but when.
Even if it’s a private repo — who is going to see that code? If it’s a public repo, then you’d better rotate that key ASAP before the horde of GitHub scraper bots find your key.
Let’s dive into pre-commit hooks. 🪝
Example
A developer wrote the following code to call a stock API and forgot to replace the hardcoded secret with an environment variable before committing and pushing up to GitHub.
import requests
token = "3X8J0d5J5bp3JKXFgroJ"
url = "https://examplestockapi.com/api/v1/test"
params = {"symbol": "AAPL", "token": token}
stock_info = requests.get(url, params=params).json()
...
Notice the three dots at the bottom of the code block. These are important for later.
As you can see from this PyCharm screenshot, the developer successfully committed the code, which we don’t want.
To demonstrate how pre-commit
hooks would block this, first, we have to install the package. I’m going to install it using brew install pre-commit
but there are several other ways.
Next, we have to create a YAML file that sits in the root directory of your repository. This file is called .pre-commit-config.yaml
.
In the .pre-commit-config.yaml
, we can add specific hooks from GitHub repositories or even write and store them locally.
In our specific YAML file, we are going to add ripsecrets
, which blocks you from committing secrets, as well as black
, which is a Python code formatter. I want to demonstrate both hooks, because pre-commit
has an every day usefulness that goes far beyond just a “blocker of committing secrets”.
Here is what our .pre-commit-config.yaml
should look like:
repos:
- repo: https://github.com/psf/black
rev: 22.10.0
hooks:
- id: black
- repo: https://github.com/sirwart/ripsecrets.git
rev: v0.1.5
hooks:
- id: ripsecrets
We can also tell pre-commit
to update the revisions for us by running pre-commit autoupdate
.
Once the pre-commit config is added to the repo, we just have to run pre-commit install
in the root directory of the repo.
Since we are adding pre-commit hooks to an already-established repo, we will run pre-commit run --all-files
to get the historical files up to the standard that our hooks will maintain going forward.
The Failed output is what we are expecting to see: black
thought my code was shitty because of the three dots, and ripsecrets
found our hardcoded secret to call the stock API and blocked it.
If I remove the three dots in my code and try to commit again, you can see that black
passes my code, but ripsecrets
still doesn’t like the secret being there.
Now, let me try re-writing my code to use an environment variable instead of the hardcoded secret:
import requests
import os
token = os.environ["examplestockapi_token"]
url = "https://examplestockapi.com/api/v1/test"
params = {"symbol": "AAPL", "token": token}
stock_info = requests.get(url, params=params).json()
Now, when I try to commit, everything passes!