Socket
Socket
Sign inDemoInstall

@hackylabs/deep-redact

Package Overview
Dependencies
Maintainers
0
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@hackylabs/deep-redact - npm Package Compare versions

Comparing version 1.0.1 to 2.0.0

dist/cjs/types.js

241

dist/cjs/index.js
"use strict";
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DeepRedact = exports.default = void 0;
const normaliseString = (key) => key.toLowerCase().replace(/\W/g, '');
const redactorUtils_1 = __importDefault(require("./utils/redactorUtils"));
class DeepRedact {
/**
* Create a new DeepRedact instance with the provided configuration.
* The configuration will be merged with the default configuration.
* `blacklistedKeys` will be normalised to an array inherited from the default configuration as the default values.
* @param {DeepRedactConfig} config. The configuration for the redaction.
*/
constructor(config) {
var _a, _b;
this.circularReference = new WeakSet();
/**
* A WeakSet to store circular references during redaction. Reset to null after redaction is complete.
* @private
*/
this.circularReference = null;
/**
* The configuration for the redaction.
* @private
*/
this.config = {
blacklistedKeys: [],
stringTests: [],
fuzzyKeyMatch: false,
caseSensitiveKeyMatch: true,
retainStructure: false,
remove: false,
replaceStringByLength: false,
replacement: '[REDACTED]',
types: ['string'],
serialise: true,
unsupportedTransformer: DeepRedact.unsupportedTransformer,
serialise: false,
};
this.removeCircular = (value) => {
var _a, _b;
if (!(value instanceof Object))
/**
* A transformer for unsupported data types. If `serialise` is false, the value will be returned as is,
* otherwise it will transform the value into a format that is supported by JSON.stringify.
*
* Error, RegExp, and Date instances are technically supported by JSON.stringify,
* but they returned as empty objects, therefore they are also transformed here.
* @protected
* @param {unknown} value The value that is not supported by JSON.stringify.
* @returns {unknown} The value in a format that is supported by JSON.stringify.
*/
this.unsupportedTransformer = (value) => {
if (!this.config.serialise)
return value;
if (!((_a = this.circularReference) === null || _a === void 0 ? void 0 : _a.has(value))) {
(_b = this.circularReference) === null || _b === void 0 ? void 0 : _b.add(value);
return value;
if (typeof value === 'bigint') {
return {
__unsupported: {
type: 'bigint',
value: value.toString(),
radix: 10,
},
};
}
return '__circular__';
if (value instanceof Error) {
return {
__unsupported: {
type: 'error',
name: value.name,
message: value.message,
stack: value.stack,
},
};
}
if (value instanceof RegExp) {
return {
__unsupported: {
type: 'regexp',
source: value.source,
flags: value.flags,
},
};
}
if (value instanceof Date)
return value.toISOString();
return value;
};
this.redactString = (value, parentShouldRedact = false) => {
if (!this.config.stringTests.some((test) => test.test(value)) && !parentShouldRedact)
return value;
if (this.config.replaceStringByLength)
return this.config.replacement.repeat(value.length);
return this.config.remove ? undefined : this.config.replacement;
};
this.shouldRedactObjectValue = (key) => {
return this.config.blacklistedKeys.some((redactableKey) => (typeof redactableKey === 'string'
? key === redactableKey
: DeepRedact.complexShouldRedact(key, redactableKey)));
};
this.deepRedact = (value, parentShouldRedact = false) => {
if (value === undefined || value === null)
return value;
let safeValue = this.removeCircular(value);
safeValue = this.config.unsupportedTransformer(safeValue);
if (!(safeValue instanceof Object)) {
// @ts-expect-error - we already know that safeValue is not a function, symbol, undefined, null, or an object
if (!this.config.types.includes(typeof safeValue))
return safeValue;
if (typeof safeValue === 'string')
return this.redactString(safeValue, parentShouldRedact);
if (!parentShouldRedact)
return safeValue;
return this.config.remove
? undefined
: this.config.replacement;
/**
* Calls `unsupportedTransformer` on the provided value and rewrites any circular references.
*
* Circular references will always be removed to avoid infinite recursion.
* When a circular reference is found, the value will be replaced with `[[CIRCULAR_REFERENCE: path.to.original.value]]`.
* @protected
* @param {unknown} value The value to rewrite.
* @param {string | undefined} path The path to the value in the object.
* @returns {unknown} The rewritten value.
*/
this.rewriteUnsupported = (value, path) => {
const safeValue = this.unsupportedTransformer(value);
if (!(safeValue instanceof Object))
return safeValue;
if (this.circularReference === null)
this.circularReference = new WeakSet();
if (Array.isArray(safeValue)) {
return safeValue.map((val, index) => {
var _a, _b;
const newPath = path ? `${path}.[${index}]` : `[${index}]`;
if ((_a = this.circularReference) === null || _a === void 0 ? void 0 : _a.has(val))
return `[[CIRCULAR_REFERENCE: ${newPath}]]`;
if (val instanceof Object) {
(_b = this.circularReference) === null || _b === void 0 ? void 0 : _b.add(val);
return this.rewriteUnsupported(val, newPath);
}
return val;
});
}
if (parentShouldRedact && (!this.config.retainStructure || this.config.remove)) {
return this.config.remove ? undefined : this.config.replacement;
}
if (Array.isArray(safeValue))
return safeValue.map((val) => this.deepRedact(val, parentShouldRedact));
return Object.fromEntries(Object.entries(safeValue).map(([key, val]) => {
const shouldRedact = parentShouldRedact || this.shouldRedactObjectValue(key);
return [key, this.deepRedact(val, shouldRedact)];
var _a, _b;
const newPath = path ? `${path}.${key}` : key;
if ((_a = this.circularReference) === null || _a === void 0 ? void 0 : _a.has(val))
return [key, `[[CIRCULAR_REFERENCE: ${newPath}]]`];
if (val instanceof Object)
(_b = this.circularReference) === null || _b === void 0 ? void 0 : _b.add(val);
return [key, this.rewriteUnsupported(val, path ? `${path}.${key}` : key)];
}));
};
this.redact = (value) => {
this.circularReference = new WeakSet();
const redacted = this.deepRedact(value);
/**
* Depending on the value of `serialise`, return the value as a JSON string or as the provided value.
*
* Also resets the `circularReference` property to null after redaction is complete.
* This is to ensure that the WeakSet doesn't cause memory leaks.
* @private
* @param value
*/
this.maybeSerialise = (value) => {
this.circularReference = null;
return this.config.serialise ? JSON.stringify(redacted) : redacted;
return this.config.serialise ? JSON.stringify(value) : value;
};
this.config = Object.assign(Object.assign(Object.assign({}, this.config), config), { blacklistedKeys: (_b = (_a = config.blacklistedKeys) === null || _a === void 0 ? void 0 : _a.map((key) => {
if (typeof key === 'string')
return key;
return Object.assign({ fuzzyKeyMatch: this.config.fuzzyKeyMatch, caseSensitiveKeyMatch: this.config.caseSensitiveKeyMatch, retainStructure: this.config.retainStructure, remove: this.config.remove }, key);
})) !== null && _b !== void 0 ? _b : [] });
/**
* Redact the provided value. The value will be stripped of any circular references and other unsupported data types, before being redacted according to the configuration and finally serialised if required.
* @param {unknown} value The value to redact.
* @returns {unknown} The redacted value.
*/
this.redact = (value) => {
return this.maybeSerialise(this.redactorUtils.recurse(this.rewriteUnsupported(value)));
};
const { serialise, serialize } = config, rest = __rest(config, ["serialise", "serialize"]);
this.redactorUtils = new redactorUtils_1.default(rest);
if (serialise !== undefined)
this.config.serialise = serialise;
if (serialize !== undefined)
this.config.serialise = serialize;
}

@@ -86,45 +153,1 @@ }

exports.DeepRedact = DeepRedact;
DeepRedact.unsupportedTransformer = (value) => {
if (typeof value === 'bigint') {
return {
__unsupported: {
type: 'bigint',
value: value.toString(),
radix: 10,
},
};
}
if (value instanceof Error) {
return {
__unsupported: {
type: 'error',
name: value.name,
message: value.message,
stack: value.stack,
},
};
}
if (value instanceof RegExp) {
return {
__unsupported: {
type: 'regexp',
source: value.source,
flags: value.flags,
},
};
}
if (value instanceof Date)
return value.toISOString();
return value;
};
DeepRedact.complexShouldRedact = (key, config) => {
if (config.key instanceof RegExp)
return config.key.test(key);
if (config.fuzzyKeyMatch && config.caseSensitiveKeyMatch)
return key.includes(config.key);
if (config.fuzzyKeyMatch && !config.caseSensitiveKeyMatch)
return normaliseString(key).includes(normaliseString(config.key));
if (!config.fuzzyKeyMatch && config.caseSensitiveKeyMatch)
return key === config.key;
return normaliseString(config.key) === normaliseString(key);
};
{
"name": "@hackylabs/deep-redact",
"version": "1.0.1",
"version": "2.0.0",
"description": "A fast, safe and configurable zero-dependency library for redacting strings or deeply redacting arrays and objects.",

@@ -8,2 +8,5 @@ "private": false,

"author": "Benjamin Green (https://bengreen.dev)",
"types": "./dist/types/index.d.ts",
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.mjs",
"keywords": [

@@ -30,3 +33,3 @@ "redact",

"require": "./dist/cjs/index.js",
"types": "./dist/esm/index.d.ts"
"types": "./dist/types/index.d.ts"
}

@@ -44,3 +47,3 @@ },

"build": "npm run lint && npm run test && npm run bench && npm run build:esm && npm run build:cjs && npm run update-readme && npm run update-license",
"build:esm": "tsc --project tsconfig.esm.json",
"build:esm": "tsc --project tsconfig.esm.json && ./scripts/js-to-mjs.sh",
"build:cjs": "tsc --project tsconfig.cjs.json",

@@ -47,0 +50,0 @@ "bench": "npx vitest bench --watch=false",

# Deep Redact
Faster than fast-redact <sup>1</sup> as well as being safer and more configurable than many other redaction libraries,
Faster than Fast Redact <sup>1</sup> as well as being safer and more configurable than many other redaction libraries,
Deep Redact is a zero-dependency tool that redacts sensitive information from strings and objects. It is designed to be

@@ -8,4 +8,4 @@ used in a production environment where sensitive information needs to be redacted from logs, error messages, files,

Circular references and other unsupported are handled gracefully, and the library is designed to be as fast as possible
while still being configurable.
Circular references and other unsupported values are handled gracefully, and the library is designed to be as fast as
possible while still being configurable.

@@ -28,21 +28,29 @@ Supporting both CommonJS and ESM, with named and default exports, Deep Redact is designed to be versatile and easy to

// ./src/example.ts
import { DeepRedact } from '@hackylabs/deep-redact'; // If you're using CommonJS, import with require('deep-redact') instead. Both CommonJS and ESM support named and default imports.
import {DeepRedact} from '@hackylabs/deep-redact'; // If you're using CommonJS, import with require('@hackylabs/deep-redact') instead. Both CommonJS and ESM support named and default imports.
const redaction = new DeepRedact({
replacement: '*',
replaceStringByLength: true,
blacklistedKeys: ['password'],
stringTests: [
/^[\d]{13,16}$/, // payment card number
/^[\d]{3,4}$/ // CVV
],
});
blacklistedKeys: ['sensitive', 'password', /name/i],
serialise: false,
})
const obj = {
password: '<h1><strong>Password</strong></h1>',
cardNumber: '1234567812345678',
cvv: '123',
};
keepThis: 'This is fine',
sensitive: 'This is not fine',
user: {
id: 1,
password: '<h1><strong>Password</strong></h1>',
firstName: 'John',
}
}
redaction.redact(obj) // { password: '**********************************', cardNumber: '****************', cvv: '***' }
redaction.redact(obj)
// {
// keepThis: 'This is fine',
// sensitive: '[REDACTED]',
// user: {
// id: 1,
// password: '[REDACTED]',
// firstName: '[REDACTED]'
// }
// }
```

@@ -56,3 +64,3 @@

| --- | --- | --- | --- | --- | --- |
| blacklistedKeys | Deeply compare names of these keys against the keys in your object. | array | Array<string│BlacklistKeyConfig> | [] | N |
| blacklistedKeys | Deeply compare names of these keys against the keys in your object. | array | Array<string│RegExp│BlacklistKeyConfig> | [] | N |
| stringTests | Array of regular expressions to perform against string values, whether that value is a flat string or nested within an object. | array | RegExp[] | [] | N |

@@ -63,7 +71,7 @@ | fuzzyKeyMatch | Loosely compare key names by checking if the key name of your unredacted object is included anywhere within the name of your blacklisted key. For example, is "pass" (your key) included in "password" (from config). | boolean | | false | N |

| retainStructure | Determines whether or not keep all nested values of a key that is going to be redacted. Circular references are always removed. | boolean | | false | N |
| replacement | When a value is going to be redacted, what would you like to replace it with? | string | | [REDACTED] | N |
| replacement | When a value is going to be redacted, what would you like to replace it with? | string │ function | | [REDACTED] | N |
| replaceStringByLength | When a string value is going to be replaced, optionally replace it by repeating the `replacement` to match the length of the value. For example, if `replaceStringByLength` were set to `true` and `replacement` was set to "x", then redacting "secret" would return "xxxxxx". This is sometimes useful for debugging purposes, although it may be less secure as it could give hints to the original value. | boolean | | false | N |
| types | JS types (values of `typeof` keyword). Only values with a typeof equal to `string`, `number`, `bigint`, `boolean` or `object` may be redacted. The other types are only listed as options to keep TypeScript happy, so you never need to list them. | array | Array<'string'│'number'│'bigint'│'boolean'│'symbol'│'undefined'│'object'│'function'> | ['string'] | N |
| serialise | Determines whether or not to serialise the object after redacting. Typical use cases for this are when you want to send it over the network or save to a file, both of which are common use cases for redacting sensitive information. | boolean | | true | N |
| unsupportedTransformer | When an unsafe value is encountered or a value that cannot be serialised. By default, this function will transform an unsupported value `Unsupported` object. BigInt values are converted a string. Dates are returned using their own `toISOString` method. Regular expressions are returned as objects with their `source` and `flags` values. Errors are converted objects. This is useful when you have a custom class that you would like to redact. For safety reasons, you should always transform a BigInt to avoid JSON.stringify throwing an error. | (value: unknown) => unknown | | DeepRedact.transformUnsupported | N |
| types | JS types (values of `typeof` keyword). Only values with a typeof equal to `string`, `number`, `bigint`, `boolean`, `symbol`, `object`, or `function` will be redacted. Undefined values will never be redacted, although the type `undefined` is included in this list to keep TypeScript happy. | array | Array<'string'│'number'│'bigint'│'boolean'│'symbol'│'undefined'│'object'│'function'> | ['string'] | N |
| serialise | Determines whether or not to serialise the object after redacting. Typical use cases for this are when you want to send it over the network or save to a file, both of which are common use cases for redacting sensitive information. | boolean | | false | N |
| serialize | Alias of `serialise` for International-English users. | boolean | | false | N |

@@ -81,9 +89,11 @@ ### BlacklistKeyConfig

### Benchmark
Comparisons are made against JSON.stringify and fast-redact as well as different configurations of deep-redact, using
[this test object](./test/setup/dummyUser.ts). The benchmark is run on a 2021 iMac with an M1 chip with 16GB memory
running Sonoma 14.5.
Comparisons are made against JSON.stringify and Fast Redact as well as different configurations of Deep Redact, using
[this test object](./test/setup/dummyUser.ts). Fast Redact was configured to redact the same keys on the same object as
Deep Redact without using wildcards.
The benchmark is run on a 2021 iMac with an M1 chip with 16GB memory running Sonoma 14.5.
JSON.stringify is included as a benchmark because it is the fastest way to deeply iterate over an object although it
doesn't redact any sensitive information. Fast-redact is included as a benchmark because it's the next fastest redaction
library available. Neither JSON.stringify nor fast-redact offer the same level of configurability as deep-redact.
library available. Neither JSON.stringify nor Fast Redact offer the same level of configurability as deep-redact.

@@ -94,20 +104,15 @@ ![Benchmark](./benchmark.png)

| --- | --- | --- | --- | --- |
| JSON.stringify, tiny object | 3878848.93 | 0.0002578084 | 0 | 1939425 |
| DeepRedact, default config, tiny object | 1530332.28 | 0.0006534529 | 0.00001 | 765167 |
| JSON.stringify, large object | 295526.11 | 0.0033837958 | 0.00001 | 147764 |
| fast redact, tiny object | 228053.93 | 0.0043849277 | 0.00002 | 114027 |
| DeepRedact, default config, large object | 92714.28 | 0.0107858256 | 0.00006 | 46358 |
| DeepRedact, remove item, single object | 92349.45 | 0.0108284349 | 0.00005 | 46175 |
| DeepRedact, fuzzy matching, single object | 89414.82 | 0.0111838282 | 0.00007 | 44708 |
| DeepRedact, fuzzy and case insensitive matching, single object | 87852.36 | 0.0113827334 | 0.00006 | 43927 |
| DeepRedact, case insensitive matching, single object | 86797.23 | 0.0115211045 | 0.00006 | 43399 |
| DeepRedact, replace string by length, single object | 84150.95 | 0.011883407 | 0.00006 | 42076 |
| DeepRedact, config per key, single object | 71236.85 | 0.0140376786 | 0.00009 | 35619 |
| DeepRedact, retain structure, single object | 69738.86 | 0.0143392076 | 0.00007 | 34870 |
| fast redact, large object | 19480.95 | 0.0513321865 | 0.00038 | 9741 |
| JSON.stringify, 1000 tiny objects | 16003.16 | 0.0624876526 | 0.00017 | 8002 |
| DeepRedact, default config, 1000 tiny objects | 15827.22 | 0.0631822932 | 0.00043 | 7914 |
| DeepRedact, default config, 1000 large objects | 13705.52 | 0.072963285 | 0.00054 | 6853 |
| fast redact, 1000 tiny objects | 7430.88 | 0.1345735164 | 0.00067 | 3716 |
| JSON.stringify, 1000 large objects | 423.52 | 2.3611880519 | 0.00982 | 212 |
| fast redact, 1000 large objects | 77.32 | 12.9327971026 | 0.25939 | 39 |
| JSON.stringify, large object | 295500.62 | 0.0033840876 | 0.00002 | 147751 |
| DeepRedact, remove item, single object | 36272.4 | 0.0275691709 | 0.00016 | 18137 |
| DeepRedact, custom replacer function, single object | 30314.59 | 0.0329874115 | 0.00028 | 15158 |
| DeepRedact, default config, large object | 30028.19 | 0.0333020395 | 0.0002 | 15015 |
| DeepRedact, replace string by length, single object | 28756.9 | 0.0347742688 | 0.00028 | 14379 |
| DeepRedact, retain structure, single object | 24803.01 | 0.0403176903 | 0.00032 | 12402 |
| DeepRedact, fuzzy matching, single object | 22243.3 | 0.0449573621 | 0.00038 | 11122 |
| DeepRedact, config per key, single object | 21603.85 | 0.0462880355 | 0.0013 | 10802 |
| fast redact, large object | 9529.2 | 0.1049406557 | 0.00064 | 4765 |
| DeepRedact, case insensitive matching, single object | 6503.72 | 0.1537581959 | 0.00105 | 3252 |
| DeepRedact, default config, 1000 large objects | 5915.05 | 0.1690602382 | 0.00296 | 2958 |
| DeepRedact, fuzzy and case insensitive matching, single object | 5591.96 | 0.1788283015 | 0.00184 | 2796 |
| JSON.stringify, 1000 large objects | 394.41 | 2.5354059248 | 0.01001 | 198 |
| fast redact, 1000 large objects | 172.23 | 5.8060829174 | 0.06886 | 87 |
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc