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

Explore granular enforcement of types #33

Closed
koto opened this issue Dec 1, 2017 · 4 comments
Closed

Explore granular enforcement of types #33

koto opened this issue Dec 1, 2017 · 4 comments

Comments

@koto
Copy link
Member

koto commented Dec 1, 2017

So far, TT are enforced per document - this is controlled by CSP. This issue is to explore whether more granular control is possible. This might serve two goals:

  1. Facilitate adoption - e.g. some 3rd party widgets (in separate scripts) may be outside of the application control, but introduce a low enough risk of actually introducing XSS, as estimated by the application owner.
  2. Control unsafe types creation (see Mechanism to constrain usages of unsafelyCreate #31). For certain applications, it's desirable to enable the type enforcement globally (per document), and introduce even stricter policies for most of the application code - i.e. only allow unsafelyCreate in certain code location (e.g. the ones defining the safe wrappers over unsafelyCreate) .

So far, a couple of mechanisms of doing so have been proposed:

  1. CSP source expressions - some examples:
  • require-trusted-types 'nonce-xyz123' could enable the enforcement for scripts with that nonce. require-trusted-types * would enable globally. This seems awkward, as the wider the whitelist, the more secure the system is, which is opposite to how other CSP directives work.
  • require-trusted-types; unsafe-disable-trusted-types: http://twitter.com/script.js seems more fitting.
  1. "use trusted-types" - just like Strict mode. This one is probably quite invasive in the language, but it could enable very precise script level (or even function level) enforcements.
@xtofian
Copy link

xtofian commented Dec 5, 2017

Additional approaches that have been proposed:

  • An "API nonce": unsafelyCreate requires an additional argument whose value has to match a nonce established via a header (it would be important that JS can't inspect this header, as IIUC is the case with CSP script nonces).

    Note that it is not strictly necessary to use a different nonce for every client (which would be undesirable from a caching perspective): In this scenario, the "adversary" is not a cross-origin attacker, but rather a (library) developer who chooses to call unsafelyCreate inappropriately (as defined by the dev organization's conventions/policies, e.g to require a security review). With that in mind, it might be sufficient to pick a nonce per build: This would be sufficient to prevent a third-party developer from calling unsafelyCreate without a user of that library being aware. Similarly, a build-time step (JS compiler/minifier) could choose a nonce, and insert the nonce in call sites that are on a whitelist (perhaps maintained by the group's security reviewer).

  • Approaches based on call stack inspection (i.e., maintain a whitelist of stack prefixes from which unsafelyCreate can be called).

  • provide all intended uses of unsafelyCreate (i.e. a complete set of builders required by the application) before any other code loads, and then delete TrustedHTML.unsafelyCreate; (see Mechanism to constrain usages of unsafelyCreate #31 (comment)).

Unless I'm missing something, the latter approach can be used to emulate the two former.

@xtofian
Copy link

xtofian commented Dec 6, 2017

How about a sort of object-capability approach:

  1. Provide a global function (say, window.trustedtypes.getFactory(). This function can be called at most once (for some definition of "once" relative to the document lifecycle).
  2. This function returns a TrustedTypesFactory when called the first time, and throws an exception (or returns null) when called again. Perhaps, the first call also enables trusted-types enforcement at the sinks (which cannot be turned off again once enabled).
  3. The factory has methods .createTrustedHTML, .createTrustedURL, etc, for each type.
  4. The types have no methods other than toString(), and have no data members.
  5. The types are unforgeable:
    • they have no exposed constructors; instances can only be created via .createTrusted* methods of the factory,
    • they cannot be subclassed, and the toString function cannot be reassigned
    • x instanceof TrustedHTML returns true iff x was created by calling the singleton factory's createTrustedHTML function

A reference to the factory represents the capability to create instances of the trusted types.

@koto @mikewest does this seem feasible?

With this in place, a builder library (e.g. a trusted-types-builders reference implementation as suggested in #32 (comment), or an application-specific library), are parameterized in the factory (of course, the libraries need be be implemented so as not to leak references to the factory).

An application's startup code calls trustedtypes.getFactory(), and instantiates the needed (and security-reviewed) builder libraries with the resulting factory, and then throws away the reference.

This approach is quite flexible: Aside from instantiating "closed", by-design-secure builder libraries, application startup code can also:

Desirable attributes of this approach:

  • The "call only once" mechanism alleviates concerns around having to initialize and lock down trusted-types builders early on: If some third-party library that our app depends on "grabs" the capability, our own app's initialization code will fail noisily.

  • third-party libraries/frameworks cannot expect to have access to unsafelyCreate, because there is none provided by the platform, and as noted above they can't simply "grab" the capability/factory. If they do need to create instances of TrustedTypes, the library either needs to depend on a common, reviewed library of builders (e.g. a trusted-types-builders reference implementation), or require the factory for instantiation. This is advantageous because it gives application owners control -- their app's initialization code needs to explicitly pass the capability to the library (which should be done only subject to review.; and will hopefully result in more reviewable code).

@koto
Copy link
Member Author

koto commented Jan 9, 2018

There's one significant downside to the object-capability approach here, which is that it assumes that all the dependencies are known at build-time, such that the reference to the canonical TT builder can be passed around. Without statically available builders no widgets (e.g. <script> with a Google +1 button) could write to DOM.

What about if the TrustedTypesFactory was actually a regular TrustedTypesSingleton that always returns the same instance? You could still:

const myPrecious = new TrustedTypeSingleton();
// configure myPrecious
delete TrustedTypeSingleton
TrustedTypeSingleton = () => { throw 'nope' }
myLib.initialize({tt: myPrecious});

to achieve the same means, but, by default, any 3rd party widget and library could obtain the reference to the builders in their established configuration.

@koto
Copy link
Member Author

koto commented Jun 4, 2018

Implemented (to some extent) with the policy approach of the new API. It's still document-specific, and not script-specific, but I can't find an easy way of making it more granular natively.

@koto koto closed this as completed Jun 4, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants