The [InjectionMitigated] WebIDL Attribute

A Collection of Interesting Ideas,

This version:
https://mikewest.github.io/injection-mitigated/
Issue Tracking:
GitHub
Inline In Spec
Editor:
Mike West (Google LLC.)

Abstract

This document defines the [InjectionMitigated] WebIDL attribute as a way of limiting the exposure of capabilities to those contexts which are sufficiently protected against injection attacks like XSS.

1. Introduction

This is a monkey-patch spec meant to outline the changes necessary to define an [InjectionMitigated] WebIDL attribute that limits the exposure of interfaces, attributes, methods, etc. to those context which sufficiently mitigate various forms of injection attack.

§ 3.1 What defenses does [InjectionMitigated] require? and § 3.2 What APIs should be marked as [InjectionMitigated]? provide useful context for the kinds of mitigation this document proposes, and the ways in which those mitigations ought to restrict the set of capabilities we offer to web developers.

This document sketches the following modifications to three specifications:

Let’s get to it.

2. Monkey Patches

2.1. Content Security Policy

In [CSP], we’ll define an algorithm for evaluating the strength of the amalgamation of policies contained within a CSP list. We’ll define a few supporting algorithms as well, but § 2.1.1 Does a policy meaningfully mitigate injection attacks? is the core entry point CSP will expose to HTML.

Note: § 3.1 What defenses does [InjectionMitigated] require? explains and justifies the threat model and constraints we believe are necessary to address it.

2.1.1. Does a policy meaningfully mitigate injection attacks?

With the characteristics described in § 3.1 What defenses does [InjectionMitigated] require? in mind, a CSP list policies is said to meaningfully mitigate injection attacks if the following algorithm returns "Meaningful":
  1. Let meets object requirements, meets base requirements, meets script requirements, and meets trusted type requirements be booleans whose value is false.

  2. For each policy in policies:

    1. If policy’s disposition is not "enforce" or policy’s source is not "header", continue.

    2. If policy sufficiently mitigates plugins, set meets object requirements to true.

    3. If policy sufficiently mitigates relative URL manipulation, set meets base requirements to true.

    4. If policy sufficiently mitigates script execution, set meets script requirements to true.

    5. If policy sufficiently mitigates DOM sinks, set meets trusted type requirements to true.

  3. Return "Meaningful" if meets object requirements, meets base requirements, meets script requirements, and meets trusted type requirements are all true.

  4. Return "Not meaningful enough".

2.1.2. Obtain the active directive for a type

CSP defines a fallback chain for some directives which we need to account for when evaluating a given policy. To obtain the active directive given a policy policy and a directive name:
  1. Let fallback chain be the result of executing Get fetch directive fallback list on directive name.

  2. For each name in fallback chain:

    1. If policy’s directive set contains a directive directive whose name is name, return directive.

  3. Return null.

2.1.3. Does a policy sufficiently mitigate plugins?

A policy policy sufficiently mitigates plugins if the following algorithm returns "Sufficient":
  1. Obtain active directive from policy, given "object-src".

  2. Return "Sufficient" if all of the following are true:

  3. Return "Not sufficient".

Note: This algorithm does not distinguish between policies with a disposition of "enforce" or "report".

2.1.4. Does a policy sufficiently mitigate relative URL manipulation?

A policy policy sufficiently mitigates relative URL manipulation if the following algorithm returns "Sufficient":
  1. For each directive in policy’s directive set:

    1. Return "Sufficient" if all of the following are true:

  2. Return "Not sufficient".

Note: This algorithm does not distinguish between policies with a disposition of "enforce" or "report".

2.1.5. Does a policy sufficiently mitigate script execution?

A policy policy sufficiently mitigates script execution if the following algorithm returns "Sufficient":
  1. Obtain element directive from policy, given "script-src-elem".

  2. If element directive is null, return "Not sufficient".

  3. Let strict-dynamic and insufficient mitigation unless strict-dynamic is specified be false.

  4. Let hash-or-nonce and insufficient mitigation unless hash-or-nonce or strict-dynamic is specified be false.

  5. For each source expression in element directive’s value:

    1. Set strict-dynamic to true if source expression is an ASCII case-insensitive match for the string "'strict-dynamic'".

    2. Set insufficient mitigation unless strict-dynamic is specified to true if any of the following conditions are met:

    3. Set hash-or-nonce to true if source expression matches the nonce-source or hash-source.

    4. Set insufficient mitigation unless hash-or-nonce or strict-dynamic is specified to true if source expression is an ASCII case-insensitive match for the string "'unsafe-inline'".

  6. Return "Sufficient" if all of the following are true:

    • insufficient mitigation unless strict-dynamic is specified is false or strict-dynamic is true.

    • insufficient mitigation unless hash-or-nonce or strict-dynamic is specified is false or strict-dynamic is true or hash-or-nonce is true.

  7. Return "Not sufficient".

Note: This algorithm does not distinguish between policies with a disposition of "enforce" or "report".

2.1.6. Does a policy sufficiently mitigate DOM sinks?

