Trusted Types Help Prevent Cross-Site Scripting
developers.google.com
developers.google.com
http://blog.hackensplat.com/2013/09/never-sanitize-your-inpu...
But if you use DOM APIs for everything, this “Trusted Types” API seems largely unnecessary. It would be enough to expose a switch to disable innerHTML and similar APIs entirely.
On the other hand, DOM APIs are rather unergonomic to use raw. Many wrappers exist, but React‘s JSX takes the cake by letting you write code that looks like string interpolation, yet compiles down to type-safe node creation. (Kind of like what parameterized queries do for SQL.) If we’re looking at browser-based approaches to solving XSS… how about standardizing something like JSX as a built-in browser feature? That way it could be used by everyone, even those who want to minimize dependencies or code directly for the browser.
(Yes, I know it’s been tried before, in the form of E4X. But that was a very different era…)
If none of the policies in your CSP headers declare a createHTML method, then you can be confident that innerHTML can't be used anywhere in your app.
You still want the other policy methods because there are other unsafe sinks in the DOM. For example:
const scriptElem = document.createElement('script');
scriptElem.src = someUntrustedInput;
document.body.appendChild(scriptElem); // arbitrary code execution!
There's a number of these sinks, and they have legit important use cases, but you want to be able to sustainably review all such uses. For example, you could make a Trusted Types policy that will only accept a small number of constants for script urls. That way you can still create script elements to e.g. implement lazy loading of code, but you're certain that those APIs will not be used by an attacker to load unknown code.A long time ago, I remember people having an insane amount of trouble with character handling; when you read binary data from a TCP socket or UNIX file, you're reading bytes, not characters. But many people would treat the bytes like characters, causing all sorts of trouble. My favorite was the double-encoding, where you read UTF-8 encoded characters as bytes, treat the bytes as Latin-1, then encode the Latin-1 characters as UTF-8. This was a perl quirk because Latin-1 was the default, but the same bug happens in other languages. Anyway, a good tainting system could prevent this sort of bug. The language can say "hey, this is a TCP socket, you can't treat those bytes as characters!" But it doesn't. And the bug occurs again and again.
(The corner cases that people don't think about are the real problems. What charset are those bytes in your URL? What about filenames? The answer is: it's often undefined. So rather than hope for the best, a compiler error would be ideal.)
The state of the art, as far as I can tell, is to just treat everything as UTF-8 these days. Since everyone seems to love UTF-8, it just works. Maybe that was the real solution. But I know there are a lot of Japanese-speakers with names that can't be encoded as UTF-8. I wonder what they're doing about that.
Dunno if that in particular is a problem in Python or not... but it is the sort of thing to watch out for.
Also, I made a mistake in my previous post. File I/O can deal with bytes OR strings depending on the parameters sent to the `open()` function.
file_handle = open('somefile.txt')
This opens the file in text mode (so `file_handle.read()` returns strings) using a platform-dependent encoding. On Windows, this will probably be CP-1252. On Linux, UTF-8. Of course, you can always explicitly choose the encoding: file_handle = open('somefile.txt', encoding='utf8')
If you know you want to only deal with bytes, you can explicitly set that: file_handle = open('somefile.ext', mode='rb')
I agree that it'd be nice to be able to see bytes vs strings errors at compile time, but this is difficult or even impossible with a duck-typed interpreted language like Python. IDEs and linters can make a good attempt at it, but they're not perfect.While rendering unsafe content still can be useful, I think Google better off added trusted-types as a tag attribute for WebComponents, like per-component CSP, and not into CSP header.
[1] https://docs.python.org/3/library/unicodedata.html#unicodeda...
https://github.com/w3c/webappsec-trusted-types/wiki/design-h...
For the longest time Firefox add-on developers were prohibited from submitting extensions with eval or innerHTML precisely because it is ~not safe~! Adding a bunch of browser-enforced regex checks to your strings is the wrong solution here. The solution is to not write code that writes itself.
You explicitly list the trusted types policies that you allow on your page, and if none of them declare the createHTML method then there is no way to set innerHTML (even inside some a new version of some obscure dependency of a dependency, or in some script that an intern plopped into the page, or in some five year old code that seemed reasonable at the time that you've totally forgotten about... (not like that happened to me recently))
FWIW, "store anything, sanitize at render time" is the preferred approach for some popular web frameworks, including Rails.
The big difference is that if you forget a place here you'll get a type error - rather than the current situation where if you forget to validate/sanitize you get an XSS attack.
You could do this by validating inputs with regexes, or you could make a safe-by-construction API, or you could not declare certain methods on your policies and thereby ban entire classes of unsafe API from your page.
The key idea is to have a small number of places that declare policies and generate trusted values. You can then audit just those places, because you know that all writes to unsafe DOM sinks must pass through those policies.