TL;DR: Users are being spied on, websites are broken and hijacked.

Update Oct 7 2022: Facebook recently announced their app will ship its own Chromium engine to power their IAB - this will replace the system-provided WebView. I've added a short section at the bottom of this post to reflect on this development. TL;DR: Facebook is making things worse.

Most people probably don't know this, but when you click a link in Facebook, Instagram, TikTok and many other apps, you're viewing the site (and all subsequent pages you visit from there) in a so-called In-App Browser (IAB). Felix Krause uncovered that these IABs are not just breaking the Mobile Web (more on that below), they're also often injecting their own javascript and attaching tap and gesture event listeners that (can) keep track of anything you do within that IAB. TikTok even goes as far as to register keyboard input - they're listening in on everything you type, including passwords 🤯.

💥 New Post: Instagram & Facebook tracks everything you do on any website in their in-app browser
krausefx.com/blog/ios-privacy-instagram-and-facebook-can-track-anything-you-do-on-any-website-in-their-in-app-browserc2

Image from Tweet

What I want to talk about right now are two things:

  1. Most users are not aware that they're not in their default browser.
  2. IABs may be actively adding trackers to sites they do not own.

I feel like I need a short disclaimer. I will often be using Facebook and TikTok in examples, but it's not that I'm trying to single them out, specifically. This post is about any app that uses IABs, injects code and/or tracks users across the web without the user being aware, nor the owner of the site being able to do anything about it.

I didn't Bring My Own Browser

Let's say you have an uncle who isn't very tech-savvy, so you set up his phone and helped him choose a browser that blocks 3rd party cookies and trackers. You went into settings to fine-tune a couple of things, maybe even installed an extension that further improves on what the browser already brings. Your uncle would probably assume that when he clicks a link in Facebook or TikTok, that all of these protections are in place.

He'd be wrong.

Worse, according to Krause's findings, he didn't just not bring his own browser, he's bringing his own trackers, even on sites that don't have any trackers of their own!

Your uncle clicked the link in the Facebook app, and while he may think he's protected by his browser and all the things you've set up for him, he ends up in Facebook's IAB instead of his browser. None of his privacy settings and extensions carry over. Facebook, in turn, injects code into the page and is able to track your uncle on the site and any pages he visits from there. Even if the site he's visiting doesn't have any trackers, the IAB made sure that there is a tracker.

Choosing a browser is (or should be, at least) a conscious decision. The browser's main task is to let you browse the web while keeping you safe, protecting your privacy and security. It's your User Agent. I would argue that when a typical user installs Facebook, it doesn't occur to them that they're putting the same amount of trust in Meta. Apps that open links in IABs and track your every move are confused about who they serve, piggy-bagging on the trust that browsers have earned - and IMHO, abusing it.

One ex-Facebook employee has tried to shed a bit of light on why an app like Facebook might choose an IAB over the alternatives, and what benefits he believes their injected javascript offers. While I absolutely don't think he's being dishonest, and I'm sure Facebook (thinks they) are offering some actual benefits to the user, I'm not buying it. Why would the user need Facebook to do autofill if the user's Browser Of Choice can do it, too? Sure, there probably are a few scenarios where Facebook may know what to autofill where the browser would not, but I'm inclined to think the opposite is true more often. Whatever the benefits, I believe they do not outweigh the costs. Most importantly, I don't think Facebook should put itself in the role of a browser without making it absolutely clear to the user how much trust they're asking for.
Also, if they really believe their IAB is better than a proper browser, they should probably put some effort into having it not break websites (which brings me to...)

My app Brought Its Own Dumpster Fire

As an aside, IABs are a problem even if they don't inject javascript and trackers. As I mentioned earlier, IABs often break websites.

As far as a site can detect, you're in a proper browser. The WebView, used by the IAB to render the site, happily reports that it supports a whole range of features. So it's safe to use them, right?

Unfortunately, some of these features require extra work on top of the WebView, and the folks behind IAB often don't do that work.

