Security model¶
Prelum runs Typst source supplied by its callers. This is suitable when authenticated users are allowed to compile documents inside a service you operate. Authentication controls who may submit a render; it does not make their source trusted.
Before exposing Prelum outside a development machine:
- use a private, rotated API token and restrict network access to intended clients;
- keep Typst package downloads disabled unless they are explicitly required;
- mount fonts and local packages read-only;
- choose request, concurrency, timeout and output limits for the available capacity;
- keep credentials, request bodies, templates and rendered documents out of logs and telemetry;
- add deployment-level process, filesystem and egress controls appropriate to your environment.
The sections below explain what Prelum itself enforces and where an operator still owns the boundary.
Controls¶
- The service requires a shared API token outside explicitly named development environments and compares it in constant time.
- The container and Typst subprocess run as uid 10001 rather than root.
- Every render has its own temporary project, which is passed to Typst as
--root. - Request file keys cannot escape or ambiguously address that project.
- Render concurrency, queue time, compiler time, request sizes and output sizes are bounded.
- Each Typst process runs under a heap limit where the deployment configures one, so a template that allocates without bound fails against that limit rather than the host's OOM killer. The bound is per process, not per service, and covers the heap and anonymous mappings only — memory-mapped font files and temporary scratch space lie outside it.
- Proxy variables point to a closed local port by default, and package cache/path arguments point to an empty per-render directory unless a local package mount is configured.
- Typst diagnostics are discarded because they may quote caller source.
- Mounted font and package directories are treated as read-only resources.
- The Typst subprocess receives an allowlisted environment rather than the service's own, so
neither the API token and Sentry DSN nor
TYPST_*argument overrides are visible to it.
Network access¶
No released Typst version has an offline flag. Prelum blocks normal compiler package downloads by
combining a closed proxy environment with empty package directories. Set
PRELUM_ALLOW_TYPST_NETWORK=true only when remote package access is intentional, and use
deployment-level egress policy as defence in depth.
Plugins¶
Prelum does not restrict WebAssembly plugins, and cannot: Typst identifies a plugin from its magic
bytes rather than its filename, so a module named mod.dat loads exactly as mod.wasm does. An
earlier check on the .wasm suffix was removed because it stopped only the honest spelling while
every caller had to mirror it.
Nothing is lost by that. A plugin runs in Typst's own sandbox with no host access, and a caller that can submit source can already run arbitrary Typst code; the bounds that matter are the render timeout and the per-render memory limit, which apply to plugins as to everything else. Deploy Prelum only where compiling authenticated caller source is acceptable.
Error privacy¶
Validation responses omit Pydantic's mirrored input and ctx. Caller-controlled values are
handled according to the field:
fileskeys appear whole incontext.keyonly after validation has bounded their characters and length;- object keys in
dataare shortened before appearing incontext.path; - validation locations in
context.errors[].locand messages incontext.errors[].msgare also shortened, because Pydantic may include values that have not passed key validation; datavalues are never returned;- configured limits such as
limitandtimeout_secsmay be returned because they are already public in the error detail and are not secrets.
Compiler stdout and stderr are neither returned nor logged. Logs and Sentry receive context as
one structured field and must not receive request bodies, credentials, proprietary templates or
generated artefacts. Sentry never attaches request bodies, and Prelum redacts the API token header
before an event leaves the process because the SDK's built-in list does not know Prelum's custom
header name.