miki-template 2.2.2 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/docs.yml +3 -1
- package/.github/workflows/release.yml +1 -0
- package/benchmarks/ejs-results.json +6 -6
- package/benchmarks/ejs.js +5 -3
- package/benchmarks/handlebars-results.json +6 -6
- package/benchmarks/handlebars.js +5 -8
- package/benchmarks/miki-results.json +6 -6
- package/benchmarks/miki.js +6 -3
- package/benchmarks/pug-results.json +6 -6
- package/benchmarks/pug.js +5 -3
- package/docs/api/async-render.md +88 -3
- package/docs/api/cache.md +90 -3
- package/docs/api/compile.md +131 -3
- package/docs/api/context-processors.md +80 -3
- package/docs/api/filters.md +223 -3
- package/docs/api/finder.md +97 -3
- package/docs/api/helpers.md +56 -3
- package/docs/api/i18n.md +160 -3
- package/docs/api/index.md +82 -28
- package/docs/api/libraries.md +210 -3
- package/docs/api/render-partial.md +84 -3
- package/docs/api/render.md +95 -3
- package/docs/api/security.md +148 -3
- package/docs/api/setup-express.md +78 -2
- package/docs/api/tags.md +138 -4
- package/docs/filter.md +0 -0
- package/docs/guide/advanced-usage.md +403 -6
- package/docs/guide/async-rendering.md +312 -4
- package/docs/guide/context-processors.md +261 -4
- package/docs/guide/custom-filters.md +315 -4
- package/docs/guide/custom-tags.md +275 -4
- package/docs/guide/filters.md +675 -3
- package/docs/guide/getting-started.md +109 -7
- package/docs/guide/installation.md +99 -4
- package/docs/guide/partial-templates.md +371 -4
- package/docs/guide/quick-start.md +228 -6
- package/docs/guide/security.md +348 -3
- package/docs/guide/tags.md +789 -6
- package/docs/guide/template-discovery.md +174 -4
- package/docs/guide/template-inheritance.md +277 -4
- package/docs/index.md +24 -42
- package/docs/integrations/elysia.md +4 -2
- package/docs/integrations/express.md +219 -219
- package/docs/integrations/fastify.md +4 -2
- package/docs/integrations/hono.md +4 -2
- package/docs/integrations/index.md +68 -68
- package/docs/integrations/koa.md +4 -2
- package/docs/integrations/nestjs.md +4 -2
- package/docs/integrations/tsed.md +4 -2
- package/docs/performance.md +45 -8
- package/ex.mjs +1 -1
- package/mkdocs.yml +0 -22
- package/overrides/main.html +1 -1
- package/package.json +1 -1
- package/requirements-docs.txt +2 -1
- package/src/codegen.js +905 -0
- package/src/context.js +42 -30
- package/src/filters.js +16 -0
- package/src/index.js +66 -61
- package/src/tags/control.js +15 -12
- package/src/utils.js +60 -0
- package/tests/filters.test.js +9 -0
- package/.github/workflows/npm-publish-github-packages.yml +0 -36
- package/docs/javascripts/extra.js +0 -174
- package/docs/stylesheets/extra.css +0 -819
- package/overrides/partials/footer.html +0 -9
package/docs/guide/security.md
CHANGED
|
@@ -1,345 +1,690 @@
|
|
|
1
|
-
# Security
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
|
|
2
4
|
|
|
3
5
|
miki-template follows Django's security semantics to protect against common web vulnerabilities.
|
|
4
6
|
|
|
7
|
+
|
|
8
|
+
|
|
5
9
|
## Table of Contents
|
|
6
10
|
|
|
11
|
+
|
|
12
|
+
|
|
7
13
|
- [Auto-Escaping](#auto-escaping)
|
|
14
|
+
|
|
8
15
|
- [SafeString](#safestring)
|
|
16
|
+
|
|
9
17
|
- [CSRF Protection](#csrf-protection)
|
|
18
|
+
|
|
10
19
|
- [CSP Nonce](#csp-nonce)
|
|
20
|
+
|
|
11
21
|
- [Path Traversal Protection](#path-traversal-protection)
|
|
22
|
+
|
|
12
23
|
- [No Unsafe Code Execution](#no-unsafe-code-execution)
|
|
24
|
+
|
|
13
25
|
- [HTML Escaping Details](#html-escaping-details)
|
|
26
|
+
|
|
14
27
|
- [Context Processor Security](#context-processor-security)
|
|
15
28
|
|
|
29
|
+
|
|
30
|
+
|
|
16
31
|
---
|
|
17
32
|
|
|
33
|
+
|
|
34
|
+
|
|
18
35
|
## Auto-Escaping
|
|
19
36
|
|
|
37
|
+
|
|
38
|
+
|
|
20
39
|
All variable output is HTML-escaped by default. This means any `<`, `>`, `&`, `"`, `'`, and `` ` `` characters in your data are converted to HTML entities before rendering.
|
|
21
40
|
|
|
41
|
+
|
|
42
|
+
|
|
22
43
|
```html
|
|
44
|
+
|
|
23
45
|
{{ user_input }}
|
|
46
|
+
|
|
24
47
|
```
|
|
25
48
|
|
|
49
|
+
|
|
50
|
+
|
|
26
51
|
If `user_input` is `<script>alert(1)</script>`, the output is:
|
|
27
52
|
|
|
53
|
+
|
|
54
|
+
|
|
28
55
|
```html
|
|
56
|
+
|
|
29
57
|
<script>alert("1")</script>
|
|
58
|
+
|
|
30
59
|
```
|
|
31
60
|
|
|
61
|
+
|
|
62
|
+
|
|
32
63
|
This prevents XSS (Cross-Site Scripting) attacks where malicious users inject executable JavaScript.
|
|
33
64
|
|
|
65
|
+
|
|
66
|
+
|
|
34
67
|
### Disabling Auto-Escaping
|
|
35
68
|
|
|
69
|
+
|
|
70
|
+
|
|
36
71
|
Use `{% autoescape off %}` to disable escaping for a block:
|
|
37
72
|
|
|
73
|
+
|
|
74
|
+
|
|
38
75
|
```html
|
|
76
|
+
|
|
39
77
|
{% autoescape off %}
|
|
78
|
+
|
|
40
79
|
{{ trusted_html }} {# not escaped #}
|
|
80
|
+
|
|
41
81
|
{% endautoescape %}
|
|
82
|
+
|
|
42
83
|
```
|
|
43
84
|
|
|
85
|
+
|
|
86
|
+
|
|
44
87
|
### Re-enabling Auto-Escaping
|
|
45
88
|
|
|
89
|
+
|
|
90
|
+
|
|
46
91
|
```html
|
|
92
|
+
|
|
47
93
|
{% autoescape on %}
|
|
94
|
+
|
|
48
95
|
{{ user_input }} {# escaped again #}
|
|
96
|
+
|
|
49
97
|
{% endautoescape %}
|
|
98
|
+
|
|
50
99
|
```
|
|
51
100
|
|
|
101
|
+
|
|
102
|
+
|
|
52
103
|
**Real-world blog post:**
|
|
53
104
|
|
|
105
|
+
|
|
106
|
+
|
|
54
107
|
```html
|
|
108
|
+
|
|
55
109
|
<article>
|
|
110
|
+
|
|
56
111
|
<!-- Post body is trusted CMS content -->
|
|
112
|
+
|
|
57
113
|
{% autoescape off %}
|
|
114
|
+
|
|
58
115
|
{{ post.body_html }}
|
|
116
|
+
|
|
59
117
|
{% endautoescape %}
|
|
60
118
|
|
|
119
|
+
|
|
120
|
+
|
|
61
121
|
<!-- User comment is untrusted -->
|
|
122
|
+
|
|
62
123
|
<div class="comments">
|
|
124
|
+
|
|
63
125
|
{% for comment in comments %}
|
|
126
|
+
|
|
64
127
|
<p>{{ comment.text }}</p>
|
|
128
|
+
|
|
65
129
|
{% endfor %}
|
|
130
|
+
|
|
66
131
|
</div>
|
|
132
|
+
|
|
67
133
|
</article>
|
|
134
|
+
|
|
68
135
|
```
|
|
69
136
|
|
|
137
|
+
|
|
138
|
+
|
|
70
139
|
## SafeString
|
|
71
140
|
|
|
141
|
+
|
|
142
|
+
|
|
72
143
|
Use the `safe` filter or `markSafe()` to mark content as trusted (bypassing auto-escaping):
|
|
73
144
|
|
|
145
|
+
|
|
146
|
+
|
|
74
147
|
=== "Template (safe filter)"
|
|
75
148
|
|
|
149
|
+
|
|
150
|
+
|
|
76
151
|
```html
|
|
152
|
+
|
|
77
153
|
{{ trusted_html|safe }}
|
|
154
|
+
|
|
78
155
|
```
|
|
79
156
|
|
|
157
|
+
|
|
158
|
+
|
|
80
159
|
=== "CommonJS (markSafe)"
|
|
81
160
|
|
|
161
|
+
|
|
162
|
+
|
|
82
163
|
```javascript
|
|
164
|
+
|
|
83
165
|
const { markSafe } = require('miki-template');
|
|
84
166
|
|
|
167
|
+
|
|
168
|
+
|
|
85
169
|
const html = markSafe('<b>ok</b>');
|
|
170
|
+
|
|
86
171
|
// Will not be escaped when rendered
|
|
172
|
+
|
|
87
173
|
```
|
|
88
174
|
|
|
175
|
+
|
|
176
|
+
|
|
89
177
|
=== "ES Modules (markSafe)"
|
|
90
178
|
|
|
179
|
+
|
|
180
|
+
|
|
91
181
|
```javascript
|
|
182
|
+
|
|
92
183
|
import { markSafe } from 'miki-template';
|
|
93
184
|
|
|
185
|
+
|
|
186
|
+
|
|
94
187
|
const html = markSafe('<b>ok</b>');
|
|
188
|
+
|
|
95
189
|
```
|
|
96
190
|
|
|
191
|
+
|
|
192
|
+
|
|
97
193
|
### SafeString Class
|
|
98
194
|
|
|
195
|
+
|
|
196
|
+
|
|
99
197
|
You can also create `SafeString` instances directly:
|
|
100
198
|
|
|
199
|
+
|
|
200
|
+
|
|
101
201
|
=== "CommonJS"
|
|
102
202
|
|
|
203
|
+
|
|
204
|
+
|
|
103
205
|
```javascript
|
|
206
|
+
|
|
104
207
|
const { SafeString } = require('miki-template');
|
|
105
208
|
|
|
209
|
+
|
|
210
|
+
|
|
106
211
|
const html = new SafeString('<b>Bold</b>');
|
|
212
|
+
|
|
107
213
|
// {{ html }} renders as <b>Bold</b>, NOT <b>Bold</b>
|
|
214
|
+
|
|
108
215
|
```
|
|
109
216
|
|
|
217
|
+
|
|
218
|
+
|
|
110
219
|
=== "ES Modules"
|
|
111
220
|
|
|
221
|
+
|
|
222
|
+
|
|
112
223
|
```javascript
|
|
224
|
+
|
|
113
225
|
import { SafeString } from 'miki-template';
|
|
114
226
|
|
|
227
|
+
|
|
228
|
+
|
|
115
229
|
const html = new SafeString('<b>Bold</b>');
|
|
230
|
+
|
|
116
231
|
```
|
|
117
232
|
|
|
233
|
+
|
|
234
|
+
|
|
118
235
|
### Checking if a value is safe
|
|
119
236
|
|
|
237
|
+
|
|
238
|
+
|
|
120
239
|
=== "CommonJS"
|
|
121
240
|
|
|
241
|
+
|
|
242
|
+
|
|
122
243
|
```javascript
|
|
244
|
+
|
|
123
245
|
const { isSafe } = require('miki-template');
|
|
124
246
|
|
|
247
|
+
|
|
248
|
+
|
|
125
249
|
if (isSafe(value)) {
|
|
250
|
+
|
|
126
251
|
// value is marked safe
|
|
252
|
+
|
|
127
253
|
}
|
|
254
|
+
|
|
128
255
|
```
|
|
129
256
|
|
|
257
|
+
|
|
258
|
+
|
|
130
259
|
=== "ES Modules"
|
|
131
260
|
|
|
261
|
+
|
|
262
|
+
|
|
132
263
|
```javascript
|
|
264
|
+
|
|
133
265
|
import { isSafe } from 'miki-template';
|
|
134
266
|
|
|
267
|
+
|
|
268
|
+
|
|
135
269
|
if (isSafe(value)) {
|
|
270
|
+
|
|
136
271
|
// value is marked safe
|
|
272
|
+
|
|
137
273
|
}
|
|
274
|
+
|
|
138
275
|
```
|
|
139
276
|
|
|
277
|
+
|
|
278
|
+
|
|
140
279
|
## HTML Filters
|
|
141
280
|
|
|
281
|
+
|
|
282
|
+
|
|
142
283
|
### safe
|
|
143
284
|
|
|
285
|
+
|
|
286
|
+
|
|
144
287
|
Mark a string as safe (no escaping):
|
|
145
288
|
|
|
289
|
+
|
|
290
|
+
|
|
146
291
|
```html
|
|
292
|
+
|
|
147
293
|
{{ content|safe }}
|
|
294
|
+
|
|
148
295
|
```
|
|
149
296
|
|
|
297
|
+
|
|
298
|
+
|
|
150
299
|
### escape
|
|
151
300
|
|
|
301
|
+
|
|
302
|
+
|
|
152
303
|
Force HTML escaping, even on SafeString values. This matches Django's `{{ value|escape }}` semantics:
|
|
153
304
|
|
|
305
|
+
|
|
306
|
+
|
|
154
307
|
```html
|
|
308
|
+
|
|
155
309
|
<!-- Even if content is marked safe, escape forces HTML entities -->
|
|
310
|
+
|
|
156
311
|
{{ content|escape }}
|
|
312
|
+
|
|
157
313
|
```
|
|
158
314
|
|
|
315
|
+
|
|
316
|
+
|
|
159
317
|
**Real-world: render user-generated content with a safe wrapper**
|
|
160
318
|
|
|
319
|
+
|
|
320
|
+
|
|
161
321
|
```html
|
|
322
|
+
|
|
162
323
|
<!-- In a filter -->
|
|
324
|
+
|
|
163
325
|
{{ user.bio|default:"No bio yet."|escape }}
|
|
326
|
+
|
|
164
327
|
```
|
|
165
328
|
|
|
329
|
+
|
|
330
|
+
|
|
166
331
|
## CSRF Protection
|
|
167
332
|
|
|
333
|
+
|
|
334
|
+
|
|
168
335
|
Use the `{% csrf_token %}` tag to output a hidden input with the CSRF token:
|
|
169
336
|
|
|
337
|
+
|
|
338
|
+
|
|
170
339
|
```html
|
|
340
|
+
|
|
171
341
|
<form method="post">
|
|
342
|
+
|
|
172
343
|
{% csrf_token %}
|
|
344
|
+
|
|
173
345
|
<button type="submit">Submit</button>
|
|
346
|
+
|
|
174
347
|
</form>
|
|
348
|
+
|
|
175
349
|
```
|
|
176
350
|
|
|
351
|
+
|
|
352
|
+
|
|
177
353
|
The token value is HTML-escaped to prevent attribute injection. The output is:
|
|
178
354
|
|
|
355
|
+
|
|
356
|
+
|
|
179
357
|
```html
|
|
358
|
+
|
|
180
359
|
<input type="hidden" name="csrfmiddlewaretoken" value="escaped_token_value">
|
|
360
|
+
|
|
181
361
|
```
|
|
182
362
|
|
|
363
|
+
|
|
364
|
+
|
|
183
365
|
### How it works
|
|
184
366
|
|
|
367
|
+
|
|
368
|
+
|
|
185
369
|
- The tag looks for `csrf_token` in the template context.
|
|
370
|
+
|
|
186
371
|
- If found, it outputs a hidden input with the escaped token value.
|
|
372
|
+
|
|
187
373
|
- If not found, it outputs an empty hidden input.
|
|
188
374
|
|
|
375
|
+
|
|
376
|
+
|
|
189
377
|
Provide `csrf_token` in context:
|
|
190
378
|
|
|
379
|
+
|
|
380
|
+
|
|
191
381
|
=== "CommonJS (Express + csurf)"
|
|
192
382
|
|
|
383
|
+
|
|
384
|
+
|
|
193
385
|
```javascript
|
|
386
|
+
|
|
194
387
|
const csrf = require('csurf');
|
|
195
388
|
|
|
389
|
+
|
|
390
|
+
|
|
196
391
|
app.use(csrf({ cookie: true }));
|
|
392
|
+
|
|
197
393
|
app.use((req, res, next) => {
|
|
394
|
+
|
|
198
395
|
res.locals.csrf_token = req.csrfToken();
|
|
396
|
+
|
|
199
397
|
next();
|
|
398
|
+
|
|
200
399
|
});
|
|
400
|
+
|
|
201
401
|
```
|
|
202
402
|
|
|
403
|
+
|
|
404
|
+
|
|
203
405
|
=== "ES Modules"
|
|
204
406
|
|
|
407
|
+
|
|
408
|
+
|
|
205
409
|
```javascript
|
|
410
|
+
|
|
206
411
|
app.use((req, res, next) => {
|
|
412
|
+
|
|
207
413
|
res.locals.csrf_token = req.csrfToken();
|
|
414
|
+
|
|
208
415
|
next();
|
|
416
|
+
|
|
209
417
|
});
|
|
418
|
+
|
|
210
419
|
```
|
|
211
420
|
|
|
421
|
+
|
|
422
|
+
|
|
212
423
|
## CSP Nonce
|
|
213
424
|
|
|
425
|
+
|
|
426
|
+
|
|
214
427
|
Use the `{% csp_nonce_attr %}` tag to output a `nonce` attribute when `csp_nonce` is in the context. This is essential for Content-Security-Policy-compliant inline scripts:
|
|
215
428
|
|
|
429
|
+
|
|
430
|
+
|
|
216
431
|
```html
|
|
432
|
+
|
|
217
433
|
<script {% csp_nonce_attr %} src="/js/app.js"></script>
|
|
434
|
+
|
|
218
435
|
```
|
|
219
436
|
|
|
437
|
+
|
|
438
|
+
|
|
220
439
|
If `csp_nonce` is present in context, the output is:
|
|
221
440
|
|
|
441
|
+
|
|
442
|
+
|
|
222
443
|
```html
|
|
444
|
+
|
|
223
445
|
<script nonce="abc123" src="/js/app.js"></script>
|
|
446
|
+
|
|
224
447
|
```
|
|
225
448
|
|
|
449
|
+
|
|
450
|
+
|
|
226
451
|
If `csp_nonce` is missing, the tag outputs nothing — the `<script>` tag is rendered without a nonce.
|
|
227
452
|
|
|
453
|
+
|
|
454
|
+
|
|
228
455
|
Provide `csp_nonce` in context:
|
|
229
456
|
|
|
457
|
+
|
|
458
|
+
|
|
230
459
|
=== "CommonJS"
|
|
231
460
|
|
|
461
|
+
|
|
462
|
+
|
|
232
463
|
```javascript
|
|
464
|
+
|
|
233
465
|
app.use((req, res, next) => {
|
|
466
|
+
|
|
234
467
|
res.locals.csp_nonce = crypto.randomBytes(16).toString('base64');
|
|
468
|
+
|
|
235
469
|
next();
|
|
470
|
+
|
|
236
471
|
});
|
|
472
|
+
|
|
237
473
|
```
|
|
238
474
|
|
|
475
|
+
|
|
476
|
+
|
|
239
477
|
=== "ES Modules"
|
|
240
478
|
|
|
479
|
+
|
|
480
|
+
|
|
241
481
|
```javascript
|
|
482
|
+
|
|
242
483
|
import crypto from 'node:crypto';
|
|
243
484
|
|
|
485
|
+
|
|
486
|
+
|
|
244
487
|
app.use((req, res, next) => {
|
|
488
|
+
|
|
245
489
|
res.locals.csp_nonce = crypto.randomBytes(16).toString('base64');
|
|
490
|
+
|
|
246
491
|
next();
|
|
492
|
+
|
|
247
493
|
});
|
|
494
|
+
|
|
248
495
|
```
|
|
249
496
|
|
|
497
|
+
|
|
498
|
+
|
|
250
499
|
## Path Traversal Protection
|
|
251
500
|
|
|
501
|
+
|
|
502
|
+
|
|
252
503
|
`{% extends %}`, `{% include %}`, and `{% extends %}` paths are validated to prevent directory traversal attacks:
|
|
253
504
|
|
|
505
|
+
|
|
506
|
+
|
|
254
507
|
```html
|
|
508
|
+
|
|
255
509
|
{% extends "../../etc/passwd" %} {# REJECTED #}
|
|
510
|
+
|
|
256
511
|
{% include "../../secrets" %} {# REJECTED #}
|
|
512
|
+
|
|
257
513
|
```
|
|
258
514
|
|
|
515
|
+
|
|
516
|
+
|
|
259
517
|
The engine checks that resolved paths stay within the allowed views directories. An error with message starting with `path traversal` is thrown if the resolved path escapes the views root.
|
|
260
518
|
|
|
519
|
+
|
|
520
|
+
|
|
261
521
|
## No Unsafe Code Execution
|
|
262
522
|
|
|
523
|
+
|
|
524
|
+
|
|
263
525
|
miki-template never uses `eval()`. Expressions are parsed and evaluated safely using the AST-based expression evaluator. This prevents code injection attacks — template expressions like `{{ user.name }}` are resolved through property lookups, never by executing arbitrary JavaScript.
|
|
264
526
|
|
|
527
|
+
|
|
528
|
+
|
|
265
529
|
## HTML Escaping Details
|
|
266
530
|
|
|
531
|
+
|
|
532
|
+
|
|
267
533
|
miki-template uses the [`he`](https://github.com/mathiasbynetworks/he) library for HTML escaping, which converts:
|
|
268
534
|
|
|
535
|
+
|
|
536
|
+
|
|
269
537
|
| Character | Escaped |
|
|
538
|
+
|
|
270
539
|
|-----------|---------|
|
|
540
|
+
|
|
271
541
|
| `&` | `&` |
|
|
542
|
+
|
|
272
543
|
| `<` | `<` |
|
|
544
|
+
|
|
273
545
|
| `>` | `>` |
|
|
546
|
+
|
|
274
547
|
| `"` | `"` |
|
|
548
|
+
|
|
275
549
|
| `'` | `'` |
|
|
550
|
+
|
|
276
551
|
| `` ` `` | ``` |
|
|
277
552
|
|
|
553
|
+
|
|
554
|
+
|
|
278
555
|
```javascript
|
|
556
|
+
|
|
279
557
|
// Access escaping directly
|
|
558
|
+
|
|
280
559
|
const { escapeHtml } = require('miki-template');
|
|
560
|
+
|
|
281
561
|
// or
|
|
562
|
+
|
|
282
563
|
import { escapeHtml } from 'miki-template';
|
|
283
564
|
|
|
565
|
+
|
|
566
|
+
|
|
284
567
|
const escaped = escapeHtml('<script>alert("xss")</script>');
|
|
568
|
+
|
|
285
569
|
// → "<script>alert("xss")</script>"
|
|
286
570
|
|
|
571
|
+
|
|
572
|
+
|
|
287
573
|
// Force-escape even SafeString values (third argument)
|
|
574
|
+
|
|
288
575
|
const reescaped = escapeHtml(safeStringInstance, true);
|
|
576
|
+
|
|
289
577
|
```
|
|
290
578
|
|
|
579
|
+
|
|
580
|
+
|
|
291
581
|
### Programmatic Escaping
|
|
292
582
|
|
|
583
|
+
|
|
584
|
+
|
|
293
585
|
=== "CommonJS"
|
|
294
586
|
|
|
587
|
+
|
|
588
|
+
|
|
295
589
|
```javascript
|
|
590
|
+
|
|
296
591
|
const { escapeHtml } = require('miki-template');
|
|
297
592
|
|
|
593
|
+
|
|
594
|
+
|
|
298
595
|
const escaped = escapeHtml('<script>');
|
|
596
|
+
|
|
299
597
|
// Output: <script>
|
|
598
|
+
|
|
300
599
|
```
|
|
301
600
|
|
|
601
|
+
|
|
602
|
+
|
|
302
603
|
=== "ES Modules"
|
|
303
604
|
|
|
605
|
+
|
|
606
|
+
|
|
304
607
|
```javascript
|
|
608
|
+
|
|
305
609
|
import { escapeHtml } from 'miki-template';
|
|
306
610
|
|
|
611
|
+
|
|
612
|
+
|
|
307
613
|
const escaped = escapeHtml('<script>');
|
|
614
|
+
|
|
308
615
|
// Output: <script>
|
|
616
|
+
|
|
309
617
|
```
|
|
310
618
|
|
|
619
|
+
|
|
620
|
+
|
|
311
621
|
## Context Processor Security
|
|
312
622
|
|
|
623
|
+
|
|
624
|
+
|
|
313
625
|
Context processors run before every render and can inject global variables. Be careful not to expose sensitive data:
|
|
314
626
|
|
|
627
|
+
|
|
628
|
+
|
|
315
629
|
=== "CommonJS"
|
|
316
630
|
|
|
631
|
+
|
|
632
|
+
|
|
317
633
|
```javascript
|
|
634
|
+
|
|
318
635
|
const { registerContextProcessor } = require('miki-template');
|
|
319
636
|
|
|
637
|
+
|
|
638
|
+
|
|
320
639
|
registerContextProcessor((context) => {
|
|
640
|
+
|
|
321
641
|
return {
|
|
642
|
+
|
|
322
643
|
siteName: 'My App',
|
|
644
|
+
|
|
323
645
|
// Don't inject secrets here - they'll be available in ALL templates
|
|
646
|
+
|
|
324
647
|
};
|
|
648
|
+
|
|
325
649
|
});
|
|
650
|
+
|
|
326
651
|
```
|
|
327
652
|
|
|
653
|
+
|
|
654
|
+
|
|
328
655
|
=== "ES Modules"
|
|
329
656
|
|
|
657
|
+
|
|
658
|
+
|
|
330
659
|
```javascript
|
|
660
|
+
|
|
331
661
|
import { registerContextProcessor } from 'miki-template';
|
|
332
662
|
|
|
663
|
+
|
|
664
|
+
|
|
333
665
|
registerContextProcessor((context) => {
|
|
666
|
+
|
|
334
667
|
return {
|
|
668
|
+
|
|
335
669
|
siteName: 'My App',
|
|
670
|
+
|
|
336
671
|
};
|
|
672
|
+
|
|
337
673
|
});
|
|
674
|
+
|
|
338
675
|
```
|
|
339
676
|
|
|
677
|
+
|
|
678
|
+
|
|
340
679
|
**Key behavior:** Context processor values respect Django semantics — existing context values **win** over processor defaults. If you render with `{ user: req.user }` and a processor returns `{ user: 'Guest' }`, the explicit `req.user` is preserved.
|
|
341
680
|
|
|
681
|
+
|
|
682
|
+
|
|
342
683
|
## Next Steps
|
|
343
684
|
|
|
344
|
-
|
|
345
|
-
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
- [Integrations](../integrations/index.md)
|
|
688
|
+
|
|
689
|
+
- [API Reference: Security](../api/security.md)
|
|
690
|
+
|