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

Turn UA into a list #46

Merged
merged 4 commits into from
Jan 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 42 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ challenges in mind with the proposal that follows.

## A Proposal

By default, servers should receive only the user agent's brand and major version number. Servers
By default, servers should receive only the user agent's brand and significant version number. Servers
yoavweiss marked this conversation as resolved.
Show resolved Hide resolved
can opt-into receiving information about minor versions, the underlying operating system's major
version, and details about the underlying architecture and device. The user agent can make
reasonable decisions about how to respond to sites' requests for more granular data. We might
Expand Down Expand Up @@ -95,10 +95,10 @@ accomplish this as follows:

3. Browsers should introduce several new Client Hint header fields:

1. The `Sec-UA` header field represents the user agent's brand and major version. For example:
1. The `Sec-UA` header field represents the user agent's brand and significant version. For example:

```http
Sec-CH-UA: "Chrome 73"
Sec-CH-UA: "Chrome"; v="73"
```

Note: See the GREASE-like discussion below for how we could anticipate the inevitable lies
Expand All @@ -107,7 +107,7 @@ accomplish this as follows:
2. The `Sec-CH-UA-Platform` header field represents the platform's brand and major version. For example:

```http
Sec-CH-UA-Platform: "Win 10"
Sec-CH-UA-Platform: "Win"; v="10"
```

3. The `Sec-CH-UA-Arch` header field represents the underlying architecture's instruction set and
Expand Down Expand Up @@ -138,15 +138,20 @@ accomplish this as follows:
interface mixin NavigatorUA {
[SecureContext] Promise<NavigatorUAData> getUserAgent();
};

Navigator includes NavigatorUA;

dictionary NavigatorUABrandVersionDict {
required DOMString brand; // "Chrome"
DOMString version; // "69"
};

interface NavigatorUAData {
readonly attribute DOMString brand; // "Chrome"
readonly attribute DOMString version; // "69"
readonly attribute DOMString platform; // "Win 10"
readonly attribute DOMString architecture; // "ARM64"
readonly attribute DOMString model; // ""
readonly attribute bool mobile; // false
readonly attribute FrozenArray<NavigatorUABrandVersionDict> brand; // [ { brand: "Chrome", version: "69" } ]
readonly attribute boolean mobile; // false
readonly attribute NavigatorUABrandVersionDict platform; // { brand: "Win", version: "10" }
readonly attribute DOMString architecture; // "ARM64"
readonly attribute DOMString model; // ""
};
```

Expand All @@ -157,7 +162,7 @@ accomplish this as follows:
approach.

User agents will attach the `Sec-CH-UA` header to every secure outgoing request by default, with a value
that includes only the major version (e.g. "`Chrome 69`"). Servers can opt-into receiving more
that includes only the significant version (e.g. `"Chrome"; v="69"`). Servers can opt-into receiving more
detailed version information in the `Sec-CH-UA` header, along with the other available Client Hints, by
delivering an `Accept-CH` header header in the usual way.

Expand All @@ -172,7 +177,7 @@ A user agent's initial request to `https://example.com` will include the followi
```http
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/71.1.2222.33 Safari/537.36
Sec-CH-UA: "Chrome 74"
Sec-CH-UA: "Chrome"; v="74"
```

If a server delivers the following response header:
Expand All @@ -186,8 +191,8 @@ Then subsequent requests to `https://example.com` will include the following req
```http
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/71.1.2222.33 Safari/537.36
Sec-CH-UA: "Chrome 74.0.3424.124"
Sec-CH-UA-Platform: "macOS 12"
Sec-CH-UA: "Chrome"; v="74.0.3424.124"
Sec-CH-UA-Platform: "macOS"; v="12"
Sec-CH-UA-Arch: "ARM64"
```

Expand Down Expand Up @@ -216,20 +221,27 @@ Locking the User-Agent string will lock in the existing behavior of UA-sniffing
may break things in the future if we diverge significantly from today's behavior in some
interesting way, but that doesn't seem like a risk unique to this proposal.

## Should the UA string be a set?
## Should the UA string really be a set?

Yes!

History has shown us that there are real incentives for user agents to lie about their branding
in order to thread the needle of sites' sniffing scripts. While I'm optimistic that we can reset
expectations around sniffing by freezing the thing that's sniffed-upon today, and creating a sane
set of options for developers, it's likely that this is hopelessly naive. It's reasonable to
ponder what we should do to encourage sniffing in the right way, if we believe it's going to
happen one way or another.
History has shown us that there are real incentives for user agents to lie
about their branding in order to thread the needle of sites' sniffing scripts,
and prevent their users from being blocked by UA-based allow/block lists.

