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(plusrunAsGroup: 65532,readOnlyRootFilesystem: true, and a/tmpemptyDir;postmortem.pyverified to write nothing to disk — itswfile.writecalls are the HTTP socket, not files),alert-routerandalert-triage(listen on :8080), andcloudflared-home/cloudflared-office(image already runs65532:65532, outbound-only and stateless, soreadOnlyRootFilesystem: trueas well). All getrunAsNonRoot: true,allowPrivilegeEscalation: false,capabilities.drop: ["ALL"], andseccompProfile: RuntimeDefault. - Non-root as uid 1000 —
directusandwikijs, whose images run asUSER node(uid 1000), get the same drop-ALL/no-priv-esc/seccomp set withrunAsUser: 1000. - Root retained, capabilities bounded —
ntfyandminio-engineeringget onlyallowPrivilegeEscalation: false,capabilities.drop: ["ALL"], andseccompProfile: RuntimeDefault; flipping their uid needs a live PVC-permission test that could not be run in this batch.ntfybinds :80 as root, so a baredrop: ["ALL"]would stripCAP_NET_BIND_SERVICEand 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 theNET_BIND_SERVICEadd-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-writerand both cloudflared instances additionally run on a read-only root filesystem. ntfyandminio-engineeringstill run as root; therunAsNonRootfollow-up is noted in the backlog pending a live volume-permission test.ntfycarriesCAP_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
USERand audit listen ports/write paths before declaringrunAsNonRoot.
References¶
- PR #2826 — fix: securityContext hardening for 9 workloads (backlog #1,3-8,16,17)