Add user path to logging exceptions

This commit is contained in:
Fedor Indutny 2021-06-01 11:15:23 -07:00 committed by GitHub
parent 0e19c17160
commit 33595646c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 60 additions and 41 deletions

View file

@ -17,7 +17,7 @@ import {
} from '@signalapp/signal-client';
import { uploadDebugLogs } from './debuglogs';
import { redactAll } from '../../js/modules/privacy';
import { redactAll } from '../util/privacy';
import {
LogEntryType,
LogLevel,

View file

@ -3,7 +3,7 @@
import { z } from 'zod';
import * as pino from 'pino';
import { redactAll } from '../../js/modules/privacy';
import { redactAll } from '../util/privacy';
import { missingCaseError } from '../util/missingCaseError';
import { reallyJsonStringify } from '../util/reallyJsonStringify';

View file

@ -0,0 +1,185 @@
// Copyright 2018-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai';
import * as Privacy from '../../util/privacy';
import { APP_ROOT_PATH } from '../../util/privacy';
Privacy.addSensitivePath('sensitive-path');
describe('Privacy', () => {
describe('redactPhoneNumbers', () => {
it('should redact all phone numbers', () => {
const text =
'This is a log line with a phone number +12223334455\n' +
'and another one +13334445566';
const actual = Privacy.redactPhoneNumbers(text);
const expected =
'This is a log line with a phone number +[REDACTED]455\n' +
'and another one +[REDACTED]566';
assert.equal(actual, expected);
});
});
describe('redactUuids', () => {
it('should redact all uuids', () => {
const text =
'This is a log line with a uuid 9e420799-acdf-4bf4-8dee-353d7e2096b4\n' +
'and another one IN ALL UPPERCASE 340727FB-E43A-413B-941B-AADA033B6CA3';
const actual = Privacy.redactUuids(text);
const expected =
'This is a log line with a uuid [REDACTED]6b4\n' +
'and another one IN ALL UPPERCASE [REDACTED]CA3';
assert.equal(actual, expected);
});
});
describe('redactGroupIds', () => {
it('should redact all group IDs', () => {
const text =
'This is a log line with two group IDs: group(123456789)\n' +
'and group(abcdefghij)';
const actual = Privacy.redactGroupIds(text);
const expected =
'This is a log line with two group IDs: group([REDACTED]789)\n' +
'and group([REDACTED]hij)';
assert.equal(actual, expected);
});
it('should remove newlines from redacted group IDs', () => {
const text =
'This is a log line with two group IDs: group(12345678\n9)\n' +
'and group(abc\ndefghij)';
const actual = Privacy.redactGroupIds(text);
const expected =
'This is a log line with two group IDs: group([REDACTED]789)\n' +
'and group([REDACTED]hij)';
assert.equal(actual, expected);
});
it('should remove newlines from redacted group V2 IDs', () => {
const text =
'This is a log line with three group IDs: groupv2(abcd32341a==)\n' +
'and groupv2(abcd32341ad=) and and groupv2(abcd32341ade)';
const actual = Privacy.redactGroupIds(text);
const expected =
'This is a log line with three group IDs: groupv2([REDACTED]41a==)\n' +
'and groupv2([REDACTED]1ad=) and and groupv2([REDACTED]ade)';
assert.equal(actual, expected);
});
});
describe('redactAll', () => {
it('should redact all sensitive information', () => {
const encodedAppRootPath = APP_ROOT_PATH.replace(/ /g, '%20');
const text =
'This is a log line with sensitive information:\n' +
`path1 ${APP_ROOT_PATH}/main.js\n` +
'phone1 +12223334455 ipsum\n' +
'group1 group(123456789) doloret\n' +
`path2 file:///${encodedAppRootPath}/js/background.js.` +
'phone2 +13334445566 lorem\n' +
'group2 group(abcdefghij) doloret\n' +
'path3 sensitive-path/attachment.noindex\n';
const actual = Privacy.redactAll(text);
const expected =
'This is a log line with sensitive information:\n' +
'path1 [REDACTED]/main.js\n' +
'phone1 +[REDACTED]455 ipsum\n' +
'group1 group([REDACTED]789) doloret\n' +
'path2 file:///[REDACTED]/js/background.js.' +
'phone2 +[REDACTED]566 lorem\n' +
'group2 group([REDACTED]hij) doloret\n' +
'path3 [REDACTED]/attachment.noindex\n';
assert.equal(actual, expected);
});
});
describe('_redactPath', () => {
it('should redact file paths', () => {
const testPath = '/Users/meow/Library/Application Support/Signal Beta';
const text =
'This is a log line with sensitive information:\n' +
`path1 ${testPath}/main.js\n` +
'phone1 +12223334455 ipsum\n';
const actual = Privacy._redactPath(testPath)(text);
const expected =
'This is a log line with sensitive information:\n' +
'path1 [REDACTED]/main.js\n' +
'phone1 +12223334455 ipsum\n';
assert.equal(actual, expected);
});
it('should redact URL-encoded paths', () => {
const testPath = '/Users/meow/Library/Application Support/Signal Beta';
const encodedTestPath = encodeURI(testPath);
const text =
'This is a log line with sensitive information:\n' +
`path1 ${testPath}/main.js\n` +
'phone1 +12223334455 ipsum\n' +
'group1 group(123456789) doloret\n' +
`path2 file:///${encodedTestPath}/js/background.js.`;
const actual = Privacy._redactPath(testPath)(text);
const expected =
'This is a log line with sensitive information:\n' +
'path1 [REDACTED]/main.js\n' +
'phone1 +12223334455 ipsum\n' +
'group1 group(123456789) doloret\n' +
'path2 file:///[REDACTED]/js/background.js.';
assert.equal(actual, expected);
});
it('should redact stack traces with both forward and backslashes', () => {
const testPath =
'C:/Users/Meow/AppData/Local/Programs/signal-desktop-beta';
const modifiedTestPath =
'C:\\Users\\Meow\\AppData\\Local\\Programs\\signal-desktop-beta';
const text =
'This is a log line with sensitive information:\n' +
`path1 ${testPath}\\main.js\n` +
'phone1 +12223334455 ipsum\n' +
'group1 group(123456789) doloret\n' +
`path2 ${modifiedTestPath}\\js\\background.js.`;
const actual = Privacy._redactPath(testPath)(text);
const expected =
'This is a log line with sensitive information:\n' +
'path1 [REDACTED]\\main.js\n' +
'phone1 +12223334455 ipsum\n' +
'group1 group(123456789) doloret\n' +
'path2 [REDACTED]\\js\\background.js.';
assert.equal(actual, expected);
});
it('should redact stack traces with escaped backslashes', () => {
const testPath =
'C:\\Users\\Meow\\AppData\\Local\\Programs\\signal-desktop-beta';
const modifiedTestPath =
'C:\\\\Users\\\\Meow\\\\AppData\\\\Local\\\\Programs\\\\signal-desktop-beta';
const text =
'This is a log line with sensitive information:\n' +
`path1 ${testPath}\\main.js\n` +
'phone1 +12223334455 ipsum\n' +
'group1 group(123456789) doloret\n' +
`path2 ${modifiedTestPath}\\js\\background.js.`;
const actual = Privacy._redactPath(testPath)(text);
const expected =
'This is a log line with sensitive information:\n' +
'path1 [REDACTED]\\main.js\n' +
'phone1 +12223334455 ipsum\n' +
'group1 group(123456789) doloret\n' +
'path2 [REDACTED]\\js\\background.js.';
assert.equal(actual, expected);
});
});
});

126
ts/util/privacy.ts Normal file
View file

@ -0,0 +1,126 @@
// Copyright 2018-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* eslint-env node */
import is from '@sindresorhus/is';
import { join as pathJoin } from 'path';
import { compose } from 'lodash/fp';
import { escapeRegExp } from 'lodash';
export const APP_ROOT_PATH = pathJoin(__dirname, '..', '..');
const PHONE_NUMBER_PATTERN = /\+\d{7,12}(\d{3})/g;
const UUID_PATTERN = /[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{9}([0-9A-F]{3})/gi;
const GROUP_ID_PATTERN = /(group\()([^)]+)(\))/g;
const GROUP_V2_ID_PATTERN = /(groupv2\()([^=)]+)(=?=?\))/g;
const REDACTION_PLACEHOLDER = '[REDACTED]';
export type RedactFunction = (value: string) => string;
export const _redactPath = (filePath: string): RedactFunction => {
if (!is.string(filePath)) {
throw new TypeError("'filePath' must be a string");
}
const filePathPattern = exports._pathToRegExp(filePath);
return (text: string): string => {
if (!is.string(text)) {
throw new TypeError("'text' must be a string");
}
if (!is.regExp(filePathPattern)) {
return text;
}
return text.replace(filePathPattern, REDACTION_PLACEHOLDER);
};
};
export const _pathToRegExp = (filePath: string): RegExp | undefined => {
try {
const pathWithNormalizedSlashes = filePath.replace(/\//g, '\\');
const pathWithEscapedSlashes = filePath.replace(/\\/g, '\\\\');
const urlEncodedPath = encodeURI(filePath);
// Safe `String::replaceAll`:
// https://github.com/lodash/lodash/issues/1084#issuecomment-86698786
const patternString = [
filePath,
pathWithNormalizedSlashes,
pathWithEscapedSlashes,
urlEncodedPath,
]
.map(escapeRegExp)
.join('|');
return new RegExp(patternString, 'g');
} catch (error) {
return undefined;
}
};
// Public API
export const redactPhoneNumbers = (text: string): string => {
if (!is.string(text)) {
throw new TypeError("'text' must be a string");
}
return text.replace(PHONE_NUMBER_PATTERN, `+${REDACTION_PLACEHOLDER}$1`);
};
export const redactUuids = (text: string): string => {
if (!is.string(text)) {
throw new TypeError("'text' must be a string");
}
return text.replace(UUID_PATTERN, `${REDACTION_PLACEHOLDER}$1`);
};
export const redactGroupIds = (text: string): string => {
if (!is.string(text)) {
throw new TypeError("'text' must be a string");
}
return text
.replace(
GROUP_ID_PATTERN,
(_, before, id, after) =>
`${before}${REDACTION_PLACEHOLDER}${removeNewlines(id).slice(
-3
)}${after}`
)
.replace(
GROUP_V2_ID_PATTERN,
(_, before, id, after) =>
`${before}${REDACTION_PLACEHOLDER}${removeNewlines(id).slice(
-3
)}${after}`
);
};
const createRedactSensitivePaths = (
paths: ReadonlyArray<string>
): RedactFunction => {
return compose(paths.map(filePath => exports._redactPath(filePath)));
};
const sensitivePaths: Array<string> = [];
let redactSensitivePaths: RedactFunction = (text: string) => text;
export const addSensitivePath = (filePath: string): void => {
sensitivePaths.push(filePath);
redactSensitivePaths = createRedactSensitivePaths(sensitivePaths);
};
addSensitivePath(APP_ROOT_PATH);
export const redactAll: RedactFunction = compose(
(text: string) => redactSensitivePaths(text),
redactGroupIds,
redactPhoneNumbers,
redactUuids
);
const removeNewlines: RedactFunction = text => text.replace(/\r?\n|\r/g, '');