A policy policy sufficiently mitigates DOM sinks if the following algorithm returns "Sufficient":
  1. For each directive in policy’s directive set:

    1. Return "Sufficient" if all of the following are true:

  2. Return "Not sufficient".

Note: This algorithm does not distinguish between policies with a disposition of "enforce" or "report".

2.2. HTML

In HTML, we’ll use the algorithms described in § 2.1 Content Security Policy to define characteristics of the environment settings object that will be examined from [WEBIDL] when determining whether or not a given IDL construct is exposed on the associated global object.

An environment settings object is said to meaningfully mitigate injection attacks if its policy container's CSP list meaningfully mitigates injection attacks.

Note: Because the definition of meaningful injection mitigation for a CSP list depends only upon the header-delivered policies, this property will not mutate during an environment’s lifetime.

2.3. WebIDL

In WebIDL, we’ll define the [InjectionMitigated] attribute, and wire it up to the hook created in HTML above:

2.3.1. [InjectionMitigated]

If the [InjectionMitigated] extended attribute appears on an interface, partial interface, interface mixin, partial interface mixin, callback interface, namespace, partial namespace, interface member, interface mixin member, or namespace member, it indicates that the construct is exposed only within an environment which can meaningfully mitigate injection attacks. The [InjectionMitigated] extended attribute must not be used on any other construct.

The [InjectionMitigated] extended attribute must take no arguments.

It might be reasonable to parameterize this attribute, either because we think that a subset of the protections defined in this document are easier to deploy (e.g. strict CSP but not Trusted Types), or because we have different characteristics in mind for different contexts (e.g. Isolated Web Apps). See the issues called out in § 3.1 What defenses does [InjectionMitigated] require? below for more thoughts.

If [InjectionMitigated] appears on an overloaded operation, then it must appear on all overloads.

The [InjectionMitigated] extended attribute must not be specified both on

Note: This is because adding the [InjectionMitigated] extended attribute on a member when its containing definition is also annotated with the [InjectionMitigated] extended attribute does not further restrict the exposure of the member.

An interface without the [InjectionMitigated] extended attribute must not inherit from another interface that does specify [InjectionMitigated].

The following IDL fragment defines an interface with one operation that is executable from all contexts, and two which are executable only from contexts with meaningful injection attack mitigation:
[Exposed=Window]
interface ExampleFeature {
  // This call will succeed in all contexts.
  Promise <Result> doBoringThing();

  // This operation will not be exposed to context that lacks sufficient mitigation against
  // injection attack. In such a context, there will be no "doPowerfulThing" property on
  // ExampleFeature.prototype.
  [InjectionMitigated] Promise<Result> doPowerfulThing();

  // The same applies here: the attribute will not be exposed to an unprotected context,
  // and in such a context there will be no "secretBoolean" property on
  // ExampleFeature.prototype.
  [InjectionMitigated] readonly attribute boolean secretBoolean;
};

2.3.2. Patches to the "exposed" algorithm

WebIDL’s exposed algorithm is adjusted as follows, adding a single step after similarly handling [CrossOriginIsolated] (step 4 below).

An interface, callback interface, namespace, or member construct is exposed in a given realm realm if the following steps return true:
  1. If construct’s exposure set is not *, and realm.[[GlobalObject]] does not implement an interface that is in construct’s exposure set, then return false.
  2. If realm’s settings object is not a secure context, and construct is conditionally exposed on [SecureContext], then return false.
  3. If realm’s settings object's cross-origin isolated capability is false, and construct is conditionally exposed on [CrossOriginIsolated], then return false.
  4. If realm’s settings object's does not meaningfully mitigate injection attacks, and construct is conditionally exposed on [InjectionMitigated], then return false.
  5. Return true.

3. Implementation Considerations

3.1. What defenses does [InjectionMitigated] require?

If we start from a threat model in which an attacker can cause a server to "reflect" unexpected content directly into the body of any given response, or manipulate the inputs to client-side code (DOM APIs and otherwise), we can point to five characteristics of a Content Security Policy that gives developers a reasonable chance of avoiding unexpected script execution:

  1. At least one of the policies in the list enforces a restriction on plugin content via object-src 'none'.

    Is this necessary anymore? Chrome certainly has weird things like Native Client, at least in Chrome App and Extensions contexts. Those seem separable from the web, though. We should look into browser behavior here, as it might be possible to simplify the recommendation.

  2. At least one of the policies in the list enforces a restriction on modifying the document base URL via base-uri 'none' or base-uri 'self'. This prevents attackers from injecting a base element that could maliciously push scripts specified with relative URLs (e.g. <script src="/app.js">) out to an attacker-controlled server.

  3. At least one of the policies in the list enforces a restriction on script execution that relies on nonces and/or hashes rather than URLs. Research like [long-live-csp] has shown URL-based allowlisting to be quite ineffective at creating meaningful protection, while content-based allowlisting (hashes) or element tagging (nonces) are far more robust.

    Isolated Web Apps are an example of a context that can get away with a looser policy for this aspect of the story (e.g. script-src 'self') because of other layers of injection mitigation (in this case the requirement that script be staticly embedded in the app’s package). Perhaps we should expand the restriction to include that case as well?

  4. At least one of the policies in the list enforces [Trusted-Types] via require-trusted-types-for 'script'.

    It might be helpful for deployment to make this last requirement optional while user agents are still in the process of implementing bits and pieces of trusted types. We could parameterize the IDL attribute, for example (e.g. [InjectionMitigated=Basic] vs [InjectionMitigated=RequireTrustedTypes]), though I’ve no idea how we’d spell out a meaningful bar that would allow folks creating APIs to decide which they’d prefer.

  5. Each of the above characteristics is true in a way provably prior to any potential code execution. This boils down to ensuring that the policies creating the relevant constraints are delivered via headers that are applied at the time the environment settings object is created, and are not added later via meta tags.

