Add user path to logging exceptions
This commit is contained in:
parent
0e19c17160
commit
33595646c1
7 changed files with 60 additions and 41 deletions
|
@ -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,
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
185
ts/test-both/util/privacy_test.ts
Normal file
185
ts/test-both/util/privacy_test.ts
Normal 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
126
ts/util/privacy.ts
Normal 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, '');
|
Loading…
Add table
Add a link
Reference in a new issue