Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 63 additions & 7 deletions workers/grouper/src/data-filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,71 @@ export default class DataFilter {
private filteredValuePlaceholder = '[filtered]';

/**
* Possibly sensitive keys
* Possibly sensitive keys (lowercase; keys are compared via key.toLowerCase())
*/
private possiblySensitiveDataKeys = new Set([
'pan',
'secret',
'credentials',
'card[number]',
'password',
/**
* Authorization and sessions
*/
'auth',
'authorization',
'access_token',
'accesstoken',
'token',
'jwt',
'session',
'sessionid',
'session_id',
/**
* API keys and secure tokens
*/
'api_key',
'apikey',
'x-api-key',
'x-auth-token',
'bearer',
'client_secret',
'secret',
'credentials',
/**
* Passwords
*/
'password',
'passwd',
'mysql_pwd',
'oldpassword',
'old-password',
'old_password',
'newpassword',
'new-password',
'new_password',
/**
* Encryption keys
*/
'private_key',
'ssh_key',
/**
* Payments data
*/
'card',
'cardnumber',
'card[number]',
'creditcard',
'credit_card',
'pan',
'pin',
'security_code',
'stripetoken',
'cloudpayments_public_id',
'cloudpayments_secret',
/**
* Config and connections
*/
'dsn',
/**
* Personal data
*/
'ssn',
]);

/**
Expand Down Expand Up @@ -127,7 +181,9 @@ export default class DataFilter {
*/
const clean = value.replace(/\D/g, '');

// Reset last index to 0
/**
* Reset last index to 0
*/
this.bankCardRegex.lastIndex = 0;
if (!this.bankCardRegex.test(clean)) {
return value;
Expand Down
73 changes: 69 additions & 4 deletions workers/grouper/tests/data-filter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,57 @@ function generateEvent({ context, addons }: {context?: Json, addons?: EventAddon
}

/**
* Example of object with sensitive information
* Example of object with sensitive information.
* Keys intentionally use snake_case/kebab-case to match data-filter list.
*/
/* eslint-disable @typescript-eslint/naming-convention */
const sensitiveDataMock = {
pan: '5500 0000 0000 0004',
secret: 'D6A03F5C2E0E356F262D56F44370E1CD813583B2',
credentials: '70BA33708CBFB103F1A8E34AFEF333BA7DC021022B2D9AAA583AABB8058D8D67',
'card[number]': '5500 0000 0000 0004',
password: 'bFb7PBm6nZ7RJRq9',
oldpassword: 'oldSecret123',
newpassword: 'newSecret456',
'old-password': 'oldSecretHyphen',
old_password: 'oldSecretUnderscore',
'new-password': 'newSecretHyphen',
new_password: 'newSecretUnderscore',
auth: 'C4CA4238A0B923820DCC509A6F75849B',
// eslint-disable-next-line @typescript-eslint/naming-convention
access_token: '70BA33708CBFB103F1A8E34AFEF333BA7DC021022B2D9AAA583AABB8058D8D67',
accessToken: '70BA33708CBFB103F1A8E34AFEF333BA7DC021022B2D9AAA583AABB8058D8D67',
};

/**
* Additional sensitive keys (newly added / previously uncovered).
* Keys intentionally use snake_case to match data-filter list.
*/
const additionalSensitiveDataMock = {
authorization: 'Bearer abc123',
token: 'token-value',
jwt: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9',
session: 'sess_xyz',
session_id: 'sid_789',
api_key: 'sk_live_xxx',
bearer: 'Bearer token',
client_secret: 'client_secret_value',
passwd: 'passwd_value',
mysql_pwd: 'mysql_pwd_value',
private_key: '-----BEGIN PRIVATE KEY-----',
ssh_key: 'ssh-rsa AAAA...',
card: '4111111111111111',
cardnumber: '5500000000000004',
creditcard: '4111111111111111',
pin: '1234',
security_code: '999',
stripetoken: 'tok_xxx',
cloudpayments_public_id: 'pk_xxx',
cloudpayments_secret: 'secret_xxx',
dsn: 'postgres://user:pass@host/db',
ssn: '123-45-6789',
};
/* eslint-enable @typescript-eslint/naming-convention */

describe('GrouperWorker', () => {
const dataFilter = new DataFilter();

Expand Down Expand Up @@ -123,6 +160,34 @@ describe('GrouperWorker', () => {
});
});

test('should filter additional sensitive keys (authorization, token, payment, dsn, ssn, etc.) in context', async () => {
const event = generateEvent({
context: additionalSensitiveDataMock,
});

dataFilter.processEvent(event);

Object.keys(additionalSensitiveDataMock).forEach((key) => {
expect(event.context[key]).toBe('[filtered]');
});
});

test('should filter additional sensitive keys in addons', async () => {
const event = generateEvent({
addons: {
vue: {
props: additionalSensitiveDataMock,
},
},
});

dataFilter.processEvent(event);

Object.keys(additionalSensitiveDataMock).forEach((key) => {
expect(event.addons['vue']['props'][key]).toBe('[filtered]');
});
});

test('should not replace values with keynames not in a list', async () => {
const normalValue = 'test123';
const event = generateEvent({
Expand Down Expand Up @@ -154,7 +219,7 @@ describe('GrouperWorker', () => {
const event = generateEvent({
context: {
userId: uuidWithManyDigits,
sessionId: uuidUpperCase,
requestId: uuidUpperCase,
transactionId: uuidNoDashes,
},
addons: {
Expand All @@ -169,7 +234,7 @@ describe('GrouperWorker', () => {
dataFilter.processEvent(event);

expect(event.context['userId']).toBe(uuidWithManyDigits);
expect(event.context['sessionId']).toBe(uuidUpperCase);
expect(event.context['requestId']).toBe(uuidUpperCase);
expect(event.context['transactionId']).toBe(uuidNoDashes);
expect(event.addons['vue']['props']['componentId']).toBe(uuidWithManyDigits);
});
Expand Down
Loading