According to GitHub’s secret scanning alerts, thousands of secrets are leaked every month. The scary part? Many of these leaks go unnoticed for days or even weeks.
It happens more often than we’d like to admit. I’ve seen AWS keys accidentally pushed to GitHub, API tokens sitting in plain text in config files, and private keys casually hanging out in documentation.
The cost of a leaked credential can be enormous - both in terms of security risks and engineering time spent rotating keys. Taking the time to set up proper safeguards now can save countless hours of incident response later.
That’s where Secretlint comes in. It’s an open-source tool I’ve been using extensively to catch these issues before they become problems.
Getting Started with Secretlint
You have two main options for installing Secretlint:
-
Using Docker (Recommended for CI/CD):
docker run -v `pwd`:`pwd` -w `pwd` --rm -it secretlint/secretlint secretlint "**/*"
-
Using NPM (Better for local development):
I prefer using NPM for local development as it integrates better with other development tools. Let’s walk through that setup.
npm install secretlint @secretlint/secretlint-rule-preset-recommend --save-dev
Next, we’ll initialize Secretlint in our project.
➜ trevorlasn.com git:(master) ✗ npx secretlint --init
This creates a .secretlintrc.json
file in the root of our project.
{
"rules": [
{
"id": "@secretlint/secretlint-rule-preset-recommend"
}
]
}
Running Secretlint for trevorlasn.com to see if it catches any secrets.
Create /Users/trevorindreklasn/Projects/trevorlasn.com/.secretlintrc.json
➜ trevorlasn.com git:(master) ✗ npx secretlint "**/*"
➜ trevorlasn.com git:(master)
Looks good, I don’t have any secrets in my codebase. To demonstrate how Secretlint works, I’ve created a controlled example below. This shows the type of sensitive information Secretlint is designed to catch:
Running Secretlint on this example triggers our security checks:
➜ trevorlasn.com git:(master) ✗ npx secretlint 'src'
/Users/trevorindreklasn/Projects/trevorlasn.com/src/content/blog/secret-lint/InsecureKeyDisplay.tsx
12:25 error [PrivateKey] found private key: -----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQCYdGaf5uYMsilGHfnx/zxXtihdGFr3hCWwebHGhgEAVn0xlsTd
1QwoKi+rpI1O6hzyVOuoQtboODsONGRlHbNl6yJ936Yhmr8PiNwpA5qIxZAdmFv2
tqEllWr0dGPPm3B/2NbjuMpSiJNAcBQa46X++doG5yNMY8NCgTsjBZIBKwIDAQAB
AoGAN+Pkg5aIm/rsurHeoeMqYhV7srVtE/S0RIA4tkkGMPOELhvRzGmAbXEZzNkk
nNujBQww4JywYK3MqKZ4b8F1tMG3infs1w8V7INAYY/c8HzfrT3f+MVxijoKV2Fl
JlUXCclztoZhxAxhCR+WC1Upe1wIrWNwad+JA0Vws/mwrEECQQDxiT/Q0lK+gYaa
+riFeZmOaqwhlFlYNSK2hCnLz0vbnvnZE5ITQoV+yiy2+BhpMktNFsYNCfb0pdKN
D87x+jr7AkEAoZWITvqErh1RbMCXd26QXZEfZyrvVZMpYf8BmWFaBXIbrVGme0/Q
d7amI6B8Vrowyt+qgcUk7rYYaA39jYB7kQJAdaX2sY5gw25v1Dlfe5Q5WYdYBJsv
0alAGUrS2PVF69nJtRS1SDBUuedcVFsP+N2IlCoNmfhKk+vZXOBgWrkZ1QJAGJlE
FAntUvhhofW72VG6ppPmPPV7VALARQvmOWxpoPSbJAqPFqyy5tamejv/UdCshuX/
9huGINUV6BlhJT6PEQJAF/aqQTwZqJdwwJqYEQArSmyOW7UDAlQMmKMofjBbeBvd
H4PSJT5bvaEhxRj7QCwonoX4ZpV0beTnzloS55Z65g==
-----END RSA PRIVATE KEY----- @secretlint/secretlint-rule-preset-recommend > @secretlint/secretlint-rule-privatekey
✖ 1 problem (1 errors, 0 warnings)
➜ trevorlasn.com git:(master) ✗
The error above shows how Secretlint identifies private keys in your codebase. In a real project, this would prevent the accidental commit of sensitive credentials. This example uses a non-functional key for demonstration purposes only.
What Secretlint Can Detect
Beyond private keys, Secretlint can identify various types of sensitive information:
- AWS access keys and secret keys
- Google Cloud credentials
- API tokens and keys
- Database connection strings
- Basic authentication credentials
- And more through custom rules
Setting Up Pre-commit Hooks
Pre-commit hooks are like security guards for your git repository. They run checks on your code before each commit, making sure no sensitive data slips through. Here’s how to set them up using Husky and lint-staged:
# Install dependencies
npm install --save-dev husky lint-staged
# Initialize Husky
npx husky-init
This creates a .husky
directory in your project with the basic hook setup. Next, we’ll configure it to run Secretlint.
Update your package.json
with these settings:
{
"scripts": {
"prepare": "husky install"
},
"lint-staged": {
"*": "secretlint"
}
}
- The
prepare
script runs automatically afternpm install
, setting up Husky for anyone who clones your repository lint-staged
configuration tells Husky to run Secretlint on all files that are staged for commit
Finally, add the pre-commit hook:
npx husky add .husky/pre-commit "npx lint-staged"
Now, when you try to commit a file containing secrets, here’s what happens:
➜ git commit -m "Update config"
🔍 Running Secretlint...
✖ secretlint found 1 problem(s)
./config.js
1:10 error Found AWS Access Key: AKIA...
commit aborted
Want to temporarily skip these checks? (Use with caution!)
git commit -m "message" --no-verify
But seriously, if you find yourself wanting to skip these checks, it’s usually a sign that something needs fixing in your code, not in your commit process.
Remember to add these files to your .gitignore
node_modules
.husky/_
Advanced Configuration
Secretlint’s real power comes from its customization options. Let’s dive into some advanced configurations I use in different scenarios:
Customizing Rules
The basic configuration is just the start. Here’s how I structure more sophisticated rule sets:
{
"rules": [
{
"id": "@secretlint/secretlint-rule-preset-recommend",
"rules": [
{
"id": "@secretlint/secretlint-rule-aws",
"options": {
"allows": [
"/^AKIA_EXAMPLE_/",
"/^DUMMY_AWS_/"
]
}
},
{
"id": "@secretlint/secretlint-rule-github",
"options": {
"allows": [
"/^ghp_EXAMPLE/"
]
}
}
]
},
{
"id": "@secretlint/secretlint-rule-pattern",
"options": {
"patterns": [
{
"name": "custom-api-key",
"pattern": "/MY_API_KEY_[A-Z0-9]{32}/",
"message": "Found custom API key"
}
]
}
}
]
}
Handling False Positives
False positives can be frustrating. Here are three ways I handle them:
- Using allowMessageIds
{
"rules": [
{
"id": "@secretlint/secretlint-rule-aws",
"allowMessageIds": [
"AWSAccountID" // Ignore AWS account ID detections
]
}
]
}
- Using Rule-Specific Allows
{
"rules": [
{
"id": "@secretlint/secretlint-rule-privatekey",
"options": {
"allows": [
"/BEGIN PUBLIC KEY/", // Allow public keys
"/EXAMPLE_PRIVATE_KEY/" // Allow example keys in docs
]
}
}
]
}
- Using Inline Comments
// secretlint-disable-next-line
const exampleKey = "AKIA_EXAMPLE_KEY";
/* secretlint-disable */
const testData = {
key: "test_key",
secret: "test_secret"
};
/* secretlint-enable */
While Secretlint is great at catching credentials before they make it into your codebase, it’s just one piece of the security puzzle. I’ve found it works best when combined with proper secret management systems, environment variables for configuration, regular security audits, and ongoing team education about security practices.