2023-01-03 19:55:46 +00:00
|
|
|
// Copyright 2018 Signal Messenger, LLC
|
2020-10-30 20:34:04 +00:00
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
2018-03-06 21:20:04 +00:00
|
|
|
/* eslint-env node */
|
|
|
|
|
2023-06-16 18:40:58 +00:00
|
|
|
import path from 'path';
|
2018-03-06 21:20:04 +00:00
|
|
|
|
2021-06-01 18:15:23 +00:00
|
|
|
import { compose } from 'lodash/fp';
|
2023-01-12 20:58:53 +00:00
|
|
|
import { escapeRegExp, isString, isRegExp } from 'lodash';
|
2021-06-01 18:15:23 +00:00
|
|
|
|
2023-06-16 18:40:58 +00:00
|
|
|
export const APP_ROOT_PATH = path.join(__dirname, '..', '..');
|
2018-03-06 21:20:04 +00:00
|
|
|
|
|
|
|
const PHONE_NUMBER_PATTERN = /\+\d{7,12}(\d{3})/g;
|
2021-11-11 22:43:05 +00:00
|
|
|
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;
|
2018-03-06 21:20:04 +00:00
|
|
|
const GROUP_ID_PATTERN = /(group\()([^)]+)(\))/g;
|
2020-09-09 02:25:05 +00:00
|
|
|
const GROUP_V2_ID_PATTERN = /(groupv2\()([^=)]+)(=?=?\))/g;
|
2018-04-06 16:54:29 +00:00
|
|
|
const REDACTION_PLACEHOLDER = '[REDACTED]';
|
2018-03-06 21:20:04 +00:00
|
|
|
|
2021-06-01 18:15:23 +00:00
|
|
|
export type RedactFunction = (value: string) => string;
|
|
|
|
|
|
|
|
export const _redactPath = (filePath: string): RedactFunction => {
|
2023-01-12 20:58:53 +00:00
|
|
|
if (!isString(filePath)) {
|
2018-04-11 19:44:52 +00:00
|
|
|
throw new TypeError("'filePath' must be a string");
|
2018-04-06 16:54:29 +00:00
|
|
|
}
|
|
|
|
|
2021-06-01 20:40:55 +00:00
|
|
|
const filePathPattern = _pathToRegExp(filePath);
|
2018-04-06 16:54:29 +00:00
|
|
|
|
2021-06-01 18:15:23 +00:00
|
|
|
return (text: string): string => {
|
2023-01-12 20:58:53 +00:00
|
|
|
if (!isString(text)) {
|
2018-04-11 19:44:52 +00:00
|
|
|
throw new TypeError("'text' must be a string");
|
2018-04-06 16:54:29 +00:00
|
|
|
}
|
|
|
|
|
2023-01-12 20:58:53 +00:00
|
|
|
if (!isRegExp(filePathPattern)) {
|
2018-04-06 16:54:29 +00:00
|
|
|
return text;
|
|
|
|
}
|
|
|
|
|
|
|
|
return text.replace(filePathPattern, REDACTION_PLACEHOLDER);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2021-06-01 18:15:23 +00:00
|
|
|
export const _pathToRegExp = (filePath: string): RegExp | undefined => {
|
2018-03-07 15:57:39 +00:00
|
|
|
try {
|
2023-06-16 18:40:58 +00:00
|
|
|
return new RegExp(
|
|
|
|
// Any possible prefix that we want to include
|
|
|
|
`(${escapeRegExp('file:///')})?${
|
|
|
|
// The rest of the file path
|
|
|
|
filePath
|
|
|
|
// Split by system path seperator ("/" or "\\")
|
|
|
|
// (split by both for tests)
|
|
|
|
.split(/\/|\\/)
|
|
|
|
// Escape all special characters in each part
|
|
|
|
.map(part => {
|
|
|
|
// This segment may need to be URI encoded
|
|
|
|
const urlEncodedPart = encodeURI(part);
|
|
|
|
// If its the same, then we don't need to worry about it
|
|
|
|
if (urlEncodedPart === part) {
|
|
|
|
return escapeRegExp(part);
|
|
|
|
}
|
|
|
|
// Otherwise, we need to test against both
|
|
|
|
return `(${escapeRegExp(part)}|${escapeRegExp(urlEncodedPart)})`;
|
|
|
|
})
|
|
|
|
// Join the parts back together with any possible path seperator
|
|
|
|
.join(
|
|
|
|
`(${[
|
|
|
|
// Posix (Linux, macOS, etc.)
|
|
|
|
path.posix.sep,
|
|
|
|
// Windows
|
|
|
|
path.win32.sep,
|
|
|
|
// Windows (URI encoded)
|
|
|
|
encodeURI(path.win32.sep),
|
|
|
|
]
|
|
|
|
// Escape the parts for use in a RegExp (e.g. "/" -> "\/")
|
|
|
|
.map(sep => escapeRegExp(sep))
|
|
|
|
// In case separators are repeated in the path (e.g. "\\\\")
|
|
|
|
.map(sep => `${sep}+`)
|
|
|
|
// Join all the possible separators together
|
|
|
|
.join('|')})`
|
|
|
|
)
|
|
|
|
}`,
|
|
|
|
'g'
|
|
|
|
);
|
2018-03-07 15:57:39 +00:00
|
|
|
} catch (error) {
|
2021-06-01 18:15:23 +00:00
|
|
|
return undefined;
|
2018-03-07 15:57:39 +00:00
|
|
|
}
|
2018-04-06 16:54:29 +00:00
|
|
|
};
|
2018-03-06 21:20:04 +00:00
|
|
|
|
2018-04-06 16:54:29 +00:00
|
|
|
// Public API
|
2021-06-01 18:15:23 +00:00
|
|
|
export const redactPhoneNumbers = (text: string): string => {
|
2023-01-12 20:58:53 +00:00
|
|
|
if (!isString(text)) {
|
2018-04-11 19:44:52 +00:00
|
|
|
throw new TypeError("'text' must be a string");
|
2018-03-06 21:20:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return text.replace(PHONE_NUMBER_PATTERN, `+${REDACTION_PLACEHOLDER}$1`);
|
|
|
|
};
|
|
|
|
|
2021-06-01 18:15:23 +00:00
|
|
|
export const redactUuids = (text: string): string => {
|
2023-01-12 20:58:53 +00:00
|
|
|
if (!isString(text)) {
|
2020-03-05 21:14:58 +00:00
|
|
|
throw new TypeError("'text' must be a string");
|
|
|
|
}
|
|
|
|
|
|
|
|
return text.replace(UUID_PATTERN, `${REDACTION_PLACEHOLDER}$1`);
|
|
|
|
};
|
|
|
|
|
2021-06-01 18:15:23 +00:00
|
|
|
export const redactGroupIds = (text: string): string => {
|
2023-01-12 20:58:53 +00:00
|
|
|
if (!isString(text)) {
|
2018-04-11 19:44:52 +00:00
|
|
|
throw new TypeError("'text' must be a string");
|
2018-03-06 21:20:04 +00:00
|
|
|
}
|
|
|
|
|
2020-09-09 02:25:05 +00:00
|
|
|
return text
|
|
|
|
.replace(
|
|
|
|
GROUP_ID_PATTERN,
|
2021-06-01 18:15:23 +00:00
|
|
|
(_, before, id, after) =>
|
2020-09-09 02:25:05 +00:00
|
|
|
`${before}${REDACTION_PLACEHOLDER}${removeNewlines(id).slice(
|
|
|
|
-3
|
|
|
|
)}${after}`
|
|
|
|
)
|
|
|
|
.replace(
|
|
|
|
GROUP_V2_ID_PATTERN,
|
2021-06-01 18:15:23 +00:00
|
|
|
(_, before, id, after) =>
|
2020-09-09 02:25:05 +00:00
|
|
|
`${before}${REDACTION_PLACEHOLDER}${removeNewlines(id).slice(
|
|
|
|
-3
|
|
|
|
)}${after}`
|
|
|
|
);
|
2018-03-06 21:20:04 +00:00
|
|
|
};
|
|
|
|
|
2021-06-01 18:15:23 +00:00
|
|
|
const createRedactSensitivePaths = (
|
|
|
|
paths: ReadonlyArray<string>
|
|
|
|
): RedactFunction => {
|
2021-06-01 20:40:55 +00:00
|
|
|
return compose(paths.map(filePath => _redactPath(filePath)));
|
2021-06-01 18:15:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
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);
|
2018-03-06 21:20:04 +00:00
|
|
|
|
2021-06-01 18:15:23 +00:00
|
|
|
export const redactAll: RedactFunction = compose(
|
|
|
|
(text: string) => redactSensitivePaths(text),
|
|
|
|
redactGroupIds,
|
|
|
|
redactPhoneNumbers,
|
|
|
|
redactUuids
|
2018-03-06 21:20:04 +00:00
|
|
|
);
|
2018-05-03 15:46:21 +00:00
|
|
|
|
2021-06-01 18:15:23 +00:00
|
|
|
const removeNewlines: RedactFunction = text => text.replace(/\r?\n|\r/g, '');
|