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

Describe navigation as a capability, not a goal. #33

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
6 changes: 6 additions & 0 deletions capabilities.bsinc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ These are things some attackers can do. The attackers can use them to achieve
their <a href="#goals">goals</a>. All attackers are assumed to be able to buy
domains and host sites on their domains.

## Show the user a navigation ## {#cap-navigate}

Users can notice a site navigating, and it disrupts the top-level site's state,
so not all attackers will be willing to create an extra navigation in order to
transfer a user ID.

## Load iframes ## {#cap-iframes}

The attacker can convince a publisher to load an iframe from the attacker's site.
Expand Down
36 changes: 12 additions & 24 deletions goals.bsinc
Original file line number Diff line number Diff line change
Expand Up @@ -17,37 +17,25 @@ it occurred. A context is a set of resources that are controlled by the same
party or jointly controlled by a set of parties. The following are building
blocks that allow a tracker to build such a log of a user's activity.

### Transfer user ID from publisher 1 to publisher 2 on navigation. ### {#goal-transfer-userid}
### Transfer user ID from publisher 1 to publisher 2. ### {#goal-transfer-userid}

When the user clicks a link from [=publisher=] 1 that navigates to [=publisher=]
2, publisher 2's server learns that a [=user ID=] on publisher 2 and a [=user ID=] on
publisher 1 represent the same [=user=].
[=Publisher=] 2's server learns that a [=user ID=] on [=publisher=] 2 and a [=user ID=]
on [=publisher=] 1 represent the same [=user=].

### Transfer user ID from tracker to that tracker running within a publisher, on navigation. ### {#goal-userid-tracker-to-self-in-pub}
### Transfer user ID from tracker to that tracker running within a publisher. ### {#goal-userid-tracker-to-self-in-pub}

