Skip to content

security(kubescape): C-0211's residual population behind the pod-security exception is unsized and unowned #3217

Closed
1 of 1 issue completed

Description

@devantler

🤖 Generated by the Agentic Engineer

Evidence

pod-security-mutations suppresses C-0211 alongside C-0013/C-0016/C-0055, cluster-wide. The
other three have been sized: #3064 measured their residual population against prod and closed
COMPLETED once the three kubescape Deployments were hardened in-spec.

C-0211 was explicitly excluded from that sizing. The exception's own annotation says so:

C-0211 is deliberately NOT sized here. It has no other exception, its field set differs from the
three above (fsGroupChangePolicy, runAsUser and runAsGroup are supplied by the mutation), and the
name-scoped kubescape-privileged.yaml does not cover it — so node-agent and storage remain
inside its residual population rather than excepted out of it.
Sizing it needs its own per-workload pass; #3064 carries that.

That last pointer no longer holds. #3064 is CLOSED/COMPLETED, and it never carried the general
per-workload pass — its only C-0211 acceptance criterion was narrow: "C-0211 coverage for
node-agent is decided explicitly"
. So the pass the annotation defers to does not exist and is
owned by nothing.

Impact

C-0211 is the one control in this exception with no compensating coverage anywhere: it has no
other exception, and kubescape-privileged.yaml does not name it, so node-agent and storage sit
inside its residual population rather than being excepted out of it. Suppression is silent and
indistinguishable from compliance, so nothing here would show a regression — the failure mode #2447
exists to prevent.

This also blocks the narrowing in #2824: the scope is now expressible (NotIn, #3083), but the set
that would surface for C-0211 is unknown, so a narrowing would be sized by guess.

Expected

The residual population for C-0211 is enumerated per workload, and each entry is either hardened
in-spec or carries a narrow, workload-scoped exception whose reason is true of it.

Acceptance criteria

  • Every workload in the twelve namespaces add-security-context excludes is checked against
    C-0211's field set (fsGroupChangePolicy, runAsUser, runAsGroup), and the result recorded.
  • node-agent and storage each get an explicit decision — hardened, or named in a
    workload-scoped exception stating why.
  • The measurement is re-run rather than inherited: the 2026-08-10 snapshot in the annotation is
    prod-only and the local overlay opts in to a different namespace set.
  • The result is written where the next reader will find it, so this does not become a third
    stale pointer.

Size

Small-to-medium, and it is measurement-first — no manifest change is justified until the population
is known.

Part of #2824

