Open-Source Wikis

/

tldraw

/

Security

tldraw/tldraw

Security

Trust boundaries, authentication, content isolation, and licensing in the tldraw repo.

Trust boundaries in tldraw.com

graph LR
    Browser[user browser] -->|wss / https| Edge[Cloudflare edge]
    Edge --> SyncWorker[apps/dotcom/sync-worker]
    Edge --> Client[apps/dotcom/client static]
    Edge --> Asset[asset-upload-worker]
    Edge --> Resize[image-resize-worker]
    Edge --> UserContent[tldrawusercontent-worker]
    SyncWorker -->|R2| R2
    SyncWorker -->|sqlite| DOSqlite[(DO sqlite)]
    Asset -->|R2| R2
    Resize -->|R2| R2
    Client -->|sync| ZeroCache[zero-cache]
    ZeroCache -->|replication| Postgres[(Postgres)]

Several boundaries are intentional:

  • tldrawusercontent-worker serves user-uploaded files under a separate domain to isolate untrusted content from the main tldraw.com origin. This is a standard "user content on a separate eTLD" defense against cookie/XSS leakage.
  • asset-upload-worker is the only entry point that writes to the R2 asset bucket. The client uses signed URLs.
  • image-resize-worker is read-only against R2.
  • sync-worker validates inbound records against the schema before applying them. If a client sends a malformed shape record, TLSyncRoom rejects the message via TLRemoteSyncError.

Auth

The signed-in dotcom area (apps/dotcom/client/src/tla/) supports user authentication. Anonymous rooms (tldraw.com/r/<roomId>) are unauthenticated by design — anyone with the URL can join. This is a product choice; the underlying sync engine has no built-in authentication, so self-hosted deployments add their own.

For self-hosting, useSync accepts a headers callback so you can inject auth tokens:

useSync({
  uri: 'wss://...',
  roomId: '...',
  // headers: async () => ({ Authorization: `Bearer ${await getToken()}` }),
});

Embeds

The default embed list (packages/tldraw/src/lib/defaultEmbedDefinitions.ts) restricts embeddable content to a known allowlist (YouTube, Figma, GitHub, CodePen, …). Each entry has a sandbox attribute string that restricts what the embedded iframe can do. Custom apps can extend this list, but the recommended path is "extend the allowlist", not "render arbitrary URLs as iframes".

External content handlers

packages/tldraw/src/lib/defaultExternalContentHandlers.ts controls what happens when something is dropped, pasted, or uploaded. The defaults treat unknown content cautiously: unknown URLs become bookmarks, unknown text becomes plain text, unknown files are rejected.

License key

The SDK ships a license-key system under packages/editor/src/lib/license/. Production use of the SDK requires a key (from https://tldraw.dev/pricing). The watermark code in packages/editor/src/lib/watermarks.ts is what enforces this when no key is present. The license check is client-side and intentionally easy to circumvent — the license is a legal contract, not a DRM mechanism.

Sentry and PII

The dotcom client uses Sentry. The Sentry config (apps/dotcom/client/sentry.client.config.ts) configures sample rates and includes the release name from sentry-release-name.ts. Don't log user-identifiable data into errors; Sentry strips most fields by default but additional filtering is the developer's responsibility.

Reporting

SECURITY.md at the repo root documents how to report vulnerabilities. The short version: email security@tldraw.com.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Security – tldraw wiki | Factory