(y-provider) check hocuspocus documentName validity

We only use uuid v4 as hocuspocus dicument name. To be sure nothing else
is used we check that the documentName is a valid uuid version 4.
This commit is contained in:
Manuel Raynaud
2025-03-27 15:50:27 +01:00
parent 8bee476b5b
commit 7e1eed3abd
4 changed files with 134 additions and 40 deletions

View File

@@ -2,6 +2,7 @@ import {
HocuspocusProvider, HocuspocusProvider,
HocuspocusProviderWebsocket, HocuspocusProviderWebsocket,
} from '@hocuspocus/provider'; } from '@hocuspocus/provider';
import { v1 as uuidv1, v4 as uuidv4 } from 'uuid';
import WebSocket from 'ws'; import WebSocket from 'ws';
const port = 5559; const port = 5559;
@@ -52,9 +53,9 @@ describe('Server Tests', () => {
test('WebSocket connection with bad origin should be closed', () => { test('WebSocket connection with bad origin should be closed', () => {
const { promise, done } = promiseDone(); const { promise, done } = promiseDone();
const room = uuidv4();
const ws = new WebSocket( const ws = new WebSocket(
`ws://localhost:${port}/collaboration/ws/?room=test-room`, `ws://localhost:${port}/collaboration/ws/?room=${room}`,
{ {
headers: { headers: {
Origin: 'http://bad-origin.com', Origin: 'http://bad-origin.com',
@@ -72,9 +73,9 @@ describe('Server Tests', () => {
test('WebSocket connection without cookies header should be closed', () => { test('WebSocket connection without cookies header should be closed', () => {
const { promise, done } = promiseDone(); const { promise, done } = promiseDone();
const room = uuidv4();
const ws = new WebSocket( const ws = new WebSocket(
`ws://localhost:${port}/collaboration/ws/?room=test-room`, `ws://localhost:${port}/collaboration/ws/?room=${room}`,
{ {
headers: { headers: {
Origin: origin, Origin: origin,
@@ -92,9 +93,46 @@ describe('Server Tests', () => {
test('WebSocket connection not allowed if room not matching provider name', () => { test('WebSocket connection not allowed if room not matching provider name', () => {
const { promise, done } = promiseDone(); const { promise, done } = promiseDone();
const room = uuidv4();
const wsHocus = new HocuspocusProviderWebsocket({ const wsHocus = new HocuspocusProviderWebsocket({
url: `ws://localhost:${portWS}/?room=my-test`, url: `ws://localhost:${portWS}/?room=${room}`,
WebSocketPolyfill: WebSocket,
maxAttempts: 1,
quiet: true,
});
const providerName = uuidv4();
const provider = new HocuspocusProvider({
websocketProvider: wsHocus,
name: providerName,
broadcast: false,
quiet: true,
preserveConnection: false,
onClose: (data) => {
expect(console.error).toHaveBeenCalledWith(
'Invalid room name - Probable hacking attempt:',
providerName,
room,
);
wsHocus.stopConnectionAttempt();
expect(data.event.reason).toBe('Forbidden');
wsHocus.webSocket?.close();
wsHocus.disconnect();
provider.destroy();
wsHocus.destroy();
done();
},
});
return promise;
});
test('WebSocket connection not allowed if room is not a valid uuid v4', () => {
const { promise, done } = promiseDone();
const room = uuidv1();
const wsHocus = new HocuspocusProviderWebsocket({
url: `ws://localhost:${portWS}/?room=${room}`,
WebSocketPolyfill: WebSocket, WebSocketPolyfill: WebSocket,
maxAttempts: 1, maxAttempts: 1,
quiet: true, quiet: true,
@@ -102,15 +140,49 @@ describe('Server Tests', () => {
const provider = new HocuspocusProvider({ const provider = new HocuspocusProvider({
websocketProvider: wsHocus, websocketProvider: wsHocus,
name: 'hocuspocus-test', name: room,
broadcast: false, broadcast: false,
quiet: true, quiet: true,
preserveConnection: false, preserveConnection: false,
onClose: (data) => { onClose: (data) => {
expect(console.error).toHaveBeenCalledWith( expect(console.error).toHaveBeenCalledWith(
'Invalid room name - Probable hacking attempt:', 'Room name is not a valid uuid:',
'hocuspocus-test', room,
'my-test', );
wsHocus.stopConnectionAttempt();
expect(data.event.reason).toBe('Forbidden');
wsHocus.webSocket?.close();
wsHocus.disconnect();
provider.destroy();
wsHocus.destroy();
done();
},
});
return promise;
});
test('WebSocket connection not allowed if room is not a valid uuid', () => {
const { promise, done } = promiseDone();
const room = 'not-a-valid-uuid';
const wsHocus = new HocuspocusProviderWebsocket({
url: `ws://localhost:${portWS}/?room=${room}`,
WebSocketPolyfill: WebSocket,
maxAttempts: 1,
quiet: true,
});
const provider = new HocuspocusProvider({
websocketProvider: wsHocus,
name: room,
broadcast: false,
quiet: true,
preserveConnection: false,
onClose: (data) => {
expect(console.error).toHaveBeenCalledWith(
'Room name is not a valid uuid:',
room,
); );
wsHocus.stopConnectionAttempt(); wsHocus.stopConnectionAttempt();
@@ -131,8 +203,9 @@ describe('Server Tests', () => {
mockDocFetch.mockRejectedValue(''); mockDocFetch.mockRejectedValue('');
const room = uuidv4();
const wsHocus = new HocuspocusProviderWebsocket({ const wsHocus = new HocuspocusProviderWebsocket({
url: `ws://localhost:${portWS}/?room=my-test`, url: `ws://localhost:${portWS}/?room=${room}`,
WebSocketPolyfill: WebSocket, WebSocketPolyfill: WebSocket,
maxAttempts: 1, maxAttempts: 1,
quiet: true, quiet: true,
@@ -140,7 +213,7 @@ describe('Server Tests', () => {
const provider = new HocuspocusProvider({ const provider = new HocuspocusProvider({
websocketProvider: wsHocus, websocketProvider: wsHocus,
name: 'my-test', name: room,
broadcast: false, broadcast: false,
quiet: true, quiet: true,
preserveConnection: false, preserveConnection: false,
@@ -167,6 +240,7 @@ describe('Server Tests', () => {
test('WebSocket connection fails if user do not have correct retrieve ability', () => { test('WebSocket connection fails if user do not have correct retrieve ability', () => {
const { promise, done } = promiseDone(); const { promise, done } = promiseDone();
const room = uuidv4();
mockDocFetch.mockResolvedValue({ mockDocFetch.mockResolvedValue({
abilities: { abilities: {
retrieve: false, retrieve: false,
@@ -174,7 +248,7 @@ describe('Server Tests', () => {
}); });
const wsHocus = new HocuspocusProviderWebsocket({ const wsHocus = new HocuspocusProviderWebsocket({
url: `ws://localhost:${portWS}/?room=my-test`, url: `ws://localhost:${portWS}/?room=${room}`,
WebSocketPolyfill: WebSocket, WebSocketPolyfill: WebSocket,
maxAttempts: 1, maxAttempts: 1,
quiet: true, quiet: true,
@@ -182,14 +256,14 @@ describe('Server Tests', () => {
const provider = new HocuspocusProvider({ const provider = new HocuspocusProvider({
websocketProvider: wsHocus, websocketProvider: wsHocus,
name: 'my-test', name: room,
broadcast: false, broadcast: false,
quiet: true, quiet: true,
preserveConnection: false, preserveConnection: false,
onClose: (data) => { onClose: (data) => {
expect(console.error).toHaveBeenCalledWith( expect(console.error).toHaveBeenCalledWith(
'onConnect: Unauthorized to retrieve this document', 'onConnect: Unauthorized to retrieve this document',
'my-test', room,
); );
wsHocus.stopConnectionAttempt(); wsHocus.stopConnectionAttempt();
@@ -217,19 +291,20 @@ describe('Server Tests', () => {
}, },
}); });
const room = uuidv4();
const wsHocus = new HocuspocusProviderWebsocket({ const wsHocus = new HocuspocusProviderWebsocket({
url: `ws://localhost:${portWS}/?room=hocuspocus-test`, url: `ws://localhost:${portWS}/?room=${room}`,
WebSocketPolyfill: WebSocket, WebSocketPolyfill: WebSocket,
}); });
const provider = new HocuspocusProvider({ const provider = new HocuspocusProvider({
websocketProvider: wsHocus, websocketProvider: wsHocus,
name: 'hocuspocus-test', name: room,
broadcast: false, broadcast: false,
quiet: true, quiet: true,
onConnect: () => { onConnect: () => {
void hocusPocusServer void hocusPocusServer
.openDirectConnection('hocuspocus-test') .openDirectConnection(room)
.then((connection) => { .then((connection) => {
connection.document?.getConnections().forEach((connection) => { connection.document?.getConnections().forEach((connection) => {
expect(connection.readOnly).toBe(!canEdit); expect(connection.readOnly).toBe(!canEdit);
@@ -262,29 +337,28 @@ describe('Server Tests', () => {
id: 'test-user-id', id: 'test-user-id',
}); });
const room = uuidv4();
const wsHocus = new HocuspocusProviderWebsocket({ const wsHocus = new HocuspocusProviderWebsocket({
url: `ws://localhost:${portWS}/?room=hocuspocus-test`, url: `ws://localhost:${portWS}/?room=${room}`,
WebSocketPolyfill: WebSocket, WebSocketPolyfill: WebSocket,
}); });
const provider = new HocuspocusProvider({ const provider = new HocuspocusProvider({
websocketProvider: wsHocus, websocketProvider: wsHocus,
name: 'hocuspocus-test', name: room,
broadcast: false, broadcast: false,
quiet: true, quiet: true,
onConnect: () => { onConnect: () => {
void hocusPocusServer void hocusPocusServer.openDirectConnection(room).then((connection) => {
.openDirectConnection('hocuspocus-test') connection.document?.getConnections().forEach((connection) => {
.then((connection) => { expect(connection.context.userId).toBe('test-user-id');
connection.document?.getConnections().forEach((connection) => {
expect(connection.context.userId).toBe('test-user-id');
});
void connection.disconnect();
provider.destroy();
wsHocus.destroy();
done();
}); });
void connection.disconnect();
provider.destroy();
wsHocus.destroy();
done();
});
}, },
}); });

View File

@@ -24,6 +24,7 @@
"cors": "2.8.5", "cors": "2.8.5",
"express": "4.21.2", "express": "4.21.2",
"express-ws": "5.0.2", "express-ws": "5.0.2",
"uuid": "11.1.0",
"y-protocols": "1.0.6", "y-protocols": "1.0.6",
"yjs": "*" "yjs": "*"
}, },

View File

@@ -1,4 +1,5 @@
import { Server } from '@hocuspocus/server'; import { Server } from '@hocuspocus/server';
import { validate as uuidValidate, version as uuidVersion } from 'uuid';
import { fetchDocument } from '@/api/getDoc'; import { fetchDocument } from '@/api/getDoc';
import { getMe } from '@/api/getMe'; import { getMe } from '@/api/getMe';
@@ -27,6 +28,12 @@ export const hocusPocusServer = Server.configure({
return Promise.reject(new Error('Wrong room name: Unauthorized')); return Promise.reject(new Error('Wrong room name: Unauthorized'));
} }
if (!uuidValidate(documentName) || uuidVersion(documentName) !== 4) {
console.error('Room name is not a valid uuid:', documentName);
return Promise.reject(new Error('Wrong room name: Unauthorized'));
}
let can_edit = false; let can_edit = false;
try { try {
@@ -35,7 +42,7 @@ export const hocusPocusServer = Server.configure({
if (!document.abilities.retrieve) { if (!document.abilities.retrieve) {
console.error( console.error(
'onConnect: Unauthorized to retrieve this document', 'onConnect: Unauthorized to retrieve this document',
roomParam, documentName,
); );
return Promise.reject(new Error('Wrong abilities:Unauthorized')); return Promise.reject(new Error('Wrong abilities:Unauthorized'));
} }

View File

@@ -6334,13 +6334,20 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/node@*", "@types/node@22.10.7", "@types/node@22.13.9", "@types/node@^22.7.5": "@types/node@*", "@types/node@^22.7.5":
version "22.13.9" version "22.13.9"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.9.tgz#5d9a8f7a975a5bd3ef267352deb96fb13ec02eca" resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.9.tgz#5d9a8f7a975a5bd3ef267352deb96fb13ec02eca"
integrity sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw== integrity sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw==
dependencies: dependencies:
undici-types "~6.20.0" undici-types "~6.20.0"
"@types/node@22.10.7":
version "22.10.7"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.7.tgz#14a1ca33fd0ebdd9d63593ed8d3fbc882a6d28d7"
integrity sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==
dependencies:
undici-types "~6.20.0"
"@types/parse-json@^4.0.0": "@types/parse-json@^4.0.0":
version "4.0.2" version "4.0.2"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239"
@@ -6396,7 +6403,7 @@
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb"
integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==
"@types/react-dom@*", "@types/react-dom@19.0.0": "@types/react-dom@*":
version "19.0.0" version "19.0.0"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.0.0.tgz#e7f5d618a080486eaf9952246dbf59eaa2c64130" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.0.0.tgz#e7f5d618a080486eaf9952246dbf59eaa2c64130"
integrity sha512-1KfiQKsH1o00p9m5ag12axHQSb3FOU9H20UTrujVSkNhuCrRHiQWFqgEnTNK5ZNfnzZv8UWrnXVqCmCF9fgY3w== integrity sha512-1KfiQKsH1o00p9m5ag12axHQSb3FOU9H20UTrujVSkNhuCrRHiQWFqgEnTNK5ZNfnzZv8UWrnXVqCmCF9fgY3w==
@@ -6415,7 +6422,7 @@
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.12.tgz#b5d76568485b02a307238270bfe96cb51ee2a044" resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.12.tgz#b5d76568485b02a307238270bfe96cb51ee2a044"
integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w== integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==
"@types/react@*", "@types/react@19.0.0": "@types/react@*":
version "19.0.0" version "19.0.0"
resolved "https://registry.yarnpkg.com/@types/react/-/react-19.0.0.tgz#fbbb53ce223f4e2b750ad5dd09580b2c43522bbf" resolved "https://registry.yarnpkg.com/@types/react/-/react-19.0.0.tgz#fbbb53ce223f4e2b750ad5dd09580b2c43522bbf"
integrity sha512-MY3oPudxvMYyesqs/kW1Bh8y9VqSmf+tzqw3ae8a9DZW68pUe3zAdHeI1jc6iAysuRdACnVknHP8AhwD4/dxtg== integrity sha512-MY3oPudxvMYyesqs/kW1Bh8y9VqSmf+tzqw3ae8a9DZW68pUe3zAdHeI1jc6iAysuRdACnVknHP8AhwD4/dxtg==
@@ -6533,7 +6540,7 @@
dependencies: dependencies:
"@types/yargs-parser" "*" "@types/yargs-parser" "*"
"@typescript-eslint/eslint-plugin@*", "@typescript-eslint/eslint-plugin@8.26.0", "@typescript-eslint/eslint-plugin@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0": "@typescript-eslint/eslint-plugin@*", "@typescript-eslint/eslint-plugin@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
version "8.26.0" version "8.26.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.0.tgz#7e880faf91f89471c30c141951e15f0eb3a0599e" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.0.tgz#7e880faf91f89471c30c141951e15f0eb3a0599e"
integrity sha512-cLr1J6pe56zjKYajK6SSSre6nl1Gj6xDp1TY0trpgPzjVbgDwd09v2Ws37LABxzkicmUjhEeg/fAUjPJJB1v5Q== integrity sha512-cLr1J6pe56zjKYajK6SSSre6nl1Gj6xDp1TY0trpgPzjVbgDwd09v2Ws37LABxzkicmUjhEeg/fAUjPJJB1v5Q==
@@ -6548,7 +6555,7 @@
natural-compare "^1.4.0" natural-compare "^1.4.0"
ts-api-utils "^2.0.1" ts-api-utils "^2.0.1"
"@typescript-eslint/parser@*", "@typescript-eslint/parser@8.26.0", "@typescript-eslint/parser@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0": "@typescript-eslint/parser@*", "@typescript-eslint/parser@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0":
version "8.26.0" version "8.26.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.26.0.tgz#9b4d2198e89f64fb81e83167eedd89a827d843a9" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.26.0.tgz#9b4d2198e89f64fb81e83167eedd89a827d843a9"
integrity sha512-mNtXP9LTVBy14ZF3o7JG69gRPBK/2QWtQd0j0oH26HcY/foyJJau6pNUez7QrM5UHnSvwlQcJXKsk0I99B9pOA== integrity sha512-mNtXP9LTVBy14ZF3o7JG69gRPBK/2QWtQd0j0oH26HcY/foyJJau6pNUez7QrM5UHnSvwlQcJXKsk0I99B9pOA==
@@ -8793,7 +8800,7 @@ eslint-visitor-keys@^4.2.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45"
integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==
eslint@*, eslint@8.57.0: eslint@*:
version "8.57.0" version "8.57.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668"
integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==
@@ -15086,7 +15093,7 @@ typed-array-length@^1.0.7:
possible-typed-array-names "^1.0.0" possible-typed-array-names "^1.0.0"
reflect.getprototypeof "^1.0.6" reflect.getprototypeof "^1.0.6"
typescript@*, typescript@5.8.2, typescript@^5.0.4: typescript@*, typescript@^5.0.4:
version "5.8.2" version "5.8.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.2.tgz#8170b3702f74b79db2e5a96207c15e65807999e4" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.2.tgz#8170b3702f74b79db2e5a96207c15e65807999e4"
integrity sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ== integrity sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==
@@ -15387,6 +15394,11 @@ utils-merge@1.0.1:
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
uuid@11.1.0:
version "11.1.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.1.0.tgz#9549028be1753bb934fc96e2bca09bb4105ae912"
integrity sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==
uuid@^11.0.3: uuid@^11.0.3:
version "11.0.5" version "11.0.5"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.0.5.tgz#07b46bdfa6310c92c3fb3953a8720f170427fc62" resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.0.5.tgz#07b46bdfa6310c92c3fb3953a8720f170427fc62"
@@ -16103,7 +16115,7 @@ yargs@17.7.2, yargs@^17.3.1:
y18n "^5.0.5" y18n "^5.0.5"
yargs-parser "^21.1.1" yargs-parser "^21.1.1"
yjs@*, yjs@13.6.23, yjs@^13.6.15: yjs@*, yjs@^13.6.15:
version "13.6.23" version "13.6.23"
resolved "https://registry.yarnpkg.com/yjs/-/yjs-13.6.23.tgz#62358dfa52e92dc870b8a0bedcf0d4cbd4c5ffa8" resolved "https://registry.yarnpkg.com/yjs/-/yjs-13.6.23.tgz#62358dfa52e92dc870b8a0bedcf0d4cbd4c5ffa8"
integrity sha512-ExtnT5WIOVpkL56bhLeisG/N5c4fmzKn4k0ROVfJa5TY2QHbH7F0Wu2T5ZhR7ErsFWQEFafyrnSI8TPKVF9Few== integrity sha512-ExtnT5WIOVpkL56bhLeisG/N5c4fmzKn4k0ROVfJa5TY2QHbH7F0Wu2T5ZhR7ErsFWQEFafyrnSI8TPKVF9Few==