The local-first security stack is six tools, and every one of them is free, and the point of the stack is that the security workflow runs on the machine instead of in a service, which means the workflow works in the restricted environment, the air-gapped build, and the compliance review, and the workflow does not depend on a vendor's uptime or a subscription's renewal date. The stack is not a product; it is a set of habits with the right tools attached, and the habits are the part that survives the tool choice, because the tools change and the habits compound.
The first is the scanner itself, the tool that finds the candidates, which is the detection layer. The second is the pre-push hook, which is the early warning layer, the check that runs before the secret reaches the remote. The third is the CI gate, the enforcement layer, the check that runs on every push from every machine. The fourth is the rotation checklist, which is the response layer, the steps that turn a finding into a dead key. The fifth is the history audit, the quarterly deep clean over the files that ever contained the pattern. The sixth is the report archive, which is the record layer, the JSON reports kept over time so the trend is visible. The section below is the six in detail, each with the setup, the cadence, and the failure mode it closes, because the stack is only as strong as the layer nobody sets up, and the layer nobody sets up is the archive, which is why it is on the list at all.
Lists are the format the reader can scan, and the scan is the feature, because the feature is the entry that the reader stops on, and the stops-on is the one that was the answer. The entries below are the ten that earned the slot, and the earned is the part that the filler does not have, because the filler is the entry that pads the count, and the pad is what the reader sees, and the sees is what the trust loses. The context is dotguard, Scan .env files, config files, and source code for exposed secrets. Zero dependencies, JSON reports, 1000+ files in seconds., and the context is the part that makes the entries the specific ones instead of the generic ones, because the generic entry is the one the reader has read before, and the read-before is the one that does not stop the scroll.
The Team-Wide Hook: Distributing the Habit
Personal hooks die with the person who set them up. The pattern that actually works is distribution: put the scan in a hook that lives in the repository itself, committed to version control, so every clone gets the behavior automatically. A new developer clones the repository, runs their first command, and the scanner is already there. Nobody has to be told, nobody has to remember, and the behavior cannot be forgotten because it is part of the repository. This is the difference between a tool and a practice: a tool is what one person runs, a practice is what the repository does to everyone. The scanner is small enough that the distributed version costs nothing to maintain — one file, one command, zero configuration — which is exactly the size at which a team habit becomes cheaper than the individual habit it replaces. The repository becomes the enforcer, and enforcement by repository is the only enforcement that survives turnover.
The Files It Reads, and the Ones It Skips
The scanner's file coverage is the difference between the scan and the audit, and the coverage list is worth knowing. It reads the environment files, the config files in the common formats, the compose files, the YAML under the deploy directories, the JSON configs, and the source code, because the source is where the debug paste lives. It skips the directories that are noise by definition: the dependency folders, the build output, the lockfiles, because the dependency folders are the supply chain and the supply chain is audited separately, and the build output is derived and the derived is not the source of truth. The skip list is the tuning that makes the scan fast on a monorepo, and the fast scan is what runs often enough to matter. The coverage list is in the documentation, and the documentation is the thing to read once, because the one read is what separates the person who trusts the scan from the person who wonders what it missed, and the wondering is what stops the trust.
Scanning the Worktree, and the History Behind It
The scanner covers what is on disk: the environment files, the config files, the source in your working tree. That catches the obvious case — the file you just created and are about to commit. For the subtler case, the secret that is already in history, you combine it with the version control system: find the files that ever contained the pattern, then scan them. A committed secret does not stop being a secret when it is deleted from the current branch; it lives in every clone, every fork, and every mirror. The honest workflow is two steps: scan the present automatically, and audit the past with the same rules applied to the files that history touched. Detection is a habit, and habits are easier to keep when the tooling is small enough to run by reflex. The present scan is the reflex; the past audit is the quarterly deep clean, and both use the same rules, which is what makes the pair coherent instead of two unrelated chores.
The 8-Month Leak That Started This
The scanner was built after an audit of my own public repositories found three leaked secrets. The worst was a production database password that had been committed, in plaintext, for eight months. Nothing dramatic happened — no breach, no incident report — which is exactly the problem. Most leaks are silent. They sit in a file that was committed in month one, get copied into forks, become the default credential in a demo, and nobody notices until the rotation happens by accident. A scanner is not a luxury for teams with a security budget. It is the same category of tool as a spellchecker: cheap, automatic, and the only thing standing between a careless commit and a very bad quarter. The eight-month leak is not a story about a mistake; it is a story about the absence of a check, and the check is the entire product.
Five Seconds to Verify a Finding
The verification is the step that turns the finding into a decision, and the five seconds is the design target, because the five seconds is what keeps the check happening. The report gives the file, the line, and the rule, and the verification is open the file, look at the line, and answer one question: is this value live, or is it a fixture? The live value is the one that the build reads, that the deploy uses, or that a request would present to a real service. The fixture is the one that the test asserts against, that the README shows as an example, that the mock returns. The answer changes the next step: live means rotate and re-scan, fixture means note it and move on. The five seconds works because the report is specific, and the specificity is the feature. A scanner that says suspicious content found is a scanner that takes an hour to triage, and the hour is what stops the habit. The five seconds is the habit.
One Line in GitHub Actions
The entire CI integration is one step: a run line that invokes the scanner via npx. No service container, no token to configure, no daemon. Every push gets a full scan, and the build fails if a secret is found, which means the secret never reaches the default branch. The beauty of the one-line integration is the maintenance cost: there is nothing to update, no version to pin, and no vendor to renew. When a security control costs one line of workflow file, the only question is why it is not already there. That is the bar every pre-merge security control should meet, and the scanner was designed to meet it on purpose. The one line is also the onboarding story: new contributors see the check in the workflow file, understand what it is doing, and never have to be told to run it. The pipeline is the policy, and the policy is one line long.
How the Scan Actually Works
The scanner looks for the patterns that real secrets actually take. Cloud provider keys start with a known prefix, Git tokens start with a known prefix, messaging platform tokens have a known shape, payment processor live keys have a known prefix, and web tokens start with a known base64 header. It also checks for high-entropy strings assigned to suspicious variable names — password, token, secret, key — because the values do not always follow the format, but variable names are a reliable signal. The scan covers environment files, config files, and source code, and it reports the file, the line, and the matched pattern so a human can verify in seconds rather than minutes. It is deliberately a detection tool, not a verdict tool: it finds candidates, a human confirms, and the key gets rotated either way. That division of labor is what keeps false positives from becoming noise fatigue.
The JSON Report That CI Can Actually Use
A security tool is only as good as its integration surface, and the scanner's is a JSON report. Run it with the report flag and you get a machine-readable list of every finding: file, line, rule, severity. Exit codes are meaningful, so a pipeline can fail the build on any finding without parsing colored terminal output. That design decision pays off in the boring way: the tool fits into CI or a local pre-push script with zero glue code. When the report is data instead of text, other tools can consume it — a dashboard, a chat alert, a compliance export. Security tooling that cannot be integrated is a demo, not infrastructure. The JSON report is the part of the design that turns a one-person tool into a team habit, and it costs nothing to use: the same command, one extra flag, and the output becomes something the rest of the system can act on.
The Rotation Checklist the Report Triggers
The finding without the rotation is a list, and the list is the part that ages badly. The rotation checklist is the four steps that turn the finding into the closed loop, and the steps are short enough to be a habit. Step one is the verify, the five-second check that the value is live and not a fixture. Step two is the rotate, the call to the provider that replaces the key and invalidates the old one. Step three is the update, the new value in the place the build reads, and the old value deleted from everywhere the working tree still holds it. Step four is the re-scan, the same command run again, and the re-scan is the proof, because the proof is the old value not in the report. The four steps take the length of a coffee, and the coffee is the part that makes the loop close on the day of the finding instead of the day of the reminder. The report is the trigger, and the trigger without the loop is the notification, and the notification is what gets ignored.
The False Positive Conversation
Every scanner has false positives, and the honest relationship with them is the part that keeps the tool in the workflow. The fixtures are the main source: the test that asserts against a fake key, the README example that shows a token shape, the mock that returns a secret-looking string. The false positive is not a bug in the scanner, it is the scanner doing its job on data that looks like the job, and the cost of the false positive is the five-second check, which is the cost the tool is designed around. The known-false-positive list is the management: the findings you have already classified stay classified, and the classification is what the report's rule field supports, because the rule tells you the shape that fired, and the shape is what the fixture is. The conversation to have with the team is not about removing the false positives, it is about budgeting for them: the five seconds each, and the list that grows, and the list that makes the next triage faster. The scanner that has no false positives is the scanner that misses the custom key, and the custom key is the one that matters.
Writing the Detection Rules Is a Window Into Developer Habits
The detection rule set is a fossil record of how developers actually handle secrets. The cloud provider rule catches the most leaks by volume, which says something about how much infrastructure runs on a single provider. The web-token rule catches a specific habit: developers pasting a full token into code to debug one endpoint, then forgetting it is there. The high-entropy rule exists because a surprising number of teams generate strong keys and store them with weak discipline — random value, obvious variable name, plaintext file. Building the rules taught me that secret leaks are not a knowledge problem; everyone knows the environment file should not be committed. They are a friction problem. The right thing is slow, the wrong thing is fast, and the scanner removes the friction from the right thing by making the check automatic. Every rule in the set is a documented instance of the friction, and the rule set is a map of where the friction lives in a codebase.
The takeaway
The ten are the ten, and the ten is the part the count promised. The entries are the working set, and the working-set is the part the reader keeps, because the keeps is the bookmark, and the bookmark is the part the list buys. dotguard is Scan .env files, config files, and source code for exposed secrets. Zero dependencies, JSON reports, 1000+ files in seconds., one command away with npx @wuchunjie/dotguard, and the one-command is the part that makes the ten entries the ten commands instead of the ten readings. The repository is https://github.com/wuchunjie00/dotguard, and the repository is the part the missing eleventh goes to, because the eleventh is the part the reader finds, and the finds is what the list that keeps working does.
Top comments (0)