diff --git a/.changeset/sour-socks-enjoy.md b/.changeset/sour-socks-enjoy.md new file mode 100644 index 000000000000..aa9b564858c3 --- /dev/null +++ b/.changeset/sour-socks-enjoy.md @@ -0,0 +1,5 @@ +--- +'astro': minor +--- + +Escape HTML inside of expressions by default. Please see [our migration guide](https://docs.astro.build/en/migrate/#deprecated-unescaped-html) for more details. diff --git a/packages/astro/package.json b/packages/astro/package.json index 69818591e8fe..d3dea5a39bb1 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -58,7 +58,7 @@ "test:match": "mocha --timeout 20000 -g" }, "dependencies": { - "@astrojs/compiler": "^0.12.0-next.8", + "@astrojs/compiler": "^0.12.0-next.9", "@astrojs/language-server": "^0.8.6", "@astrojs/markdown-remark": "^0.6.4", "@astrojs/prism": "0.4.0", @@ -95,10 +95,10 @@ "resolve": "^1.20.0", "rollup": "^2.64.0", "semver": "^7.3.5", - "sirv": "^2.0.2", "serialize-javascript": "^6.0.0", "shiki": "^0.10.0", "shorthash": "^0.0.2", + "sirv": "^2.0.2", "slash": "^4.0.0", "sourcemap-codec": "^1.4.8", "srcset-parse": "^1.1.0", diff --git a/packages/astro/src/runtime/server/escape.ts b/packages/astro/src/runtime/server/escape.ts index a0482fdf2093..6c6eb4ff6b1e 100644 --- a/packages/astro/src/runtime/server/escape.ts +++ b/packages/astro/src/runtime/server/escape.ts @@ -1,23 +1,6 @@ const entities = { '"': 'quot', '&': 'amp', "'": 'apos', '<': 'lt', '>': 'gt' } as const; -const warned = new Set(); -export const escapeHTML = (string: any, { deprecated = false }: { deprecated?: boolean } = {}) => { - const escaped = string.replace(/["'&<>]/g, (char: keyof typeof entities) => '&' + entities[char] + ';'); - if (!deprecated) return escaped; - if (warned.has(string) || !string.match(/[&<>]/g)) return string; - // eslint-disable-next-line no-console - console.warn(`Unescaped HTML content found inside expression! - -The next minor version of Astro will automatically escape all -expression content. Please use the \`set:html\` directive. - -Expression content: -${string}`); - warned.add(string); - - // Return unescaped content for now. To be removed. - return string; -}; +export const escapeHTML = (string: any) => string.replace(/["'&<>]/g, (char: keyof typeof entities) => '&' + entities[char] + ';'); /** * RawString is a "blessed" version of String diff --git a/packages/astro/src/runtime/server/index.ts b/packages/astro/src/runtime/server/index.ts index 19affdb0b604..d2a022805f2d 100644 --- a/packages/astro/src/runtime/server/index.ts +++ b/packages/astro/src/runtime/server/index.ts @@ -8,7 +8,7 @@ import { escapeHTML, UnescapedString, unescapeHTML } from './escape.js'; export type { Metadata } from './metadata'; export { createMetadata } from './metadata.js'; -export { escapeHTML, unescapeHTML } from './escape.js'; +export { unescapeHTML } from './escape.js'; const voidElementNames = /^(area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/i; const htmlBooleanAttributes = @@ -36,7 +36,7 @@ async function _render(child: any): Promise { // of wrapping it in a function and calling it. return _render(child()); } else if (typeof child === 'string') { - return escapeHTML(child, { deprecated: true }); + return escapeHTML(child); } else if (!child && child !== 0) { // do nothing, safe to ignore falsey values. } diff --git a/packages/astro/test/astro-expr.test.js b/packages/astro/test/astro-expr.test.js index e08323f86411..e996062b5bc3 100644 --- a/packages/astro/test/astro-expr.test.js +++ b/packages/astro/test/astro-expr.test.js @@ -99,4 +99,13 @@ describe('Expressions', () => { // test 9: Expected {undefined && } not to render expect($('#frag-undefined')).to.have.lengthOf(0); }); + + it('Escapes HTML by default', async () => { + const html = await fixture.readFile('/escape/index.html'); + const $ = cheerio.load(html); + + expect($('body').children()).to.have.lengthOf(1); + expect($('body').text()).to.include('<script>console.log("pwnd")</script>') + expect($('#trusted')).to.have.lengthOf(1); + }); }); diff --git a/packages/astro/test/fixtures/astro-expr/src/pages/escape.astro b/packages/astro/test/fixtures/astro-expr/src/pages/escape.astro new file mode 100644 index 000000000000..8c16d7fd2263 --- /dev/null +++ b/packages/astro/test/fixtures/astro-expr/src/pages/escape.astro @@ -0,0 +1,9 @@ + + + My site + + + {''} + console.log("yay!")'} /> + + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 97ea5446fbb3..0902dfc0cb52 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -358,7 +358,7 @@ importers: packages/astro: specifiers: - '@astrojs/compiler': ^0.12.0-next.8 + '@astrojs/compiler': ^0.12.0-next.9 '@astrojs/language-server': ^0.8.6 '@astrojs/markdown-remark': ^0.6.4 '@astrojs/parser': ^0.22.1 @@ -432,7 +432,7 @@ importers: yargs-parser: ^21.0.0 zod: ^3.8.1 dependencies: - '@astrojs/compiler': 0.12.0-next.8 + '@astrojs/compiler': 0.12.0-next.9 '@astrojs/language-server': 0.8.10 '@astrojs/markdown-remark': link:../markdown/remark '@astrojs/prism': link:../astro-prism @@ -1336,10 +1336,12 @@ packages: leven: 3.1.0 dev: true - /@astrojs/compiler/0.12.0-next.8: - resolution: {integrity: sha512-HeREaw5OR5J7zML+/LxhrqUr57571kyNXL4HD2pU929oevhx3PQ37PQ0FkD5N65X9YfO+gcoEO6whl76vtSZag==} + /@astrojs/compiler/0.12.0-next.9: + resolution: {integrity: sha512-XHvGrPBhr/LBYZT4TuXf8mK2+CYCClQoPln9FlF6gIx4yTsWpUwAi3mhhFOGJi4NB1Y1ZbcXS60IjMy/2adpLg==} dependencies: + tsm: 2.2.1 typescript: 4.6.2 + uvu: 0.5.3 dev: false /@astrojs/language-server/0.8.10: @@ -8795,6 +8797,7 @@ packages: /source-map/0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + requiresBuild: true /source-map/0.7.3: resolution: {integrity: sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==}