The secret scanning workflow has five tiers, and the tiers are the difference between a tool and a practice, because the tool is the same at every tier, and the practice is what wraps the tool, and the practice is the part that survives the person who set it up. Tier one is the ad hoc scan, the one you run when something feels off, and it is the tier everyone starts at, and the tier that catches the leak after it is already a problem. Each tier below adds one layer of automation, and each layer moves the detection earlier, and earlier is the entire value, because the cost of a leak is a function of the time between the commit and the detection.
Tier two is the pre-push hook, which moves the detection to the moment of the push, and the tier is the one that turns the scan from a project into a reflex. Tier three is the CI gate, which moves the detection to the server, and the tier is the one that cannot be bypassed, because the gate is in the repository and the repository is the policy. Tier four is the team distribution, which moves the practice from the machine to the clone, and the tier is the one that survives turnover. Tier five is the quarterly recursive sweep, which moves the detection from the present to the past, and the tier is the one that catches the leak that is already old. The section below is the five tiers in detail, each with the setup, the cost, and the failure mode it closes, because the tier you skip is the leak you keep.
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.
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 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.
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.
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.
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.
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.
Secrets Hide in Unlikely Places
The environment file is where people look, and it is where secrets are least well hidden. The real distribution is wider: a database URL inside a container compose file, an API key in a YAML config under a deploy directory, a webhook token in a JSON file checked in for one test, a token pasted into a README as a working example. The scanner covers source code, not just dotfiles, precisely because the pattern of where secrets actually live is messier than the pattern of where people think they live. If your security review only opens environment files, you are reviewing one out of five places. The scanner's job is to make the review exhaustive by making it automatic, and the exhaustive part is the whole value. The surprising findings — the ones in files nobody would call secret files — are the ones that justify the tool, because they are the ones no checklist would ever reach. A checklist asks about secrets; the scanner asks about every file, which is a different and better question.
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.
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 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 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)