fix: Upgrade sse-channel to mitigate CVE-2019-10744 (#4835)

sse-channel 4 removed CORS support, that's why we need to handle CORS for `/push` ourselves now.
This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™
2022-12-07 15:13:36 +01:00
committed by GitHub
parent 1fc17b5d81
commit 7e1a13f9b2
7 changed files with 71 additions and 139 deletions

View File

@@ -173,7 +173,7 @@
"shelljs": "^0.8.5", "shelljs": "^0.8.5",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
"sqlite3": "^5.1.2", "sqlite3": "^5.1.2",
"sse-channel": "^3.1.1", "sse-channel": "^4.0.0",
"swagger-ui-express": "^4.3.0", "swagger-ui-express": "^4.3.0",
"tslib": "1.14.1", "tslib": "1.14.1",
"typeorm": "0.2.45", "typeorm": "0.2.45",

View File

@@ -1,48 +1,20 @@
// @ts-ignore import SSEChannel from 'sse-channel';
import sseChannel from 'sse-channel'; import type { Request, Response } from 'express';
import express from 'express';
import { LoggerProxy as Logger } from 'n8n-workflow'; import { LoggerProxy as Logger } from 'n8n-workflow';
import type { IPushData, IPushDataType } from '@/Interfaces'; import type { IPushData, IPushDataType } from '@/Interfaces';
interface SSEChannelOptions {
cors?: {
origins: string[];
};
}
namespace SSE {
export type Channel = {
on(event: string, handler: (channel: string, res: express.Response) => void): void;
removeClient: (res: express.Response) => void;
addClient: (req: express.Request, res: express.Response) => void;
send: (msg: string, clients?: express.Response[]) => void;
};
}
export class Push { export class Push {
private channel: SSE.Channel; private channel = new SSEChannel();
private connections: { private connections: Record<string, Response> = {};
[key: string]: express.Response;
} = {};
constructor() { constructor() {
const options: SSEChannelOptions = {}; this.channel.on('disconnect', (channel: string, res: Response) => {
if (process.env.NODE_ENV !== 'production') {
options.cors = {
// Allow access also from frontend when developing
origins: ['http://localhost:8080'],
};
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
this.channel = new sseChannel(options) as SSE.Channel;
this.channel.on('disconnect', (channel: string, res: express.Response) => {
if (res.req !== undefined) { if (res.req !== undefined) {
Logger.debug(`Remove editor-UI session`, { sessionId: res.req.query.sessionId }); const { sessionId } = res.req.query;
delete this.connections[res.req.query.sessionId as string]; Logger.debug(`Remove editor-UI session`, { sessionId });
delete this.connections[sessionId as string];
} }
}); });
} }
@@ -51,10 +23,10 @@ export class Push {
* Adds a new push connection * Adds a new push connection
* *
* @param {string} sessionId The id of the session * @param {string} sessionId The id of the session
* @param {express.Request} req The request * @param {Request} req The request
* @param {express.Response} res The response * @param {Response} res The response
*/ */
add(sessionId: string, req: express.Request, res: express.Response) { add(sessionId: string, req: Request, res: Response) {
Logger.debug(`Add editor-UI session`, { sessionId }); Logger.debug(`Add editor-UI session`, { sessionId });
if (this.connections[sessionId] !== undefined) { if (this.connections[sessionId] !== undefined) {

View File

@@ -158,6 +158,7 @@ import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData'
import { toHttpNodeParameters } from '@/CurlConverterHelper'; import { toHttpNodeParameters } from '@/CurlConverterHelper';
import { setupErrorMiddleware } from '@/ErrorReporting'; import { setupErrorMiddleware } from '@/ErrorReporting';
import { getLicense } from '@/License'; import { getLicense } from '@/License';
import { corsMiddleware } from './middlewares/cors';
require('body-parser-xml')(bodyParser); require('body-parser-xml')(bodyParser);
@@ -624,10 +625,9 @@ class App {
this.app.use(cookieParser()); this.app.use(cookieParser());
// Get push connections // Get push connections
this.app.use( this.app.use(`/${this.restEndpoint}/push`, corsMiddleware, async (req, res, next) => {
async (req: express.Request, res: express.Response, next: express.NextFunction) => { const { sessionId } = req.query;
if (req.url.indexOf(`/${this.restEndpoint}/push`) === 0) { if (sessionId === undefined) {
if (req.query.sessionId === undefined) {
next(new Error('The query parameter "sessionId" is missing!')); next(new Error('The query parameter "sessionId" is missing!'));
return; return;
} }
@@ -642,12 +642,8 @@ class App {
} }
} }
this.push.add(req.query.sessionId as string, req, res); this.push.add(sessionId as string, req, res);
return; });
}
next();
},
);
// Compress the response data // Compress the response data
this.app.use(compression()); this.app.use(compression());
@@ -719,19 +715,7 @@ class App {
}), }),
); );
if (process.env.NODE_ENV !== 'production') { this.app.use(corsMiddleware);
this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
// Allow access also from frontend when developing
res.header('Access-Control-Allow-Origin', 'http://localhost:8080');
res.header('Access-Control-Allow-Credentials', 'true');
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept, sessionid',
);
next();
});
}
// eslint-disable-next-line consistent-return // eslint-disable-next-line consistent-return
this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => { this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {

View File

@@ -27,6 +27,7 @@ import type { ICustomRequest, IExternalHooksClass, IPackageVersions } from '@/In
import config from '@/config'; import config from '@/config';
import { WEBHOOK_METHODS } from '@/WebhookHelpers'; import { WEBHOOK_METHODS } from '@/WebhookHelpers';
import { setupErrorMiddleware } from '@/ErrorReporting'; import { setupErrorMiddleware } from '@/ErrorReporting';
import { corsMiddleware } from './middlewares/cors';
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-call // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-call
require('body-parser-xml')(bodyParser); require('body-parser-xml')(bodyParser);
@@ -278,18 +279,7 @@ class App {
}), }),
); );
if (process.env.NODE_ENV !== 'production') { this.app.use(corsMiddleware);
this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
// Allow access also from frontend when developing
res.header('Access-Control-Allow-Origin', 'http://localhost:8080');
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept, sessionid',
);
next();
});
}
this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => { this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
if (!Db.isInitialized) { if (!Db.isInitialized) {

View File

@@ -0,0 +1,18 @@
import type { RequestHandler } from 'express';
const { NODE_ENV } = process.env;
const inDevelopment = !NODE_ENV || NODE_ENV === 'development';
export const corsMiddleware: RequestHandler = (req, res, next) => {
if (inDevelopment && 'origin' in req.headers) {
// Allow access also from frontend when developing
res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header('Access-Control-Allow-Credentials', 'true');
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept, sessionid',
);
}
next();
};

17
packages/cli/src/sse-channel.d.ts vendored Normal file
View File

@@ -0,0 +1,17 @@
import type { Request, Response } from 'express';
declare module 'sse-channel' {
declare class Channel {
constructor();
on(event: string, handler: (channel: string, res: Response) => void): void;
removeClient: (res: Response) => void;
addClient: (req: Request, res: Response) => void;
send: (msg: string, clients?: Response[]) => void;
}
export = Channel;
}

57
pnpm-lock.yaml generated
View File

@@ -195,7 +195,7 @@ importers:
shelljs: ^0.8.5 shelljs: ^0.8.5
source-map-support: ^0.5.21 source-map-support: ^0.5.21
sqlite3: ^5.1.2 sqlite3: ^5.1.2
sse-channel: ^3.1.1 sse-channel: ^4.0.0
supertest: ^6.2.2 supertest: ^6.2.2
swagger-ui-express: ^4.3.0 swagger-ui-express: ^4.3.0
ts-node: ^9.1.1 ts-node: ^9.1.1
@@ -278,7 +278,7 @@ importers:
shelljs: 0.8.5 shelljs: 0.8.5
source-map-support: 0.5.21 source-map-support: 0.5.21
sqlite3: 5.1.2 sqlite3: 5.1.2
sse-channel: 3.1.1 sse-channel: 4.0.0
swagger-ui-express: 4.5.0_express@4.18.2 swagger-ui-express: 4.5.0_express@4.18.2
tslib: 1.14.1 tslib: 1.14.1
typeorm: 0.2.45_6spgkqhramqg35yodisibk43rm typeorm: 0.2.45_6spgkqhramqg35yodisibk43rm
@@ -6848,14 +6848,6 @@ packages:
mime-types: 2.1.35 mime-types: 2.1.35
negotiator: 0.6.3 negotiator: 0.6.3
/access-control/1.0.1:
resolution: {integrity: sha512-H5aqjkogmFxfaOrfn/e42vyspHVXuJ8er63KuljJXpOyJ1ZO/U5CrHfO8BLKIy2w7mBM02L5quL0vbfQqrGQbA==}
dependencies:
millisecond: 0.1.2
setheader: 1.0.2
vary: 1.1.2
dev: false
/acorn-globals/6.0.0: /acorn-globals/6.0.0:
resolution: {integrity: sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==} resolution: {integrity: sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==}
dependencies: dependencies:
@@ -8951,10 +8943,6 @@ packages:
resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==} resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==}
dev: true dev: true
/colornames/1.1.1:
resolution: {integrity: sha512-/pyV40IrsdulWv+wFPmERh9k/mjsPZ64yUMDmWrtj/k1nmgrzzIENWKdaVKyBbvFdQWqkcaRxr+polCo3VMe7A==}
dev: false
/colorspace/1.1.4: /colorspace/1.1.4:
resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==} resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==}
dependencies: dependencies:
@@ -10096,14 +10084,6 @@ packages:
wrappy: 1.0.2 wrappy: 1.0.2
dev: true dev: true
/diagnostics/1.1.1:
resolution: {integrity: sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==}
dependencies:
colorspace: 1.1.4
enabled: 1.0.2
kuler: 1.0.1
dev: false
/diff-sequences/28.1.1: /diff-sequences/28.1.1:
resolution: {integrity: sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==} resolution: {integrity: sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==}
engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
@@ -10390,12 +10370,6 @@ packages:
engines: {node: '>= 4'} engines: {node: '>= 4'}
dev: true dev: true
/enabled/1.0.2:
resolution: {integrity: sha512-nnzgVSpB35qKrUN8358SjO1bYAmxoThECTWw9s3J0x5G8A9hokKHVDFzBjVpCoSryo6MhN8woVyascN5jheaNA==}
dependencies:
env-variable: 0.0.6
dev: false
/enabled/2.0.0: /enabled/2.0.0:
resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==}
dev: false dev: false
@@ -10467,10 +10441,6 @@ packages:
dev: false dev: false
optional: true optional: true
/env-variable/0.0.6:
resolution: {integrity: sha512-bHz59NlBbtS0NhftmR8+ExBEekE7br0e01jw+kk0NDro7TtZzBYZ5ScGPs3OmwnpyfHTHOtr1Y6uedCdrIldtg==}
dev: false
/err-code/2.0.3: /err-code/2.0.3:
resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==}
dev: false dev: false
@@ -14675,12 +14645,6 @@ packages:
engines: {node: '>= 8'} engines: {node: '>= 8'}
dev: true dev: true
/kuler/1.0.1:
resolution: {integrity: sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==}
dependencies:
colornames: 1.1.1
dev: false
/kuler/2.0.0: /kuler/2.0.0:
resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==}
dev: false dev: false
@@ -15509,10 +15473,6 @@ packages:
brorand: 1.1.0 brorand: 1.1.0
dev: true dev: true
/millisecond/0.1.2:
resolution: {integrity: sha512-BJ8XtxY+woL+5TkP6uS6XvOArm0JVrX2otkgtWZseHpIax0oOOPW3cnwhOjRqbEJg7YRO/BDF7fO/PTWNT3T9Q==}
dev: false
/mime-db/1.52.0: /mime-db/1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
@@ -18903,12 +18863,6 @@ packages:
split-string: 3.1.0 split-string: 3.1.0
dev: true dev: true
/setheader/1.0.2:
resolution: {integrity: sha512-A704nIwzqGed0CnJZIqDE+0udMPS839ocgf1R9OJ8aq8vw4U980HWeNaD9ec8VnmBni9lyGEWDedOWXT/C5kxA==}
dependencies:
diagnostics: 1.1.1
dev: false
/setimmediate/1.0.5: /setimmediate/1.0.5:
resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==}
dev: true dev: true
@@ -19327,11 +19281,8 @@ packages:
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
dev: false dev: false
/sse-channel/3.1.1: /sse-channel/4.0.0:
resolution: {integrity: sha512-vgf4QFh60vlAMX0vGJpn6S+7gTO3ckRn7xq4DOgQGcgDs7ULBkaQFQxy4b3vj/umyk0ydhGu7i4A1nHQc5HcYw==} resolution: {integrity: sha512-I539Tc0gyDTQ2QCSg4v78Flxo/UbqR9x7JoyPcqaPtwo+qzeOw/fF+aPSbk0xTvBQAAAZk7Dlkc8K1bum5GUnw==}
dependencies:
access-control: 1.0.1
lodash: 4.17.21
dev: false dev: false
/ssf/0.11.2: /ssf/0.11.2: