The .env File Is the Wrong Place to Think About Secrets
The .env file is what the security talk is about, and the about is the talk's map. The map centers on the .env, and the real distribution does not match the centering, because the .env is one of five places, and the five
The .env file is what the security talk is about, and the about is the talk's map. The map centers on the .env, and the real distribution does not match the centering, because the .env is one of five places, and the five places are what the scan covers.
The covering is what the .env-only review does not. The compose file is the first. The compose is where the database URL lives, because the compose is the service definition and the definition is where the connection string is. The YAML under the deploy directory is the second. The YAML is what the CI reads, and the reading is what the credential is for. The JSON config is the third. The JSON is what the test reads, and the test is where the webhook token is. The README is the fourth. The README is the example, and the example is where the working token is, because the example needs the working. The source code is the fifth. The source is the debug paste, and the paste is where the console token is. The section below is the five in detail, each with the file, the credential shape, and the scan rule that finds it, because the finding is what the .env-only review misses, and the missing is the leak.
The security section reads like an audit, because it is one: the asset, the threat, the control, and the residual risk, in that order, and the order is the part the fear-based security writing skips, because the skip is what makes the fear do the arguing. The asset here is the data the workflow touches, and the touches is the part that dotguard changes, because the changes is Scan .env files, config files, and source code for exposed secrets. Zero dependencies, JSON reports, 1000+ files in seconds.. The threat is the specific one for this workflow, and the specific is the part the section names before it names the control, because the named threat is what the control answers. The residual risk is stated at the end, because the stated residual is the part that makes the rest of the section the honest one.
The Exit Code Is the Integration
The scanner's interface to the machine is the exit code, and the exit code is the part that makes it CI-ready without any glue. A clean scan exits zero, a scan with findings exits nonzero, and the nonzero is what the pipeline turns into a failure, and the failure is what blocks the push. The pre-push hook uses the same code: the hook runs the scan, and the push goes through only if the exit code is zero. The JSON report is the second interface, for the things that consume data instead of pass/fail: the dashboard that charts findings over time, the chat alert that posts the file and the line, the compliance export that needs the record. The two interfaces cover the two consumers, the pipeline and the person, and the coverage is the design. A tool that needs a wrapper script to be useful in CI is a tool that will not be in CI, because the wrapper is the step that does not get written, and the not-written is the check that does not run.
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.
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.
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.
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.
Two Rhythms: The Daily Path and the Quarterly Tree
The scanner runs on two cadences, and the two cadences are the practice. The daily one is the targeted scan: the path pointed at the directory that changed, the service that touched the config, the deploy folder that holds the real credentials. The targeted scan is fast enough to run before the push, and the fast is what makes it the reflex. The quarterly one is the recursive sweep: the flag pointed at the whole projects directory, the tree that includes the forgotten experiment and the demo repository and the fork that nobody maintains. The sweep is the deep clean, and the deep clean is what catches the leak that is already old, because the old leak is the one the targeted scan never points at, and the never-pointed-at is where the leak waits. The same command, the same rules, two scopes, two rhythms. The monorepo does not need a different tool for the two cadences, it needs the same tool aimed at the two scopes, and the aiming is the practice, and the practice is what the scanner's flags exist to make cheap.
False Positives Are the Real Cost of a Scanner
Every secret scanner has to make a tradeoff: miss a real key, or flag a false one. The scanner leans toward flagging, and the price is occasional false positives β an encoded blob that looks like a key, a test fixture with a fake credential, a documentation example that uses a real-looking prefix. That is why the output includes the file, the line, and the matched rule: the cost of verifying a finding should be five seconds, not a forensic exercise. A scanner with zero false positives that also misses real keys is a liability, not a tool. Budget a minute per finding, verify, rotate if it is real, and note the known test values so the team stops re-checking them. The loop is the product. Over time the false-positive list becomes its own artifact β a record of the places in the codebase that look like secrets and are not, which is useful information in its own right, because it maps the codebase's sensitive-looking surfaces.
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 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.
The takeaway
The security section ends where the architecture ends: at the boundary the tool does not cross. dotguard is Scan .env files, config files, and source code for exposed secrets. Zero dependencies, JSON reports, 1000+ files in seconds., installed with npx @wuchunjie/dotguard, source at https://github.com/wuchunjie00/dotguard. The boundary is the part the threat model respects, because the respects is the control, and the control is the part the residual risk is measured against. The reader who runs the tool runs the boundary, and the runs is the part the claim becomes the behavior, and the behavior is what the security section is for, because the for is the practice, and the practice is the habit with the check in it.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.