Skip to content

Commit 29df9a0

Browse files
committed
fix: xss vulnerability patch for 4.3.1
1 parent 7e34238 commit 29df9a0

File tree

7 files changed

+112
-16
lines changed

7 files changed

+112
-16
lines changed

.changeset/nervous-pets-sip.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@vue-storefront/middleware": patch
3+
---
4+
5+
- **[FIXED]** Now parameters are properly sanitized and validated before being used in the middleware.

packages/middleware/__tests__/integration/createServer.spec.ts

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,38 +13,38 @@ describe("[Integration] Create server", () => {
1313
configuration: {
1414
myCfgEntry: true,
1515
},
16-
errorHandler: (error: unknown, req: any, res: any) => {
16+
errorHandler: (_error: unknown, _req: any, res: any) => {
1717
res.status(410); // awkward status code to test if it's working
1818
res.send("Custom error handler");
1919
},
2020
location: "./__tests__/integration/bootstrap/server",
21-
extensions() {
22-
return [
21+
extensions: (extensions) =>
22+
[
23+
...extensions,
2324
{
2425
name: "my-extension",
2526
extendApiMethods: {
26-
myFunc(context) {
27+
myFunc: (context) => {
2728
return context.api.success();
2829
},
29-
myFuncWithDependencyToOtherExtension(context) {
30-
return (context.api as any).myFunc();
30+
myFuncWithDependencyToOtherExtension: (context) => {
31+
return context.api.myFunc();
3132
},
3233
},
3334
},
3435
{
3536
name: "my-namespaced-extension",
3637
isNamespaced: true,
3738
extendApiMethods: {
38-
myFunc(context) {
39+
myFunc: (context) => {
3940
return context.api.error();
4041
},
41-
myFuncNamespaced(context) {
42+
myFuncNamespaced: (context) => {
4243
return context.api.success();
4344
},
4445
},
4546
},
46-
];
47-
},
47+
] as any,
4848
},
4949
},
5050
});
@@ -132,7 +132,7 @@ describe("[Integration] Create server", () => {
132132
configuration: {
133133
myCfgEntry: true,
134134
},
135-
errorHandler: (error: unknown, req: any, res: any) => {
135+
errorHandler: (_error: unknown, _req: any, res: any) => {
136136
res.status(410); // awkward status code to test if it's working
137137
res.send("Custom error handler");
138138
},
@@ -208,4 +208,27 @@ describe("[Integration] Create server", () => {
208208
expect(status).toEqual(200);
209209
expect(response).toEqual(apiMethodResult);
210210
});
211+
212+
describe("prevent XSS attacks", () => {
213+
test.each([
214+
[
215+
"/z--%3E%3C!--hi--%3E%3Cimg%20src=x%20onerror=alert('DOM--XSS')%3E%3C!--%3C%3C/success",
216+
`"z--&gt;<img src>" integration is not configured. Please, check the request path or integration configuration.`,
217+
],
218+
[
219+
"/test_integration/z--%3E%3C!--hi--%3E%3Cimg%20src=x%20onerror=alert('DOM--XSS')%3E%3C!--%3C%3C",
220+
`Failed to resolve apiClient or function: The function "z--&gt;<img src>" is not registered.`,
221+
],
222+
[
223+
"/test_integration/z--%3E%3C!--hi--%3E%3Cimg%20src=x%20onerror=alert('DOM--XSS')%3E%3C!--%3C%3C/success",
224+
`Failed to resolve apiClient or function: Extension "z--&gt;<img src>" is not namespaced or the function "success" is not available in the namespace.`,
225+
],
226+
])("Use case: %s", async (maliciousUrl, expectedMessage) => {
227+
const res = await request(app).get(maliciousUrl).send();
228+
expect(res.error && res.error.text).not.toContain(
229+
"z--><!--hi--><img src=x onerror=alert('DOM--XSS')><!--<<"
230+
);
231+
expect(res.error && res.error.text).toEqual(expectedMessage);
232+
});
233+
});
211234
});

packages/middleware/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
"cookie-parser": "^1.4.6",
2525
"cors": "^2.8.5",
2626
"express": "^4.18.1",
27-
"helmet": "^5.1.1"
27+
"helmet": "^5.1.1",
28+
"xss": "^1.0.15"
2829
},
2930
"devDependencies": {
3031
"@types/body-parser": "^1.19.2",

packages/middleware/src/createServer.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
prepareErrorHandler,
1818
prepareArguments,
1919
callApiFunction,
20+
validateParams,
2021
} from "./handlers";
2122

2223
const defaultCorsOptions: CreateServerOptions["cors"] = {
@@ -65,13 +66,15 @@ async function createServer<
6566

6667
app.post(
6768
"/:integrationName/:extensionName?/:functionName",
69+
validateParams(integrations),
6870
prepareApiFunction(integrations),
6971
prepareErrorHandler(integrations),
7072
prepareArguments,
7173
callApiFunction
7274
);
7375
app.get(
7476
"/:integrationName/:extensionName?/:functionName",
77+
validateParams(integrations),
7578
prepareApiFunction(integrations),
7679
prepareErrorHandler(integrations),
7780
prepareArguments,

packages/middleware/src/handlers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from "./callApiFunction";
22
export * from "./prepareApiFunction";
33
export * from "./prepareErrorHandler";
44
export * from "./prepareArguments";
5+
export * from "./validateParams";
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import xss from "xss";
2+
import type { Request, Response, NextFunction } from "express";
3+
import { IntegrationsLoaded } from "../../types";
4+
5+
export function validateParams(integrations: IntegrationsLoaded) {
6+
return (req: Request, res: Response, next: NextFunction) => {
7+
// Validate & sanitize the request params
8+
Object.entries(req.params).forEach(([key, value]) => {
9+
req.params[key] = typeof value === "string" ? xss(value) : value;
10+
});
11+
12+
// Validate the integration
13+
const { integrationName } = req.params;
14+
if (!integrations || !integrations[integrationName]) {
15+
res.status(404);
16+
res.send(
17+
`"${integrationName}" integration is not configured. Please, check the request path or integration configuration.`
18+
);
19+
20+
return;
21+
}
22+
23+
next();
24+
};
25+
}

yarn.lock

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7829,7 +7829,7 @@ commander@^10.0.0:
78297829
resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06"
78307830
integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==
78317831

7832-
commander@^2.18.0, commander@^2.20.0:
7832+
commander@^2.18.0, commander@^2.20.0, commander@^2.20.3:
78337833
version "2.20.3"
78347834
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
78357835
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
@@ -8254,6 +8254,11 @@ cssesc@^3.0.0:
82548254
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
82558255
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
82568256

8257+
8258+
version "0.0.10"
8259+
resolved "https://registrynpm.storefrontcloud.io/cssfilter/-/cssfilter-0.0.10.tgz#c6d2672632a2e5c83e013e6864a42ce8defd20ae"
8260+
integrity sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4=
8261+
82578262
cssnano-preset-default@^7.0.1:
82588263
version "7.0.1"
82598264
resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-7.0.1.tgz#b05c93a29868dd7bd810fa8bbf89f482804da922"
@@ -18469,7 +18474,16 @@ string-length@^4.0.1:
1846918474
char-regex "^1.0.2"
1847018475
strip-ansi "^6.0.0"
1847118476

18472-
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
18477+
"string-width-cjs@npm:string-width@^4.2.0":
18478+
version "4.2.3"
18479+
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
18480+
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
18481+
dependencies:
18482+
emoji-regex "^8.0.0"
18483+
is-fullwidth-code-point "^3.0.0"
18484+
strip-ansi "^6.0.1"
18485+
18486+
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
1847318487
version "4.2.3"
1847418488
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
1847518489
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -18592,7 +18606,7 @@ string_decoder@~1.1.1:
1859218606
dependencies:
1859318607
safe-buffer "~5.1.0"
1859418608

18595-
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
18609+
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
1859618610
version "6.0.1"
1859718611
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
1859818612
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -18606,6 +18620,13 @@ strip-ansi@^3.0.0:
1860618620
dependencies:
1860718621
ansi-regex "^2.0.0"
1860818622

18623+
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
18624+
version "6.0.1"
18625+
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
18626+
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
18627+
dependencies:
18628+
ansi-regex "^5.0.1"
18629+
1860918630
strip-ansi@^7.0.1:
1861018631
version "7.1.0"
1861118632
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@@ -20657,7 +20678,7 @@ wordwrap@^1.0.0:
2065720678
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
2065820679
integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
2065920680

20660-
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
20681+
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
2066120682
version "7.0.0"
2066220683
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
2066320684
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -20675,6 +20696,15 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0:
2067520696
string-width "^4.1.0"
2067620697
strip-ansi "^6.0.0"
2067720698

20699+
wrap-ansi@^7.0.0:
20700+
version "7.0.0"
20701+
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
20702+
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
20703+
dependencies:
20704+
ansi-styles "^4.0.0"
20705+
string-width "^4.1.0"
20706+
strip-ansi "^6.0.0"
20707+
2067820708
wrap-ansi@^8.0.1, wrap-ansi@^8.1.0:
2067920709
version "8.1.0"
2068020710
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
@@ -20798,6 +20828,14 @@ xmlchars@^2.2.0:
2079820828
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
2079920829
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
2080020830

20831+
xss@^1.0.15:
20832+
version "1.0.15"
20833+
resolved "https://registrynpm.storefrontcloud.io/xss/-/xss-1.0.15.tgz#96a0e13886f0661063028b410ed1b18670f4e59a"
20834+
integrity sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==
20835+
dependencies:
20836+
commander "^2.20.3"
20837+
cssfilter "0.0.10"
20838+
2080120839
xtend@^4.0.0, xtend@~4.0.1:
2080220840
version "4.0.2"
2080320841
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"

0 commit comments

Comments
 (0)