DEV Community

ke jia
ke jia

Posted on

The Onboarding Day a New Dev Found Our Oldest Secret

The onboarding day was the day the new developer found the secret. The team did not know about it, because the secret had been in the repository since the first year, and the first year was before the scanner was in the repository. The scanner in the repository was what the onboarding included, because the onboarding ran the first push, the first push ran the CI, and the CI ran the scan.
The irony is the piece the story keeps. The scanner found the oldest secret on the first day of the newest developer. The onboarding doc did the introducing; the doc says run the first push and watch the pipeline, and the pipeline said the scan found a finding, and the finding was in a file the first year committed. The section below is the day in detail: the onboarding, the first push, the finding, the verification, the rotation, and the two lessons. The first is that the scanner in the repository does not depend on the person, and the person is what turns over. The second is that the oldest secret waits for whoever runs the first push, and the first push is what every onboarding does.

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 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.

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.

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.

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.

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 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.

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.

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 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.

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.

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 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)