Repository: pinojs/pino
Pull request #732 (2019-10-23 04:49:47)

fix(redaction): fix corner cases with paths
In particular, this supports single-item paths where the first item is bracketed (e.g. `{ redact: ["['some-key']"] }`) and paths where one of the segments is the empty string [in brackets] (e.g., `{ redact: ["a[''].x"] }`).

Conceptually, the change is quite simple: it uses the namespace rather, than the string, even for single-segment paths, since the namespace has the brackets + quotes removed. It also changes the namespace `.replace` regex to allow 0 character (i.e., `.*` instead of `.+`) between the quotes.

ethanresnick: @mcollina Next steps to getting this merged/published? I'd love to delete my fork :)
mcollina: @davidmarkclements can you take a look?

Patch:
diff --git a/lib/redaction.js b/lib/redaction.js
index 771faed..c1dd721 100644
--- a/lib/redaction.js
+++ b/lib/redaction.js
@@ -19,29 +19,31 @@ function redaction (opts, serialize) {
     rx.lastIndex = 0
     const first = rx.exec(str)
     const next = rx.exec(str)
-    // top level key:
-    if (next === null) {
-      if (str === '*') {
-        str = wildcardFirstSym
-      }
-      o[str] = null
-      return o
-    }
-    const { index } = next
-    const nextPath = `${str.substr(index, str.length - 1)}`
-    var ns = first[1]
-      ? first[1].replace(/^(?:"|'|`)(.+)(?:"|'|`)$/, '$1')
+
+    // ns is the top-level path segment, brackets + quoting removed.
+    let ns = first[1] !== undefined
+      ? first[1].replace(/^(?:"|'|`)(.*)(?:"|'|`)$/, '$1')
       : first[0]
 
     if (ns === '*') {
       ns = wildcardFirstSym
     }
 
+    // top level key:
+    if (next === null) {
+      o[ns] = null
+      return o
+    }
+
+    // path with at least two segments:
     // if ns is already redacted at the top level, ignore lower level redactions
     if (o[ns] === null) {
       return o
     }
 
+    const { index } = next
+    const nextPath = `${str.substr(index, str.length - 1)}`
+
     o[ns] = o[ns] || []
 
     // shape is a mix of paths beginning with literal values and wildcard


Test patch:
diff --git a/test/redact.test.js b/test/redact.test.js
index 30cbfda..5a812cc 100644
--- a/test/redact.test.js
+++ b/test/redact.test.js
@@ -626,6 +626,17 @@ test('supports bracket notation with further nesting', async ({ is }) => {
   is(o.a['b.b'].c, '[Redacted]')
 })
 
+test('supports bracket notation with empty string as path segment', async ({ is }) => {
+  const stream = sink()
+  const instance = pino({ redact: ['a[""].c'] }, stream)
+  const obj = {
+    a: { '': { c: 'd' } }
+  }
+  instance.info(obj)
+  const o = await once(stream, 'data')
+  is(o.a[''].c, '[Redacted]')
+})
+
 test('supports leading bracket notation (single quote)', async ({ is }) => {
   const stream = sink()
   const instance = pino({ redact: ['[\'a.a\'].b'] }, stream)
@@ -658,3 +669,25 @@ test('supports leading bracket notation (backtick quote)', async ({ is }) => {
   const o = await once(stream, 'data')
   is(o['a.a'].b, '[Redacted]')
 })
+
+test('supports leading bracket notation (single-segment path)', async ({ is }) => {
+  const stream = sink()
+  const instance = pino({ redact: ['[`a.a`]'] }, stream)
+  const obj = {
+    'a.a': { b: 'c' }
+  }
+  instance.info(obj)
+  const o = await once(stream, 'data')
+  is(o['a.a'], '[Redacted]')
+})
+
+test('supports leading bracket notation (single-segment path, wilcard)', async ({ is }) => {
+  const stream = sink()
+  const instance = pino({ redact: ['[*]'] }, stream)
+  const obj = {
+    'a.a': { b: 'c' }
+  }
+  instance.info(obj)
+  const o = await once(stream, 'data')
+  is(o['a.a'], '[Redacted]')
+})