One suggestion is to model `UA` as a set, rather than a single entry. For example, user agents
might encourage standardized processing of the `UA` string by randomly including additional,
intentionally incorrect, comma-separated entries with arbitrary ordering (similar conceptually
to TLS's [GREASE][4]). Chrome 73's `UA` value might then be `"Chrome 73", "NotBrowser 12"`, or
`"BrowsingIsFun Version 12b", "Chrome 73"`.
Reseting expectations may help to prevent abuse of the UA string's brand in the
short term, but probably won't help in the long run.

Having `UA` be a set enables browsers to express their brand, as well as their
different equivalence sets. We hope that this enabled expressiveness will
enable sites to differentially serve based on capabilities while minimizing
wrongfully categorizing browsers in the wrong buckets.

Complementary to that, user agents might encourage standardized processing of
the `UA` string by randomly including additional, intentionally incorrect,
comma-separated entries with arbitrary ordering (similar conceptually to TLS's
[GREASE][4]). Chrome 73's `UA` value might then be `"Chrome"; v="73"`,
`"NotBrowser"; v="12"`, or `"BrowsingIsFun"; v="12b", "Chrome"; v="73"`.

We'd reflect this value in the `NavigatorUAData.brand` attribute.

Expand All @@ -244,14 +256,11 @@ cases?

There are a few options for the string:

1. `"Chrome 73"`, which has the least entropy, but also sets poor expectations.
2. `"CriOS 73"` (or `"Chrome on iOS 73"`, or similar) which is basically what's sent today, and categorizes the browser as distinct.
3. `"CriOS 73", "Safari 12"`, which is interesting.
4. `"Chrome 73", "Safari 12"`, which is more interesting.

1. `"Chrome"; v="73"`, which has the least entropy, but also sets poor expectations.
2. `"CriOS"; v="73"` (or `"Chrome on iOS", v="73"`, or similar) which is basically what's sent today, and categorizes the browser as distinct.
3. `"CriOS"; v="73", "Safari"; v="12"`, which is interesting.
4. `"Chrome"; v="73", "Safari";v="12"`, which is more interesting.

(A more verbose alternative could add a `Sec-CH-UA-Engine` header, containing values like `Blink`,
`EdgeHTML`, `Gecko`, or `WebKit`.)

## Wait a minute, where is the Client Hints infrastructure specified?

Expand Down
51 changes: 31 additions & 20 deletions index.bs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Repository: wicg/ua-client-hints
Shortname: ua-client-hints
Level: None
Editor: Mike West 56384, Google Inc., mkwst@google.com
Editor: Yoav Weiss 58673, Google Inc., yoav@yoav.ws
Abstract:
This document defines a set of Client Hints that aim to provide developers with the ability to
perform agent-based content negotiation when necessary, while avoiding the historical baggage and
Expand Down Expand Up @@ -142,7 +143,7 @@ A user navigates to `https://example.com/` for the first time. Their user agent
header along with the HTTP request:

``` http
Sec-CH-UA: "Examplary Browser 73"
Sec-CH-UA: "Examplary Browser"; v="73"
```

The server is interested in rendering content consistent with the user's underlying platform, and
Expand All @@ -157,8 +158,8 @@ In response, the user agent includes more detailed version information, as well
the underlying platform in the next request:

``` http
Sec-CH-UA: "Examplary Browser 73.3R8.2H.1"
Sec-CH-UA-Platform: "Windows 10"
Sec-CH-UA: "Examplary Browser"; v="73.3R8.2H.1"
Sec-CH-UA-Platform: "Windows"; v="10"
```


Expand All @@ -171,7 +172,7 @@ in [[I-D.ietf-httpbis-client-hints]]. The definitions below assume that each use
a number of properties for itself:

* <dfn for="user agent" export>brand</dfn> (for example: "cURL", "Edge", "The World's Best Web Browser")
* <dfn for="user agent" export>major version</dfn> (for example: "72", "3", or "28")
* <dfn for="user agent" export>significant version</dfn> (for example: "72", "3", or "28")
* <dfn for="user agent" export>full version</dfn> (for example: "72.0.3245.12", "3.14159", or "297.70E04154A")
* <dfn for="user agent" export>platform brand and version</dfn> (for example: "Windows NT 6.0", "iOS 15", or "AmazingOS 17G")
* <dfn for="user agent" export>platform architecture</dfn> (for example: "ARM64", or "ia32")
Expand Down Expand Up @@ -226,6 +227,10 @@ The header's ABNF is:
Sec-CH-UA-Platform = sh-string
```

Note: The `sh-string` above can include a `v` parameter, indicating the platform's version.

ISSUE: Add processing model here that creates the header using SH serialization.

The 'Sec-CH-UA' Header Field {#sec-ch-ua}
----------------------------

Expand All @@ -239,9 +244,11 @@ The header's ABNF is:
Sec-CH-UA = sh-list
```

Note: The `sh-list` above is a list of `sh-string`, each MAY include a `v` parameter, indicating the user agent version.

Unlike most Client Hints, the `Sec-CH-UA` header will be sent with all requests, whether or not the
server opted-into receiving the header via an `Accept-CH` header. Prior to an opt-in, however, it
will include only the user agent's branding information, and the major version number (both of which
will include only the user agent's branding information, and the significant version number (both of which
are fairly clearly sniffable by "examining the structure of other headers and by testing for the
availability and semantics of the features introduced or modified between releases of a particular
browser" [[Janc2014]]).
Expand All @@ -253,13 +260,10 @@ user agents MUST:
1. Let |value| be a [=Structured Header=] object whose value is a [=structured header/list=].

2. Let |version| be the user agent's [=user agent/full version=] if |set|
[=list/contains=] `UA`, and the user agent's [=user agent/major version=] otherwise.
[=list/contains=] `UA`, and the user agent's [=user agent/significant version=] otherwise.

3. Let |ua| be a string whose value is the [=string/concatenation=] of the user agent's
[=user agent/brand=], a U+0020 SPACE character, and |version|.

ISSUE(wicg/ua-client-hints#7): Should we split the version out into a separate
`Sec-CH-UA-Version` header? Or keep it here?
[=user agent/brand=], the string ";v=", and |version|.

4. [=list/Append=] |ua| to |value|.

Expand All @@ -274,6 +278,10 @@ user agents MUST:

6. Return |value|.

Issue: Fix the processing model here so that it will use Structured Headers
serialization, and make sure that the value set for headers if the same as the
one returned from the JS API.

The 'Sec-CH-UA-Mobile' Header Field {#sec-ch-mobile}
--------------------------------

Expand All @@ -297,15 +305,20 @@ Interface {#interface}
=================

<pre class="idl">
dictionary NavigatorUABrandVersionDict {
required DOMString brand;
DOMString version;
};

[Exposed=Window]
interface NavigatorUAData {
readonly attribute DOMString brand;
readonly attribute DOMString version;
readonly attribute DOMString platform;
readonly attribute FrozenArray&lt;NavigatorUABrandVersionDict&gt; brand;
readonly attribute boolean mobile;
readonly attribute NavigatorUABrandVersionDict platform;
readonly attribute DOMString architecture;
readonly attribute DOMString model;
readonly attribute boolean mobile;
};

interface mixin NavigatorUA {
[SecureContext] Promise&lt;NavigatorUAData&gt; getUserAgent();
};
Expand Down Expand Up @@ -333,14 +346,12 @@ Processing model {#processing}
:: The user agent's [=user agent/model=].
: {{NavigatorUAData/mobile}}
:: The user agent's [=user agent/mobileness=].
: {{NavigatorUAData/version}}
:: The user agent's [=user agent/full version=].

2. [=Resolve=] |p| with |UAData|.

3. Return |p|.

ISSUE: Provide a method to only access the UA's major version.
ISSUE: Provide a method to only access the UA's significant version.

Security and Privacy Considerations {#security-privacy}
===================================
Expand Down Expand Up @@ -409,13 +420,13 @@ could encourage standardized processing of the `UA` string, by randomly
including additional, intentionally incorrect, comma-separated entries with
arbitrary ordering. That would reduce the chance that we ossify on a few
required strings. For example, Chrome 73's `Sec-CH-UA` header might be
`"Chrome 73", "NotBrowser 12"`, `"BrowsingIsFun Version 12b", "Chrome 73"`, or
something completely different.
`"Chrome"; v="73"`, `"NotBrowser"; v="12"`, `"BrowsingIsFun"; v="12b",
"Chrome"; v="73"`, or something completely different.

The 'Sec-CH-' prefix {#sec-ch}
--------------------

Based on some discussion in https://github.com/w3ctag/design-reviews/issues/320, it seems
Based on some [discussion with the TAG](https://github.com/w3ctag/design-reviews/issues/320), it seems
reasonable to forbid access to these headers from JavaScript, and demarcate them as
browser-controlled client hints so they can be documented and included in requests without
triggering CORS preflights. A `Sec-CH-` prefix seems like a viable approach, but this bit might
Expand Down