Skip to content

Let Kubernetes RBAC, not the Cloud role, decide user-run operations - #1899

Merged
nadaverell merged 16 commits into
skyhook-io:mainfrom
arlenvasconcelos:arlen/rad-578-decouple-hub-roles
Oct 4, 2026
Merged

nadaverell merged 16 commits into
skyhook-io:mainfrom
arlenvasconcelos:arlen/rad-578-decouple-hub-roles

Conversation

@arlenskh

@arlenskh arlenskh commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Why

Part of RAD-578: in Radar Cloud, hub roles (owner / member / viewer) should cover Radar's own features, and for orgs with an IdP, IdP groups (radar:idp:<id>) should decide cluster access. Those orgs turn the chart's hub-role bindings off (cloud.defaultRbac.create=false). Two things broke in that setup:

  1. Hub background jobs lost cluster access. The alerts worker and timeline puller only reached the cluster through radar:owner → admin, so alerts and the hub timeline went silent. The same thing happens today to anyone who sets defaultRbac.owner=false.
  2. The Cloud role overrode Kubernetes. Helm reads and writes and in-cluster probe pods were refused on the Cloud role before Kubernetes was asked. They run as the impersonated user, so a viewer whose IdP group grants edit in a namespace couldn't roll back there.

What

Chart

  • New cloud-rbac-system.yaml: binds radar:system (the hub's alerts worker and timeline puller) to view plus Secret get/list/watch. The cluster-read and integration-read add-ons also bind it, and now still render when defaultRbac.create=false. AI diagnose does not use it; it stays on radar:viewer.
  • Secret read is there for Helm release alerts and Secret changes in the hub timeline: Radar lists releases as the caller, and Helm stores releases as Secrets. It's read-only, and it's far less than today, where these identities get admin through radar:owner.
  • New value cloud.systemRbac (default true), all or nothing. An absent value counts as off, like defaultRbac.create, so a --reuse-values upgrade from an older release never gains cluster-wide Secret read without someone choosing it. Schema, chart README and docs/authentication.md updated.

Backend

  • Removed requireCloudRole from all 16 Helm handlers and the MCP get_helm_release gate.
  • Removed the role gate from the in-cluster reachability test (capability check, run endpoint, MCP diagnose). Probe pods are created with the user's impersonated client, and reachability.Capability already checks Kubernetes permissions.
  • The Cloud role still gates what runs as Radar's service account: settings, Argo CD and Kubecost integration, audit settings, self-upgrade.

UI

  • RoleGatedPanel is removed from the Helm Manifest, Values and Compare tabs. useCanHelmAct(namespace) checks the user's own Helm write permission in the release's namespace (see below).
  • RestrictedState keeps the exact group, resource and verbs for the denied kind in its RBAC snippet, and leaves the subject as a placeholder the admin fills in.

OSS without a Hub is unaffected: the cloud templates need cloud.enabled, and the role gates already did nothing without a Cloud role.

Test plan

  • helm unittest (new cloud_rbac_system_test.yaml, updated integration-read suite) and scripts/test-chart.sh
  • go test ./... and pkg module tests. The chart-rendering TestIntegrationReadBindings now expects radar:system.
  • New TestHelmHandlers_NotGatedOnCloudRole and TestTraceInClusterNotGatedOnCloudRole, both red before the change
  • make tsc; web and k8s-ui vitest, plus a new RestrictedState.test.tsx
  • Visual test on EKS, with proxy auth simulating Cloud users:
    • a Cloud viewer with admin-level RBAC sees Manifest, Values, Rollback and Compare;
    • a viewer with only view gets Kubernetes' "Access Restricted" instead of a role message;
    • the RBAC snippet binds the user's first IdP group;
    • OSS without auth is unchanged;
    • a user with no access sees the banner on Home, Resources and Topology; 25 groups stay collapsed; checked in dark and light themes.
  • TestHandleAuthMe_NoNamespaceAccess (reported only when connected with zero namespaces) and noClusterAccess.test.ts

No-access banner

  • A user who can read no namespace used to see an empty cluster ("No Node found", every count "–") with no explanation. That's the day-one view for an IdP org before any binding exists, and it also happens to OSS proxy/OIDC users with no RBAC.
  • /api/auth/me now reports noNamespaceAccess: true for such a user, and only once connected: before that, namespace discovery fails closed.
  • The shell shows a banner above every view, including when the Hub embeds Radar. It says why the views are empty and who is signed in, with the user's bindable groups in a collapsed list, so they can tell a cluster admin which group to bind.
  • It's based on the user's own access, not on how the org means to set up the cluster, so it never claims a cluster is IdP-only.
  • While shown, it re-checks every minute, so it clears once a binding lands.

Release order

Release this chart before the Hub web wizard can default new installs to IdP-only access. The Hub PRs keep sending radar:owner for one release so older charts keep working.

Related: skyhook-dev/radar-hub#272, skyhook-dev/radar-hub-web#326, skyhook-io/helm-charts#51.

Cross-review follow-up (2026-09-29)

  • Helm writes check the signed-in user, not Radar's ServiceAccount (55fc3efa). With a user attached, requireHelmWrite checks the caller's secrets/create in the release's namespace, before any chart is fetched (so a caller who could never install can't make Radar fetch an arbitrary repo URL). Chart-cache and OCI-source changes need no cluster permission. No-auth installs keep the ServiceAccount check. /api/capabilities?namespace= reports helmWrite per namespace; with auth on, a denial names the user's RBAC, not rbac.helm. Also: streaming rollback runs as the user, and a forbidden release read returns 403.
  • No write-everything ServiceAccount under rbac.helm in Cloud/auth mode (acbdbad4). The */* create/update/patch/delete rule renders only for no-auth, non-Cloud installs; every Helm action there runs as the user, and no ServiceAccount write path depends on it (audited).
  • radar:system is one all-or-nothing grant; absent means off (acbdbad4, c4018889).
  • /auth/me reads the permission cache only (e51efe09), so app startup no longer runs a SubjectAccessReview per namespace. The banner title now reads "You can't read any namespace in this cluster".
  • Known limit: on a first visit to a large cluster, the banner's /auth/me check can arrive before namespace discovery fills the cache, so the banner may need one reload to appear. A retry loop for this (6eb752a8) was reverted (c0f51b96) as not worth the extra state.
  • The banner's group list is ph-no-capture (af4afc7c): group IDs name the customer's directory structure, and Radar Cloud records this view in session replay.
  • go test for internal/helm, internal/k8s, internal/server pass (the one internal/server failure, TestNoEffortNotesReferencedFromSource, comes from other local worktrees under .claude/). helm unittest: 70/72, the 2 failures (service_test.yaml) also fail on HEAD. Web vitest for contexts/helm/api pass.
  • Follow-ups: RAD-656 (reads through the cache aren't checked per kind), RAD-657 (diagnose permissions; redact Helm manifest/notes diffs before any diagnose identity gets Secret read).

🤖 Generated with Claude Code


Note

High Risk
Changes cluster RBAC (including optional cluster-wide Secret read for radar:system) and broadens who can attempt Helm and probe operations based on Kubernetes permissions instead of Cloud role, which affects security boundaries for IdP-only orgs.

Overview
Radar Cloud orgs that turn off default hub-role bindings (cloud.defaultRbac.create=false) can keep alerts and the hub timeline working via a new cloud.systemRbac path that binds radar:system to read-only access (view, cluster-read/integration-read add-ons, and cluster-wide Secret read). That grant is separate from tier bindings; --reuse-values upgrades without the key stay off until explicitly enabled.

Authorization shifts from Cloud tier to Kubernetes RBAC for user-impersonated work: Helm reads/writes, previews, rollbacks (including streaming), in-cluster reachability probes, and MCP Helm/diagnose paths no longer call requireCloudRole. Writes are gated by the caller’s secrets/create in the release namespace (before chart fetch); release read failures surface as 403 when RBAC denies Secret access. The chart stops granting cluster-wide */* write on Radar’s ServiceAccount when auth or cloud mode is on—only no-auth installs keep that for Helm-as-SA.

The UI drops member-only Helm panels and uses namespace-scoped useCanHelmAct, aligned with per-namespace helmWrite capabilities. /api/auth/me adds noNamespaceAccess and a warning banner when a connected user can read no namespaces.

Reviewed by Cursor Bugbot for commit 812e48f. Bugbot is set up for automated code reviews on this repo. Configure here.

arlenvasconcelos and others added 13 commits September 25, 2026 14:03
Radar Cloud orgs whose IdP groups decide cluster access turn the chart's
hub-role bindings off. Two things broke in that setup:

- The hub's alerts worker and timeline puller only had cluster access
  through radar:owner. The chart now always binds radar:system, which
  they assert, to a read-only set: view, the cluster-read and
  integration-read add-ons, and Secret read (Helm release alerts list
  releases as the caller, and Helm stores them as Secrets). It is
  controlled by cloud.systemRbac, independent of cloud.defaultRbac.
- Helm reads and writes, and in-cluster probe pods, were refused on the
  Cloud role before Kubernetes was asked. They run as the impersonated
  user, so a viewer whose IdP group grants access was wrongly denied.
  Those role checks are removed from the REST handlers, the MCP tools
  and the UI; the role still gates Radar's own configuration.

The restricted-access panel now fills its RBAC snippet with the user's
radar:idp:* groups, binding only the first.
Every read is filtered to the user's namespaces, so a user bound to none
saw empty lists in every view with nothing saying why. /api/auth/me now
reports noNamespaceAccess once connected, and the shell shows a banner
above every view: who is signed in, their bindable groups in a collapsed
list, and a ClusterRoleBinding with placeholders that grants nothing
until an admin fills it in.

The banner is based on the user's own access, not on how the cluster is
meant to be set up, so it holds in OSS proxy and OIDC modes and on Cloud
clusters whether they use hub roles or IdP groups. It re-checks every
minute while shown, so it clears once a binding lands.
Knowing why every view is empty is the part the user needs; binding a group is the cluster admin's job, done in their own tooling. The banner keeps the explanation and the collapsed list of the user's groups.
No chart ever bound cloud:system and every running hub sends radar:system with
it, so the legacy subject matched nothing. The hub stops sending it too
(skyhook-dev/radar-hub#272).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The filter picked which group was safe to suggest binding, which only mattered while the banner showed an RBAC snippet. The list is now a diagnostic of who Kubernetes sees the user as, so hiding tier and org groups made it wrong.
The restricted-access snippet filled its subject from the caller's radar:idp:* groups. That only applied in Radar Cloud, bound whichever IdP group came first (possibly an org-wide one), and hard-coded the hub's group vocabulary in the frontend. The snippet keeps the exact group, resource and verbs for the denied kind; the subject goes back to a placeholder the admin fills in.
…nger needs a write-everything SA

cloud.systemRbac binds radar:system (the hub's alerts worker, timeline puller
and AI diagnose agent) to view, the cluster-read and integration-read add-ons,
and Secret read, which Helm release alerts and Secret changes in the hub
timeline need. One flag: false removes the whole grant. An absent value now
means off, like defaultRbac.create, so a --reuse-values upgrade from a release
that predates the key never gains cluster-wide Secret read without someone
choosing it. radar:system has no legacy cloud:* twin in the integration-read
bindings.

rbac.helm's */* create/update/patch/delete rule for the ServiceAccount now
renders only in no-auth, non-Cloud installs. Under auth or Cloud every Helm
action runs as the signed-in user, and no ServiceAccount write path depends on
the grant; rbac.helm there only adds the per-tier Helm add-on roles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017J25KHG5W4DzS32bU1J8ou
With a user attached, every Helm action already runs as that user, yet the
write gate asked whether Radar's ServiceAccount could create Secrets, i.e.
whether rbac.helm=true had granted it write on everything. An IdP group bound
to edit in one namespace could not use Helm there without that grant.

requireHelmWrite now checks the caller's secrets/create in the release's
namespace (Helm stores each release as a Secret there), before any chart is
fetched, so a caller who could never install cannot make Radar reach out to an
arbitrary repository URL. Chart-cache and OCI-source changes are pod-local and
need no cluster permission. Without auth the ServiceAccount check stays.

/api/capabilities?namespace= reports helmWrite for that namespace, and the
release drawer, values editor and install wizard ask about the release's own
namespace. With auth on, a denial names the user's RBAC rather than rbac.helm.

Also: the streaming rollback runs as the user like the plain one, and a
forbidden read of release content returns 403 instead of 500.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017J25KHG5W4DzS32bU1J8ou
The flag behind the no-access banner ran namespace discovery when nothing was
cached: one or two SubjectAccessReviews per namespace on the request app
startup waits for, racing the same discovery in the first resource load. It
now reads the cache and leaves the flag out on a miss; the banner asks again
once content has loaded, when the cache is warm. Only a cached (successful)
discovery is reported, never the fail-closed result of an error.

The banner title says what is true: the user can't read any namespace, while
cluster-scoped kinds may still be readable. It renders once content is ready.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017J25KHG5W4DzS32bU1J8ou
AI diagnose keeps reading as radar:viewer, as on main, so it never gets the
Secret read radar:system carries. How diagnose reads clusters whose hub-role
bindings are off is RAD-657.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/auth/me answers from the permission cache only, so the banner's one refetch
could beat the first namespace discovery and get no answer. Polling ran only
while the answer was "no access", so the banner then stayed hidden and the
user saw an unexplained empty cluster.

/auth/me now sends noNamespaceAccess false once access is known, and leaves
it out only while it isn't. The banner re-asks every 5s while it's unknown,
at most 12 times, so it still stops against a server that predates the field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017J25KHG5W4DzS32bU1J8ou
The banner lists the user's groups, IdP group IDs included, which name the
customer's directory structure. Radar Cloud embeds this view with replay on,
and its text mask only catches credential-shaped values, so the list is
ph-no-capture. It does nothing without PostHog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017J25KHG5W4DzS32bU1J8ou
This reverts commit 6eb752a. The unknown state and its retries only covered
the first visit, where /auth/me can answer before namespace discovery fills
the cache. A reload shows the banner then (the cache is warm), and people
reload an empty screen anyway, so the extra state isn't worth keeping.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017J25KHG5W4DzS32bU1J8ou
@arlenskh
arlenskh marked this pull request as ready for review September 29, 2026 19:40
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Let Kubernetes RBAC authorize user-run Helm and probe operations

🐞 Bug fix ✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Give Cloud background jobs independent read access when hub-role bindings are disabled.
• Let impersonated users’ Kubernetes RBAC decide Helm writes and in-cluster probes.
• Explain empty views when a connected user cannot read any namespace.
Diagram

graph TD
  Jobs["Hub background jobs"] --> SystemRBAC["System RBAC bindings"] --> Kubernetes["Kubernetes API"]
  User["Signed-in user"] --> UI["Radar UI"] --> Backend["Radar API"] --> Kubernetes
  Backend --> Banner["No-access banner"] --> UI
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Operator-managed, namespace-scoped system bindings
  • ➕ Can limit the background identity’s Secret read to selected namespaces.
  • ➖ Requires operators to maintain bindings as monitored namespaces change.
  • ➖ Could leave alerts or timeline entries missing where bindings are absent.

Recommendation: The separate, opt-out radar:system grant is a practical default for cluster-wide background jobs, and impersonated user operations should remain governed by Kubernetes RBAC. Review the cluster-wide Secret-read exposure explicitly and document namespace-scoped operator-managed bindings for installations that cannot accept it.

Files changed (38) +947 / -408

Enhancement (6) +123 / -33
capabilities.goExpose namespace-scoped Helm write capability +8/-0

Expose namespace-scoped Helm write capability

• Checks secrets/create for the current identity or an impersonated user and reports the result as helmWrite, including check-error tracking.

internal/k8s/capabilities.go

server.goMerge Helm permissions and report cached no-access status +16/-0

Merge Helm permissions and report cached no-access status

• Makes Helm write capability namespace-aware. Adds noNamespaceAccess to /api/auth/me only when connected and cached permissions confirm the user can read no namespace.

internal/server/server.go

App.tsxShow the no-access banner across views +2/-0

Show the no-access banner across views

• Mounts the banner in the shared application shell once cluster content is ready.

web/src/App.tsx

client.tsUse namespace RBAC for Helm actions and refresh auth status +24/-32

Use namespace RBAC for Helm actions and refresh auth status

• Adds the noNamespaceAccess response field and periodic refresh while access is absent. Replaces Cloud-tier Helm action gating with namespace capability checks and identity-appropriate denial messages.

web/src/api/client.ts

NoClusterAccessBanner.tsxExplain why a connected cluster appears empty +67/-0

Explain why a connected cluster appears empty

• Shows the signed-in user and a collapsed, session-replay-protected group list when no namespace is readable, so an administrator can identify a group to bind.

web/src/components/NoClusterAccessBanner.tsx

CapabilitiesContext.tsxExpose auth state and namespaced Helm permissions +6/-1

Expose auth state and namespaced Helm permissions

• Adds an authentication-state hook and includes helmWrite in namespace capability results for Helm UI controls.

web/src/contexts/CapabilitiesContext.tsx

Bug fix (10) +126 / -237
client.goRun streaming rollbacks with user impersonation +15/-16

Run streaming rollbacks with user impersonation

• Adds an impersonated progress-reporting rollback path and shares rollback progress logic with the existing client path.

internal/helm/client.go

handlers.goAuthorize Helm operations through caller RBAC +85/-130

Authorize Helm operations through caller RBAC

• Removes Cloud-role gates, checks authenticated writes against Secret creation in the release namespace before chart retrieval, and returns 403 for forbidden release reads. Streaming rollback now uses the attached user’s identity.

internal/helm/handlers.go

tools_diagnose.goRemove the MCP probe Cloud-role gate +0/-8

Remove the MCP probe Cloud-role gate

• Allows requested in-cluster diagnose tests to reach the existing Kubernetes capability checks regardless of Cloud tier.

internal/mcp/tools_diagnose.go

tools_helm.goUse caller RBAC for MCP Helm release content +6/-26

Use caller RBAC for MCP Helm release content

• Removes Cloud-role refusals for values and revision diffs, leaving impersonated Helm reads to determine access.

internal/mcp/tools_helm.go

reachability_run.goLet Kubernetes decide in-cluster probe access +0/-15

Let Kubernetes decide in-cluster probe access

• Removes Cloud-role checks from the probe capability response and execution handler.

internal/server/reachability_run.go

ChartBrowser.tsxAllow authenticated users to refresh the chart cache +6/-7

Allow authenticated users to refresh the chart cache

• Treats repository refresh as a pod-local action for authenticated users while retaining the Helm setting requirement for no-auth installs.

web/src/components/helm/ChartBrowser.tsx

HelmCompareRoute.tsxRemove Cloud-tier gating from release comparison +1/-7

Remove Cloud-tier gating from release comparison

• Fetches and displays revision comparisons whenever a valid revision pair exists, leaving release-read authorization to Kubernetes.

web/src/components/helm/HelmCompareRoute.tsx

HelmReleaseDrawer.tsxUse release-namespace permissions in the Helm drawer +8/-26

Use release-namespace permissions in the Helm drawer

• Removes role-gated Manifest and Values panels and Cloud-tier checks on compare controls. Bases write actions on the release storage namespace.

web/src/components/helm/HelmReleaseDrawer.tsx

InstallWizard.tsxCheck install permission in the selected namespace +4/-1

Check install permission in the selected namespace

• Evaluates Helm write capability against the validated target namespace rather than a global Helm permission.

web/src/components/helm/InstallWizard.tsx

ValuesViewer.tsxScope Values editing permission to the release namespace +1/-1

Scope Values editing permission to the release namespace

• Passes the release namespace to the Helm action capability hook before enabling edits.

web/src/components/helm/ValuesViewer.tsx

Tests (11) +539 / -123
cloud_rbac_integration_read_test.yamlTest system-only integration-read rendering +10/-0

Test system-only integration-read rendering

• Adds coverage for integration-read add-ons when default Cloud tier bindings are turned off.

deploy/helm/radar/tests/cloud_rbac_integration_read_test.yaml

cloud_rbac_system_test.yamlExercise system RBAC grants and opt-outs +189/-0

Exercise system RBAC grants and opt-outs

• Tests view, cluster-read, integration-read and Secret-read resources, including disabled, absent-value, non-Cloud and operator-managed RBAC cases.

deploy/helm/radar/tests/cloud_rbac_system_test.yaml

helm_write_rbac_test.yamlTest ServiceAccount Helm write boundaries +65/-0

Test ServiceAccount Helm write boundaries

• Checks that wildcard writes render for no-auth Helm installs but not for authenticated, Cloud or Helm-disabled installs.

deploy/helm/radar/tests/helm_write_rbac_test.yaml

handlers_test.goReplace Cloud-role gate tests with RBAC-boundary tests +32/-102

Replace Cloud-role gate tests with RBAC-boundary tests

• Asserts Helm handlers do not reject Cloud viewers solely for their role and that forbidden release reads return 403.

internal/helm/handlers_test.go

helm_write_gate_test.goTest caller-scoped Helm write checks +85/-0

Test caller-scoped Helm write checks

• Uses a test SubjectAccessReview server to verify namespace and IdP-group decisions, including denial for a hub role alone and exemption for pod-local changes.

internal/helm/helm_write_gate_test.go

rollback_impersonation_test.goVerify streaming rollback impersonates its caller +41/-0

Verify streaming rollback impersonates its caller

• Checks that Kubernetes requests made by the progress-reporting rollback carry the signed-in username.

internal/helm/rollback_impersonation_test.go

integration_read_baseline_test.goValidate chart-rendered system integration bindings +19/-7

Validate chart-rendered system integration bindings

• Extends integration-read rendering expectations to radar:system and tests combinations of disabled tier, system and chart RBAC settings.

internal/k8s/integration_read_baseline_test.go

capabilities_test.goTest namespace Helm capability precedence +16/-0

Test namespace Helm capability precedence

• Verifies that a clean namespace grant or denial overrides the global Helm write capability.

internal/server/capabilities_test.go

reachability_run_test.goGuard against Cloud-role probe refusals +6/-12

Guard against Cloud-role probe refusals

• Replaces the Member-only expectation with a test that viewer roles are not themselves grounds for refusal.

internal/server/reachability_run_test.go

server_auth_test.goTest no-namespace-access reporting conditions +68/-0

Test no-namespace-access reporting conditions

• Covers connected users with zero, partial or cluster-wide namespace access, plus uncached and connecting states.

internal/server/server_auth_test.go

test-chart.shCheck cluster-read rendering with system bindings +8/-2

Check cluster-read rendering with system bindings

• Adjusts tier-disabled cases for the new system grant and checks that radar:system retains cluster-read access when default tier bindings are off.

scripts/test-chart.sh

Documentation (4) +30 / -7
README.mdDocument background identity and Helm RBAC behavior +22/-1

Document background identity and Helm RBAC behavior

• Explains the radar:system read grant, its independent opt-out and reuse-values upgrade behavior. Clarifies that authenticated Helm writes do not require a ServiceAccount-wide write grant.

deploy/helm/radar/README.md

authentication.mdExplain Cloud-role and Kubernetes authorization boundaries +2/-0

Explain Cloud-role and Kubernetes authorization boundaries

• Documents radar:system access and clarifies that Cloud roles gate Radar configuration, while Kubernetes RBAC decides impersonated Helm and probe operations.

docs/authentication.md

reachability.mdRemove the Cloud-role prerequisite for probes +1/-1

Remove the Cloud-role prerequisite for probes

• Describes in-cluster probe authorization through Kubernetes RBAC without requiring Cloud Member status.

docs/reachability.md

cloud_role.goClarify the scope of Cloud roles +5/-5

Clarify the scope of Cloud roles

• Updates the CloudRole contract to distinguish Radar-owned features from operations authorized through user impersonation.

pkg/auth/cloud_role.go

Other (7) +129 / -8
_helpers.tplAdd fail-safe system RBAC rendering helper +16/-0

Add fail-safe system RBAC rendering helper

• Enables the Cloud system binding only when cloud.systemRbac is explicitly true, leaving absent reused values off.

deploy/helm/radar/templates/_helpers.tpl

cloud-rbac-cluster-read.yamlKeep cluster-read access for radar:system +21/-3

Keep cluster-read access for radar:system

• Renders the cluster-read role and system binding independently of default Cloud tier bindings, while retaining tier-specific controls.

deploy/helm/radar/templates/cloud-rbac-cluster-read.yaml

cloud-rbac-integration-read.yamlBind integration-read add-ons to radar:system +8/-2

Bind integration-read add-ons to radar:system

• Keeps namespaced and cluster integration-read add-ons available when tier bindings are disabled. The system binding has no legacy cloud:system subject.

deploy/helm/radar/templates/cloud-rbac-integration-read.yaml

cloud-rbac-system.yamlGrant background jobs read-only Kubernetes access +60/-0

Grant background jobs read-only Kubernetes access

• Adds radar:system bindings to Kubernetes view and a dedicated get/list/watch Secrets role, without granting writes.

deploy/helm/radar/templates/cloud-rbac-system.yaml

clusterrole.yamlLimit ServiceAccount-wide Helm writes to no-auth installs +4/-1

Limit ServiceAccount-wide Helm writes to no-auth installs

• Emits the wildcard write rule only when Helm is enabled and neither authentication nor Cloud mode is active.

deploy/helm/radar/templates/clusterrole.yaml

values.schema.jsonValidate cloud.systemRbac as a boolean +1/-0

Validate cloud.systemRbac as a boolean

• Adds the new system RBAC value to the chart schema.

deploy/helm/radar/values.schema.json

values.yamlDefault system RBAC on for fresh installs +19/-2

Default system RBAC on for fresh installs

• Introduces cloud.systemRbac with an explanation of its read scope and upgrade behavior. Clarifies how Helm RBAC differs between authenticated and no-auth installs.

deploy/helm/radar/values.yaml

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit c0f51b9. Configure here.

Comment thread internal/helm/handlers.go
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (3) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (1) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Helm comments retain upgrade history 📘 Rule violation ⚙ Maintainability
Description
The new radar.cloudSystemRbac template comment describes a --reuse-values upgrade from a release
that predates the setting. The same release-history explanation appears in the values file and chart
test, putting migration history in code comments rather than only in release documentation.
Code

deploy/helm/radar/templates/_helpers.tpl[R78-79]

+value means OFF: a `--reuse-values` upgrade from a release that predates the
+key renders with the previous release's tree and never gains Secret read
Evidence
The cited added comments explicitly describe upgrades from earlier releases; rule 3036538 disallows
change-history references in code comments.

Rule 3036538: Disallow references to tickets or PR history in code comments
deploy/helm/radar/templates/_helpers.tpl[75-82]
deploy/helm/radar/values.yaml[534-535]
deploy/helm/radar/tests/cloud_rbac_system_test.yaml[109-111]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Added Helm comments describe releases that predate `systemRbac`, contrary to the code-comment rule.
## Fix Focus Areas
- deploy/helm/radar/templates/_helpers.tpl[75-82]
- deploy/helm/radar/values.yaml[534-535]
- deploy/helm/radar/tests/cloud_rbac_system_test.yaml[109-111]
## Recommended Fix
Keep the security rationale and absent-value behavior in code comments, but move release-specific upgrade history to release documentation.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Helm errors use a nonstandard log format 📘 Rule violation ◔ Observability
Description
requireHelmWrite logs a failed permission check with %s in %q: %v instead of the required
%s/%s: %v structure. When the check returns an error, that log precedes the new HTTP 500 response
but does not identify its operands in the standardized format.
Code

internal/helm/handlers.go[R1233-1234]

+			log.Printf("[helm] Failed to check Helm permissions for %s in %q: %v", k8s.SanitizeForLog(user.Username), k8s.SanitizeForLog(namespace), err)
+			writeError(w, http.StatusInternalServerError, "failed to check permissions: "+err.Error())
Evidence
The added branch logs Failed to check Helm permissions for %s in %q: %v before writing a 500; rule
3036628 requires the specified %s/%s: %v structure.

Rule 3036628: Log 500 errors with standardized module/action format before writing the response
internal/helm/handlers.go[1231-1235]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new Helm permission-check 500 path logs with a format that does not match the required module/action and `%s/%s: %v` structure.
## Fix Focus Areas
- internal/helm/handlers.go[1231-1235]
## Recommended Fix
Log the triggering error before writing the response using the required structure, with appropriate sanitized values for the two `%s` operands.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Auth requests skip cluster readiness checks 📘 Rule violation ☼ Reliability
Description
handleAuthMe now reads connection state and s.permCache without calling s.requireConnected(w).
When the cluster is disconnected, the handler continues to return its normal auth response, while
its connected path uses cluster-derived permissions to set noNamespaceAccess.
Code

internal/server/server.go[R5158-5160]

+		if k8s.IsConnected() {
+			if perms := s.permCache.Get(user.Username, user.Groups); perms != nil && noNamespaceAccess(auth.FilterNamespacesForUser(nil, user, perms)) {
+				resp["noNamespaceAccess"] = true
Evidence
The added code accesses connection state and the permission cache in handleAuthMe, which has no
s.requireConnected(w) call. Rule 3036634 requires that gate before a modified handler accesses
cluster-backed state.

Rule 3036634: Cluster-touching HTTP handlers must enforce connectivity with requireConnected
internal/server/server.go[5148-5165]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The changed auth handler consults cluster-derived cache state without the required connectivity gate, although the auth response must remain available during initial connection.
## Fix Focus Areas
- internal/server/server.go[5148-5162]
## Recommended Fix
Move the cache-dependent access signal to a connected-only handler that calls `s.requireConnected(w)` before reading cluster state; preserve the preconnection auth response used by startup.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View medium (4)
4. Group chips bypass shared badge styling ✓ Resolved
Description
NoClusterAccessBanner renders each group as a custom styled ` rather than a shared Badge`. When
a user expands the group list, these pill-shaped labels use locally specified appearance classes
instead of the badge component's semantic styling.
Code

web/src/components/NoClusterAccessBanner.tsx[R53-55]

+                  <span
+                    key={g}
+                    className="inline-flex items-center rounded-md bg-theme-elevated px-1.5 py-0.5 font-mono text-[11px] text-theme-text-secondary ring-1 ring-theme-border"
Evidence
The added group list maps identifiers to pill-shaped spans with background and text classes. Rule
3036677 requires badge-like UI to use the shared Badge component rather than locally styled labels.

Rule 3036677: Use Badge components instead of hard-coded badge color strings
web/src/components/NoClusterAccessBanner.tsx[50-59]
packages/k8s-ui/src/components/ui/Badge.tsx[31-45]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new group chips are badge-like labels implemented with local styling instead of the shared Badge component.
## Fix Focus Areas
- web/src/components/NoClusterAccessBanner.tsx[52-59]
## Recommended Fix
Render each group with the shared `Badge` component and an appropriate semantic appearance prop while preserving the group text and capture masking.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Helm buttons enable in denied namespaces 🐞 Bug ≡ Correctness
Description
useCanHelmAct accepts canHelmWrite from useNamespacedCapabilities, which falls back to the
global permission while the namespace check is pending or has failed. When the user can create
Secrets in the default namespace but not the selected release namespace, write controls become
clickable until that check resolves, or remain clickable after a failed check, before the backend
rejects the action.
Code

web/src/contexts/CapabilitiesContext.tsx[165]

+    canHelmWrite: nsCaps?.helmWrite ?? globalCaps.helmWrite,
Evidence
The global check can use the effective namespace as a fallback, so its grant does not establish
permission in another namespace. The new namespaced Helm value falls back to that global grant
without accounting for the query state; the backend independently checks the requested namespace.

internal/k8s/capabilities.go[540-566]
web/src/api/client.ts[2124-2141]
web/src/contexts/CapabilitiesContext.tsx[149-168]
web/src/api/client.ts[2217-2232]
internal/helm/handlers.go[1226-1246]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Helm write controls can use a global grant while the selected namespace's capability request is pending or has failed.
## Fix Focus Areas
- web/src/contexts/CapabilitiesContext.tsx[149-168]
- web/src/api/client.ts[2217-2232]
## Recommended Fix
Expose the namespace check's pending and error states to the Helm action hook. For an authenticated user and a concrete namespace, keep writes disabled unless that namespace's Helm permission has been successfully verified.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Users without access lose the warning 🐞 Bug ≡ Correctness
Description
handleAuthMe omits noNamespaceAccess whenever the permission-cache entry is absent, and the
banner polls only while its last response contains that flag. If its first refetch precedes
namespace discovery, or a poll lands after the cache entry expires, the banner disappears or never
appears and stops checking despite the user still having no readable namespace.
Code

web/src/api/client.ts[2164]

+    refetchInterval: (query) => (query.state.data?.noNamespaceAccess ? 60000 : false),
Evidence
The endpoint only reads an existing cache entry; discovery occurs in resource requests, and
permission-cache entries expire after two minutes. A response without the flag disables the new
query interval, while the banner renders only when the flag is true.

internal/server/server.go[5154-5162]
internal/server/server.go[5250-5298]
pkg/auth/permissions.go[136-159]
web/src/components/NoClusterAccessBanner.tsx[10-21]
web/src/api/client.ts[2157-2165]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
An absent cached permission result disables polling of the no-access banner, even though absence does not establish that the user has access.
## Fix Focus Areas
- internal/server/server.go[5148-5162]
- web/src/api/client.ts[2157-2165]
- web/src/components/NoClusterAccessBanner.tsx[10-21]
## Recommended Fix
Distinguish an unverified namespace result from verified access. Keep rechecking while access is unverified or denied, or arrange for namespace discovery to complete before reporting the banner state.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Cloud operators miss Secret grants 🔗 Cross-repo conflict ≡ Correctness
Description
cloud-rbac-system.yaml grants radar:system cluster-wide Secret read independently of the Cloud
tier bindings, but radar-docs still says disabling every tier leaves no add-on grants rendered. When
an operator follows the published full-IdP setup, that guidance omits a chart-managed grant they
must account for in their access review.
Code

deploy/helm/radar/templates/cloud-rbac-system.yaml[R42-44]

+  - apiGroups: [""]
+    resources: ["secrets"]
+    verbs: ["get", "list", "watch"]
Evidence
The new template binds the system group to cluster-wide Secret read. The separate published Cloud
RBAC page tells full-IdP operators that disabling all tiers leaves no add-on grants, while the sync
mapping shows that page is not regenerated from the changed authentication guide.

radar -> radar-docs
deploy/helm/radar/templates/cloud-rbac-system.yaml[19-44]
docs/authentication.md[235-236]
External repo: skyhook-dev/radar-docs, cloud/rbac.mdx [190-198]
External repo: skyhook-dev/radar-docs, scripts/sync-oss-docs.mjs [4-20]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The published full-IdP guidance says disabling all Cloud tiers removes the add-on grants, but the new independent `radar:system` binding includes cluster-wide Secret read.
## Fix Focus Areas
- /cross_repos/radar-docs/cloud/rbac.mdx[190-198]
- deploy/helm/radar/templates/cloud-rbac-system.yaml[19-44]
- docs/authentication.md[235-236]
## Recommended Fix
Coordinate an update to the curated Cloud RBAC page in radar-docs. Distinguish human tier grants from the independent background-identity grant, document its Secret access and `cloud.systemRbac=false` opt-out, and retain the accurate explanation in the synchronized authentication guide.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can describe a rule in plain language on the Rules page and Qodo drafts it for you

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +78 to +79
value means OFF: a `--reuse-values` upgrade from a release that predates the
key renders with the previous release's tree and never gains Secret read

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. Helm comments retain upgrade history 📘 Rule violation ⚙ Maintainability

The new radar.cloudSystemRbac template comment describes a --reuse-values upgrade from a release
that predates the setting. The same release-history explanation appears in the values file and chart
test, putting migration history in code comments rather than only in release documentation.
Agent Prompt
## Issue description
Added Helm comments describe releases that predate `systemRbac`, contrary to the code-comment rule.

## Fix Focus Areas
- deploy/helm/radar/templates/_helpers.tpl[75-82]
- deploy/helm/radar/values.yaml[534-535]
- deploy/helm/radar/tests/cloud_rbac_system_test.yaml[109-111]

## Recommended Fix
Keep the security rationale and absent-value behavior in code comments, but move release-specific upgrade history to release documentation.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping it. The comment explains why an absent value means off, which is the logic, not history.

Comment thread internal/helm/handlers.go
Comment thread internal/server/server.go
Comment thread web/src/components/NoClusterAccessBanner.tsx Outdated
Comment thread web/src/contexts/CapabilitiesContext.tsx
Comment thread web/src/api/client.ts
Comment thread deploy/helm/radar/templates/cloud-rbac-system.yaml
Preview reads the release as the caller and can fetch a chart version from a
repository. It lost its Cloud-role gate without gaining the write check, so a
caller who could read a release but not change it could still make Radar
fetch a repository through it. It now runs requireHelmWrite first, as
install does.
Group names are neutral data fragments, which is the structural tone. The
chips keep their look and now follow any retune of that tone.
The install wizard waited on every keystroke's /capabilities call; it now
checks the namespace after typing pauses. useHelmManifest lost its unused
enabled param, the compare and drawer views lose the indentation the
removed RoleGatedPanel left behind, and comments that still described the
role gate for Helm are corrected.

@nadaverell nadaverell left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@nadaverell
nadaverell merged commit ed5eb1c into skyhook-io:main Oct 4, 2026
10 checks passed
nadaverell added a commit that referenced this pull request Oct 4, 2026
…, no-access banner (#1971)

Follow-up to #1899: closes the gaps from its review. RAD-578.

## Must-fix

- **Registered OCI chart sources are owner-only in Radar Cloud.**
Upgrade discovery picks the highest version across all registered
prefixes, so any Cloud user, even one with no cluster RBAC, could add a
prefix that feeds someone else's upgrade. Add and remove now take the
owner gate, like the rest of Radar's own config, and the Track chart
source dialog hides the controls from non-owners.
- **Under Cloud, a request with no role group is refused by owner-gated
endpoints.** "No role" skips the gate so OSS keeps its own config, but
under Cloud it meant the Hub's `radar:system` identity passed owner-only
settings. OSS behaviour is unchanged.
- **Per-user capability caches are keyed on username and groups.** They
now decide Helm writes. Keyed by username alone, a user who lost an IdP
group kept the old answer for up to a minute.

## Smaller fixes

- Release reads map only a Kubernetes denial to 403. A chart
repository's own 401/403 is no longer reported as the caller's RBAC.
- When Kubernetes refuses the Helm release listing for issues, it is
logged once per identity, so missing Helm alerts leave a trace.
- The no-access banner asks `/api/auth/me?check=namespaces`, which runs
namespace discovery, so it can't vanish when the permission cache
expires. It keeps its last definite answer through a check that couldn't
decide, and polls until access is confirmed.
- Helm actions stay disabled until the per-namespace permission check
succeeds, including when it fails.

## Tests

- New: a user losing a group (end to end through `requireHelmWrite`);
the Cloud no-role deny; the owner gate on chart sources; repository
401/403 vs RBAC denial; `Impersonate-Group` on streaming rollback; the
banner's discovery path failing closed.
- `go test` (both modules), `tsc`, and web vitest pass.

## Verified on EKS (radar-test-nonprod), proxy auth with simulated users

- Banner still shown 150 s in, past the permission cache expiry; it
cleared by itself 64 s after a binding was added.
- A Hub `viewer` whose IdP group grants `edit` in one namespace: Helm
rollback and uninstall work there and are refused elsewhere. Dropping
the group takes effect on the next request.
- A user who can list pods but not Secrets: the Helm-denied log line
appears once across repeated calls.
- Not live-testable locally: the Cloud no-role deny and the owner gate
under real Cloud mode, which only accepts requests over the Hub tunnel.
Unit tests cover both.

Docs: skyhook-dev/radar-docs#129 (owner-only chart sources).

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes authorization for Cloud config (OCI sources, no-tier denial)
and Helm write caching keyed on groups; incorrect behavior would block
legitimate admins or briefly allow stale permissions, but scope is
bounded and covered by new tests.
> 
> **Overview**
> Follow-up to #1899 that tightens **Radar Cloud config auth**,
**Helm/RBAC correctness**, and the **no-namespace-access** UX.
> 
> **Cloud config:** Registered OCI chart sources are now **owner-only**
in Cloud (`requireHelmSourceConfigWrite`), with matching UI in the
track-chart-source dialog. In Cloud mode, callers **without** a
`radar:owner|member|viewer` tier (e.g. Hub `radar:system`) are
**denied** on tier-gated Radar config instead of bypassing the gate like
OSS.
> 
> **RBAC caching & Helm:** Per-user capability caches (including
namespace Helm-write SARs) are keyed by **username + groups** via
`IdentityCacheKey`, so revoking an IdP group stops reusing stale “can
write” for up to the TTL. Release read errors map to **403 only for
Kubernetes RBAC** (`isReleaseReadForbidden`), not chart-repo 401/403.
Helm issues omit logging is **once per identity** when Secret list is
forbidden.
> 
> **No-access banner:** `/api/auth/me?check=namespaces` runs namespace
discovery; the banner polls that path, keeps the last **boolean**
`noNamespaceAccess`, and Helm actions stay disabled while per-namespace
write capability is **unknown** (`helmWriteUnknown`).
> 
> Docs note owner-only chart sources and no-role Cloud denial on config.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
7c39b11. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
nadaverell added a commit that referenced this pull request Oct 5, 2026
… for background Diagnose (#1949)

Linear: RAD-657

## Why

Radar Cloud's AI Diagnose is blind on clusters whose role bindings are
off (`cloud.defaultRbac.create=false`, IdP groups decide access),
because it reads as `radar:viewer`, which is unbound there. After this
release:
- **Manual** Diagnose runs read as the person who started them
(skyhook-dev/radar-hub#292), so the AI never exceeds that person's
access. A person may be able to read Secrets, so Helm diffs handed to
the model must not carry Secret values.
- **Background** (alert-triggered) runs read as one view-only identity,
the `radar:ai` group (skyhook-dev/radar-hub#288), which this chart
binds.

MCP clients are not affected: they keep reading with the user's own
identity.

## What changes

**1. Secret redaction in `get_helm_release` (every MCP caller)**
- `include=diff` (the manifest diff) and `include=notes_diff` were
returned unredacted; only `values_diff` went through `RedactSecrets`.
- Each `kind: Secret` document now has its `data` / `stringData` values
replaced by `[REDACTED]` before diffing. The keys stay, so added and
removed keys still show. Secrets inside `kind: List` items are covered,
the `last-applied-configuration` annotation (a full copy of the object)
is dropped, and a document that doesn't parse is withheld whole. Other
documents are left byte-for-byte untouched.
- `RedactSecrets` then runs over both diffs as a second layer.
- `include=values` and `include=values_diff` now use key-aware redaction
built for Helm values (`aicontext.RedactHelmValues`). Chart authors name
credentials freely (`dbPassword`, `auth.postgresPassword`), so any key
ending in `password`, `token`, `apiKey`, `secretKey`, `clientSecret`,
`credentials` and similar is masked, numbers included. Secret
*references* (`existingSecret`, `*SecretName`, `*Ref`) keep their
values. The values diff masks both revisions before diffing. CRD specs
keep the existing exact-key matching.
- The REST/UI Helm diff is unchanged: it is the user's own view.

**2. `radar:ai` chart binding**
- New `templates/cloud-rbac-ai.yaml`: binds `radar:ai` to `view`. No
Secret role, no writes.
- `radar:ai` is added to the cluster-read and integration-read add-ons.
- New value `cloud.aiRbac`, default `true`, independent of
`cloud.defaultRbac`. As with `cloud.systemRbac`, an absent value means
off, so a `--reuse-values` upgrade never gains the binding silently.
- Docs: the chart README and `docs/authentication.md`.

The binding does nothing until Radar Cloud sends the group. Background
runs send `radar:ai` with no tier group, so the AI's access is exactly
this binding: `cloud.aiRbac=false` really turns it off, and a widened
viewer role doesn't widen the AI. Clusters on older charts get no
background Diagnose until they upgrade. Radar Cloud refuses manual runs
on a Radar older than this release, rather than reading with less
protection.

## Tests
- Go unit tests for Helm values redaction: nested keys, numeric
credentials, references kept under `credentials`, both revisions of a
values diff.
- Go unit tests for the manifest-diff redaction: Secret values gone,
keys and non-Secret changes kept, last-applied copies, Secrets inside
Lists, and unparseable documents. Plus a check that `resource_diff`
masks Secret values.
- `TestIntegrationReadBindings` expects the `radar:ai` bindings
alongside `radar:system`, with cases for `aiRbac=false`.
- New `tests/cloud_rbac_ai_test.yaml`: renders with
`defaultRbac.create=false`, absent or `false` means off, bound to `view`
only, add-ons present whatever the tier settings.
- `scripts/test-chart.sh`: new render cases; the "no orphan role" cases
now also set `cloud.aiRbac=false`.
- `helm unittest`: the `service_test.yaml` failures also fail on `main`
locally; CI's Helm chart job passes.

### Live test on EKS (proxy auth, simulated Diagnose identities)
- **Background identity** (`radar:ai,radar:org:<org>`) with the chart's
`radar:ai` binding: pods in every namespace, no Secrets.
- **Manual run as a contractor** whose IdP group has `view` in one
namespace: pods in that namespace only; listing all namespaces returns
only that one.
- **Helm release with a Secret, a token in its notes, and `dbPassword` /
`apiKey` values**, read through `get_helm_release` by a user who can
read Secrets: no sentinel value in `values`, `diff`, `notes_diff` or
`values_diff`. The REST diff (the user's own view in the UI) still shows
them, as intended.

### End to end on kind
- **Role bindings off, `aiRbac=true`:** the AI identity reads pods,
events and nodes; Secrets are forbidden.
- **`aiRbac=false`:** it reads nothing.
- **`get_helm_release include=diff,notes_diff`, called by an owner who
can read Secrets:** no Secret values in the output. The #1899 binary
returns the plaintext values and a `ghp_` token from the notes.


<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes cluster RBAC defaults and how credentials appear in MCP Helm
diffs; misconfiguration could block background Diagnose or leak secrets
if redaction regresses, but grants stay read-only and UI paths are
unchanged.
> 
> **Overview**
> Adds **default Kubernetes RBAC for automatic Radar Cloud Diagnose**
via new `cloud.aiRbac` (default on): chart-owned aggregated **view**
role for `radar:ai`, plus cluster-read and integration-read add-ons—**no
Secret access**. Refactors `radar:system` the same way (owned aggregated
view role instead of binding directly to `view`), with Secret read
unchanged for alerts/timeline. Docs and Helm tests cover independence
from `cloud.defaultRbac`, `--reuse-values` absent-key behavior, and
installer `escalate` requirements.
> 
> **Hardens Helm MCP output for AI callers** while leaving REST/UI diffs
as the user’s own view: manifest diffs strip Secret `data`/`stringData`
(and unparseable Secret docs) before diffing; values diffs use new
**`RedactHelmValues`** (suffix/key-aware credentials, preserved secret
*references*); notes diffs get pattern redaction. `get_helm_release`
`include=diff`, `values_diff`, and `notes_diff` route through these
paths.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
16be4f7. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Nadav Erell <nadaverell@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants