fix(passwords): use argon2id instead of bcrypt

OWASP [1] recommends for password hashing the following algorithms in
descending order: argon2id, scrypt, bcrypt. They state that bcrypt may
be used in legacy systems or when required due to legal regulations.
We're however not building any legacy application. Even HedgeDoc 1.x
utilizes a more modern algorithm by using scrypt.

While bcrypt is not insecure per se, our implementation had a major
security flaw, leading to invalid passwords being accepted in certain
cases. The bcrypt nodejs package - and the OWASP cheatsheet as well -
point out, that the maximum input length of passwords is limited to 72
bytes with bcrypt. When some user has a password longer than 72 bytes in
use, only the first 72 bytes are required to log in successfully.
Depending on the encoding (which could be UTF-8 or UTF-16 depending on
different circumstances) this could in worst-case be at 36 characters,
which is not very unusual for a password. See also [2].

This commit changes the used algorithm to argon2id. Argon2id has been in
use for several years now and seems to be a well-designed password
hashing function that even won the 2015 Password Hashing Competition.
Argon2 does not have any real-world max input length for passwords (it
is at 4 GiB).

The node-rs/argon2 implementation seems to be well maintained, widely
used (more than 150k downloads per week) and is published with
provenance, proving that the npm package was built on GitHub actions
using the source code in the repository. The implementation is written
in Rust, so it should be safe against memory leakages etc.

[1]: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Che
     at_Sheet.html#password-hashing-algorithms
[2]: https://security.stackexchange.com/a/39851

Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
Erik Michelson 2024-08-08 12:05:20 +02:00
parent 6684b0f886
commit f30f0d8e51
5 changed files with 271 additions and 125 deletions

View file

@ -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",

View file

@ -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.');

View file

@ -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(),
);
});
});

View file

@ -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<string> {
// 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<boolean> {
return await compare(cleartext, password);
return await verify(passwordHash, cleartext);
}
export function bufferToBase64Url(text: Buffer): string {

297
yarn.lock
View file

@ -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: