本页暂无中文翻译
以下为英文原文。
Security
What Janja Protects Against
Cross-site scripting: output is escaped by default
Everything written with {{= ... }} is HTML-escaped (&, <, >, ", ') unless you opt out.
import { render } from 'janja';
await render('{{= input }}', { input: '<script>alert(1)</script>' });
// <script>alert(1)</script>Opt out per value, for HTML you trust:
import { render, Safe } from 'janja';
await render('{{= html }} {{= other | safe }}', {
html: new Safe('<b>trusted</b>'),
other: '<i>also trusted</i>',
});Keep autoEscape: true (the default). Escaping is for HTML text and quoted attribute values; it does not make a value safe inside <script>, inline event handlers, CSS, or unquoted attributes. Use the json filter to pass data to scripts and urlencode for URL components.
security.deepEscape: true changes how arrays and objects are written: instead of their default string form, the escaped values they contain are concatenated.
Expressions cannot leave the data you give them
Template expressions are not JavaScript. They can read properties of the data and globals you pass, call functions found there, and apply registered filters. They cannot reference JavaScript globals such as process, globalThis or require.
The identifiers constructor, prototype, __proto__, __defineGetter__, __defineSetter__, __lookupGetter__ and __lookupSetter__ are rejected at compile time anywhere in an expression, and the built-in get, pick and map filters refuse them as keys. This closes the usual route from a plain object to the Function constructor.
Restricting globals: sandbox mode
security.sandbox limits the renderer-level globals to an allow-list. Data passed to an individual render() call is always available.
import { Renderer } from 'janja';
const renderer = new Renderer({
globals: { site, db },
security: {
sandbox: true,
allowedGlobals: ['site'], // `db` is not visible to templates
},
});Math, Date and JSON are added in sandbox mode.
What Janja Does Not Protect Against
Do not render templates written by people you do not trust without additional isolation. Janja is not a hardened sandbox:
- A template can call any function reachable from the data and globals you pass. If you pass an object with a method that deletes files, a template can call it. Pass plain data.
- Custom filters and plugins run with full privileges.
- There are no CPU or memory limits. A template can loop over a huge collection or recurse through macros until the process runs out of resources.
includeloads whatever yourloaderreturns for the requested path. If template authors are untrusted, the loader must confine paths itself.
If you must run untrusted templates, do it in a separate process or isolate with resource limits, and pass only JSON-like data.
Auditing Helper
renderer.auditTemplate(template) scans the template text for a few suspicious patterns (eval(, Function(, require(, import(). It is a lint-level heuristic, not a security boundary: a clean result does not mean a template is safe.
Content Security Policy
Janja does not add CSP headers or nonces to your markup. renderer.getCSPHeaders() is a small convenience that formats the nonce you configured:
const renderer = new Renderer({ security: { cspNonce: nonce } });
renderer.getCSPHeaders();
// { 'Content-Security-Policy': "script-src 'nonce-...'" }Generate a fresh nonce per response, send the header yourself, and pass the nonce to the template as data for your <script nonce="..."> tags.
Checklist
- [ ]
autoEscapeis left enabled - [ ]
safe/Safeare only applied to HTML you produced or sanitized - [ ] Templates come from your own codebase, or run in an isolated process
- [ ] Data passed to templates is plain data, without powerful methods
- [ ] The
loadercannot be steered outside the template directory - [ ] Templates are validated in CI (
validateTemplate)
Reporting Security Issues
Please do not open a public issue. Report vulnerabilities privately through GitHub: https://github.com/vvenv/janja/security/advisories/new.