CVE-2022-24822: Insufficient Information (NVD-CWE-noinfo)
value: Podium is a library for building micro frontends. @podium/layout is a module for building a Podium layout server, and @podium/proxy is a module for proxying HTTP requests from a layout server to a podlet server. In @podium/layout prior to version 4.6.110 and @podium/proxy prior to version 4.2.74, an attacker using the `Trailer` header as part of the request against proxy endpoints has the ability to take down the server. All Podium layouts that include podlets with proxy endpoints are affected. `@podium/layout`, which is the main way developers/users are vulnerable to this exploit, has been patched in version `4.6.110`. All earlier versions are vulnerable.`@podium/proxy`, which is the source of the vulnerability and is used by `@podium/layout` has been patched in version `4.2.74`. All earlier versions are vulnerable. It is not easily possible to work around this issue without upgrading.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
CVSS v3 base score: 7.5
Published: 2022-04-06T18:15Z
Repository: https://github.com/podium-lib/proxy
Language: JavaScript
Files changed: lib/proxy.js, tests/proxying.js
Commit date: 2022-04-05 11:14:03 +0200
Commit message: fix: remove trailer header if present when proxying

Diff: diff --git a/lib/proxy.js b/lib/proxy.js
index e5000cb..b63882c 100644
--- a/lib/proxy.js
+++ b/lib/proxy.js
@@ -149,10 +149,13 @@ const PodiumProxy = class PodiumProxy {
         return new Promise((resolve, reject) => {
             const match = this.pathnameParser.exec(incoming.url.pathname);
             let errored = false;
 
             if (match) {
+                // eslint-disable-next-line no-param-reassign
+                delete incoming.request.headers.trailer;
+
                 // Turn matched uri parameters into an object of parameters
                 const params = {};
                 for (let i = 1; i < match.length; i += 1) {
                     const key = this.pathnameEntries[i - 1];
                     params[key.name] = match[i];
diff --git a/tests/proxying.js b/tests/proxying.js
index 86fea79..df24bba 100644
--- a/tests/proxying.js
+++ b/tests/proxying.js
@@ -1,8 +1,9 @@
 'use strict';
 
 const { test } = require('tap');
+const { exec } = require('child_process');
 const {
     destinationObjectStream,
     HttpServer,
     request,
 } = require('@podium/test-utils');
@@ -440,5 +441,59 @@ test('Proxying() - proxy to a non existing server - GET request will error - sho
 
     await proxy.close();
 
     t.end();
 });
+
+test('Proxying() - trailer header - is not forwarded according to spec', async (t) => {
+    const server = new HttpServer();
+    server.request = (req, res) => {
+        t.ok(!req.headers.trailer, 'Trailer header should not be set');
+        res.statusCode = 200;
+        res.setHeader('Content-Type', 'application/json');
+        res.end(
+            JSON.stringify({
+                method: req.method,
+                type: 'destination',
+                url: `http://${req.headers.host}${req.url}`,
+            }),
+        );
+    };
+
+    const serverAddr = await server.listen();
+
+    const proxy = new ProxyServer(
+        [
+            {
+                name: 'foo',
+                proxy: {
+                    a: `${serverAddr}/some/path`,
+                },
+                version: '1.0.0',
+                content: '/',
+            },
+        ],
+        { name: 'mylayout' },
+    );
+
+    const proxyAddr = await proxy.listen();
+
+    await new Promise((resolve, reject) => {
+        exec(
+            `curl -i -H 'Trailer: Krynos' -H 'User-Agent: Mozilla' '${proxyAddr}/layout/proxy/foo/a'`,
+            (error, stdoutput, stderror) => {
+                if (error) {
+                    reject(error);
+                    return;
+                }
+                resolve({ stdout: stdoutput, stderr: stderror });
+            },
+        );
+    });
+
+    t.ok(true);
+
+    await proxy.close();
+    await server.close();
+
+    t.end();
+});

Fixed code:                 // eslint-disable-next-line no-param-reassign
                delete incoming.request.headers.trailer;

const { exec } = require('child_process');

test('Proxying() - trailer header - is not forwarded according to spec', async (t) => {
    const server = new HttpServer();
    server.request = (req, res) => {
        t.ok(!req.headers.trailer, 'Trailer header should not be set');
        res.statusCode = 200;
        res.setHeader('Content-Type', 'application/json');
        res.end(
            JSON.stringify({
                method: req.method,
                type: 'destination',
                url: `http://${req.headers.host}${req.url}`,
            }),
        );
    };

    const serverAddr = await server.listen();

    const proxy = new ProxyServer(
        [
            {
                name: 'foo',
                proxy: {
                    a: `${serverAddr}/some/path`,
                },
                version: '1.0.0',
                content: '/',
            },
        ],
        { name: 'mylayout' },
    );

    const proxyAddr = await proxy.listen();

    await new Promise((resolve, reject) => {
        exec(
            `curl -i -H 'Trailer: Krynos' -H 'User-Agent: Mozilla' '${proxyAddr}/layout/proxy/foo/a'`,
            (error, stdoutput, stderror) => {
                if (error) {
                    reject(error);
                    return;
                }
                resolve({ stdout: stdoutput, stderr: stderror });
            },
        );
    });

    t.ok(true);

    await proxy.close();
    await server.close();

    t.end();
});