---
title: "Security"
manual: "Look - real frontend previews in the TYPO3 page module"
version: "1.1"
permalink: "https://docs.typo3.org/permalink/flowd/typo3-look:security@1.1"
source: "Security/Index.rst"
rendered: "2026-09-23T15:29:09+00:00"
---

# Security {#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](https://docs.typo3.org/permalink/flowd/typo3-look:the-preview-frame-is-sandboxed@1.1)
-   [Scripts are limited by a Content Security Policy](https://docs.typo3.org/permalink/flowd/typo3-look:scripts-are-limited-by-a-content-security-policy@1.1)
-   [Media are blocked by default](https://docs.typo3.org/permalink/flowd/typo3-look:media-are-blocked-by-default@1.1)
-   [No permissions](https://docs.typo3.org/permalink/flowd/typo3-look:no-permissions@1.1)
-   [What this means for you](https://docs.typo3.org/permalink/flowd/typo3-look:what-this-means-for-you@1.1)
-   [Previews rendered in a separate request](https://docs.typo3.org/permalink/flowd/typo3-look:previews-rendered-in-a-separate-request@1.1)

## The preview frame is sandboxed {#security-sandbox}

Every preview is an `<iframe>` with the `sandbox` attribute set
to `allow-scripts` 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-referrer`, so requests from the frame
carry no backend URL.

## Scripts are limited by a Content Security Policy {#security-csp}

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:

```text
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](https://docs.typo3.org/permalink/flowd/typo3-look:confval-flag-allow-site-scripts@1.1),
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-dynamic'` lets the trusted scripts
import their own modules.

## Media are blocked by default {#security-media}

Unless [allowMedia](https://docs.typo3.org/permalink/flowd/typo3-look:confval-flag-allow-media@1.1) is enabled, the same policy
also contains `media-src 'none'; frame-src 'none'`. 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](https://docs.typo3.org/permalink/flowd/typo3-look:security-preview-request@1.1)) 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-src 'self'`, 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 {#security-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 {#security-implications}

-   **Enable feature flags deliberately**

    The defaults give previews no capabilities beyond CSS. Enabling
    [allowSiteScripts](https://docs.typo3.org/permalink/flowd/typo3-look:confval-flag-allow-site-scripts@1.1) 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-Control-Allow-Origin: *` for their path, see
    [If your frontend uses web fonts](https://docs.typo3.org/permalink/flowd/typo3-look:installation-webserver@1.1). The header is standard practice for public
    static files and exposes nothing the files did not expose before.

-   **Some frontend techniques need adjustments**

    External SVG sprites (`<use href="...svg#icon">`) cannot load inside
    an opaque origin, see [SVG icons are missing](https://docs.typo3.org/permalink/flowd/typo3-look:known-problems-icons@1.1). Scripts running in the
    frame (with [allowSiteScripts](https://docs.typo3.org/permalink/flowd/typo3-look:confval-flag-allow-site-scripts@1.1)) have no
    `sessionStorage` or `localStorage` and cannot autoplay media;
    guard such calls in your frontend code as you would for private browsing
    modes.

## Previews rendered in a separate request {#security-preview-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 {#how-the-frame-gets-its-content}

1.  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.
1.  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.
1.  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 {#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['TYPO3_CONF_VARS']['SYS']['encryptionKey']`). 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.
-   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['BE_USER']`, fails in the frame with a callout, not in the
    page module.
-   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.
