Paragon

Security

A template is executable code, not content. It runs arbitrary JavaScript in a browser inside the consuming application's process. Everything below follows from that one fact.

If you read nothing else: templates must come from a source you trust, and only people you trust with code should be able to publish one. No amount of hardening makes an untrusted template safe, because running it is the entire point of the engine.


What the engine actually enforces

These hold whatever the template tries, and each is covered by a test.

Control What it stops
Origin blocking Every request the page makes is served from the report's own assets or aborted. A chart library reaching for a CDN, a webfont, or a telemetry endpoint gets nothing, and the attempt is reported in BlockedRequests.
Report code charset Codes are matched against a pre-built index and restricted to [A-Za-z0-9._-], so GenerateAsync("../../appsettings") cannot resolve to a file. Structural, not a check that can be forgotten.
Asset path containment Every path a template asks for is normalised and resolved against the root; anything absolute, rooted, UNC, or climbing out with .. returns nothing.
Render timeout A template that hangs — an infinite loop, a readiness signal that never comes — is killed at RenderTimeout and the page force-closed, rather than holding a thread of the host.
Fresh browser context per render No cookies, storage or state carries from one report to the next.
No data at rest The engine writes nothing to disk during generation. Payloads live in memory for the length of the call.

What it does not

Be clear about the limits, because a control you think you have is worse than one you know you lack.

  • A template is not sandboxed from its own report's data. It receives the JSON you pass and can put any of it anywhere in the document. Do not pass a payload containing fields the recipient of the document should not see.
  • Blocking network egress is not confinement. A hostile template cannot phone home, but it can still write whatever it likes into the PDF — including data you did pass it.
  • A template can consume resources up to the limits. The timeout and size cap bound it; they do not prevent it.
  • The engine does not authenticate anything. Who may generate which report is the host application's decision, and it is not one the engine can make.

Verifying the template you are about to run

The designer stamps every exported template with a hash of itself:

"integrity": { "algorithm": "SHA-256", "templateHash": "…" }

Turn on verification in production:

options.VerifyTemplateIntegrity = true;   // off by default
options.VerifyAssetHashes = true;         // checks each asset against its recorded hash

The template hash covers the markup and the manifest — changing a page size or dropping a required asset alters what the report produces just as surely as editing the body, so both are sealed. What it deliberately ignores: line endings (a folder that travelled through source control or a zip is not tampered with) and manifest formatting (property order carries no meaning).

Two behaviours worth knowing:

  • A template whose content no longer matches its hash raises TamperedTemplateException, naming the file and both hashes.
  • A template carrying no hash is also refused when verification is on. Accepting one would reduce the check to a suggestion — anybody could pass it by deleting the block.

Verification is off by default so an existing folder keeps working after an upgrade. Turning it on is a deployment decision; make it deliberately.


Running the browser

The engine launches Chromium with its own sandbox enabled — it never passes --no-sandbox. That sandbox protects the operating system from the renderer process. It is not what protects you from a hostile template, and it is worth being precise about the difference.

Recommended, in order of value:

  1. Run the host application under a low-privilege account with no rights beyond its own folders. The browser inherits that account, so this bounds what a browser exploit could reach.
  2. Give the report folder read-only permissions to that account. The engine only reads it; the designer, which writes, runs elsewhere.
  3. Deny outbound network access to the account at the firewall if your environment allows it. The engine already blocks egress at the route handler — this is the belt to that pair of braces, and it also covers a browser exploit that bypasses the handler.
  4. Publish the pinned browser with the application (PLAYWRIGHT_BROWSERS_PATH=0). The version is pinned deliberately: an unreviewed browser upgrade changes both pagination and the security surface.

Containers make the first three straightforward: a non-root user, a read-only mount for the report folder, and no egress.


The injection footgun

Handlebars escapes {{ }}. It does not escape {{{ }}}.

<p>{{customer.notes}}</p>      escaped — data cannot become markup
<p>{{{customer.notes}}}</p>    raw — data becomes markup

A template using {{{ }}} is putting the caller's data into the document verbatim. Sometimes that is the intent — the {{{json .}}} island that feeds chart code is exactly that, and its encoder is what stops a value containing </script> from ending the island early. Often it is a mistake carried over from a web page.

The exporter flags every {{{ for review. Treat the warning as a question to answer rather than noise to clear: whoever publishes the template is deciding that the data source is as trusted as the template.


Reviewing a template before publishing

The designer's pre-export checks warn about the mechanical problems — raw output, external URLs, missing assets, charts without a readiness signal. A human still has to answer the rest:

  • Does this template's JavaScript do only what a report needs?
  • Is every {{{ }}} deliberate, and is that field's source trusted?
  • Does it reference only assets in this project?
  • Is the data the caller will pass free of fields the document's recipient must not see?
  • Was it published by someone entitled to ship code into this application?

Deployment checklist

  • VerifyTemplateIntegrity = true
  • VerifyAssetHashes = true
  • Host runs under a low-privilege account, report folder read-only to it
  • Outbound network denied to that account where the environment allows
  • RenderTimeout set for your reports, not left to chance
  • BlockedRequests monitored — a non-empty list means a template reached outside itself
  • Only trusted operators can publish and export templates