From a1ac8103436362d5bcfe6a1d2b6991af46a9478c Mon Sep 17 00:00:00 2001 From: Daniel Gasienica Date: Thu, 22 Feb 2018 13:21:53 -0500 Subject: [PATCH] Security: Replace Unicode order overrides in attachment names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As a user, when I receive a file attachment, I want to have confidence that the filename I see in the Signal Desktop app is the same as it will be on disk. To prevent user confusion when receiving files with Unicode order override characters, e.g. `testfig.exe` appearing as `testexe.gif`, we replace all occurrences of order overrides (`U+202D` and `U+202E`) with `U+FFFD`. **Changes** - [x] Bump `Attachment` `schemaVersion` to 2. - [x] Replace all Unicode order overrides in `attachment.filename`: `Attachment.replaceUnicodeOrderOverrides`. - [x] Add tests for existing `Attachment.upgradeSchema` - [x] Add tests for existing `Attachment.withSchemaVersion` - [x] Add tests for `Attachment.replaceUnicodeOrderOverrides` positives. - [x] Add `testcheck` generative property-based testing library (based on QuickCheck) to ensure valid filenames are preserved. --- commit 855bdbc7e647e44f73b9e1f5e6d64f734c61169a Author: Daniel Gasienica Date: Thu Feb 22 13:02:01 2018 -0500 Log error stack in case of error commit 6e053ed66aee136f186568fa88aacd4814b2ab07 Author: Daniel Gasienica Date: Thu Feb 22 12:30:28 2018 -0500 Improve `upgradeStep` error handling commit 8c226a2523b701cb578b2137832c3eaf3475bb2b Author: Daniel Gasienica Date: Thu Feb 22 12:30:08 2018 -0500 Check for expected version before upgrade Prevents out of order upgrade steps. commit 28b0675591e782169128f75429b7bab2a22307fa Author: Daniel Gasienica Date: Thu Feb 22 12:29:52 2018 -0500 Reject invalid attachments commit 41f4f457dae9416dae66dc2fa2079483d1f127a9 Author: Daniel Gasienica Date: Thu Feb 22 12:29:36 2018 -0500 Fix upgrade pipeline order commit 3935629e91c49b8d96c1e02bd37b1b31d1180720 Author: Daniel Gasienica Date: Thu Feb 22 12:28:25 2018 -0500 Avoid `_.isPlainObject` Attachments are deserialized from a protocol buffer and can have a non-plain-object constructor. commit 39f6e7f622ff4885e2ccafa354e0edb5864c55d8 Author: Daniel Gasienica Date: Thu Feb 22 12:19:07 2018 -0500 Define basic attachment validity commit adcf7e3243cd90866cc35990c558ff7829019037 Author: Daniel Gasienica Date: Thu Feb 22 12:18:54 2018 -0500 Add tests for attachment upgrade pipeline commit 82fc4644d7e654eea9f348518b086497be2b0cb4 Author: Daniel Gasienica Date: Wed Feb 21 12:20:24 2018 -0500 Favor `async` / `await` over `then` commit 8fe49e3c40e78ced0b8f2eb0b678f4bae842855d Author: Daniel Gasienica Date: Wed Feb 21 12:19:59 2018 -0500 Add `eslint-more` plugin This will enable us to disallow `then` in favor of `async` / `await`. commit 020beefb25f508ae96cf3fc099599fbbca98802b Author: Daniel Gasienica Date: Wed Feb 21 11:31:49 2018 -0500 Remove unnecessary `async` modifiers commit 177090c5f5ad9836f0ca0a5c2f298779519e3692 Author: Daniel Gasienica Date: Wed Feb 21 11:30:55 2018 -0500 Document `operator-linebreak` ESLint rule commit 25622b7c59291cb672ae057c47e7327a564cca40 Author: Daniel Gasienica Date: Wed Feb 21 11:14:15 2018 -0500 Prefix internal function with `_` commit 6aa3cf5098df71e9b710064739ec49d74f81b7bf Author: Daniel Gasienica Date: Fri Feb 16 19:00:07 2018 -0500 Replace all Unicode order override occurrences commit fd6e23b0a519bce3c12c5b9ac676bcd198034fed Author: Daniel Gasienica Date: Fri Feb 16 17:48:41 2018 -0500 Whitelist `testcheck` `check` and `gen` globals commit 400bae9fac5078821813bc0ca17a5d7a72900161 Author: Daniel Gasienica Date: Fri Feb 16 17:46:57 2018 -0500 :art: Fix lint errors commit da53d3960aa7aa36b7cc1fcff414c9e929c0d9fc Author: Daniel Gasienica Date: Fri Feb 16 17:42:42 2018 -0500 Add tests for `Attachment.withSchemaVersion` commit ec203444239d9e3c443ba88cab7ef4672151072d Author: Daniel Gasienica Date: Fri Feb 16 17:42:17 2018 -0500 Add test for `Attachment.upgradeSchema` commit 4540d5bdf7a4279f49d2e4c6ee03f47b93df46bf Author: Daniel Gasienica Date: Fri Feb 16 17:05:29 2018 -0500 Rename `setSchemaVersion` --> `withSchemaVersion` Put the schema version first for better readability. commit e379cf919feda31d1fa96d406c30fd38e159a11d Author: Daniel Gasienica Date: Fri Feb 16 17:03:22 2018 -0500 Add filename sanitization to upgrade pipeline commit 1e344a0d15926fc3e17be20cd90bfa882b65f337 Author: Daniel Gasienica Date: Fri Feb 16 17:01:55 2018 -0500 Test that we preserve non-suspicious filenames commit a2452bfc98f93f82bed48b438757af2e66a6af82 Author: Daniel Gasienica Date: Fri Feb 16 17:00:56 2018 -0500 Add `testcheck` dependency Allows for generative property-based testing similar to Haskell’s QuickCheck. See: https://medium.com/javascript-inside/f91432247c27 commit ceb5bfd2484a77689fdb8e9edd18d4a7b093a486 Author: Daniel Gasienica Date: Fri Feb 16 16:15:33 2018 -0500 Replace Unicode order override characters Prevents users from being tricked into clicking a file named `testexe.fig` that appears as `testexe.gif` due to a Unicode order override character. See: - http://unicode.org/reports/tr36/#Bidirectional_Text_Spoofing - https://krebsonsecurity.com/2011/09/right-to-left-override-aids-email-attacks/ commit bc605afb1c6af3a5ebc31a4c1523ff170eb96ffe Author: Daniel Gasienica Date: Fri Feb 16 16:12:29 2018 -0500 Remove `CURRENT_PROCESS_VERSION` Reintroduce this whenever we need it. We currently only deal with schema version numbers within this module. --- .eslintrc.js | 8 + app/logging.js | 3 + js/modules/types/attachment.js | 141 ++++++++++++--- js/views/file_input_view.js | 9 + main.js | 3 + package.json | 3 + test/modules/.eslintrc | 6 + test/modules/types/attachment_test.js | 246 ++++++++++++++++++++++++++ test/server/app/logging_test.js | 3 + yarn.lock | 14 ++ 10 files changed, 409 insertions(+), 27 deletions(-) create mode 100644 test/modules/.eslintrc create mode 100644 test/modules/types/attachment_test.js diff --git a/.eslintrc.js b/.eslintrc.js index 55d953c9a..124289e01 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -11,6 +11,10 @@ module.exports = { 'airbnb-base', ], + plugins: [ + 'more', + ], + rules: { 'comma-dangle': ['error', { arrays: 'always-multiline', @@ -29,6 +33,9 @@ module.exports = { ignoreUrls: true, }], + // encourage consistent use of `async` / `await` instead of `then` + 'more/no-then': 'error', + // it helps readability to put public API at top, 'no-use-before-define': 'off', @@ -38,6 +45,7 @@ module.exports = { // though we have a logger, we still remap console to log to disk 'no-console': 'off', + // consistently place operators at end of line except ternaries 'operator-linebreak': 'error', } }; diff --git a/app/logging.js b/app/logging.js index 52bf2cc04..1fa10e0ea 100644 --- a/app/logging.js +++ b/app/logging.js @@ -1,3 +1,6 @@ +// NOTE: Temporarily allow `then` until we convert the entire file to `async` / `await`: +/* eslint-disable more/no-then */ + const path = require('path'); const fs = require('fs'); diff --git a/js/modules/types/attachment.js b/js/modules/types/attachment.js index 048f4f368..11c313b86 100644 --- a/js/modules/types/attachment.js +++ b/js/modules/types/attachment.js @@ -1,18 +1,25 @@ +const isFunction = require('lodash/isFunction'); +const isNumber = require('lodash/isNumber'); +const isString = require('lodash/isString'); +const isUndefined = require('lodash/isUndefined'); + const MIME = require('./mime'); const { arrayBufferToBlob, blobToArrayBuffer, dataURLToBlob } = require('blob-util'); const { autoOrientImage } = require('../auto_orient_image'); -// Increment this everytime we change how attachments are upgraded. This allows us to -// retroactively upgrade existing attachments. As we add more upgrade steps, we could -// design a pipeline that does this incrementally, e.g. from version 0 (unknown) -> 1, -// 1 --> 2, etc., similar to how we do database migrations: -const CURRENT_PROCESS_VERSION = 1; +// Increment this version number every time we change how attachments are upgraded. This +// will allow us to retroactively upgrade existing attachments. As we add more upgrade +// steps, we could design a pipeline that does this incrementally, e.g. from +// version 0 / unknown -> 1, 1 --> 2, etc., similar to how we do database migrations: +exports.CURRENT_SCHEMA_VERSION = 2; // Schema version history // // Version 1 // - Auto-orient JPEG attachments using EXIF `Orientation` data // - Add `schemaVersion` property +// Version 2 +// - Sanitize Unicode order override characters // // Incoming message attachment fields // { @@ -37,34 +44,81 @@ const CURRENT_PROCESS_VERSION = 1; // schemaVersion: integer // } +// Returns true if `rawAttachment` is a valid attachment based on our (limited) +// criteria. Over time, we can expand this definition to become more narrow: +exports.isValid = (rawAttachment) => { + // NOTE: We cannot use `_.isPlainObject` because `rawAttachment` is + // deserialized by protobuf: + if (!rawAttachment) { + return false; + } + + return isString(rawAttachment.contentType) && + isString(rawAttachment.fileName); +}; + // Middleware // type UpgradeStep = Attachment -> Promise Attachment -// UpgradeStep -> SchemaVersion -> UpgradeStep -const setSchemaVersion = (next, schemaVersion) => async (attachment) => { - const isAlreadyUpgraded = attachment.schemaVersion >= schemaVersion; - if (isAlreadyUpgraded) { - return attachment; +// SchemaVersion -> UpgradeStep -> UpgradeStep +exports.withSchemaVersion = (schemaVersion, upgrade) => { + if (!isNumber(schemaVersion)) { + throw new TypeError('`schemaVersion` must be a number'); + } + if (!isFunction(upgrade)) { + throw new TypeError('`upgrade` must be a function'); } - let upgradedAttachment; - try { - upgradedAttachment = await next(attachment); - } catch (error) { - console.error('Attachment.setSchemaVersion: error:', error); - upgradedAttachment = null; - } + return async (attachment) => { + if (!exports.isValid(attachment)) { + console.log('Attachment.withSchemaVersion: Invalid input attachment:', attachment); + return attachment; + } - const hasSuccessfullyUpgraded = upgradedAttachment !== null; - if (!hasSuccessfullyUpgraded) { - return attachment; - } + const isAlreadyUpgraded = attachment.schemaVersion >= schemaVersion; + if (isAlreadyUpgraded) { + return attachment; + } - return Object.assign( - {}, - upgradedAttachment, - { schemaVersion } - ); + const expectedVersion = schemaVersion - 1; + const isUnversioned = isUndefined(attachment.schemaVersion); + const hasExpectedVersion = isUnversioned || + attachment.schemaVersion === expectedVersion; + if (!hasExpectedVersion) { + console.log( + 'WARNING: Attachment.withSchemaVersion: Unexpected version:' + + ` Expected attachment to have version ${expectedVersion},` + + ` but got ${attachment.schemaVersion}.`, + attachment + ); + return attachment; + } + + let upgradedAttachment; + try { + upgradedAttachment = await upgrade(attachment); + } catch (error) { + console.log( + 'Attachment.withSchemaVersion: error:', + error && error.stack ? error.stack : error + ); + return attachment; + } + + if (!exports.isValid(upgradedAttachment)) { + console.log( + 'Attachment.withSchemaVersion: Invalid upgraded attachment:', + upgradedAttachment + ); + return attachment; + } + + return Object.assign( + {}, + upgradedAttachment, + { schemaVersion } + ); + }; }; // Upgrade steps @@ -93,6 +147,39 @@ const autoOrientJPEG = async (attachment) => { return newAttachment; }; +const UNICODE_LEFT_TO_RIGHT_OVERRIDE = '\u202D'; +const UNICODE_RIGHT_TO_LEFT_OVERRIDE = '\u202E'; +const UNICODE_REPLACEMENT_CHARACTER = '\uFFFD'; +const INVALID_CHARACTERS_PATTERN = new RegExp( + `[${UNICODE_LEFT_TO_RIGHT_OVERRIDE}${UNICODE_RIGHT_TO_LEFT_OVERRIDE}]`, + 'g' +); +// NOTE: Expose synchronous version to do property-based testing using `testcheck`, +// which currently doesn’t support async testing: +// https://github.com/leebyron/testcheck-js/issues/45 +exports._replaceUnicodeOrderOverridesSync = (attachment) => { + if (!isString(attachment.fileName)) { + return attachment; + } + + const normalizedFilename = attachment.fileName.replace( + INVALID_CHARACTERS_PATTERN, + UNICODE_REPLACEMENT_CHARACTER + ); + const newAttachment = Object.assign({}, attachment, { + fileName: normalizedFilename, + }); + + return newAttachment; +}; + +exports.replaceUnicodeOrderOverrides = async attachment => + exports._replaceUnicodeOrderOverridesSync(attachment); + // Public API +const toVersion1 = exports.withSchemaVersion(1, autoOrientJPEG); +const toVersion2 = exports.withSchemaVersion(2, exports.replaceUnicodeOrderOverrides); + // UpgradeStep -exports.upgradeSchema = setSchemaVersion(autoOrientJPEG, CURRENT_PROCESS_VERSION); +exports.upgradeSchema = async attachment => + toVersion2(await toVersion1(attachment)); diff --git a/js/views/file_input_view.js b/js/views/file_input_view.js index 943771af1..9e5eec921 100644 --- a/js/views/file_input_view.js +++ b/js/views/file_input_view.js @@ -143,6 +143,9 @@ break; } + // NOTE: Temporarily allow `then` until we convert the entire file + // to `async` / `await`: + // eslint-disable-next-line more/no-then window.autoOrientImage(file) .then(dataURL => this.addThumb(dataURL)); break; @@ -150,6 +153,9 @@ this.addThumb('images/file.svg'); break; } + // NOTE: Temporarily allow `then` until we convert the entire file + // to `async` / `await`: + // eslint-disable-next-line more/no-then this.autoScale(file).then(function(blob) { var limitKb = 1000000; var blobType = file.type === 'image/gif' ? 'gif' : type; @@ -214,6 +220,9 @@ return newAttachment; }; + // NOTE: Temporarily allow `then` until we convert the entire file + // to `async` / `await`: + // eslint-disable-next-line more/no-then return this.autoScale(file) .then(this.readFile) .then(setFlags(attachmentFlags)); diff --git a/main.js b/main.js index e74a3e8c9..597c8eac7 100644 --- a/main.js +++ b/main.js @@ -377,6 +377,8 @@ function showAbout() { // Some APIs can only be used after this event occurs. let ready = false; app.on('ready', () => { + // NOTE: Temporarily allow `then` until we convert the entire file to `async` / `await`: + /* eslint-disable more/no-then */ let loggingSetupError; logging.initialize().catch((error) => { loggingSetupError = error; @@ -416,6 +418,7 @@ app.on('ready', () => { const menu = Menu.buildFromTemplate(template); Menu.setApplicationMenu(menu); }); + /* eslint-enable more/no-then */ }); app.on('before-quit', () => { diff --git a/package.json b/package.json index 0ae6567a7..d68ae4889 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "rimraf": "^2.6.2", "semver": "^5.4.1", "spellchecker": "^3.4.4", + "testcheck": "^1.0.0-rc.2", "websocket": "^1.0.25" }, "devDependencies": { @@ -80,6 +81,7 @@ "eslint": "^4.14.0", "eslint-config-airbnb-base": "^12.1.0", "eslint-plugin-import": "^2.8.0", + "eslint-plugin-more": "^0.3.1", "extract-zip": "^1.6.6", "grunt": "^1.0.1", "grunt-cli": "^1.2.0", @@ -92,6 +94,7 @@ "grunt-jscs": "^3.0.1", "grunt-sass": "^2.0.0", "mocha": "^4.1.0", + "mocha-testcheck": "^1.0.0-rc.0", "node-sass-import-once": "^1.2.0", "nyc": "^11.4.1", "spectron": "^3.7.2", diff --git a/test/modules/.eslintrc b/test/modules/.eslintrc new file mode 100644 index 000000000..4cb311c54 --- /dev/null +++ b/test/modules/.eslintrc @@ -0,0 +1,6 @@ +{ + "globals": { + "check": true, + "gen": true + } +} diff --git a/test/modules/types/attachment_test.js b/test/modules/types/attachment_test.js new file mode 100644 index 000000000..4de9d8526 --- /dev/null +++ b/test/modules/types/attachment_test.js @@ -0,0 +1,246 @@ +require('mocha-testcheck').install(); + +const { assert } = require('chai'); + +const Attachment = require('../../../js/modules/types/attachment'); + +describe('Attachment', () => { + describe('upgradeSchema', () => { + it('should upgrade an unversioned attachment to the latest version', async () => { + const input = { + contentType: 'application/json', + data: null, + fileName: 'test\u202Dfig.exe', + size: 1111, + }; + const expected = { + contentType: 'application/json', + data: null, + fileName: 'test\uFFFDfig.exe', + size: 1111, + schemaVersion: Attachment.CURRENT_SCHEMA_VERSION, + }; + + const actual = await Attachment.upgradeSchema(input); + assert.deepEqual(actual, expected); + }); + + context('with multiple upgrade steps', () => { + it('should return last valid attachment when any upgrade step fails', async () => { + const input = { + contentType: 'application/json', + data: null, + fileName: 'test\u202Dfig.exe', + size: 1111, + }; + const expected = { + contentType: 'application/json', + data: null, + fileName: 'test\u202Dfig.exe', + size: 1111, + schemaVersion: 1, + hasUpgradedToVersion1: true, + }; + + const v1 = async attachment => + Object.assign({}, attachment, { hasUpgradedToVersion1: true }); + const v2 = async () => { + throw new Error('boom'); + }; + const v3 = async attachment => + Object.assign({}, attachment, { hasUpgradedToVersion3: true }); + + const toVersion1 = Attachment.withSchemaVersion(1, v1); + const toVersion2 = Attachment.withSchemaVersion(2, v2); + const toVersion3 = Attachment.withSchemaVersion(3, v3); + + const upgradeSchema = async attachment => + toVersion3(await toVersion2(await toVersion1(attachment))); + + const actual = await upgradeSchema(input); + assert.deepEqual(actual, expected); + }); + + it('should skip out-of-order upgrade steps', async () => { + const input = { + contentType: 'application/json', + data: null, + fileName: 'test\u202Dfig.exe', + size: 1111, + }; + const expected = { + contentType: 'application/json', + data: null, + fileName: 'test\u202Dfig.exe', + size: 1111, + schemaVersion: 2, + hasUpgradedToVersion1: true, + hasUpgradedToVersion2: true, + }; + + const v1 = async attachment => + Object.assign({}, attachment, { hasUpgradedToVersion1: true }); + const v2 = async attachment => + Object.assign({}, attachment, { hasUpgradedToVersion2: true }); + const v3 = async attachment => + Object.assign({}, attachment, { hasUpgradedToVersion3: true }); + + const toVersion1 = Attachment.withSchemaVersion(1, v1); + const toVersion2 = Attachment.withSchemaVersion(2, v2); + const toVersion3 = Attachment.withSchemaVersion(3, v3); + + // NOTE: We upgrade to 3 before 2, i.e. the pipeline should abort: + const upgradeSchema = async attachment => + toVersion2(await toVersion3(await toVersion1(attachment))); + + const actual = await upgradeSchema(input); + assert.deepEqual(actual, expected); + }); + }); + }); + + describe('withSchemaVersion', () => { + it('should require a version number', () => { + const toVersionX = () => {}; + assert.throws( + () => Attachment.withSchemaVersion(toVersionX, 2), + '`schemaVersion` must be a number' + ); + }); + + it('should require an upgrade function', () => { + assert.throws( + () => Attachment.withSchemaVersion(2, 3), + '`upgrade` must be a function' + ); + }); + + it('should skip upgrading if attachment has already been upgraded', async () => { + const upgrade = async attachment => + Object.assign({}, attachment, { foo: true }); + const upgradeWithVersion = Attachment.withSchemaVersion(3, upgrade); + + const input = { + contentType: 'image/gif', + data: null, + fileName: 'foo.gif', + size: 1111, + schemaVersion: 4, + }; + const actual = await upgradeWithVersion(input); + assert.deepEqual(actual, input); + }); + + it('should return original attachment if upgrade function throws', async () => { + const upgrade = async () => { + throw new Error('boom!'); + }; + const upgradeWithVersion = Attachment.withSchemaVersion(3, upgrade); + + const input = { + contentType: 'image/gif', + data: null, + fileName: 'foo.gif', + size: 1111, + }; + const actual = await upgradeWithVersion(input); + assert.deepEqual(actual, input); + }); + + it('should return original attachment if upgrade function returns null', async () => { + const upgrade = async () => null; + const upgradeWithVersion = Attachment.withSchemaVersion(3, upgrade); + + const input = { + contentType: 'image/gif', + data: null, + fileName: 'foo.gif', + size: 1111, + }; + const actual = await upgradeWithVersion(input); + assert.deepEqual(actual, input); + }); + }); + + describe('replaceUnicodeOrderOverrides', () => { + it('should sanitize left-to-right order override character', async () => { + const input = { + contentType: 'image/jpeg', + data: null, + fileName: 'test\u202Dfig.exe', + size: 1111, + schemaVersion: 1, + }; + const expected = { + contentType: 'image/jpeg', + data: null, + fileName: 'test\uFFFDfig.exe', + size: 1111, + schemaVersion: 1, + }; + + const actual = await Attachment.replaceUnicodeOrderOverrides(input); + assert.deepEqual(actual, expected); + }); + + it('should sanitize right-to-left order override character', async () => { + const input = { + contentType: 'image/jpeg', + data: null, + fileName: 'test\u202Efig.exe', + size: 1111, + schemaVersion: 1, + }; + const expected = { + contentType: 'image/jpeg', + data: null, + fileName: 'test\uFFFDfig.exe', + size: 1111, + schemaVersion: 1, + }; + + const actual = await Attachment.replaceUnicodeOrderOverrides(input); + assert.deepEqual(actual, expected); + }); + + it('should sanitize multiple override characters', async () => { + const input = { + contentType: 'image/jpeg', + data: null, + fileName: 'test\u202e\u202dlol\u202efig.exe', + size: 1111, + schemaVersion: 1, + }; + const expected = { + contentType: 'image/jpeg', + data: null, + fileName: 'test\uFFFD\uFFFDlol\uFFFDfig.exe', + size: 1111, + schemaVersion: 1, + }; + + const actual = await Attachment.replaceUnicodeOrderOverrides(input); + assert.deepEqual(actual, expected); + }); + + const hasNoUnicodeOrderOverrides = value => + !value.includes('\u202D') && !value.includes('\u202E'); + + check.it( + 'should ignore non-order-override characters', + gen.string.suchThat(hasNoUnicodeOrderOverrides), + (fileName) => { + const input = { + contentType: 'image/jpeg', + data: null, + fileName, + size: 1111, + schemaVersion: 1, + }; + + const actual = Attachment._replaceUnicodeOrderOverridesSync(input); + assert.deepEqual(actual, input); + } + ); + }); +}); diff --git a/test/server/app/logging_test.js b/test/server/app/logging_test.js index 865232bfc..04f73e1d1 100644 --- a/test/server/app/logging_test.js +++ b/test/server/app/logging_test.js @@ -1,3 +1,6 @@ +// NOTE: Temporarily allow `then` until we convert the entire file to `async` / `await`: +/* eslint-disable more/no-then */ + const fs = require('fs'); const path = require('path'); diff --git a/yarn.lock b/yarn.lock index b4d5e145e..9aaa198d0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1661,6 +1661,10 @@ eslint-plugin-import@^2.8.0: minimatch "^3.0.3" read-pkg-up "^2.0.0" +eslint-plugin-more@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-more/-/eslint-plugin-more-0.3.1.tgz#ff688fb3fa8f153c8bfd5d70c15a68dc222a1b31" + eslint-restricted-globals@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz#35f0d5cbc64c2e3ed62e93b4b1a7af05ba7ed4d7" @@ -3468,6 +3472,12 @@ mksnapshot@^0.3.0: fs-extra "0.26.7" request "^2.79.0" +mocha-testcheck@^1.0.0-rc.0: + version "1.0.0-rc.0" + resolved "https://registry.yarnpkg.com/mocha-testcheck/-/mocha-testcheck-1.0.0-rc.0.tgz#05e50203043be1537aef2a87dd96ccd447702773" + dependencies: + testcheck "^1.0.0-rc" + mocha@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/mocha/-/mocha-4.1.0.tgz#7d86cfbcf35cb829e2754c32e17355ec05338794" @@ -4982,6 +4992,10 @@ test-exclude@^4.1.1: read-pkg-up "^1.0.1" require-main-filename "^1.0.1" +testcheck@^1.0.0-rc, testcheck@^1.0.0-rc.2: + version "1.0.0-rc.2" + resolved "https://registry.yarnpkg.com/testcheck/-/testcheck-1.0.0-rc.2.tgz#11356a25b84575efe0b0857451e85b5fa74ee4e4" + text-table@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"