Add sanitization to log messages to reduce chance of tokens ending up in server logs
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { IncomingMessage } from "http";
|
import { IncomingMessage } from "http";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import { isEmpty } from "lodash";
|
import { isEmpty, isArray, isObject, isString } from "lodash";
|
||||||
import winston from "winston";
|
import winston from "winston";
|
||||||
import env from "@server/env";
|
import env from "@server/env";
|
||||||
import Metrics from "@server/logging/Metrics";
|
import Metrics from "@server/logging/Metrics";
|
||||||
@@ -27,7 +27,7 @@ type Extra = Record<string, any>;
|
|||||||
class Logger {
|
class Logger {
|
||||||
output: winston.Logger;
|
output: winston.Logger;
|
||||||
|
|
||||||
constructor() {
|
public constructor() {
|
||||||
this.output = winston.createLogger({
|
this.output = winston.createLogger({
|
||||||
level: env.LOG_LEVEL,
|
level: env.LOG_LEVEL,
|
||||||
});
|
});
|
||||||
@@ -54,8 +54,8 @@ class Logger {
|
|||||||
* @param category A log message category that will be prepended
|
* @param category A log message category that will be prepended
|
||||||
* @param extra Arbitrary data to be logged that will appear in prod logs
|
* @param extra Arbitrary data to be logged that will appear in prod logs
|
||||||
*/
|
*/
|
||||||
info(label: LogCategory, message: string, extra?: Extra) {
|
public info(label: LogCategory, message: string, extra?: Extra) {
|
||||||
this.output.info(message, { ...extra, label });
|
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 category A log message category that will be prepended
|
||||||
* @param extra Arbitrary data to be logged that will appear in prod logs
|
* @param extra Arbitrary data to be logged that will appear in prod logs
|
||||||
*/
|
*/
|
||||||
debug(label: LogCategory, message: string, extra?: Extra) {
|
public debug(label: LogCategory, message: string, extra?: Extra) {
|
||||||
this.output.debug(message, { ...extra, label });
|
this.output.debug(message, { ...this.sanitize(extra), label });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -74,7 +74,7 @@ class Logger {
|
|||||||
* @param message A warning message
|
* @param message A warning message
|
||||||
* @param extra Arbitrary data to be logged that will appear in prod logs
|
* @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");
|
Metrics.increment("logger.warning");
|
||||||
|
|
||||||
if (env.SENTRY_DSN) {
|
if (env.SENTRY_DSN) {
|
||||||
@@ -90,7 +90,7 @@ class Logger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isProduction) {
|
if (isProduction) {
|
||||||
this.output.warn(message, extra);
|
this.output.warn(message, this.sanitize(extra));
|
||||||
} else if (extra) {
|
} else if (extra) {
|
||||||
console.warn(message, extra);
|
console.warn(message, extra);
|
||||||
} else {
|
} else {
|
||||||
@@ -106,7 +106,7 @@ class Logger {
|
|||||||
* @param extra Arbitrary data to be logged that will appear in prod logs
|
* @param extra Arbitrary data to be logged that will appear in prod logs
|
||||||
* @param request An optional request object to attach to the error
|
* @param request An optional request object to attach to the error
|
||||||
*/
|
*/
|
||||||
error(
|
public error(
|
||||||
message: string,
|
message: string,
|
||||||
error: Error,
|
error: Error,
|
||||||
extra?: Extra,
|
extra?: Extra,
|
||||||
@@ -122,7 +122,7 @@ class Logger {
|
|||||||
scope.setLevel("error");
|
scope.setLevel("error");
|
||||||
|
|
||||||
for (const key in extra) {
|
for (const key in extra) {
|
||||||
scope.setExtra(key, extra[key]);
|
scope.setExtra(key, this.sanitize(extra[key]));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request) {
|
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<T>(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();
|
export default new Logger();
|
||||||
|
|||||||
Reference in New Issue
Block a user