It's also just a poor user experience. IABs don't know about your settings. They don't know which sites you're already logged in to. They are silos that don't know you like your default browser does.

And then there are some serious security concerns:

When a native app embeds a website via a webview, the native app has control over that page. Yes, even if it’s on a domain that the native app doesn’t control (!). This means the native app can inject whatever JavaScript it likes into any website that’s viewed in the webview.
Let websites framebust out of native apps, by Adrian Holovaty

The Mobile Web is bad enough on its own. It's slow because developers are still having trouble targeting small screens, underpowered processors and spotty connections. It's buggy because Apple has neglected WebKit for over a decade (and with it, every single browser on iOS). It's full of pop-ups, location and notification permission prompts and other stuff that blocks the content that you're trying to get to.

Imagine that Worst Version of The Web being served by a dumpster fire - and then imagine that dumpster fire keeping tabs on everything you do.

Facebook Mobile Browser
Hobson's Browser, by Alex Russell.

My site just got hijacked

Now, let's look at the information Krause uncovered from the perspective of a website / web developer.

Say you built a site. As the owner, you may not want your users to be tracked by 3rd parties. You carefully built the thing, making sure that you didn't add anything that might track your users (hey! you're welcome!). You wrote an article on how users can protect their online privacy. The article gets some traction, people are reading it!

But according to Krause's findings, you have IABs hijacking your site, injecting it with javascript and event listeners, tracking your users. Sure, their platform got that user to click your link and visit your site, but injecting javascript and attaching event listeners to your buttons feels a little too intimate to be allowed without consent.

It turns out that your users, reading your article on your site, unknowingly brought someone along for the ride, and that someone is using your site to track your users.

Don't IAB me bro

A long time ago, the web decided we should not be able to just embed any website inside another website (this was done using so-called frames and/or iframes). Embedding was used to piggy-bag on someone else's content, like injecting ads into the embedded site. It was also used as an effective attack vector, getting users to click a link that embedded their banking site and have them enter their password. Everything seemed to work fine - it was their bank's actual site, after all - but the attacker was able to add keyboard input event listeners to the embedded site and steal passwords.

All this may sound familiar, because if you swap "website embeds a website" with "app embeds a website", you may realize that, as Krause found out, as soon as a mechanism exists to embed sites, it's used to inject scripts and add event listeners. We just can't help ourselves, it seems. 🤷

On the web, we came up with a solution: sites can send an X-Frame-Options header, later replaced by CSP: frame-ancestors, that basically tells the browser that a page should not be rendered in a frame:

# CSP: frame-ancestors
Content-Security-Policy: \ 
  frame-ancestors 'none';
Content-Security-Policy: \
  frame-ancestors 'self' https://www.example.org;

# X-Frame-Options, deprecated
X-Frame-Options: DENY
X-Frame-Options: SAMEORIGIN
<!-- CSP: frame-ancestors -->
<meta http-equiv="Content-Security-Policy"
      content="frame-ancestors 'none'">

The CSP: frame-ancestors can also be set using a <meta /> tag. Going forward, I may only mention the header but everything that applies to the header, should apply to the <meta /> tag as well.

We need a similar solution for In-App Browsers.

The hotfix

Adrian Holovaty proposed one solution where using the existing X-Frame-Options would have the IAB's WebView just open the link in the default browser - or at least in Android's Chrome Custom Tab (CCT) or iOS's SFSafariViewController (SFSVC) - instead of rendering it in the WebView.

While Holovaty only mentions X-Frame-Options, I assume we would want the equivalent CSP: frame-ancestors header values to have the same effect.

This should be a fairly easy fix that could be quickly implemented in the WebView that is supplied by the operating system, and rolled out to users via an update. Since IABs rely on WebViews, apps that use IAB would automatically be enrolled in this behavior, no update required on their part, and it would work whether Meta, TikTok and other app developers like it or not. Many sites are already using the header (making it immediately work for any site that does), devs are familiar with it and can easily add it.

