Redact More Variants Of Paths In Stack Traces (#2229)

- [x] Redact stack traces with both forward and backslashes.
- [x] Redact paths with escaped forward slashes.
- [x] Redact URL-encoded paths.
- [x] Minor: Use `is` vs Lodash `is*` for type checking.
- [x] Minor: Rename `Path` to `path`, etc.
- [x] Add ESLint `quotes` rule to allow double quotes to avoid escaping single quotes.
- [x] Consistently use single quotes to denote identifiers in error messages,
      e.g. `'foo' is required`.
This commit is contained in:
Daniel Gasienica 2018-04-11 16:35:54 -04:00 committed by GitHub
commit 9b22e0667a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 192 additions and 99 deletions

View file

@ -51,5 +51,7 @@ module.exports = {
// consistently place operators at end of line except ternaries
'operator-linebreak': 'error',
'quotes': ['error', 'single', { 'avoidEscape': true, 'allowTemplateLiterals': false }],
}
};

View file

@ -11,7 +11,7 @@ const PATH = 'attachments.noindex';
// getPath :: AbsolutePath -> AbsolutePath
exports.getPath = (userDataPath) => {
if (!isString(userDataPath)) {
throw new TypeError('"userDataPath" must be a string');
throw new TypeError("'userDataPath' must be a string");
}
return path.join(userDataPath, PATH);
};
@ -19,7 +19,7 @@ exports.getPath = (userDataPath) => {
// ensureDirectory :: AbsolutePath -> IO Unit
exports.ensureDirectory = async (userDataPath) => {
if (!isString(userDataPath)) {
throw new TypeError('"userDataPath" must be a string');
throw new TypeError("'userDataPath' must be a string");
}
await fse.ensureDir(exports.getPath(userDataPath));
};
@ -29,12 +29,12 @@ exports.ensureDirectory = async (userDataPath) => {
// IO (Promise ArrayBuffer)
exports.createReader = (root) => {
if (!isString(root)) {
throw new TypeError('"root" must be a path');
throw new TypeError("'root' must be a path");
}
return async (relativePath) => {
if (!isString(relativePath)) {
throw new TypeError('"relativePath" must be a string');
throw new TypeError("'relativePath' must be a string");
}
const absolutePath = path.join(root, relativePath);
@ -48,12 +48,12 @@ exports.createReader = (root) => {
// IO (Promise RelativePath)
exports.createWriterForNew = (root) => {
if (!isString(root)) {
throw new TypeError('"root" must be a path');
throw new TypeError("'root' must be a path");
}
return async (arrayBuffer) => {
if (!isArrayBuffer(arrayBuffer)) {
throw new TypeError('"arrayBuffer" must be an array buffer');
throw new TypeError("'arrayBuffer' must be an array buffer");
}
const name = exports.createName();
@ -70,16 +70,16 @@ exports.createWriterForNew = (root) => {
// IO (Promise RelativePath)
exports.createWriterForExisting = (root) => {
if (!isString(root)) {
throw new TypeError('"root" must be a path');
throw new TypeError("'root' must be a path");
}
return async ({ data: arrayBuffer, path: relativePath } = {}) => {
if (!isString(relativePath)) {
throw new TypeError('"relativePath" must be a path');
throw new TypeError("'relativePath' must be a path");
}
if (!isArrayBuffer(arrayBuffer)) {
throw new TypeError('"arrayBuffer" must be an array buffer');
throw new TypeError("'arrayBuffer' must be an array buffer");
}
const buffer = Buffer.from(arrayBuffer);
@ -95,12 +95,12 @@ exports.createWriterForExisting = (root) => {
// IO Unit
exports.createDeleter = (root) => {
if (!isString(root)) {
throw new TypeError('"root" must be a path');
throw new TypeError("'root' must be a path");
}
return async (relativePath) => {
if (!isString(relativePath)) {
throw new TypeError('"relativePath" must be a string');
throw new TypeError("'relativePath' must be a string");
}
const absolutePath = path.join(root, relativePath);
@ -117,7 +117,7 @@ exports.createName = () => {
// getRelativePath :: String -> IO Path
exports.getRelativePath = (name) => {
if (!isString(name)) {
throw new TypeError('"name" must be a string');
throw new TypeError("'name' must be a string");
}
const prefix = name.slice(0, 2);

View file

@ -100,11 +100,11 @@
},
getOrCreate: function(id, type) {
if (typeof id !== 'string') {
throw new TypeError('"id" must be a string');
throw new TypeError("'id' must be a string");
}
if (type !== 'private' && type !== 'group') {
throw new TypeError('"type" must be "private" or "group"; got: ' + type);
throw new TypeError(`'type' must be 'private' or 'group'; got: '${type}'`);
}
if (!this._initialFetchComplete) {

View file

@ -496,7 +496,7 @@ async function writeAttachment(attachment, options) {
}
if (!Attachment.hasData(attachment)) {
throw new TypeError('"attachment.data" is required');
throw new TypeError("'attachment.data' is required");
}
const ciphertext = await crypto.encryptSymmetric(key, attachment.data);

View file

@ -56,7 +56,7 @@ exports.getVersion = async (name) => {
exports.getCount = async ({ store } = {}) => {
if (!isObject(store)) {
throw new TypeError('"store" is required');
throw new TypeError("'store' is required");
}
const request = store.count();

View file

@ -29,15 +29,15 @@ exports.createConversation = async ({
} = {}) => {
if (!isObject(ConversationController) ||
!isFunction(ConversationController.getOrCreateAndWait)) {
throw new TypeError('"ConversationController" is required');
throw new TypeError("'ConversationController' is required");
}
if (!isNumber(numMessages) || numMessages <= 0) {
throw new TypeError('"numMessages" must be a positive number');
throw new TypeError("'numMessages' must be a positive number");
}
if (!isFunction(WhisperMessage)) {
throw new TypeError('"WhisperMessage" is required');
throw new TypeError("'WhisperMessage' is required");
}
const conversation =
@ -80,7 +80,7 @@ const SAMPLE_MESSAGES = [
const ATTACHMENT_SAMPLE_RATE = 0.33;
const createRandomMessage = async ({ conversationId } = {}) => {
if (!isString(conversationId)) {
throw new TypeError('"conversationId" must be a string');
throw new TypeError("'conversationId' must be a string");
}
const sentAt = Date.now() - random(100 * 24 * 60 * 60 * 1000);

View file

@ -29,20 +29,20 @@ exports.processNext = async ({
upgradeMessageSchema,
} = {}) => {
if (!isFunction(BackboneMessage)) {
throw new TypeError('"BackboneMessage" (Whisper.Message) constructor is required');
throw new TypeError("'BackboneMessage' (Whisper.Message) constructor is required");
}
if (!isFunction(BackboneMessageCollection)) {
throw new TypeError('"BackboneMessageCollection" (Whisper.MessageCollection)' +
throw new TypeError("'BackboneMessageCollection' (Whisper.MessageCollection)" +
' constructor is required');
}
if (!isNumber(numMessagesPerBatch)) {
throw new TypeError('"numMessagesPerBatch" is required');
throw new TypeError("'numMessagesPerBatch' is required");
}
if (!isFunction(upgradeMessageSchema)) {
throw new TypeError('"upgradeMessageSchema" is required');
throw new TypeError("'upgradeMessageSchema' is required");
}
const startTime = Date.now();
@ -85,19 +85,19 @@ exports.dangerouslyProcessAllWithoutIndex = async ({
upgradeMessageSchema,
} = {}) => {
if (!isString(databaseName)) {
throw new TypeError('"databaseName" must be a string');
throw new TypeError("'databaseName' must be a string");
}
if (!isNumber(minDatabaseVersion)) {
throw new TypeError('"minDatabaseVersion" must be a number');
throw new TypeError("'minDatabaseVersion' must be a number");
}
if (!isNumber(numMessagesPerBatch)) {
throw new TypeError('"numMessagesPerBatch" must be a number');
throw new TypeError("'numMessagesPerBatch' must be a number");
}
if (!isFunction(upgradeMessageSchema)) {
throw new TypeError('"upgradeMessageSchema" is required');
throw new TypeError("'upgradeMessageSchema' is required");
}
const connection = await database.open(databaseName);
@ -155,7 +155,7 @@ exports.processNextBatchWithoutIndex = async ({
upgradeMessageSchema,
} = {}) => {
if (!isFunction(upgradeMessageSchema)) {
throw new TypeError('"upgradeMessageSchema" is required');
throw new TypeError("'upgradeMessageSchema' is required");
}
const connection = await _getConnection({ databaseName, minDatabaseVersion });
@ -170,11 +170,11 @@ exports.processNextBatchWithoutIndex = async ({
// Private API
const _getConnection = async ({ databaseName, minDatabaseVersion }) => {
if (!isString(databaseName)) {
throw new TypeError('"databaseName" must be a string');
throw new TypeError("'databaseName' must be a string");
}
if (!isNumber(minDatabaseVersion)) {
throw new TypeError('"minDatabaseVersion" must be a number');
throw new TypeError("'minDatabaseVersion' must be a number");
}
const connection = await database.open(databaseName);
@ -194,15 +194,15 @@ const _processBatch = async ({
upgradeMessageSchema,
} = {}) => {
if (!isObject(connection)) {
throw new TypeError('"connection" must be a string');
throw new TypeError("'connection' must be a string");
}
if (!isFunction(upgradeMessageSchema)) {
throw new TypeError('"upgradeMessageSchema" is required');
throw new TypeError("'upgradeMessageSchema' is required");
}
if (!isNumber(numMessagesPerBatch)) {
throw new TypeError('"numMessagesPerBatch" is required');
throw new TypeError("'numMessagesPerBatch' is required");
}
const isAttachmentMigrationComplete =
@ -273,7 +273,7 @@ const _saveMessageBackbone = ({ BackboneMessage } = {}) => (message) => {
const _saveMessage = ({ transaction } = {}) => (message) => {
if (!isObject(transaction)) {
throw new TypeError('"transaction" is required');
throw new TypeError("'transaction' is required");
}
const messagesStore = transaction.objectStore(MESSAGES_STORE_NAME);
@ -289,12 +289,12 @@ const _saveMessage = ({ transaction } = {}) => (message) => {
const _fetchMessagesRequiringSchemaUpgrade =
async ({ BackboneMessageCollection, count } = {}) => {
if (!isFunction(BackboneMessageCollection)) {
throw new TypeError('"BackboneMessageCollection" (Whisper.MessageCollection)' +
throw new TypeError("'BackboneMessageCollection' (Whisper.MessageCollection)" +
' constructor is required');
}
if (!isNumber(count)) {
throw new TypeError('"count" is required');
throw new TypeError("'count' is required");
}
const collection = new BackboneMessageCollection();
@ -318,15 +318,15 @@ const _fetchMessagesRequiringSchemaUpgrade =
const _dangerouslyFetchMessagesRequiringSchemaUpgradeWithoutIndex =
({ connection, count, lastIndex } = {}) => {
if (!isObject(connection)) {
throw new TypeError('"connection" is required');
throw new TypeError("'connection' is required");
}
if (!isNumber(count)) {
throw new TypeError('"count" is required');
throw new TypeError("'count' is required");
}
if (lastIndex && !isString(lastIndex)) {
throw new TypeError('"lastIndex" must be a string');
throw new TypeError("'lastIndex' must be a string");
}
const hasLastIndex = Boolean(lastIndex);
@ -359,7 +359,7 @@ const _dangerouslyFetchMessagesRequiringSchemaUpgradeWithoutIndex =
const _getNumMessages = async ({ connection } = {}) => {
if (!isObject(connection)) {
throw new TypeError('"connection" is required');
throw new TypeError("'connection' is required");
}
const transaction = connection.transaction(MESSAGES_STORE_NAME, 'readonly');

View file

@ -19,12 +19,12 @@ const closeDatabaseConnection = ({ Backbone } = {}) =>
exports.runMigrations = async ({ Backbone, database } = {}) => {
if (!isObject(Backbone) || !isObject(Backbone.Collection) ||
!isFunction(Backbone.Collection.extend)) {
throw new TypeError('"Backbone" is required');
throw new TypeError("'Backbone' is required");
}
if (!isObject(database) || !isString(database.id) ||
!Array.isArray(database.migrations)) {
throw new TypeError('"database" is required');
throw new TypeError("'database' is required");
}
const {
@ -58,7 +58,7 @@ exports.runMigrations = async ({ Backbone, database } = {}) => {
const getMigrationVersions = (database) => {
if (!isObject(database) || !Array.isArray(database.migrations)) {
throw new TypeError('"database" is required');
throw new TypeError("'database' is required");
}
const firstMigration = head(database.migrations);

View file

@ -1,35 +1,64 @@
/* eslint-env node */
const Path = require('path');
const is = require('@sindresorhus/is');
const path = require('path');
const {
escapeRegExp,
isRegExp,
isString,
} = require('lodash');
const { compose } = require('lodash/fp');
const { escapeRegExp } = require('lodash');
const APP_ROOT_PATH = path.join(__dirname, '..', '..', '..');
const PHONE_NUMBER_PATTERN = /\+\d{7,12}(\d{3})/g;
const GROUP_ID_PATTERN = /(group\()([^)]+)(\))/g;
const REDACTION_PLACEHOLDER = '[REDACTED]';
const APP_ROOT_PATH = Path.join(__dirname, '..', '..', '..');
const APP_ROOT_PATH_PATTERN = (() => {
// _redactPath :: Path -> String -> String
exports._redactPath = (filePath) => {
if (!is.string(filePath)) {
throw new TypeError("'filePath' must be a string");
}
const filePathPattern = exports._pathToRegExp(filePath);
return (text) => {
if (!is.string(text)) {
throw new TypeError("'text' must be a string");
}
if (!is.regExp(filePathPattern)) {
return text;
}
return text.replace(filePathPattern, REDACTION_PLACEHOLDER);
};
};
// _pathToRegExp :: Path -> Maybe RegExp
exports._pathToRegExp = (filePath) => {
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
return new RegExp(escapeRegExp(APP_ROOT_PATH), 'g');
const patternString = [
filePath,
pathWithNormalizedSlashes,
pathWithEscapedSlashes,
urlEncodedPath,
].map(escapeRegExp).join('|');
return new RegExp(patternString, 'g');
} catch (error) {
return null;
}
})();
const REDACTION_PLACEHOLDER = '[REDACTED]';
};
// Public API
// redactPhoneNumbers :: String -> String
exports.redactPhoneNumbers = (text) => {
if (!isString(text)) {
throw new TypeError('"text" must be a string');
if (!is.string(text)) {
throw new TypeError("'text' must be a string");
}
return text.replace(PHONE_NUMBER_PATTERN, `+${REDACTION_PLACEHOLDER}$1`);
@ -37,8 +66,8 @@ exports.redactPhoneNumbers = (text) => {
// redactGroupIds :: String -> String
exports.redactGroupIds = (text) => {
if (!isString(text)) {
throw new TypeError('"text" must be a string');
if (!is.string(text)) {
throw new TypeError("'text' must be a string");
}
return text.replace(
@ -49,17 +78,7 @@ exports.redactGroupIds = (text) => {
};
// redactSensitivePaths :: String -> String
exports.redactSensitivePaths = (text) => {
if (!isString(text)) {
throw new TypeError('"text" must be a string');
}
if (!isRegExp(APP_ROOT_PATH_PATTERN)) {
return text;
}
return text.replace(APP_ROOT_PATH_PATTERN, REDACTION_PLACEHOLDER);
};
exports.redactSensitivePaths = exports._redactPath(APP_ROOT_PATH);
// redactAll :: String -> String
exports.redactAll = compose(

View file

@ -26,11 +26,11 @@ exports.markAttachmentMigrationComplete = connection =>
// Private API
exports._getItem = (connection, key) => {
if (!isObject(connection)) {
throw new TypeError('"connection" is required');
throw new TypeError("'connection' is required");
}
if (!isString(key)) {
throw new TypeError('"key" must be a string');
throw new TypeError("'key' must be a string");
}
const transaction = connection.transaction(ITEMS_STORE_NAME, 'readonly');
@ -47,11 +47,11 @@ exports._getItem = (connection, key) => {
exports._setItem = (connection, key, value) => {
if (!isObject(connection)) {
throw new TypeError('"connection" is required');
throw new TypeError("'connection' is required");
}
if (!isString(key)) {
throw new TypeError('"key" must be a string');
throw new TypeError("'key' must be a string");
}
const transaction = connection.transaction(ITEMS_STORE_NAME, 'readwrite');
@ -68,11 +68,11 @@ exports._setItem = (connection, key, value) => {
exports._deleteItem = (connection, key) => {
if (!isObject(connection)) {
throw new TypeError('"connection" is required');
throw new TypeError("'connection' is required");
}
if (!isString(key)) {
throw new TypeError('"key" must be a string');
throw new TypeError("'key' must be a string");
}
const transaction = connection.transaction(ITEMS_STORE_NAME, 'readwrite');

View file

@ -10,15 +10,15 @@ exports.syncReadReceiptConfiguration = async ({
storage,
}) => {
if (!is.string(deviceId)) {
throw new TypeError('"deviceId" is required');
throw new TypeError("'deviceId' is required");
}
if (!is.function(sendRequestConfigurationSyncMessage)) {
throw new TypeError('"sendRequestConfigurationSyncMessage" is required');
throw new TypeError("'sendRequestConfigurationSyncMessage' is required");
}
if (!is.object(storage)) {
throw new TypeError('"storage" is required');
throw new TypeError("'storage' is required");
}
const isPrimaryDevice = deviceId === '1';

View file

@ -1,6 +1,6 @@
exports.stringToArrayBuffer = (string) => {
if (typeof string !== 'string') {
throw new TypeError('"string" must be a string');
throw new TypeError("'string' must be a string");
}
const array = new Uint8Array(string.length);

View file

@ -116,12 +116,12 @@ exports.hasData = attachment =>
// IO (Promise Attachment)
exports.loadData = (readAttachmentData) => {
if (!isFunction(readAttachmentData)) {
throw new TypeError('"readAttachmentData" must be a function');
throw new TypeError("'readAttachmentData' must be a function");
}
return async (attachment) => {
if (!exports.isValid(attachment)) {
throw new TypeError('"attachment" is not valid');
throw new TypeError("'attachment' is not valid");
}
const isAlreadyLoaded = exports.hasData(attachment);
@ -130,7 +130,7 @@ exports.loadData = (readAttachmentData) => {
}
if (!isString(attachment.path)) {
throw new TypeError('"attachment.path" is required');
throw new TypeError("'attachment.path' is required");
}
const data = await readAttachmentData(attachment.path);
@ -143,12 +143,12 @@ exports.loadData = (readAttachmentData) => {
// IO Unit
exports.deleteData = (deleteAttachmentData) => {
if (!isFunction(deleteAttachmentData)) {
throw new TypeError('"deleteAttachmentData" must be a function');
throw new TypeError("'deleteAttachmentData' must be a function");
}
return async (attachment) => {
if (!exports.isValid(attachment)) {
throw new TypeError('"attachment" is not valid');
throw new TypeError("'attachment' is not valid");
}
const hasDataInMemory = exports.hasData(attachment);
@ -157,7 +157,7 @@ exports.deleteData = (deleteAttachmentData) => {
}
if (!isString(attachment.path)) {
throw new TypeError('"attachment.path" is required');
throw new TypeError("'attachment.path' is required");
}
await deleteAttachmentData(attachment.path);

View file

@ -15,7 +15,7 @@ const {
// Promise Attachment
exports.migrateDataToFileSystem = async (attachment, { writeNewAttachmentData } = {}) => {
if (!isFunction(writeNewAttachmentData)) {
throw new TypeError('"writeNewAttachmentData" must be a function');
throw new TypeError("'writeNewAttachmentData' must be a function");
}
const { data } = attachment;

View file

@ -81,10 +81,10 @@ exports.initializeSchemaVersion = (message) => {
// SchemaVersion -> UpgradeStep -> UpgradeStep
exports._withSchemaVersion = (schemaVersion, upgrade) => {
if (!SchemaVersion.isValid(schemaVersion)) {
throw new TypeError('"schemaVersion" is invalid');
throw new TypeError("'schemaVersion' is invalid");
}
if (!isFunction(upgrade)) {
throw new TypeError('"upgrade" must be a function');
throw new TypeError("'upgrade' must be a function");
}
return async (message, context) => {
@ -192,12 +192,12 @@ exports.createAttachmentLoader = (loadAttachmentData) => {
// IO (Promise Message)
exports.createAttachmentDataWriter = (writeExistingAttachmentData) => {
if (!isFunction(writeExistingAttachmentData)) {
throw new TypeError('"writeExistingAttachmentData" must be a function');
throw new TypeError("'writeExistingAttachmentData' must be a function");
}
return async (rawMessage) => {
if (!exports.isValid(rawMessage)) {
throw new TypeError('"rawMessage" is not valid');
throw new TypeError("'rawMessage' is not valid");
}
const message = exports.initializeSchemaVersion(rawMessage);
@ -217,11 +217,11 @@ exports.createAttachmentDataWriter = (writeExistingAttachmentData) => {
attachments.forEach((attachment) => {
if (!Attachment.hasData(attachment)) {
throw new TypeError('"attachment.data" is required during message import');
throw new TypeError("'attachment.data' is required during message import");
}
if (!isString(attachment.path)) {
throw new TypeError('"attachment.path" is required during message import');
throw new TypeError("'attachment.path' is required during message import");
}
});

View file

@ -1,11 +1,11 @@
const Path = require('path');
const path = require('path');
const { assert } = require('chai');
const Privacy = require('../../js/modules/privacy');
const APP_ROOT_PATH = Path.join(__dirname, '..', '..', '..');
const APP_ROOT_PATH = path.join(__dirname, '..', '..', '..');
describe('Privacy', () => {
describe('redactPhoneNumbers', () => {
@ -34,11 +34,12 @@ describe('Privacy', () => {
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:///${APP_ROOT_PATH}/js/background.js.` +
`path2 file:///${encodedAppRootPath}/js/background.js.` +
'phone2 +13334445566 lorem\n' +
'group2 group(abcdefghij) doloret\n';
@ -53,4 +54,75 @@ describe('Privacy', () => {
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);
});
});
});

View file

@ -246,14 +246,14 @@ describe('Message', () => {
const toVersionX = () => {};
assert.throws(
() => Message._withSchemaVersion(toVersionX, 2),
'"schemaVersion" is invalid'
"'schemaVersion' is invalid"
);
});
it('should require an upgrade function', () => {
assert.throws(
() => Message._withSchemaVersion(2, 3),
'"upgrade" must be a function'
"'upgrade' must be a function"
);
});