Nerdsense

I Let Fable 5.1 Audit My Cluster. Here Is What It Found.

Greg Heffner September 4th, 2026
I Let Fable 5.1 Audit My Cluster. Here Is What It Found.
TL;DR: I gave Claude Fable 5.1 read access to my four-node Kubernetes lab and asked it to audit the cluster against the CIS benchmark and make a plan. Two days later: etcd encryption that would have vanished on the next upgrade, kubelet certificates expired since May, five service accounts running as cluster-admin, GitHub Actions runners running under an account with passwordless sudo on every node, and a hole in nginx that let anyone pick who got blocked at my edge for a month. Every change shipped as a pull request I read and merged. The site never dropped a request.

Hey y'all. This one is uncomfortable to write. I have spent two years posting about Kubernetes, scanners and SIEMs, and risk-based patching, and I do security for a living. Then I let a model audit the cluster that hosts this very blog, and it came back with a list that included an encryption setup one upgrade away from breaking, certificates that expired in May, and a way for any stranger to get any address on the internet blocked from my site. Some of those had been sitting there the whole time I was writing the other posts.

I thought about not publishing this. Then I remembered why I started the blog: to learn in public and be honest about the gaps. Every one of these is fixed now, and I would rather you read how they got found than pretend a home lab run by one person on weekends is airtight. It is not. Still learning :)

The setup, for anyone new here: four cheap mini PCs running kubeadm, hosting exactly two things, this blog and my SSH bastion. When Fable 5.1 landed I decided the hardening pass I had been putting off was the project. Not a riddle, not a toy. Point the most capable model I have access to at real infrastructure I actually depend on, and see whether it earns any trust.

The rules were simple. It gets a read-only look at the cluster through my bastion, it writes a plan, and nothing changes unless I say so. Every change that touches the cluster goes through a pull request in my GitOps repo. The agent drafts, I merge.

What the audit found

It ran kube-bench, the open source checker for the CIS Kubernetes Benchmark, on all four nodes, then went looking at the things kube-bench cannot see: RBAC bindings, certificates, how the apiserver was actually launched, what the GitHub Actions runners were allowed to do. The list was longer than I expected and a couple of items were genuinely dangerous.

The encryption that would have disappeared

Etcd encryption at rest was on. I set it up months ago and was proud of it. What I did not know is that I had turned it on by hand-editing the static apiserver manifest, and the kubeadm configuration the cluster keeps as its source of truth had an empty apiserver section. The next kubeadm upgrade would have regenerated that manifest without the encryption flag, and the apiserver would have come up unable to read a single Secret. Fable called this the dangerous one, and it was right. It proved the drift with kubeadm upgrade diff before touching anything. The fix was a ConfigMap edit, and while we were in there it rotated to a stronger cipher and escrowed the old key, because older backups still need it to restore.

Certificates that had been expired for four months

Every kubelet on every node was serving a self-signed certificate that expired in May. Nothing broke because the apiserver was never told to verify them, which is its own finding. The order of operations matters here: turn on verification first and you lose kubectl exec and logs on every node. It did it the right way round, cluster-issued certificates through the kubelet TLS bootstrap flow, an approver for the signing requests, and only then made the apiserver check them.

Five cluster-admins, one of them dead since January

Five service accounts were bound to cluster-admin. Two belonged to a dashboard I do not use. Two belonged to the home page widget that shows my pod counts. The audit found that the widget's token had expired in January, an hour after it was minted, and had been returning 401 for seven months. I had not noticed because I do not look at that widget. That is five to zero, and a lesson about who watches the watchers.

No audit log at all

The API server had no audit log at all. The first policy it wrote produced about a gigabyte a day, which would have filled the disk in a month. It tuned it in two passes to under a hundred megabytes and wrote Wazuh rules so the log actually alerts on something instead of just existing. My SIEM now tells me when a Secret is read by something that is not supposed to read Secrets.

The one that was not on any benchmark