So my proposal is this: Apple and Google should honor the existing X-Frame-Options HTTP header in webviews. If a website is loaded into a webview, and the website includes the appropriate X-Frame-Options header, the mobile OS should immediately stop loading the webview and open the URL in the user’s preferred web browser.
Let websites framebust out of native apps, by Adrian Holovaty.

The downside is that, you guessed it, sites are already using the header, but for the purpose it was designed for at the time ("don't iframe me bro!"). A site may be completely fine with, or even prefer, being opened inside a IAB. So, in a way, this would break backwards compatibility.

Go read Adrian Holovaty's full post: Let websites framebust out of native apps

The long-term fix

Alex Russell proposed a more long-term solution that would not risk breaking backwards compatibility but will require existing sites to be updated. It would still use the CSP: frame-ancestors header, just adding support for a new value:

Content-Security-Policy: \
  frame-ancestors 'system-default';

This would still benefit from being implemented at the WebView-level, so it's just an update away from reaching users and it would affect any IAB that relies on WebViews. Apps like Facebook can't just override the behaviour.

OS vendors would update their system WebViews to respect this tag and invoke CCT if encountered in a top-level document. This is compatible with the existing ecosystem, as no first-party content (help pages) or second-party integration (ad network) would send these headers, existing apps would not need to be updated. Websites could incrementally add the hint and benefit from the new behavior.
Hobson's Browser, by Alex Russell.

There is no real downside except that sites need to be updated to benefit from the behaviour, but the upside is that it would not change anything for existing sites.

Go read Alex Russell's full post: Hobson's Browser

Fix it

We really need this IAB situation to be fixed. It's crazy that Apple and Google, with all their big talk on how their monopolistic app stores are protecting the security and privacy of their users, are allowing apps distributed through those same stores to embed any webpage, making the web and its users vulnerable to serious security and privacy issues.

Not to mention that website owners have their sites broken and their content hijacked.

This is not okay.

iOS and Android both provide privacy-preserving alternatives to WebView IABs, but they don't require apps to use them.

Remember that the next time you see an Apple or Google ad about privacy; they aren't even doing the easy stuff. twitter.com/slightlylate/status/1560461904866226176

Replacing WebView

Update: Oct 7 2022

Facebook recently announced their app will ship its own Chromium engine to power their IAB - this will replace the system-provided WebView.

Facebook mentions "stability" as an important reason for shipping their own engine. Apparently, Android apps that embed WebViews crash when the underlying WebView app is updated. The thing is that, as far as I know, this would not be an issue if Facebook would just use Chrome Custom Tabs instead of a WebView.

Also, none of this solves the problem of the Facebook "browser" not bringing your privacy and security settings, sessions, passwords, etc. CCT solves all of this.

The timing of this change seems a bit dubious, too, as the teams building WebViews started discussing IAB security and privacy risks, and whether and how they should block IAB-rendering if the site requests it. There is no consensus yet, but they are exploring options as described in the previous chapter. If Facebook would continue to use the WebView to render IAB content, they would have no control over whatever restrictions WebViews may implement. By shipping their own engine, they can circumvent any solutions the WebView teams come up with. They could ignore a site's request to be opened in a proper browser, keep injecting javascript into sites they don't own, and continue their business of spying on users who are almost certainly unaware of the fact that they didn't leave Facebook. They would have almost unlimited access to whatever happens in their IAB window.

So, basically, Facebook isn't really solving anything here. At best, they're doing the super difficult thing of maintaining and shipping a fork of an entire rendering engine instead of using what's safer, a better UX and already provided for: Chrome Custom Tabs. They are actually making things worse by granting themselves more control and, to me, it just feels like an attempt to pro-actively circumvent any protections that WebViews may implement in the future.

Further reading

I've sprinkled links to the posts below throughout the article, but it's very much worth reading them so here's a quick list:

Thanks!

  • A warm thank-you to Alex Russell and Bruno Stasse for proof-reading early drafts 💖. They didn't check the final version so if I got anything wrong, that's probably on me 🤷