CVE-2023-38690: Improper Neutralization of Special Elements used in a Command ('Command Injection') (CWE-77)
value: matrix-appservice-irc is a Node.js IRC bridge for Matrix. Prior to version 1.0.1, it is possible to craft a command with newlines which would not be properly parsed. This would mean you could pass a string of commands as a channel name, which would then be run by the IRC bridge bot. Versions 1.0.1 and above are patched. There are no robust workarounds to the bug. One may disable dynamic channels in the config to disable the most common execution method but others may exist.
CWE description: The product constructs all or part of a command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended command when it is sent to a downstream component.
CVSS v3 base score: 9.8
Published: 2023-08-04T17:15Z
Repository: https://github.com/matrix-org/matrix-appservice-irc
Language: TypeScript
Files changed: .eslintrc, changelog.d/1753.bugfix, package.json, spec/e2e/pooling.spec.ts, spec/e2e/powerlevels.spec.ts, spec/integ/provisioning.spec.ts, spec/unit/pool-service/IrcClientRedisState.ts, src/irc/BridgedClient.ts, yarn.lock
Commit date: 2023-07-28 10:06:42 +0100
Commit message: Add a test to ensure that the irc client state doesn't bloat on VERSION (#1753)

* Add a test to ensure that the irc client state doesn't bloat on VERSION

* Sensible linting

* Update matrix-org-irc
Diff: diff --git a/.eslintrc b/.eslintrc
index f2e767aaf..f73baf08b 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -8,12 +8,11 @@
       "jsx": false
     }
   },
   "env": {
     "node": true,
-    "es6": true,
-    "jasmine": true
+    "es6": true
   },
   "extends": [
     "eslint:recommended",
     "plugin:@typescript-eslint/recommended"
   ],
@@ -100,10 +99,15 @@
   "overrides": [
     {
       "files": [
         "spec/**/*.js"
       ],
+      "env": {
+        "node": true,
+        "es6": true,
+        "jasmine": true
+      },
       "rules": {
         "@typescript-eslint/no-this-alias": "off", // I'm unsure if we should disallow this.
         "@typescript-eslint/no-empty-function": "off",
         "@typescript-eslint/no-var-requires": "off",
         "strict": "off",
@@ -111,9 +115,18 @@
         "@typescript-eslint/no-unused-vars": "off",
         "indent": "off",
         "prefer-const": "off",
         "no-var": "off",
       }
+    },
+    {
+      "extends": ["plugin:jest/recommended"],
+      "files": [
+        "spec/e2e/*.spec.ts"
+      ],
+      "rules": {
+        "jest/expect-expect": "off" // E2E tests don't always assert
+      }
     }
   ],
   "ignorePatterns": ["widget/**/*"]
 }
diff --git a/changelog.d/1753.bugfix b/changelog.d/1753.bugfix
new file mode 100644
index 000000000..27a3bca1b
--- /dev/null
+++ b/changelog.d/1753.bugfix
@@ -0,0 +1 @@
+Ensure we don't bloat irc supported state.
diff --git a/package.json b/package.json
index f69167e80..31ac3b2d7 100644
--- a/package.json
+++ b/package.json
@@ -15,11 +15,11 @@
     "build:app": "tsc --project ./tsconfig.json",
     "build:widget": "vite build --config widget/vite.config.ts",
     "dev:widget": "vite dev --config widget/vite.config.ts",
     "test": "ts-node --project spec/tsconfig.json node_modules/jasmine/bin/jasmine --stop-on-failure=true",
     "test:e2e": "jest --config spec/e2e/jest.config.js --forceExit",
