Skip to content
Permalink
Browse files Browse the repository at this point in the history
fix: cleanse ssr attribute name and class value (#2475)
Co-Authored-By: OhB00 <43827372+ohb00@users.noreply.github.com>
  • Loading branch information
adamdbradley and OhB00 committed Dec 19, 2022
1 parent e0dbb33 commit 4b2f89d
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 1 deletion.
12 changes: 11 additions & 1 deletion packages/qwik/src/core/render/ssr/render-ssr.ts
Expand Up @@ -544,6 +544,10 @@ const renderNode = (
classStr = attrValue;
} else if (attrName === 'value' && tagName === 'textarea') {
htmlStr = escapeHtml(attrValue);
} else if (isSSRUnsafeAttr(attrName)) {
if (qDev) {
logError('Attribute value is unsafe for SSR');
}
} else {
openingElement +=
' ' + (value === '' ? attrName : attrName + '="' + escapeAttr(attrValue) + '"');
Expand Down Expand Up @@ -631,7 +635,7 @@ This goes against the HTML spec: https://html.spec.whatwg.org/multipage/dom.html
}

if (classStr) {
openingElement += ' class="' + classStr + '"';
openingElement += ' class="' + escapeAttr(classStr) + '"';
}

if (listeners.length > 0) {
Expand Down Expand Up @@ -1073,6 +1077,12 @@ const escapeAttr = (s: string) => {
});
};

// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
const unsafeAttrCharRE = /[>/="'\u0009\u000a\u000c\u0020]/; // eslint-disable-line no-control-regex
export const isSSRUnsafeAttr = (name: string): boolean => {
return unsafeAttrCharRE.test(name);
};

const listenersNeedId = (listeners: Listener[]) => {
return listeners.some((l) => l[1].$captureRef$ && l[1].$captureRef$.length > 0);
};
Expand Down
31 changes: 31 additions & 0 deletions packages/qwik/src/core/render/ssr/render-ssr.unit.tsx
Expand Up @@ -1262,6 +1262,37 @@ renderSSRSuite('null component', async () => {
`<html q:container="paused" q:version="dev" q:render="ssr-dev"><!--qv q:id=0 q:key=sX:--><!--/qv--></html>`
);
});

renderSSRSuite('cleanse attribute name', async () => {
const o = {
'"><script>alert("ಠ~ಠ")</script>': 'xss',
};
await testSSR(
<body {...o}></body>,
'<html q:container="paused" q:version="dev" q:render="ssr-dev"><body></body></html>'
);
});

renderSSRSuite('cleanse class attribute', async () => {
const o = {
class: '"><script>alert("ಠ~ಠ")</script>',
};
await testSSR(
<body {...o}></body>,
'<html q:container="paused" q:version="dev" q:render="ssr-dev"><body class="&quot;><script>alert(&quot;ಠ~ಠ&quot;)</script>"></body></html>'
);
});

renderSSRSuite('class emoji valid', async () => {
const o = {
class: 'package📦',
};
await testSSR(
<body {...o}></body>,
'<html q:container="paused" q:version="dev" q:render="ssr-dev"><body class="package📦"></body></html>'
);
});

// TODO
// Merge props on host
// - host events
Expand Down

0 comments on commit 4b2f89d

Please sign in to comment.