Why Web and App Isolation Did Not Isolate Identity
In Bridges to Self, the immediate problem is not that browsers or Android lack isolation; it is that their isolation rules govern different kinds of access. This chapter focuses on the gap between protecting a web origin or an app’s private files and controlling a local process that public web content can contact. The useful result is a working model: the reader should be able to say which boundary each protection covers, then see why a coordinated web script and native app can still exchange identity data.
Four kinds of isolation that answer different questions
The browser’s Same-Origin Policy is the first protection to understand. An origin is roughly the combination of a scheme, host, and port that identifies a web security context. The policy prevents a script from one origin from directly reading or altering another origin’s data and execution context. If a page on one site could freely inspect another site’s cookies, account state, or page contents, the web would lose a basic security boundary.
Modern browsers add privacy controls around that boundary. They restrict third-party cookies and partition some client-side storage by site, so that the same embedded service has less ability to recognize a person across unrelated sites. These controls can make a web identifier less portable. They do not, however, define what happens when the page sends information to a process outside the browser. A page can be prevented from reading another origin’s storage and still be allowed to place a value in a request whose destination is not another origin at all.
That difference becomes clearer if we ask what each control owns. The browser owns the rules for a script’s access to web data and the storage associated with an origin. A cookie-clearing operation changes browser-held state. Storage partitioning changes which sites can see or reuse that state. Neither operation automatically knows that a native process has received a copy of a value, because the copy is no longer inside the browser’s storage model.
Android’s application sandbox answers a different question. Each app runs with a distinct Linux user identity and is intended to have its own code, memory, and storage. One app should not simply open another app’s private files or inspect its in-memory state. Cross-app interaction is supposed to happen through explicit inter-process communication, or IPC, such as intents, content providers, or broadcasts. Those mechanisms can be governed by permissions and platform rules.
A socket on the local device is not the same as direct access to another app’s memory. That distinction is important. A native app can keep its private storage protected while also choosing to listen for network connections on a local interface. If a browser can reach that listener, the browser has not broken into the app’s sandbox in the narrow sense. It has reached an interface the app exposed. The interface can still carry sensitive information across the two contexts.
This is why the paper’s finding is best understood as a failure to isolate identity relationships, not as a claim that every sandbox was removed. The browser may continue to protect a cookie from other web origins. The app may continue to protect its account database from other apps. But if the browser sends the cookie to an app that is prepared to receive it, and the app returns a native identifier, the two systems have acquired a shared relationship without sharing their private stores.
The state belongs to different owners at different points. The browser owns the web cookie. The app owns its native account or device identifier. The operator’s back end can own the durable association created after the exchange. A privacy control at the first point does not automatically erase state created at the third. This is also why the word “identity” needs care. The web value may be pseudonymous, and the native value may be an advertising identifier rather than a person’s name. The privacy significance comes from joining them, because the resulting record can be used to recognize activity across contexts that were meant to remain separate.
Isolation still has value. It can prevent a malicious page from reading arbitrary app files, and it can prevent one app from directly inspecting another app’s memory. The paper’s point is narrower and more consequential: a system can preserve those protections while exposing a permitted interface that transfers exactly the information an attacker needs. Security boundaries are defined not only by what a component cannot read, but also by what data it is allowed to send to a reachable recipient.
Why localhost was a special opening
Localhost normally refers to the device itself, especially the loopback interface. A request to 127.0.0.1 or ::1 is addressed back to the same machine rather than to a remote server. That sounds safer than sending a request across the internet, but “where the packet goes” and “who is allowed to receive the data” are different questions.
The paper points to a long-standing web classification that treats localhost as a potentially trustworthy origin. In a browser security context, “trustworthy” has a specific technical meaning; it does not necessarily mean that every local service is authenticated, encrypted, or benign. The classification also means that some browser rules, including mixed-content handling, have treated local resources differently from ordinary insecure remote resources. A public HTTPS page could therefore have a path to a local HTTP service without the same friction that would apply to an arbitrary remote endpoint.
Before Local Network Access protections were deployed, the paper reports that browsers allowed JavaScript APIs to access localhost without a user or platform decision at the moment of access. The result was an unmediated handoff: the browser treated the action as use of a web capability, while the operating system treated the recipient as a process using its own local socket. Neither layer was necessarily asked whether the data being sent created a cross-context identity link.
Chrome’s Local Network Access description makes the missing question more explicit. Its initial model covers requests from the public network to destinations on a local network or the loopback interface. The site must be in a secure context, and the user must grant permission before the browser allows the gated request. Chrome describes local destinations using private address ranges, loopback addresses, .local hostnames, or an explicit indication that a request targets local address space. The permission is intended to protect against attacks on routers and other local devices as well as against network fingerprinting.
That model distinguishes a public origin from a local destination, but it still has to decide what counts as local before a connection is made. A hostname may resolve to a loopback address. A request may use a name rather than a literal IP address. A protocol may discover a route through a candidate or a multicast lookup instead of issuing a familiar HTTP request. The paper’s later analysis shows why these distinctions matter: destination classification, protocol handling, and information disclosure can fail in different ways. A rule that recognizes one form of local access may not automatically govern another.
But the first version of a permission model is not the same as complete coverage. In the Chrome description captured in June 2025, fetch(), subresource loading, and subframe navigation were part of the initial opt-in restrictions, while WebSockets, WebTransport, and WebRTC were listed as not yet gated. This matters because a bridge is defined by the information relationship, not by the syntax of one request. If one protocol is blocked while another remains available, the transport may change while the identity path survives.
The threat model has three essential participants. A public page or embedded third-party script provides the web-side execution. A native app provides the local listener and access to a longer-lived account or device identifier. A cloud service receives the result or uses it for matching. For a large campaign, the same actor needs a script present on many sites and an app installed by many users, so both sides are likely to coexist on a device. Background execution makes the local listener useful even while the browser is in the foreground.
The actor’s control of both sides is what turns reachability into a bridge. A page controlled by one organization cannot normally assume that an unrelated app will understand its request or return useful data. Conversely, an app listener without a matching web distribution channel may expose a local service but cannot create the same population-scale correlation. In the cases studied, the shared operator could arrange the message format, recognize the value when it returned, and send the combined record to its own back end. The local interface supplied the missing connection between two otherwise separate identity systems.
The model also has clear failure conditions. If the relevant app is not installed, there is no native endpoint. If its listener is not active, the request has no intended recipient. If the browser blocks the protocol or asks for permission that the user denies, that path fails. If no identifier crosses the boundary or no back end retains the association, a local request may remain a local integration rather than a tracking bridge. These conditions are why the paper had to analyze coordinated app-and-website behavior instead of inferring abuse from the mere existence of a port.
The converse is equally important. A successful request alone is not enough to establish de-anonymization. The evidence must connect the request to a native listener, show what value was exchanged, and establish where the resulting relationship was used. The local channel is the mediator; the identity join and the back-end use are the privacy consequence.
The boundary mismatch in one view
The diagram shows why the phrase “the app was sandboxed” does not settle the question. Sandboxing can prevent direct file and memory access while leaving a deliberately opened network interface available. Likewise, the Same-Origin Policy can prevent one website from reading another website’s storage while leaving the page free to call an API whose destination is local. The controls are not meaningless; they are simply aimed at different edges of the system.
This also clarifies why the paper describes cookie clearing, VPNs, advertising-ID resets, and work/personal profiles as insufficient by themselves. Cookie clearing acts on browser-held state. A VPN primarily changes remote routing. An advertising-ID reset changes one native identifier. Profile separation changes how Android organizes app and data contexts. None of those operations necessarily removes a relationship already created when a web value was handed to a native process and sent to a back end. Their failure in the tested scenarios is a scope failure, not proof that each control is useless for its original purpose.
For the same reason, an app’s permission to use networking and a browser’s permission to reach local services should not be treated as the same decision. One authorizes a process to communicate; the other determines whether arbitrary web content can initiate a path to that process. The paper’s cases emerged because those decisions were made in separate systems with no shared model of cross-context tracking.
The next case makes this model concrete through Yandex Metrica and AppMetrica. There, the important question is no longer whether the boundary can be crossed in principle. It is how a native SDK kept a listener available, how the web and native values met, and how timing and remote configuration made that lifecycle difficult to observe.