These characteristics aren’t pulled from thin air, but are the result of a good deal of experimentation and research over the years since CSP was introduced. [long-live-csp] is a seminal paper on the topic.

Policies matching these requirements are deployed at scale today, and are emperically proven to be fairly robust defenses.

Anne noted in a previous iteration of this proposal that it differs meaningfully from [SecureContext] and [CrossOriginIsolated] insofar as it doesn’t require anything of a context’s ancestors. That is, we’re evaluting the injection mitigation of a specific document or worker or etc, and would expose an IDL construct there regardless of whether that context’s parent also met the requirements. Should we apply the same considerations of ancestral risk that we took into account when defining Secure Contexts? Perhaps! [Issue #mikewest/securer-contexts#1]

3.2. What APIs should be marked as [InjectionMitigated]?

Ideally, we’d be able to apply [InjectionMitigated] broadly, covering a broad spectrum of capabilities to ensure that they’re used for the purposes that users and developers alike might expect.

Realistically, applying the attribute to existing APIs is going to be a difficult sell (similar to the introduction of the [SecureContext] attribute). We’ll want to pick our battles.

The highest-priority APIs are those that grant access to capabilities that sit somewhat outside the web’s general origin-based security model. Device capabilities like [WebUSB], OS primitives like the clipboard, and user location are all good examples of powerful and potentially dangerous capabilites that we’d really like to ensure are used by the site to whom they’re granted, and not to anyone who can trick the site into executing script.

More broadly, any API that requires user permission seems like a valid target for this new attribute. When user agents gain confidence that code running on an origin is code that origin intended to execute, we can make a much more reasonable claim about the exclusivity associated with a user’s choice to grant a capability to that origin, and thereby ship new capabilities more safely.

Conformance

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[CSP]
Mike West; Antonio Sartori. Content Security Policy Level 3. URL: https://w3c.github.io/webappsec-csp/
[ECMASCRIPT]
ECMAScript Language Specification. URL: https://tc39.es/ecma262/multipage/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[Trusted-Types]
Krzysztof Kotowicz. Trusted Types. URL: https://w3c.github.io/trusted-types/dist/spec/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/

Informative References

[LONG-LIVE-CSP]
Lukas Weichselbaum; et al. CSP Is Dead, Long Live CSP! On the Insecurity of Whitelists and the Future of Content Security Policy. 24 October 2016. URL: https://dl.acm.org/doi/10.1145/2976749.2978363
[SECURER-CONTEXTS]
Mike West. Securer Contexts. URL: https://github.com/mikewest/securer-contexts
[STRICT-CSP]
Lukas Weichselbaum. Mitigate cross-site scripting (XSS) with a strict Content Security Policy (CSP). 15 March 2021. URL: https://web.dev/strict-csp/
[WebUSB]
WebUSB API. cg-draft. URL: https://wicg.github.io/webusb/

Issues Index

It might be reasonable to parameterize this attribute, either because we think that a subset of the protections defined in this document are easier to deploy (e.g. strict CSP but not Trusted Types), or because we have different characteristics in mind for different contexts (e.g. Isolated Web Apps). See the issues called out in § 3.1 What defenses does [InjectionMitigated] require? below for more thoughts.
Is this necessary anymore? Chrome certainly has weird things like Native Client, at least in Chrome App and Extensions contexts. Those seem separable from the web, though. We should look into browser behavior here, as it might be possible to simplify the recommendation.
Isolated Web Apps are an example of a context that can get away with a looser policy for this aspect of the story (e.g. script-src 'self') because of other layers of injection mitigation (in this case the requirement that script be staticly embedded in the app’s package). Perhaps we should expand the restriction to include that case as well?
It might be helpful for deployment to make this last requirement optional while user agents are still in the process of implementing bits and pieces of trusted types. We could parameterize the IDL attribute, for example (e.g. [InjectionMitigated=Basic] vs [InjectionMitigated=RequireTrustedTypes]), though I’ve no idea how we’d spell out a meaningful bar that would allow folks creating APIs to decide which they’d prefer.
Anne noted in a previous iteration of this proposal that it differs meaningfully from [SecureContext] and [CrossOriginIsolated] insofar as it doesn’t require anything of a context’s ancestors. That is, we’re evaluting the injection mitigation of a specific document or worker or etc, and would expose an IDL construct there regardless of whether that context’s parent also met the requirements. Should we apply the same considerations of ancestral risk that we took into account when defining Secure Contexts? Perhaps! [Issue #mikewest/securer-contexts#1]