- Blocking credentials
- Blocking normal link decoration
- Blocking privileged link decoration
- Privileged server-side collaboration
- Appendices
This explainer attempts to solve the problems identified in issues #422 and #423. Specifically, we're worried that a distributor might be able to use the fact that it can choose which exchange or package, signed by a publisher, to serve in response to a client's request, to transfer its notion of the user's identity to the publisher.
The approach this document takes is to prevent the distributor from accessing its notion of the user's identity. There are several routes by which a distributor could learn the user's identity:
- The browser could send the distributor its top-level credentials. Countermeasures
- The source of a link could use the same link-decoration tools as it might use in a cross-origin link, which other efforts are blocking. Note that the source of a link and the distributor of a package could be the same origin. Countermeasures
- The source of a link could decorate links to its distributor to send a user ID in ways an unrelated cross-origin target couldn't be expected to receive. Countermeasures
- The source of a link could collaborate with its distributor on the server side to send a user ID in ways an unrelated cross-origin target couldn't be induced to collaborate. Discussion
These are tackled individually in the following sections.
The navigation to a signed package (or exchange) must be done without
credentials. This is roughly credentials
mode ==
"omit"
, with changes to Fetch to make it work for navigations, but it may need
additional changes to segregate the connection from other uncredentialed
requests that might have included identifying information.
There are security concerns for allowing an attacker to load an arbitrary site without credentials, so if the target of an uncredentialed navigation isn't a signed package, the navigation should fail.
There are lots of possible spellings for a navigation that omits credentials:
The first two options say generically that the referrer can choose whether to enter a link target with or without credentials. The rest attempt to avoid changing the semantics of top-level navigations while still getting package fetches to omit credentials.
An attribute to set credentials-mode to "omit"
<a href="https://target" credentials="omit">
This seems like the most straightforward way to say what we currently want, but it doesn't automatically adapt if we later decide that package navigations should differ from other navigations in an additional way.
An attribute to set the init parameter to fetch()
<a href="https://target" fetchoptions='{"credentials": "omit"}'>
<a href="https://target" fetchoptions="credentials: omit">
We'd add this to all fetch-causing elements, which would unify the current
crossorigin=""
, referrpolicy=""
, and integrity=""
attributes, and be a
better future extension point. The two hard parts here are
- Getting people to swallow putting JSON in an attribute, or alternately coming up with a new microsyntax that has enough capabilities.
- Checking all of the options for possible security holes.
This is either more attractive than the credentials
attribute because it solves a larger class of problems, or
less attractive because it introduces more possible avenues of abuse.
<a href="https://sxg" ispackage>
Just tell the browser directly to expect a package. For now, this would do the same thing as credentials="omit" above.
<a href="https://signed_inner_url">
If there has already been a prenavigate that found a package holding the mentioned URL, it uses the content of that package for the navigation. If there hasn't been such a prenavigate, it acts like a normal link.
<a href="https://package/path" publisherurl="https://signed/inner/url">
An attribute declares the expected start_url
of the package (or inner url of a
signed exchange). If these don't match, the navigation
fails.
<a href="https://signed/inner/url" fetchfrom="https://package/path">
This naturally works on browsers that haven't implemented packages. It also allows any browser to skip the package if it wants to make sure the publisher is notified/checked/etc. Again, if the package's content doesn't match the href, the navigation fails.
<a href="https://signed/inner/url" distributor="https://distributor.origin">
This relies on us defining a single path within distributor.origin
that serves
a given signed URL, as Blocking privileged link
decoration suggests. It has similar
properties to the fetchfrom
option otherwise.
This is neither better or worse from the fetchfrom
attribute from a technical perspective: fetchfrom
can
reject paths that don't match the one required by Blocking privileged link
decoration, and if we ever decide to
relax the single-path restriction, the distributor
attribute could begin to
allow a full path.
<a href="https://target" crossorigin="no-credentials">
This would have the same semantics as <a href="https://target" credentials="omit">
, but it would also apply to same-origin navigations. It
seems really confusing to use a "crossorigin" attribute to modify same-origin
navigations.
<a href="package://package">
Basically encodes the ispackage
flag into the scheme of the URL. The package
is fetched by changing the scheme to https
. We're developing a more general
package scheme, but
as it's useful for unsigned packages, which don't necessarily need the same
restrictions as signed package, it seems like the scheme won't do the right
thing.
Same-origin links to packages should use the same restrictions as cross-origin links to non-packages.
A link source run by the same entity as the distributor can encode a user ID in ways that might break an arbitrary publisher's serving code. So, we need to define a deterministic function from the inner URL to the acceptable URL within a distributor used to fetch that inner URL's package.
The two main options are
https://distributor.origin/.well-known/package/<hash(innerUrl)>
https://distributor.origin/.well-known/package/innerAuthority/innerPath?innerQuery
The hash-based path is a bit easier to compute, but leaving the inner URL mostly intact makes it easier for a human to guess where the link is going to go.
The primary mechanism by which a distributor's server could collaborate with a
link source's server is by deciding that if there's a click by user A
at time
T
on a link to https://distributor.example/package
, then a subsequent
uncredentialed load of that package at time T+ε
is probably user A
. Servers
can optionally refine this by limiting it to clicks and loads from the same IP
address. This same mechanism allows cross-origin user ID transfer between
arbitrary servers, and we don't know any ways to block it in that context, so we
only need to make the package-assisted transfer at least as difficult as the
cross-server transfer.
We know of two ways for a distributor to pass their user ID onto the publisher of a package, and in either case—the publisher shares their signing key with the distributor, or the publisher generates 2^N variants of a package to encode N bits of a user ID—it seems safer or simpler for the publisher to share their logs instead.
There are worries that uncredentialed navigation could be used in an attack on the target site, since the main request won't send or save cookies, but subrequests will. The only concrete attacks we know of involve surprising users instead of stealing data or executing malicious code, but we'd like to prevent those surprises anyway.
Similar worries came up in the discussion of cross-origin prefetch, which led to
the new "prenavigate"
operation, for which the target site opts in using an
Allow-Uncredentialed-Navigation
header.
We think the act of creating a signed exchange is an equivalent opt-in.
If a link declares that its target is a package or has a particular inner URL, and then the actual response isn't a package or has a different inner URL, what should the browser do?
- If it fails the navigation with a network error, link sources would quickly discover their error and correct it, but it means that a distributor could never change the content of a URL that once contained a package.
- If it reloads the outer URL as a normal navigation or redirects to the claimed inner URL, that wastes network traffic and time, but gives users a smoother experience.
Security folks generally prefer to fail fast, so the rest of this explainer suggests that option.