I am interested in integrating a library that optionally redacts-secrets from log messages.
Perhaps something like - https://www.npmjs.com/package/redact-secrets.
Wondering if there is an appetite for a PR of this nature?
One way to accomplish this today is to do something like this. However, this is harder to do
when the output format is not an object.
const redact = require('redact-secrets')('[REDACTED]');
const winston = require('winston');
winston.configure({
transports: [
new (winston.transports.File)({
filename: 'somefile.log',
json: false,
prettyPrint: (json) => JSON.stringify(redact.map(json), null, 2)
})
]
});
This proposal would apply this internally before the log message is serialized, make it's application more reliable. Thoughts?
An alternative to this is using a rewriter.
// Redact all sensitive information contained in the meta
logger.rewriters.push((level, msg, meta) => redact.map(meta));
In [email protected] you can implement this using a custom format. Please consider upgrading.
const redact = require('redact-secrets')('[REDACTED]');
const { createLogger, format, transports } = require('winston');
const logger = createLogger({
format: format.combine(
// Redact any properties
format(info => redact.map(info))(),
// Then make it JSON
format.json()
)
transports: [new transports.Console()]
});
This doesn't work for me for some reason, there is no output when adding the redact formatter
This doesn't work for me for some reason, there is no output when adding the redact formatter
It seems the traverse(info).map result is missing the Symbol(level) and Symbol(splat) keys from the info object.
re-adding them to the traverse result seems to make it work. I'm not sure on how to clean up the typing yet, but the following works
const sensitiveKeys = ['client_secret'];
const redact = format(info => {
const result = traverse(info).map(function redactor() {
if (this.key && sensitiveKeys.includes(this.key)) {
this.update('[REDACTED]');
}
});
const levelSym = Symbol.for('level');
const splatSym = Symbol.for('splat');
result[levelSym] = info[(levelSym as unknown) as string];
result[splatSym] = info[(splatSym as unknown) as string];
return result;
});
UPDATE:
Found this issue in the traverse package
https://github.com/substack/js-traverse/issues/65
I ended up doing this, for those of you interested (winston: v3.3.3):
import traverse from "traverse";
import { klona } from "klona/full";
const sensitiveKeys = [
/cookie/i,
/passw(or)?d/i,
/^pw$/,
/^pass$/i,
/secret/i,
/token/i,
/api[-._]?key/i,
];
function isSensitiveKey(keyStr) {
if (keyStr) {
return sensitiveKeys.some(regex => regex.test(keyStr));
}
}
function redactObject(obj) {
traverse(obj).forEach(function redactor() {
if (isSensitiveKey(this.key)) {
this.update("[REDACTED]");
}
});
}
function redact(obj) {
const copy = klona(obj); // Making a deep copy to prevent side effects
redactObject(copy);
const splat = copy[Symbol.for("splat")];
redactObject(splat); // Specifically redact splat Symbol
return copy;
}
const logger = winston.createLogger({
format: winston.format.combine(
format(info => redact(info))(), // Prevent logging sensitive data
...
Most helpful comment
In
[email protected]you can implement this using a custom format. Please consider upgrading.