Skip to content

Commit

Permalink
improvements:
Browse files Browse the repository at this point in the history
* better password regex
* exclusion of matchers
* added tests
* updated README.md
  • Loading branch information
mvelten committed Oct 26, 2023
1 parent 14c94c2 commit 474b111
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 16 deletions.
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ Here's a quick peek at how `data-guardian` can be integrated into your JavaScrip
const { maskData, maskString, maskArguments } = require('data-guardian');

// Masking a string
console.log(maskString('SensitiveData123!')); // Output: "Se************123!"
console.log(maskString('SensitiveData123!'));
// Output: "Se************123!"

console.log(maskString('connection to postgres://dbuser:SuperSecretPassword!@localhost:5432/mydb established'));
// output: "connection to po****es://db**er:Su**************************st:5432/mydb established"

// Masking an arbitary string with sensitive data
console.log(maskString('a dude once exposed his super secret A1vbcvc.De#3435?r password to the world but luckily we could help'));
Expand Down Expand Up @@ -166,6 +170,15 @@ console.log(maskData(data, customMaskingConfig));
// Output: { id: 1, SensitiveInfo: 'Very##########Data' }
```

### Exclusion of certain matchers:
`data-guardian` allows for exclusion of certain matchers. This is useful when you want to mask all sensitive data except for a few. For example, you may want to mask all sensitive data except for the password in a URI.

```javascript
const exampleString = 'I do not want to mask my credit card number 1234-5678-9012-3456 but my password SuperSecretPassword should not be visible';
console.log(maskString(exampleString, { excludeMatchers: ['creditCard'] }));
// Output: "I do not want to mask my credit card number 1234-5678-9012-3456 but my password Su***************rd should not be visible"
```

### Explicit exclusion for masking:
`data-guardian` allows for explicit bypassing masking of potential sensitive content in strings by wrapping the content with '##'

Expand Down Expand Up @@ -194,13 +207,16 @@ An alias for the keys of the sensitiveContentRegExp object.

```typescript
declare const sensitiveContentRegExp: {
readonly uuid: RegExp;
readonly creditCard: RegExp;
readonly ssn: RegExp;
readonly url: RegExp;
readonly ipv4: RegExp;
readonly email: RegExp;
readonly passwordSubstring: RegExp;
readonly password: RegExp;
readonly uuid: RegExp;
readonly passwordInUri: RegExp;
readonly passwordMention: RegExp;
};
```

Expand All @@ -224,6 +240,7 @@ An interface representing the available options for masking.
types (SensitiveContentKey[]): The types of sensitive content to check for.
customSensitiveContentRegExp (object): An object similar to customPatterns, but used only in certain masking contexts.
fixedMaskLength (boolean): When enabled, masks the sensitive content with a fixed number of characters. Default is 'false'.
excludeMatchers (SensitiveContentKey[]): The types of sensitive content to exclude from masking.

### `maskString(value: string, options?: Partial<IMaskOptions>): string`

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "data-guardian",
"version": "1.1.1",
"version": "1.1.2",
"description": "Tiny, zero-dependencies, package which tries to mask sensitive data in arbitrary collections, errors, objects and strings.",
"main": ".build/src/index.js",
"files": [
Expand Down
10 changes: 5 additions & 5 deletions src/lib/dataGuard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,16 @@ const defaultSensitiveKeyFragments: Set<string> = new Set([
]);

const sensitiveContentRegExp = {
uuid: /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\b/gi,
creditCard: /\b(?:\d[ -]*?){13,16}\b/g,
ssn: /\b[0-9]{3}-[0-9]{2}-[0-9]{4}\b/g,
url: /\b(?:https?|ftp):\/\/[a-z0-9-+&@#/%?=~_|!:,.;]*[a-z0-9-+&@#/%=~_|]\b/gi,
ipv4: /\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b/g,
email: /(?<=^|[\s'"-#+.><])[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
password: /\b(?=\S*\d)(?=\S*[A-Za-z])[\w!@#$%^&*()_+=\-,.]{6,}\b/gm,
passwordSubstring: /\b(?!password\b)\w*password\w*\b/gi,
password: /\b(?!\w*\/)(?=\S*\d)(?=\S*[A-Za-z])[\w!@#$%^&*()_+=\-,.]{6,32}\b/gm,
passwordInUri: /(?<=:\/\/[^:]+:)[^@]+?(?=@)/,
passwordMention: /(?<=.*(password|passwd|pwd)(?:\s*:\s*|\s+))\S+/gi,
uuid: /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\b/gi
passwordMention: /(?<=.*(password|passwd|pwd)(?:\s*:\s*|\s+))\S+/gi
} as const;

function maskSensitiveValue(value: string, options: Partial<IMaskDataOptions>): string {
Expand Down Expand Up @@ -198,7 +199,7 @@ export function maskString(
if (!types) types = Object.keys(sensitiveContentRegExp) as SensitiveContentKey[];
if (!customSensitiveContentRegExp) customSensitiveContentRegExp = {};
if (options?.excludeMatchers) {
types = types.filter(t => !options.excludeMatchers.includes(t))
types = types.filter(t => !options.excludeMatchers.includes(t));
}

const applicablePatterns = types.reduce(
Expand Down Expand Up @@ -316,7 +317,6 @@ function performMasking<T>(key: string, value: unknown, options: Partial<IMaskDa
) as T;
}


/**
* Masks data based on the type and the provided masking options.
* This function is recursively called for nested objects.
Expand Down
2 changes: 0 additions & 2 deletions src/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ export function hasCircularReference<T>(item: T): boolean {
return detect(item);
}



export function deepClone<T>(obj: T, visited = new WeakMap()): T {
if (obj === null || typeof obj !== 'object') {
return obj;
Expand Down
25 changes: 19 additions & 6 deletions tests/masking.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { maskArguments, maskData, maskString, SensitiveContentKey } from '../src/';
import { maskArguments, maskData, maskString } from '../src/';

describe('Test all possible masking', () => {
it('should be able to deal with nullish values', () => {
Expand Down Expand Up @@ -150,9 +150,9 @@ describe('Test all possible masking', () => {
});

it('should not mask an url', () => {
expect(maskString('my designated url is https://www.acme.com and you will find out', {excludeMatchers: ['url']})).toBe(
'my designated url is https://www.acme.com and you will find out'
);
expect(
maskString('my designated url is https://www.acme.com and you will find out', { excludeMatchers: ['url'] })
).toBe('my designated url is https://www.acme.com and you will find out');
});

it('should mask a social security number', () => {
Expand Down Expand Up @@ -373,7 +373,8 @@ describe('Test all possible masking', () => {

it('should mask any explicit password mentions', () => {
expect(maskString('here is my password: test01!')).toBe('here is my password: te***1!');
expect(maskString('here is my SecretPassword: test')).toBe('here is my SecretPassword: ****');
expect(maskString('here is my secret password: test')).toBe('here is my secret password: ****');
expect(maskString('here is my secret password: password')).toBe('here is my secret password: pa****rd');
expect(maskString('here is my passwd test')).toBe('here is my passwd ****');
expect(maskString('here is my pwd test01!')).toBe('here is my pwd te***1!');
expect(maskString('here is my pwd test01!')).toBe('here is my pwd te***1!');
Expand All @@ -399,7 +400,9 @@ describe('Test all possible masking', () => {
expect(maskString('my shiny password: test01!', { fixedMaskLength: true })).toBe(
'my shiny password: ****************'
);
expect(maskData({ username: 'johndoe', password: 'testPass' }, { maskingChar: 'X', fixedMaskLength: true })).toEqual({
expect(
maskData({ username: 'johndoe', password: 'testPass' }, { maskingChar: 'X', fixedMaskLength: true })
).toEqual({
username: 'johndoe',
password: 'XXXXXXXXXXXXXXXX'
});
Expand All @@ -418,4 +421,14 @@ describe('Test all possible masking', () => {
});
});

it('should mask an obvious password in an arbitrary string', () => {
expect(maskString('My SuperSecretPassword should be masked')).toBe('My Su***************rd should be masked');
expect(maskString('You will never get that GeniusPasswordForEverything is my password')).toBe(
'You will never get that Ge***********************ng is my password'
);
});

it('sucks', () => {
console.log(maskString('connection to postgres://dbuser:SuperSecretPassword!@localhost:5432/mydb established'));
});
});

0 comments on commit 474b111

Please sign in to comment.