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
Establish an oracle that shows whether an in-cluster posture exception applies #2946
Kubescape posture exceptions in this platform have three consumer surfaces, and measurement on
2026-08-04 shows they behave differently:
Surface
Exceptions applied?
RBAC-subject controls evaluated?
CI manifest scan (ksail workload scan --exceptions)
yes — 71 SARIF results without the flag, 4 with it
no — C-0187/C-0053/C-0037/C-0188/C-0031/C-0063/C-0035 produce zero findings
In-cluster workloadconfigurationscansummaries
no — exempted longhorn-system/observability still read failed for C-0057, identical to non-exempted kyverno
yes — this is where those findings live
In-cluster compliance score / "excluding N controls"
claimed, never measured
yes
So every RBAC-anchored exception this repo maintains is judged today on a surface that cannot show
whether it works. k8s/bases/infrastructure/controllers/kubescape/cluster-role.yaml documents the
long fight to make the scanner read these CRs (image ≥ v4.0.10 plus supplemental RBAC), but nothing
currently reads back the result.
Affected audience and impact
Platform maintainers and any agent doing posture work. Without this oracle an exception cannot be
told from a no-op, in either direction: a correct exception looks broken, and an inert one looks
configured. #2823 stalled on exactly this — its planned migration of six controls rests on counts
that cannot move regardless of whether the exceptions work.
Expected behaviour
A repeatable, documented read of the surface where in-cluster posture exceptions actually apply, so
an exception change can be verified before and after.
Acceptance criteria
A documented, repeatable command reads the scanner's own exception-applied output (the
"excluding N controls" / compliance figure), not the raw stored summaries.
It is shown to discriminate: a deliberately removed or broken exception changes the number, and
restoring it changes it back. An oracle that reads the same either way is not an oracle.
The result settles whether the scanner matches exceptions on the kubescape.io/workload-kind
label or on the wlid kind — these disagree for every RBAC-subject finding.
controller-rbac.yaml's standing instruction ("migrate these if they surface failing objects")
is corrected to name the surface that actually decides it, so the next reader is not sent back
to the non-discriminating count.
Rough size
M. Blocks #2823 and any further posture-exception authoring.
#2316 - fix(kubescape): make ClusterSecurityException posture exceptions take effect [merged] #2440 - fix(security): make C-0002 exec-into-container exception effective for infra controllers [merged] #2442 - fix(kubescape): make C-0015 secret-list exceptions take effect [merged] #2446 - fix(kubescape): except C-0007 delete-capable RBAC by object, not namespace [merged] #2837 - fix(kubescape): stop the Headlamp mirror granting exceptions the CRs never granted [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!
Measurement: the planned oracle cannot be built on either in-cluster summary surface
Read-only measurement against the live cluster today. Two findings, both with their controls run.
1. The exempted controls appear on the workload surface in NO namespace — exempted or not
controller-rbac exempts C-0053, C-0037, C-0031, C-0063, C-0035, C-0188 for 21 namespaces
(longhorn-system among them). None of those six control IDs appears in a WorkloadConfigurationScanSummary in longhorn-system — and none appears in github-config or crossview either, which that exception does not cover.
So the absence in an exempted namespace is not evidence the exception applies. Enumerating every
control result in github-config (10 objects, 28 results) gives 11 distinct control IDs and none of
the six. This surface simply does not emit RBAC-subject controls, which makes AC-3's kubescape.io/workload-kind-vs-wlid question unanswerable here — there is no result to match
either way.
2. The two in-cluster surfaces disagree systematically
For longhorn-system: all 97WorkloadConfigurationScanSummary objects report critical/high/medium/low = 0/0/0/0, and every control result sampled reads passed. The namespace ConfigurationScanSummary for the same namespace reports high=104, medium=87, low=50.
That aggregate's summaryRef lists exactly those 97 objects and all 97 resolve in the list, so this
is the same object set read two ways. It reproduces: keda agg high=12 / per-workload sum 0; homepage 6 / 0; dex 4 / 0.
Consequence for this issue:ConfigurationScanSummary is the only surface carrying non-zero
numbers, but it holds severity totals plus refs and nothing else — no control ID, no per-object
verdict. So it cannot attribute a change to a specific exception, which is exactly what AC-2 requires
of an oracle. WorkloadConfigurationScanSummary can attribute, but reads zero everywhere.
Method note
kubectl get workloadconfigurationscansummariesstrips .spec.controls on LIST — all 97 came
back null. Control data requires a per-object GET. My first pass read those nulls as "0 failures for
the exempted controls", which looked like a clean confirmation; the positive control (a query that
should have found some failing control and found none either) is what exposed it. Any oracle built
here must GET per object, and must carry a control that fails when the oracle is broken.
What this leaves
AC-1 is not satisfiable from these two CRs. The remaining candidate is the scanner's own
compliance/"excluding N controls" output rather than the stored summaries — which is what the issue
suspected, now with the alternatives eliminated rather than assumed. I did not measure that surface:
reaching it needs a port-forward this host is not granted, so it is a named gap, not a guess.
Nothing changed on the cluster; this was read-only.
Measured: which surface applies exceptions, and why every prior oracle read the same either way
Read-only measurement against admin@prod today. Two API behaviours of the Kubescape storage
aggregated APIserver turn out to explain the non-discriminating reads this issue describes — and
both are invisible unless you run a negative control.
1. The store does not apply exceptions — confirmed, with a control group
infrastructure-privileged ignores C-0045/C-0048 (among others) in longhorn-system. kyverno is
not in that exception's namespace list, so it is the control group.
Namespace
Exempted for C-0045/C-0048?
Stored status
longhorn-system
yes
C-0045 failed, C-0048 failed
kyverno
no
all seven sampled controls passed
The exempted namespace reads failed; the non-exempted one reads passed. So the reads work, and
the stored workloadconfigurationscansummaries are the raw, pre-exception surface. This
confirms the table in the issue body.
Consistent with that, the scan trigger carries no exception argument at all — cm/kubescape-scheduler posts exactly {"commands":[{"CommandName":"kubescapeScan","args":{"scanV1":{"keepLocal":true}}}]}
to operator:4002/v1/triggerAction.
2. Two traps that make a naive oracle non-discriminating
Both were found by running a deliberately-wrong input and getting the same answer as the right one.
LIST silently trims spec.controls to null. All 97 summaries in longhorn-system return spec.controls: null from a list call; only an individual GET of one object populates it. A kubectl get wcss -o json | jq '.spec.controls…' pipeline therefore yields nothing regardless of
posture.
Label selectors are silently ignored. This is the more dangerous one:
kubectl get workloadconfigurationscansummaries -n longhorn-system -> 97
kubectl get workloadconfigurationscansummaries -n longhorn-system -l …/workload-kind=Deployment -> 97
kubectl get workloadconfigurationscansummaries -n longhorn-system -l …/workload-kind=NoSuchKindXYZ -> 97
A bogus selector returns the full set, identical to no selector. The objects carry the labels
correctly (longhorn-system really is 23 CSIStorageCapacity / 16 Secret / 6 Deployment / …), but
server-side selection never happens — so filtering must be done client-side on the returned objects.
Together these two produce exactly the symptom in the issue title: a query filtered by workload
label and read for control status returns an empty/uniform result whether or not the exception
works, which is the definition of a non-discriminating oracle.
What this implies for the oracle (criterion 2)
Because exceptions are applied by consumers rather than baked into the store, the discriminating
read does not require a prod deploy to ablate. The oracle can be built read-only and client-side:
raw finding from the store (individual GET, client-side filtering) × the exception set evaluated
against that workload. Ablating it then means perturbing the exception set locally and
re-evaluating the matcher, which is cheap and repeatable — a much better fit than the
change-an-exception-and-redeploy loop this issue was scoped around.
Criterion 3 (label vs wlid) — not settled yet
Still open. Note the finding above narrows it usefully: since server-side label selection does not
happen at all, any answer that relies on selecting by kubescape.io/workload-kind is measuring
nothing, and the question has to be settled on the matcher's own code path.
Not a defect: the 21-vs-19 exception count
Worth recording so the next reader does not re-derive it as drift. The cluster has 21 ClusterSecurityException CRs while the Headlamp mirror ConfigMap has 19. That gap is
deliberate — talos-cis-control-plane-false-positives and talos-cis-worker-false-positives both
carry platform.devantler.tech/headlamp-mirror: exclude, which scripts/generate-kubescape-exceptions honours. The mirror is generated, not hand-maintained.
Part of #2627; this also unblocks the measurement #2823 stalled on.
(Claim released at the end of this run — no code was written, so the issue is left unassigned and open for the next run to implement against the corrected scope in the follow-up comment.)
Criterion 1's premise does not hold — proposing a re-scope
Following the measurement above, I checked for the surface criterion 1 assumes ("the scanner's own
exception-applied output — the excluding N controls / compliance figure"). It does not exist in
this cluster.
No compliance-, framework-, posture- or report-shaped resource is served by any Kubescape API
group. The only posture surfaces are configurationscansummaries (a per-namespace severity
rollup, currently all zeros) and the raw per-workload workloadconfigurationscansummaries.
cm/ks-cloud-config has serviceDiscovery: false with local storage: true, and the scheduled
scan runs keepLocal: true. Nothing is posted to a backend that would compute a compliance score.
The "excluding N controls" figure is a Kubescape CLI/SaaS artifact, not something this
deployment produces. So criterion 1 is not merely unmet — as written it is unsatisfiable, and any
work item derived from it would be building toward a surface that is never going to appear.
Proposed re-scope (making the same guarantee reachable)
The intent — "an exception cannot be told from a no-op" — is right and still worth closing. What
changes is where the answer comes from. Given the store is provably pre-exception (measured above),
the oracle is a client-side evaluation, not a server-side read:
Raw finding per workload, obtained the only way that actually works: individual GET (LIST
trims spec.controls to null) with client-side filtering (server-side label selectors are
silently ignored).
The exception set, evaluated against that workload/control pair.
The oracle's answer = whether the pair is matched. Ablation = perturb the exception set locally
and re-evaluate — no prod deploy, no scan cycle, and it discriminates by construction.
This is strictly cheaper than the original framing and it removes the dependency on a redeploy to
prove discrimination, which is what made the original criterion 2 expensive.
I have not implemented this yet — this run's output is the measurement and this scoping
correction. Recording it now so the next run (or another instance) does not spend its time hunting
a compliance figure that is not there.
Two notes on sequencing, both honest rather than convenient:
GitHub Actions has been in a major outage for the duration of this run, so nothing could be
validated through CI in any case; the findings above are all direct read-only cluster
measurements with their own controls, which is why they stand independently of that.
Activity
coderabbitai commentedon Aug 4, 2026
🔗 Related PRs
#2316 - fix(kubescape): make ClusterSecurityException posture exceptions take effect [merged]
#2440 - fix(security): make C-0002 exec-into-container exception effective for infra controllers [merged]
#2442 - fix(kubescape): make C-0015 secret-list exceptions take effect [merged]
#2446 - fix(kubescape): except C-0007 delete-capable RBAC by object, not namespace [merged]
#2837 - fix(kubescape): stop the Headlamp mirror granting exceptions the CRs never granted [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 Aug 6, 2026
Measurement: the planned oracle cannot be built on either in-cluster summary surface
Read-only measurement against the live cluster today. Two findings, both with their controls run.
1. The exempted controls appear on the workload surface in NO namespace — exempted or not
controller-rbacexempts C-0053, C-0037, C-0031, C-0063, C-0035, C-0188 for 21 namespaces(
longhorn-systemamong them). None of those six control IDs appears in aWorkloadConfigurationScanSummaryinlonghorn-system— and none appears ingithub-configorcrossvieweither, which that exception does not cover.So the absence in an exempted namespace is not evidence the exception applies. Enumerating every
control result in
github-config(10 objects, 28 results) gives 11 distinct control IDs and none ofthe six. This surface simply does not emit RBAC-subject controls, which makes AC-3's
kubescape.io/workload-kind-vs-wlidquestion unanswerable here — there is no result to matcheither way.
2. The two in-cluster surfaces disagree systematically
For
longhorn-system: all 97WorkloadConfigurationScanSummaryobjects reportcritical/high/medium/low = 0/0/0/0, and every control result sampled readspassed. The namespaceConfigurationScanSummaryfor the same namespace reports high=104, medium=87, low=50.That aggregate's
summaryReflists exactly those 97 objects and all 97 resolve in the list, so thisis the same object set read two ways. It reproduces:
kedaagg high=12 / per-workload sum 0;homepage6 / 0;dex4 / 0.Consequence for this issue:
ConfigurationScanSummaryis the only surface carrying non-zeronumbers, but it holds severity totals plus refs and nothing else — no control ID, no per-object
verdict. So it cannot attribute a change to a specific exception, which is exactly what AC-2 requires
of an oracle.
WorkloadConfigurationScanSummarycan attribute, but reads zero everywhere.Method note
kubectl get workloadconfigurationscansummariesstrips.spec.controlson LIST — all 97 cameback
null. Control data requires a per-object GET. My first pass read those nulls as "0 failures forthe exempted controls", which looked like a clean confirmation; the positive control (a query that
should have found some failing control and found none either) is what exposed it. Any oracle built
here must GET per object, and must carry a control that fails when the oracle is broken.
What this leaves
AC-1 is not satisfiable from these two CRs. The remaining candidate is the scanner's own
compliance/"excluding N controls" output rather than the stored summaries — which is what the issue
suspected, now with the alternatives eliminated rather than assumed. I did not measure that surface:
reaching it needs a port-forward this host is not granted, so it is a named gap, not a guess.
Nothing changed on the cluster; this was read-only.
devantler commentedon Aug 6, 2026
Measured: which surface applies exceptions, and why every prior oracle read the same either way
Read-only measurement against
admin@prodtoday. Two API behaviours of the Kubescape storageaggregated APIserver turn out to explain the non-discriminating reads this issue describes — and
both are invisible unless you run a negative control.
1. The store does not apply exceptions — confirmed, with a control group
infrastructure-privilegedignores C-0045/C-0048 (among others) inlonghorn-system.kyvernoisnot in that exception's namespace list, so it is the control group.
longhorn-systemfailed, C-0048failedkyvernopassedThe exempted namespace reads
failed; the non-exempted one readspassed. So the reads work, andthe stored
workloadconfigurationscansummariesare the raw, pre-exception surface. Thisconfirms the table in the issue body.
Consistent with that, the scan trigger carries no exception argument at all —
cm/kubescape-schedulerposts exactly{"commands":[{"CommandName":"kubescapeScan","args":{"scanV1":{"keepLocal":true}}}]}to
operator:4002/v1/triggerAction.2. Two traps that make a naive oracle non-discriminating
Both were found by running a deliberately-wrong input and getting the same answer as the right one.
LIST silently trims
spec.controlstonull. All 97 summaries inlonghorn-systemreturnspec.controls: nullfrom a list call; only an individualGETof one object populates it. Akubectl get wcss -o json | jq '.spec.controls…'pipeline therefore yields nothing regardless ofposture.
Label selectors are silently ignored. This is the more dangerous one:
A bogus selector returns the full set, identical to no selector. The objects carry the labels
correctly (
longhorn-systemreally is 23 CSIStorageCapacity / 16 Secret / 6 Deployment / …), butserver-side selection never happens — so filtering must be done client-side on the returned objects.
Together these two produce exactly the symptom in the issue title: a query filtered by workload
label and read for control status returns an empty/uniform result whether or not the exception
works, which is the definition of a non-discriminating oracle.
What this implies for the oracle (criterion 2)
Because exceptions are applied by consumers rather than baked into the store, the discriminating
read does not require a prod deploy to ablate. The oracle can be built read-only and client-side:
raw finding from the store (individual
GET, client-side filtering) × the exception set evaluatedagainst that workload. Ablating it then means perturbing the exception set locally and
re-evaluating the matcher, which is cheap and repeatable — a much better fit than the
change-an-exception-and-redeploy loop this issue was scoped around.
Criterion 3 (label vs
wlid) — not settled yetStill open. Note the finding above narrows it usefully: since server-side label selection does not
happen at all, any answer that relies on selecting by
kubescape.io/workload-kindis measuringnothing, and the question has to be settled on the matcher's own code path.
Not a defect: the 21-vs-19 exception count
Worth recording so the next reader does not re-derive it as drift. The cluster has 21
ClusterSecurityExceptionCRs while the Headlamp mirror ConfigMap has 19. That gap isdeliberate —
talos-cis-control-plane-false-positivesandtalos-cis-worker-false-positivesbothcarry
platform.devantler.tech/headlamp-mirror: exclude, whichscripts/generate-kubescape-exceptionshonours. The mirror is generated, not hand-maintained.Part of #2627; this also unblocks the measurement #2823 stalled on.
(Claim released at the end of this run — no code was written, so the issue is left unassigned and open for the next run to implement against the corrected scope in the follow-up comment.)
devantler commentedon Aug 6, 2026
Criterion 1's premise does not hold — proposing a re-scope
Following the measurement above, I checked for the surface criterion 1 assumes ("the scanner's own
exception-applied output — the excluding N controls / compliance figure"). It does not exist in
this cluster.
group. The only posture surfaces are
configurationscansummaries(a per-namespace severityrollup, currently all zeros) and the raw per-workload
workloadconfigurationscansummaries.cm/ks-cloud-confighasserviceDiscovery: falsewith localstorage: true, and the scheduledscan runs
keepLocal: true. Nothing is posted to a backend that would compute a compliance score.The "excluding N controls" figure is a Kubescape CLI/SaaS artifact, not something this
deployment produces. So criterion 1 is not merely unmet — as written it is unsatisfiable, and any
work item derived from it would be building toward a surface that is never going to appear.
Proposed re-scope (making the same guarantee reachable)
The intent — "an exception cannot be told from a no-op" — is right and still worth closing. What
changes is where the answer comes from. Given the store is provably pre-exception (measured above),
the oracle is a client-side evaluation, not a server-side read:
GET(LISTtrims
spec.controlstonull) with client-side filtering (server-side label selectors aresilently ignored).
and re-evaluate — no prod deploy, no scan cycle, and it discriminates by construction.
This is strictly cheaper than the original framing and it removes the dependency on a redeploy to
prove discrimination, which is what made the original criterion 2 expensive.
I have not implemented this yet — this run's output is the measurement and this scoping
correction. Recording it now so the next run (or another instance) does not spend its time hunting
a compliance figure that is not there.
Two notes on sequencing, both honest rather than convenient:
specified rather than open-ended.
validated through CI in any case; the findings above are all direct read-only cluster
measurements with their own controls, which is why they stand independently of that.
chore: agent-claim #2946 nonce=139222d14c4f90b2612d54c2a78346f1