obscure authorization header value when logging
authorJustin Wind <justin.wind+git@gmail.com>
Fri, 29 Oct 2021 20:41:40 +0000 (13:41 -0700)
committerJustin Wind <justin.wind+git@gmail.com>
Fri, 29 Oct 2021 20:41:40 +0000 (13:41 -0700)
CHANGELOG.md
lib/common.js
test/lib/common.js

index 33468ed846c43397ec19e64ab2d641cca5be759c..8e0e95dade7a64f727ce0469bee0f9f7cd541075 100644 (file)
@@ -4,6 +4,10 @@ Releases and notable changes to this project are documented here.
 
 ## [Unreleased]
 
+### Added
+
+- obscure sensitive request header data when logging
+
 ## [v1.2.1] - 2021-10-22
 
 ### Added
index ff68e9d7ac7b5ea5258ce1aab70cb99f991d037f..21070b8782918f267bf32e3adf96537df08e615a 100644 (file)
@@ -158,19 +158,51 @@ const pick = (obj, props) => {
 
 /**
  * Return a subset of a request object, suitable for logging.
+ * Obscures sensitive header values.
  * @param {http.ClientRequest} req 
  */
 const requestLogData = (req) => {
-  return pick(req, [
+  const data = pick(req, [
     'method',
     'url',
     'httpVersion',
     'headers',
     'trailers',
   ]);
+  scrubHeaderObject(data);
+  return data;
 };
 
 
+/**
+ * Remove sensitive header data.
+ * @param {Object} data
+ * @param {Object} data.headers
+ */
+const scrubHeaderObject = (data) => {
+  if (data && data.headers && 'authorization' in data.headers) {
+    data.headers = Object.assign({}, data.headers, {
+      authorization: obscureAuthorizationHeader(data.headers['authorization']),
+    });
+  }
+}
+
+
+/**
+ * Hide sensitive part of an Authorization header.
+ * @param {String} authHeader
+ * @returns {String}
+ */
+const obscureAuthorizationHeader = (authHeader) => {
+  if (!authHeader) {
+    return authHeader;
+  }
+  const space = authHeader.indexOf(' ');
+  // This blurs entire string if no space found, because -1.
+  return authHeader.slice(0, space + 1) + '*'.repeat(authHeader.length - (space + 1));
+}
+
+
 /**
  * Return a subset of a response object, suitable for logging.
  * @param {http.ServerResponse} res 
@@ -272,10 +304,12 @@ module.exports = {
   mergeEnum,
   nop,
   nullLogger,
+  obscureAuthorizationHeader,
   pick,
   requestId,
   requestLogData,
   responseLogData,
+  scrubHeaderObject,
   setOptions,
   splitFirst,
   unfoldHeaderLines,
index a04bf614eeadac136f2df8ff44cf1519846d8221..bacd6be3d54d2aaa7e6b019c98e0e664050392dc 100644 (file)
@@ -141,6 +141,46 @@ describe('common', function () {
     });
   }); // requestLogData
 
+  describe('obscureAuthorizationHeader', function () {
+    it('obscures basic data', function () {
+      const authHeader = 'Basic Zm9vOmJhcg==';
+      const expected = 'Basic ************';
+      const result = common.obscureAuthorizationHeader(authHeader);
+      assert.strictEqual(result, expected);
+    });
+    it('obscures all of other types', function () {
+      const authHeader = 'someWeirdAuth';
+      const expected = '*************';
+      const result = common.obscureAuthorizationHeader(authHeader);
+      assert.strictEqual(result, expected);
+    });
+    it('does nothing when empty', function () {
+      const authHeader = undefined;
+      const expected = undefined;
+      const result = common.obscureAuthorizationHeader(authHeader);
+      assert.strictEqual(result, expected);
+    });
+  }); // obscureAuthorizationHeader
+
+  describe('scrubHeaderObject', function () {
+    it('', function () {
+      const data = {
+        headers: {
+          'foo': 'bar',
+          'authorization': 'Basic Zm9vOmJhcg==',
+        },
+      };
+      const expected = {
+        headers: {
+          'foo': 'bar',
+          'authorization': 'Basic ************',
+        },
+      };
+      common.scrubHeaderObject(data);
+      assert.deepStrictEqual(data, expected);
+    });
+  }); // scrubHeaderObject
+
   describe('responseLogData', function () {
     it('gives data', function () {
       const res = {