diff --git a/backend/package.json b/backend/package.json index b3926ce57..deb2e84cc 100644 --- a/backend/package.json +++ b/backend/package.json @@ -39,6 +39,7 @@ "@nestjs/swagger": "7.3.0", "@nestjs/typeorm": "10.0.2", "@nestjs/websockets": "10.3.3", + "@node-rs/argon2": "^1.8.3", "@types/bcrypt": "5.0.2", "@types/cron": "2.0.1", "@types/minio": "7.1.0", @@ -48,7 +49,6 @@ "@zxcvbn-ts/language-common": "3.0.4", "@zxcvbn-ts/language-en": "3.0.2", "base32-encode": "1.2.0", - "bcrypt": "5.1.1", "class-transformer": "0.5.1", "class-validator": "0.14.1", "cli-color": "2.0.3", diff --git a/backend/src/identity/identity.service.ts b/backend/src/identity/identity.service.ts index fa263abd8..a695324c7 100644 --- a/backend/src/identity/identity.service.ts +++ b/backend/src/identity/identity.service.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) + * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ @@ -186,7 +186,7 @@ export class IdentityService { await getFirstIdentityFromUser(user, ProviderType.LOCAL); if (internalIdentity === undefined) { this.logger.debug( - `The user with the username ${user.username} does not have a internal identity.`, + `The user with the username ${user.username} does not have an internal identity.`, 'checkLocalPassword', ); throw new NoLocalIdentityError('This user has no internal identity.'); diff --git a/backend/src/utils/password.spec.ts b/backend/src/utils/password.spec.ts index 199c7d16b..ad0d5ca7e 100644 --- a/backend/src/utils/password.spec.ts +++ b/backend/src/utils/password.spec.ts @@ -1,51 +1,67 @@ /* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ -import bcrypt from 'bcrypt'; -import { randomBytes } from 'crypto'; +import argon2 from '@node-rs/argon2'; import { bufferToBase64Url, checkPassword, hashPassword } from './password'; const testPassword = 'thisIsATestPassword'; +const hashOfTestPassword = + '$argon2id$v=19$m=19456,t=2,p=1$40fR6RcTofpngCk4xXhY8w$wAkstPrKkMgrb26TyNqrUzT78jZ+EIjwcJYZHcjrL+Q'; describe('hashPassword', () => { - it('output looks like a bcrypt hash with 2^12 rounds of hashing', async () => { + it('output looks like a argon2 hash', async () => { /* - * a bcrypt hash example with the different parts highlighted: - * $2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy - * \__/\/ \____________________/\_____________________________/ - * Alg Cost Salt Hash - * from https://en.wikipedia.org/wiki/Bcrypt#Description + * a argon2 hash example with the different parts highlighted: + * $argon2id$v=19$m=19456,t=2,p=1$40fR6RcTofpngCk4xXhY8w$wAkstPrKkMgrb26TyNqrUzT78jZ+EIjwcJYZHcjrL+Q + * \________/\___/\______________/\_____________________/\_________________________________________/ + * Alg Ver Parameters Salt Hash */ - const regexBcrypt = /^\$2[abxy]\$12\$[A-Za-z0-9/.]{53}$/; + const regexArgon2 = + /^\$argon2id\$v=19\$m=19456,t=2,p=1\$[\w+./]{22}\$[\w+./]{43}$/; const hash = await hashPassword(testPassword); - expect(regexBcrypt.test(hash)).toBeTruthy(); + expect(regexArgon2.test(hash)).toBeTruthy(); }); - it('calls bcrypt.hash with the correct parameters', async () => { - const spy = jest.spyOn(bcrypt, 'hash'); + it('calls argon2.hash with the correct parameters', async () => { + const spy = jest.spyOn(argon2, 'hash'); await hashPassword(testPassword); - expect(spy).toHaveBeenCalledWith(testPassword, 12); + expect(spy).toHaveBeenCalledWith(testPassword, { + memoryCost: 19456, + timeCost: 2, + parallelism: 1, + }); }); }); describe('checkPassword', () => { - it("is returning true if the inputs are a plaintext password and it's bcrypt-hashed version", async () => { - const hashOfTestPassword = - '$2a$12$WHKCq4c0rg19zyx5WgX0p.or0rjSKYpIBcHhQQGLrxrr6FfMPylIW'; + it("is returning true if the inputs are a plaintext password and it's hashed version", async () => { await checkPassword(testPassword, hashOfTestPassword).then((result) => expect(result).toBeTruthy(), ); }); - it('fails, if secret is too short', async () => { - const secret = bufferToBase64Url(randomBytes(54)); - const hash = await hashPassword(secret); - await checkPassword(secret, hash).then((result) => + it('fails, if password is non-matching', async () => { + const password = 'anotherTestPassword'; + await checkPassword(password, hashOfTestPassword).then((result) => + expect(result).toBeFalsy(), + ); + }); + it('calls argon2.verify with the correct parameters', async () => { + const spy = jest.spyOn(argon2, 'verify'); + await checkPassword(testPassword, hashOfTestPassword); + expect(spy).toHaveBeenCalledWith(hashOfTestPassword, testPassword); + }); + it('verifies even passwords longer than 72 bytes', async () => { + const password = 'a'.repeat(70); + const hash = + '$argon2id$v=19$m=19456,t=2,p=1$4aBLKxd7MqYQqf/th835yQ$iUMe+HHphn8B8q6gQ3IPL2k1+Bdbb505r7LuqZIMTjg'; + await checkPassword(password, hash).then((result) => expect(result).toBeTruthy(), ); - await checkPassword(secret.substr(0, secret.length - 1), hash).then( - (result) => expect(result).toBeFalsy(), + const password2 = 'a'.repeat(73); + await checkPassword(password2, hash).then((result) => + expect(result).toBeFalsy(), ); }); }); diff --git a/backend/src/utils/password.ts b/backend/src/utils/password.ts index a419bf01f..0bf090ff5 100644 --- a/backend/src/utils/password.ts +++ b/backend/src/utils/password.ts @@ -1,21 +1,38 @@ /* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ -import { compare, hash } from 'bcrypt'; +import { hash, verify } from '@node-rs/argon2'; +/** + * Hashes a password using argon2id + * + * @param cleartext The password to hash + * @returns The hashed password + */ export async function hashPassword(cleartext: string): Promise { - // hash the password with bcrypt and 2^12 iterations - // this was decided on the basis of https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#bcrypt - return await hash(cleartext, 12); + // options recommended by OWASP + // https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id + return await hash(cleartext, { + memoryCost: 19456, + timeCost: 2, + parallelism: 1, + }); } +/** + * Checks if a cleartext password matches a password hash + * + * @param cleartext The cleartext password + * @param passwordHash The password hash + * @returns Whether the password matches the hash + */ export async function checkPassword( cleartext: string, - password: string, + passwordHash: string, ): Promise { - return await compare(cleartext, password); + return await verify(passwordHash, cleartext); } export function bufferToBase64Url(text: Buffer): string { diff --git a/yarn.lock b/yarn.lock index fda5767a8..ef7db4a29 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2206,6 +2206,16 @@ __metadata: languageName: node linkType: hard +"@emnapi/core@npm:^1.1.0": + version: 1.2.0 + resolution: "@emnapi/core@npm:1.2.0" + dependencies: + "@emnapi/wasi-threads": "npm:1.0.1" + tslib: "npm:^2.4.0" + checksum: 10c0/a9cf024c1982cd965f6888d1b4514926ad3675fa9d0bd792c9a0770fb592c4c4d20aa1e97a225a7682f9c7900231751434820d5558fd5a00929c2ee976ce5265 + languageName: node + linkType: hard + "@emnapi/runtime@npm:^0.45.0": version: 0.45.0 resolution: "@emnapi/runtime@npm:0.45.0" @@ -2215,6 +2225,24 @@ __metadata: languageName: node linkType: hard +"@emnapi/runtime@npm:^1.1.0": + version: 1.2.0 + resolution: "@emnapi/runtime@npm:1.2.0" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/7005ff8b67724c9e61b6cd79a3decbdb2ce25d24abd4d3d187472f200ee6e573329c30264335125fb136bd813aa9cf9f4f7c9391d04b07dd1e63ce0a3427be57 + languageName: node + linkType: hard + +"@emnapi/wasi-threads@npm:1.0.1": + version: 1.0.1 + resolution: "@emnapi/wasi-threads@npm:1.0.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/1e0c8036b8d53e9b07cc9acf021705ef6c86ab6b13e1acda7fffaf541a2d3565072afb92597419173ced9ea14f6bf32fce149106e669b5902b825e8b499e5c6c + languageName: node + linkType: hard + "@emotion/cache@npm:^10.0.27": version: 10.0.29 resolution: "@emotion/cache@npm:10.0.29" @@ -2383,6 +2411,7 @@ __metadata: "@nestjs/testing": "npm:10.3.3" "@nestjs/typeorm": "npm:10.0.2" "@nestjs/websockets": "npm:10.3.3" + "@node-rs/argon2": "npm:^1.8.3" "@trivago/prettier-plugin-sort-imports": "npm:4.3.0" "@tsconfig/node18": "npm:18.2.2" "@types/bcrypt": "npm:5.0.2" @@ -2411,7 +2440,6 @@ __metadata: "@zxcvbn-ts/language-common": "npm:3.0.4" "@zxcvbn-ts/language-en": "npm:3.0.2" base32-encode: "npm:1.2.0" - bcrypt: "npm:5.1.1" class-transformer: "npm:0.5.1" class-validator: "npm:0.14.1" cli-color: "npm:2.0.3" @@ -3423,25 +3451,6 @@ __metadata: languageName: node linkType: hard -"@mapbox/node-pre-gyp@npm:^1.0.11": - version: 1.0.11 - resolution: "@mapbox/node-pre-gyp@npm:1.0.11" - dependencies: - detect-libc: "npm:^2.0.0" - https-proxy-agent: "npm:^5.0.0" - make-dir: "npm:^3.1.0" - node-fetch: "npm:^2.6.7" - nopt: "npm:^5.0.0" - npmlog: "npm:^5.0.1" - rimraf: "npm:^3.0.2" - semver: "npm:^7.3.5" - tar: "npm:^6.1.11" - bin: - node-pre-gyp: bin/node-pre-gyp - checksum: 10c0/2b24b93c31beca1c91336fa3b3769fda98e202fb7f9771f0f4062588d36dcc30fcf8118c36aa747fa7f7610d8cf601872bdaaf62ce7822bb08b545d1bbe086cc - languageName: node - linkType: hard - "@microsoft/tsdoc@npm:^0.14.2": version: 0.14.2 resolution: "@microsoft/tsdoc@npm:0.14.2" @@ -3456,6 +3465,17 @@ __metadata: languageName: node linkType: hard +"@napi-rs/wasm-runtime@npm:^0.2.3": + version: 0.2.4 + resolution: "@napi-rs/wasm-runtime@npm:0.2.4" + dependencies: + "@emnapi/core": "npm:^1.1.0" + "@emnapi/runtime": "npm:^1.1.0" + "@tybys/wasm-util": "npm:^0.9.0" + checksum: 10c0/1040de49b2ef509db207e2517465dbf7fb3474f20e8ec32897672a962ff4f59872385666dac61dc9dbeae3cae5dad265d8dc3865da756adeb07d1634c67b03a1 + languageName: node + linkType: hard + "@nestjs/cli@npm:10.3.2": version: 10.3.2 resolution: "@nestjs/cli@npm:10.3.2" @@ -3827,6 +3847,157 @@ __metadata: languageName: node linkType: hard +"@node-rs/argon2-android-arm-eabi@npm:1.8.3": + version: 1.8.3 + resolution: "@node-rs/argon2-android-arm-eabi@npm:1.8.3" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@node-rs/argon2-android-arm64@npm:1.8.3": + version: 1.8.3 + resolution: "@node-rs/argon2-android-arm64@npm:1.8.3" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@node-rs/argon2-darwin-arm64@npm:1.8.3": + version: 1.8.3 + resolution: "@node-rs/argon2-darwin-arm64@npm:1.8.3" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@node-rs/argon2-darwin-x64@npm:1.8.3": + version: 1.8.3 + resolution: "@node-rs/argon2-darwin-x64@npm:1.8.3" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@node-rs/argon2-freebsd-x64@npm:1.8.3": + version: 1.8.3 + resolution: "@node-rs/argon2-freebsd-x64@npm:1.8.3" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@node-rs/argon2-linux-arm-gnueabihf@npm:1.8.3": + version: 1.8.3 + resolution: "@node-rs/argon2-linux-arm-gnueabihf@npm:1.8.3" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@node-rs/argon2-linux-arm64-gnu@npm:1.8.3": + version: 1.8.3 + resolution: "@node-rs/argon2-linux-arm64-gnu@npm:1.8.3" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@node-rs/argon2-linux-arm64-musl@npm:1.8.3": + version: 1.8.3 + resolution: "@node-rs/argon2-linux-arm64-musl@npm:1.8.3" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@node-rs/argon2-linux-x64-gnu@npm:1.8.3": + version: 1.8.3 + resolution: "@node-rs/argon2-linux-x64-gnu@npm:1.8.3" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@node-rs/argon2-linux-x64-musl@npm:1.8.3": + version: 1.8.3 + resolution: "@node-rs/argon2-linux-x64-musl@npm:1.8.3" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@node-rs/argon2-wasm32-wasi@npm:1.8.3": + version: 1.8.3 + resolution: "@node-rs/argon2-wasm32-wasi@npm:1.8.3" + dependencies: + "@napi-rs/wasm-runtime": "npm:^0.2.3" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@node-rs/argon2-win32-arm64-msvc@npm:1.8.3": + version: 1.8.3 + resolution: "@node-rs/argon2-win32-arm64-msvc@npm:1.8.3" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@node-rs/argon2-win32-ia32-msvc@npm:1.8.3": + version: 1.8.3 + resolution: "@node-rs/argon2-win32-ia32-msvc@npm:1.8.3" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@node-rs/argon2-win32-x64-msvc@npm:1.8.3": + version: 1.8.3 + resolution: "@node-rs/argon2-win32-x64-msvc@npm:1.8.3" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@node-rs/argon2@npm:^1.8.3": + version: 1.8.3 + resolution: "@node-rs/argon2@npm:1.8.3" + dependencies: + "@node-rs/argon2-android-arm-eabi": "npm:1.8.3" + "@node-rs/argon2-android-arm64": "npm:1.8.3" + "@node-rs/argon2-darwin-arm64": "npm:1.8.3" + "@node-rs/argon2-darwin-x64": "npm:1.8.3" + "@node-rs/argon2-freebsd-x64": "npm:1.8.3" + "@node-rs/argon2-linux-arm-gnueabihf": "npm:1.8.3" + "@node-rs/argon2-linux-arm64-gnu": "npm:1.8.3" + "@node-rs/argon2-linux-arm64-musl": "npm:1.8.3" + "@node-rs/argon2-linux-x64-gnu": "npm:1.8.3" + "@node-rs/argon2-linux-x64-musl": "npm:1.8.3" + "@node-rs/argon2-wasm32-wasi": "npm:1.8.3" + "@node-rs/argon2-win32-arm64-msvc": "npm:1.8.3" + "@node-rs/argon2-win32-ia32-msvc": "npm:1.8.3" + "@node-rs/argon2-win32-x64-msvc": "npm:1.8.3" + dependenciesMeta: + "@node-rs/argon2-android-arm-eabi": + optional: true + "@node-rs/argon2-android-arm64": + optional: true + "@node-rs/argon2-darwin-arm64": + optional: true + "@node-rs/argon2-darwin-x64": + optional: true + "@node-rs/argon2-freebsd-x64": + optional: true + "@node-rs/argon2-linux-arm-gnueabihf": + optional: true + "@node-rs/argon2-linux-arm64-gnu": + optional: true + "@node-rs/argon2-linux-arm64-musl": + optional: true + "@node-rs/argon2-linux-x64-gnu": + optional: true + "@node-rs/argon2-linux-x64-musl": + optional: true + "@node-rs/argon2-wasm32-wasi": + optional: true + "@node-rs/argon2-win32-arm64-msvc": + optional: true + "@node-rs/argon2-win32-ia32-msvc": + optional: true + "@node-rs/argon2-win32-x64-msvc": + optional: true + checksum: 10c0/ae4c466fb8845b6b4db99a6f8814aa89cc6ba38cd351c0c6df171301ecab95a44a3cb8ae4683e6ae93d12cf72cd1fe12934abff8b1246e6bf5ad0eb4e84844d3 + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -4496,6 +4667,15 @@ __metadata: languageName: node linkType: hard +"@tybys/wasm-util@npm:^0.9.0": + version: 0.9.0 + resolution: "@tybys/wasm-util@npm:0.9.0" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/f9fde5c554455019f33af6c8215f1a1435028803dc2a2825b077d812bed4209a1a64444a4ca0ce2ea7e1175c8d88e2f9173a36a33c199e8a5c671aa31de8242d + languageName: node + linkType: hard + "@types/accepts@npm:*": version: 1.3.7 resolution: "@types/accepts@npm:1.3.7" @@ -6206,16 +6386,6 @@ __metadata: languageName: node linkType: hard -"are-we-there-yet@npm:^2.0.0": - version: 2.0.0 - resolution: "are-we-there-yet@npm:2.0.0" - dependencies: - delegates: "npm:^1.0.0" - readable-stream: "npm:^3.6.0" - checksum: 10c0/375f753c10329153c8d66dc95e8f8b6c7cc2aa66e05cb0960bd69092b10dae22900cacc7d653ad11d26b3ecbdbfe1e8bfb6ccf0265ba8077a7d979970f16b99c - languageName: node - linkType: hard - "are-we-there-yet@npm:^3.0.0": version: 3.0.1 resolution: "are-we-there-yet@npm:3.0.1" @@ -6685,16 +6855,6 @@ __metadata: languageName: node linkType: hard -"bcrypt@npm:5.1.1": - version: 5.1.1 - resolution: "bcrypt@npm:5.1.1" - dependencies: - "@mapbox/node-pre-gyp": "npm:^1.0.11" - node-addon-api: "npm:^5.0.0" - checksum: 10c0/743231158c866bddc46f25eb8e9617fe38bc1a6f5f3052aba35e361d349b7f8fb80e96b45c48a4c23c45c29967ccd11c81cf31166454fc0ab019801c336cab40 - languageName: node - linkType: hard - "bcryptjs@npm:^2.4.0": version: 2.4.3 resolution: "bcryptjs@npm:2.4.3" @@ -7381,7 +7541,7 @@ __metadata: languageName: node linkType: hard -"color-support@npm:^1.1.2, color-support@npm:^1.1.3": +"color-support@npm:^1.1.3": version: 1.1.3 resolution: "color-support@npm:1.1.3" bin: @@ -7525,7 +7685,7 @@ __metadata: languageName: node linkType: hard -"console-control-strings@npm:^1.0.0, console-control-strings@npm:^1.1.0": +"console-control-strings@npm:^1.1.0": version: 1.1.0 resolution: "console-control-strings@npm:1.1.0" checksum: 10c0/7ab51d30b52d461412cd467721bb82afe695da78fff8f29fe6f6b9cbaac9a2328e27a22a966014df9532100f6dd85370460be8130b9c677891ba36d96a343f50 @@ -10439,23 +10599,6 @@ __metadata: languageName: node linkType: hard -"gauge@npm:^3.0.0": - version: 3.0.2 - resolution: "gauge@npm:3.0.2" - dependencies: - aproba: "npm:^1.0.3 || ^2.0.0" - color-support: "npm:^1.1.2" - console-control-strings: "npm:^1.0.0" - has-unicode: "npm:^2.0.1" - object-assign: "npm:^4.1.1" - signal-exit: "npm:^3.0.0" - string-width: "npm:^4.2.3" - strip-ansi: "npm:^6.0.1" - wide-align: "npm:^1.1.2" - checksum: 10c0/75230ccaf216471e31025c7d5fcea1629596ca20792de50c596eb18ffb14d8404f927cd55535aab2eeecd18d1e11bd6f23ec3c2e9878d2dda1dc74bccc34b913 - languageName: node - linkType: hard - "gauge@npm:^4.0.3": version: 4.0.4 resolution: "gauge@npm:4.0.4" @@ -12883,15 +13026,6 @@ __metadata: languageName: node linkType: hard -"make-dir@npm:^3.1.0": - version: 3.1.0 - resolution: "make-dir@npm:3.1.0" - dependencies: - semver: "npm:^6.0.0" - checksum: 10c0/56aaafefc49c2dfef02c5c95f9b196c4eb6988040cf2c712185c7fe5c99b4091591a7fc4d4eafaaefa70ff763a26f6ab8c3ff60b9e75ea19876f49b18667ecaa - languageName: node - linkType: hard - "make-dir@npm:^4.0.0": version: 4.0.0 resolution: "make-dir@npm:4.0.0" @@ -14036,15 +14170,6 @@ __metadata: languageName: node linkType: hard -"node-addon-api@npm:^5.0.0": - version: 5.1.0 - resolution: "node-addon-api@npm:5.1.0" - dependencies: - node-gyp: "npm:latest" - checksum: 10c0/0eb269786124ba6fad9df8007a149e03c199b3e5a3038125dfb3e747c2d5113d406a4e33f4de1ea600aa2339be1f137d55eba1a73ee34e5fff06c52a5c296d1d - languageName: node - linkType: hard - "node-addon-api@npm:^7.0.0": version: 7.1.0 resolution: "node-addon-api@npm:7.1.0" @@ -14176,18 +14301,6 @@ __metadata: languageName: node linkType: hard -"npmlog@npm:^5.0.1": - version: 5.0.1 - resolution: "npmlog@npm:5.0.1" - dependencies: - are-we-there-yet: "npm:^2.0.0" - console-control-strings: "npm:^1.1.0" - gauge: "npm:^3.0.0" - set-blocking: "npm:^2.0.0" - checksum: 10c0/489ba519031013001135c463406f55491a17fc7da295c18a04937fe3a4d523fd65e88dd418a28b967ab743d913fdeba1e29838ce0ad8c75557057c481f7d49fa - languageName: node - linkType: hard - "npmlog@npm:^6.0.0": version: 6.0.2 resolution: "npmlog@npm:6.0.2" @@ -16152,7 +16265,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^6.0.0, semver@npm:^6.1.0, semver@npm:^6.3.0, semver@npm:^6.3.1": +"semver@npm:^6.1.0, semver@npm:^6.3.0, semver@npm:^6.3.1": version: 6.3.1 resolution: "semver@npm:6.3.1" bin: @@ -16383,7 +16496,7 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": +"signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" checksum: 10c0/25d272fa73e146048565e08f3309d5b942c1979a6f4a58a8c59d5fa299728e9c2fcd1a759ec870863b1fd38653670240cd420dad2ad9330c71f36608a6a1c912 @@ -18977,7 +19090,7 @@ __metadata: languageName: node linkType: hard -"wide-align@npm:^1.1.2, wide-align@npm:^1.1.5": +"wide-align@npm:^1.1.5": version: 1.1.5 resolution: "wide-align@npm:1.1.5" dependencies: