# REPRO-2026-00226: Grafana IAM LIST authorization could be bypassed for folder-scoped CRDs when a user had wildcard resource permissions, returning `All: true` without folder-level checks. ## Summary Status: published Severity: high Type: security Confidence: high ## Identifiers REPRO ID: REPRO-2026-00226 ## Package Name: grafana/grafana Ecosystem: Go Affected: Versions prior to commit b9b897b3c512ee434341bb9d698eac24f90eca89 (folder-scoped LIST authz check occurs after wildcard scope check) Fixed: b9b897b3c512ee434341bb9d698eac24f90eca89 ## Root Cause ## Summary Grafana IAM LIST authorization for folder-scoped Kubernetes-native/custom resources could be bypassed when the requesting identity had a resource-type wildcard permission (`scope: "*"`) for the CRD action but lacked folder-level authorization. In the vulnerable parent of fix commit `b9b897b3c512ee434341bb9d698eac24f90eca89`, `pkg/services/authz/rbac.Service.listPermission` returned `ListResponse{All: true}` as soon as `scopeMap["*"]` was present, before detecting mapper-miss resources such as `widget.ext.grafana.app/widgets` and before applying the folder-scoped authorization model. The current reproduction starts a real TCP gRPC `authz.v1.AuthzService` endpoint, sends a LIST request over `/authz.v1.AuthzService/List`, and shows that the vulnerable commit returns `All=true` while the fixed commit returns `All=false` for the same request and permission state. ## Impact - **Package/component affected:** `github.com/grafana/grafana`, specifically `pkg/services/authz/rbac` and the Grafana IAM/AuthZ gRPC LIST authorization path. - **Affected versions:** Builds containing the pre-fix behavior before `b9b897b3c512ee434341bb9d698eac24f90eca89` (`IAM: folder-scoped authz with LIST (#126931)`). The reproduction anchors the vulnerable checkout to the fixed commit's parent: `27750f0e0e3443c39f992bfe22efe3d352ee4357`. - **Risk level and consequences:** High. A user or service identity with only a wildcard resource permission for a folder-scoped CRD action (for example `widget.ext.grafana.app/widgets:get` with `scope: "*"`) could receive `All: true` from the AuthZ LIST API. That response authorizes listing across all folders even when the identity has no folder-level access, bypassing folder-scoped authorization boundaries. ## Impact Parity - **Disclosed/claimed maximum impact:** Authorization bypass for folder-scoped CRD LIST authorization through the Grafana IAM/AuthZ API surface. - **Reproduced impact from this run:** Authorization bypass was reproduced over the real gRPC AuthzService LIST API boundary. The vulnerable commit returned `ListResponse All=true` for a folder-scoped mapper-miss CRD when the subject had only wildcard resource permission and no folder authorization. The fixed commit returned `All=false` for the same request. - **Parity:** `full` for the claimed authorization-bypass behavior and API surface. - **Not demonstrated:** The proof does not enumerate actual end-user CRD objects in a full Grafana deployment. It demonstrates the authorization decision primitive (`All=true`) that the LIST caller would use to list all objects. ## Root Cause The root cause is an ordering error in `pkg/services/authz/rbac/service.go` inside `Service.listPermission`. Before the fix, `listPermission` checked `scopeMap["*"]` before distinguishing mapper-hit and mapper-miss resources. Grafana's scope-map construction collapses wildcard grants into `scopeMap["*"]`; for ordinary resources that can be an all-resource allow. However, folder-scoped Kubernetes-native CRDs that miss the static mapper (for example groups ending in `.ext.grafana.app`) require a stricter model: the caller must satisfy both stack/resource permission and folder-level authorization. Because the vulnerable implementation returned `ListResponse{All: true}` immediately when `scopeMap["*"]` was present, folder-scoped CRDs skipped the `listPermissionWithFolderAuthz` logic entirely. The fix in `b9b897b3c512ee434341bb9d698eac24f90eca89` moves the mapper-miss/folder-scoped branch before the wildcard early return: - Vulnerable parent `27750f0e0e3443c39f992bfe22efe3d352ee4357`: `scopeMap["*"]` can return `All=true` before folder authorization. - Fixed commit `b9b897b3c512ee434341bb9d698eac24f90eca89`: mapper-miss resources first call `listPermissionWithFolderAuthz`, so wildcard resource permission alone does not yield `All=true`. Fix commit: `https://github.com/grafana/grafana/commit/b9b897b3c512ee434341bb9d698eac24f90eca89` ## Reproduction Steps 1. Use `bundle/repro/reproduction_steps.sh`. 2. The script reads `bundle/project_cache_context.json`, reuses the prepared Grafana checkout at `/repo`, resolves the fixed commit and its parent, and verifies that the vulnerable parent lacks the `listPermissionWithFolderAuthz` fork while the fixed commit contains it. 3. The script writes and injects `bundle/repro/repro_grpc_boundary_test.go` into `pkg/services/authz/rbac` for each checkout. That test starts a real TCP gRPC server, registers Grafana's real `rbac.Service` via `authzv1.RegisterAuthzServiceServer`, performs a TCP health check, and sends an attacker-controlled `ListRequest` over `/authz.v1.AuthzService/List`. 4. The request uses `namespace=org-12`, `subject=user:test-uid`, `group=widget.ext.grafana.app`, `resource=widgets`, `verb=list`. The configured identity has only `widget.ext.grafana.app/widgets:get` with `scope="*"` and no folder-level permission. 5. The script runs two vulnerable attempts and two fixed attempts. Expected evidence is vulnerable `All=true` in both attempts and fixed `All=false` in both attempts. ## Evidence Primary evidence files: - `bundle/logs/reproduction_steps.log` — current-run summary and request/response excerpts for all four attempts. - `bundle/logs/vuln_grpc_attempt1.log` and `bundle/logs/vuln_grpc_attempt2.log` — vulnerable API-boundary request/response logs. - `bundle/logs/fixed_grpc_attempt1.log` and `bundle/logs/fixed_grpc_attempt2.log` — fixed negative-control API-boundary request/response logs. - `bundle/repro/runtime_manifest.json` — runtime evidence manifest showing `entrypoint_kind="api_remote"`, `service_started=true`, `healthcheck_passed=true`, and `target_path_reached=true`. - `bundle/repro/repro_grpc_boundary_test.go` — the gRPC boundary test code used by the script. Key excerpts from `bundle/logs/reproduction_steps.log`: ```text Patch check: vulnerable parent lacks listPermissionWithFolderAuthz fork; fixed commit contains it VULNERABLE attempt 1 request/response evidence: SERVER: Grafana AuthzService gRPC endpoint listening on 127.0.0.1:39885 HEALTHCHECK: TCP connection to Grafana AuthzService endpoint 127.0.0.1:39885 succeeded CLIENT: sending LIST over gRPC /authz.v1.AuthzService/List namespace=org-12 subject=user:test-uid group=widget.ext.grafana.app resource=widgets verb=list permission=widget.ext.grafana.app/widgets:get scope=* no_folder_permission=true SERVER: accepted gRPC method=/authz.v1.AuthzService/List namespace=org-12 subject=user:test-uid group=widget.ext.grafana.app resource=widgets verb=list token_prefix=pruva SERVER: completed gRPC method=/authz.v1.AuthzService/List response All=true Folders=[] Items=[] err= CLIENT: received ListResponse: All=true Folders=[] Items=[] FIXED attempt 1 request/response evidence: SERVER: Grafana AuthzService gRPC endpoint listening on 127.0.0.1:43707 HEALTHCHECK: TCP connection to Grafana AuthzService endpoint 127.0.0.1:43707 succeeded CLIENT: sending LIST over gRPC /authz.v1.AuthzService/List namespace=org-12 subject=user:test-uid group=widget.ext.grafana.app resource=widgets verb=list permission=widget.ext.grafana.app/widgets:get scope=* no_folder_permission=true SERVER: accepted gRPC method=/authz.v1.AuthzService/List namespace=org-12 subject=user:test-uid group=widget.ext.grafana.app resource=widgets verb=list token_prefix=pruva SERVER: completed gRPC method=/authz.v1.AuthzService/List response All=false Folders=[] Items=[] err= CLIENT: received ListResponse: All=false Folders=[] Items=[] Summary: Vulnerable attempt 1: All=true Vulnerable attempt 2: All=true Fixed attempt 1: All=false Fixed attempt 2: All=false CONFIRMED: vulnerable Grafana AuthzService gRPC LIST returns All=true across the remote API boundary, while the fixed commit returns All=false. ``` Environment details captured: - Go toolchain: `go version go1.26.4 linux/amd64`. - Repository path: `/data/pruva/project-cache/6c6f6fd2-6e61-4267-8db1-032ee6a303f9/repo` from the prepared project cache. - Vulnerable commit: `27750f0e0e3443c39f992bfe22efe3d352ee4357`. - Fixed commit: `b9b897b3c512ee434341bb9d698eac24f90eca89`. ## Recommendations / Next Steps - Keep the fix's ordering: route mapper-miss/folder-scoped resources to `listPermissionWithFolderAuthz` before honoring `scopeMap["*"]`. - Add regression coverage that exercises the public gRPC `AuthzService/List` boundary for folder-scoped CRDs with wildcard resource permission but no folder permission. - Audit other authorization paths for wildcard early returns before resource-specific or folder-specific constraints are applied. - Upgrade Grafana deployments to a version containing `b9b897b3c512ee434341bb9d698eac24f90eca89` or an equivalent backport. ## Additional Notes - The reproduction script was run twice consecutively in this workspace and succeeded both times. - Each script run performs two vulnerable attempts and two fixed attempts, so the final current-run evidence contains four gRPC request/response executions. - The proof uses the real Grafana AuthZ gRPC service handler and protobuf client/server boundary. Test scaffolding supplies deterministic identity/permission/folder data so the vulnerable authorization branch is reached repeatably without requiring an external Grafana deployment. ## Reproduction Details Reproduced: 2026-07-04T19:53:31.434Z Duration: 1321 seconds Tool calls: 277 Turns: Unknown Handoffs: 3 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00226 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00226&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00226/artifacts/bundle/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - Source: https://github.com/spaceraccoon/vulnerability-spoiler-alert/issues/292 ## Artifacts - bundle/repro/reproduction_steps.sh (reproduction_script, 17767 bytes) - bundle/repro/rca_report.md (analysis, 9290 bytes) - bundle/artifact_promotion_manifest.json (other, 5523 bytes) - bundle/repro/repro_bypass_test.go (other, 3888 bytes) - bundle/logs/vuln_test.log (log, 531 bytes) - bundle/logs/fixed_test.log (log, 479 bytes) - bundle/repro/validation_verdict.json (other, 781 bytes) - bundle/repro/runtime_manifest.json (other, 968 bytes) - bundle/logs/reproduction_steps.log (log, 6845 bytes) - bundle/logs/vuln_grpc_attempt1.log (log, 1275 bytes) - bundle/logs/fixed_grpc_attempt1.log (log, 1277 bytes) - bundle/logs/vuln_grpc_attempt2.log (log, 1275 bytes) - bundle/logs/fixed_grpc_attempt2.log (log, 1277 bytes) - bundle/repro/repro_grpc_boundary_test.go (other, 6057 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00226 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00226/artifacts/bundle/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00226 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://pruva.dev