Authorization Bypass / Access Control

MEDIUM
grafana/grafana
Commit: e0d5ef9a56c3
Affected: Grafana 12.0.0 through 12.4.0 (inclusive)
2026-07-09 14:31 UTC

Description

The commit adds a wrapper around several Annotation-related API handlers to override 4xx error responses, ensuring proper access-control behavior. Specifically, it enforces 403 Forbidden for requests lacking the necessary organization-scoped permissions (e.g., annotations:read with organization scope) instead of leaking information via generic 4xx responses. This prevents unauthorized access or information leakage through ambiguous 4xx errors when accessing custom annotation routes.

Proof of Concept

Proof of Concept (Grafana 12.x)\nPrerequisites:\n- A Grafana 12.x deployment with Annotations API enabled.\n- A user token that does NOT have the organization-scoped annotations:read permission.\n\nExploit (pre-fix behavior illustration):\n1) Obtain a token for a user without organization scope (TOKEN_NO_ORG).\n2) Attempt to read annotation data/tags:\n curl -s -S -D - -H "Authorization: Bearer TOKEN_NO_ORG" https://grafana.example.com/api/annotations/tags -o /dev/null -w "%{http_code}\n"\n\nExpected on vulnerable builds (before the fix): HTTP 404 Not Found or another 4xx/empty body, which can leak resource existence or allow enumeration due to non-specific error handling.\n\nExploit after the fix:\n3) Re-run the same request with the same TOKEN_NO_ORG:\n curl -s -S -D - -H "Authorization: Bearer TOKEN_NO_ORG" https://grafana.example.com/api/annotations/tags -o /dev/null -w "%{http_code}\n"\n\nExpected on fixed builds: HTTP 403 Forbidden with a diagnostic message indicating missing organization-scoped permission (e.g., "requires the annotations:read permission with the organization scope"). This confirms proper authorization handling and prevents information leakage via 4xx responses.\n\nOptionally, validate with a token that has the proper scope to ensure that lack of scope is what triggers the 403 rather than other failures.

Commit Details

Author: Jessica Liu

Date: 2026-07-09 13:54 UTC

Message:

MT Annotations: override 4xx response for custom routes (#128075)

Triage Assessment

Vulnerability Type: Authorization bypass / Access control

Confidence: MEDIUM

Reasoning:

The commit wraps key API handlers to override 4xx error responses and enforce proper authorization responses (e.g., 403 when lacking organization scope). The test confirms an access-control change, suggesting a fix to prevent unauthorized access or leakage of internal errors through generic 4xx responses.

Verification Assessment

Vulnerability Type: Authorization Bypass / Access Control

Confidence: MEDIUM

Affected Versions: Grafana 12.0.0 through 12.4.0 (inclusive)

Code Diff

diff --git a/pkg/registry/apps/annotation/register.go b/pkg/registry/apps/annotation/register.go index b98b5c5759e89..ce3b337233cba 100644 --- a/pkg/registry/apps/annotation/register.go +++ b/pkg/registry/apps/annotation/register.go @@ -136,10 +136,10 @@ func NewAppInstaller( // We could consider combining the TagProvider with the Store interface to avoid this type assertion? return nil, fmt.Errorf("store does not implement TagProvider, cannot serve tags API") } - tagHandler := newTagsHandler(tagProvider, accessClient, installer.tracer, installer.metrics, logger) + tagHandler := withAPIStatusErrorResponse(newTagsHandler(tagProvider, accessClient, installer.tracer, installer.metrics, logger)) // Create the search handler - searchHandler := newSearchHandler(instrumentedStore, accessClient, folderResolver, installer.tracer, installer.metrics, logger) + searchHandler := withAPIStatusErrorResponse(newSearchHandler(instrumentedStore, accessClient, folderResolver, installer.tracer, installer.metrics, logger)) // Create the graphite handler graphiteHandler := withAPIStatusErrorResponse(newGraphiteHandler(installer.k8sAdapter, installer.tracer, installer.metrics, logger)) diff --git a/pkg/tests/apis/annotations/annotations_test.go b/pkg/tests/apis/annotations/annotations_test.go index 078b5f2b81700..5d1c8691b31e1 100644 --- a/pkg/tests/apis/annotations/annotations_test.go +++ b/pkg/tests/apis/annotations/annotations_test.go @@ -254,6 +254,19 @@ func TestIntegrationAnnotationTags(t *testing.T) { for _, tag := range rsp.Result.Tags { require.Contains(t, tag.Tag, "env") } + + // A caller without organization annotation read is rejected with the handler's 403. + errorRsp := apis.DoRequest(helper, apis.RequestParams{ + User: helper.Org1.None, + Path: basePath, + }, &metav1.Status{}) + require.Equal(t, http.StatusForbidden, errorRsp.Response.StatusCode) + + status := errorRsp.Result + require.NotNil(t, status) + require.Equal(t, int32(http.StatusForbidden), status.Code) + require.Equal(t, metav1.StatusReasonForbidden, status.Reason) + require.Contains(t, status.Message, "requires the annotations:read permission with the organization scope") } func TestIntegrationAnnotationGraphite(t *testing.T) {
← Back to Alerts View on GitHub →