Remember the sandbox I was so proud of? Disposable containers, no network, no capabilities, read-only root filesystem? The one with 34 passing tests?
None of it was working.
I found out during end-to-end testing when files written by one step couldn’t be found by the next. The workspace volume wasn’t mounting. I traced the problem through the config chain, checked the compose settings, verified the environment variables — everything looked correct.
Then I tested the Podman API directly and discovered the root cause. Every field in my sandbox creation payload was snake_case: host_config, binds, network_disabled, cap_drop. Podman’s Docker-compatible API requires PascalCase: HostConfig, Binds, NetworkDisabled, CapDrop.
And here’s the thing that still bothers me — Podman accepted the request. HTTP 201 Created. No warning, no error, no log entry. It just silently ignored every field it didn’t recognise and created a bare container with defaults.
The impact was comprehensive. Read-only root filesystem? Not set. Drop all capabilities? Not set. Disable network access? Not set. The sandbox had been providing zero containment since the day I built it.
My unit tests passed because they mocked the HTTP transport and checked the same wrong field names. The integration tests didn’t exist. The scanner pipeline caught everything before it reached the sandbox, so the sandbox’s emptiness was invisible from the outside.
Three fixes landed: PascalCase fields (Fix V), additional hardening through NetworkMode and SecurityOpt (Fix W), and non-root execution via privilege drop (Fix X). The sandbox is real now. But the lesson — that a silent success response can hide a total failure — stuck with me harder than any bug I’ve found.