Every CVE Search/ Get API key

CVE-2026-108261

TinaCMS admin preview iframe loads an attacker-controlled origin from the URL fragment

9.3 CriticalFix available

Tina is a headless content management system. Prior to tinacms 3.14.0 and @tinacms/app 2.5.14, the /~/* admin preview route in packages/tinacms/src/admin/index.tsx can turn an attacker-controlled hash-router splat into an off-origin iframe URL through packages/@tinacms/app/src/preview.tsx, while packages/@tinacms/app/src/lib/preview-origin.ts derives expectedOrigin from that same URL for the GraphQL message channel in packages/@tinacms/app/src/lib/graphql-reducer.ts. An unauthenticated attacker can send a crafted link to a signed-in editor, cause the admin to frame an attacker origin, and have that frame treated as the trusted preview. The attacker-controlled frame can submit GraphQL reads or mutations that the admin executes with the editor credentials, exposing or modifying protected content. This issue is fixed in tinacms 3.14.0 and @tinacms/app 2.5.14.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
Low
Privileges needed
None
User interaction
Required

What an attacker can affect

Confidentiality
High
Integrity
High
Availability
None

Exploit likelihood

0.20%Unchanged since Oct 10, 2026
90%50%10%1%

What each source reports

CVE List Score used 9.3

TinaCMS admin preview iframe loads an attacker-controlled origin from the URL fragment

Tina is a headless content management system. Prior to tinacms 3.14.0 and @tinacms/app 2.5.14, the /~/* admin preview route in packages/tinacms/src/admin/index.tsx can turn an attacker-controlled hash-router splat into an off-origin iframe URL through packages/@tinacms/app/src/preview.tsx, while packages/@tinacms/app/src/lib/preview-origin.ts derives expectedOrigin from that same URL for the GraphQL message channel in packages/@tinacms/app/src/lib/graphql-reducer.ts. An unauthenticated attacker can send a crafted link to a signed-in editor, cause the admin to frame an attacker origin, and have that frame treated as the trusted preview. The attacker-controlled frame can submit GraphQL reads or mutations that the admin executes with the editor credentials, exposing or modifying protected content. This issue is fixed in tinacms 3.14.0 and @tinacms/app 2.5.14.

Record
CVE-2026-108261
CVSS 3.1
9.3 CriticalCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N
Weakness
CWE-346, CWE-441, CWE-601
Updated
2026-11-02
EUVDEUVD-2026-95990 9.3

Tina is a headless content management system. Prior to tinacms 3.14.0 and @tinacms/app 2.5.14, the /~/* admin preview route in packages/tinacms/src/admin/index.tsx can turn an attacker-controlled hash-router splat into an off-origin iframe URL through packages/@tinacms/app/src/preview.tsx, while packages/@tinacms/app/src/lib/preview-origin.ts derives expectedOrigin from that same URL for the GraphQL message channel in packages/@tinacms/app/src/lib/graphql-reducer.ts. An unauthenticated attacker can send a crafted link to a signed-in editor, cause the admin to frame an attacker origin, and have that frame treated as the trusted preview. The attacker-controlled frame can submit GraphQL reads or mutations that the admin executes with the editor credentials, exposing or modifying protected content. This issue is fixed in tinacms 3.14.0 and @tinacms/app 2.5.14.

Record
EUVD-2026-95990
CVSS 3.1
9.3 CriticalCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N
Updated
2026-11-02
GitHub AdvisoriesGHSA-x34j-47hf-4xg7 9.3

TinaCMS admin preview iframe loads an attacker-controlled origin from the URL fragment

Summary

The TinaCMS admin builds its preview <iframe src> from the /~/* hash-router splat without checking that the value stays same-origin. A fragment with a doubled slash (#/~//attacker.example/p) becomes the protocol-relative URL //attacker.example/p, so the admin frames an external site. That same unvalidated string derives expectedOrigin, the only trust anchor for the admin↔preview postMessage channel, so the attacker's frame is treated as trusted: it can submit any GraphQL operation, which the admin executes with the signed-in editor's token and posts back to the attacker's origin.

One link, opened by a logged-in editor, gives an unauthenticated remote attacker arbitrary read and write access to the site's content API as that editor.

Details

Root cause — the router splat becomes the frame source with no same-origin check:

packages/tinacms/src/admin/index.tsx:16     HashRouter as Router,
packages/tinacms/src/admin/index.tsx:329      path='/~/*'
packages/tinacms/src/admin/index.tsx:173    const [url, setURL] = React.useState(`/${params['*']}`);
packages/tinacms/src/admin/index.tsx:176    const paramURL = `/${params['*']}`;
packages/@tinacms/app/src/preview.tsx:24        src={props.url}

The leading / is meant to force a relative path, but [email protected] returns the splat with its own leading slash for /~//x, producing //x. The iframe has no sandbox attribute and the admin bundle ships no CSP.

The trust anchor is then computed from that same value:

packages/@tinacms/app/src/lib/graphql-reducer.ts:209-212   getExpectedPreviewOrigin(url)
packages/@tinacms/app/src/lib/preview-origin.ts:22           return new URL(url, baseOrigin || undefined).origin;
packages/@tinacms/app/src/lib/preview-origin.ts:43-46        event.origin !== expectedOrigin -> reject

Both guards pass for the attacker: event.origin is expectedOrigin, and event.source is the frame the admin itself loaded.

PreviewInner's URL-correction poll (packages/tinacms/src/admin/index.tsx:189-200) does not recover the frame: reading ref.current.contentWindow.location.href across origins throws an uncaught SecurityError, so setReportedURL never fires and no corrective navigate() happens. The PoC below includes that effect verbatim and the attack still completes.

Sink — the attacker's GraphQL string reaches the authenticated client, and the result goes back to the attacker:

packages/@tinacms/app/src/lib/graphql-reducer.ts:613-624   'open' handler; zod validates types only, not query content
packages/@tinacms/app/src/lib/graphql-reducer.ts:973-978   cms.api.tina.request(expandedQuery, { variables })
packages/@tinacms/app/src/lib/graphql-reducer.ts:497-505   postMessageToPreview(..., expectedOrigin)

expandQuery (packages/@tinacms/app/src/lib/expand-query.ts:3-18) is operation-agnostic, so mutations pass through unchanged.

Default-enabled: packages/@tinacms/app/src/App.tsx:70 always passes preview={Preview}, and packages/tinacms/src/admin/index.tsx:327 registers /~/* whenever preview is truthy — so the route exists in every tinacms build output and in tinacms dev.

Incomplete-fix note: [email protected] / @tinacms/[email protected] (PR #7056, c491fc5) added the sender-side origin check, but never validated the URL that check compares against.

Affected-range basis, stated plainly: I tested only [email protected] / @tinacms/[email protected] (commit 0d38acf). The ranges below are given as <= because the vulnerable lines are byte-identical across every commit available to me — a 123-commit shallow clone, earliest 8a86ffa (2026-06-26), which predates the 3.9.3 hardening release — but I did not fetch tags or test earlier releases, so the true lower bound is undetermined. Please narrow it from your own history.

Suggested fix: normalise the splat to a same-origin path before it becomes url (reject a leading / or \), and have getExpectedPreviewOrigin…

Record
GHSA-x34j-47hf-4xg7
CVSS 3.1
9.3 CriticalCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N
Weakness
CWE-346, CWE-441, CWE-601
Updated
2026-11-02
NVD 9.3

Tina is a headless content management system. Prior to tinacms 3.14.0 and @tinacms/app 2.5.14, the /~/* admin preview route in packages/tinacms/src/admin/index.tsx can turn an attacker-controlled hash-router splat into an off-origin iframe URL through packages/@tinacms/app/src/preview.tsx, while packages/@tinacms/app/src/lib/preview-origin.ts derives expectedOrigin from that same URL for the GraphQL message channel in packages/@tinacms/app/src/lib/graphql-reducer.ts. An unauthenticated attacker can send a crafted link to a signed-in editor, cause the admin to frame an attacker origin, and have that frame treated as the trusted preview. The attacker-controlled frame can submit GraphQL reads or mutations that the admin executes with the editor credentials, exposing or modifying protected content. This issue is fixed in tinacms 3.14.0 and @tinacms/app 2.5.14.

Record
CVE-2026-108261
CVSS 3.1
9.3 CriticalCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N
Weakness
CWE-346, CWE-441, CWE-601
Updated
2026-11-02
OSV 9.3

TinaCMS admin preview iframe loads an attacker-controlled origin from the URL fragment

Tina is a headless content management system. Prior to tinacms 3.14.0 and @tinacms/app 2.5.14, the /~/* admin preview route in packages/tinacms/src/admin/index.tsx can turn an attacker-controlled hash-router splat into an off-origin iframe URL through packages/@tinacms/app/src/preview.tsx, while packages/@tinacms/app/src/lib/preview-origin.ts derives expectedOrigin from that same URL for the GraphQL message channel in packages/@tinacms/app/src/lib/graphql-reducer.ts. An unauthenticated attacker can send a crafted link to a signed-in editor, cause the admin to frame an attacker origin, and have that frame treated as the trusted preview. The attacker-controlled frame can submit GraphQL reads or mutations that the admin executes with the editor credentials, exposing or modifying protected content. This issue is fixed in tinacms 3.14.0 and @tinacms/app 2.5.14.

Record
CVE-2026-108261
CVSS 3.1
9.3 CriticalCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N
Weakness
CWE-346, CWE-441, CWE-601
Updated
2026-11-02
OSVGHSA-x34j-47hf-4xg7 9.3

TinaCMS admin preview iframe loads an attacker-controlled origin from the URL fragment

Summary

The TinaCMS admin builds its preview <iframe src> from the /~/* hash-router splat without checking that the value stays same-origin. A fragment with a doubled slash (#/~//attacker.example/p) becomes the protocol-relative URL //attacker.example/p, so the admin frames an external site. That same unvalidated string derives expectedOrigin, the only trust anchor for the admin↔preview postMessage channel, so the attacker's frame is treated as trusted: it can submit any GraphQL operation, which the admin executes with the signed-in editor's token and posts back to the attacker's origin.

One link, opened by a logged-in editor, gives an unauthenticated remote attacker arbitrary read and write access to the site's content API as that editor.

Details

Root cause — the router splat becomes the frame source with no same-origin check:

packages/tinacms/src/admin/index.tsx:16     HashRouter as Router,
packages/tinacms/src/admin/index.tsx:329      path='/~/*'
packages/tinacms/src/admin/index.tsx:173    const [url, setURL] = React.useState(`/${params['*']}`);
packages/tinacms/src/admin/index.tsx:176    const paramURL = `/${params['*']}`;
packages/@tinacms/app/src/preview.tsx:24        src={props.url}

The leading / is meant to force a relative path, but [email protected] returns the splat with its own leading slash for /~//x, producing //x. The iframe has no sandbox attribute and the admin bundle ships no CSP.

The trust anchor is then computed from that same value:

packages/@tinacms/app/src/lib/graphql-reducer.ts:209-212   getExpectedPreviewOrigin(url)
packages/@tinacms/app/src/lib/preview-origin.ts:22           return new URL(url, baseOrigin || undefined).origin;
packages/@tinacms/app/src/lib/preview-origin.ts:43-46        event.origin !== expectedOrigin -> reject

Both guards pass for the attacker: event.origin is expectedOrigin, and event.source is the frame the admin itself loaded.

PreviewInner's URL-correction poll (packages/tinacms/src/admin/index.tsx:189-200) does not recover the frame: reading ref.current.contentWindow.location.href across origins throws an uncaught SecurityError, so setReportedURL never fires and no corrective navigate() happens. The PoC below includes that effect verbatim and the attack still completes.

Sink — the attacker's GraphQL string reaches the authenticated client, and the result goes back to the attacker:

packages/@tinacms/app/src/lib/graphql-reducer.ts:613-624   'open' handler; zod validates types only, not query content
packages/@tinacms/app/src/lib/graphql-reducer.ts:973-978   cms.api.tina.request(expandedQuery, { variables })
packages/@tinacms/app/src/lib/graphql-reducer.ts:497-505   postMessageToPreview(..., expectedOrigin)

expandQuery (packages/@tinacms/app/src/lib/expand-query.ts:3-18) is operation-agnostic, so mutations pass through unchanged.

Default-enabled: packages/@tinacms/app/src/App.tsx:70 always passes preview={Preview}, and packages/tinacms/src/admin/index.tsx:327 registers /~/* whenever preview is truthy — so the route exists in every tinacms build output and in tinacms dev.

Incomplete-fix note: [email protected] / @tinacms/[email protected] (PR #7056, c491fc5) added the sender-side origin check, but never validated the URL that check compares against.

Affected-range basis, stated plainly: I tested only [email protected] / @tinacms/[email protected] (commit 0d38acf). The ranges below are given as <= because the vulnerable lines are byte-identical across every commit available to me — a 123-commit shallow clone, earliest 8a86ffa (2026-06-26), which predates the 3.9.3 hardening release — but I did not fetch tags or test earlier releases, so the true lower bound is undetermined. Please narrow it from your own history.

Suggested fix: normalise the splat to a same-origin path before it becomes url (reject a leading / or \), and have getExpectedPreviewOrigin…

Record
GHSA-x34j-47hf-4xg7
CVSS 3.1
9.3 CriticalCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N
Weakness
CWE-346, CWE-441, CWE-601
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
tinacms
npm
through 3.13.0
3.14.0
@tinacms/app
npm
through 2.5.13
2.5.14
tinacms
npm
before 3.14.0
3.14.0
@tinacms/app
npm
before 2.5.14
2.5.14

History

History
RecordedChangedSources
first indexedCVE List, EUVD, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-108261 is a critical vulnerability (CVSS 9.3) in @tinacms App, published October 9, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.20%, the 9th percentile. Fixed in tinacms 3.14.0.

References

This page doesn't list everything

Left out here: the rest of 2 source descriptions. The JSON record has them.

MoveEnter Open