Skip to content

✨ feat(filter): libpod container-create body inspector with golden fixtures (#148) - #191

Merged
scttbnsn merged 6 commits into
dev/v1.6from
feat/148-libpod-container-create
Aug 5, 2026
Merged

✨ feat(filter): libpod container-create body inspector with golden fixtures (#148)#191
scttbnsn merged 6 commits into
dev/v1.6from
feat/148-libpod-container-create

Conversation

@scttbnsn

@scttbnsn scttbnsn commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

PR 2 of the #148 train, building on the merged routing groundwork (#189).

What's here

  • Golden fixtures first: 20 real POST /libpod/containers/create bodies captured from a live Podman socket (client 6.0.2 darwin/arm64, server engine 5.8.1 linux/arm64), committed under testdata/libpod/ with per-file provenance. The fixtures settled shape questions the design drafts disagreed on (ipcns is the real tag) and surfaced quirks no draft anticipated: the mounts/volumes casing split, singular sysctl, unsplit device paths, and systemd defaulting to "true" even when never passed — that last one is why the systemd gate reads the effective value, not just presence.
  • Inspector (libpod_container_create.go + types): path-exclusive (gated by isLibpodContainerCreatePath), subset decode via the shared JSON helper, ~20 gates mirroring container_create's operator vocabulary (privileged, host namespaces via the {nsmode,value} shape, namespace-sharing restriction, bind-mount allowlist through the shared normalizeBindMount, devices, capabilities, seccomp/apparmor/selinux including selinux disable, non-root user, memory/CPU/CPU-hard/PIDs from resource_limits, sysctls, read-only rootfs, image-trust on image) plus two libpod-only gates: allow_systemd_mode and allow_custom_id_mappings. Same 1 MiB body cap.
  • TestInspectorRoutingIsPathExclusive — the design's central proof: one body carrying both Docker-shaped and libpod-shaped privilege keys with opposing values, run against both paths, each inspector reads only its own shape. A mis-shaped decode reads zero values ("safe"), so this is the fail-open class the whole structure exists to prevent.
  • Config block request_body.libpod_container_create (fail-closed defaults, validation per the existing pattern), blind-write/inspection-configured table entries, FuzzLibpodContainerCreate seeded from the fixtures (~1.3M execs clean), configuration.mdx section + env-var table.

Verification

CI-mirrored measurement: production coverage 96.2% vs the 96% floor; go test -race ./... green; golangci-lint 0 issues; full pre-push hook suite green.

Part of #148 (does not close it). Will need a rebase against #190 (shared wiring files) — whichever merges second.

Real POST /libpod/containers/create request bodies captured from a live
podman (client 6.0.2 darwin/arm64) talking to a podman machine VM (Podman
Engine 5.8.1 linux/arm64), covering basic create, privileged, host
netns/pidns/ipcns/userns, container:<ref> namespace sharing, bind/tmpfs
mounts, named volumes, devices, cap_add/cap_drop, seccomp/apparmor/selinux
opts, resource limits (including cpu.shares-only), systemd mode, custom
idmappings, labels, sysctls, read-only rootfs, and non-root user.

Resolves the field-name ambiguity the design doc left open (ipcns, not
ipc) and surfaces several real-world quirks no draft anticipated: the
mounts/volumes field-casing split, singular "sysctl" naming, unsplit
device path strings, and systemd defaulting to "true" even when --systemd
is never passed on the CLI. README.md documents exact client/server
versions and per-fixture capture provenance.
libpod_container_create_types.go / libpod_container_create.go implement
POST /libpod/containers/create body inspection, structured like
container_create_types.go/container_create.go: subset decode of the
SpecGenerator body, a policy struct, and an inspect() method gated by
isLibpodContainerCreatePath (path-exclusive — never reads a Docker-shaped
body, and the Docker inspector never reads a libpod-shaped one).

Gates mirror ContainerCreateOptions where the semantics map: privileged,
host netns/pidns/ipcns/userns, namespace-sharing restriction reading
{nsmode,value}, allowed_bind_mounts via the shared normalizeBindMount,
devices, allowed_capabilities, seccomp/apparmor/selinux allowlists
(including selinux disable), require_non_root_user, memory/cpu/cpu-hard/
pids limits from resource_limits, sysctls, read_only_filesystem, and
image trust reusing the existing ImageTrust wiring on the image field.
Two libpod-only gates have no Docker analog: allow_systemd_mode (denies
non-"false" systemd by default, since SpecGenerator's own default is
"true") and allow_custom_id_mappings. Field names and gate semantics are
pinned from the golden fixtures captured in the prior commit, not from
either draft of the design doc.
Adds LibpodContainerCreateRequestBodyConfig under request_body under a
new libpod_container_create mapstructure key, following
ContainerCreateRequestBodyConfig's field/tag pattern. ToFilterOptions
plumbs it into filter.LibpodContainerCreateOptions, and
validateLibpodContainerCreateConfig mirrors validateContainerCreateConfig
(image_trust validation, allowlist sanity checks). All defaults fail
closed; ImageTrust.RequireRekorInclusion defaults to true, matching
container_create's default.
middleware.go registers the libpod inspector in compileRuntimePolicy's
policy list, keyed by isLibpodContainerCreatePath (path-exclusive, right
after the Docker container-create entry). cmd/rules.go adds POST
/libpod/containers/create to bodySensitiveWriteEndpoints and a
bodyInspectionConfiguredForEndpoint arm, so the startup blind-write
validator recognizes the new inspector the same way it does every other
body-sensitive endpoint.

Kept minimal and additive — a sibling PR is touching the same three files
(pod-create/exec/volume inspectors) and will need a rebase against this.
Per-gate table tests driven off the golden fixtures for every one of the
~20 gates in libpod_container_create.go, plus:

- TestInspectorRoutingIsPathExclusive: the design doc's central test — a
  single body carrying both a Docker-shaped dangerous value
  (HostConfig.Privileged) and a libpod-shaped safe value (top-level
  "privileged") with opposing truth values (and the mirror body), proving
  neither inspector ever reads the other's shape, and that each is a
  structural no-op on the other family's path.
- Fail-closed decode tests: malformed JSON, oversized body (1 MiB cap via
  requestRejectionError), empty body allowed, and nil-request/wrong-method/
  wrong-path/nil-body no-ops.
- Image-trust tests reusing the existing mockImageVerifier/
  mockSignatureFetcher fixtures from container_create_test.go, including
  the verify→pin digest rewrite and duplicate-case-variant-key rejection.
- FuzzLibpodContainerCreate, seeded from all 20 fixtures plus adversarial
  inputs; 20s local smoke run (~1.3M execs) found no crashes.

Every captured fixture defaults "systemd":"true" (SpecGenerator's own
default even when --systemd is never passed), which the new
allow_systemd_mode gate denies by default — so every "expect allow"
assertion in this file explicitly opts a policy into AllowSystemdMode
where the fixture would otherwise trip that gate first and mask the gate
actually under test. Assertions expecting deny from a gate that runs
before the systemd check in inspect()'s order don't need it.
Adds request_body.libpod_container_create to the YAML sample, a prose
paragraph describing the gates (mirroring the container_create section's
structure), a Request Body Policy Reference table row, and a dedicated
environment-variable table following the container_create precedent.
CHANGELOG gains an Unreleased ### Added entry summarizing the new
inspector, its gate set, and the fixture-derived field-name resolution.
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
sockguard-website Ready Ready Preview Aug 5, 2026 2:40pm

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@scttbnsn, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 58 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 16fdcf34-a6e3-4cec-8bd9-565254f5cabf

📥 Commits

Reviewing files that changed from the base of the PR and between 78aaa2f and 5adc8d1.

⛔ Files ignored due to path filters (1)
  • CHANGELOG.md is excluded by !CHANGELOG.md
📒 Files selected for processing (30)
  • app/internal/cmd/rules.go
  • app/internal/config/config.go
  • app/internal/config/filter_options.go
  • app/internal/config/validate.go
  • app/internal/filter/libpod_container_create.go
  • app/internal/filter/libpod_container_create_test.go
  • app/internal/filter/libpod_container_create_types.go
  • app/internal/filter/middleware.go
  • app/internal/filter/testdata/libpod/README.md
  • app/internal/filter/testdata/libpod/basic_create.json
  • app/internal/filter/testdata/libpod/capabilities.json
  • app/internal/filter/testdata/libpod/devices.json
  • app/internal/filter/testdata/libpod/host_ipc.json
  • app/internal/filter/testdata/libpod/host_network.json
  • app/internal/filter/testdata/libpod/host_pid.json
  • app/internal/filter/testdata/libpod/host_userns.json
  • app/internal/filter/testdata/libpod/idmappings.json
  • app/internal/filter/testdata/libpod/labels.json
  • app/internal/filter/testdata/libpod/mounts_bind_tmpfs.json
  • app/internal/filter/testdata/libpod/namespace_share_container_ref.json
  • app/internal/filter/testdata/libpod/privileged.json
  • app/internal/filter/testdata/libpod/read_only_filesystem.json
  • app/internal/filter/testdata/libpod/resource_limits.json
  • app/internal/filter/testdata/libpod/resource_limits_cpu_shares.json
  • app/internal/filter/testdata/libpod/security_opts_seccomp_apparmor_selinux.json
  • app/internal/filter/testdata/libpod/sysctls.json
  • app/internal/filter/testdata/libpod/systemd_mode.json
  • app/internal/filter/testdata/libpod/user.json
  • app/internal/filter/testdata/libpod/volumes_named.json
  • docs/content/docs/configuration.mdx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant