Sanitize dynamic tags (#5615)

* fix: sanitize tags

* fix: better element sanitization

* chore: remove unused import

Co-authored-by: Nate Moore <nate@astro.build>
This commit is contained in:
Nate Moore 2022-12-16 14:14:42 -05:00 committed by GitHub
parent d1abb63a64
commit d85ec7484c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 81 additions and 3 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Sanitize dynamically rendered tags to strip out any attributes

View file

@ -239,12 +239,14 @@ If you're still stuck, please open an issue on GitHub or join us at https://astr
// This is a custom element without a renderer. Because of that, render it
// as a string and the user is responsible for adding a script tag for the component definition.
if (!html && typeof Component === 'string') {
// Sanitize tag name because some people might try to inject attributes 🙄
const Tag = sanitizeElementName(Component);
const childSlots = Object.values(children).join('');
const iterable = renderAstroTemplateResult(
await renderTemplate`<${Component}${internalSpreadAttributes(props)}${markHTMLString(
childSlots === '' && voidElementNames.test(Component)
await renderTemplate`<${Tag}${internalSpreadAttributes(props)}${markHTMLString(
childSlots === '' && voidElementNames.test(Tag)
? `/>`
: `>${childSlots}</${Component}>`
: `>${childSlots}</${Tag}>`
)}`
);
html = '';
@ -322,6 +324,12 @@ If you're still stuck, please open an issue on GitHub or join us at https://astr
return renderAll();
}
function sanitizeElementName(tag: string) {
const unsafe = /[&<>'"\s]+/g;
if (!unsafe.test(tag)) return tag;
return tag.trim().split(unsafe)[0].trim();
}
async function renderFragmentComponent(result: SSRResult, slots: any = {}) {
const children = await renderSlot(result, slots?.default);
if (children == null) {

View file

@ -0,0 +1,65 @@
import { expect } from 'chai';
import * as cheerio from 'cheerio';
import { runInContainer } from '../../../dist/core/dev/index.js';
import { createFs, createRequestAndResponse } from '../test-utils.js';
import svelte from '../../../../integrations/svelte/dist/index.js';
import { defaultLogging } from '../../test-utils.js';
const root = new URL('../../fixtures/alias/', import.meta.url);
describe('core/render components', () => {
it('should sanitize dynamic tags', async () => {
const fs = createFs(
{
'/src/pages/index.astro': `
---
const TagA = 'p style=color:red;'
const TagB = 'p><script id="pwnd">console.log("pwnd")</script>'
---
<html>
<head><title>testing</title></head>
<body>
<TagA id="target" />
<TagB />
</body>
</html>
`,
},
root
);
await runInContainer(
{
fs,
root,
logging: {
...defaultLogging,
// Error is expected in this test
level: 'silent',
},
userConfig: {
integrations: [svelte()],
},
},
async (container) => {
const { req, res, done, text } = createRequestAndResponse({
method: 'GET',
url: '/',
});
container.handle(req, res);
await done;
const html = await text();
const $ = cheerio.load(html);
const target = $('#target');
expect(target).not.to.be.undefined;
expect(target.attr('id')).to.equal('target');
expect(target.attr('style')).to.be.undefined;
expect($('#pwnd').length).to.equal(0);
}
);
});
});