Skip to content
Draft
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
1 change: 1 addition & 0 deletions cspell.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"language": "en",
"words": [
"amet",
"apikey",
"codemods",
"containerfile",
"deprioritized",
Expand Down
2 changes: 2 additions & 0 deletions src/__tests__/__snapshots__/logger.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ exports[`formatLogEvent should return a formatted log event, partial 1`] = `"[DE

exports[`formatLogEvent should return a formatted log event, undefined 1`] = `"[INFO]:"`;

exports[`formatLogEvent should support sanitizing messages: sanitized 1`] = `"[INFO]: Authorization: Bearer [REDACTED] :https://patternfly.org/?lorem=ipsum&private_token=%5BREDACTED%5D"`;

exports[`formatUnknownError should attempt to return a formatted error on non-errors, bigint 1`] = `"9007199254740991n"`;

exports[`formatUnknownError should attempt to return a formatted error on non-errors, boolean 1`] = `"Non-Error thrown: true"`;
Expand Down
143 changes: 143 additions & 0 deletions src/__tests__/logger.helpers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import {
sanitizeHeaderContent,
sanitizeMessage,
sanitizeTokenContent,
sanitizeUrlContent
} from '../logger.helpers';

describe('sanitizeHeaderContent', () => {
it.each([
{
description: 'authorization bearer token',
input: 'Authorization: Bearer abc123',
expected: 'Authorization: Bearer [REDACTED]'
},
{
description: 'private token header',
input: 'Private-Token: abc123',
expected: 'Private-Token: [REDACTED]'
},
{
description: 'non-string input',
input: undefined,
expected: undefined
},
{
description: 'custom redacted marker',
input: 'Authorization: Bearer abc123',
options: { redacted: '<MASKED>' },
expected: 'Authorization: Bearer <MASKED>'
}
])('should sanitize header content, $description', ({ input, options, expected }) => {
expect(sanitizeHeaderContent(input, options as any)).toBe(expected);
});
});

describe('sanitizeUrlContent', () => {
it.each([
{
description: 'redact secret query param in absolute URL',
input: 'https://example.com/docs?private_token=abc123&page=1',
expected: 'https://example.com/docs?private_token=%5BREDACTED%5D&page=1'
},
{
description: 'redact basic auth credentials',
input: 'https://user:pass@example.com/docs',
expected: 'https://%5BREDACTED%5D:%5BREDACTED%5D@example.com/docs'
},
{
description: 'leave non-url content unchanged',
input: 'not a url',
expected: 'not a url'
},
{
description: 'sanitize url embedded in text',
input: 'Fetch https://example.com/api?access_token=xyz now',
expected: 'Fetch https://example.com/api?access_token=%5BREDACTED%5D now'
},
{
description: 'non-string input',
input: null,
expected: undefined
},
{
description: 'custom redacted marker',
input: 'https://example.com/docs?token=abc',
options: { redacted: '<MASKED>' },
expected: 'https://example.com/docs?token=%3CMASKED%3E'
}
])('should sanitize url content, $description', ({ input, options, expected }) => {
expect(sanitizeUrlContent(input as any, options as any)).toBe(expected);
});
});

describe('sanitizeTokenContent', () => {
it.each([
{
description: 'redact sha-like token',
input: 'token 0123456789abcdef0123456789abcdef',
expected: 'token [REDACTED]'
},
{
description: 'redact base64-like token',
input: 'token QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo=',
expected: 'token [REDACTED]'
},
{
description: 'keep normal sentence intact',
input: 'this is normal text',
expected: 'this is normal text'
},
{
description: 'custom min length avoids short token redaction',
input: 'token abcdef12',
options: { minLength: 64 },
expected: 'token abcdef12'
},
{
description: 'custom redacted marker',
input: 'token 0123456789abcdef0123456789abcdef',
options: { redacted: '<MASKED>' },
expected: 'token <MASKED>'
},
{
description: 'non-string input',
input: { token: 'abc' },
expected: undefined
},
{
description: 'does not redact natural language, "adipiscing" false positive',
input: 'consectetur adipiscing elit',
expected: 'consectetur adipiscing elit'
},
{
description: 'does not redact natural language in a serialized object',
input: '{"lorem":"ipsum dolor sit amet","dolor":"sit amet","amet":"consectetur adipiscing elit"}',
expected: '{"lorem":"ipsum dolor sit amet","dolor":"sit amet","amet":"consectetur adipiscing elit"}'
}
])('should sanitize token content, $description', ({ input, options, expected }) => {
expect(sanitizeTokenContent(input as any, options as any)).toBe(expected);
});
});

describe('sanitizeMessage', () => {
it.each([
{
description: 'sanitize bearer token and url query token in one message',
input: 'Authorization: Bearer abc123 https://example.com/api?access_token=xyz',
expected: 'Authorization: Bearer [REDACTED] https://example.com/api?access_token=%5BREDACTED%5D'
},
{
description: 'sanitize secret in Error message',
input: new Error('Private-Token: abc123'),
expected: 'Error: Private-Token: [REDACTED]'
},
{
description: 'non-string serializable object',
input: { auth: 'Authorization: Bearer abc123' },
expected: '{"auth":"Authorization: Bearer [REDACTED]"}'
}
])('should sanitize message content, $description', ({ input, expected }) => {
expect(sanitizeMessage(input)).toContain(expected);
});
});
16 changes: 16 additions & 0 deletions src/__tests__/logger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ describe('formatUnknownError', () => {
])('should attempt to return a formatted error on non-errors, $description', ({ err }) => {
expect(formatUnknownError(err)).toMatchSnapshot();
});

it('should support sanitizing messages', () => {
const input = 'Authorization: Bearer abc123';

expect(formatUnknownError(input)).toBe('Authorization: Bearer [REDACTED]');
});
});

describe('formatLogEvent', () => {
Expand Down Expand Up @@ -152,6 +158,16 @@ describe('formatLogEvent', () => {
])('should return a formatted log event, $description', ({ event }) => {
expect(formatLogEvent(event as any)).toMatchSnapshot();
});

it('should support sanitizing messages', () => {
const event = {
level: 'info',
msg: 'Authorization: Bearer abc123',
args: ['https://patternfly.org?lorem=ipsum&private_token=dolor']
};

expect(formatLogEvent(event as any)).toMatchSnapshot('sanitized');
});
});

describe('publish', () => {
Expand Down
133 changes: 132 additions & 1 deletion src/__tests__/server.helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
portValid,
splitUri,
stringJoin,
timeoutFunction
timeoutFunction, isBase64Like
} from '../server.helpers';

describe('buildSearchString', () => {
Expand Down Expand Up @@ -709,6 +709,137 @@ describe('isReferenceLike', () => {
});
});

describe('isBase64Like', () => {
it.each([
{
description: 'default strict accepts valid padded base64',
value: 'YWJjZA==',
expected: true
},
{
description: 'default strict rejects missing padding',
value: 'YWJjZA',
expected: false
},
{
description: 'loose valid unpadded base64, match modulo-4',
value: 'TWFu', // "Man"
options: { isStrict: false },
expected: true
},
{
description: 'loose valid unpadded base64, requireSignalChars rejects alpha-only base64',
value: 'TWFu',
options: { isStrict: false, requireSignalChars: true },
expected: false
},
{
description: 'loose valid padded base64 with ==',
value: 'YWJjZA==', // "abcd"
options: { isStrict: false },
expected: true
},
{
description: 'loose missing trailing padding still accepted',
value: 'YWJjZA', // decodes/re-encodes to YWJjZA==
options: { isStrict: false },
expected: true
},
{
description: 'loose trims leading/trailing whitespace',
value: ' YWJjZA== ',
options: { isStrict: false },
expected: true
},
{
description: 'loose invalid alphabet character',
value: 'YWJjZA*=',
options: { isStrict: false },
expected: false
},
{
description: 'loose invalid padding shape',
value: 'abcde=',
options: { isStrict: false },
expected: false
},
{
description: 'loose too short for configured minLength',
value: 'TWFu',
options: { isStrict: false, minLength: 8 },
expected: false
},
{
description: 'strict valid 4-char unpadded base64, potential false positive',
value: 'TWFu',
options: { isStrict: true },
expected: true
},
{
description: 'strict valid 4-char unpadded base64, potential false positive, requireSignalChars',
value: 'TWFu',
options: { isStrict: true, requireSignalChars: true },
expected: false
},
{
description: 'strict valid padded base64 with ==',
value: 'YWJjZA==',
options: { isStrict: true },
expected: true
},
{
description: 'strict valid padded base64 with ==, requireSignalChars',
value: 'YWJjZA==',
options: { isStrict: true, requireSignalChars: true },
expected: true
},
{
description: 'strict fails modulo-4 length check when padding is missing',
value: 'YWJjZA', // length 6
options: { isStrict: true },
expected: false
},
{
description: 'strict invalid alphabet character',
value: 'YWJjZA*=',
options: { isStrict: true },
expected: false
},
{
description: 'strict too short for configured minLength',
value: 'TWFu',
options: { isStrict: true, minLength: 8 },
expected: false
},
{
description: 'guard loose non-string number',
value: 1234,
options: { isStrict: false },
expected: false
},
{
description: 'guard strict non-string null',
value: null,
options: { isStrict: true },
expected: false
},
{
description: 'strict behavior unchanged when requireSignalChars is false',
value: 'QUJDREVG',
options: { isStrict: true, requireSignalChars: false },
expected: true
},
{
description: 'strict behavior when requireSignalChars is true',
value: 'QUJDREVG',
options: { isStrict: true, requireSignalChars: true },
expected: false
}
])('check if value is base64-like, $description', ({ value, options = {}, expected }) => {
expect(isBase64Like(value, { minLength: 4, ...options })).toBe(expected);
});
});

describe('isShaHexLike', () => {
it.each([
{
Expand Down
Loading
Loading