From f6ac73a741570aa86f47c550997873019bee882a Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Mon, 13 Mar 2023 20:42:08 -0400 Subject: [PATCH] Add sanitization to log messages to reduce chance of tokens ending up in server logs --- server/logging/Logger.ts | 70 ++++++++++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 10 deletions(-) diff --git a/server/logging/Logger.ts b/server/logging/Logger.ts index da398cc0b..cc65b909d 100644 --- a/server/logging/Logger.ts +++ b/server/logging/Logger.ts @@ -1,6 +1,6 @@ import { IncomingMessage } from "http"; import chalk from "chalk"; -import { isEmpty } from "lodash"; +import { isEmpty, isArray, isObject, isString } from "lodash"; import winston from "winston"; import env from "@server/env"; import Metrics from "@server/logging/Metrics"; @@ -27,7 +27,7 @@ type Extra = Record; class Logger { output: winston.Logger; - constructor() { + public constructor() { this.output = winston.createLogger({ level: env.LOG_LEVEL, }); @@ -54,8 +54,8 @@ class Logger { * @param category A log message category that will be prepended * @param extra Arbitrary data to be logged that will appear in prod logs */ - info(label: LogCategory, message: string, extra?: Extra) { - this.output.info(message, { ...extra, label }); + public info(label: LogCategory, message: string, extra?: Extra) { + this.output.info(message, { ...this.sanitize(extra), label }); } /** @@ -64,8 +64,8 @@ class Logger { * @param category A log message category that will be prepended * @param extra Arbitrary data to be logged that will appear in prod logs */ - debug(label: LogCategory, message: string, extra?: Extra) { - this.output.debug(message, { ...extra, label }); + public debug(label: LogCategory, message: string, extra?: Extra) { + this.output.debug(message, { ...this.sanitize(extra), label }); } /** @@ -74,7 +74,7 @@ class Logger { * @param message A warning message * @param extra Arbitrary data to be logged that will appear in prod logs */ - warn(message: string, extra?: Extra) { + public warn(message: string, extra?: Extra) { Metrics.increment("logger.warning"); if (env.SENTRY_DSN) { @@ -90,7 +90,7 @@ class Logger { } if (isProduction) { - this.output.warn(message, extra); + this.output.warn(message, this.sanitize(extra)); } else if (extra) { console.warn(message, extra); } else { @@ -106,7 +106,7 @@ class Logger { * @param extra Arbitrary data to be logged that will appear in prod logs * @param request An optional request object to attach to the error */ - error( + public error( message: string, error: Error, extra?: Extra, @@ -122,7 +122,7 @@ class Logger { scope.setLevel("error"); for (const key in extra) { - scope.setExtra(key, extra[key]); + scope.setExtra(key, this.sanitize(extra[key])); } if (request) { @@ -147,6 +147,56 @@ class Logger { }); } } + + /** + * Sanitize data attached to logs and errors to remove sensitive information. + * + * @param input The data to sanitize + * @returns The sanitized data + */ + private sanitize(input: T): T { + // Short circuit if we're not in production to enable easier debugging + if (!isProduction) { + return input; + } + + const sensitiveFields = [ + "accessToken", + "refreshToken", + "token", + "password", + "content", + ]; + + if (isString(input)) { + if (sensitiveFields.some((field) => input.includes(field))) { + return "[Filtered]" as T; + } + } + + if (isArray(input)) { + return input.map(this.sanitize) as T; + } + + if (isObject(input)) { + const output = { ...input }; + + for (const key of Object.keys(output)) { + if (isObject(output[key])) { + output[key] = this.sanitize(output[key]); + } else if (isArray(output[key])) { + output[key] = output[key].map(this.sanitize); + } else if (sensitiveFields.includes(key)) { + output[key] = "[Filtered]"; + } else { + output[key] = this.sanitize(output[key]); + } + } + return output; + } + + return input; + } } export default new Logger();