Skip to content

Commit 6c05abe

Browse files
fix: sanitize user-provided Handlebars templates to prevent code injection
1 parent 06e331b commit 6c05abe

1 file changed

Lines changed: 28 additions & 6 deletions

File tree

packages/api/src/routes/notifications.ts

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,32 @@ import {
1818
} from '@evtivity/database';
1919
import Handlebars from 'handlebars';
2020
import { decryptString, wrapEmailHtml } from '@evtivity/lib';
21+
22+
/**
23+
* Compile a user-provided Handlebars template safely.
24+
* Rejects templates containing block helpers, partials, or subexpressions
25+
* that could be used for code injection. Only simple variable interpolation
26+
* ({{var}} and {{{var}}}) is allowed.
27+
*/
28+
function safeCompile(template: string): HandlebarsTemplateDelegate {
29+
// Block helpers: {{#each}}, {{#if}}, {{#with}}, {{#unless}}, {{#lookup}}, etc.
30+
if (/\{\{#/.test(template)) {
31+
throw new Error('Block helpers are not allowed in templates');
32+
}
33+
// Partials: {{> partialName}}
34+
if (/\{\{>/.test(template)) {
35+
throw new Error('Partials are not allowed in templates');
36+
}
37+
// Subexpressions: {{helper (subexpr)}}
38+
if (/\{\{[^}]*\(/.test(template)) {
39+
throw new Error('Subexpressions are not allowed in templates');
40+
}
41+
// Lookup/log helpers: {{lookup}}, {{log}}
42+
if (/\{\{\s*(lookup|log|helperMissing|blockHelperMissing)\b/.test(template)) {
43+
throw new Error('Dangerous helpers are not allowed in templates');
44+
}
45+
return Handlebars.compile(template);
46+
}
2147
import { zodSchema } from '../lib/zod-schema.js';
2248
import { paginationQuery } from '../lib/pagination.js';
2349
import nodemailer from 'nodemailer';
@@ -1047,12 +1073,8 @@ export function notificationRoutes(app: FastifyInstance): void {
10471073
email: 'john.doe@example.com',
10481074
};
10491075

1050-
const renderedSubject = body.subject
1051-
? Handlebars.compile(body.subject)(sampleVariables)
1052-
: null;
1053-
let renderedBodyHtml = body.bodyHtml
1054-
? Handlebars.compile(body.bodyHtml)(sampleVariables)
1055-
: null;
1076+
const renderedSubject = body.subject ? safeCompile(body.subject)(sampleVariables) : null;
1077+
let renderedBodyHtml = body.bodyHtml ? safeCompile(body.bodyHtml)(sampleVariables) : null;
10561078

10571079
if (body.channel === 'email' && renderedBodyHtml != null) {
10581080
renderedBodyHtml = wrapEmailHtml(

0 commit comments

Comments
 (0)