loofah
Version:
Scrubs sensitive information from data
168 lines (145 loc) • 8.02 kB
text/coffeescript
_ = require 'underscore'
assert = require 'assert'
os = require 'os'
Scrubbers = require ("#{__dirname}/../lib/loofah")
user_scrub = (keywords) ->
return (object) ->
_.omit object, 'omit_this_key'
describe 'Loofah', ->
describe 'object_keys', ->
_.each [
[{a: 'non sensitive'}, {a: 'non sensitive'}]
[
{b: {secret:'shhh', c: 'non sensitive'}, big_secret: 'SHHH'}
{b: {secret:'[REDACTED]', c: 'non sensitive'}, big_secret: 'SHHH'}
]
[{password: 'pwd'}, {password: '[REDACTED]'}]
[
{b: [{secret:['shhh'], c: ['non sensitive']}], big_secret: 'SHHH'}
{b: [{secret:['[REDACTED]'], c: ['non sensitive']}], big_secret: 'SHHH'}
]
[
{secret:[{b: [{a:[['shhh']]}]}]}
{secret:[{b: [{a:[['[REDACTED]']]}]}]}
]
], ([input, output]) ->
it 'scrubs keys with banned names', ->
assert.deepEqual (Scrubbers.object_keys(['secret', 'password']) input), output
_.each ['string', 2, undefined, null], (value) ->
it 'if not given an object, returns what it was given', ->
assert.equal (Scrubbers.object_keys(['secret', 'password']) value), value
describe 'substrings', ->
_.each [
[{a: 'a string of text contains thisIsOurApiKey'}, {a: 'a string of text contains [REDACTED]'}]
[{b: 'a string of text contains thisisourapikey'}, {b: 'a string of text contains thisisourapikey'}]
[{c: 'thisIsOurApiKeythisIsOurApiKeythisIsOurApiKey'}, {c: '[REDACTED][REDACTED][REDACTED]'}]
['thisIsOurApiKeythisIsOurApiKeythisIsOurApiKey', '[REDACTED][REDACTED][REDACTED]']
], ([input, output]) ->
it 'scrubs banned values in strings and objects', ->
assert.deepEqual (Scrubbers.substrings(['thisIsOurApiKey']) input), output
_.each [2, undefined, null], (value) ->
it 'if not given an object or string, returns what it was given', ->
assert.equal (Scrubbers.substrings(['some', 'kwargs']) value), value
describe 'url_query_params', ->
_.each [
[
{url: 'refresh_token=1234567890asdfghjkl&CliENT_Id=123456789.apps.googleusercontent.com&client_secret=123456789asdfghjkl&grant_type=refresh_token'}
{url: 'refresh_token=[REDACTED]&CliENT_Id=[REDACTED].apps.googleusercontent.com&client_secret=[REDACTED]&grant_type=refresh_token'}
]
[
'refresh_token=1234567890asdfghjkl&CliENT_Id=123456789.apps.googleusercontent.com&client_secret=123456789asdfghjkl&grant_type=refresh_token'
'refresh_token=[REDACTED]&CliENT_Id=[REDACTED].apps.googleusercontent.com&client_secret=[REDACTED]&grant_type=refresh_token'
]
], ([input, output]) ->
it 'replaces sensitive url encoded info in strings and objects with [REDACTED]', ->
assert.deepEqual (Scrubbers.url_query_params([/client_*/i, 'refresh_token']) input), output
_.each ['this username NAME is in a string', 2, undefined, null], (value) ->
it 'if not given a url, returns what it was given', ->
assert.deepEqual (Scrubbers.url_query_params(['username']) value), value
describe 'key_value_pairs', ->
_.each [
['Error: something went wrong', 'Error: something went wrong']
['Error: Username 12345@example.com was taken', 'Error: Username [REDACTED] was taken']
['Error: thisUsernames 12345@example.com was taken', 'Error: thisUsernames 12345@example.com was taken']
['username 12345@example.com was taken', 'username [REDACTED] was taken']
['Error: Username 12345@example.com', 'Error: Username [REDACTED]']
['Error: Username = 12345@example.com', 'Error: Username = [REDACTED]']
['Error: Username', 'Error: Username']
[{a:'Error: Username 12345@example.com'}, {a:'Error: Username [REDACTED]'}]
], ([input, output]) ->
it 'replaces sensitive data in plain text with [REDACTED]', ->
assert.deepEqual (Scrubbers.key_value_pairs(['username']) input), output
_.each [2, undefined, null], (value) ->
it 'if not given an object or string, returns what it was given', ->
assert.equal (Scrubbers.key_value_pairs(['username']) value), value
it 'allows you to specify delimiters', ->
assert.equal (Scrubbers.key_value_pairs(['username'], ['_']) 'username_NAME'), 'username_[REDACTED]'
describe 'error objects', ->
class CustomError extends Error
constructor: (@message, @custom = 'nada') ->
class StackError extends Error
constructor: (@message) -> Error.captureStackTrace @, arguments.callee
_.each [Error, CustomError, StackError], (ErrorClass) ->
_.each ['message', 'new_field'], (field) ->
it "correctly scrubs the #{field} in instances of #{ErrorClass.name}", ->
err = new ErrorClass 'test error'
orig_val = 'email 123454@example.com failed'
err[field] = orig_val
output = (Scrubbers.key_value_pairs(['email']) err)
assert.equal output[field], 'email [REDACTED] failed'
assert.equal err[field], orig_val
assert.notEqual output, err # Expect a copy, not the original object itself
assert.deepEqual _.omit(output, field), _.omit(err, field)
assert.equal output.stack, Scrubbers.key_value_pairs(['email']) output.stack
assert output instanceof Error
assert output instanceof ErrorClass
_.each [
input: {a: new Error('email 12345@example.com failed')}
expected: {a: new Error('email [REDACTED] failed')}
,
input: ['email xyz', new Error('email 12345@example.com failed')]
expected: ['email [REDACTED]', new Error('email [REDACTED] failed')]
], ({input, expected}) ->
it 'correctly deals with error objects embedded in other objects', ->
assert.deepEqual (Scrubbers.key_value_pairs(['email']) input), expected
describe 'composition and extension', ->
_.each [
[{user:'name'}, {user:'[REDACTED]'}]
[{id:'number'}, {id:'number'}]
[{a:'user'}, {a:'user'}]
[{b:'id 1234'}, {b:'id [REDACTED]'}]
[{c:'someurl?id=12345&user=name'}, {c:'someurl?id=[REDACTED]&user=name'}]
], ([input, output]) ->
it 'allows different illegal words for different functions', ->
scrub = _.compose(Scrubbers.key_value_pairs(['id']), Scrubbers.object_keys(['user']), Scrubbers.url_query_params(['id']),)
assert.deepEqual scrub(input), output
_.each [
[Scrubbers.object_keys(), {password: 'pwd', a: 'password'}, {password: '[REDACTED]', a: 'password'}]
[Scrubbers.key_value_pairs(), 'user NAME is taken', 'user [REDACTED] is taken']
[Scrubbers.url_query_params(), 'www.example.com/?client_id=abc&client_secret=123'
,'www.example.com/?client_id=[REDACTED]&client_secret=[REDACTED]']
], ([func, input, output]) ->
it 'has default args when none are given', ->
assert.deepEqual func(input), output
_.each [
[[{password: 'pwd'}, {secret: 'sth'}], [{password: '[REDACTED]'}, {secret: '[REDACTED]'}]]
[
{url: 'refresh_token=1234512345a&client_id=someid&client_secret=somethingelse'}
{url: 'refresh_token=[REDACTED]&client_id=[REDACTED]&client_secret=[REDACTED]'}
]
[{string: 'username = 12345@example.com'}, {string: 'username = [REDACTED]'}]
], ([input, output]) ->
it 'allows default composition', ->
assert.deepEqual (Scrubbers.default() input), output
_.each [
[{password: 'pwd'}, {password: '[REDACTED]'}]
[
{url: 'refresh_token=1234512345a&client_id=someid&client_secret=somethingelse'}
{url: 'refresh_token=[REDACTED]&client_id=[REDACTED]&client_secret=[REDACTED]'}
]
[{string: 'username = 12345@example.com'}, {string: 'username = [REDACTED]'}]
[{omit_this_key: 'val'}, {}]
], ([input, output]) ->
it 'allows user defined functions to be composed with default ones', ->
scrub = _.compose(Scrubbers.default(), user_scrub(['some', 'keywords']))
assert.deepEqual (scrub input), output