high · 8.5CVE-2026-45414Jul 13, 2026

CVE-2026-45414: Decidim JWT Cross-Organization Authentication Bypass

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

A valid JWT issued to a user on one Decidim organization can be replayed against a different organization's API just by changing the HTTP Host header, granting unauthorized access to sensitive partici

Packagedecidim
Ecosystemrubygems
Affected< 0.31.5
Fixed in0.31.5

The problem

Decidim's multi-tenant API uses the HTTP Host header to select the active organization context at request time. Before 0.31.5, JWT-backed authentication (both participant JWTs from login and API-user JWTs from the system panel) was verified only for signature validity and expiry.

No claim in the token tied it to the issuing organization's ID.

An attacker who holds a valid JWT for Org 1 can replay that token against Org 2's API endpoint by changing the Host header. The API accepts the token, resolves the principal in Org 2's context, and grants access to admin-gated GraphQL fields such as `participantDetails` and the `proposal.answer` mutation path.

Proof of concept

A working proof-of-concept for CVE-2026-45414 in decidim, with the exact payload below.

http
# Step 1: Obtain a JWT from Org 1 (via login or system-panel API key)
POST /api/sign_in HTTP/1.1
Host: org1.example.com
Content-Type: application/x-www-form-urlencoded

user[email]=admin@org1.example.com&user[password]=secret

# Response header contains:
# Authorization: Bearer <JWT_FROM_ORG1>

# Step 2: Replay the same JWT to Org 2 by changing only the Host header
POST /api HTTP/1.1
Host: org2.example.com
Authorization: Bearer <JWT_FROM_ORG1>
Content-Type: application/json

{"query": "{ participantDetails(id: 1) { name email } }"}

The root cause is a missing organization binding in JWT validation (CWE-287: Improper Authentication). Decidim's Devise::JWT configuration used `request_keys: [:env]` to derive the org from the request host, but the token itself carried no claim asserting which organization it was issued for.

Because the same shared JWT secret signs tokens for all organizations in a single-host deployment, a cryptographically valid token for Org 1 is also cryptographically valid when presented to Org 2.

PR #16673 and #16756 introduced an organization-bound claim into the JWT payload and added a validation step that compares that claim against the organization resolved from the current request host. A token issued for Org 1 will now be rejected when the request host resolves to Org 2, closing the trust-boundary gap.

The fix

Upgrade to decidim 0.31.5 or later, which embeds and validates an organization identifier claim in every JWT. If upgrading immediately is not possible, disable JWT credentials in the system panel at /system until the patch is applied.

Reported by Radically Open Security (security audit commissioned by the Decidim Association, funded by NGI).

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

Related research