Skip to content

Instantly share code, notes, and snippets.

@spookalucca
Forked from mary-ext/bluesky-osa.md
Created July 24, 2025 05:05
Show Gist options
  • Select an option

  • Save spookalucca/f0dd7d6024b6291068bbc3d66d24c5c4 to your computer and use it in GitHub Desktop.

Select an option

Save spookalucca/f0dd7d6024b6291068bbc3d66d24c5c4 to your computer and use it in GitHub Desktop.
Bluesky's UK age assurance sucks, here's how to work around it.

Bluesky's UK age assurance sucks, here's how to work around it.

Bluesky recently announced that they're complying with the UK's Online Safety Act, which requires users to provide personal identity verification confirming their age (through Epic Games' Kids Web Services) before accessing certain parts of the platform.

This sucks for privacy reasons, but thankfully there are ways to work around it.

Workaround methods

Method 1. Third-party clients

You don't have to use the official Bluesky app to access the platform. Third-party clients like Klearsky or TOKIMEKI currently don't implement these restrictions. For read-only access without signing in, Anartia is also available.

Note that these alternatives work only as long as their developers don't implement similar age assurance measures or block UK users outright.

Method 2. uBlock Origin

Open uBlock Origin dashboard › My filters.

Enable Allow custom filters requiring trust, then add the following rules:

||bsky.app/ipcc$replace=/(?<="isAgeRestrictedGeo":)true/false/

! Optional, if Bluesky ever adds regional content moderation to UK users
||bsky.app/ipcc$replace=/(?<="countryCode":").+?(?=")/US/

These filter rules work with similar capable adblockers like AdGuard or Brave.

Method 3. Userscripts and Brave scriptlets

You can use userscript extensions like Violentmonkey or Greasemonkey, or if you use Brave browser, you can use custom scriptlets.

For userscript extensions

Install Violentmonkey for Firefox or Chrome, then install the userscript provided below by clicking on the Raw button.

For Brave browser

Open Settings › Shields › Content filtering (or go to about:adblock)

Enable Developer mode, and add the following scriptlet, saving it as user-bsky-age-assurance.js.

const _fetch = globalThis.fetch;
globalThis.fetch = async function (req, init) {
  if (req instanceof Request) {
    const url = new URL(req.url);

    switch (url.pathname) {
      case "/xrpc/app.bsky.unspecced.getAgeAssuranceState": {
        return Response.json({
          lastInitiatedAt: "2025-07-14T14:22:43.912Z",
          status: "assured",
        });
      }
    }
  } else if (req === "https://bsky.app/ipcc") {
    return Response.json({
      countryCode: "US",
      isAgeRestrictedGeo: false,
    });
  }

  return _fetch.call(this, req, init);
};

Then reference the scriptlet in a custom filter.

bsky.app##+js(user-bsky-age-assurance.js)
main.bsky.dev##+js(user-bsky-age-assurance.js)

Method 4. Self-hosted PDS

If your account is hosted on a PDS you own or control, you can add these rules to your Nginx or Caddy configuration.

For Nginx users:

server {
	server_name pds.example.com;

	location /xrpc/app.bsky.unspecced.getAgeAssuranceState {
		default_type application/json;
		add_header access-control-allow-headers "authorization,dpop,atproto-accept-labelers,atproto-proxy" always;
		add_header access-control-allow-origin "*" always;
		return 200 '{"lastInitiatedAt":"2025-07-14T14:22:43.912Z","status":"assured"}';
	}
}

For Caddy users:

pds.example.com {
	handle /xrpc/app.bsky.unspecced.getAgeAssuranceState {
		header content-type "application/json"
		header access-control-allow-headers "authorization,dpop,atproto-accept-labelers,atproto-proxy"
		header access-control-allow-origin "*"
		respond `{"lastInitiatedAt":"2025-07-14T14:22:43.912Z","status":"assured"}` 200
	}
}

Background

Bluesky's implementation of age-based content restrictions is entirely client-side. The API (called the "AppView") doesn't impose content restrictions directly and isn't aware of your UK location as requests are proxied through your account's hosting server (called the "PDS").

bsky.app checks your location by making a request to https://bsky.app/ipcc, which returns:

  • countryCode: your country based on IP address, which is used for regional content moderation.
  • isAgeRestrictedGeo: whether your country mandates identity verification

When isAgeRestrictedGeo is true, it will then make a request to <pds host>/xrpc/app.bsky.unspecced.getAgeAssuranceState, which returns:

  • status: your account's current verification status
  • lastInitiated: when you last started the identity verification process

These workarounds exploit the client-side nature of these checks.

It remains unclear whether UK regulators would consider Bluesky's client-side implementation sufficient for OSA compliance. However, Bluesky Social PBC could reasonably argue the restrictions are effectively enforced for users on their PDS using unmodified clients, and that's as far as they can guarantee in a decentralized network like Bluesky.

What you can do next

While these workarounds provide immediate relief, consider taking action to address the root cause:

Support the petition to repeal the Online Safety Act

There's an ongoing petition to the UK Parliament calling for the repeal of the Online Safety Act. If you're a UK citizen or resident, consider signing it to show opposition to these privacy-invasive measures.

Contact your MP

Reach out to your Member of Parliament to express concerns about the Act's impact on privacy and digital rights. You can find your MP and their contact details on the UK Parliament website.

// ==UserScript==
// @name Bluesky age assurance bypass
// @version 1.0
// @match https://bsky.app/*
// @match https://main.bsky.dev/*
// @grant none
// @run-at document-start
// ==/UserScript==
const _fetch = globalThis.fetch;
globalThis.fetch = async function (req, init) {
if (req instanceof Request) {
const url = new URL(req.url);
switch (url.pathname) {
case "/xrpc/app.bsky.unspecced.getAgeAssuranceState": {
return Response.json({
lastInitiatedAt: "2025-07-14T14:22:43.912Z",
status: "assured",
});
}
}
} else if (req === "https://bsky.app/ipcc") {
return Response.json({
countryCode: "US",
isAgeRestrictedGeo: false,
});
}
return _fetch.call(this, req, init);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment