Release v0.55.0
Welcome to the v0.55.0 release!
This release first comes with a large number of security fixes, several of them critical, so we strongly recommend upgrading as soon as possible.
On the dashboard side, you can now repeat a single panel, define annotations per panel, drag and drop panels between groups and lock the plugin versions used by a dashboard. Prometheus exemplars are supported, the search has been rewritten on the backend, and two new plugins are available: Canvas and JSON.
As usual, this blog post doesn't cover every change. For the complete list, check the changelog.
Security#
This release fixes a significant number of security vulnerabilities, several of them critical. Most of them can be
exploited without authentication when Perses runs with its default configuration (security.enable_auth: false).
We strongly recommend upgrading as soon as possible.
Some of these fixes are breaking changes, check the upgrade guide before upgrading and or the section Breaking changes below for more details.
We also published a security considerations page (#4512) describing how to deploy Perses safely.
Authentication bypass via forged JWTs (GHSA-mpfg-6jh9-3jmg)#
When authentication was enabled without setting security.encryption_key (or security.encryption_key_file), Perses
fell back to a key hardcoded in its public source code. This key is also used to sign the JWTs, so anyone could forge a
valid token for any user, including an admin.
Perses now refuses to start when authentication is enabled and no encryption key is provided.
Server-Side Request Forgery in the datasource proxy (GHSA-58wc-w2cq-7vf3)#
The datasource proxy (HTTP and SQL) connected to whatever destination the datasource defined. Combined with the "unsaved" proxy endpoints, Perses could be used to read any service reachable from the server: loopback, cloud metadata endpoints, the Kubernetes API, etc.
The destination of the proxy is now verified when a datasource is saved and before each request, including on every
resolved IP address. Loopback, link-local, the cloud metadata endpoints and the Kubernetes API are denied by default.
Private networks remain allowed. The restrictions can be adjusted in the new datasource.proxy configuration section:
datasource:
proxy:
allowed_networks: ["127.0.0.0/8", "::1/128"]Arbitrary file read through secrets (GHSA-2qpg-wfx2-frv2)#
Secrets can reference files (passwordFile, credentialsFile, clientSecretFile, caFile, certFile, keyFile).
These files could be anywhere on the server, so a user able to create a secret could exfiltrate any file readable by
Perses through the datasource proxy.
File references are now rejected by default. They are only allowed in the directories listed in the new
security.secret_file_allowed_directories configuration.
Caller credentials forwarded to the datasource (GHSA-phr8-237x-w7mj)#
The HTTP datasource proxy forwarded the Cookie and Authorization headers of the caller to the datasource. Anyone
controlling a datasource could steal the Perses session of its users (including the refresh token) and take over their
account.
The Cookie header of the caller is no longer forwarded, and neither is the Authorization header when the native
authorization is enabled. To authenticate against a datasource, use a secret or the oauthPassthrough option.
Stored XSS through the datasource proxy (GHSA-8hm2-fqjh-pgj2)#
The responses of the datasource proxy are served from the Perses origin, almost unchanged. A malicious datasource could serve HTML and JavaScript running with the session of any user opening a link to it, set cookies on the Perses domain, or redirect users anywhere.
The proxy responses are now sandboxed with a strict Content-Security-Policy, and the headers that apply to the Perses
origin (Set-Cookie, CORS headers, absolute redirections, etc.) are removed. As a consequence, the UI of a datasource
(e.g. the Prometheus UI) can no longer be browsed through the proxy.
SQL proxy vulnerabilities#
Several vulnerabilities have been fixed in the SQL proxy:
- Read-only restriction bypass (GHSA-r457-39g6-wpc6):
queries modifying the data or the schema could get through the keyword check. Queries must now start with a read
keyword (
SELECT,WITH,SHOW, etc.) and are executed in a read-only transaction. MySQLmultiStatements, as well asSELECT ... INTO OUTFILE / DUMPFILE, are rejected. - Arbitrary file read with MySQL/MariaDB (GHSA-92jg-r5jf-x5fh):
the
allowAllFilesdriver option could be enabled from the datasource params, letting the database server read any file of the Perses server. This option, as well asallowCleartextPasswordsandallowOldPasswords, is now always disabled. - MySQL/MariaDB TLS config mix-up (GHSA-jg7f-c7hw-wxgw): two datasources with colliding names could use each other's TLS configuration (CA, client certificate, verification settings). The TLS configuration is now set per connection.
Missing secret authorization in dashboard datasources (GHSA-mc34-vgvh-gpr7)#
A user allowed to edit a dashboard, but not to read secrets, could embed a datasource referencing any secret of the project and make the proxy send it to a server they control. Perses now checks that the user can read the secret used by a datasource embedded in a dashboard.
Wiping the database with a single request (GHSA-j8hv-r356-hfw6)#
With the file system database, deleting the project .. (DELETE /api/v1/projects/..) deleted the entire database.
The name of the resource in the URL was never validated. Every parameter of the URL is now validated before being used.
Path traversal in the file system database (GHSA-63qw-3pm6-j4f3, GHSA-2wxp-jhwc-h2ff)#
The fix released in v0.54.0 for CVE-2026-63445
could be bypassed with a project named .. (e.g. GET /api/v1/variables?project=..), returning the whole database. The
anonymous endpoint POST /api/validate/dashboards was also using the metadata.project of the dashboard without any
validation.
The project name is now validated for every HTTP method and in the body of the requests, and the file system database itself rejects any key that would escape its directory.
Note that these issues (as well as the previous one) don't affect the SQL database.
Breaking changes#
Removal of the package @perses-dev/core#
As announced in v0.54.0, @perses-dev/core is no longer published
(#4325). Its code has been dispatched in @perses-dev/spec,
@perses-dev/client, @perses-dev/plugin-system and @perses-dev/components. It is also no longer shared with the
plugins at runtime (perses/shared#246), so plugins still importing it must
be migrated.
Dependency upgrades: React 17 dropped, Node 24, TypeScript 7 and end of CommonJS#
The frontend toolchain has been modernized across all the repositories:
- React 17 is no longer supported (#4391, perses/shared#269, perses/plugins#790), as planned in the roadmap.
- Node.js 24 (LTS) and ES2023 are now the target (#4380, perses/shared#248, perses/spec#78, perses/plugins#779).
- Packages are published as ESM only: the CommonJS output has been removed.
- TypeScript 7, as well as newer versions of Zod and React Hook Form (#4421).
If you embed Perses in your own application, you now have to register the Perses packages shared with the plugins before rendering your app (perses/shared#268):
import * as PersesClient from '@perses-dev/client';
import * as PersesComponents from '@perses-dev/components';
import * as PersesDashboards from '@perses-dev/dashboards';
import * as PersesExplore from '@perses-dev/explore';
import * as PersesPluginSystem from '@perses-dev/plugin-system';
import { registerHostSharedModules } from '@perses-dev/plugin-system';
import * as PersesSpec from '@perses-dev/spec';
registerHostSharedModules({
'@perses-dev/spec': PersesSpec,
'@perses-dev/client': PersesClient,
'@perses-dev/components': PersesComponents,
'@perses-dev/plugin-system': PersesPluginSystem,
'@perses-dev/explore': PersesExplore,
'@perses-dev/dashboards': PersesDashboards,
});Removal of the old dashboard and datasource specification#
Since v0.54.0, the dashboard and datasource specifications live in perses/spec. The
deprecated copies remaining in Perses have been removed (#4326): the Go
packages pkg/model/api/v1/{common,dashboard,datasource,plugin,variable} and their CUE equivalents. If you still import
them, switch to the ones provided by github.com/perses/spec (e.g. github.com/perses/spec/go/dashboard).
Go SDK: datasource.Selector is now a union type#
A datasource selector can now reference a variable, which is serialized as a plain string (e.g. "$myDatasource")
(#4545). As a result, datasource.Selector no longer has the Kind and
Name fields, but a Static or a Variable selector. Use the new constructors instead of a struct literal
(#4557):
// Before
sel := &datasource.Selector{Kind: "PrometheusDatasource", Name: "myPrometheus"}
// After
static := datasource.NewStaticSelector("PrometheusDatasource", "myPrometheus")
variable, err := datasource.NewVariableSelector("myDatasource")
// Or let the SDK detect a variable reference ("$foo" or "${foo}")
auto, err := datasource.NewSelector("PrometheusDatasource", name)The official plugins have been updated (perses/plugins#857): their
Selector helper now detects variable references, and a new VariableSelector helper is available. If you maintain
your own datasource plugin with a Selector helper, update it the same way.
Likewise, the deprecated CUE package cue/common/proxy has been removed from perses/shared
(perses/shared#327). Use github.com/perses/spec/cue/datasource/proxy/http
and github.com/perses/spec/cue/datasource/proxy/sql instead.
API changes in the TypeScript packages#
If you develop plugins or embed Perses, these changes in the @perses-dev/* packages may require an update:
useDeepMemoanduseMemoizedare removed, use ReactuseMemoinstead. Thespecof aResourceand the cell values ofTableColumnConfigare nowunknowninstead ofany(perses/shared#310).useIdis no longer exported from@perses-dev/components, use ReactuseId()instead (perses/shared#348).- Zod schemas are typed with
z.ZodType<T, T>instead ofz.ZodType<T>or the deprecatedz.ZodSchema<T>(perses/shared#282, perses/spec#95). - The
PanelEditorValuestype and its schema are removed from@perses-dev/spec, use the ones from@perses-dev/plugin-system(perses/spec#59). - Annotations are now fetched on demand, only by the panels displaying them
(perses/shared#330). The
annotationStatestore field and thesetAnnotationStateaction are removed, andusePanelAnnotationsWithDatano longer returns hidden annotations.
Security changes#
Some of the security fixes require a configuration change on your side:
- Encryption key: if authentication is enabled,
security.encryption_key(orsecurity.encryption_key_file) must be set, otherwise Perses won't start. If you were relying on the default key, the secrets previously stored can no longer be decrypted and must be re-created, and users will have to log in again. - Datasource proxy destinations: datasources pointing to loopback, link-local addresses, cloud metadata endpoints or
the Kubernetes API are now rejected. If you rely on such datasources (e.g. a Prometheus running next to Perses on
localhost), allow them throughdatasource.proxy.allowed_networksordatasource.proxy.allowed_hosts. URLs containing credentials (https://user:password@host) are also refused: use a secret instead. - Files referenced in secrets: they are rejected by default. List the directories containing them in
security.secret_file_allowed_directories. - Resource names: a name can no longer contain
.., nor start or end with.(perses/spec#102). Rename the resources using such a name. - Proxy headers: the
Authorizationheader of the caller is no longer forwarded to the datasource when the native authorization is enabled, and the UI of a datasource can no longer be browsed through the proxy.
See the upgrade guide for more details.
Performance improvements#
This release comes with several performance improvements on the backend. The numbers below come from the benchmarks shared in the corresponding pull requests.
Datasource proxy reuses its connections#
Until now, the proxy created a new HTTP transport for every proxied request, so no TCP or TLS connection was ever reused. Perses now keeps one transport per saved datasource (#4521), which is particularly visible with HTTPS datasources:
| Case | Before | After | Gain |
|---|---|---|---|
| HTTP, sequential | 153 µs/op | 70 µs/op | ~2.2x |
| HTTP, parallel (10 CPUs) | 60 µs/op | 22 µs/op | ~2.8x |
| HTTPS, sequential | 1.69 ms/op | 72 µs/op | ~23x |
| HTTPS, parallel (10 CPUs) | 295 µs/op | 23 µs/op | ~13x |
It also uses ~4x less memory per request. On a real network, each request additionally saves the round trips of the
TCP and TLS handshakes. The connection limits can be tuned with the new datasource.proxy.http configuration:
datasource:
proxy:
http:
max_conns_per_host: 0 # 0 = no limit
max_idle_conns: 100
max_idle_conns_per_host: 10Faster native authorization#
The permission cache of the native authorization is rebuilt at startup, periodically, on every RBAC change and on the first login of every OAuth/OIDC user. Its build now runs in linear time (#4540). With 10,000 users and 1,000 projects:
| Benchmark | Before | After | Delta |
|---|---|---|---|
| Cache refresh | 2.81 s | 21 ms | −99.25% |
| 25 simultaneous first OAuth logins | 12.68 s | 149 ms | −98.82% |
The cache is now also an immutable snapshot read without any lock (#4537). It fixes a deadlock that could hang every authorization check until a restart, and makes permission checks under concurrent load ~7x faster (from ~145 ns to ~20 ns per check).
Faster plugin extraction at startup#
Plugin archives are now extracted in parallel (#4507), and the ones already extracted are skipped (#4534). With the default plugins (32 archives, ~9,000 files), the extraction step goes from ~1–2 s to ~1 ms when the archives are already extracted, and the whole plugin initialization from ~1.35 s to ~0.3 s.
Skipping the archives benefits binary installations and deployments where the plugin folder is kept between restarts (e.g. mounted on a volume). With the official container image and its default setup, every new container still extracts all archives, but in parallel.
Better browser caching of static assets#
Content-hashed JS and CSS bundles (#4475), as well as fonts and images from the app and the plugins (#4533), are now served as immutable assets. Browsers no longer download or revalidate them on every page load.
Dashboard lock mode#
Dashboards don't pin the version of the plugins they use, so a plugin upgrade can change how a panel looks. The new lock mode (perses/shared#243) pins every plugin of a dashboard to the latest installed version. When a newer version is available, a button lets you update the plugins, with a preview of the change based on the first panel using it. If a pinned version is no longer installed, an error is displayed.
The panel type selector also shows the plugin version when several versions are installed.
The feature is disabled by default (#4370):
frontend:
enable_lock_mode: trueDrag and drop panels between panel groups#
The grid layout library has been replaced by Snapgrid (perses/shared#296), so panels can now be dragged from one panel group to another. Drag and drop in lists (e.g. item actions) is also only triggered from the drag handle, so you can select text again (perses/shared#335).
Panel repeat#
Until now, only a panel group could be repeated over the values of a variable. A single panel can now be repeated as
well (perses/spec#32,
perses/shared#149). In the Layout tab of the panel editor, choose the
variable, the alignment (horizontal or vertical) and the maximum number of panels per row or column:
layouts:
- kind: Grid
spec:
items:
- x: 0
y: 0
width: 12
height: 6
content:
$ref: '#/spec/panels/cpu'
repeatVariable:
value: instance
alignment: horizontal
maxPer: 4Only the selected values are used, and a group repeat takes precedence over a panel repeat. Repeated panels are also supported by the Grafana migration (#4328).
Panel-level annotations#
v0.54.0 introduced annotations at the dashboard level. They can now be defined on a panel as well (perses/spec#61, perses/shared#206, #4356). The panel displays the dashboard annotations plus its own ones.
A new Annotations tab is available in the panel editor for the plugins supporting it. For now, only the
TimeSeriesChart does (perses/plugins#754). Panel annotations are
validated by the backend like the dashboard ones (#4299), and the Go SDK
can now build both of them (#4448).
Exemplars support#
The Prometheus datasource can now fetch exemplars (perses/plugins#799). It is disabled by default and must be enabled per datasource:
kind: GlobalDatasource
metadata:
name: prometheus
spec:
plugin:
kind: PrometheusDatasource
spec:
exemplars:
enable: true
proxy:
kind: HTTPProxy
spec:
url: https://prometheus.example.comExemplars are then fetched with range queries and displayed by the TimeSeriesChart as diamonds on top of their series
(perses/plugins#800). Hovering one shows its labels (e.g. the trace ID),
value and timestamp, and clicking it pins the tooltip (perses/shared#278).
Improved search experience#
The search is now done by the backend, through the new endpoint /api/v1/search/dashboards
(#4355). Dashboards are indexed in memory and matched with a fuzzy search.
With a SQL database, every Perses instance periodically checks for changes to keep its index in sync. By default, the
name and the display name are indexed, but you can index any attribute, like the panel names:
search:
index_keys:
dashboard:
- metadata.name
- spec.display.name
- spec.panels.@dig:display.nameThe project view also uses this endpoint to list the dashboards, without loading their full definition (#4366), and its search bar now matches tags too (#4446).
Finally, the important dashboards of the home page can be organized in groups, and an entire project can be marked as important (#4506). The previous flat list is still supported but deprecated.
frontend:
important_dashboards:
- title: Perses demos
dashboards:
- project: perses
- project: testing
- title: Quick links
description: Highlighted dashboards
dashboards:
- project: perses
dashboard: nodeexporterAuthentication & authorization#
-
Roles from OAuth/OIDC claims (#4172): roles can be assigned from the claims of the identity provider token, without creating any RoleBinding. Without
project, the role is a GlobalRole.security: authentication: providers: oidc: - slug_id: keycloak # ... claims: - claim_name: roles mappings: - claim_value: admin role_name: admin - claim_value: ops role_name: editor project: platform -
OAuth pass-through (#4404): with
oauthPassThrough: truein itsHTTPProxy, a datasource is queried with the OAuth/OIDC token of the current user. The token is refreshed when needed. -
Kubernetes impersonation (#4339): with the Kubernetes authorization, the
Impersonate-Userheader is honored if the caller is allowed to impersonate this user. -
Pre-hashed passwords (#4423): users can be provisioned with a bcrypt hash (
spec.nativeProvider.passwordHash) instead of a cleartext password. -
OIDC login property (#4468): like the OAuth provider, the OIDC provider supports
custom_login_propertyto choose the user info field used as login (e.g.preferred_username).
HTTP proxy improvements#
The HTTP proxy used by the datasources received a lot of attention in this release: it is safer, faster and more configurable.
Header filtering policies#
Some datasources don't play well with certain headers sent by the browser or added by Perses (e.g. X-Forwarded-For).
The HTTPProxy spec now supports two optional header policies
(#4428):
allowHeaders: only the listed headers are forwarded to the datasource.dropHeaders: the listed headers are removed, and the others are forwarded.
kind: Datasource
metadata:
name: prometheus
project: my-project
spec:
default: true
plugin:
kind: PrometheusDatasource
spec:
proxy:
kind: HTTPProxy
spec:
url: https://prometheus.example.com
dropHeaders:
- X-Forwarded-For
- User-AgentHeader names are case-insensitive and only one of the two lists can be set. The filtering is applied after the headers
defined in the datasource, but before the secret authentication, so there is no need to allow Authorization when the
datasource uses a secret.
Configurable timeout#
The HTTPProxy spec also accepts a new optional timeout field, available in the datasource editor as well
(#4525). To avoid letting users hold goroutines and sockets against
unreachable hosts for too long, the timeout of a datasource is bounded by the server configuration:
datasource:
proxy:
http:
default_timeout: 30s # used when the datasource doesn't define a timeout
max_timeout: 1m # highest timeout a datasource can defineA datasource defining a timeout greater than max_timeout is rejected when it is saved, and its timeout is clamped when
it is used.
Security hardening#
As described in the Security section, the proxy has been hardened in many ways:
- its destination is verified against a list of allowed and denied networks/hosts (loopback, link-local, cloud metadata
endpoints and Kubernetes API denied by default), configurable through
datasource.proxy. The allowed schemes can be restricted withdatasource.proxy.http.allowed_schemes; - the
Cookieheader of the caller is never forwarded, and neither is theAuthorizationheader when the native authorization is enabled. AnAuthorizationheader defined in theheadersof a datasource is ignored: use a secret instead; - its responses are sandboxed with a strict
Content-Security-Policy, and the headers applying to the Perses origin are removed.
Some of these changes may require a configuration update, check the Security changes section and the proxy documentation for more details.
Connection reuse#
Finally, the proxy now keeps one HTTP transport per datasource and reuses its connections, making HTTPS requests up to ~23x faster. See Datasource proxy reuses its connections for the numbers and the new connection limits.
Refresh interval configuration#
The auto-refresh options of the dashboards can now be configured (#4432),
or the auto-refresh can be disabled entirely (#4338). In that case, the
interval picker is hidden and the refreshInterval of the dashboards, as well as the ?refresh= URL parameter, are
ignored.
Similarly, administrators can define the default timezone, theme and number of rows per page, used when a user hasn't chosen their own (#4268).
frontend:
auto_refresh:
disable: false
options: ["0s", "30s", "1m", "5m"]
default_user_preferences:
timezone: UTC
theme: dark
rows_per_page: 50Provisioning improvements#
- Perses can now watch the provisioning folders and apply the changes right away, instead of waiting for the next interval (#4136). Resources are also only updated when they actually changed, which avoids unnecessary writes and ever-increasing versions.
- A provisioning file can contain several resources separated by
---(#4332).
provisioning:
enable_watch: true
folders:
- /etc/perses/provisioningCLI improvements#
- The new command
percli dac watchrebuilds your Dashboard-as-Code files on every change, including the dashboards depending on a modified library file (#3903). Combined with the provisioning watch mode, it gives you a live reload of your dashboards. percli dac previewsupports the--create-projectflag, likepercli apply(#4413).
Dashboard and plugin schemas API#
Perses now exposes the CUE schemas of the dashboards and of the installed plugins (#4505). It is particularly useful for tools generating dashboards, like the Perses MCP server:
GET /api/v1/schemas/dashboards: the complete dashboard schema, including the plugin schemas.GET /api/v1/schemas/plugins: the schemas of all plugins.GET /api/v1/schemas/plugins/<plugin_name>: the schema of a single plugin, with the optionalversionandregistryquery parameters.
Datasource improvements#
The datasource editor has a new Test Connection button to check the datasource is reachable before saving it
(#4396, perses/shared#99).
It is available for Prometheus (perses/plugins#620), OpenSearch
(perses/plugins#797) and AlertManager
(perses/plugins#798).
The Kubernetes datasource discovery can also mark a discovered datasource as the default one, based on its labels or annotations (#4368):
datasource:
global:
discovery:
- name: prometheus
kubernetes_sd:
# ...
default:
enable: true
labels:
app.kubernetes.io/name: prometheusNew plugins#
Canvas#
The Canvas panel (perses/plugins#729) is a free-form diagram editor to build weathermap-like visualizations. Place nodes, connect them with edges and add background shapes or images. Nodes and edges can be bound to a query: their color then follows thresholds and the edge thickness can scale with the value.
Json#
The new JSON datasource and query (perses/plugins#770) can query any HTTP
endpoint returning JSON, with GET or POST, query parameters and variables. The response can be transformed with a
JSONata expression, and the result displayed in a Table panel.
Plugin improvements#
- TimeSeriesChart:
- the color palette can be selected again (perses/plugins#845).
Autokeeps the current color by series name, the categorical palettes give more distinct colors. - rendering is faster, especially with many series (perses/plugins#832).
- the color palette can be selected again (perses/plugins#845).
- StatChart: multiple series are laid out in a grid adapting to the panel size, instead of a single row (perses/plugins#812, perses/plugins#814). Their values and legends also share the same font size, which can be customized (perses/plugins#778).
- Table: sorting can be enabled for all columns at once (perses/plugins#759),
and the new
PivotByLabeltransform turns series into a time × label matrix (perses/shared#288). - BarChart: colors can be overridden with regexes on the series or bar names (perses/plugins#774).
- PieChart: the inner and outer radius can be configured, e.g. to display a donut (perses/plugins#823).
- Markdown: external links open in a new tab (perses/plugins#815).
- Prometheus: a query can be executed as an instant query, regardless of the panel (perses/plugins#752).
- Pyroscope: profiles are queried through the Connect API instead of the legacy
/renderendpoint (perses/plugins#763). The minimum step can be configured on the datasource (perses/plugins#810), and the profile types and labels proposed are restricted to the selected service (perses/plugins#820, perses/plugins#835).
| TimeSeriesChart color palette | StatChart grid layout |
|---|---|
Other improvements#
-
Duplicate a dashboard into another project (#4536), including from a project where you only have read access.
-
Open in a new tab: the project and admin tabs, as well as the ephemeral dashboards, are now real links (#4252).
-
Page title: the browser tab title now reflects the current page (#4489).
-
Collapsed panel groups show the number of panels they contain (perses/shared#309).
-
Custom unit label: a panel can display its own unit label (e.g.
trx/s) while keeping the formatting of a standard unit (perses/shared#297). -
Grafana migration: the default time range, the refresh interval and the timezone of the Grafana dashboard are kept (#4555). Table thresholds are migrated to cell settings (perses/plugins#808), and datasource variables for Jaeger and OpenSearch are supported (perses/plugins#769).
-
Compressed requests: the API accepts request bodies compressed with gzip (
Content-Encoding: gzip) (#4381). -
Custom fetch: apps embedding Perses can override the
fetchused by the client and the datasources through a fetch provider, e.g. to add custom headers (perses/shared#214, perses/shared#316, perses/plugins#854).