When the user clicks a link from `tracker.example` to `publisher.example`, that
[=tracker's=] server learns that a [=user ID=] for either `publisher.example` or
the tracker running within `publisher.example` and a [=user ID=] for
`tracker.example` represent the same [=user=].
<code>[=tracker=].example</code>'s server learns that a [=user ID=] for either
`publisher.example` or the tracker running within `publisher.example` and a
[=user ID=] for `tracker.example` represent the same [=user=].

### Transfer user ID from tracker within publisher 1 to that tracker within publisher 2, on navigation. ### {#goal-userid-tracker-in-pub1-to-self-in-pub2}
### Transfer user ID from tracker within publisher 1 to that tracker within publisher 2. ### {#goal-userid-tracker-in-pub1-to-self-in-pub2}

When the user clicks a link from `publisher1.example` (where a tracker was
embedded within that site) to `publisher2.example` (which has the same tracker
embedded), that [=tracker's=] server learns that a [=user ID=] for either
A [=tracker's=] server learns that a [=user ID=] for either
`publisher1.example` or the tracker running within `publisher1.example` and a
[=user ID=] for either `publisher2.example` or the tracker running within
`publisher2.example` represent the same [=user=].

### Probabilistically transfer user ID from publisher 1 to publisher 2 on navigation. ### {#goal-prob-transfer-userid}
### Probabilistically transfer user ID from publisher 1 to publisher 2. ### {#goal-prob-transfer-userid}

When the user clicks a link from [=publisher=] 1 that navigates to [=publisher=]
2, publisher 2's server learns that a [=user ID=] on publisher 2 and a [=user ID=] on
publisher 1 are more likely than chance to represent the same [=user=].

### Probabilistically transfer user ID from publisher 1 to publisher 2 without navigation. ### {#goal-prob-transfer-userid-no-nav}

While the user is visiting [=publisher=] 1, that publisher can tell
[=publisher=] 2 that a [=user ID=] on publisher 2 and a [=user ID=] on publisher
1 are more likely than chance to represent the same [=user=], without requiring
the user to navigate from publisher 1 to publisher 2.
[=Publisher=] 2's server learns that a [=user ID=] on [=publisher=] 2 and a [=user ID=] on
[=publisher=] 1 are more likely than chance to represent the same [=user=].
69 changes: 69 additions & 0 deletions index.bs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,75 @@ details[open] {
text-align: left;
}
</style>
<script type="module">
/** Takes a threat model table header, which should be written in Bikeshed
* like
*
* <th><div><div>[[#goal-transfer-userid]]</div></div></th>
*
* and extracts the "transfer-userid" part from the generated <a> tag.
*/
function findGoalFromColumnHeader(header) {
const firstDiv = header.firstElementChild;
const secondDiv = firstDiv?.firstElementChild;
const link = secondDiv?.firstElementChild;
if (header.colSpan != 1 ||
link == null ||
firstDiv.tagName !== "DIV" ||
secondDiv.tagName !== "DIV" ||
link.tagName !== "A" ||
link.href === "") {
console.error("Expected header cell to be written like <th><div><div><a href=...>...\n",
header);
return "error";
}
const target = new URL(link.href);
if (!target.hash.startsWith("#goal-")) {
console.error("Column header expected to have a fragment starting with '#goal-': ",
link);
}
return target.hash.slice(6);
}

for (const table of document.getElementsByClassName("threatmodel")) {
// Make sure that the checks and Xes in the source are meant to apply to the
// attacker goal described by the heading they appear under.
const goalOrder = [];
let goalsStarted = false;
for (const header of table.rows[0].cells) {
if (header.textContent.trim() === "" && !goalsStarted) {
for (const _ of Array(header.colSpan)) {
goalOrder.push(undefined);
}
continue;
}
goalsStarted = true;
goalOrder.push(findGoalFromColumnHeader(header));
}

// Check the body rows.
for (const row of Array.prototype.slice.call(table.rows, 1)) {
let column = 0;
for (const cell of row.cells) {
let expectedGoal = goalOrder[column];
column += cell.colSpan;
if (expectedGoal === undefined && "goal" in cell.dataset) {
console.error("Cell without visible header has a data-goal attribute.\n",
cell);
} else if (expectedGoal === "error") {
// Don't be spammy if the header's broken.
continue;
} else if (cell.dataset.goal !== expectedGoal) {
console.error("Cell expected to have goal '" + expectedGoal +
"' but actually claims '" + cell.dataset.goal + "'.\n",
cell);
} else {
// The cell is in the right column.
}
}
}
}
</script>

Advisement: This document is at a very early stage. Many things in it are wrong
and/or incomplete. Please take it as a rough shape for how we might document the
Expand Down
49 changes: 26 additions & 23 deletions xsite-tracking-model.bsinc
Original file line number Diff line number Diff line change
@@ -1,36 +1,43 @@
## Cross-site recognition ## {#model-cross-site-recognition}

<table class="threatmodel">
<thead>
<tr class="goals">
<td></td>
<td colspan="2"></td>
<th><div><div>[[#goal-transfer-userid]]</div></div></th>
<th><div><div>[[#goal-userid-tracker-to-self-in-pub]]</div></div></th>
<th><div><div>[[#goal-userid-tracker-in-pub1-to-self-in-pub2]]</div></div></th>
<th><div><div>[[#goal-prob-transfer-userid-no-nav]]</div></div></th>
<th><div><div>[[#goal-prob-transfer-userid]]</div></div></th>
</tr>
<tbody>
<tr>
<th colspan="2">[[#cap-iframes]] on both sides of a [[#cap-navigate|navigation]]</th>
<td data-goal="transfer-userid" style="color:green">✘</td>
<td data-goal="userid-tracker-to-self-in-pub" style="color:green">✘</td>
<td data-goal="userid-tracker-in-pub1-to-self-in-pub2" style="color:green">✘</td>
<td data-goal="prob-transfer-userid" style="color:green">✘</td>
</tr>
<tr>
<th>[[#cap-iframes]]</th>
<th colspan="2">[[#cap-first-party-js]] on both sides of a [[#cap-navigate|navigation]]</th>
<td data-goal="transfer-userid" style="color:green">✘</td>
<td data-goal="userid-tracker-to-self-in-pub" style="color:green">✘</td>
<td data-goal="userid-tracker-in-pub1-to-self-in-pub2" style="color:green">✘</td>
<td data-goal="prob-transfer-userid-no-nav" style="color:green">✘</td>
<td data-goal="prob-transfer-userid" style="color:green">✘</td>
</tr>
<tbody>
<tr>
<th>[[#cap-first-party-js]]</th>
<th colspan="2" scope="rowgroup">[[#cap-read-logs]] on other publishers</th>
<td data-goal="transfer-userid" style="color:green">✘</td>
<td data-goal="userid-tracker-to-self-in-pub" style="color:green">✘</td>
<td data-goal="userid-tracker-in-pub1-to-self-in-pub2" style="color:green">✘</td>
<td data-goal="prob-transfer-userid-no-nav" style="color:green">✘</td>
<td data-goal="prob-transfer-userid" style="color:green">✘</td>
</tr>
<tr>
<th>[[#cap-read-logs]] on other publishers</th>
<td></td>
<th>if publisher 1 can [[#cap-navigate]]</th>
<td data-goal="transfer-userid" style="color:green">✘</td>
<td data-goal="userid-tracker-to-self-in-pub" style="color:green">✘</td>
<td data-goal="userid-tracker-in-pub1-to-self-in-pub2" style="color:green">✘</td>
<td data-goal="prob-transfer-userid-no-nav" style="color:green">✘</td>
<td data-goal="prob-transfer-userid">
<details>
<summary style="color:red">✓</summary>
Expand All @@ -40,8 +47,9 @@
</details>
</td>
</tr>
<tbody>
<tr>
<th>[[#cap-run-on-server]] on the target publisher</th>
<th colspan="2">[[#cap-run-on-server]] on the post-[[#cap-navigate|navigation]] publisher</th>
<td data-goal="transfer-userid">
<span style="color:green">✘</span>
</td>
Expand All @@ -60,15 +68,12 @@
<td data-goal="userid-tracker-in-pub1-to-self-in-pub2" >
<span style="color:green">✘</span>
</td>
<td data-goal="prob-transfer-userid-no-nav" >
<span style="color:green">✘</span>
</td>
<td data-goal="prob-transfer-userid">
<span style="color:green">✘</span>
</td>
</tr>
<tr>
<th>[[#cap-first-party-js]] or [[#cap-run-on-server]] on the source site and [[#cap-run-on-server]] on the target publisher</th>
<th colspan="2">[[#cap-first-party-js]] or [[#cap-run-on-server]] on the source site and [[#cap-run-on-server]] on the target publisher</th>
<td data-goal="transfer-userid">
<span style="color:green">✘</span>
</td>
Expand Down Expand Up @@ -96,13 +101,11 @@
tracker's server</a>.
</details>
</td>
<td data-goal="prob-transfer-userid-no-nav" >
<span style="color:red">✓</span>
</td>
<td data-goal="prob-transfer-userid">
<span style="color:red">✓</span>
</td>
</tr></table>
</tr>
</table>

Further cross-site recognition is available by combining capabilities with the
ability to [[#cap-first-party-js]] (or [[#cap-run-on-server]] to add
Expand All @@ -111,11 +114,11 @@ attacker-controlled javascript):
<table class="threatmodel">
<tr class="goals">
<td></td>
<th><div><div>[[#goal-prob-transfer-userid-no-nav]]</div></div></th>
<th><div><div>[[#goal-prob-transfer-userid]]</div></div></th>
</tr>
<tr>
<th>[[#cap-type-identifier]]</th>
<td data-goal="prob-transfer-userid-no-nav">
<td data-goal="prob-transfer-userid">
<details>
<summary style="color:red">✓</summary>
The tracker gets a report of the identifiers typed in both publisher
Expand All @@ -129,7 +132,7 @@ attacker-controlled javascript):
</tr>
<tr>
<th>[[#cap-same-device-same-time]]</th>
<td data-goal="prob-transfer-userid-no-nav">
<td data-goal="prob-transfer-userid">
<details>
<summary style="color:red">✓</summary>
The tracker reads the two devices, and if they give the same output at
Expand All @@ -146,7 +149,7 @@ attacker-controlled javascript):
</tr>
<tr>
<th>[[#cap-same-rw-device]]</th>
<td data-goal="prob-transfer-userid-no-nav">
<td data-goal="prob-transfer-userid">
<details>
<summary style="color:red">✓</summary>
The tracker writes identifying content to the device and then reads it
Expand All @@ -162,13 +165,13 @@ attacker-controlled javascript):
</tr>
<tr>
<th>[[#cap-open-for-browser-event]]</th>
<td data-goal="prob-transfer-userid-no-nav">
<td data-goal="prob-transfer-userid">
<span style="color:green">✘</span>
</td>
</tr>
<tr>
<th>[[#cap-visible-for-browser-event]]</th>
<td data-goal="prob-transfer-userid-no-nav">
<td data-goal="prob-transfer-userid">
<details>
<summary style="color:red">✓</summary>
Browser-wide events generally need to be visible immediately when a user
Expand Down