Field Notes

I scanned my own SaaS for secret debt. Here's the three-line answer.

August 18, 2026

A real Unrotated assessment run on Ignaria — 289 raw findings, four real credentials in HEAD, and the triage that turns a wall of noise into a short list you can act on.

Honesty note up top. This is a real scan of a real codebase I own — Ignaria, a SaaS I built and have run for most of the last year. I ran my own assessment practice's pipeline on it. The numbers below are the actual scanner output, not a hypothetical. Secret values are redacted everywhere (you never publish live secrets, even your own). File paths and finding types are real, because they're mine to show. The four "rotate this week" credentials were live in HEAD at scan time; I rotated them before publishing this.

The setup

Ignaria is a monorepo. One repo, 46 feature branches, 6,890 commits of history, backend and frontend packages, a Phoenix app, a Postgres database, and a Meilisearch search index. Real infrastructure, not a demo.

I'm a security consultant who sells a focused thing: taking the noisy wall of findings a secret scanner produces and turning it into a short, human-judged list an engineering leader can actually act on. So I pointed my own tooling at my own code. This is what came back.

The wall

289 raw findings.

That's what the scanner handed me: 202 pattern matches plus 87 credential candidates, across the full git history of all 46 branches. If you've ever opened a secret-scanning dashboard — GitHub's, GitGuardian's, anyone's — this is the moment where it dies. 289 rows. You scroll, your eyes glaze, you close the tab. The secret debt stays.

This is the problem I'm in business to solve: the scan is raw material, the triage is the value. So let me show you the triage.

The three-line answer

After dedup, after liveness verification, after filtering placeholders and history, those 289 rows became four actions:

Rotate this week — four real credentials, currently in HEAD, pointing at real infrastructure:

  1. Meilisearch API keypackages/backend/index_meilisearch.py:27, hardcoded next to a URL to a public server. The same key shows up again in finding #3.
  2. Database passwordpackages/backend/index_meilisearch.py:22, hardcoded in a backend config dict.
  3. Postgres URL with credentials + Meilisearch keypackages/backend/reingest_reader.sh:6, a postgresql://user:[REDACTED]@[redacted-ip]:5432/... plus a --meilisearch-key, committed in a shell script against a real host.
  4. Phoenix SECRET_KEY_BASEdocker-compose.supabase.yml:130, a high-entropy framework secret (entropy 5.16) hardcoded in a compose file.

Rotate today — zero. Nothing verified live. I'll come back to that, because "zero verified live" is doing a lot of work in that sentence and it's where most scanners would mislead you.

Log and ignore — 90. Not actionable. I'll show you what's in there, because "ignore" is a judgment call, not a wave of the hand.

The part most scanners get quietly wrong

The four above are marked unverified, not "live." My verification engine made read-only identity calls against each credential's provider API, and all four came back false.

Here's the trap: a lot of tooling would report "0 live secrets found" and an engineering director would exhale and close the dashboard. That's the wrong read. "Unverified" means the verification engine couldn't confirm liveness — revoked, unreachable, or (in this case) an unsupported provider. Meilisearch and Phoenix SECRET_KEY_BASE aren't things you can ping with a standard identity call. So the engine honestly says "I don't know," and the right response is treat as live until a human confirms otherwise, not "ignore."

This is the gap I sell into. The scanner did its job. Nobody did the judgment. Those four credentials sat in HEAD, against real infrastructure, until a person (me, in this case — it's my repo) went and looked.

What got filtered, and why

90 findings dropped into "log and ignore." Breaking that down is the unsexy half of the triage — and it's the half that makes the short list trustworthy.

The breakdown:

Type Count Why filtered
JWT 28 Example/test tokens in documentation and .env.example.
Generic API key 19 Example values in docs/.env.*.example; 1 scanner false positive.
Postgres 19 Example/local-dev connection strings (changeme / service-name host).
curl auth header 17 Documented example headers in deployment notes.
OpenAI 6 Example OPENAI_API_KEY values in setup guides.

That's 90 rows an engineering director would have to open one by one to realize none of them matter. Nobody does. So either the real ones get missed inside the noise, or the team gives up and treats all 289 as "probably fine." Both outcomes are how secret debt ages.

What I did about it

It's my repo, so the "shared session" was me talking to myself: I rotated the Meilisearch key, the database password, the Postgres URL credentials, and the Phoenix SECRET_KEY_BASE; moved each into environment variables / a secret store; and scrubbed the four from git history. The point of this piece isn't the cleanup — it's that the cleanup took minutes once the three-line answer existed, and it had never existed before because nobody's job was to produce it.

What this is, and isn't

This isn't a demo on a fake repo. It's the real deliverable run on real code I own, with the real numbers — including the honest "zero verified live" that a less honest writeup would have buried, and the four unverified-but-real credentials that the "zero" would have let me ignore.

It's also not a sales pitch that your repos are full of secrets. Maybe they're clean. Two other repos I scanned with this same pipeline came back with zero real findings — that's a valid, realistic outcome. The value isn't in finding debt; it's in knowing, either way, with a list you can act on instead of a dashboard you avoid opening.

Sound familiar?

A scanner is probably already running at your org, producing a wall of findings nobody is triaging. I turn that wall into a short, human-judged list — rotate these today, ignore these forty — delivered as a prioritized report in a week. Read-only, no tooling to install, a scoped token you revoke anytime. If your secret hygiene is already clean, you'll know in five minutes and I'll leave you alone.

Book a 20-minute call

Nearly 30 years building and architecting software, now focused on one under-owned problem — credentials committed to version control that get found and never rotated.