Skip to content

0049. Security Context Hardening for Multiple Workloads

  • Status: accepted
  • Date: 2026-07-21
  • Deciders: lusoris
  • Related: PR #2826

Context

Nine workloads from the small-fixes backlog (items 1, 3–8, 16, 17) ran without pod- or container-level securityContext: the AI helpers incident-postmortem-writer, alert-router, alert-triage (all python:3.14-alpine), the engineering apps directus, minio-engineering, wikijs, and the infra apps cloudflared-home, cloudflared-office, ntfy. Blanket runAsNonRoot: true would crashloop any image that actually starts as root, so each image's real default USER was resolved with crane before the spec was written, and every hunk was reviewed against listen ports and write paths.

Decision

Apply PSS-restricted-shaped security contexts per verified image behavior, in three tiers:

  • Full non-root hardening (uid 65532)incident-postmortem-writer (plus runAsGroup: 65532, readOnlyRootFilesystem: true, and a /tmp emptyDir; postmortem.py verified to write nothing to disk — its wfile.write calls are the HTTP socket, not files), alert-router and alert-triage (listen on :8080), and cloudflared-home/cloudflared-office (image already runs 65532:65532, outbound-only and stateless, so readOnlyRootFilesystem: true as well). All get runAsNonRoot: true, allowPrivilegeEscalation: false, capabilities.drop: ["ALL"], and seccompProfile: RuntimeDefault.
  • Non-root as uid 1000directus and wikijs, whose images run as USER node (uid 1000), get the same drop-ALL/no-priv-esc/seccomp set with runAsUser: 1000.
  • Root retained, capabilities boundedntfy and minio-engineering get only allowPrivilegeEscalation: false, capabilities.drop: ["ALL"], and seccompProfile: RuntimeDefault; flipping their uid needs a live PVC-permission test that could not be run in this batch. ntfy binds :80 as root, so a bare drop: ["ALL"] would strip CAP_NET_BIND_SERVICE and fail the bind — add: ["NET_BIND_SERVICE"] is added back, the one capability PSS-restricted allows.

Alternatives considered

  • Blanket runAsNonRoot: true + uid 65532 across all nine workloads: rejected — the ntfy and minio images genuinely start as root, and their PVC data ownership is untested for a uid flip; this would have crashlooped them.
  • Bare drop: ["ALL"] on ntfy without the NET_BIND_SERVICE add-back: rejected in review — it strips the capability the root :80 bind depends on and would have crashlooped a public service.
  • Deferring ntfy/minio entirely until the uid question is settled: rejected — drop-ALL, no-priv-esc, and RuntimeDefault seccomp are safe now and independent of the uid decision (documented retroactively).

Consequences

  • Seven of the nine workloads now run non-root with dropped capabilities under RuntimeDefault seccomp; incident-postmortem-writer and both cloudflared instances additionally run on a read-only root filesystem.
  • ntfy and minio-engineering still run as root; the runAsNonRoot follow-up is noted in the backlog pending a live volume-permission test.
  • ntfy carries CAP_NET_BIND_SERVICE; removing it (or the root uid) without moving the listener off :80 breaks the service.
  • The crane-verify-then-write pattern is the template for future securityContext batches: resolve the image's default USER and audit listen ports/write paths before declaring runAsNonRoot.

References

  • PR #2826 — fix: securityContext hardening for 9 workloads (backlog #1,3-8,16,17)