Luther Monson, Staff Software Engineer at SUSE

Luther Monson

Go by way of PHP · Containers · VMs · Windows · Kubernetes

Your Agent's Shell Allowlist Is Bypassable

A permission gate for AI agents' shell commands judged spellings instead of commands. I found several classes of bypass in shipmates' own gate, and one idea fixes most of them.

9-Minute Read

A shell command decomposed into the nested command lines the permission gate has to judge

Every shipmates crew member runs shell commands. Before any of them runs, it goes through a permission gate. The gate uses the same rule syntax as Claude Code settings: Bash(git status), Bash(rm *), Write(**/.claude/settings.json). The rules are merged from several layers:

Bind Mounts Across Mount Namespaces: Four PRs and a Container Escape

Making GitHub Actions container: jobs work through ephemerd's Docker API shim took four tries, then a TOCTOU race turned the working version into an escape.

8-Minute Read

Sequence diagram of the bind mount TOCTOU race and the openat2 plus staging fix

GitHub Actions has a container: key that runs a job’s steps inside an image of your choosing. It’s popular because it gives you a reproducible toolchain without building a custom runner image. On a self-hosted runner it works by having the runner drive Docker. On ephemerd, “Docker” is a shim that speaks the Docker API and creates containers in containerd. Getting bind mounts right through that shim took four PRs in a week. A couple of months later the version that worked…

Every Way a Tenant Escaped: A Cross-Tenant Breakout Field Guide

Eight ways one site broke out of its sandbox on a multi-tenant PHP server, all found by attacking it on purpose, all fixed.

10-Minute Read

Table of tenant isolation boundaries in ePHPm, how each was crossed, and the PR that fixed it

ePHPm can serve a directory full of sites out of one process. Point sites_dir at a folder and every subdirectory is a virtual host. The Host header decides which one you get. That’s how the PR preview environment runs. Every pull request gets a subdomain, and the code behind it is whatever somebody pushed. One process holding many tenants means ePHPm enforces every per-tenant boundary itself, in-process. There’s no container wall between site A and site B unless you add one.

We Disabled PHP's Stack Guard Ourselves: Containing Segfaults in a Single-Process PHP Server

How ePHPm went from dying silently on a segfault to containing stack overflows per thread. It also covers the linker flag that had turned off PHP's own guard.

12-Minute Read

Diagram of a PHP thread stack, the guard window below it, and which faults get contained versus which kill the process

php-fpm has a property nobody thinks about until they leave it. When a worker segfaults, you lose one request. The master forks a new child and life goes on. ePHPm runs every tenant’s PHP as threads inside one process. That’s where the memory and latency wins come from. It also means one tenant’s segfault takes down every other tenant on the box.

Clustered ACME in Production: Three Bugs That Took TLS Down

Moving wildcard TLS onto a live 3-node ePHPm cluster found a leader election that wasn't, a certificate only two nodes could see, and an HTTP port speaking TLS.

10-Minute Read

Diagram comparing a sharded KV write that leaves one of three nodes without the certificate to a broadcast write that reaches all three

ePHPm gets its own certificates from Let’s Encrypt. In a cluster only one node should be talking to the CA. The nodes elect an ACME leader over the shared KV store. The leader orders the cert, and everybody else installs it from KV. That all had unit tests and had worked on single nodes for months. Then I tried to move wildcard TLS termination for our preview environment off a separate proxy and onto the 3-node cluster itself. In about two days it found three separate bugs that each took…

Per-Tenant Network Policy in One Process: ePHPm's eBPF Thread Tags

Every site on an ePHPm server runs inside the same process, so a normal firewall can't tell them apart. A small eBPF program tags each thread with its site. It keeps sites from reaching each other's local ports for under a microsecond per connection.

17-Minute Read

Flow from a PHP pool thread writing its vhost tag into a BPF map, through the bind and connect cgroup hooks, to allow, rewrite or EPERM

In multi-tenant mode ePHPm serves every site in sites_dir from one process. Every tenant’s PHP runs as the same OS user on the same pool of ZTS threads. A thread that just served blog.example will serve shop.example a few milliseconds later. That’s the density win. It also means the kernel has no idea which tenant is behind a given connect(). As far as Linux is concerned it’s all ephpm-web.

Security and Performance Hardening of a Rust Database Proxy

What a week of hardening litewire turned up. A dependency had a memory bug, one site could open its neighbor's database, a security rule lived only in a comment and every query paid for a needless 1 MB clear.

11-Minute Read

Bar chart of point SELECT throughput over the litewire MySQL frontend at three revisions: before the command filter, with the filter, and with the poll_read fix

litewire is the piece of ePHPm that lets PHP apps talk MySQL to a SQLite file. I wrote about how it works and about the MySQL quirks WordPress depends on. This one is the less visible half of August:

My iptables Rule Matched 0 Packets in 9.8 GB

Container-to-host traffic goes through INPUT, not FORWARD, so ephemerd's carefully written allow-list never matched a single packet.

8-Minute Read

Netfilter flow showing container traffic to the host taking INPUT instead of FORWARD

After a week of Windows firewall rules that listed fine and enforced nothing, I went back to the Linux side of ephemerd feeling pretty good about it. iptables is the boring, well-understood one. Then I looked at the packet counters on a live node. I found a rule that had never matched a packet in its life. That node had pushed 9.8 GB through the same chain.

Five Ways to Not Firewall a Windows Container (and the One That Works)

Five different Windows firewall approaches failed to stop a container from reaching my home network. The sixth, a different network mode for the container, finally worked.

10-Minute Read

Packet path of a Windows container on an HNS NAT network versus an L2Bridge network

ephemerd runs CI jobs from pull requests. One of the rules is that a job can reach the internet but not my LAN. On Linux that’s a few iptables rules in the FORWARD path, and I didn’t think about it much. On Windows I spent about a week finding out how many ways there are to write a firewall rule that Windows accepts, lists and never applies.

Recent Posts

Categories

About

Luther is a 20+ year software veteran specializing in backend APIs and architecting products. Employed at SUSE as the Team Lead for the Containers on Windows Team working on Rancher + Kubernetes