-    "lint": "eslint -c .eslintrc --max-warnings 0 'spec/**/*.js' 'src/**/*.ts' && eslint -c ./widget/.eslintrc.js 'widget/src/**/*.{ts,tsx}'",
+    "lint": "eslint -c .eslintrc --max-warnings 0 'spec/**/*.js' 'spec/**/*.ts' 'src/**/*.ts' && eslint -c ./widget/.eslintrc.js 'widget/src/**/*.{ts,tsx}'",
     "check": "yarn test && yarn lint",
     "ci-test": "nyc --report text jasmine",
     "ci": "yarn lint && yarn ci-test"
   },
   "repository": {
@@ -43,21 +43,21 @@
     "he": "^1.2.0",
     "ioredis": "^5.3.1",
     "logform": "^2.4.2",
     "matrix-appservice-bridge": "^9.0.0",
     "matrix-bot-sdk": "npm:@vector-im/matrix-bot-sdk@^0.6.6-element.1",
-    "matrix-org-irc": "^2.0.1",
+    "matrix-org-irc": "^2.1.0",
     "matrix-widget-api": "^1.4.0",
     "nopt": "^6.0.0",
     "p-queue": "^6.6.2",
     "pg": "^8.8.0",
     "quick-lru": "^5.1.1",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
     "sanitize-html": "^2.7.2",
-    "typescript": "^5.0.4",
     "typed-emitter": "^2.1.0",
+    "typescript": "^5.0.4",
     "url-join": "^5.0.0",
     "winston": "^3.8.2",
     "winston-daily-rotate-file": "^4.7.1"
   },
   "devDependencies": {
@@ -80,10 +80,11 @@
     "@typescript-eslint/eslint-plugin": "^5.38.0",
     "@typescript-eslint/parser": "^5.38.0",
     "@vitejs/plugin-react": "^3.0.1",
     "autoprefixer": "^10.4.13",
     "eslint": "^8.24.0",
+    "eslint-plugin-jest": "^27.2.3",
     "eslint-plugin-react": "^7.32.2",
     "eslint-plugin-react-hooks": "^4.6.0",
     "homerunner-client": "^0.0.6",
     "jasmine": "^3.99.0",
     "jest": "^29.1.1",
diff --git a/spec/e2e/pooling.spec.ts b/spec/e2e/pooling.spec.ts
index c08ffd2d1..b601295bc 100644
--- a/spec/e2e/pooling.spec.ts
+++ b/spec/e2e/pooling.spec.ts
@@ -70,6 +70,33 @@ describeif('Connection pooling', () => {
         alice.sendText(cRoomId, "Hello bob!");
         await aliceMsg;
         bob.say(channel, "Hi alice!");
         await bobMsg;
     });
+
+    it('should store the IRC client state once', async () => {
+        const channel = `#${TestIrcServer.generateUniqueNick("test")}`;
+        const { homeserver, ircBridge } = testEnv;
+        const { client, userId } = homeserver.users[0];
+        const adminRoomId = await testEnv.createAdminRoomHelper(client);
+
+        // Ensure we join IRC.
+        const cRoomId = await testEnv.joinChannelHelper(client, adminRoomId, channel);
+        await client.sendText(cRoomId, "Hello bob!");
+
+
+        const bridgedClient = await ircBridge.getBridgedClient(ircBridge.getServers()[0], userId);
+        await bridgedClient.waitForConnected();
+        const ircClient = await bridgedClient.assertConnected();
+
+        // This is the original state of supported. We clone the object to be safe.
+        const expectedState = JSON.parse(JSON.stringify(ircClient.supported));
+
+        // Request VERSION to re-request state.
+        await bridgedClient.sendCommands('VERSION');
+        await new Promise<void>(resolve => setTimeout(resolve, 2000));
+        const newState = { ...ircClient.supported };
+
+        expect(expectedState).toEqual(newState);
+    });
+
 });
diff --git a/spec/e2e/powerlevels.spec.ts b/spec/e2e/powerlevels.spec.ts
index 2e4ae0009..aa82fc253 100644
--- a/spec/e2e/powerlevels.spec.ts
+++ b/spec/e2e/powerlevels.spec.ts
@@ -44,8 +44,8 @@ describe('Ensure powerlevels are appropriately applied', () => {
             },
         );
 
         await charlieJoinPromise;
         await bob.send('MODE', channel, '+o', charlie.nick);
-        await plEvent;
+        expect(await plEvent).toBeDefined();
     });
 });
