diff --git a/app/global_errors.js b/app/global_errors.js index da746e691999..3120db89e42c 100644 --- a/app/global_errors.js +++ b/app/global_errors.js @@ -6,7 +6,7 @@ const electron = require('electron'); const Errors = require('../js/modules/types/errors'); const { app, dialog, clipboard } = electron; -const { redactAll } = require('../js/modules/privacy'); +const { redactAll } = require('../ts/util/privacy'); // We use hard-coded strings until we're able to update these strings from the locale. let quitText = 'Quit'; diff --git a/main.js b/main.js index 398877a1dd57..f54ade696a75 100644 --- a/main.js +++ b/main.js @@ -19,7 +19,7 @@ const electron = require('electron'); const packageJson = require('./package.json'); const GlobalErrors = require('./app/global_errors'); const { setup: setupSpellChecker } = require('./app/spell_check'); -const { redactAll } = require('./js/modules/privacy'); +const { redactAll, addSensitivePath } = require('./ts/util/privacy'); const removeUserConfig = require('./app/user_config').remove; GlobalErrors.addHandler(); @@ -1163,6 +1163,8 @@ app.on('ready', async () => { const userDataPath = await getRealPath(app.getPath('userData')); const installPath = await getRealPath(app.getAppPath()); + addSensitivePath(userDataPath); + if (process.env.NODE_ENV !== 'test' && process.env.NODE_ENV !== 'test-lib') { installFileHandler({ protocol: electronProtocol, diff --git a/preload.js b/preload.js index 0867a14f4b68..40c86a0791ec 100644 --- a/preload.js +++ b/preload.js @@ -550,6 +550,11 @@ try { window.baseStickersPath = Attachments.getStickersPath(userDataPath); window.baseTempPath = Attachments.getTempPath(userDataPath); window.baseDraftPath = Attachments.getDraftPath(userDataPath); + + const { addSensitivePath } = require('./ts/util/privacy'); + + addSensitivePath(window.baseAttachmentsPath); + window.Signal = Signal.setup({ Attachments, userDataPath, diff --git a/ts/logging/set_up_renderer_logging.ts b/ts/logging/set_up_renderer_logging.ts index a19a0f250488..407651d4a646 100644 --- a/ts/logging/set_up_renderer_logging.ts +++ b/ts/logging/set_up_renderer_logging.ts @@ -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, diff --git a/ts/logging/shared.ts b/ts/logging/shared.ts index 2641bb2740b9..e890313dcbea 100644 --- a/ts/logging/shared.ts +++ b/ts/logging/shared.ts @@ -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'; diff --git a/test/modules/privacy_test.js b/ts/test-both/util/privacy_test.ts similarity index 93% rename from test/modules/privacy_test.js rename to ts/test-both/util/privacy_test.ts index 72067c69de29..80934baf2d25 100644 --- a/test/modules/privacy_test.js +++ b/ts/test-both/util/privacy_test.ts @@ -1,13 +1,12 @@ -// Copyright 2018-2020 Signal Messenger, LLC +// Copyright 2018-2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -const path = require('path'); +import { assert } from 'chai'; -const { assert } = require('chai'); +import * as Privacy from '../../util/privacy'; +import { APP_ROOT_PATH } from '../../util/privacy'; -const Privacy = require('../../js/modules/privacy'); - -const APP_ROOT_PATH = path.join(__dirname, '..', '..', '..'); +Privacy.addSensitivePath('sensitive-path'); describe('Privacy', () => { describe('redactPhoneNumbers', () => { @@ -86,7 +85,8 @@ describe('Privacy', () => { 'group1 group(123456789) doloret\n' + `path2 file:///${encodedAppRootPath}/js/background.js.` + 'phone2 +13334445566 lorem\n' + - 'group2 group(abcdefghij) doloret\n'; + 'group2 group(abcdefghij) doloret\n' + + 'path3 sensitive-path/attachment.noindex\n'; const actual = Privacy.redactAll(text); const expected = @@ -96,7 +96,8 @@ describe('Privacy', () => { 'group1 group([REDACTED]789) doloret\n' + 'path2 file:///[REDACTED]/js/background.js.' + 'phone2 +[REDACTED]566 lorem\n' + - 'group2 group([REDACTED]hij) doloret\n'; + 'group2 group([REDACTED]hij) doloret\n' + + 'path3 [REDACTED]/attachment.noindex\n'; assert.equal(actual, expected); }); }); diff --git a/js/modules/privacy.js b/ts/util/privacy.ts similarity index 58% rename from js/modules/privacy.js rename to ts/util/privacy.ts index b65dc7db879c..0acd28ff32f6 100644 --- a/js/modules/privacy.js +++ b/ts/util/privacy.ts @@ -1,30 +1,32 @@ -// Copyright 2018-2020 Signal Messenger, LLC +// Copyright 2018-2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only /* eslint-env node */ -const is = require('@sindresorhus/is'); -const path = require('path'); +import is from '@sindresorhus/is'; +import { join as pathJoin } from 'path'; -const { compose } = require('lodash/fp'); -const { escapeRegExp } = require('lodash'); +import { compose } from 'lodash/fp'; +import { escapeRegExp } from 'lodash'; + +export const APP_ROOT_PATH = pathJoin(__dirname, '..', '..'); -const APP_ROOT_PATH = path.join(__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]'; -// _redactPath :: Path -> String -> String -exports._redactPath = filePath => { +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 => { + return (text: string): string => { if (!is.string(text)) { throw new TypeError("'text' must be a string"); } @@ -37,8 +39,7 @@ exports._redactPath = filePath => { }; }; -// _pathToRegExp :: Path -> Maybe RegExp -exports._pathToRegExp = filePath => { +export const _pathToRegExp = (filePath: string): RegExp | undefined => { try { const pathWithNormalizedSlashes = filePath.replace(/\//g, '\\'); const pathWithEscapedSlashes = filePath.replace(/\\/g, '\\\\'); @@ -55,13 +56,12 @@ exports._pathToRegExp = filePath => { .join('|'); return new RegExp(patternString, 'g'); } catch (error) { - return null; + return undefined; } }; // Public API -// redactPhoneNumbers :: String -> String -exports.redactPhoneNumbers = text => { +export const redactPhoneNumbers = (text: string): string => { if (!is.string(text)) { throw new TypeError("'text' must be a string"); } @@ -69,8 +69,7 @@ exports.redactPhoneNumbers = text => { return text.replace(PHONE_NUMBER_PATTERN, `+${REDACTION_PLACEHOLDER}$1`); }; -// redactUuids :: String -> String -exports.redactUuids = text => { +export const redactUuids = (text: string): string => { if (!is.string(text)) { throw new TypeError("'text' must be a string"); } @@ -78,8 +77,7 @@ exports.redactUuids = text => { return text.replace(UUID_PATTERN, `${REDACTION_PLACEHOLDER}$1`); }; -// redactGroupIds :: String -> String -exports.redactGroupIds = text => { +export const redactGroupIds = (text: string): string => { if (!is.string(text)) { throw new TypeError("'text' must be a string"); } @@ -87,29 +85,42 @@ exports.redactGroupIds = text => { return text .replace( GROUP_ID_PATTERN, - (match, before, id, after) => + (_, before, id, after) => `${before}${REDACTION_PLACEHOLDER}${removeNewlines(id).slice( -3 )}${after}` ) .replace( GROUP_V2_ID_PATTERN, - (match, before, id, after) => + (_, before, id, after) => `${before}${REDACTION_PLACEHOLDER}${removeNewlines(id).slice( -3 )}${after}` ); }; -// redactSensitivePaths :: String -> String -exports.redactSensitivePaths = exports._redactPath(APP_ROOT_PATH); +const createRedactSensitivePaths = ( + paths: ReadonlyArray +): RedactFunction => { + return compose(paths.map(filePath => exports._redactPath(filePath))); +}; -// redactAll :: String -> String -exports.redactAll = compose( - exports.redactSensitivePaths, - exports.redactGroupIds, - exports.redactPhoneNumbers, - exports.redactUuids +const sensitivePaths: Array = []; + +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 = text => text.replace(/\r?\n|\r/g, ''); +const removeNewlines: RedactFunction = text => text.replace(/\r?\n|\r/g, '');