Skip to content

Commit

Permalink
fix(cve-2023-29827): replace EJS with Handlebars to resolve security …
Browse files Browse the repository at this point in the history
…warning

Relates to: loopbackio/loopback-next#9867

Signed-off-by: KalleV <[email protected]>
  • Loading branch information
KalleV committed Sep 1, 2023
1 parent 13adb46 commit 37fefdc
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 98 deletions.
52 changes: 48 additions & 4 deletions lib/send-html.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// License text available at https://opensource.org/licenses/MIT

'use strict';
const ejs = require('ejs');
const handlebars = require('handlebars');
const fs = require('fs');
const path = require('path');

Expand All @@ -16,13 +16,49 @@ const compiledTemplates = {

module.exports = sendHtml;

/**
* Sends HTML response to the client.
*
* @param {Object} res - The response object.
* @param {Object} data - The data object to be rendered in the HTML.
* @param {Object} options - The options object.
*/
function sendHtml(res, data, options) {
const toRender = {options, data};
// TODO: ability to call non-default template functions from options
const body = compiledTemplates.default(toRender);
sendResponse(res, body);
}

/**
* Returns the content of a Handlebars partial file as a string.
* @param {string} name - The name of the Handlebars partial file.
* @returns {string} The content of the Handlebars partial file as a string.
*/
function partial(name) {
const partialPath = path.resolve(assetDir, `${name}.hbs`);
const partialContent = fs.readFileSync(partialPath, 'utf8');
return partialContent;
}

handlebars.registerHelper('partial', partial);

/**
* Checks if the given property is a standard property.
* @param {string} prop - The property to check.
* @param {Object} options - The Handlebars options object.
* @returns {string} - The result of the Handlebars template.
*/
function standardProps(prop, options) {
const standardProps = ['name', 'statusCode', 'message', 'stack'];
if (standardProps.indexOf(prop) === -1) {
return options.fn(this);
}
return options.inverse(this);
}

handlebars.registerHelper('standardProps', standardProps);

/**
* Compile and cache the file with the `filename` key in options
*
Expand All @@ -32,15 +68,23 @@ function sendHtml(res, data, options) {
function compileTemplate(filepath) {
const options = {cache: true, filename: filepath};
const fileContent = fs.readFileSync(filepath, 'utf8');
return ejs.compile(fileContent, options);
return handlebars.compile(fileContent, options);
}

// loads and cache default error templates
/**
* Loads the default error handlebars template from the asset directory and compiles it.
* @returns {Function} The compiled handlebars template function.
*/
function loadDefaultTemplates() {
const defaultTemplate = path.resolve(assetDir, 'default-error.ejs');
const defaultTemplate = path.resolve(assetDir, 'default-error.hbs');
return compileTemplate(defaultTemplate);
}

/**
* Sends an HTML response with the given body to the provided response object.
* @param {Object} res - The response object to send the HTML response to.
* @param {string} body - The HTML body to send in the response.
*/
function sendResponse(res, body) {
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.end(body);
Expand Down
131 changes: 66 additions & 65 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
"dependencies": {
"accepts": "^1.3.8",
"debug": "^4.3.4",
"ejs": "^3.1.9",
"fast-safe-stringify": "^2.1.1",
"handlebars": "^4.7.8",
"http-status": "^1.6.2",
"js2xmlparser": "^5.0.0",
"strong-globalize": "^6.0.6"
Expand Down
9 changes: 6 additions & 3 deletions test/handler.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -607,10 +607,12 @@ describe('strong-error-handler', function() {
expect(res.statusCode).to.eql(404);
const body = res.error.text;
expect(body).to.match(
/<title>Error&lt;img onerror=alert\(1\) src=a&gt;<\/title>/,
// eslint-disable-next-line max-len
/<title>Error&lt;img onerror&#x3D;alert\(1\) src&#x3D;a&gt;<\/title>/,
);
expect(body).to.match(
/with id &lt;img onerror=alert\(1\) src=a&gt; found for Model/,
// eslint-disable-next-line max-len
/with id &lt;img onerror&#x3D;alert\(1\) src&#x3D;a&gt; found for Model/,
);
done();
});
Expand All @@ -627,7 +629,8 @@ describe('strong-error-handler', function() {
.expect(500)
.expect(/<title>ErrorWithProps<\/title>/)
.expect(
/500(.*?)a test error message&lt;img onerror=alert\(1\) src=a&gt;/,
// eslint-disable-next-line max-len
/500(.*?)a test error message&lt;img onerror&#x3D;alert\(1\) src&#x3D;a&gt;/,
done,
);
});
Expand Down
25 changes: 0 additions & 25 deletions views/default-error.ejs

This file was deleted.

Loading

0 comments on commit 37fefdc

Please sign in to comment.