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