Security
Authentication and network placement#
The render service has no built-in authentication. Run it on a private network, or behind your API gateway or reverse proxy, and let that layer handle authentication, authorization, rate limiting and TLS. Typically only your back-end services can reach it.
CORS is off by default. Enable RD_CORS_ORIGINS only if browsers must call the API directly, and then put authentication in front of it.
Templates are untrusted input#
Template authors are not necessarily trusted, and templates can arrive through import files. The engine is built for that:
- Expressions are interpreted, never executed. A hand-written parser and evaluator, with no
evalornew Function. Only whitelisted functions run. Prototype access (__proto__,constructor,prototype) is blocked. - Resource bounds. Validation rejects extreme values: font sizes over 1000 pt, pages over 14,400 pt, more than 10,000 minimum rows, more than 20,000 elements.
Expression results are capped at 1,000,000 characters,
padat 10,000. Tables stop at 100,000 rows, and documents atRD_MAX_PAGES. - Isolation. Each render runs in a worker thread with a heap cap (
RD_WORKER_MAX_MEMORY_MB) and a deadline (RD_RENDER_TIMEOUT_MS). The thread is terminated on timeout or memory exhaustion, and replaced. - The watermark can't be bypassed by template content or render options. It is drawn by the engine, on top of every page, from built-in vector outlines, and only a valid license key switches it off. The designer has no key, so the browser can only produce PREVIEW-marked PDFs.
Images and SSRF#
- Templates and data can reference images. By default the server accepts only
data:URIs. - Remote images are fetched only from hosts in
RD_IMAGE_URL_ALLOWLIST: https only, no redirects, with a size cap and a timeout. - Local file paths and
file:URLs in templates are never read, and neither are fonts outside the bundled folder andRD_FONT_DIR. - Image dimensions are checked before decoding (
RD_MAX_IMAGE_PIXELS) to stop decompression bombs.
Data handling#
- The service processes data only for the duration of a render. It doesn't store data and doesn't log request bodies.
- The designer never receives business data. Its previews use preview text stored in the template. Tell authors not to paste real customer data into preview text.
- Templates may contain embedded images and fonts. Treat stored templates like other application configuration (access control, backups).
- Import PDF reads the PDF in the browser, with pdf.js (script evaluation off). The file is never uploaded. What the import keeps (text, shapes, images, and an optional tracing image of the page) is stored in the template, so a page imported from a real document contains that document's values until the author replaces them with fields. The tracing image is shown only in the designer; the render service never draws it.
The designer in your page#
- It renders inside Shadow DOM and builds no HTML from template strings. Template content is rendered as text. Fonts are registered through the
FontFaceAPI, and no CSS is generated from template values. - Links in templates are limited to
http(s),mailtoandtel. - It runs under a strict CSP without
unsafe-eval(Install). - It holds no license key: the key stays on the render service, which never returns it (
GET /v1/infoshows only the license state).
The portal's own Content Security Policy#
Portal pages are sent with a strict policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data: blob:; font-src 'self'; connect-src 'self';
frame-src 'self' blob:; worker-src 'self' blob:; base-uri 'none'; form-action 'self'; frame-ancestors 'none'. There are no inline scripts or styles.
The only exception is /playground/, which also allows style-src 'unsafe-inline', because it hosts the real designer, and the designer positions canvas
elements with inline style attributes. Your own pages that host the designer need the same: style-src 'unsafe-inline', plus blob: in frame-src (PDF
preview) and worker-src (Content Security Policy).
Help and docs pages#
The service serves this portal at / and the user guide at /help/. They contain no data, forms or authentication, and run only same-origin scripts under a strict
CSP (default-src 'none', form-action 'none', base-uri 'none'), with nosniff and Referrer-Policy: no-referrer.
RD_HELP_FRAME_ANCESTORS(default*) sets which origins may embed them in an iframe, as the designer's help drawer does. In production, set your application's origin(s), e.g.'self' https://app.customer.com, or'none'. Invalid values (containing;or line breaks) stop the server at startup.- Disable the portal and the user guide with
RD_SERVE_DOCS=falsefor API-only deployments.