You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(kubescape): headlamp-exceptions ConfigMap has drifted from the ClusterSecurityException CRs #2586
The headlamp-exceptions ConfigMap (k8s/bases/infrastructure/controllers/kubescape/config-map-headlamp-exceptions.yaml) is a hand-maintained JSON mirror of the ClusterSecurityException CRs, and it has drifted badly:
4 CSEs are missing entirely from the mirror: kubescape-privileged, readonly-rootfs-pending, talos-cis-control-plane-false-positives, talos-cis-worker-false-positives.
Resource scopes diverge: delete-capable-rbac-by-design has 26 matchers in the CRs but 3 in the mirror; batch-workloads 2 vs 1; pod-security-mutations control counts differ (4 vs 5).
The Headlamp Kubescape plugin therefore shows a different (stale) exception set than the operator and CI actually apply — the UI mis-reports posture.
Proposed direction
Stop hand-maintaining the mirror: scripts/generate-kubescape-exceptions.py (added by #2584) already derives the exact PostureExceptionPolicy JSON from the CSE CRs. Either regenerate the ConfigMap's exceptionPolicies key from that output (with a CI drift check comparing the two), or — better long-term — drop the mirror once the Headlamp plugin can read the CRDs directly (upstream capability to verify). Reason strings may need light copy-editing for UI brevity, which the generator's output can carry as-is.
Rough size
S–M — one script/CI hookup plus a regenerated ConfigMap; the compare-and-fail drift gate is the part worth doing carefully.
#2316 - fix(kubescape): make ClusterSecurityException posture exceptions take effect [merged] #2320 - feat(kubescape): mirror ClusterSecurityException CRs as Headlamp plugin ConfigMap [merged] #2434 - fix(kubescape): except GitOps-managed CronJobs from C-0026 posture control [merged] #2440 - fix(security): make C-0002 exec-into-container exception effective for infra controllers [merged] #2446 - fix(kubescape): except C-0007 delete-capable RBAC by object, not namespace [merged] #2452 - fix(kubescape): bump posture scanner to v4.0.10 so exception CRs apply offline [merged] #2479 - fix(kubescape): anchor the remaining literal CSE name matchers [merged]
📝 Issue Planner
Check the box below or use the @coderabbitai plan command to generate an implementation plan and prompts that you can use with your favorite coding assistant.
Create Plan
🧪 Issue enrichment is currently in open beta.
You can configure auto-planning by selecting labels in the issue_enrichment configuration.
To disable automatic issue enrichment, add the following to your .coderabbit.yaml:
issue_enrichment:
auto_enrich:
enabled: false
💬 Have feedback or questions? Drop into our discord!
Two corrections to this issue's premises, both measured today
1. The Python generator is gone — that half is already done. This issue proposes reusing scripts/generate-kubescape-exceptions.py. There is no such file: it is now scripts/generate-kubescape-exceptions/ (main.go + main_test.go), and it is already wired into
CI, which runs go test ./scripts/generate-kubescape-exceptions and then generates the exceptions
file for the offline scan. So the tooling this issue wanted to build on exists, in Go, tested.
Worth noting the CI-side goal is met by that generator: CI and the cluster derive their exception set
from the same CRs. This issue's remaining scope is only the Headlamp presentation mirror.
2. "Just delete the mirror" is not currently supported by evidence. The ConfigMap's own header
says it becomes redundant once the scanner is ≥ v4.0.10, because the operator then applies the CRs at
scan time and writes status=passed / subStatus=w-exceptions into the stored summaries for the
plugin to render.
The deployed scanner is v4.0.10. But a complete sweep of all 2200 workloadconfigurationscansummaries, fetched by name, found no object anywhere carrying subStatus: w-exceptions — and separately, objects named explicitly in a kind+name exception CR are
still reported as failing (evidence on platform#2823).
So the version threshold is met while the behaviour it was supposed to unlock is not observable. On
this evidence the mirror is not redundant, and deleting it would take the plugin's exception view
back to "No data to be shown".
Suggested resequencing
This issue is now blocked on the same question as #2823: whether ClusterSecurityException CRs are
being consumed for posture at all. That answer decides which of this issue's two options is even
available:
CRs are consumed → the mirror can eventually be deleted, and the interim fix is regeneration
plus a CI drift gate.
CRs are not consumed → the mirror is the only thing making the plugin's exception view correct,
and generating it from the CRs (which the Go tool already knows how to do) becomes the durable fix
rather than a stopgap.
Either way the hand-maintenance should end; only the endpoint is in question.
Also worth re-measuring before acting: the specific drift figures in the issue body are from
2026-07-11 and several CSE files have changed since, so the "4 missing / 26 vs 3 matchers" numbers
should be re-derived rather than trusted.
Drift re-measured today, and the first slice is shipped as #2837
The 2026-07-11 figures in the body were re-derived against the current tree. Comparing the mirror to go run ./scripts/generate-kubescape-exceptions output:
Claim in the body
Measured today
4 CSEs missing
2 genuinely missing (kubescape-privileged, readonly-rootfs-pending). The two talos-cis-* CRs are a deliberate exclusion documented in the file header — the plugin only evaluates workload posture and those controls never attach to a workload.
delete-capable-rbac-by-design 26 vs 3 matchers
Not drift. The mirror alternation-collapses the same 26 names (19 ClusterRoleBinding + 7 RoleBinding). It did carry one extra kind: ^Group$ / ^crossplane-masters$ resource with no CR counterpart — transcribed from a trailing comment in delete-rbac.yaml.
batch-workloads 2 vs 1
Not drift — ^(Job|CronJob)$ is one alternation of the generator's two entries.
pod-security-mutations 4 vs 5 controls
Real, and the security-relevant one. The mirror added ^C-0017$ under a namespace: ".*" resource, suppressing C-0017 cluster-wide in the dashboard while the CRs except it only in three scoped places.
#2837 fixes the faithfulness half. Control sets now match the CRs exactly across all 19 shared policies.
Notes for the remaining scope (generate + CI drift gate)
Two findings that cost real time to establish — please do not re-derive them:
1. There is NO structural discriminator for the host/CIS exclusion. The obvious candidate — "exclude
policies whose resources are a bare wildcard" — is refuted: nine generated policies carry a bare
wildcard, including mirrored ones (admission-controllers, cilium-network-policies, helm-chart-metadata, image-verification, pod-security-mutations, upstream-chart-defaults, vpa-managed-resources). The talos-cis-* CRs omit spec.matchfor a documented reason (host
findings are not namespaced), and the generator renders that as kind: ".*" — correct for the offline
CI scan, wrong for a workload-presentation mirror. So the exclusion is an editorial judgement and needs
to be marked on the CRs (an annotation) rather than inferred.
2. The mirror and the generator use different but equivalent forms, so a byte-comparison drift gate
would fire on day one: the mirror alternation-collapses resource lists, and expresses "everything" as namespace: ".*" where the generator emits kind: ".*". A gate therefore needs either a semantic
comparison, or a decision to let the generator's canonical form overwrite the file (simpler and more
robust, at the cost of a larger generated file).
Live-state note
Read-only check against prod: the three C-0017 scopes in the CRs are doing real work — opencost/deployment-opencost{,-primary} and wedding-app/deployment-wedding-app genuinely fail
C-0017, while all 14 sampled workloads outside the excepted scopes pass it. So the wildcard was not
hiding anything today; it was a latent suppression of any future regression.
Activity
coderabbitai commentedon Jul 11, 2026
🔗 Related PRs
#2316 - fix(kubescape): make ClusterSecurityException posture exceptions take effect [merged]
#2320 - feat(kubescape): mirror ClusterSecurityException CRs as Headlamp plugin ConfigMap [merged]
#2434 - fix(kubescape): except GitOps-managed CronJobs from C-0026 posture control [merged]
#2440 - fix(security): make C-0002 exec-into-container exception effective for infra controllers [merged]
#2446 - fix(kubescape): except C-0007 delete-capable RBAC by object, not namespace [merged]
#2452 - fix(kubescape): bump posture scanner to v4.0.10 so exception CRs apply offline [merged]
#2479 - fix(kubescape): anchor the remaining literal CSE name matchers [merged]
📝 Issue Planner
Check the box below or use the
@coderabbitai plancommand to generate an implementation plan and prompts that you can use with your favorite coding assistant.🧪 Issue enrichment is currently in open beta.
You can configure auto-planning by selecting labels in the issue_enrichment configuration.
To disable automatic issue enrichment, add the following to your
.coderabbit.yaml:💬 Have feedback or questions? Drop into our discord!
devantler commentedon Jul 27, 2026
Two corrections to this issue's premises, both measured today
1. The Python generator is gone — that half is already done. This issue proposes reusing
scripts/generate-kubescape-exceptions.py. There is no such file: it is nowscripts/generate-kubescape-exceptions/(main.go+main_test.go), and it is already wired intoCI, which runs
go test ./scripts/generate-kubescape-exceptionsand then generates the exceptionsfile for the offline scan. So the tooling this issue wanted to build on exists, in Go, tested.
Worth noting the CI-side goal is met by that generator: CI and the cluster derive their exception set
from the same CRs. This issue's remaining scope is only the Headlamp presentation mirror.
2. "Just delete the mirror" is not currently supported by evidence. The ConfigMap's own header
says it becomes redundant once the scanner is ≥ v4.0.10, because the operator then applies the CRs at
scan time and writes
status=passed / subStatus=w-exceptionsinto the stored summaries for theplugin to render.
The deployed scanner is v4.0.10. But a complete sweep of all 2200
workloadconfigurationscansummaries, fetched by name, found no object anywhere carryingsubStatus: w-exceptions— and separately, objects named explicitly in a kind+name exception CR arestill reported as failing (evidence on platform#2823).
So the version threshold is met while the behaviour it was supposed to unlock is not observable. On
this evidence the mirror is not redundant, and deleting it would take the plugin's exception view
back to "No data to be shown".
Suggested resequencing
This issue is now blocked on the same question as #2823: whether
ClusterSecurityExceptionCRs arebeing consumed for posture at all. That answer decides which of this issue's two options is even
available:
plus a CI drift gate.
and generating it from the CRs (which the Go tool already knows how to do) becomes the durable fix
rather than a stopgap.
Either way the hand-maintenance should end; only the endpoint is in question.
Also worth re-measuring before acting: the specific drift figures in the issue body are from
2026-07-11 and several CSE files have changed since, so the "4 missing / 26 vs 3 matchers" numbers
should be re-derived rather than trusted.
devantler commentedon Jul 28, 2026
Drift re-measured today, and the first slice is shipped as #2837
The 2026-07-11 figures in the body were re-derived against the current tree. Comparing the mirror to
go run ./scripts/generate-kubescape-exceptionsoutput:kubescape-privileged,readonly-rootfs-pending). The twotalos-cis-*CRs are a deliberate exclusion documented in the file header — the plugin only evaluates workload posture and those controls never attach to a workload.delete-capable-rbac-by-design26 vs 3 matcherskind: ^Group$/^crossplane-masters$resource with no CR counterpart — transcribed from a trailing comment indelete-rbac.yaml.batch-workloads2 vs 1^(Job|CronJob)$is one alternation of the generator's two entries.pod-security-mutations4 vs 5 controls^C-0017$under anamespace: ".*"resource, suppressing C-0017 cluster-wide in the dashboard while the CRs except it only in three scoped places.#2837 fixes the faithfulness half. Control sets now match the CRs exactly across all 19 shared policies.
Notes for the remaining scope (generate + CI drift gate)
Two findings that cost real time to establish — please do not re-derive them:
1. There is NO structural discriminator for the host/CIS exclusion. The obvious candidate — "exclude
policies whose resources are a bare wildcard" — is refuted: nine generated policies carry a bare
wildcard, including mirrored ones (
admission-controllers,cilium-network-policies,helm-chart-metadata,image-verification,pod-security-mutations,upstream-chart-defaults,vpa-managed-resources). Thetalos-cis-*CRs omitspec.matchfor a documented reason (hostfindings are not namespaced), and the generator renders that as
kind: ".*"— correct for the offlineCI scan, wrong for a workload-presentation mirror. So the exclusion is an editorial judgement and needs
to be marked on the CRs (an annotation) rather than inferred.
2. The mirror and the generator use different but equivalent forms, so a byte-comparison drift gate
would fire on day one: the mirror alternation-collapses resource lists, and expresses "everything" as
namespace: ".*"where the generator emitskind: ".*". A gate therefore needs either a semanticcomparison, or a decision to let the generator's canonical form overwrite the file (simpler and more
robust, at the cost of a larger generated file).
Live-state note
Read-only check against prod: the three C-0017 scopes in the CRs are doing real work —
opencost/deployment-opencost{,-primary}andwedding-app/deployment-wedding-appgenuinely failC-0017, while all 14 sampled workloads outside the excepted scopes pass it. So the wildcard was not
hiding anything today; it was a latent suppression of any future regression.
fix(kubescape): generate the Headlamp exception mirror from the CRs