Skip to content

Commit

Permalink
Implement additional filtering for Sentry, Issue #347
Browse files Browse the repository at this point in the history
More Sentry filtering: remove draft from token from URL

Unit tests for sentry filtering functionality

Sentry filtering: preserve some query params, always clear request body

Better organization and comments

Removed duplicate test url

Code and tests to sanitize enketo st query parameter

Remove x-action-notes header

Added GET + projects/formList to sensitive endpoints

appease linter with const sanitizedEvent
  • Loading branch information
ktuite committed Jul 21, 2021
1 parent 59556ec commit 66d4b5f
Show file tree
Hide file tree
Showing 2 changed files with 662 additions and 24 deletions.
116 changes: 92 additions & 24 deletions lib/external/sentry.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,93 @@
const { inspect } = require('util');
const { isBlank } = require('../util/util');

const sensitiveEndpoints = ['/users/reset/verify', '/users/(.*?)/password'];

// Endpoints where query strings are sensitive and should be removed
const sensitiveEndpoints = [
['GET', '/users[^/]'], // ?q=<part of name or email>
['GET', '/projects/([^/]*)/forms/([^/]*)/submissions.csv'], // ?key=passphrase for managed encryption
['GET', '/projects/([^/]*)/forms/([^/]*)/draft/submissions.csv'], // ?key=passphrase for managed encryption
['GET', '/projects/([^/]*)/formList'], // ?st=token for enketo public link access (showing form)
['HEAD', '/projects/([^/]*)/formList'], // ?st=token for enketo public link access (showing form)
['POST', '/projects/([^/]*)/submission'], // ?st=token for enketo public link access (submitting)
];

const isSensitiveEndpoint = (request) => {
for (const [method, endpoint] of sensitiveEndpoints) {
if (request.method === method && request.url.match(endpoint)) {
return true;
}
}
return false;
};

const filterTokenFromUrl = (url) => {
// remove user token from any URL
if (url.match('/v1/key/(.*?)')) {
return url.replace(/v1\/key\/([^/]*)/, 'v1/key/[FILTERED]');
}

// remove draft token from any URL
if (url.match('/v1/test/(.*?)')) {
return url.replace(/v1\/test\/([^/]*)/, 'v1/test/[FILTERED]');
}

// return original URL if there are no tokens
return url;
};

const sanitizeEventRequest = (event) => {
/* eslint-disable no-param-reassign */
if (event.request) {

// clear out all cookie values
if (event.request.headers && event.request.headers.cookie) {
const cookies = event.request.headers.cookie.split('; ')
.map((cookie) => cookie.slice(0, cookie.indexOf('=')))
.join(', ');

event.request.headers.cookie = cookies;
}

// clear out cookie values not stored in request header
if (event.request.cookies)
event.request.cookies = null;

// clear out Authorization header
if (event.request.headers && event.request.headers.authorization)
event.request.headers.authorization = null;

// clear out referer header (from enketo submission, can have ?st=token in url)
if (event.request.headers && event.request.headers.referer)
event.request.headers.referer = null;

// clear out x-action-notes
if (event.request.headers && event.request.headers['x-action-notes'])
event.request.headers['x-action-notes'] = null;

// clear out request body data for all endpoints
if (event.request.data)
event.request.data = null;

// remove sensitive info from URL via query string or path
if (event.request.url) {
// handle endpoints that might expose sensitive data through query params
if (isSensitiveEndpoint(event.request) && event.request.query_string) {
// remove query string from request data and from URL
event.request.query_string = null;
// remove it from URL, too, otherwise Sentry will find it
event.request.url = event.request.url.split('?')[0]; // eslint-disable-line prefer-destructuring
}

// filter out access tokens embedded in URLs
event.request.url = filterTokenFromUrl(event.request.url);
}
}

/* eslint-enable no-param-reassign */

return event;
};

const init = (config) => {
if ((config == null) || isBlank(config.key) || isBlank(config.project)) {
Expand All @@ -24,35 +110,17 @@ const init = (config) => {
Sentry.init({
dsn: `https://${config.key}@sentry.io/${config.project}`,
beforeSend(event) {
if (event.request) {
// clear out all cookie values
if (event.request.headers && event.request.headers.cookie) {
const cookies = event.request.headers.cookie.split('; ')
.map((cookie) => cookie.slice(0, cookie.indexOf('=')))
.join(', ');

event.request.headers.cookie = cookies; // eslint-disable-line no-param-reassign
}

// clear out all request body data for sensitive endpoints (logging in, etc)
if (event.request.url) {
for (const endpoint of sensitiveEndpoints) {
if (event.request.url.match(endpoint)) {
event.request.data = null; // eslint-disable-line no-param-reassign
}
}
}
}
const sanitizedEvent = sanitizeEventRequest(event);

// only file the event if it is a bare exception or it is a true 500.x Problem.
const error = event.extra.Error;
if (error == null) return event; // we aren't sure why there isn't an exception; pass through.
if ((error.isProblem !== true) || (error.httpCode === 500)) return event; // throw exceptions.
const error = sanitizedEvent.extra.Error;
if (error == null) return sanitizedEvent; // we aren't sure why there isn't an exception; pass through.
if ((error.isProblem !== true) || (error.httpCode === 500)) return sanitizedEvent; // throw exceptions.
return null; // we have a user-space problem.
}
});
return Sentry;
};

module.exports = { init };
module.exports = { init, sanitizeEventRequest, isSensitiveEndpoint, filterTokenFromUrl };

Loading

0 comments on commit 66d4b5f

Please sign in to comment.