Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vite 6 breaks ability to using special chars in html tags attrs #18811

Open
7 tasks done
brokenmass opened this issue Nov 27, 2024 · 9 comments
Open
7 tasks done

vite 6 breaks ability to using special chars in html tags attrs #18811

brokenmass opened this issue Nov 27, 2024 · 9 comments

Comments

@brokenmass
Copy link

brokenmass commented Nov 27, 2024

Describe the bug

when using ejs tags (like html: {cspNonce: '<%= nonce _%>'}, or experimental: { renderBuiltUrl(filename) {return <%= somePath %>/${filename};}})) , or URLS that contains multiple query strings parameters http:host.com/url?query=123&limit=100, in any html tag attribute, the ", ', &, <, and >. are escaped and so the output html is no longer correct.

(the typical use case for the ejs approach is to have express render the built html page and populate those template variables at runtime).

The bug is not present in vite 5 and was introduced by #18067 , to fix issue #18040.
The issue seems to me to have no actual foundation (cause if some malicious function is able to inject a tag, escaping attrs is pretty much useless as code can be injected in much easier ways anyway) and the fix mangle every attrs that use the above special character.

Reverting the PR resolves the problem.

Reproduction

n/a

Steps to reproduce

No response

System Info

n/a

Used Package Manager

npm

Logs

No response

Validations

@brokenmass brokenmass changed the title vite 6 breaks when using ejs style tags in attrs vite 6 breaks ability to using special chars in html tags attrs Nov 27, 2024
Copy link

Hello @brokenmass. Please provide a minimal reproduction using a GitHub repository or StackBlitz. Issues marked with needs reproduction will be closed if they have no activity within 3 days.

@hi-ogawa
Copy link
Collaborator

The use case seems legitimate, but it would be nice to have a reproduction. I'm not sure what you mean by the last one about "URLS that contains multiple query strings".

@terry-au
Copy link

terry-au commented Nov 28, 2024

@hi-ogawa I'm also encountering this problem. Here's an excerpt of the plugin we're using:

const entrypointPlugin: Plugin = {
  name: 'entrypoint-plugin',
  apply: 'build',
  enforce: 'pre',
  config(config): UserConfig {
    return {
      experimental: {
        renderBuiltUrl: (filename, { hostType }) => {
          if (hostType === 'html') {
            return `<%= cdn.baseUrl %>/${filename}`;
          }
          return undefined;
        },
      },
    };
  },
};

Vite 5:

<script type="module" crossorigin src="<%= cdn.baseUrl %>/assets/index-yAKNFxux.js"></script>

Vite 6:

<script type="module" crossorigin src="&lt;%= cdn.baseUrl %&gt;/assets/index-yAKNFxux.js"></script>

@brokenmass
Copy link
Author

brokenmass commented Nov 28, 2024

The use case seems legitimate, but it would be nice to have a reproduction. I'm not sure what you mean by the last one about "URLS that contains multiple query strings".

Link : https://stackblitz.com/edit/vitejs-vite-taxulw

vite.config.ts

import { defineConfig } from 'vite';

export default defineConfig({
  html: {
    cspNonce: '<%= cspNonce %>',
  },
  experimental: {
    renderBuiltUrl: (filename, { hostType }) => {
      if (hostType === 'html') {
        return `<%= cdn.baseUrl %>/${filename}`;
      }
      return undefined;
    },
  },
  plugins: [
    {
      name: 'inject-tag',
      enforce: 'post',
      transformIndexHtml: () => {
        return [
          {
            tag: 'image',
            injectTo: 'body',
            attrs: {
              src: 'https://placehold.co/600x400?text=Hello+World&font=Oswald',
            },
          },
        ];
      },
    },
  ],
});

run the command npm install && npm run preview

with vite 5

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite App</title>
    <meta property="csp-nonce" nonce="<%= cspNonce %>">
    <script type="module" crossorigin src="<%= cdn.baseUrl %>/assets/index-MQ_MSdzi.js" nonce="<%= cspNonce %>"></script>
    <link rel="stylesheet" crossorigin href="<%= cdn.baseUrl %>/assets/index-DRNMkqs8.css" nonce="<%= cspNonce %>">
  </head>
  <body>
    <div id="app"></div>
    <image src="https://placehold.co/600x400?text=Hello+World&font=Oswald"></image>
  </body>
</html>

with vite 6

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite App</title>
    <meta property="csp-nonce" nonce="&lt;%= cspNonce %&gt;">
    <script type="module" crossorigin src="&lt;%= cdn.baseUrl %&gt;/assets/index-MQ_MSdzi.js" nonce="<%= cspNonce %>"></script>
    <link rel="stylesheet" crossorigin href="&lt;%= cdn.baseUrl %&gt;/assets/index-DRNMkqs8.css" nonce="<%= cspNonce %>">
  </head>
  <body>
    <div id="app"></div>
    <image src="https://placehold.co/600x400?text=Hello+World&amp;font=Oswald"></image>
  </body>
</html>

notice how in the second output the special characters <, > and & have been converted to their html escaped versions (&lt;, &gt and &amp; respectively).

the URLS that contains multiple query strings parameters issue is highlighted bythe injected placeholder image. multiple query string parameters are concatenated with & that in vite 6 gets escaped. Buy that's actually not a real issue as the browser seems to interpret that correctly.

Seems like this should be quite a relevant issue, no ?

@bluwy
Copy link
Member

bluwy commented Nov 28, 2024

For me, this feels more of an intentional breaking change (that we didn't intend to be very breaking but should've mentioned in the migration guide anyways) than a bug. Vite couldn't tell that you're specifying a string as a placeholder that shouldn't be escaped. And escaping would've been the safer default.

I think it should be possible to migrate / adapt by using URL-safe characters instead? Or is using the specific <%= syntax required?

@brokenmass
Copy link
Author

For me, this feels more of an intentional breaking change (that we didn't intend to be very breaking but should've mentioned in the migration guide anyways) than a bug.
Vite couldn't tell that you're specifying a string as a placeholder that shouldn't be escaped. And escaping would've been the safer default.

Yes i get your point, but HTML-Escaping attributes in a build tools does not seem the standard behaviour, and is definitely an unexpected one.
The output of the compilation does not have to be fully valid html as there might be some other task or services that parse/transform the html before serving it.

I think it should be possible to migrate / adapt by using URL-safe characters instead? Or is using the specific <%= syntax required?

Sure i think it's possible to use custom delimiter or move to a different template engine, like pug. but would avoid it if possible.

As a solution, if you do not want to revert the pr, you could allow an option to opt out of this defensive behaviour

@sapphi-red
Copy link
Member

While we escape " / ' / & / < / > now, I guess we only need to escape & and " because we know that the value will be used as an attribute with double quotes.

@terry-au
Copy link

terry-au commented Nov 29, 2024

Maybe we should consider letting renderBuiltUrl return its string output unmodified, since its purpose is to provide precise control over asset paths. As an alternative, we could explore adding an option to skip string escaping by returning:

{
  rawUrl: `<%= cdn.baseUrl %>/${filename}`,
}

@brokenmass
Copy link
Author

brokenmass commented Nov 29, 2024

While we escape " / ' / & / < / > now, I guess we only need to escape & and " because we know that the value will be used as an attribute with double quotes.

Yes, just html-escaping the double quotes should be enough. Why also the & ?
But again what’s the requirement here ? Protect from self code injection ? Seems like a solution to a non-problem, and even partial one as then you should escape the tag name too, and probably many other locations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants