Security
A preview shows content that editors wrote, rendered with templates and scripts of the frontend, inside the backend of the site. Look treats that content as untrusted and isolates every preview as far as browsers allow. This chapter explains what is in place and what it means for you.
The preview frame is sandboxed
Every preview is an <iframe> with the sandbox attribute set
to allow- and nothing else. Browsers then give the frame an
opaque origin: the content behaves as if it came from an unknown, foreign
website.
Concretely, content inside a preview
- cannot access the backend: no access to the TYPO3 backend document, the editor's session cookie, local storage or session storage;
- cannot navigate or open windows: no links to follow, no popups, no redirects of the backend;
- cannot submit forms: a contact form in the preview is just markup;
- cannot show dialogs: no
alert, no download prompts;() - cannot be clicked: the frame ignores pointer events, the editor's clicks go to the page module controls as usual.
The referrerpolicy is no-, so requests from the frame
carry no backend URL.
Scripts are limited by a Content Security Policy
Scripts may run inside the frame, otherwise Look could not report the content height to the page module. Which scripts run is decided by a Content Security Policy in the preview document:
script-src 'nonce-<nonce of the backend request>' 'strict-dynamic'
Only script tags that Look itself writes into the document carry that nonce:
its own height script and, with allowSiteScripts,
the scripts of your frontend build. A <script> that arrives inside
the content, for example through an unsafe rich text field, has no nonce and
is refused by the browser. 'strict- lets the trusted scripts
import their own modules.
Media are blocked by default
Unless allowMedia is enabled, the same policy
also contains media-. Video and audio
files are neither downloaded nor played while the page module is open, and
embedded players (YouTube, Vimeo and other iframes) are not loaded either;
videos appear as striped placeholder boxes. Besides bandwidth this avoids a
page module full of playing videos.
Previews rendered in the page module request (srcdoc) also inherit the
Content Security Policy of the TYPO3 backend, whose default only allows assets
from the backend's own host. Previews rendered in their own request (see
Previews rendered in a separate request) inherit nothing from the backend page, so
their document carries a complete policy of its own, sent as HTTP header and
repeated as <meta>: default-, images and fonts from
the own host or inline as data:, stylesheets from the own host or
inline, no connections to other hosts, no plugins, no <base>, no form
targets, no media and no embedded frames unless allowed by the feature flag.
Either way, a tracking pixel or a script from a foreign host in an editor's
text does not load in the page module.
No permissions
Camera, microphone, geolocation, fullscreen, autoplay and the other browser permissions are not available to the frame. The TYPO3 backend does not grant them to embedded frames, and the opaque origin of the sandbox denies the rest.
What this means for you
- Enable feature flags deliberately
- The defaults give previews no capabilities beyond CSS. Enabling allowSiteScripts runs your frontend build inside the frame. It stays isolated from the backend, but review what the build does (tracking, external requests) before enabling it.
- Web fonts need a CORS header
- The opaque origin makes web fonts and script modules cross-origin
requests. Look's own script is a classic script and works without any
server configuration, but your frontend fonts only load if the server
answers with
Access-for their path, see If your frontend uses web fonts. The header is standard practice for public static files and exposes nothing the files did not expose before.Control- Allow- Origin: * - Some frontend techniques need adjustments
- External SVG sprites (
<use href="...) cannot load inside an opaque origin, see SVG icons are missing. Scripts running in the frame (with allowSiteScripts) have nosvg#icon"> sessionorStorage localand cannot autoplay media; guard such calls in your frontend code as you would for private browsing modes.Storage
Previews rendered in a separate request
Previews with the record argument run the site's PHP, the frontend
TypoScript with its templates, data processors and plugins, in a request of
its own instead of the page module request. Whatever that code does, from an
exception to a fatal error, a timeout, a plugin sending headers or leaving
global state behind, ends with that request and never reaches the page module
or the editor's backend session.
How the frame gets its content
- The page module emits the frame without a source, only with a signed descriptor: table and uid of the record, its workspace and language, the content object to render, the frame options (stylesheets, scripts, body class, scale, height) and the id of the backend user. The signature is an HMAC over the site's encryption key. The descriptor grants nothing by itself and can be exchanged for tokens for eight hours after the page module rendered it.
- When the frame comes into view, Look's script in the page module sends the descriptor to the token route, a regular authenticated AJAX route of the backend. The route checks the signature, that the logged in user is the one the descriptor was issued for, that the descriptor is younger than eight hours, and that the user may still see the record: same workspace, read access to the table, the page and the language, and the record still on that page. Then it answers with a token: the descriptor plus an expiry five seconds ahead, signed again. Nothing is stored on the server.
- The token becomes the frame's
src, a public backend route. The frame has an opaque origin and must work without a session, so the route is answered before the backend authentication and never looks at cookies; it accepts only the token. Signature and expiry are checked, then the element is rendered and the frame document is returned with its Content Security Policy as HTTP header. An expired token gets a 410 response and the page module fetches one fresh token; a bad signature gets a 403.
What this means
- A token that leaks, through a log or a browser history, is useless seconds later and only ever rendered one element for one user's view. The descriptor in the page module cannot be turned into a token without that user's session, and a descriptor kept from an earlier view stops working when the user loses access to the record or eight hours pass.
- Both signatures use the site's encryption key
(
$GLOBALS). Whoever knows the key can forge tokens and render any record without a login, the same way they could forge everything else TYPO3 signs with it. Keep the key out of repositories and logs; rotating it invalidates all descriptors and tokens at once, editors just reload the page module.['TYPO3_ CONF_ VARS'] ['SYS'] ['encryption Key'] - The preview request has no backend user. Storages, image processing
and URL generation behave as in the frontend. Workspace and language are
taken from the token, so editors in a workspace see their versions.
TypoScript that expects a backend user, or a data processor reading
$GLOBALS, fails in the frame with a callout, not in the page module.['BE_ USER'] - The route is public by necessity, but it renders nothing without a valid token, and a token can only be minted by an authenticated backend user for a record the page module already showed them.
- Rendering frontend TypoScript means running the site package's PHP with the rights of the web server, as every frontend request does. This is the same trust you place in the frontend; nothing in the preview request grants it more.