A secure proxy model for third-party content
Core to the Discover approach is a rethink of how a proxy can present arbitrary third-party web content without breaking the security assumptions browsers normally enforce. The fundamental problem: when a client talks to a proxy rather than directly to an origin server, cookies, CORS, and CSP all get evaluated against the proxy's domain, not the site's. A naive proxy that simply rewrites URLs and forwards responses creates a single namespace where any script running in a page could read or set cookies meant for other origins.
The earlier Free Basics proxy solved this by not running JavaScript at all. That constraint became increasingly limiting as more sites, including mobile versions, moved critical rendering and functionality into client-side code. Supporting JavaScript safely required a different architecture — one that preserves origin isolation while still funneling traffic through a single, operator-friendly domain.
Why not the obvious alternatives
Two candidate architectures were considered and rejected before settling on the current design:
Subdomain cooperation. Sites would opt in by allocating a subdomain (e.g., free.example.com) that resolves into the proxy's IP space. This gives direct client-to-server communication and minimal proxy intervention. But it requires every site to cooperate, adds engineering cost for site owners, and depends on Server Name Indication (SNI) to route requests — support for which is not universal. Users who browsed directly to example.com would not be redirected to the free subdomain without extra carrier logic, risking accidental data charges.
IPv4-in-IPv6 encapsulation. Encapsulating the entire IPv4 space inside a single free IPv6 subnet avoids the need for site cooperation and eliminates the SNI dependency. But browsers would show the rewritten domain (e.g., www.example.com.freebasics.com) while the certificate presented would be for the original host — a mismatch that produces errors. Few carrier gateways support IPv6 encapsulation, and even fewer devices — especially older ones — support IPv6 at all.
The workable answer turned out to be origin collapsing: everything runs under a single origin-collapsed domain namespace, with each third-party origin encoded as a subdomain. Operators can then permit free traffic to one domain and keep their gateway configuration simple, while name resolution always directs traffic to a free IP.
https://example.com/path/?query=value#anchor
is rewritten to:
https://https-example-com.0.freebasics.com/path/?query=value#anchor
Server-side logic rewrites links and hrefs consistently, and HTTP-only sites are delivered to the client over HTTPS between client and proxy. A single namespace and TLS certificate covers all origins — no per-site certificates needed. All internet origins become siblings under 0.freebasics.com, which rules out using the Public Suffix List: doing so would require a distinct cookie per origin, and the browser cookie limit per domain would be exhausted quickly.
Cookie handling in a proxy world
In a normal browsing session the client stores and sends cookies straight to the origin site. With a proxy, the client only ever talks to the proxy's domain, so cookies must live somewhere else. Free Basics keeps user cookies in a server-side cookie jar, for two reasons. First, older browsers have limited cookie support — even issuing one cookie per site under the proxy domain would rapidly hit local storage limits. Second, the domain namespace prevents hierarchical cookies: a cookie set on .example.com is readable by any subdomain normally, but a-example-com.0.freebasics.com setting a cookie on example.com.0.freebasics.com is not allowed by the standard, so siblings like b-example-com.0.freebasics.com could not read their parent's cookies anyway.
Two client-side cookies make the server-side jar usable:
datr— a browser identifier used for site integrity.ick(internet cookie key) — a cryptographic key that encrypts the server-side cookie jar. Stored only on the client, it means the jar cannot be decrypted when the user is not actively using the service.
Additional protections on the jar:
- Server-side cookies are encrypted with the
ick, which is held only on the client. - The
ickis forgotten by the server after each request and is never logged. - Both client-side cookies are marked
SecureandHttpOnly. - Cookie indexes are hashed using the client-side key, so the index is not traceable to the user when the key is absent.
This cookie model is what makes running scripts risky. If scripts can read or overwrite the client-side cookie that stores the encryption key — the ick — session fixation becomes possible. Scripts can manipulate the cookie jar, and the complexity of JavaScript makes static analysis impractical as a complete defense.
The solution is a framing model that separates trust from content. An outer frame, which we control, inspects and attests that the inner frame — the one presenting third-party content — has not been tampered with. This lets the proxy safely present third-party pages with JavaScript enabled while protecting the session key cookie from being overwritten. The same framing approach also mitigates phishing and clickjacking: the outer frame serves as the security boundary, and any script-level attempt to interfere with the cookie's integrity is blocked by the attestation boundary between frames.
Every site that can be reached through the service is still reviewed individually for abuse vectors, independent of content type, before it is made available.
Cookie fixation defense in Discover
Once we moved to the new, origin-collapsed discoverapp.com domain, third-party JavaScript became a viable attack surface. Scripts can rewrite links, reach anywhere in the DOM, and — worst case — fixate client-side cookies. Rather than attempting to parse and block individual script calls, we detect fixation when it happens and make it useless.
Detecting and neutralizing fixation
At signup, we generate a secure random ick and send it to the browser as an HttpOnly cookie. We then derive an HMAC value, ickt, from a digest of both ick and datr, so both are protected against fixation. A copy of ickt is stored on the client in localStorage, at an origin an attacker cannot write to: https://www.0.discoverapp.com. Since this origin never serves third-party content and is a sibling to all third-party origins, domain lowering or other domain modifications are not possible there — it is a trusted origin.
Every third-party proxy response embeds an ickt derived from the ick cookie seen in the request. On page load, the embedded ickt is compared with the trusted copy via window.postMessage(). A mismatch triggers session invalidation by deleting the datr and ick cookies. We additionally set a fresh datr cookie if multiple copies appear at the same location, embedding a timestamp so the most recent one always wins.
Two-frame validation
To validate the ickt, a third-party page needs a way to query and check it. We achieve this by embedding the third-party site in an <iframe> inside a page at the secure origin, then injecting JavaScript into the third-party content. This creates two frames: a secure outer frame and a third-party inner frame.
In the inner frame, every proxied page gets an injected script along with the ickt computed from the request's ick. The script performs two checks. First, it postMessages the embedded ickt to top and waits for an acknowledgement. If the reply comes from an unexpected origin or takes too long, the frame navigates to a generic error screen with no third-party content (our “Oops” page). Second, it postMessages to parent and expects a reply where source===parent and the origin is under .0.discoverapp.com; otherwise it similarly redirects to the error page.
Two assumptions underpin the inner frame design. Even if the inner frame script is circumvented, an attacker can only fixate cookies on an origin where they already have code execution — making the fixation vector redundant. We also assume a benign origin will not deliberately break the inner-outer messaging protocol.
Outer frame attestation
The outer frame's role is to attest that the inner frame is consistent. It is always the top frame, served with JavaScript and X-Frame-Options: DENY. When it receives a postMessage, it checks whether the sender is an inner frame origin and whether the reported ickt is correct. A valid report gets an acknowledgement; an invalid one deletes the session and all cookies and navigates to a safe origin. If no message arrives within a few seconds, or the subframe is not the topmost inner frame, the secure frame's address bar removes the location.
Blocking interaction during validation
To avoid race conditions — for instance, a user typing a password under a fixated cookie before verification finishes — the server adds style="display:none" to the <html> element of every proxied page. The inner frame removes it only after receiving the outer frame's confirmation. JavaScript may still run and resources still load during this window, but until the user provides input, the browser does nothing an attacker could not do by simply visiting the site — unless the site is already vulnerable to CSRF.
Handling asynchronous fixation
The synchronous protections do not cover asynchronous cookie fixation. For that, we adopted a classic CSRF defense: POSTs must include a query parameter carrying the datr observed when the page loaded, and the server rejects requests where that value does not match the datr cookie. To keep datr from leaking, the inner frame carries an encrypted copy of it and injects the parameter into every <form> and XHR object. The page cannot derive the token itself, so it always uses the datr it saw at load time.
Anonymous requests must carry the datr query parameter as well, but since the ick cookie is absent, we cannot validate against the cookie jar. This means anonymous POSTs can technically occur under a fixated session, but without the ick there is no sensitive information to expose.
Clickjacking and phishing
Websites that set X-Frame-Options: DENY will not load inside our inner frame. We remove that header from the HTTP response, but compensate by having the inner frame verify via postMessage that parent is the top window. Failed validation sends the user to the “Oops” page.
The address bar shown in the secure frame exposes the topmost inner frame origin, but phishing sites could imitate it. To keep malicious links from navigating away from Discover, we block top navigation with the <iframe sandbox> attribute. The only way out of the outer frame is by directly navigating to another site.
Client-side cookie shim
document.cookie lets JavaScript read and write cookies lacking the HttpOnly flag. Supporting this in a server-maintained cookie jar is the hard part. On each request, the proxy enumerates the cookies visible at that origin and attaches them to the response page as a JSON payload. Injected client-side code shims document.cookie so these cookies appear to scripts as normal client-side cookies.
Arbitrary cookie writes are riskier: origin evil.com must not be able to set a sensitive cookie on example.com. Browser CORS rules are not sufficient here, because a.example.com setting a cookie on example.com is blocked — those origins are siblings, not hierarchical. And when the server receives a client-set cookie, it cannot reliably know the writer's origin from the request alone.
To prove eligibility, the server sends, alongside the JSON payload, a list of cryptographic tokens for each origin the requesting origin is allowed to set cookies on. These tokens are salted with the ick value, so they cannot be reused across users. The client-side shim resolves the right token and embeds it in the cookie text sent to the proxy. The proxy verifies that the writer had the token for the target domain before storing the cookie in the server-side jar and sending it back on subsequent requests.
Bootstrap protocols
The architecture uses three origin types — portal (datr required), secure (ickt required), and rewrite (datr and ick required) — and two bootstrap paths depending on browser support.
With localStorage
Most modern mobile browsers support localStorage, enabling a single-request provisioning flow:
The secure origin's bootstrap endpoint always issues a fresh ick and ickt, never deriving ick from user input, which prevents reflection. Both ick and datr are set with domain=.discoverapp.com, making them available at all origin types; ickt exists only on the secure origin.
Without localStorage
Browsers without localStorage — Opera Mini, for example, which is common in several markets where Discover operates — cannot store ick and ickt values that way, so the flow changes:
In this case we separate the rewrite origin from the secure origin entirely, so they do not share a host suffix under the Public Suffix List. The secure copy of ickt is stored as a cookie at www.0.discoverapp.com, and all third-party origins move under 0.i.org. In a conforming browser, a cookie set at the secure origin is not available to any rewrite origin.
With separate origins, bootstrapping becomes a two-step process. Previously one request could set ick and provision localStorage with ickt together; now both origins must be bootstrapped in separate requests without opening ick fixation vectors. We bootstrap the secure origin with the ickt cookie first and hand the user an encrypted ick — keyed only to the proxy — along with a single-use nonce that permits decryption at the rewrite origin to set the cookie once.
An attacker can either use the nonce to reveal the ick cookie or pass it to a user to fixate its value, but cannot do both simultaneously. The process also keeps datr synchronized across the origins.
This design has undergone extensive internal and external security review, and we believe it resists the web application attacks currently seen in production. With Discover live in Peru, additional trials are slated for partners in Thailand, the Philippines, and Iraq where beta testing has already run, with further rollouts to follow as operators opt in.





