โโโ โโโโโโโโโโโโโโ โโโ โโโโโโโ โโโ โโโ โโโโโโ โโโโโโโ โโโโโโโ โโโ โโโโโโโโโโโโโโโโ โโโโ โโโโโโโโ โโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโ โโโโโโ โโโโโโโ โโโโโโโโโ โโโโโโโ โโโโโโโโโโโโโโโโโโโโโโ โโโ โโโโโโโ โโโโโโ โโโโโ โโโโโโโโโ โโโโโโ โโโโโโโโโโโโโโโโโโโโโโ โโโ โโโ โโโโโโโโโโโ โโโ โโโโโโโโโโโโโโโโโโโโโ โโโโโโ โโโโโโโโโโโ โโโ โโโโโโโโโโโ โโโ โโโโโโโ โโโโโโโ โโโ โโโโโโ โโโโโโโโโโ
Blocks credentials before they reach git, finds the ones you already leaked, and tells you exactly how to rotate them.
$ npm install -g keyguard-cli && keyguard install
GitHubยท How it worksยท Why it's built this wayยท Commandsยท Editorsยท CI
Protection lives in git hooks, not an editor plugin. So it works the same in Cursor, VS Code, PhpStorm, Vim, or a plain terminal, and your colleague doesn't have to install your editor to be protected.
$ git commit -m "wip" โ BLOCKED: secrets detected .env โ 1:14 GitLab Token glpaโขโขโขโขโขโข[26 chars] 1 blocking finding Rotate these credentials GitLab โ https://gitlab.com/-/user_settings/personal_access_tokens โข Revoke the token in User settings > Access tokens (self-hosted: your own GitLab URL) โข Create a replacement with a short expiry โข Check the audit events for use of the token To resolve: โข Move the value into an environment variable and reference it โข If it is a false positive, add // keyguard:allow on that line โข If it was ever pushed, assume it is public and rotate it
Most scanners check the code in front of you.
keyguard doctor audits the machine: plaintext tokens in your shell config,
.env files one git add -A away from being committed, and every
repository where git is quietly ignoring your hooks.
Key-Guard: prevent ยท detect ยท rotate Machine audit โโโโโโโโโโโโโ โ Global git hooks are installed hooksPath = ~/.keyguard/hooks โ 5 repositories bypass the global hook These set their own core.hooksPath, so git ignores the global hook: ~/work/checkout-service (.husky/_, Husky) ~/work/design-system (.husky/_, Husky) Fix with: cd <repo> && keyguard install --repo โ Plaintext credentials in ~/.zshrc line 155: OpenAI API Key sk-jRโขโขโขโขโขโข[25 chars] Readable by any process running as you, including any AI tool that reads your config files. โ 1 env file is not gitignored .env Not committed yet, but `git add -A` would commit it. 1 critical issue ยท 2 warnings
Findings carry a redacted preview and a hash fingerprint, never the raw value. Printing it in full would re-leak it into your shell history and CI logs.
If a scan crashes, the commit is blocked rather than waved through. A security tool that silently stops working is worse than none.
Husky repos override core.hooksPath and bypass global hooks. Key-Guard
finds them, fixes them, and verifies git will really run the hook.
A committed credential is public. You get a per-service revoke checklist instead of a
filter-repo command that breaks every clone.
Entropy findings never block. False-positive rate on real repositories is a release gate, not a hope.
Nothing in node_modules at runtime. A tool that reads your secrets
shouldn't pull in a supply chain to do it.
Five commands, each answering one question.
| Command | Answers |
|---|---|
keyguard doctor |
What is exposed on this machine right now? |
keyguard scan |
Does this project contain secrets? |
keyguard history |
Did I already commit something, and how do I rotate it? |
keyguard install |
Protect every repo on this machine |
keyguard uninstall |
Remove the hooks, restore previous git config |
The same engine, so CI and your machine never disagree about what counts as a secret.
- uses: L-ubu/Key-Guard@v0.1.1
Fails the job and writes a redacted findings table to the job summary. Emits SARIF for code scanning.
include: - remote: 'โฆ/keyguard.gitlab-ci.yml'
Three jobs: working-tree scan, merge-request range scan, and a manual full-history audit.
- repo: L-ubu/Key-Guard hooks: [{ id: keyguard }]
For teams already standardised on pre-commit.
SARIF upload to GitHub code scanning is free on public repositories but needs GitHub Advanced Security on private ones. Without it the job still fails and still prints the redacted table, and the docs say so rather than letting you discover it in a pipeline.
keyguard lsp is a language server, so any editor that speaks LSP underlines
the secret as you type it. Same engine as the hooks, so the editor and the commit block
never disagree with each other.
One quick fix replaces the literal with an environment lookup in the right syntax for
the language, then appends the value to the nearest .env, creating it if
it does not exist. The variable name is read from the assignment, so
stripeKey becomes STRIPE_KEY.
The other quick fix inserts keyguard:allow using a comment syntax the
file actually accepts, so a Python file gets # and not //.
The JSON-RPC framing is written by hand. A process that reads every keystroke of your source is the last place to want an unaudited dependency tree.
vim.lsp.config.keyguard = { cmd = { 'keyguard', 'lsp' } } vim.lsp.enable('keyguard')
Neovim, Helix and Sublime work through the server directly. VS Code and Cursor have a
dedicated extension that bundles it, currently installed from a locally built
.vsix rather than a marketplace. JetBrains is tracked for v1.0. Whatever your
editor, the git hooks are the part that actually blocks a commit.
The decisions are the interesting part, so they're written down rather than buried in the code.
--no-verify
Use KEYGUARD_DISABLE=1 git commit. This exists so people reach for a
documented, greppable switch instead of learning --no-verify, which skips
every hook you have and becomes a habit.
If the scan errors, you're blocked. But if the
keyguard binary is absent, hooks exit 0 with a warning, because
otherwise uninstalling the package would brick every commit on the machine.
The pre-commit hook reads from the git index. Stage a secret, then edit the file, and it's the staged version that gets committed, so that's the version that gets scanned.
git rev-list --objects yields each blob once regardless of how many commits
contain it. Walking git log -p instead re-reads the same content hundreds
of times.
The header is base64-decoded and parsed as JSON. Plenty of strings start with
eyJ; far fewer decode to an object with an alg field.
ftp://anonymous:anonymous@host is documentation. Treating
admin:admin as a breach turns a secret scanner into a weak-password linter,
a different job and a much noisier one.
.env"
.env), MCP
credential handling