diff --git a/spec/integ/provisioning.spec.ts b/spec/integ/provisioning.spec.ts
index 6999e72db..195d5bb69 100644
--- a/spec/integ/provisioning.spec.ts
+++ b/spec/integ/provisioning.spec.ts
@@ -1,5 +1,6 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
 import { ErrCode } from "matrix-appservice-bridge";
 
 import { defer } from "../../src/promiseutil";
 import envBundle from "../util/env-bundle";
 import {IrcErrCode, RequestLinkBody, UnlinkBody} from "../../src/provisioning/Schema";
@@ -135,11 +136,11 @@ describe("Provisioning API", function() {
         };
 
         let sentReply = false;
         if (shouldOpRespond) {
             // Listen for message from bot
-            env.ircMock._whenClient(config._server, config._botnick, "say", (self: any, message: any) => {
+            env.ircMock._whenClient(config._server, config._botnick, "say", (self: any) => {
                 // Say yes back to the bot
                 if (sentReply) {
                     return;
                 }
                 sentReply = true;
@@ -535,11 +536,11 @@ describe("Provisioning API", function() {
             let gotSayCall = false;
             env.ircMock._whenClient(
                 linkBody.remote_room_server,
                 nickForDisplayName,
                 "say",
-                (client, channel, text) => {
+                (client, channel) => {
                     expect(client.nick).toEqual(nickForDisplayName);
                     expect(client.addr).toEqual(linkBody.remote_room_server);
                     expect(channel).toEqual(linkBody.remote_room_channel);
                     gotSayCall = true;
                 },
@@ -581,11 +582,11 @@ describe("Provisioning API", function() {
             let countSays = 0;
             env.ircMock._whenClient(
                 linkBody.remote_room_server,
                 nickForDisplayName,
                 "say",
-                (client, channel, text) => {
+                (client, channel) => {
                     expect(client.nick).toEqual(nickForDisplayName);
                     expect(client.addr).toEqual(linkBody.remote_room_server);
                     expect(channel).toEqual(linkBody.remote_room_channel);
                     countSays++;
                 },
diff --git a/spec/unit/pool-service/IrcClientRedisState.ts b/spec/unit/pool-service/IrcClientRedisState.ts
index 7c44e594f..e6e4d14e7 100644
--- a/spec/unit/pool-service/IrcClientRedisState.ts
+++ b/spec/unit/pool-service/IrcClientRedisState.ts
@@ -1,10 +1,11 @@
 import { DefaultIrcSupported } from "matrix-org-irc";
 import { IrcClientRedisState, IrcClientStateDehydrated } from "../../../src/pool-service/IrcClientRedisState";
 
 const userId = "@foo:bar";
 
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
 function fakeRedis(existingData: string|null = null): any {
     return {
         async hget(key, clientId) {
             if (clientId !== userId) {
                 throw Error('Wrong user!');
diff --git a/src/irc/BridgedClient.ts b/src/irc/BridgedClient.ts
index 7b9dca555..96bbce312 100644
--- a/src/irc/BridgedClient.ts
+++ b/src/irc/BridgedClient.ts
@@ -1167,11 +1167,11 @@ export class BridgedClient extends EventEmitter {
             throw Error('Client is not connecting or connected');
         }
         return this.connectDefer.promise;
     }
 
-    private assertConnected(): Client {
+    public assertConnected(): Client {
         if (this.state.status !== BridgedClientStatus.CONNECTED) {
             throw Error('Client is not connected');
         }
         return this.state.client as Client;
     }
diff --git a/yarn.lock b/yarn.lock
index b7b29e7f0..f49997cef 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1200,10 +1200,18 @@
   integrity sha512-mau0waO5frJctPuAzcxiNWqJR5Z8V0190FTSqRw1Q4Euop6+zTwHAf8YIXNwDOT29tyUDrQ65jSg9aTU/H0omA==
   dependencies:
     "@typescript-eslint/types" "5.59.1"
     "@typescript-eslint/visitor-keys" "5.59.1"
 
+"@typescript-eslint/scope-manager@5.62.0":
+  version "5.62.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c"
+  integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==
+  dependencies:
+    "@typescript-eslint/types" "5.62.0"
+    "@typescript-eslint/visitor-keys" "5.62.0"
+
 "@typescript-eslint/type-utils@5.59.1":
   version "5.59.1"
   resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.1.tgz#63981d61684fd24eda2f9f08c0a47ecb000a2111"
   integrity sha512-ZMWQ+Oh82jWqWzvM3xU+9y5U7MEMVv6GLioM3R5NJk6uvP47kZ7YvlgSHJ7ERD6bOY7Q4uxWm25c76HKEwIjZw==
   dependencies:
@@ -1215,10 +1223,15 @@
 "@typescript-eslint/types@5.59.1":
   version "5.59.1"
   resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.1.tgz#03f3fedd1c044cb336ebc34cc7855f121991f41d"
   integrity sha512-dg0ICB+RZwHlysIy/Dh1SP+gnXNzwd/KS0JprD3Lmgmdq+dJAJnUPe1gNG34p0U19HvRlGX733d/KqscrGC1Pg==
 
+"@typescript-eslint/types@5.62.0":
+  version "5.62.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f"
+  integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==
+
 "@typescript-eslint/typescript-estree@5.59.1":
   version "5.59.1"
   resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.1.tgz#4aa546d27fd0d477c618f0ca00b483f0ec84c43c"
   integrity sha512-lYLBBOCsFltFy7XVqzX0Ju+Lh3WPIAWxYpmH/Q7ZoqzbscLiCW00LeYCdsUnnfnj29/s1WovXKh2gwCoinHNGA==
   dependencies:
@@ -1228,10 +1241,23 @@
     globby "^11.1.0"
     is-glob "^4.0.3"
     semver "^7.3.7"
     tsutils "^3.21.0"
 
+"@typescript-eslint/typescript-estree@5.62.0":
+  version "5.62.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b"
+  integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==
+  dependencies:
+    "@typescript-eslint/types" "5.62.0"
+    "@typescript-eslint/visitor-keys" "5.62.0"
+    debug "^4.3.4"
+    globby "^11.1.0"
+    is-glob "^4.0.3"
+    semver "^7.3.7"
+    tsutils "^3.21.0"
+
 "@typescript-eslint/utils@5.59.1":
   version "5.59.1"
   resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.1.tgz#d89fc758ad23d2157cfae53f0b429bdf15db9473"
   integrity sha512-MkTe7FE+K1/GxZkP5gRj3rCztg45bEhsd8HYjczBuYm+qFHP5vtZmjx3B0yUCDotceQ4sHgTyz60Ycl225njmA==
   dependencies:
@@ -1242,18 +1268,40 @@
     "@typescript-eslint/types" "5.59.1"
     "@typescript-eslint/typescript-estree" "5.59.1"
     eslint-scope "^5.1.1"
     semver "^7.3.7"
 
+"@typescript-eslint/utils@^5.10.0":
+  version "5.62.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86"
+  integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==
+  dependencies:
+    "@eslint-community/eslint-utils" "^4.2.0"
+    "@types/json-schema" "^7.0.9"
+    "@types/semver" "^7.3.12"
+    "@typescript-eslint/scope-manager" "5.62.0"
+    "@typescript-eslint/types" "5.62.0"
+    "@typescript-eslint/typescript-estree" "5.62.0"
+    eslint-scope "^5.1.1"
+    semver "^7.3.7"
+
 "@typescript-eslint/visitor-keys@5.59.1":
   version "5.59.1"
   resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.1.tgz#0d96c36efb6560d7fb8eb85de10442c10d8f6058"
   integrity sha512-6waEYwBTCWryx0VJmP7JaM4FpipLsFl9CvYf2foAE8Qh/Y0s+bxWysciwOs0LTBED4JCaNxTZ5rGadB14M6dwA==
   dependencies:
     "@typescript-eslint/types" "5.59.1"
     eslint-visitor-keys "^3.3.0"
 
+"@typescript-eslint/visitor-keys@5.62.0":
+  version "5.62.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e"
+  integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==
+  dependencies:
+    "@typescript-eslint/types" "5.62.0"
+    eslint-visitor-keys "^3.3.0"
+
 "@vitejs/plugin-react@^3.0.1":
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-3.1.0.tgz#d1091f535eab8b83d6e74034d01e27d73c773240"
   integrity sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==
   dependencies:
@@ -2376,10 +2424,17 @@ escape-string-regexp@^2.0.0:
 escape-string-regexp@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
   integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
 
+eslint-plugin-jest@^27.2.3:
+  version "27.2.3"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-27.2.3.tgz#6f8a4bb2ca82c0c5d481d1b3be256ab001f5a3ec"
+  integrity sha512-sRLlSCpICzWuje66Gl9zvdF6mwD5X86I4u55hJyFBsxYOsBCmT5+kSUjf+fkFWVMMgpzNEupjW8WzUqi83hJAQ==
+  dependencies:
+    "@typescript-eslint/utils" "^5.10.0"
+
 eslint-plugin-react-hooks@^4.6.0:
   version "4.6.0"
   resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3"
   integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==
 
@@ -4247,14 +4302,14 @@ matrix-appservice@^2.0.0:
     morgan "^1.10.0"
     request "^2.88.2"
     request-promise "^4.2.6"
     sanitize-html "^2.8.0"
 
-matrix-org-irc@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/matrix-org-irc/-/matrix-org-irc-2.0.1.tgz#80cc1ce3abb3e8f240bbc3b4acf1a0fe0f1057e7"
-  integrity sha512-Qnzx1r5QjTtm63oGUY/XyE3t+1xH43CaC8r3QifkKmBihrYyhHq4bpMsnxNYb558sc2j52pixJ5CUsQ8zMediQ==
+matrix-org-irc@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/matrix-org-irc/-/matrix-org-irc-2.1.0.tgz#513d284d081a01ef752a29cd410c11fce0c5d2c5"
+  integrity sha512-MV9Q8Mt8RTKf72U0D5zNbBL9P4JQJzU63HTeomguq6a+Zb5QZJvnBHfrdLJXtJyTsWGCtSbJ6rcSFJVrFmKUxA==
   dependencies:
     chardet "^1.5.1"
     iconv-lite "^0.6.3"
     typed-emitter "^2.1.0"
     utf-8-validate "^6.0.3"

Vulnerable code:     "es6": true,
    "jasmine": true
    "lint": "eslint -c .eslintrc --max-warnings 0 'spec/**/*.js' 'src/**/*.ts' && eslint -c ./widget/.eslintrc.js 'widget/src/**/*.{ts,tsx}'",
    "matrix-org-irc": "^2.0.1",
    "typescript": "^5.0.4",
        await plEvent;
            env.ircMock._whenClient(config._server, config._botnick, "say", (self: any, message: any) => {
                (client, channel, text) => {
                (client, channel, text) => {
    private assertConnected(): Client {
matrix-org-irc@^2.0.1:
  version "2.0.1"
  resolved "https://registry.yarnpkg.com/matrix-org-irc/-/matrix-org-irc-2.0.1.tgz#80cc1ce3abb3e8f240bbc3b4acf1a0fe0f1057e7"
  integrity sha512-Qnzx1r5QjTtm63oGUY/XyE3t+1xH43CaC8r3QifkKmBihrYyhHq4bpMsnxNYb558sc2j52pixJ5CUsQ8zMediQ==
Fixed code:     "es6": true
      "env": {
        "node": true,
        "es6": true,
        "jasmine": true
      },
    },
    {
      "extends": ["plugin:jest/recommended"],
      "files": [
        "spec/e2e/*.spec.ts"
      ],
      "rules": {
        "jest/expect-expect": "off" // E2E tests don't always assert
      }
Ensure we don't bloat irc supported state.
    "lint": "eslint -c .eslintrc --max-warnings 0 'spec/**/*.js' 'spec/**/*.ts' 'src/**/*.ts' && eslint -c ./widget/.eslintrc.js 'widget/src/**/*.{ts,tsx}'",
    "matrix-org-irc": "^2.1.0",
    "typescript": "^5.0.4",
    "eslint-plugin-jest": "^27.2.3",

    it('should store the IRC client state once', async () => {
        const channel = `#${TestIrcServer.generateUniqueNick("test")}`;
        const { homeserver, ircBridge } = testEnv;
        const { client, userId } = homeserver.users[0];
        const adminRoomId = await testEnv.createAdminRoomHelper(client);

        // Ensure we join IRC.
        const cRoomId = await testEnv.joinChannelHelper(client, adminRoomId, channel);
        await client.sendText(cRoomId, "Hello bob!");


        const bridgedClient = await ircBridge.getBridgedClient(ircBridge.getServers()[0], userId);
        await bridgedClient.waitForConnected();
        const ircClient = await bridgedClient.assertConnected();

        // This is the original state of supported. We clone the object to be safe.
        const expectedState = JSON.parse(JSON.stringify(ircClient.supported));

        // Request VERSION to re-request state.
        await bridgedClient.sendCommands('VERSION');
        await new Promise<void>(resolve => setTimeout(resolve, 2000));
        const newState = { ...ircClient.supported };

        expect(expectedState).toEqual(newState);
    });

        expect(await plEvent).toBeDefined();
/* eslint-disable @typescript-eslint/no-explicit-any */
            env.ircMock._whenClient(config._server, config._botnick, "say", (self: any) => {
                (client, channel) => {
                (client, channel) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
    public assertConnected(): Client {
"@typescript-eslint/scope-manager@5.62.0":
  version "5.62.0"
  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c"
  integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==
  dependencies:
    "@typescript-eslint/types" "5.62.0"
    "@typescript-eslint/visitor-keys" "5.62.0"

"@typescript-eslint/types@5.62.0":
  version "5.62.0"
  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f"
  integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==

"@typescript-eslint/typescript-estree@5.62.0":
  version "5.62.0"
  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b"
  integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==
  dependencies:
    "@typescript-eslint/types" "5.62.0"
    "@typescript-eslint/visitor-keys" "5.62.0"
    debug "^4.3.4"
    globby "^11.1.0"
    is-glob "^4.0.3"
    semver "^7.3.7"
    tsutils "^3.21.0"

"@typescript-eslint/utils@^5.10.0":
  version "5.62.0"
  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86"
  integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==
  dependencies:
    "@eslint-community/eslint-utils" "^4.2.0"
    "@types/json-schema" "^7.0.9"
    "@types/semver" "^7.3.12"
    "@typescript-eslint/scope-manager" "5.62.0"
    "@typescript-eslint/types" "5.62.0"
    "@typescript-eslint/typescript-estree" "5.62.0"
    eslint-scope "^5.1.1"
    semver "^7.3.7"

"@typescript-eslint/visitor-keys@5.62.0":
  version "5.62.0"
  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e"
  integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==
  dependencies:
    "@typescript-eslint/types" "5.62.0"
    eslint-visitor-keys "^3.3.0"

eslint-plugin-jest@^27.2.3:
  version "27.2.3"
  resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-27.2.3.tgz#6f8a4bb2ca82c0c5d481d1b3be256ab001f5a3ec"
  integrity sha512-sRLlSCpICzWuje66Gl9zvdF6mwD5X86I4u55hJyFBsxYOsBCmT5+kSUjf+fkFWVMMgpzNEupjW8WzUqi83hJAQ==
  dependencies:
    "@typescript-eslint/utils" "^5.10.0"

matrix-org-irc@^2.1.0:
  version "2.1.0"
  resolved "https://registry.yarnpkg.com/matrix-org-irc/-/matrix-org-irc-2.1.0.tgz#513d284d081a01ef752a29cd410c11fce0c5d2c5"
  integrity sha512-MV9Q8Mt8RTKf72U0D5zNbBL9P4JQJzU63HTeomguq6a+Zb5QZJvnBHfrdLJXtJyTsWGCtSbJ6rcSFJVrFmKUxA==