Repository navigation
Six posture exceptions in controller-rbac.yaml are structurally inert, and the migrated four have not gone to zeroΒ #2823
Description
π€ Generated by the Agentic Engineer
β οΈ STATUS 2026-08-15 β read this before doing anything belowStep 1 is ANSWERED. Do not re-run it. The measurement is
in this comment:
on the CI scan β the only surface that applies these exceptions β thecontroller-rbacCR
suppresses 17 findings (C-0053 Γ13, C-0037 Γ3, C-0031 Γ1), and C-0053's residual 10 are
100% out-of-scope (tenant namespaces absent from the selector). By this issue's own decision
rule, the mechanism works.The sections below are preserved as dated evidence, and two of their conclusions are refuted.
The title's "structurally inert" is wrong for three of the six controls, and every count in
Problem and What is NOT yet established was taken from the kubescape-operator's stored scan
resources β which apply no exceptions at all (appliedIgnoreRulesis empty, recorded in
ci.yamlsince #2969). A non-zero count there is not evidence about an exception, so those tables
cannot answer the question this issue asks.This banner exists because the original Direction sent 15 separate runs over 19 days to
re-measure the same refuted premise, twice ending in public self-correction. Measure exception
effect only by diffing twoksail workload scan --exceptionsruns.
Problem
k8s/bases/infrastructure/cluster-security-exceptions/controller-rbac.yaml suppresses six controls through a match.namespaceSelector. That file already documents why such a selector cannot suppress an RBAC-anchored finding, and says to migrate the six "if they surface failing objects".
They do β all six. Measured 2026-07-27 against admin@prod:
| Control | Name | Failing objects | Anchored on an RBAC object | Cluster-scoped (empty namespace) |
|---|---|---|---|---|
| C-0053 | Access container service account | 155 | 100% | 103 |
| C-0037 | CoreDNS poisoning | 41 | 100% | 30 |
| C-0188 | CIS-5.1.4 minimize access to create pods | 20 | 100% | 13 |
| C-0031 | Delete Kubernetes events | 18 | 100% | 14 |
| C-0063 | Portforwarding privileges | 9 | 100% | 5 |
| C-0035 | Access restriction to infrastructure β admin access | 6 | 100% | 5 |
249 (control, object) pairs over 156 unique objects. Zero are ordinary namespaced workloads; every one carries kubescape.io/rbac-resource: "true". So the namespaceSelector is not merely imprecise on these β it can never match any of them, and all six exceptions are inert while appearing configured.
(Object names are deliberately not listed. An enumeration of which bindings can reach service-account tokens or create pods is a weakness map; the aggregate counts and the anchor analysis are the sanitized minimum needed to review a fix.)
The trap in the data
The summaries are labelled kubescape.io/workload-kind: ServiceAccount with a populated workload-namespace, which makes a namespaceSelector look viable. But matching keys on the kubescape.io/wlid annotation, which reads wlid://cluster-prod/namespace-/clusterrolebinding-<name> β binding kind, empty namespace. Any migration must name ClusterRoleBinding/RoleBinding kinds, not ServiceAccount.
Also note for anyone re-measuring: the aggregated storage API returns spec-stripped objects on LIST β controls reads null and severities read all-zero across every item. Those numbers are only real when each object is fetched by name. A LIST-based sweep here reports a clean cluster.
Historical evidence β NOT a current instruction (see STATUS above)
The four controls already migrated to explicit kind+name match.resources CRs have not gone to zero:
| Migrated control | Still failing |
|---|---|
| C-0015 (list secrets) | 70 |
| C-0007 (delete-capable roles) | 42 |
| C-0002 (exec into container) | 9 |
| C-0187 (wildcard RBAC) | 6 |
They are anchored the same way. This is not evidence the migration approach is broken β the residue may simply fall outside those CRs' named scope, which would be correct behaviour. But it does mean "migrate to kind+name" is currently an unproven remediation, and migrating six more controls on that assumption risks writing six more exceptions that also do not take effect.
Direction β what actually remains
Step 1 is done; the remaining work is narrow and does not need another measurement of the operator CRDs.
Answer the in-scope/out-of-scope questionβ done, see STATUS above.- Correct
controller-rbac.yaml's guidance so it stops advising migration of three working exceptions. Shipped in docs(kubescape): correct the controller-rbac migration guidanceΒ #3142. - Decide C-0063 / C-0035 / C-0188 on evidence. They produce zero findings in both arms, so they are unfalsifiable on the gating surface, not proven inert. Either keep them with that stated, or remove them β but record the basis.
- If any control is migrated, re-measure with the paired-arm oracle and record before/after.
Acceptance criteria
- The in-scope/out-of-scope question is answered for at least one migrated control, with evidence
-
controller-rbac.yaml's guidance reflects the measured per-control effect (docs(kubescape): correct the controller-rbac migration guidanceΒ #3142) - C-0063 / C-0035 / C-0188 each carry a recorded keep-or-remove decision with its basis
- Any before/after counts are measured with the two-arm CI-scan oracle, never off the operator CRDs
Rough size: S β what is left is a decision to record, not an investigation.
Evidence freshness
Scanner proven alive rather than assumed: kubescape v4.0.10, scheduler CronJob last job Complete, newest summary objects written 2026-07-27T10:08β10:09Z, aggregate scan summary non-zero (212 high / 840 medium / 89 low), and 2206/2206 objects returned populated control maps on direct GET.
Related
- Remediate over-privileged secret readers C-0015 leaves flaggedΒ #2441 β raised this as a closing note; this issue is that note measured.
Metadata
Metadata
Assignees
Labels
Type
Fields
Projects
- StatusShow more project fieldsβ Done
Activity
coderabbitai commentedon Jul 27, 2026
π Related PRs
#3037 - fix(cilium): refuse a zero-count gate release and correct the runbook order [merged]
#3085 - security(kubescape): except the operator-bundle RBAC the CI scan actually sees [merged]
#3086 - security(minio): read the root credential from a file instead of the environment [merged]
#3113 - fix(coredns): disposition CKV_K8S_11 as a stored-spec blind spot [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
Step 1 is done, and the result is NEGATIVE β kind+name matching is not working either
I ran the check this issue proposed as its first step: sweep all 2200 summaries by name, list every
object still failing C-0187, and ask whether each is inside or outside
wildcard-rbac-by-design's named scope.All six failures, with their
wlidanchors:clusterrolebinding-cluster-reconciler-flux-system(kustomize-controller)clusterrolebinding-cluster-reconciler-flux-system(helm-controller)clusterrolebinding-flux-operatorclusterrolebinding-velero-serverrolebinding-velero-serverkind: Role, the finding anchors on the RoleBindingclusterrolebinding-longhorn-support-bundleThe CR declares four matchers. Five of the six failures are on objects it names, three of them by
exact kind and exact name (
ClusterRoleBinding/cluster-reconciler-flux-system,flux-operator,velero-server). They are still failing.So the residue is not out-of-scope. It is almost entirely in-scope. That falsifies the assumption
behind this issue's proposed direction: migrating the other six controls to explicit kind+name
match.resourceswould not have suppressed anything, because that mechanism is not suppressinganything today.
Corroborating signal
No object in the sweep carries
subStatus: w-exceptionsβ the marker the operator is supposed towrite when it applies an exception at scan time. Combined with the above, the likeliest reading is
that
ClusterSecurityExceptionCRs are not being applied at scan time at all, rather than thatthe match syntax is subtly wrong. Scanner is v4.0.10 and provably alive (scan completed, 2206/2206
objects carry populated control maps, aggregate non-zero).
The one genuinely suspicious matcher is the fifth row: the CR names
kind: Roleforvelero-server, but the finding anchors on the RoleBinding of that name. Worth fixing regardless,but it cannot explain the three exact ClusterRoleBinding matches.
What this changes
The direction in the issue body is superseded. Migrating six more controls is now the wrong next
step β it would produce six more exceptions that do not take effect, which is exactly the state this
issue was opened to end.
Revised order:
consume
ClusterSecurityExceptionfor posture at all; the matcher schema differs from what theseCRs use; or exceptions are applied at a layer these stored summaries bypass.
RolevsRoleBindingmatcher forvelero-serverβ cheap, and correct either way.Knock-on for #2586
That issue asks whether the hand-maintained
headlamp-exceptionsConfigMap can simply be deleted,on the reasoning that with the scanner at β₯ v4.0.10 the operator applies the CRs at scan time and the
plugin renders the result. The absence of
w-exceptionsanywhere is evidence against thatpremise. The mirror should not be deleted on the strength of the version alone.
devantler commentedon Aug 4, 2026
Ran step 1 (the in-scope/out-of-scope question for a migrated control). The answer is that the
question cannot be answered from the surface this issue measures β and that finding invalidates the
evidence base for both halves of the issue, so it needs recording before any migration proceeds.
First, the method reproduces
Measured against
admin@prodtoday, RBAC-anchored objects only, per-object GET as this issueinstructs (scanner proven alive first: 2164 summaries, scheduler job Complete 4h ago, kubescape +
node-agent pods Running):
So the numbers are reproducible and my read of the data matches yours.
The decisive finding:
workloadconfigurationscansummariesis a PRE-exception surfaceNo
ClusterSecurityExceptionis reflected in it β neither anamespaceSelectorone nor a kind+nameone. The discriminating control:
infrastructure-privilegedsuppresses C-0057 via anamespaceSelectornaminglonghorn-systemandobservability. C-0057 is evaluated on namespaced workload kinds, so unlike the RBAC controlsthat selector can match here β this is the case where a working exception must show. It does not:
longhorn-systemobservabilitykyvernoThe exempted namespaces still carry
failed. Stored summaries also expose onlypassed/failedβthere is no exception/skipped state in the schema at all.
Consequence: "the migrated four have not gone to zero" is expected, not evidence that
kind+name migration is broken. Nothing anyone writes in a CSE CR can move a count on this surface.
By the same token, this surface cannot show that the six
controller-rbac.yamlentries are inerteither β a working and a broken exception look identical here. The structural argument about a
namespaceSelectornever matching an empty-namespacewlidmay still hold, but the counts in thisissue do not demonstrate it.
A second, independent finding β and it inverts this issue's "trap in the data" note
C-0187 is evaluated only on subject kinds, never on the RBAC object:
The issue says "any migration must name
ClusterRoleBinding/RoleBindingkinds, notServiceAccount", reasoning from thewlidstring. But thewlidand theworkload-kindlabeldisagree: the failing objects carry
wlid://β¦/clusterrolebinding-<name>while theirkubescape.io/workload-kindisServiceAccount.wildcard-rbac.yamlalready namesClusterRoleBinding+Roleby exact name β the kinds that never carry this control at all.Which of those two fields the scanner matches an exception against is now the open question, and
it decides how all six should be written. I did not guess it.
Revised direction
Step 1 as written cannot be executed. The replacement first step is to establish an oracle where
exceptions demonstrably bite before authoring any more of them:
(
scripts/generate-kubescape-exceptionsβksail workload scan --exceptions β¦), which is wherethis repo already has evidence exceptions apply β the β98.9% floor is measured with them.
workload-kindor on thewlidkind.I have deliberately not touched
controller-rbac.yamlthis run: changing six exceptions whilethe matching semantics are unknown is how six more inert entries get written, which is the exact
failure this issue exists to prevent.
(Object names withheld as before β an enumeration of which subjects hold wildcard or token-reaching
grants is a weakness map. Aggregate counts and the kind analysis are the sanitized minimum.)
devantler commentedon Aug 4, 2026
Followed my own recommendation and built the oracle. It works β and it completes the answer, because
it shows the two surfaces evaluate different object populations, not just apply exceptions
differently.
The oracle discriminates
Ran the CI path locally (static, no cluster), identical command both times except the
--exceptionsflag:
--exceptions--exceptionsSo exceptions demonstrably bite on the CI surface β 71 β 4 from one flag. That is the oracle this
issue needs, and it is cheap and offline.
But C-0187 is absent from that surface entirely
Per-control breakdown of the unexcepted run β the population before anything is suppressed:
C-0187 does not appear at all, and neither do C-0053 / C-0037 / C-0188 / C-0031 / C-0063 /
C-0035 β the six this issue is about. A manifest scan has no live subjectβrole bindings to flag; the
ServiceAccount Γ ClusterRole Γ ClusterRoleBindingtriples that carry these findings only exist in arunning cluster.
What that means β there are three surfaces, and the six controls appear on only one
ksail workload scan)longhorn-system/observabilitycontrol in my previous commentSo the six controls are only ever evaluated on the live cluster, and the only surface that could
show an exception working on them is the third one β which is precisely the one nobody has measured.
wildcard-rbac-by-designsuppresses nothing in CI because there is nothing there to suppress; thatis not evidence it is broken, and my previous comment's kind analysis is not evidence it is correct
either. Both remain open against surface 3.
Revised acceptance criteria I'd propose
confirm it reflects the CRs at all. This is the gating step; nothing else can be judged
without it.
workload-kind(
ServiceAccount) or on thewlidkind (ClusterRoleBinding) β they disagree for every oneof these findings.
controller-rbac.yamlunchanged until the above resolves.One observation, flagged not claimed
The excepted run printed
All controls passed. No issues foundwhile its SARIF carried 4 results(3Γ C-0034, 1Γ C-0017, both low). That is probably a severity threshold in the CLI summary rather
than a gate defect β the gate reads the compliance score, not this line β but a run that prints
"no issues" over four real findings is worth a second look by someone who knows the scoring, so I am
recording it rather than acting on it.
Still no change to
controller-rbac.yamlthis run, for the reason in my previous comment.devantler commentedon Aug 5, 2026
Step 1 measured (2026-08-05,
admin@prod) β with a method correction and one honest caveatSelecting the RBAC objects by the
kubescape.io/workload-kindlabel finds 201 objects. Selecting them by thekubescape.io/wlidannotation β as this issue's "trap in the data" section says to β finds 326, a strict superset (overlap is exactly 201). The 125 extra are the ones labelledworkload-kind: ServiceAccountwhose wlid isβ¦/clusterrolebinding-<name>.I ran the label-based sweep first and it reported one failing pair across the whole set. That is a near-perfect false all-clear, and it is only distinguishable from a genuinely clean cluster by running the wlid-based sweep next to it. Anyone re-measuring: filter on the wlid, never on the kind label.
Two further gotchas confirmed:
spec.controlsisnullon LIST (control status is real only on a GET by name), and the aggregated API silently ignores-llabel selectors β-l kubescape.io/rbac-resource=truereturned all 1542 objects, including ones carrying no such label.Current counts (wlid-selected, GET-by-name, 326 objects)
344 failed (control, object) pairs over 97 unique objects. All ten controls have moved down since 2026-07-27:
The C-0187 residue, classified against the CR's named scope
wildcard-rbac.yamlnames four objects. The three still-failing objects fall out as:clusterrolebinding-cluster-reconciler-flux-system^cluster-reconciler-flux-system$, kindClusterRoleBindingclusterrolebinding-flux-operator^flux-operator$, kindClusterRoleBindingrolebinding-velero-server(nsvelero)Rolevelero-server, but the failing object is aRoleBindingπ΄ Why this is NOT yet the decisive answer this issue asked for
The issue's decision rule is "in-scope residue means the mechanism does not work". Two objects are in scope, which points that way β but I could not confirm the premise it rests on, so I am not calling the mechanism broken yet.
Across a 300-control sample the only statuses
workloadconfigurationscansever reports arefailedandpassedβ there is noignored/exceptedstate in this resource at all. So a rawfailedhere is consistent with both explanations: the exception is inert, or the exception is applied when the posture summary/report is computed and this resource simply records the pre-exception scan. Those two have opposite consequences for the migration, and nothing measured so far separates them.The next concrete step
Determine which layer applies a
ClusterSecurityExceptionβ compare an in-scope object's status inworkloadconfigurationscansagainst its representation in the posture summary/report layer. If the summary also still counts it, the mechanism is inert and the six-control migration must not proceed on this design. If the summary drops it, the mechanism works, this resource is just the wrong place to measure, and the real gap is the kind mismatch below.One finding that stands either way
wildcard-rbac.yamlexemptsRolevelero-server, but the object failing C-0187 in namespacevelerois aRoleBinding. Whichever layer applies exceptions, that entry cannot match the object that is actually failing β a kind mismatch of exactly the class this issue's "trap in the data" section warns about.Releasing my assignment β this is a clean hand-off point, and nothing here is blocked.
Reproduction
devantler commentedon Aug 5, 2026
The inertness is not confined to the namespaceSelector/RBAC mismatch
This issue's diagnosis is that the six controls are inert because a
namespaceSelectorcannot match a cluster-scoped RBAC object. That mismatch is real. But it is not sufficient to
explain the data, and a fix built only on it may not change the numbers.
Control (measured 2026-08-05,
admin@prod).readonly-rootfs-pendingignores C-0017for namespaces
opencostandwedding-app. That is the easy case in every respect: anordinary namespaced
Deployment, matched by namespace, no RBAC anchoring, no kind mismatch.workloadconfigurationscans/deployment-opencostβ C-0017status=failedworkloadconfigurationscansummaries/deployment-opencostβ C-0017status=failedA correctly-scoped exception on a correctly-shaped object is equally inert.
The scan result has a dedicated slot for this, and it is empty everywhere
Each rule entry carries an
appliedIgnoreRulesfield β the mechanism by which an appliedexception would be recorded:
Swept across 9 exception-covered namespaces, batching by name:
appliedIgnoreRulessetThe zero is ablation-verified, not assumed: the same jq expression run against a synthetic
object carrying
appliedIgnoreRules: ["synthetic-rule"]returnsnn=1, so it does detect a setvalue when one exists.
Two corrections to earlier notes on this issue
passed/failed/skippedβ the earliernote that only
passed/failedexist is wrong. Butskippedis not an exception state:every instance found carries
subStatus=configuration,info="Control configurations are empty"(e.g. C-0078, C-0236). Exceptions andskippedare unrelated.spec.controls; a multi-name GET does not. The same objectreturns 0 controls via
get workloadconfigurationscans -o jsonand 50 viaget workloadconfigurationscans <name1> <name2> -o json. Any sweep built on the collectionLIST silently measures nothing β worth knowing before re-deriving counts here.
Deployment context
ks-cloud-configreads"keepLocal": true(no external backend consumes these results) and"riskAcceptance": true(the feature is configured on). WithkeepLocal, these CRDs are theonly observable consumer in this deployment β there is no higher posture layer here that could
be applying the exceptions out of view.
What this does and does not establish
Established: no in-cluster surface reflects any
ClusterSecurityException, including forexceptions whose scope matches correctly. Every count taken over these CRDs β including this
issue's 249 pairs / 156 objects β therefore counts exception-covered findings as genuine
failures, and is an over-count relative to intended posture.
Not established: whether the deployed operator consumes the
kubescape.io/v1beta1 ClusterSecurityExceptionkind at all. Aheadlamp-exceptionsConfigMap (3 keys) also existsin
kubescape, which hints the consumption path may not be the CRD. Distinguishing"consumed but not recorded" from "not consumed" is the next step, and it decides the fix:
ClusterRoleBinding/RoleBindingkinds will not move any number, and this issue's premise needs reframing;
used to verify it β which changes how "have the migrated four gone to zero?" can be answered
at all.
I have not claimed this issue. Recording the measurement so whoever picks it up does not
build the selector fix on an unverified premise.
devantler commentedon Aug 5, 2026
Closing the open question above: there are three surfaces, and only one of them ignores the exceptions
My previous comment left it open whether the exceptions are "consumed but not recorded" or "not
consumed". Resolved β and the answer is neither, exactly:
.github/workflows/ci.yaml:431-432βgenerate-kubescape-exceptionsconvertscluster-security-exceptions/to Kubescape's native format, passed asksail workload scan --exceptionskubescape/headlamp-exceptionsConfigMap is populated βexceptionPoliciesis 34043 bytes and contains C-0017workloadconfigurationscans,β¦summaries,configurationscansummaries)appliedIgnoreRulesnull across 1448 rule entries / 225 objects / 9 namespaces (ablation-verified); correctly-scoped C-0017 stillfailedSo the exceptions are not inert β the two surfaces a human or CI actually looks at do apply them.
What is wrong is the assumption that the operator CRDs reflect them.
ci.yaml:399-404states the exceptions directory is "the single source of truth, also consumedin-cluster by the kubescape-operator". That parenthetical is the inaccurate part: whatever the
operator does with the CRD, it does not surface in any of its three stored scan resources.
What this changes here
This issue's counts β 249 (control, object) pairs over 156 unique objects β were taken from the
operator CRDs, so they are pre-exception figures. That does not weaken the finding about the
namespaceSelector/RBAC-kind mismatch, which is a static property of the YAML and stands on its own.It does mean two things:
that surface, because the number there will not move when an exception starts matching β it never
reflected exceptions in the first place. Verify against the CI scan output instead, which is
the surface that applies them and already gates on
--compliance-threshold 95.a post-exception CI figure will show a difference produced entirely by the surface change.
Correcting my own earlier framing: I wrote that these counts are "an over-count relative to intended
posture". Accurate as far as it goes, but the useful statement is narrower β they are the
pre-exception view, and the exception-aware view already exists in CI.
21 remaining items
devantler commentedon Aug 11, 2026
The C-0007 residue is diagnosed β the exception names the wrong object population
The 08-10 differential left exactly one survivor of this issue's original suspicion:
delete-rbacmoved C-0007 by 0. That is now root-caused, and it is not a defect in the kind+name mechanism.
There are two disjoint object populations, and an exception only suppresses in the one it names.
delete-rbac.yaml's 26 entries were derived from a live cluster scan, so they name the bindingsthe operators create at runtime. The CI gate runs
ksail workload scanover the renderedmanifests, where those runtime bindings do not exist at all. Measured against the scanned resource
set (ksail 7.178.20,
--framework nsa,mitre):delete-rbac.yamlcdi-sa,kubevirt-apiserver,kubevirt-controller,cloudnative-pg,longhorn-bind,velero-server,flaggerβ¦And what does fail C-0007 there is each vendored operator bundle's own RBAC β four resources,
named by none of the 26 entries: the
cdi-operatorClusterRoleBinding and RoleBinding, thekubevirt-operatorClusterRoleBinding, and thekubevirt-operator-rolebindingRoleBinding.The control that proves the mechanism, not just the correlation
Ablating
exec-into-container-rbac(the CR that demonstrably works) leaves exactly one C-0007sibling finding β
ClusterRoleBinding/kubevirt-operator. That CR has seven entries; only that one ismanifest-visible, and it is the entire C-0002 delta. So the working CR and the non-working CR differ
in precisely one property: whether any entry names an object the scanner can see.
delete-rbacisnot broken β it is aimed at a population this surface does not contain.
Fix
#3085 adds the four operator-bundle objects. C-0007 goes 4 β 0 and the combined score 97 β 98.
All four carry genuine
deleteverbs from the pinned upstream bundles (the CDI cluster role alone on12 rule blocks, the KubeVirt one on 50), so they are the by-design class this CR exists to except, and
C-0007 still flags anything new.
The live-derived entries are deliberately left in place. They are correct for the cluster surface,
and removing them would be a posture change resting on the operator's stored resources applying no
exceptions today β a separate defect, not a licence to drop them.
What this leaves on this issue
controller-rbac.yamlcarries entries that cannot match anything) β the premise wasrefuted on 08-10: that selector suppresses 17 findings. It needs rewording or dropping.
AGENTS.md: 52 of the 76excepted controls are in neither gated framework, so their exceptions are unmeasurable here
whether or not they are correct. That is the part worth keeping this issue open for.
devantler commentedon Aug 12, 2026
Step-1 measurement note β 2026-08-12. Partial, and it changes how step 1 must be measured.
I took step 1 (the C-0187 in-scope/out-of-scope question) and did not finish it, but three of the things I hit invalidate the measurement approach itself, so they are worth more than the answer would have been.
1.
configurationscansummariesLIST is spec-stripped too β the freshness check in this issue can read as a dead scannerThe issue's evidence-freshness section cites "aggregate scan summary non-zero (212 high / 840 medium / 89 low)" as proof the scanner is alive. Taken via LIST, that same check now returns
high=0 medium=0 low=0across all 40 summaries, total findings 0. Taken by name, the same objects return real numbers:kubescapeveleroflux-systemThe known trap was documented for
workloadconfigurationscans. It applies toconfigurationscansummariesas well. I nearly filed a live "posture scanning is dead" incident off that zero β the cluster reads exactly like a silently-broken scanner, and every corroborating signal agrees with the false reading (scheduler CronJob Complete in 19s, no pod restarts, newest objectcreationTimestamp46 days old because these objects are updated in place rather than recreated). Only the by-name GET separates the two.2. A multi-name GET silently returns NOTHING above some batch size
get workloadconfigurationscans <n1> <n2>works. The same call with 20 names, and with 138, returns no output and no error, and ajqover it yields a clean empty result. My first sweep concluded "0 of 201 RBAC objects carry C-0187" on exactly that silent empty, and it was wrong-shaped evidence, not a finding.This compounds badly with the LIST trap: the batching is the documented workaround for the LIST trap, so the recommended fix has its own silent-zero mode. Any sweep here needs a positive control β a known-populated object fetched in the same call shape β or it cannot distinguish "no findings" from "no output".
3. What the reliable reading actually says β C-0187 is no longer evaluated on the excepted bindings
At a batch size that is provably working (2 names, verified against a populated control map):
Both are named explicitly in
wildcard-rbac.yaml. On 2026-07-27 this issue measured C-0187 failing on 6 RBAC-anchored objects; today these two carry only C-0262 and no C-0187 at all. Notpassed, notfailedβ absent.That is not the exception working: absence is not suppression, and
appliedIgnoreRulesreads null. Nor is it a scanner upgrade β the image is stillv4.0.10, the same version this issue measured. The one thing that did change is a deployment rollout on 2026-08-08T21:31:40Z, after this issue's measurement and after the follow-up measurements of 08-05/08-07.Consequence for this issue
The 249 (control, object) pairs and the six-control table were measured on 07-27 and the evaluated control set has moved since. Re-measure before migrating anything β migrating six exceptions to match a control that is no longer evaluated on those objects would write six more inert entries, which is precisely the failure this issue exists to stop.
Step 1 as written is still the right first step, but it needs a prior half: establish which controls are currently evaluated on RBAC-kind objects at all, with a positive control in the query shape, before asking whether residue is in or out of scope.
Not established
The full in/out-of-scope answer (my chunked sweep timed out), and why C-0187 disappeared. 201 RBAC-kind objects is the current population, which matches the previously recorded figure exactly.
Unassigning myself β I am not carrying this further this run, and on this repo a stale assignment cannot be shown expired (monorepo#2798), so leaving it assigned would park the issue rather than reserve it.
devantler commentedon Aug 15, 2026
Step 1 is answered: the mechanism works, and the residue is entirely out-of-scope
Measured 2026-08-15 on the CI scan β the surface that actually applies
ClusterSecurityExceptionβ with a paired control, offline, no cluster. This is the first measurement in this thread taken on
the correct surface with an A/B arm, and it settles the question this issue has been circling.
Method
Identical command both arms, ksail 7.178.20, at
platform@44f14fab:The two arms differ by exactly one policy (
diffof the policy-name lists returns onlycontroller-rbac).Result β the exception suppresses 17 findings
This refutes the title. The six entries are not "structurally inert" β three of them are load-bearing
on the gating surface, and removing the CR would add 17 findings to the CI posture gate. Acceptance
criterion 4 as written ("no longer carries exception entries that cannot match anything") would, if
applied literally to all six, delete three working exceptions.
The other three (C-0063, C-0035, C-0188) suppress nothing here β but note that is because they produce
zero findings in both arms, i.e. they are unfalsifiable on this surface, not proven inert.
The in-scope/out-of-scope question, answered
All 10 residual C-0053 findings are
ServiceAccount-anchored and fall in 7 distinct tenantapplication namespaces. None of the seven appears in the CR's
namespaceSelectorlist, whichenumerates infrastructure-controller and platform-middleware namespaces only.
(Namespaces and object names deliberately withheld, per this issue's own precedent β an enumeration of
which service accounts hold broad token access is a weakness map. The aggregate and the scope verdict
are the sanitized minimum needed to review a fix.)
So the residue is 100% out-of-scope. By this issue's own decision rule β
β the mechanism works. And the second half does not follow here either: the residue is tenant
workloads, not infrastructure controllers, so those findings are correctly left flagged. The CR is
not incomplete; it is scoped exactly right.
Why this took 19 days and 15 comments β the body is the trap
Every run that picked this up ran step 1 against
workloadconfigurationscans/β¦summaries, discoveredthose surfaces apply no exceptions at all, and posted that finding. That has now happened
repeatedly, including two self-corrections. The cause is not the runs: the body still prescribes the
refuted method, and the body is what a fresh run reads.
ci.yamlhas recorded the correct surface since #2969 and cites this very issue:The codebase knew. The issue body did not. I am updating the body so the next run does not make this
the sixteenth attempt.
What actually remains
Answer the in-scope/out-of-scope questionβ done above.controller-rbac.yaml's NOTE. It currently tells the reader all six controls "share thislimitation; migrate them the same way if they surface failing objects." Three of them do not share it
β migrating those would remove 17 suppressions. This is the actively harmful part and I am opening a
PR for it.
surface today. Either keep them as forward-looking guards with that stated, or drop them β but the
basis must be recorded, not inferred.
chore: agent-claim #2823 nonce=1ff7ea5a6f439de4429b1fe4b476bdc9
devantler commentedon Aug 18, 2026
AC 3 answered β decision: keep C-0063 / C-0035 / C-0188
Recorded in
controller-rbac.yamlvia #3212. The basis, and why the opposite conclusion is the tempting one:Both arms show zero findings for these three, which reads as "this exception suppresses nothing, so drop it". That reading is wrong here. They sit outside NSA-CISA and MITRE, so the gating scan never evaluates them β the silence is the gate's blind spot, not a property of the exception.
AGENTS.mdstates this directly for the 52 excepted controls in neither framework: never read "the score did not move" as "the exception is broken".The discriminator, measured this run (ksail paired-arm oracle,
--framework nsa,mitre, baselineC-0015 2 Β· C-0034 3 Β· C-0053 11):11 β 250 β 3C-0037 is the load-bearing row. It shares the zero baseline that made these three look inert, yet it responds β so a zero baseline is not evidence of redundancy, and the absence of response is what distinguishes the two cases. Removing the three on this scan's silence would have stripped a justified exception governing findings that are real on the surfaces which do evaluate them.
Re-decide only if one of them comes under a gated framework, at which point the same two-arm oracle gives a real answer.
Acceptance criteria status
controller-rbac.yamlguidance reflects measured per-control effect β shipped in docs(kubescape): correct the controller-rbac migration guidanceΒ #3142 (merged 2026-08-15)Note the numbers moved slightly since the 2026-08-15 measurement in the file (C-0053 now
11 β 25, suppressing 14 rather than 13) β expected drift from manifest and ksail changes, and it does not affect any conclusion. #3212 updates the recorded figures.security(ci): strip a shell comment before joining its continuation