Skip to content
Open
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
8 changes: 7 additions & 1 deletion lib/hbs.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,13 @@ function middleware (filename, options, cb) {
let res = template

Object.keys(values).forEach(function (id) {
res = res.replace(id, values[id])
const value = values[id]

const escaped = handlebars.Utils.escapeExpression(
value == null ? String(value) : value
)

res = res.replace(id, function () { return escaped })
})

cb(null, res)
Expand Down
40 changes: 40 additions & 0 deletions test/async_helpers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,21 @@ describe('express 4.x async helpers', () => {
cb(val)
})

hbs.registerAsyncHelper('async-payload', function (context, cb) {
const val = '<script>alert(document.cookie)</script>'

process.nextTick(function () {
cb(val)
})
})

// resolves to whatever it is given, to reach the non-string values
hbs.registerAsyncHelper('async-echo', function (value, context, cb) {
process.nextTick(function () {
cb(value)
})
})

app.get('/', function (req, res) {
res.render('async', {
layout: false,
Expand Down Expand Up @@ -86,6 +101,18 @@ describe('express 4.x async helpers', () => {
layout: 'layout_async',
})
})

app.get('/async-escape', function (req, res) {
res.render('async-escape', {
layout: false,
})
})

app.get('/async-non-string', function (req, res) {
res.render('async-non-string', {
layout: false,
})
})
})

test('index', async () => {
Expand Down Expand Up @@ -135,4 +162,17 @@ describe('express 4.x async helpers', () => {
)
await request(app).get('/layout-with-async').expect(expected)
})

// @see https://github.com/pillarjs/hbs/security/advisories/GHSA-rg36-rxv9-2m9q
test('async helper output is HTML-escaped', async () => {
await request(app)
.get('/async-escape')
.expect('&lt;script&gt;alert(document.cookie)&lt;/script&gt;')
})

test('async helper non-string values keep their text output', async () => {
await request(app)
.get('/async-non-string')
.expect('[null][undefined][false][0]')
})
})
1 change: 1 addition & 0 deletions test/views/async-escape.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{async-payload}}
1 change: 1 addition & 0 deletions test/views/async-non-string.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{{async-echo null}}][{{async-echo undefined}}][{{async-echo false}}][{{async-echo 0}}]
Loading