Repository navigation
security(kubescape): patch the two non-privilege C-0211 fields into workload templates, so the exception can narrowΒ #3522
Description
π€ Generated by the Agentic Engineer
Evidence
add-baseline-context-optin-* supplies fsGroupChangePolicy and an effective SELinux level to pods in opted-in namespaces, and it demonstrably works β velero's pods carry both fields and none failed to start. But Kyverno mutates the pod at admission, while Kubescape scores the stored workload spec. Settled with evidence on #3239 (2026-09-02): each scan object is keyed to the workload, not a pod (deployment-velero carries wlid://cluster-prod/namespace-velero/deployment-velero, kubescape.io/workload-kind: Deployment, relatedObjects: null), and the exception's own reason field has always said so β "Kubescape scans the stored spec, not the live pod."
velero is the natural experiment: opted in, its pods measurably carry both fields, its template carries neither, and the scan object is the Deployment's.
So no amount of widening the namespace opt-in can move a template-scored verdict. Re-measured live against prod stored specs on 2026-09-02:
| field (pod level, stored spec) | absent on |
|---|---|
fsGroupChangePolicy |
35 / 35 |
effective SELinux level on every container |
35 / 35 |
fsGroup |
20 / 35 |
runAsGroup |
26 / 35 |
runAsNonRoot |
19 / 35 |
Population is 35 workloads across the four scanned excluded namespaces β 17 Deployments, 8 DaemonSets, 7 CronJobs, 3 StatefulSets (flux-system 6, longhorn-system 12, observability 15, velero 2).
Audience and problem
The pod-security-mutations-unscoped exception suppresses C-0211 across this population, so every workload reports passed w/exceptions regardless of its actual state. That exception is waiting to be narrowed, and it cannot narrow while the underlying templates still fail β which, on the scanned surface, all 35 do. The admission-time mutation improves runtime posture and can never discharge the exception.
Hypothesis
Patching the two non-privilege fields into the workload templates at source β chart values where the chart exposes them, kustomize patches where it does not β moves the scanned verdict for those two controls, after which C-0211's residual shrinks to the workloads that still fail on the genuinely privilege-adjacent fields (fsGroup, runAsGroup, runAsNonRoot) and narrowing the exception becomes sizable rather than all-or-nothing.
Deliberately not in scope: fsGroup, runAsGroup, runAsNonRoot. Those are the fields the namespace exclusions exist to protect, and supplying them is a privilege change, not a compliance one.
Success signal
- Baseline: 35 / 35 templates missing
fsGroupChangePolicy; 35 / 35 missing an effective SELinuxlevel. - Target: 0 / 35 for both, read with the
subStatusdiscriminator rather than the barestatusβ a suppressed control readspassedeither way, so the naive read returns a clean result over an unfixed population (this is exactly the fail-open feat(security): document every suppressed control's fail-open, and fail CI when one is notΒ #3517 generalised). - Guardrail: no workload regressed on privilege, user or group fields, and no pod sandbox failing to start.
Smallest useful change
One namespace, smallest first: velero (2 workloads). It is already opted in, so the pod-scope and template-scope effects can be compared directly on the same workloads β which also proves the measurement method before it is applied to the other 33.
Acceptance criteria
- The two non-privilege fields are present in the stored spec of the targeted workloads, via chart values or kustomize patches, not via admission mutation.
- Verified by a single-object
GETof the workload'sworkloadconfigurationscansobject β a LIST returns.spec.controlsnull and would read as a clean sweep. -
subStatusis used to distinguish a genuine pass from a suppressed one. - No regression on privilege, user or group fields; pods still start.
- The C-0211 residual recorded in
pod-security-mutations-unscoped.yamlis updated to the new figure, re-measured rather than inherited.
Size
Medium, and naturally decomposable per namespace. The investigation is already done β #3239 carries it β so what remains is the mechanical work plus its measurement.
Follow-up to #3239, which established that admission-time mutation cannot move this surface. The two are complementary, not alternatives: the mutation stays for runtime posture, this is the narrowing path.
Metadata
Metadata
Assignees
Labels
Type
Fields
Projects
- StatusShow more project fieldsβ Done
Activity
chore: agent-claim #3522 nonce=cff7a754e01714f0079467c5ab1ef0c8
fix(security): re-approve the authorization surface digest for the veβ¦
devantler commentedon Sep 3, 2026
Post-merge verification: the fix is live, but C-0211 cannot be its success signal
Checked in prod after #3541 merged. Two findings, and the second one changes how this issue should be closed.
1. The fix is in the stored specs β confirmed
Both workloads now carry the two non-privilege fields directly in the template, which is the thing Kubescape actually scans:
fsGroupChangePolicyseLinuxOptions.levelrunAsUserDeployment/veleroOnRootMismatchs065532(unchanged)DaemonSet/node-agentOnRootMismatchs00(unchanged β keeps its host-path root)2. C-0211 reads
passed, and that reading is worthless hereIt would be easy to read
C-0211: passedon both workloads as success. It is not evidence of anything, for two independent reasons:It passes by exception, not by compliance. The two rules this issue targeted both list the suppression explicitly:
pod-security-mutations-unscopedignores C-0211 cluster-wide, so these rules reportpassedwhether or not the fields are present. The control is insensitive to this change by construction. (For contrast,set-fsgroup-valuepasses with no ignore and no fix path β that one is a genuine pass.)The scan is also stale. Both rules still emit a
fixPathfor the very fields that are now present:A fix path is only emitted when the rule found the field missing. So the scan evaluated a pre-#3541 spec. That is corroborated independently: the newest
workloadconfigurationscananywhere in the cluster was created2026-09-02T10:21Z, while the velero rollout completed2026-09-03T00:19Z. Nothing has re-scanned these workloads since the change.What this means for closing this issue
This issue's own title is the right frame β the goal is "so the exception can narrow", not "so C-0211 turns green". C-0211 was always going to read
passedhere. So:reasoncites.The remaining work is therefore to re-measure the residual population once a scan cycle has actually run against the new specs, and narrow
pod-security-mutations-unscopedby whatever that measurement licenses. I have not attempted the narrowing in this run, because doing it against a stale scan would size it wrong.Worth noting for anyone auditing posture more broadly: 11 rules across this one workload pass only via an applied ignore rule. An exception-suppressed
passedand a compliedpassedare indistinguishable in a summary count, so posture percentages built from control status alone will read better than the cluster is.devantler commentedon Sep 14, 2026
Status 2026-09-14: the template slices shipped; narrowing waits on #3239
I picked this up as the oldest unclaimed Security issue and checked it against what's on
mainnow.mainrecords six of the population passing both gaps.I've marked this issue blocked by #3239, so the queue skips it for a real reason until the rollout lands. Once it does: re-measure the residual against the live stored specs (not the scan objects, which read
passedunder the exception either way), then narrowpod-security-mutations-unscopedby what that measurement supports.docs(kubescape): count fsGroup in the C-0211 residual and record the β¦
devantler commentedon Sep 26, 2026
Re-measured 2026-09-26: the 2026-09-25 sizing holds, and #3239 no longer blocks this
The blocker is gone. #3239 closed as completed on 2026-09-25, so the
blocked by #3239dependency recorded above is satisfied.I re-measured read-only against the live prod stored specs, the surface Kubescape scans. The numbers match the sizing that #4200 recorded in
pod-security-mutations-unscoped.yaml:fsGroupChangePolicy, and an effectiveseLinuxOptionsobject reaches every container, init containers included. The hypothesis this issue was opened for is delivered.fsGroupis missing on 19,runAsGroupon 11, andrunAsNonRooton 11. So C-0211 still cannot narrow without surfacing unfixed findings, and the exception correctly stays cluster-wide.Next slice for the narrowing this issue now tracks. Seven of the 19 already run as non-root and miss only
fsGroup: flux-operator, longhorn-ui, the longhorn stale-node-cleanup CronJob, and four Coroot CronJobs. They are the cheapest to move at source. The other 12 genuinely run with elevated privileges: CSI sidecars, storage and node agents, and the log forwarder. Each of those needs a scoped, name-level exception with a recorded reason, not a field change.Once both groups are handled, C-0211 can leave the cluster-wide exception.
I filed #4210 for this narrowing before I saw that #4200 had already pointed the exception at this issue. I've closed #4210 as a duplicate so the work stays tracked here.
chore: agent-claim #3522 nonce=95f0b6c8402296da230c12e8b3ae1d2f
chore: agent-claim #3522 nonce=6a8bbcb19275c542611fff0c3d095004
chore: agent-claim #3522 nonce=9f2551aba90f77e337290c6c67ad1694