About Professional
How I Build How I Build Meet the Team
Technology Homelab App Showcase Case Studies
Maverick & Luke Say Hello

Case study

Auditing a Self-Hosted Fleet: Closing the App-Auth Gap a Strong Perimeter Was Hiding

A parallelized, evidence-driven security audit that found a strong network perimeter had quietly become the only thing authenticating several backends, then hardened the real gaps, made deliberate accept-vs-fix calls on the rest, and turned every finding into an enforced promotion gate. A follow-up audit five weeks later tested whether that codification held: the gate class held, but a single finding deferred without a tracked row stayed open on production for 22 days.


The challenge

The environment is a self-hosted fleet spread across a development server, a private production node, and a public-facing server. Its security model is intentionally perimeter-heavy: a private mesh VPN for all machine-to-machine traffic, a default-deny host firewall on every box, and gitignored secrets. Almost everything is VPN-only.

That strong perimeter had become a trap. Because "you're on the VPN" was effectively true for every internal caller, several app backends had shipped without real application-level auth, a posture that stayed invisible while those apps were VPN-only and would have turned into genuine exposure the moment any of them went public. The audit found several backends in that state, along with a handful of hardening gaps in the shared secrets and credential store, and closed all of it before anything was promoted.

The job: find out whether the boundary actually held, and close the app-layer gaps before the first public promotion made them real.

Approach

Four choices shaped the work: parallel fan-out, evidence over intuition, proportionate fixes, and blast-radius-gated trust:

  1. Read-only parallel fan-out. Four parallel explorer agents were scoped to distinct domains (secrets infrastructure, the web apps, deploy/config, and the source-of-truth docs), and every finding was cross-checked against the actual files, never taken on a single agent's word.
  2. Prove it with evidence, not intuition. Design decisions were backed by live data: request-log analysis before flipping a network bind, a live anon-role probe instead of a static RLS read.
  3. Proportionate hardening. The audit framed the app findings as defense-in-depth and promotion-readiness, not emergencies: nothing found was remotely exploitable today. That drove a deliberate accept-vs-fix triage rather than reflexively closing every ticket.
  4. Blast radius gated by machine trust. The dev server was fully autonomous; production gated behind per-step operator approval; the public server got read-only review only.

Execution highlights:

Results


The sequel: a full-fleet audit five weeks later

The follow-up covered the full fleet plus the NAS, a wider scope than the first pass, read-only throughout, with every change applied afterward under individual approval. Its first finding came before any scanning: the maintenance tracker claimed the audit was six weeks overdue, and it wasn't: an intervening full-system audit had done that work and nobody updated the row. What genuinely had never been audited were the two newest machines.

Three HIGH findings, all closed.

1. Production had quietly published several internal services beyond their intended boundary, and it was a known finding. The exposure was proven at the application layer, not just as open ports.

The part that matters: this was a finding from the previous audit. It had been fixed on the dev-tier machines then; the production leg was deferred as "needs a gated session" and never executed. The only record of the deferral was prose inside a closed report, so nothing resurfaced it, and it stayed open for weeks. Fixed one service at a time, along with a related startup-ordering gap found in the same pass.

2. The drift monitor had been dead for 21 days while alerting daily. The nightly configuration check reported zero passes and "fetch failed" on every check for three weeks (the day before, it had passed 107 of 107). The checks were fine; re-run by hand they passed. Two things made it worse than a simple outage: because every check failed, the alert fired every morning with ~194 entries, and three weeks of identical daily alarms is indistinguishable from noise: that noise is precisely why the third finding sat unseen. Underneath was a design flaw: the checker reported a network error as a security failure, conflating "I couldn't reach this" with "this is insecure." Fixed with a distinct unreachable state that cannot be suppressed by a baseline.

3. Several production apps never received a hardening fix that had already been written. The fix existed, but it was not the thing actually running, and the release branch lagged the working branch. Two independent gaps, both invisible to a check that only asks whether the fix is present. Blast radius was checked rather than assumed and proved limited: every token found was either a public-by-design anon key or a placeholder string. No live credential was exposed.

Also closed: directory listing on a couple of apps (fixed as a class, not per-app); two stale suppressions marked as accepted failures that had actually been passing for a week: a baselined failure that starts passing has zero coverage; and a VNC-exposure hardening pass completed fleet-wide after some machines were found unfiltered that appeared nowhere in the tracking for it.

"Active" is not "working." Restarting one machine's remote-desktop service to apply a filter revealed it had been reporting healthy for weeks while serving nothing: it had attached to its display long ago, and the check that would have caught the problem only runs at startup. It could never have survived a restart. The fix is recorded with an explicit caveat: only one machine needed it, the reason was never established, and it was validated by testing rather than by understanding.

What the auditor got wrong, recorded because an audit that hides its misses is worth less: two confident causal explanations were later disproven by the session that actually fixed the problem; committed tokens were described as "dead credentials" when they had never been real credentials at all; and three separate shell-quoting bugs in the auditor's own tooling each produced a false "all clear": a port sweep that silently probed nothing, a filtered search that never ran, and a process check that twice named the wrong owner.

What this demonstrates

From the sequel: