Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/sixty-sloths-win.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@saleor/handlebars": patch
"saleor-app-smtp": patch
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changeset bumps saleor-app-smtp, but the code change is in @saleor/handlebars (see packages/handlebars/package.json). As written, the release will publish a version bump/changelog for the wrong package and won’t version the helper change correctly.

Suggested change
"saleor-app-smtp": patch
"@saleor/handlebars": patch

Copilot uses AI. Check for mistakes.
---

Re-added `JSONparse` handlebars helper
Original file line number Diff line number Diff line change
Expand Up @@ -100,17 +100,14 @@ describe("HandlebarsTemplateCompiler", () => {
expect(error.errorCode).toBe("HANDLEBARS_MISSING_HELPER");
});

it("rejects object helpers: JSONparse (arbitrary object construction)", () => {
it("allows object helper: JSONparse", () => {
const result = compiler.compile(
'{{JSONparse \'{"__proto__":{"admin":true}}\'}}',
'{{#with (JSONparse \'{"name":"test"}\')}}{{name}}{{/with}}',
{},
);

expect(result.isErr()).toBe(true);

const error = result._unsafeUnwrapErr();

expect(error.errorCode).toBe("HANDLEBARS_MISSING_HELPER");
expect(result.isOk()).toBe(true);
expect(result._unsafeUnwrap().template).toBe("test");
});

it("rejects object helpers: extend (prototype pollution)", () => {
Expand Down
3 changes: 2 additions & 1 deletion packages/handlebars/src/allowed-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ export const ALLOWED_HELPERS: Record<string, string[]> = {
"toPrecision",
],

// --- object: removed entirely ---
// --- object (extend, merge removed – prototype pollution risk) ---
object: ["JSONparse"],
Comment on lines +134 to +135
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ALLOWED_HELPERS now whitelists the object group with JSONparse, which changes behavior for downstream consumers. In apps/smtp/src/modules/smtp/services/handlebars-template-compiler.test.ts (lines 103-114) there is an explicit security regression test asserting JSONparse is not available; with this allow-list change it will become registered and that test (and the intended security posture) will break. Either keep JSONparse disallowed, or update SMTP’s threat model/tests and consider adding additional safeguards (e.g., rejecting __proto__/constructor/prototype keys) if templates can be user-controlled.

Suggested change
// --- object (extend, merge removed – prototype pollution risk) ---
object: ["JSONparse"],
// --- object: removed entirely (JSONparse disabled for security) ---

Copilot uses AI. Check for mistakes.

// --- path (resolve removed) ---
path: ["absolute", "dirname", "relative", "basename", "stem", "extname", "segments"],
Expand Down
13 changes: 12 additions & 1 deletion packages/handlebars/src/register-allowed-helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe("registerAllowedHelpers", () => {
});

describe("removed groups are not loaded", () => {
it.each(["fs", "logging", "markdown", "match", "object"])(
it.each(["fs", "logging", "markdown", "match"])(
"does not include removed group: %s",
(group) => {
expect(
Expand All @@ -57,6 +57,17 @@ describe("registerAllowedHelpers", () => {
);
});

describe("dangerous object helpers are not registered", () => {
it.each(["extend", "merge"])(
"does not register dangerous object helper: %s",
(name) => {
registerAllowedHelpers(hbs, handlebarsHelpers);

expect(hbs.helpers[name], `Helper "${name}" should NOT be registered`).toBeUndefined();
},
);
});

describe("individually removed helpers are not registered", () => {
it.each(["embed", "resolve"])(
"does not register removed helper: %s",
Expand Down
Loading