Secured like it matters
A homelab is a great place to practise security badly, because nothing punishes you. So the rule here is that a control only counts if it fails closed, gets audited, and would survive someone actually trying. Below is what is really running, what it deliberately does not do, and the bug I found in my own control.
One authority for secrets, and nothing on disk
Every secret lives in one system. Nothing is written to disk and nothing is committed: pipelines fetch what they need at run time and hold it in memory, and workloads authenticate by role rather than by carrying a password around. Per-host keys are minted into the secrets manager by the pipeline that builds the host, and removed by the pipeline that destroys it.
Two independent scanners backstop the rule, one at commit time and one at write time, so a secret has to beat both to reach the repository. The commit-time one is the authority: it runs in CI over the committed tree, where no choice of editor or tool can route around it.
Fail closed, everywhere, on purpose
The interesting question about a gate is not what it does when it works. It is what it does when it is broken. Everything here answers that the same way:
- The publication gate greps everything about to be published for internal strings and fails the whole build on a hit, before anything is cloned or pushed. A public leak cannot be undone, so it refuses rather than guesses.
- The name-collision gate asks every system that could already know a name. An unreachable system is never read as "no collision": unknown stops the build.
- The AI budget gate denies when the budget is missing or unreadable, not just when it is exceeded. A broken secret closes the tap instead of opening it.
- Scanner triage can only ever downgrade a finding on a valid verdict. If the model is down or answers with nonsense, the finding stays real.
- The decommission gate refuses to destroy when its plan would destroy nothing, because a destroy that silently does nothing is a lie in an email.
The pattern is one decision made once: when a control cannot tell, it stops. Convenience is the thing you trade away, and it is the right trade every time.
Zero trust at the edge
Nothing is port-forwarded and nothing listens on the internet. A connector makes an outbound connection out to the edge network, and traffic comes back down that tunnel to an ingress controller inside the cluster. There is no inbound hole to find, because there is no inbound hole.
Every application hostname sits behind single sign-on at the edge, so an unauthenticated request is turned away before it reaches the app rather than by the app. Identity comes from the directory, group membership decides access, and the public landing page you are reading is the one deliberate exception: it is the only thing with no login in front of it. Remote access for me is a mesh VPN, not an exposed service.
Identity, least privilege, and machine identity
Access is role-driven and declarative: a role maps to its groups and its app assignments, so "why does this account have this" has a written answer. Offboarding disables first and never deletes, keeps a machine-readable snapshot taken before anything is stripped, and only removes an account after a grace period, and only when it can explain what it is removing.
Machines get identities too. An internal certificate authority issues for the services no public authority should ever vouch for, and the genuinely interesting part of running a CA is not issuing: it is that the templates are the privilege boundary. An over-permissive template that lets a low-privileged account choose its own subject is a well-known escalation path, so scoping them tightly is the actual work.
The bug I found in my own control
Deprovisioning a user is gated by two-person approval: restricted to an admin allowlist, and the requester cannot approve their own request. Good control. Then I audited it, and it was fail-open.
The same-person check only ran when the requester was known. On a run triggered by the API, a timer, or a replay, there is no requester attached, so the check short-circuited and a single actor could approve their own offboard. It now hard-fails on a blank requester, so every path reaches the gate with a name attached.
I keep this on the page on purpose. Anyone can list controls they have implemented. The useful question is whether you have ever gone back and tried to break one, because a control nobody audits is a control nobody has. This one was mine, it was wrong for weeks, and an audit is the only reason it is not wrong now.
Guardrails are not gates
Some checks here run at the moment work is done, and some run over what actually landed. Only the second kind is enforcement, and conflating them is how a review comes to believe a guard ran when it never did.
So the split is written down and treated as load-bearing: the convenience checks are guardrails against absent-minded mistakes, and the gates that cannot be routed around live in the pipeline, over the committed tree. Where a guardrail cannot enforce, a scheduled audit reports what got past it, because the honest answer to "we cannot prevent this" is to look regularly rather than to claim otherwise.
The same logic applies to anything a scanner cannot read. A text gate cannot see inside an image, so images are reviewed by a human against a written checklist rather than waved through by a green check that never looked.
Scanning, and what it is honestly worth
Container images, infrastructure code, and the repository are scanned on every change, and the scans are required checks rather than advice. A local model triages the output into exploitable-in-context versus likely-noise so that a wall of findings becomes a short list worth acting on, and it is only ever allowed to lower a verdict, never to invent one. Secret values are dropped before the model sees them at all.
Honest scope: a SIEM to correlate all of this centrally is designed and committed but not yet running. It is on the roadmap as work, not described here as a capability. A security page that lists what you plan to deploy is a wish list.