While looking at the fail2ban sidecar that bans scanners at my Cloudflare edge, it noticed nginx trusted the forwarded-for header from any source. Cloudflare appends the real client address to whatever the visitor sent, and my config read the first value. I watched it prove this against the live site with a single request carrying a fake address, and the fake address landed in the log.

Put that together with a jail that blocks an address for thirty days after ten missing pages, and any visitor could have gotten anyone blocked from my site for a month. Googlebot. My own monitoring. My house. The fix was a few lines, switching to the header Cloudflare overwrites on every request, and it verified the attack was dead before moving on. That finding is worth more to me than the whole benchmark.

What went wrong

I want to be fair to the tool, so here is where it stumbled.

  • It broke fail2ban. Moving the site content into a git-sync sidecar meant the nginx log started empty, and fail2ban refuses to start without a log file to watch. Six pods crash-looping. It diagnosed its own mistake in a couple of minutes and shipped a one-line fix, but it made the mistake.
  • Moving the GitHub runner to an unprivileged user failed twice. The runner install uses symlinks back into the original home directory, and the copy carried the links instead of the files. The error message pointed at a node binary and was completely misleading. Third attempt worked.
  • Its sub-agents kept hanging when they tried to SSH. It noticed, pulled the results out of the journal, and did the SSH work itself. Resilient, but not clean.
  • It talked too much early on. I had to tell it "explain first, then we decide" more than once.

What I said no to

This is the part I care about most. Trust earns autonomy, not the other way around, and the way you build that trust is by disagreeing with the tool and seeing how it takes it.

  • It wanted to pin the cloudflared image to a version. I said no, because my weekly restart on a floating tag is how I get security patches. It agreed that was a working design and dropped it.
  • It recommended I decline resource limits on thirty-one third-party containers, and argued its own case against the item: no memory pressure, one tenant, real risk of throttling the control plane. I agreed.
  • It proposed retiring fail2ban for Cloudflare's native rules. I am on the free plan and that rule does not exist there. It checked, said so, and stopped proposing it.

Final Thoughts

I have used a lot of models on this cluster. Fable 5.1 is the first one I would describe as a colleague rather than a tool. It did not just run a checker and hand me a list. It proved each finding before it touched anything, it ordered the risky changes so nothing broke on the way through, it caught its own mistakes and fixed them, and when I pushed back it either defended the call with evidence or dropped it. The forwarded-header hole is the clearest example. No benchmark asked for that. It noticed it while looking at something else, tested it against the live site, and closed it inside the hour.

The goal I set on day one was simple to say and hard to do: bring a two-year-old home cluster up to the CIS benchmark without taking the site down, and leave it in a state where drift gets caught instead of quietly accumulating again. That is done. Etcd encryption survives an upgrade now. Every kubelet serves a certificate the cluster issued and the apiserver checks. Nothing is bound to cluster-admin. Seven namespaces reject a non-compliant pod at admission. The API server logs who reads what, Wazuh alerts on it, and a weekly drift check compares the running cluster to the config in git and pages me on the difference. I ran kube-bench again after the last merge: zero failing controls on all four nodes, where the first run had turned up the encryption drift, the world-readable signing key, the missing audit log and the expired certificates. Two days, three phases, a dozen pull requests, and the blog answered 200 on every check through every rollout.

None of that happened without me in the loop, and I would not want it to. The agent drafts, I merge. Fable 5.1 earned a lot of trust this week, and it earned it the right way: by being right, by showing its work, and by taking no for an answer. The lab is where the opinions get tested, and this one passed. Still learning :)

Stay safe online!

About Me

I served in the U.S. Army, specializing in Network Switching Systems and was attached to a Patriot Missile System Battalion. After my deployment and Honorable discharge, I went to college in Jacksonville, FL for Computer Science. I have two beautiful and very intelligent daughters. I have more than 20 years professional IT experience. This page is made to learn and have fun. If it's messed up, let me know. I'm still learning! :)

Weather Loop

Animated radar loop of Southeast US weather from NOAA