A vukem app holds secrets that must never reach git: APP_KEY and APP_PREVIOUS_KEYS, API tokens, database URLs, Stripe, mail and OAuth keys. They belong in .env (ignored by git) or in your host's secret store. A secret pushed once stays in the history and in every clone, so the only fix is to rotate it.
Projects created with vukem new --standards or --ci scan for leaked secrets with Betterleaks, an open-source scanner by the Gitleaks authors. It is a separate binary: nothing is added to your go.mod.
You will learn:
- what the starter writes and when each scan runs;
- which secret formats the rules catch and how placeholders stay quiet;
- how to install Betterleaks and check it with
vukem doctor; - what to do when a scan finds something.
What the starter writes#
| File | Flag | What it does |
|---|---|---|
.betterleaks.toml | --standards or --ci | The Betterleaks default rules plus vukem's secret formats and a placeholder allowlist. Every scan below picks it up by name. |
.githooks/pre-commit | --standards | Scans the staged changes before each commit, then runs gofmt and go vet. |
package.json script secrets:scan | --standards | Scans the whole git history: npm run secrets:scan (or bun run, pnpm run). |
.github/workflows/ci.yml job secrets | --ci | Downloads Betterleaks by exact version and sha256 checksum and scans the whole history on every push and pull request. |
When Betterleaks is not installed, the pre-commit hook prints a message and lets the commit through. The CI job always runs the scan, so a leak never passes silently.
Rules#
.betterleaks.toml extends the default rule set with:
| Rule | Matches |
|---|---|
vukem-app-key | base64: followed by 32 base64-encoded bytes: APP_KEY and each APP_PREVIOUS_KEYS entry |
vukem-api-token | API tokens from auth.Tokens with the default vk_ prefix |
stripe-secret-key | Stripe secret and restricted keys: sk_test_, sk_live_, rk_test_, rk_live_ |
stripe-webhook-secret | Stripe webhook signing secrets: whsec_ |
The default rule stripe-access-token is turned off because stripe-secret-key replaces it, so a Stripe key is reported once.
The allowlist matches placeholder values, never file paths: a value containing _FAKE_, placeholder, changeme, example, eight or more x, or ending in ... is ignored. .env.example with empty values or placeholders such as sk_test_... or sk_test_ followed by a run of x passes. A real key pasted into .env.example still fails the scan.
Add your own formats as more [[rules]] entries. The Betterleaks README describes the format.
Install and check#
Download a release for your platform from the Betterleaks releases page and put the binary on your PATH. Use a stable release. The CI job pins v1.9.0.
vukem doctorbetterleaks: 1.9.0 (/usr/local/bin/betterleaks)vukem doctor reports whether Betterleaks is installed and its version. It always exits 0.
Scan by hand:
betterleaks git --pre-commit --staged --no-banner --redact # staged changes, what the hook runs
betterleaks git --no-banner --redact # the whole history, what CI runs--redact keeps the secret out of the output and CI logs.
Validation stays off#
Betterleaks can check a finding against the provider's API to see whether the key is live. That sends your secret to a third party, so the starter never enables it. To opt in, run betterleaks git --validation yourself, and read the Betterleaks docs on the --validation-* flags first.
When a scan finds a secret#
- Rotate the secret at its source. For
APP_KEY, runvukem key:rotate. For an API token, revoke it. For Stripe, roll the key in the dashboard. - Remove it from the code and load it from the environment.
- If the commit was already pushed, treat the secret as public even after you rewrite the history.
A false positive in a test fixture: change the value so it contains _FAKE_ or placeholder. Do not add the file path to the allowlist, or a real secret in that file would also pass.