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:
-
[CSP] will define the characteristics of a policy that’s robust enough to meaningfully defend against attack. It builds on what we’ve learned from explorations like [strict-csp] and [securer-contexts], pushing developers towards well-understood and valuable defenses.
-
[HTML] will define the ways in which those characteristics are evaluated within a given context, similar conceptually to secure context and cross-origin isolated capability.
-
[WEBIDL] will define the
[InjectionMitigated]
attribute, and the way it relies on the changes above to control the exposure of a given WebIDL construct.
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?
Meaningful
":
-
Let meets object requirements, meets base requirements, meets script requirements, and meets trusted type requirements be booleans whose value is
false
. -
For each policy in policies:
-
If policy’s disposition is not "
enforce
" or policy’s source is not "header
", continue. -
If policy sufficiently mitigates plugins, set meets object requirements to
true
. -
If policy sufficiently mitigates relative URL manipulation, set meets base requirements to
true
. -
If policy sufficiently mitigates script execution, set meets script requirements to
true
. -
If policy sufficiently mitigates DOM sinks, set meets trusted type requirements to
true
.
-
-
Return "
Meaningful
" if meets object requirements, meets base requirements, meets script requirements, and meets trusted type requirements are alltrue
. -
Return "
Not meaningful enough
".
2.1.2. Obtain the active directive for a type
-
Let fallback chain be the result of executing Get fetch directive fallback list on directive name.
-
For each name in fallback chain:
-
If policy’s directive set contains a directive directive whose name is name, return directive.
-
-
Return null.
2.1.3. Does a policy sufficiently mitigate plugins?
Sufficient
":
-
Obtain active directive from policy, given "
object-src
". -
Return "
Sufficient
" if all of the following are true:-
active directive is not null
-
active directive’s value[0] is an ASCII case-insensitive match for the string "
'none'
".
-
-
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?
Sufficient
":
-
For each directive in policy’s directive set:
-
Return "
Sufficient
" if all of the following are true:-
directive’s name is "
base-uri
". -
directive’s value[0] is an ASCII case-insensitive match for either the string "
'none'
" or the string "'self'
".
-
-
-
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?
Sufficient
":
-
Obtain element directive from policy, given "
script-src-elem
". -
If element directive is null, return "
Not sufficient
". -
Let strict-dynamic and insufficient mitigation unless strict-dynamic is specified be
false
. -
Let hash-or-nonce and insufficient mitigation unless hash-or-nonce or strict-dynamic is specified be
false
. -
For each source expression in element directive’s value:
-
Set strict-dynamic to
true
if source expression is an ASCII case-insensitive match for the string "'strict-dynamic'
". -
Set insufficient mitigation unless strict-dynamic is specified to
true
if any of the following conditions are met:-
source expression is an ASCII case-insensitive match for the string "
'self'
". -
source expression is an ASCII case-insensitive match for the string "
'unsafe-eval'
". -
source expression matches the host-source grammar.
-
source expression matches the scheme-source grammar.
-
-
Set hash-or-nonce to
true
if source expression matches the nonce-source or hash-source. -
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'
".
-
-
Return "
Sufficient
" if all of the following are true:-
insufficient mitigation unless strict-dynamic is specified is
false
or strict-dynamic istrue
. -
insufficient mitigation unless hash-or-nonce or strict-dynamic is specified is
false
or strict-dynamic istrue
or hash-or-nonce istrue
.
-
-
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?
Sufficient
":
-
For each directive in policy’s directive set:
-
Return "
Sufficient
" if all of the following are true:-
directive’s name is "
require-trusted-types-for
". [TRUSTED-TYPES] -
directive’s value contains[0] an ASCII case-insensitive match for the string "
'script'
".
-
-
-
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.
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
-
an interface member and its interface or partial interface;
-
an interface mixin member and its interface mixin or partial interface mixin;
-
a namespace member and its namespace or partial namespace.
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
].
[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).
- 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. - If realm’s settings object is not a secure context, and construct is conditionally exposed on [
SecureContext
], then return false. - If realm’s settings object's cross-origin isolated capability is false, and construct is conditionally exposed on [
CrossOriginIsolated
], then return false. -
If realm’s settings object's does not meaningfully mitigate injection attacks, and construct is conditionally exposed on [
InjectionMitigated
], then returnfalse
. - 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:
-
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.
-
At least one of the policies in the list enforces a restriction on modifying the document base URL via
base-uri 'none'
orbase-uri 'self'
. This prevents attackers from injecting abase
element that could maliciously push scripts specified with relative URLs (e.g.<script src="/app.js">
) out to an attacker-controlled server. -
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? -
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. -
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.
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. Identity primitives like Digital Credentials (see wicg/digital-credentials#133). 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.
4. Security Considerations
4.1. Same-origin Documents
The rules associated with "meaningfully mitigate injection attacks"
differ in one important way from those associated with [SecureContext
] and
[CrossOriginIsolated
]. Those attributes ensure not only that a given
document is protected, but also set up constraints that prevent other,
same-origin documents from accessing a protected document.
[SecureContext
] requires that a document and its ancestors be delivered
securely. [MIX] implies, therefore, that any nested document in its frame
tree will also be delivered securely, as non-secure frames would be blocked
or upgraded. Similarly, any other document that has access to the secure
context through a window handle must likewise be delivered securely, as the
scheme forms part of the origin. Any API gated on [SecureContext
], then,
is fairly well protected from access in non-secure contexts.
NOTE: I think the only scenario that allows this access would be a secure
context that pops up a non-secure context that then frames the original
context’s origin. The framed document would not itself be a secure context,
but could reach through its parents' opener
to access otherwise
protected data. Are there more?
Similarly, [CrossOriginIsolated
] requires COEP and COOP constraints that
separate same-origin documents with different policies into distinct agent clusters, preventing them from accessing each other. This is a
robust protection against a non-COI context gaining access to an API that’s
gated on [CrossOriginIsolated
].
[InjectionMitigated
], on the other hand, does not require anything in
particular from its embedders or embedees, nor does it prevent a same-origin
document elsewhere from gaining a handle to its window and thereby accessing
the otherwise-protected API.
Should we apply the same considerations [SECURE-CONTEXTS] described in Secure Contexts § 4.2 Ancestral Risk? Should we go further, requiring some COOP
variant like noopener-allow-popups
? Even further, forcing agent cluster
separation for injection-mitigated contexts?
Maybe? It’s certainly a hole. It’s not clear to me whether it’s a hole worth paying the probable cost to ease of deployment (COI hasn’t exactly caught the world by storm). WDYT?
This is an open decision point that Anne raised in a previous version of this proposal. We should hammer it out, one way or the other. [Issue #mikewest/securer-contexts#1]