limbserver
A 47-service self-hosted platform running in production for my family: single sign-on, encrypted secrets, immutable offsite backups, and full observability. Then I attacked it with an AI-driven penetration test and fixed what it found.
What it is
One platform repo orchestrates five service engines through a single make up. Everything shares one identity provider and one secrets store, and nothing is exposed that does not need to be.
Security was the interesting part
I ran a structured, AI-driven penetration test against my own platform: 30 subagents researched known CVEs for every pinned image, and an external probe tested the live attack surface from outside the network. Findings were tiered L0 to L7.
Decisions worth noting
Assume-breach network segmentation, then prove it
Every database and cache sits on a private per-stack network, off the shared bridge. I did not take that on faith: I ran the test from inside a container on the shared network and confirmed the data tiers were unreachable.
# assume-breach test from a container on the shared network $ nc -z authentik-postgres 5432 # → blocked (name does not resolve) $ nc -z immich-redis 6379 # → blocked $ nc -z jellyfin 8096 # → reachable (intended)
A guardrail that stops AI agents from leaking secrets
Because I build and operate this platform with coding agents, I wrote a pre-tool hook that denies any agent attempt to read plaintext secrets, while still allowing the encrypted store. It is real, working defense in depth, and it fired and blocked an agent during this very portfolio's research.
$ agent> read .env ⛔ blocked by .claude/hooks/block-secret-reads.py reason: plaintext secret access denied for agents allowed: encrypted secrets.sops.env # decrypt only via `make`
Secrets and recovery designed for the worst day
One encrypted secrets file with two independent recovery keys, immutable offsite backups with a ransomware-proof retention lock, and a disaster-recovery drill that restores and validates every database dump. Built around a box that powers off nightly, so backups catch up on their own.
Outcome
What I'd do differently
I hand-wrote a lot of the operational glue before deciding what deserved to be permanent. Some of it earned its place; some was effort spent on parts that never changed again. Next time I would let the runbook and the pain points tell me what to automate, instead of automating first.