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
473 changes: 471 additions & 2 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,14 @@
"csv": "~5.3.2",
"csv-stringify": "~5.6.5",
"d3": "~7.8.5",
"dompurify": "~3.3.3",
"dotenv": "~16.4.5",
"escape-html": "~1.0.3",
"express": "~4.19.2",
"express-rate-limit": "~7.2.0",
"history": "~5.3.0",
"ini": "~4.1.3",
"jsdom": "~26.1.0",
"jsonschema": "~1.4.1",
"jsonwebtoken": "~9.0.0",
"lodash": "~4.17.21",
Expand Down
2 changes: 2 additions & 0 deletions src/scripts/checkHeader.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ FILES=$(echo "$FILES" | grep -v "src\/server\/test\/web\/readingsData\/.*")
FILES=$(echo "$FILES" | grep -v ".github\/**")
# Filter out unit test files
FILES=$(echo "$FILES" | grep -v "src\/server\/data\/unit\/.*")
# Filter out crossSite test files
FILES=$(echo "$FILES" | grep -v "src/server/test/crossSite/something.csv")

# Counts the files listed in FILES
NFILES=$(echo $FILES | wc -w)
Expand Down
1 change: 1 addition & 0 deletions src/server/routes/csv.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ router.post('/readings', validateReadingsCsvUploadParams, async (req, res) => {
message += '<h3>However, note that the processing of the readings returned these warning(s):</h3>' + msgTotal;
}
success(req, res, message);
//see if theres a way to accept this specific message
} else {
message = '<h2>It looks like the insert of the readings had issues with some or all of the readings where' +
' the processing of the readings returned these warning(s)/error(s):</h2>' + msgTotal;
Expand Down
31 changes: 15 additions & 16 deletions src/server/routes/response.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,31 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Functions to return a code and comment from an Express request.
// Functions to return a code and comment from an Express request.

const DOMPurify = require('../services/utils/sanitizer');

/**
* Inform the client of a success (200 OK).
*
* @param res The Express response object
* @param comment Any additional data to be returned to the client as a string
* Inform the client of a success (200 OK) with sanitized content.
*
* @param {express.Response} res The Express response object.
* @param {string} comment Any additional data to be returned to the client as a string.
*/
function success(res, comment = '') {
res.status(200) // 200 OK
.send(comment);
function success(res, comment = '') {
const safeComment = DOMPurify.sanitize(comment);
res.status(200).send(safeComment);
}

/**
* Inform the client of a failure with provided code or 500.
*
* @param res The Express response object
* @param code The code number to send back for request
* @param comment Any additional data to be returned to the client as a string
* Inform the client of a failure with provided code or 500, using sanitized content.
*
* @param {express.Response} res The Express response object.
* @param {number} code The code number to send back for the request.
* @param {string} comment Any additional data to be returned to the client as a string.
*/
function failure(res, code = 500, comment = '') {
res.status(code)
.send(comment);

const safeComment = DOMPurify.sanitize(comment);
res.status(code).send(safeComment);
}

module.exports = { success, failure };
28 changes: 13 additions & 15 deletions src/server/services/csvPipeline/success.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,33 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const express = require('express') /* needed to resolve types in JSDoc comments */
const express = require('express');
const DOMPurify = require('../utils/sanitizer');

/**
* Inform the client of a success (200 OK).
* Inform the client of a success (200 OK) with sanitized HTML content.
*
* @param {express.Request} req The Express request object
* @param {express.Response} res The Express response object
* @param {express.Request} req The Express request object.
* @param {express.Response} res The Express response object.
* @param {string} comment Any additional data to be returned to the client.
*
*/
function success(req, res, comment = '') {
res.status(200) // 200 OK
.send(`<h1>SUCCESS</h1>${comment}`);
const safeComment = DOMPurify.sanitize(comment);
res.status(200).send(`<h1>SUCCESS</h1>${safeComment}`);
}

/**
* Inform the client of a failure (400 OK).
* Inform the client of a failure (400 OK) with sanitized HTML content.
*
* @param {express.Request} req The Express request object
* @param {express.Response} res The Express response object
* @param {express.Request} req The Express request object.
* @param {express.Response} res The Express response object.
* @param {string} comment Any additional data to be returned to the client.
*
*/
function failure(req, res, comment = '') {
const safeComment = DOMPurify.sanitize(comment);
// 400 is client error. There is a small chance the insert into the DB failed
// but overlooking that.
res.status(400)
.send(`<h1>FAILURE</h1>${comment}`);

res.status(400).send(`<h1>FAILURE</h1>${safeComment}`);
}

module.exports = { success, failure };
module.exports = { success, failure };
12 changes: 12 additions & 0 deletions src/server/services/utils/sanitizer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const { JSDOM } = require('jsdom');
const createDOMPurify = require('dompurify');

// Create jsdom window application and initialize
const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window);

module.exports = DOMPurify;
60 changes: 60 additions & 0 deletions src/server/test/crossSite/crossSite.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* This file tests the functionality of the DOMPurify library. It tests for XSS
vulnerabilities in HTML of user uploaded data.*/

/* Run in OED Docker web container terminal/shell:
npm run testsome src/server/test/crossSite/crossSite.js */
const { chai, mocha, expect, app, testUser } = require('../common');

mocha.describe('Cross site', () => {

const xssIndicators = [
'onerror',
'alert',
'document.domain',
'<script',
'javascript:',
'onload',
'onclick',
'<iframe'
];

mocha.it('Test for sanitization of HTML', async () => {
const filePath = 'src/server/test/crossSite/readings.csv';

const res = await chai.request(app).post('/api/csv/readings')
.field('email', testUser.username)
.field('password', testUser.password)
/* This next line should produce:
res.text: <h1>FAILURE</h1>CSVPipelineError:
User Error: Meter with name '<img src="x">' not found.
*/
.field('meterName', '<img src=x onerror="alert(document.domain)">')
.field('gzip', "no")
.attach('csvfile', 'src/server/test/crossSite/something.csv');

expect(res).to.have.status(400);
expect(res.text).to.include('<img src="x">');

xssIndicators.forEach(indicator => {
expect(res.text, `Output should not contain ${indicator}`).to.not.include(indicator);
});
});

mocha.it('Test for sanitization of login HTML in username', async () => {
const res = await chai.request(app)
.post('/api/login')
.send({
username: '<img src=x onerror="alert(1)">',
password: 'password123'
});
expect(res).to.have.status(401);

xssIndicators.forEach(indicator => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I looked at the res.text and it would never have the username as it only indicates with "Not authorized". Thus, this isn't a good test of sanitization. I think a different route should be used.

expect(res.text, `Output should not contain ${indicator}`).to.not.include(indicator);
});
});
});
1 change: 1 addition & 0 deletions src/server/test/crossSite/something.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1,2,3
1 change: 1 addition & 0 deletions src/server/test/routes/responseParamsTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ mocha.describe('Response Utility Functions', () => {
expect(mockRes.sentData).to.equal('');
});

//should also accept empty quotes
mocha.it('should handle null comment', () => {
const mockRes = createMockResponse();

Expand Down