The recursive sweep of the projects directory found the demo repository from three years ago, and the demo repository had a credential in it, and the credential was not a demo credential, it was the live one, the one that had been rotated in the service but not in the demo, which is the gap that the rotation process does not close, because the rotation closes the service and the demo is not a service. The demo had been a working example for a talk, and the talk was long over, and the repository was public, and the credential in it had been public for three years, and the scan found it in the time it takes to read the finding, which is the part that makes the sweep worth the quarterly schedule.
The lesson is the gap between the rotation and the repository, and the gap is the specific failure that the scanner exists to close. The rotation made the credential dead in the system that issues it, which is the part everyone does. The rotation did not make the credential dead in the places that had copied it, which is the part nobody does, because the places are not in the system, the places are in the file systems, the demos, the forks, and the mirrors. The sweep is the check that the dead credential is actually dead everywhere, and the check is the same command pointed at the directory instead of the repository. The section below is the sweep in detail: the recursive scan, the findings, the verification that each one is dead at the source, and the cleanup that makes the next sweep quiet, because the quiet sweep is the goal, and the quiet sweep is the proof that the leak is closed.
The story is the one that happened, and the happened is the part the tutorial does not cover, because the tutorial is the smooth path and the story is the path with the specific date, the specific number, and the specific moment the decision was made. The tool in the story is dotguard, Scan .env files, config files, and source code for exposed secrets. Zero dependencies, JSON reports, 1000+ files in seconds., and the tool is the character that enters at the turning point, because the turning point is where the smooth path ended and the story began. The details below are the ones that were real, and the real is the part that makes the story the evidence, because the evidence is the part the reader checks, and the checks out is what the specific date is for.
The Cost of a Leaked Key, Quantified
The numbers on public cloud incidents are uncomfortable in a specific way: they are all larger than the annual budget of the team that leaked the key. Compromised cloud credentials have produced bills in the hundreds of thousands of dollars in a single weekend. Leaked repository tokens have been used to push malicious code into downstream packages that millions of installs then inherit. Leaked database passwords have emptied tables into the clear. The economics are one-directional: the cost of a scan is a line of workflow file and a few seconds of CPU; the cost of a miss is measured in six figures and a postmortem you will give in the morning with your team in the room. The asymmetry is the entire argument for running the scanner by default, on every push, in every repository, whether or not you think you have secrets in it. The repositories that are sure they have no secrets are the ones that have never checked, and the check is the only difference between those two statements.
What Zero Dependencies Buys You in a Security Tool
A security tool has a special trust problem: you are asking it to read your most sensitive files. The natural question is what the tool itself trusts. For this one, the answer is nothing. No dependencies, no network calls, no telemetry, no update daemon. It reads files, matches patterns, and writes a report. That matters in the places where security tooling gets blocked: restricted CI runners, air-gapped builds, compliance environments that require an audit of every third-party package in the pipeline. A single-file scanner with zero dependencies is auditable in an afternoon by a security reviewer who would never approve a forty-package tree. In security, small is not a feature. Small is the product. The auditability is the trust model: you can read the whole thing, you can verify what it matches, and you can be confident that the thing reading your secrets is not also phoning home with them. That confidence is not a nice property of the design; it is the design.
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.
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.
Rotation Beats Detection, Every Time
There is a comfortable misconception that finding a secret is the win. It is not. The win is the key that no longer works. A detected secret that stays valid is a secret that is still leaking; the only difference is that now you know about it, which is a strange kind of liability. The workflow that matters is: scan, confirm, rotate, and then scan again to confirm the rotation happened and the old value is gone from the working tree or accepted as a known dead value. Rotation is boring, it touches other teams, and it is the step everyone skips. A detection tool that makes rotation feel like a natural next step — by giving you the exact file and line, so the fix is a two-minute job — is doing more than pattern matching. The finding is a ticket, and the ticket's definition of done is the rotated key, not the closed alert. Designing the tool around that definition of done is what separates a scanner from a security workflow.
What a Clean Scan Does for the Team
The clean scan is the output nobody celebrates, and the no-celebration is the point, because the clean scan is the state the team should be in by default. The value of the default-clean is the attention: the team's security attention goes to the findings, and the findings are the exceptions, and the exceptions are what get the rotate-today treatment instead of the quarter-review treatment. The clean scan also does the onboarding: the new developer who runs the scan and gets nothing learns the habit without the incident, and the habit learned without the incident is the habit that holds, because the first experience is the quiet one instead of the alarming one. The quiet is the design goal, and the quiet is what the pre-push hook and the CI gate buy, because the gate catches the leak before the team sees it, and the team that never sees the leak is the team that never has the incident meeting. The clean scan is the boring output, and the boring output is the expensive one to keep.
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.
Monorepos, Home Directories, and the Recursive Flag
The scanner assumes your secrets live in one project, and then breaks that assumption on purpose. The path flag lets you point it at a subdirectory — scan just the deploy configs, or just one service in the tree. The recursive flag lets you do the opposite: point it at your projects directory and audit everything you have ever written, including the old side projects you forgot about. That last use is the one that pays for itself. Leaks do not respect project boundaries; the forgotten demo repository from a few years ago is a live credential the moment someone finds it. A scanner that can sweep an entire directory tree in seconds turns auditing all your code from a month-long project into a coffee break. The monorepo use is the inverse: one command covers every service, so the scan cost does not grow with the organization. Either direction, the same principle: the scan should be cheap enough to run often, and running it often is what catches the leak while it is still cheap to fix.
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.
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 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 takeaway
The story is the one that happened, and the happened is the part the generalization does not cover. dotguard is the tool that was in the story: Scan .env files, config files, and source code for exposed secrets. Zero dependencies, JSON reports, 1000+ files in seconds. The install line, for the reader who is at the story's beginning instead of its end, is npx @wuchunjie/dotguard, and the repository is https://github.com/wuchunjie00/dotguard. The generalization the story supports is the one the reader can check against the specifics above, and the check is the part the anecdote does not offer, because the anecdote is the story without the date, and the date is what the story above has.
Top comments (0)