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
Follow-up to the C-0015 exception fix (fix(kubescape): make C-0015 secret-list exceptions take effect). That change makes the "List Kubernetes secrets" control meaningful by exempting the legitimate secret-readers. A few identities were deliberately left flagged because they are genuine over-privilege — they should be fixed at the RBAC root, not silenced.
1. Unused tenant SAs bound to ClusterRole/edit (actionable, low risk)
wedding-app/wedding-app and ascoachingogvaner/ascoachingogvaner are bound to the built-in edit role (namespace-wide Secret read/write) but no workload uses them — both apps' pods run as the default SA (verified live). The bindings are dormant over-privilege.
Proposal: drop the edit bindings (or bind a minimal role). Needs a call on whether the tenant-baseline edit grant is intentional.
2. crossview-role wildcard read (upstream chart)
crossview-role grants */* get,list,watch — it reads all Secret contents for a topology viewer. The ClusterRole ships from the crossview Helm chart, so tightening (exclude Secrets, or scope) belongs upstream in that chart, not this repo.
Left flagged by design — no action needed
Surfacing the highest-privilege identities is the control working as intended: system:masters (cluster-admin), crossplane:masters (crossplane-admin), and the on-demand longhorn-support-bundle diagnostic SA.
Related
The other RBAC controls still in controller-rbac.yaml's namespaceSelector (C-0053, C-0007, C-0037, C-0031, C-0002, C-0063, C-0035, C-0188) have the same limitation — a namespaceSelector never suppresses RBAC-attached findings — and should migrate to kind+name matching too.
Triage / re-scope — item 1 is stale and its proposed action is now a footgun; verified against origin/main.
Since this issue was filed, the tenant reconciliation identity was migrated off the built-in edit ClusterRole onto a purpose-scoped, aggregated tenant-edit role (k8s/bases/infrastructure/cluster-roles/tenant-edit.yaml + the *-tenant-edit pieces). That migration already fixed the meaningful over-privilege at the RBAC root.
Item 1 — drop the wedding-app / ascoachingogvaner bindings: do NOT do this. The premise ("no workload uses them → dormant") is incorrect. Both tenants' Flux Kustomizations set spec.serviceAccountName: <tenant> (k8s/bases/apps/{wedding-app,ascoachingogvaner}/flux-kustomization.yaml:19), so Flux's kustomize-controller impersonates these SAs to reconcile each tenant's manifests. The app pods run as default (hence automountServiceAccountToken: false on the tenant SA), but the SA's RBAC is consumed by Flux impersonation, not by a pod. Removing the binding would break tenant reconciliation — it is by design, not dormant cruft.
What actually changed the over-privilege picture:
The pods/exec (+ attach/portforward/proxy), SA-token/impersonation, and dynamically-aggregated operator-CRD surface that the old edit granted — the C-0002 driver — is gone in tenant-edit.
The residual namespaced secrets: get,list,watch,create,… in tenant-base-edit is required: each tenant owns its OpenBao-backed secrets via its own ExternalSecret→Secret in its deploy artifact. This is a legitimate secret-reader, so C-0015 for these two SAs is an exception concern (kind+name CSE, per the existing C-0002/C-0015 pattern), never a binding to drop.
Item 2 — crossview-role wildcard read: out of this repo's scope, as the issue already notes (it ships from the upstream crossview Helm chart; tighten upstream).
Remaining actionable residual: the "Related" note — migrating the other RBAC-attached exceptions (C-0053, C-0007, C-0037, C-0031, C-0002, C-0063, C-0035, C-0188) from namespaceSelector to kind+name matching so they actually suppress — is the real work here. That requires live Kubescape SARIF to enumerate the exact flagged kind/name per control, so it belongs on the security-posture live cadence (part of #2447), not a blind manifest edit. Re-scoping this issue to that residual; item 1 and item 2 are resolved / upstream.
The substance of the C-0015 finding survives that correction — tenant-edit does grant secrets get,list,watch (plus write) namespace-scoped, so the control is firing on something real.
But it is the tenant baseline, not dormant residue.tenant-edit is bound in every tenant
namespace, including one created after this issue was filed:
Three of three, one per tenant, each to its own same-named SA. That is a deliberate per-tenant grant
being applied to new tenants as they arrive — not two leftovers. Dropping it from two of the three
would break the baseline for those two and leave the pattern inconsistent, which is a worse state
than the one being fixed.
A tenant service account that can read Secrets in its own namespace is the intended tenant model,
and it is what edit-shaped roles mean everywhere else. So under the fix-vs-except ladder this is the
"scoped, justified exception" case: except it, with the reasoning recorded — do not remove the
binding.
A separate finding worth its own issue
The observation that the SAs are unused is accurate, but points somewhere else than this issue
concluded. All three tenants' pods run as the default ServiceAccount:
wedding-app-… default
ascoachingogvaner-… default
So the scoped SA sits unused while the workloads run under default. The hardening move is to point
the Deployments at their own tenant SA — the opposite of removing it — and it is a behaviour change
on two live public sites, so it needs its own issue and its own validation rather than riding along
here.
Item 2 and item 3 unchanged
crossview-role remains upstream chart territory, and the by-design entries stay flagged.
The "Related" note is now measured
That closing note turned out to be the substantial half of this issue: all six controls do surface
failing objects, all of them RBAC-anchored, so all six exceptions are inert. Filed as #2823 with the
counts, the wlid analysis, and one important caveat — the already-migrated controls have not gone to
zero either, so the migration is unproven as a remedy and should be verified before six more are
written.
Item 1 is stale, and acting on it as written would break both tenants
Investigated this as the oldest unassigned Security issue. Do not drop those bindings. Both of item 1's premises are false against the current tree (f89efff4).
1. They are not bound to the built-in edit. Both bind to tenant-edit:
k8s/bases/apps/ascoachingogvaner/role-binding-ascoachingogvaner.yaml → same
tenant-edit is a custom aggregated role introduced by #2453 (fix(security): scope tenant Flux-impersonation role to drop pods/exec (C-0002)), which is the commit that changed these very files. It exists precisely to avoid the built-in edit surface: it drops the exec family (pods/exec, attach, portforward, proxy), serviceaccounts/token, and the operator CRDs that edit dynamically aggregates. The hardening this item asks for has, in substance, already happened.
2. The ServiceAccounts are not unused. This is the load-bearing correction. They are the Flux reconciliation identities:
tenant-edit's own header states it: "Flux's kustomize-controller IMPERSONATES this SA (spec.serviceAccountName) to reconcile the tenant's manifests, so this role is the tenant's entire in-namespace capability."
The original check looked for pods using the SA and correctly found none — but the consumer is not a pod, it is kustomize-controller impersonating the identity. Removing the bindings would leave both Kustomizations unable to apply anything, so the two live sites would silently stop reconciling. Filed as "actionable, low risk"; it is neither.
What that leaves
The secrets access these identities hold is inherent capability, not dormant over-privilege: a tenant's reconciler must create and update the Secrets its own manifests declare. So there is no RBAC root-cause fix here to make — if C-0015 still flags them, the correct instrument under the fix-vs-except ladder is a scoped, justified exception naming these as reconciliation identities, not a binding removal.
Item 2 (crossview-role wildcard read, upstream chart) and the "left flagged by design" set are untouched by this and still stand.
Suggest item 1 be rewritten to that effect, or split out and closed, so a later run does not read the original text and ship the removal.
Scope of verification: static, against the pinned tree — file contents, the roleRef, the aggregation rule, and the serviceAccountName wiring, plus the commit that changed them. I did not confirm live cluster state: kubectl is blocked by this runtime's permission classifier, and I did not work around it. Live confirmation that the two Kustomizations are currently reconciling under these SAs is still worth doing before any change here.
These are impersonation identities. kustomize-controller assumes them to reconcile each tenant,
which is what confines a tenant's Flux reconciliation to its own namespace. So the tenant-edit
binding is not dormant at all — it is the permission Flux actually uses on every reconcile, and its
Secret access is load-bearing rather than latent.
That strengthens the conclusion but invalidates my reasoning for it:
Unchanged: the binding stays. Removing it would stop both tenants reconciling — silently, since
a Kustomization that cannot impersonate simply fails rather than alerting.
Withdrawn: my suggestion to point the Deployments at the tenant SA. The pods running as default is the intended split — Flux holds the write identity, the workload does not. Filing that
as a hardening issue would have been wrong.
The edit-vs-tenant-edit correction and the "all three tenants have it" measurement both stand.
Credit where due: a concurrent agent instance identified the impersonation mechanism independently;
I verified it against the manifests above before writing this.
Disposition: closing — every item here is answered, and leaving it open is now costing more than it buys
This is the oldest open Security issue in the portfolio, so it is handed to whichever run reaches
rung 2. Four separate investigations today alone (17:15Z, and three between 16:08Z and 16:25Z)
reached the same conclusions and none of them closed it, so the next run picks it up and re-derives
them again. That is the actual harm: the issue is not tracking work, it is recycling a settled
answer — and the action it still recommends in its own body would break two tenants.
Recording the disposition per item rather than leaving a fifth investigation on the pile.
Item 1 — "unused tenant SAs bound to ClusterRole/edit" → will not do; both premises are false
Not edit.wedding-app/wedding-app and ascoachingogvaner/ascoachingogvaner bind to the
custom aggregated tenant-edit ClusterRole, which was introduced after this issue was filed
precisely to avoid the built-in edit surface — it drops pods/exec, attach, portforward, proxy and serviceaccounts/token.
Not unused. The consumer is not a pod, it is Flux: both tenants' Kustomizations set spec.serviceAccountName, so kustomize-controller impersonates these SAs to reconcile each
tenant. That impersonation is what confines a tenant to its own namespace. Dropping the binding
would stop both tenants reconciling — and silently, because a Kustomization that cannot impersonate
fails rather than alerting.
The C-0015 finding is real (tenant-edit does grant secrets get,list,watch), but it is the tenant
baseline, bound in every tenant namespace including ones created since. So the remedy was never
"remove"; it is "except" — see below.
Item 2 — crossview-role wildcard read → out of scope for this repo
The ClusterRole ships from the upstream crossview Helm chart. Tightening it belongs upstream, not
here, exactly as the issue itself says.
"Left flagged by design" → no action, working as intended
system:masters, crossplane:masters and the on-demand longhorn-support-bundle SA are supposed to
surface. Surfacing the highest-privilege identities is the control doing its job.
"Related" — migrate the other RBAC controls to kind+name matching → tracked in #2823, and the proposed fix is known not to work
This is the only live thread, and it does not belong here. Migrating those controls to kind+name
matching would produce more inert exceptions: measured on 2026-07-27, five of C-0187's six failing
objects are ones the wildcard-rbac-by-design CR explicitly names, three by exact kind and name,
and no object anywhere in the cluster carries the w-exceptions marker the operator writes when it
applies an exception. #2823 owns that, with its direction already rewritten.
Net
Nothing here is an unshipped remediation: item 1 is a correction, item 2 is upstream, the flagged
identities are by design, and the one live thread has its own issue. Closing as completed —
reopen if the exception mechanism starts applying and the tenant-baseline suppression becomes
writable again.
Activity
devantler commentedon Jul 17, 2026
Triage / re-scope — item 1 is stale and its proposed action is now a footgun; verified against
origin/main.Since this issue was filed, the tenant reconciliation identity was migrated off the built-in
editClusterRole onto a purpose-scoped, aggregatedtenant-editrole (k8s/bases/infrastructure/cluster-roles/tenant-edit.yaml+ the*-tenant-editpieces). That migration already fixed the meaningful over-privilege at the RBAC root.Item 1 — drop the
wedding-app/ascoachingogvanerbindings: do NOT do this. The premise ("no workload uses them → dormant") is incorrect. Both tenants' FluxKustomizations setspec.serviceAccountName: <tenant>(k8s/bases/apps/{wedding-app,ascoachingogvaner}/flux-kustomization.yaml:19), so Flux's kustomize-controller impersonates these SAs to reconcile each tenant's manifests. The app pods run asdefault(henceautomountServiceAccountToken: falseon the tenant SA), but the SA's RBAC is consumed by Flux impersonation, not by a pod. Removing the binding would break tenant reconciliation — it is by design, not dormant cruft.What actually changed the over-privilege picture:
pods/exec(+ attach/portforward/proxy), SA-token/impersonation, and dynamically-aggregated operator-CRD surface that the oldeditgranted — the C-0002 driver — is gone intenant-edit.secrets: get,list,watch,create,…intenant-base-editis required: each tenant owns its OpenBao-backed secrets via its ownExternalSecret→Secretin its deploy artifact. This is a legitimate secret-reader, so C-0015 for these two SAs is an exception concern (kind+name CSE, per the existing C-0002/C-0015 pattern), never a binding to drop.Item 2 —
crossview-rolewildcard read: out of this repo's scope, as the issue already notes (it ships from the upstream crossview Helm chart; tighten upstream).Remaining actionable residual: the "Related" note — migrating the other RBAC-attached exceptions (C-0053, C-0007, C-0037, C-0031, C-0002, C-0063, C-0035, C-0188) from
namespaceSelectorto kind+name matching so they actually suppress — is the real work here. That requires live Kubescape SARIF to enumerate the exact flaggedkind/nameper control, so it belongs on the security-posture live cadence (part of #2447), not a blind manifest edit. Re-scoping this issue to that residual; item 1 and item 2 are resolved / upstream.devantler commentedon Jul 27, 2026
Item 1 — the premise is wrong, and the call is "except", not "remove"
I went to implement this and the live state does not match the description. Recording the correction
and the decision rather than leaving it deferred.
These bindings do not reference the built-in
editrole. Both reference a customtenant-editClusterRole:The substance of the C-0015 finding survives that correction —
tenant-editdoes grantsecretsget,list,watch(plus write) namespace-scoped, so the control is firing on something real.But it is the tenant baseline, not dormant residue.
tenant-editis bound in every tenantnamespace, including one created after this issue was filed:
Three of three, one per tenant, each to its own same-named SA. That is a deliberate per-tenant grant
being applied to new tenants as they arrive — not two leftovers. Dropping it from two of the three
would break the baseline for those two and leave the pattern inconsistent, which is a worse state
than the one being fixed.
A tenant service account that can read Secrets in its own namespace is the intended tenant model,
and it is what
edit-shaped roles mean everywhere else. So under the fix-vs-except ladder this is the"scoped, justified exception" case: except it, with the reasoning recorded — do not remove the
binding.
A separate finding worth its own issue
The observation that the SAs are unused is accurate, but points somewhere else than this issue
concluded. All three tenants' pods run as the
defaultServiceAccount:So the scoped SA sits unused while the workloads run under
default. The hardening move is to pointthe Deployments at their own tenant SA — the opposite of removing it — and it is a behaviour change
on two live public sites, so it needs its own issue and its own validation rather than riding along
here.
Item 2 and item 3 unchanged
crossview-roleremains upstream chart territory, and the by-design entries stay flagged.The "Related" note is now measured
That closing note turned out to be the substantial half of this issue: all six controls do surface
failing objects, all of them RBAC-anchored, so all six exceptions are inert. Filed as #2823 with the
counts, the
wlidanalysis, and one important caveat — the already-migrated controls have not gone tozero either, so the migration is unproven as a remedy and should be verified before six more are
written.
devantler commentedon Jul 27, 2026
Item 1 is stale, and acting on it as written would break both tenants
Investigated this as the oldest unassigned Security issue. Do not drop those bindings. Both of item 1's premises are false against the current tree (
f89efff4).1. They are not bound to the built-in
edit. Both bind totenant-edit:k8s/bases/apps/wedding-app/role-binding.yaml→roleRef: ClusterRole/tenant-editk8s/bases/apps/ascoachingogvaner/role-binding-ascoachingogvaner.yaml→ sametenant-editis a custom aggregated role introduced by #2453 (fix(security): scope tenant Flux-impersonation role to drop pods/exec (C-0002)), which is the commit that changed these very files. It exists precisely to avoid the built-ineditsurface: it drops the exec family (pods/exec,attach,portforward,proxy),serviceaccounts/token, and the operator CRDs thateditdynamically aggregates. The hardening this item asks for has, in substance, already happened.2. The ServiceAccounts are not unused. This is the load-bearing correction. They are the Flux reconciliation identities:
k8s/bases/apps/wedding-app/flux-kustomization.yaml:19→serviceAccountName: wedding-appk8s/bases/apps/ascoachingogvaner/flux-kustomization.yaml:19→serviceAccountName: ascoachingogvanertenant-edit's own header states it: "Flux's kustomize-controller IMPERSONATES this SA (spec.serviceAccountName) to reconcile the tenant's manifests, so this role is the tenant's entire in-namespace capability."The original check looked for pods using the SA and correctly found none — but the consumer is not a pod, it is kustomize-controller impersonating the identity. Removing the bindings would leave both Kustomizations unable to apply anything, so the two live sites would silently stop reconciling. Filed as "actionable, low risk"; it is neither.
What that leaves
The secrets access these identities hold is inherent capability, not dormant over-privilege: a tenant's reconciler must create and update the Secrets its own manifests declare. So there is no RBAC root-cause fix here to make — if C-0015 still flags them, the correct instrument under the fix-vs-except ladder is a scoped, justified exception naming these as reconciliation identities, not a binding removal.
Item 2 (
crossview-rolewildcard read, upstream chart) and the "left flagged by design" set are untouched by this and still stand.Suggest item 1 be rewritten to that effect, or split out and closed, so a later run does not read the original text and ship the removal.
Scope of verification: static, against the pinned tree — file contents, the
roleRef, the aggregation rule, and theserviceAccountNamewiring, plus the commit that changed them. I did not confirm live cluster state:kubectlis blocked by this runtime's permission classifier, and I did not work around it. Live confirmation that the two Kustomizations are currently reconciling under these SAs is still worth doing before any change here.devantler commentedon Jul 27, 2026
Correction to my previous comment — the ServiceAccounts are NOT unused
My "separate finding" above was wrong, and the suggestion in it would have broken both tenants.
I checked for pods using those ServiceAccounts, found none, and concluded they were idle. The
consumer is not a pod — it is Flux:
These are impersonation identities.
kustomize-controllerassumes them to reconcile each tenant,which is what confines a tenant's Flux reconciliation to its own namespace. So the
tenant-editbinding is not dormant at all — it is the permission Flux actually uses on every reconcile, and its
Secret access is load-bearing rather than latent.
That strengthens the conclusion but invalidates my reasoning for it:
a Kustomization that cannot impersonate simply fails rather than alerting.
defaultis the intended split — Flux holds the write identity, the workload does not. Filing thatas a hardening issue would have been wrong.
The
edit-vs-tenant-editcorrection and the "all three tenants have it" measurement both stand.Credit where due: a concurrent agent instance identified the impersonation mechanism independently;
I verified it against the manifests above before writing this.
devantler commentedon Jul 27, 2026
Disposition: closing — every item here is answered, and leaving it open is now costing more than it buys
This is the oldest open Security issue in the portfolio, so it is handed to whichever run reaches
rung 2. Four separate investigations today alone (17:15Z, and three between 16:08Z and 16:25Z)
reached the same conclusions and none of them closed it, so the next run picks it up and re-derives
them again. That is the actual harm: the issue is not tracking work, it is recycling a settled
answer — and the action it still recommends in its own body would break two tenants.
Recording the disposition per item rather than leaving a fifth investigation on the pile.
Item 1 — "unused tenant SAs bound to
ClusterRole/edit" → will not do; both premises are falseedit.wedding-app/wedding-appandascoachingogvaner/ascoachingogvanerbind to thecustom aggregated
tenant-editClusterRole, which was introduced after this issue was filedprecisely to avoid the built-in
editsurface — it dropspods/exec,attach,portforward,proxyandserviceaccounts/token.Kustomizations setspec.serviceAccountName, so kustomize-controller impersonates these SAs to reconcile eachtenant. That impersonation is what confines a tenant to its own namespace. Dropping the binding
would stop both tenants reconciling — and silently, because a Kustomization that cannot impersonate
fails rather than alerting.
The C-0015 finding is real (
tenant-editdoes grantsecrets get,list,watch), but it is the tenantbaseline, bound in every tenant namespace including ones created since. So the remedy was never
"remove"; it is "except" — see below.
Item 2 —
crossview-rolewildcard read → out of scope for this repoThe ClusterRole ships from the upstream crossview Helm chart. Tightening it belongs upstream, not
here, exactly as the issue itself says.
"Left flagged by design" → no action, working as intended
system:masters,crossplane:mastersand the on-demandlonghorn-support-bundleSA are supposed tosurface. Surfacing the highest-privilege identities is the control doing its job.
"Related" — migrate the other RBAC controls to kind+name matching → tracked in #2823, and the proposed fix is known not to work
This is the only live thread, and it does not belong here. Migrating those controls to kind+name
matching would produce more inert exceptions: measured on 2026-07-27, five of C-0187's six failing
objects are ones the
wildcard-rbac-by-designCR explicitly names, three by exact kind and name,and no object anywhere in the cluster carries the
w-exceptionsmarker the operator writes when itapplies an exception. #2823 owns that, with its direction already rewritten.
Net
Nothing here is an unshipped remediation: item 1 is a correction, item 2 is upstream, the flagged
identities are by design, and the one live thread has its own issue. Closing as completed —
reopen if the exception mechanism starts applying and the tenant-baseline suppression becomes
writable again.