matrix-appservice-irc IRC command injection via admin commands containing newlines
Description
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.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
CVE-2023-38690 allows command injection via crafted newlines in channel names in Matrix IRC bridge prior to 1.0.1, enabling arbitrary IRC bot command execution.
Root
Cause
matrix-appservice-irc, a Node.js IRC bridge for Matrix, prior to version 1.0.1 contained a parsing vulnerability in its IRC command handling. Specifically, it was possible to craft input containing newline characters that the parser did not properly sanitize, allowing multiple commands to be embedded within what was expected to be a single channel name [1].
Exploitation
An attacker could send a specially crafted message to a connected Matrix room, causing the bridge to interpret the injected newline-delimited IRC commands as part of the channel name. This would then be executed by the IRC bridge bot with the bot's privileges [1]. The primary attack vector involved dynamic channels, though the advisory notes that disabling dynamic channels may only block the most common exploitation method and not fully close all avenues [1].
Impact
Successful exploitation allows an unauthenticated attacker to execute arbitrary IRC commands through the bridge bot, potentially enabling actions such as joining/parting channels, sending messages, or conducting further reconnaissance of the bridged IRC network. The severity is rated as high (CVSS 4.0: 8.7) due to the low complexity and lack of required privileges [1].
Mitigation
The vulnerability is fixed in version 1.0.1, released on 2023-07-31, which includes improvements to command parsing and trimming [2][4]. Users are strongly recommended to upgrade immediately, as no robust workarounds exist [1].
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
matrix-appservice-ircnpm | < 1.0.1 | 1.0.1 |
Affected products
2- matrix-org/matrix-appservice-ircv5Range: < 1.0.1
Patches
10afb064635d3Add a test to ensure that the irc client state doesn't bloat on VERSION (#1753)
9 files changed · +113 −14
changelog.d/1753.bugfix+1 −0 added@@ -0,0 +1 @@ +Ensure we don't bloat irc supported state.
.eslintrc+15 −2 modified@@ -10,8 +10,7 @@ }, "env": { "node": true, - "es6": true, - "jasmine": true + "es6": true }, "extends": [ "eslint:recommended", @@ -102,6 +101,11 @@ "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", @@ -113,6 +117,15 @@ "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/**/*"]
package.json+4 −3 modified@@ -17,7 +17,7 @@ "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" @@ -45,7 +45,7 @@ "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", @@ -54,8 +54,8 @@ "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" @@ -82,6 +82,7 @@ "@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",
spec/e2e/pooling.spec.ts+27 −0 modified@@ -72,4 +72,31 @@ describeif('Connection pooling', () => { 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); + }); + });
spec/e2e/powerlevels.spec.ts+1 −1 modified@@ -46,6 +46,6 @@ describe('Ensure powerlevels are appropriately applied', () => { await charlieJoinPromise; await bob.send('MODE', channel, '+o', charlie.nick); - await plEvent; + expect(await plEvent).toBeDefined(); }); });
spec/integ/provisioning.spec.ts+4 −3 modified@@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { ErrCode } from "matrix-appservice-bridge"; import { defer } from "../../src/promiseutil"; @@ -137,7 +138,7 @@ 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; @@ -537,7 +538,7 @@ describe("Provisioning API", function() { 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); @@ -583,7 +584,7 @@ describe("Provisioning API", function() { 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);
spec/unit/pool-service/IrcClientRedisState.ts+1 −0 modified@@ -3,6 +3,7 @@ import { IrcClientRedisState, IrcClientStateDehydrated } from "../../../src/pool 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) {
src/irc/BridgedClient.ts+1 −1 modified@@ -1169,7 +1169,7 @@ export class BridgedClient extends EventEmitter { return this.connectDefer.promise; } - private assertConnected(): Client { + public assertConnected(): Client { if (this.state.status !== BridgedClientStatus.CONNECTED) { throw Error('Client is not connected'); }
yarn.lock+59 −4 modified@@ -1202,6 +1202,14 @@ "@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" @@ -1217,6 +1225,11 @@ 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" @@ -1230,6 +1243,19 @@ 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" @@ -1244,6 +1270,20 @@ 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" @@ -1252,6 +1292,14 @@ "@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" @@ -2378,6 +2426,13 @@ escape-string-regexp@^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" @@ -4249,10 +4304,10 @@ matrix-appservice@^2.0.0: 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"
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-3pmj-jqqp-2mj3ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-38690ghsaADVISORY
- github.com/matrix-org/matrix-appservice-irc/commit/0afb064635d37e039067b5b3d6423448b93026d3ghsax_refsource_MISCWEB
- github.com/matrix-org/matrix-appservice-irc/releases/tag/1.0.1ghsax_refsource_MISCWEB
- github.com/matrix-org/matrix-appservice-irc/security/advisories/GHSA-3pmj-jqqp-2mj3ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.