Activity

  1. moved this to 📥 Backlog in 🌊 Project Boardon Aug 18, 2026
  2. added a commit that references this issue on Aug 19, 2026
  3. devantler commented on Aug 19, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Agentic Engineer

    Measurement — C-0211's residual population is sized

    Measured 2026-08-19 against the live prod stored specs (the surface Kubescape reads), not
    inherited from the annotation snapshot.

    the population is 33 workloads, not two

    The twelve namespaces add-security-context excludes split into three groups in prod:

    Group Namespaces In the population?
    Scanned, mutation absent flux-system, longhorn-system, observability, velero Yes — all 33 workloads
    Workloads not scanned kube-system, kube-public, kube-node-lease, kubescape No — no verdict to un-suppress
    No namespace in prod local-path-storage, kubevirt, cdi, chaos-mesh No

    Not one of the 33 passes C-0211 today. Two gaps are universal:

    Field Missing on
    spec.template.spec.securityContext.fsGroupChangePolicy 33 / 33
    securityContext.seLinuxOptions (every container) 33 / 33
    pod-level fsGroup 20 / 33
    runAsGroup 18 / 33
    runAsNonRoot 12 / 33

    The row-level per-workload table is in the private operator notes rather than here — a complete
    per-workload hardening inventory of a live cluster is more than the sanitized minimum this repo
    publishes.

    Three premises in this issue did not survive measurement

    1. C-0211 is a twelve-rule control, not a three-field check. Its rules are rule-privilege-escalation,
      immutable-container-filesystem, non-root-containers, drop-capability-netraw, set-seLinuxOptions,
      set-seccomp-profile, set-procmount-default, set-fsgroup-value, set-fsgroupchangepolicy-value,
      set-sysctls-params, set-supplementalgroups-values, rule-allow-privilege-escalation. The stated
      field set (fsGroupChangePolicy, runAsUser, runAsGroup) omits fsGroup and seLinuxOptions,
      both of which fail widely — so sizing against it would have under-counted.

    2. node-agent and storage are not in the residual population. Both live in kubescape, whose
      workloads are excluded from scanning: neither has a workloadconfigurationscan object at all, so
      there is no verdict to un-suppress. Unscanned is not the same as passing, but it does mean narrowing
      cannot change their result. ⚠️ Do not read the 384 scan objects in that namespace as evidence it
      is scanned — they are cluster-scoped results (ClusterRoleBindings, ServiceAccounts, node and
      control-plane info) that Kubescape stores there, and none is a workload.

    3. The name is shared, the workload is not. There are two node-agent DaemonSets —
      kubescape/node-agent (not scanned) and velero/node-agent (in the population). An unqualified
      "node-agent" resolves to the wrong one.

    Decision — C-0211 stays cluster-wide

    Narrowing it today would surface all 33 with no fix behind any of them, which lowers the score to
    absorb a gap instead of closing it. The sizing and this decision now live in
    pod-security-mutations-unscoped.yaml itself, replacing the pointer that deferred to #3064.

    What I could NOT establish

    Corroboration for #3223

    Independently of that issue, this pass measured all six flux-system controllers
    (flux-operator, helm-controller, kustomize-controller, notification-controller,
    source-controller, tofu-controller) as missing runAsGroup today — so #3223's premise still holds
    on current state and has not been overtaken.

  4. moved this from 📥 Backlog to 🏃🏻‍♂️ In Progress in 🌊 Project Boardon Aug 19, 2026
  5. self-assigned this
    on Aug 29, 2026
  6. devantler commented on Aug 29, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Agentic Engineer

    Closing — every acceptance criterion is met, re-verified independently against live prod today

    I selected this as the oldest actionable Security issue and measured it from scratch against admin@prod before reading the existing record, so the confirmation below is independent rather than inherited.

    The four criteria

    Criterion State
    Every workload in the twelve excluded namespaces checked against C-0211's field set, result recorded met — the per-workload measurement is the 2026-08-19 comment above
    node-agent and storage each get an explicit decision met — both are resolved as outside the population, with the reason stated
    The measurement is re-run rather than inherited met — 2026-08-19, after this issue was filed, and again today
    The result is written where the next reader will find it met — pod-security-mutations-unscoped.yaml now carries the sizing inline; the stale #3064 pointer this issue was filed about is gone

    Independent reconciliation

    I enumerated pod-level securityContext across all twelve excluded namespaces on admin@prod and rolled pods up to their owning workload. Restricting to the four scanned namespaces (flux-system, longhorn-system, observability, velero) and excluding ephemeral Jobs gives exactly 33 workloads — matching the recorded 17 Deployments + 7 DaemonSets + 6 CronJobs + 3 StatefulSets. My live-pod view resolves some owners differently (a CronJob's pods carry a Job owner; Longhorn's carry InstanceManager/Cluster), which is why the kind split differs while the total does not.

    The universal gap reproduces: fsGroupChangePolicy is absent on every one of them. Across all six populated namespaces (127 workloads including the unscanned ones) it is absent on 127 of 127, with runAsGroup absent on 55 and runAsUser on 43 — each a strict subset, as recorded.

    The node-agent / storage decision, verified rather than assumed

    The record says both are outside the population because the kubescape namespace is unscanned, and warns against reading the scan objects sitting there as evidence it is scanned. Checked directly:

    • 1597 workloadconfigurationscans cluster-wide, 440 of them stored in kubescape.
    • Filtering those for a workload named node-agent or storage returns, in kubescape, only ClusterRoleBinding objects — cluster-scoped RBAC results, not workloads. There is no DaemonSet/node-agent and no Deployment/storage scan object.
    • The same filter returns velero/node-agent as a real DaemonSet scan object, confirming the name collision the record calls out is exactly that.

    So there is no verdict to un-suppress for either, and narrowing cannot change their result. Unscanned is not the same as passing — which is why they are documented rather than silently dropped.

    What this does not close

    C-0211 stays cluster-wide, and correctly so: narrowing it would surface all 33 with no fix behind them. That remains #2824, gated on the remediation work, and the fsGroupChangePolicy-absent-on-33-of-33 finding is the concrete shape of it. C-0013's own blocker is #3223, unaffected by this.

    Closing as completed. Claim retired and self-assignment removed.

  7. moved this from 🏃🏻‍♂️ In Progress to ✅ Done in 🌊 Project Boardon Aug 29, 2026
  8. removed their assignment
    on Aug 29, 2026
  9. added a commit that references this issue on Sep 20, 2026
    3cccb49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Fields

    No fields configured for Security.

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions