Winston: redacting secrets

Created on 15 Sep 2017  路  5Comments  路  Source: winstonjs/winston

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?

use a custom format

Most helpful comment

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()]
}); 

All 5 comments

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
    ...
Was this page helpful?
0 / 5 - 0 ratings