sveltejs / svelte

Cybernetically enhanced web apps
https://svelte.dev
MIT License
77.46k stars 4.04k forks source link

Discrepancy in @html directive regarding script tags #10900

Open Narigo opened 5 months ago

Narigo commented 5 months ago

Describe the bug

When having a string like <script>alert(1);</script> inserted to a {@html stringWithScriptTag}, the script gets evaluated when refreshed on the page or the user gets to the page via a link with rel="external". It does not get evaluated when you follow a link without rel="external" to the page.

Reproduction

Here is a reproducer on sveltelab.dev with two pages where page2 includes the script injection.

Logs

No response

System Info

System:
    OS: Linux 5.0 undefined
    CPU: (8) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
    Memory: 0 Bytes / 0 Bytes
    Shell: 1.0 - /bin/jsh
  Binaries:
    Node: 18.18.0 - /usr/local/bin/node
    Yarn: 1.22.19 - /usr/local/bin/yarn
    npm: 10.2.3 - /usr/local/bin/npm
    pnpm: 8.14.0 - /usr/local/bin/pnpm
  npmPackages:
    @sveltejs/adapter-auto: ^3.0.0 => 3.1.1 
    @sveltejs/kit: ^2.0.0 => 2.4.1 
    @sveltejs/vite-plugin-svelte: ^3.0.0 => 3.0.1 
    svelte: ^4.0.5 => 4.2.9 
    vite: ^5.0.0 => 5.0.12

Severity

serious, but I can work around it

Additional Information

For my use case, I would be happy if script injections would be always allowed, but I would understand to disallow them always. It doesn't feel right to me that it behaves differently depending on how the user switches to the page in question though. I didn't see this documented somewhere, so I tested it and saw this happening.

Narigo commented 5 months ago

Btw, on Discord, khromov shared a potential workaround:

Use the onMount to inject a third party script on the page:

const script = document.createElement('script');
script.src = 'https://example.com/external-script.js';
script.type = 'text/javascript';
document.body.appendChild(script);
dummdidumm commented 3 months ago

This is expected although confusing. The reason is that @html when rendered on the client uses .innerHTML = ... which does not execute script tags - this is a security measure by browsers. On the server this doesn't happen because it becomes regular html then. I'm not sure if we should "fix" this - @html is already pretty insecure by nature, this would make it even more so.