TL;DR
- Apple's "Limit Adult Websites" filter starts with a bloom filter of 5.9 million domains and URLs that ships with the OS and updates over the air. A match triggers an encrypted lookup on Apple's servers, which make the final decision.
- 22,600 of the top 1 million sites are in it. Besides the porn sites, that includes archive.org, Tinder, Hinge, Bumble, OkCupid, 4chan, and a batch of proxy services added in the last year.
starbucks.com/menuis in the filter. Apple's hand-maintained allow list has 175 Starbucks menu pages on it, from the Pink Drink to the pumpkin spice latte.- A bloom filter can only be queried, so there's no list to dump. The code below checks any site against it.
The question
My previous article about the word list macOS uses to rate swear words ended up on Hacker News, and Retr0id asked for something else:
I'd appreciate if someone could dump the iOS "adult websites" list (which are blocked by default in the UK unless you submit government ID etc.)
A few people in the thread didn't believe this part. Since iOS 26.4 in March 2026, UK iPhones ask you to prove you're an adult. If you don't, the account is treated as a minor's and the web content filter turns on in Safari and every other browser (Phandroid, Cybernews).
The same thread had a few theories about how it works. flanz_10 said
it isn't a list but ML classification of the whole URL and the page content.
cuttysnark asked how a list like that could keep up with new sites.
Retr0id later added that they'd been told it's a bloom filter in
/System/Library/PrivateFrameworks/WebContentRestrictions.framework.
I had Claude dig into it. Everything below comes from that session, run on macOS 15.6, which has the same framework.
What's on disk
The framework is there on a Mac too, with two data files:
/System/Library/PrivateFrameworks/WebContentRestrictions.framework/Resources/
├── WCRFilter-2025-04-07.plist 14 MB
└── WCRAppleAllowList-2025-04-07.plist 334 URLs
The filter plist has six keys:
bitVectorData <13,958,134 bytes>
expectedNumberOfItems 4617705
falsePositiveTolerance 9e-06
murmurSeed 3489179881
numberOfBits 111665066
numberOfHashes 17
A bloom filter only stores bits. You can ask it whether pornhub.com is in there and get "probably" or "definitely
not", and there is no way to get the list back out. With a false-positive rate of 9 in a million, about one random
string in 110,000 gets a wrong "probably".
The file updates itself
Those April 2025 files are only the fallback that ships with the OS. The first time Claude's test program loaded the framework, macOS asked the MobileAsset service for a fresh copy, and these files appeared within two minutes:
/System/Library/AssetsV2/com_apple_MobileAsset_WebContentRestrictions/purpose_auto/<hash>.asset/AssetData/
├── Bloom Filters/WCRFilter-2026-10-05.plist 17.9 MB, 5,907,794 entries
├── Apple Allow Lists/WCRAppleAllowList-2026-10-05.plist 653 URLs
└── Authentication Sites/WCRAuthenticationSites-2026-09-25.plist 1,621 URLs
The asset catalog says it came from gdmf.apple.com, version 1.0.0.11.6694, posted on 13 October 2026. That
answers cuttysnark's question: Apple pushes the list to devices as a downloadable asset, separately from OS updates,
and it has grown by 1.3 million entries since the April 2025 copy.
How a URL gets checked
The framework is written in Objective-C with a little Swift, so you can load it with dlopen and call its methods
directly.
+[WCRBloomFilter _matchingStringsForURL:] shows what gets looked up. For one URL it generates every host, every
parent domain and every path prefix:
https://sub.a.b.reddit.com/r/nsfw?x=1
sub.a.b.reddit.com/r/nsfw?x=1
sub.a.b.reddit.com/r/nsfw
sub.a.b.reddit.com/r
sub.a.b.reddit.com
a.b.reddit.com
b.reddit.com
reddit.com
com
If any of those strings is in the bloom filter, the URL goes on to a server check. Entries can be paths and full URLs as well as domains, so flanz_10 was partly right about it looking at the whole URL.
Disassembling +[WCRBloomFilter _shouldBlock:withBloomFilter:useCipherML:] shows what happens next:
- No key in the bloom filter: allowed.
- A key hits, and the device can reach Apple's private lookup service (
CipherML, use casewebContentRestrictions): the matching key is sent as an encrypted keyword lookup. The server answers with a one-letter category:v,|,gorw, which map to category codes 301, 303, 1505 and 501. Any of those four means blocked. No answer means allowed. - A key hits and the lookup fails: the framework logs
CipherML error: %s. Falling back to local filter verdict.and blocks the page.
Apple's server lookup can't be tested from outside, because ciphermld refuses any process without Apple's entitlement
(Client does not have specified entitlement). Everything below is about the bloom filter.
On the Mac, the caller is WebContentAnalysis.framework, the parental-controls framework WebKit uses for content
filtering, which dates back to Mac OS X Leopard. It feeds the Apple allow list into its whitelist, asks
WCRBloomFilter for a verdict, and also has a page-content classifier in the same code path.
Checking sites against it
The easiest way to get the same results as the framework is to let it do the hashing. This program reads domains or URLs from stdin and prints the ones that match, along with the key that matched:
// wcrcheck.m: test domains or URLs against Apple's web content filter.
// clang -O2 -fobjc-arc -framework Foundation wcrcheck.m -o wcrcheck
// usage: wcrcheck [-a] [filter.plist] < list (one domain or URL per line; -a prints misses too)
#import <Foundation/Foundation.h>
#import <objc/message.h>
#include <dlfcn.h>
int main(int argc, char **argv) { @autoreleasepool {
dlopen("/System/Library/PrivateFrameworks/WebContentRestrictions.framework/WebContentRestrictions", RTLD_NOW);
Class WCR = NSClassFromString(@"WCRBloomFilter");
Class Shim = NSClassFromString(@"_TtC22WebContentRestrictions15BloomFilterShim");
BOOL all = NO; NSString *path = nil;
for (int i = 1; i < argc; i++) { if (!strcmp(argv[i], "-a")) all = YES; else path = @(argv[i]); }
if (!path) path = @"/System/Library/PrivateFrameworks/WebContentRestrictions.framework/Resources/WCRFilter-2025-04-07.plist";
id bloom = ((id(*)(id,SEL,id))objc_msgSend)([Shim alloc], sel_registerName("initWithPath:"), path);
if (!bloom) { fprintf(stderr, "could not load %s\n", path.UTF8String); return 1; }
SEL keysFor = sel_registerName("_matchingStringsForURL:"), contains = sel_registerName("contains:");
char line[8192];
while (fgets(line, sizeof line, stdin)) { @autoreleasepool {
line[strcspn(line, "\r\n")] = 0;
if (!*line) continue;
NSString *s = @(line);
if (![s containsString:@"://"]) s = [NSString stringWithFormat:@"https://%@/", s];
NSURL *url = [NSURL URLWithString:s];
if (!url) continue;
NSMutableArray *hits = [NSMutableArray array];
for (NSString *k in ((id(*)(id,SEL,id))objc_msgSend)(WCR, keysFor, url))
if (((BOOL(*)(id,SEL,id))objc_msgSend)(bloom, contains, k)) [hits addObject:k];
if (hits.count || all) printf("%s\t%s\n", line, [[hits componentsJoinedByString:@","] UTF8String]);
}}
}}
$ printf '%s\n' pornhub.com example.com archive.org https://www.starbucks.com/menu/drinks/refreshers \
| ./wcrcheck -a "$NEW"
pornhub.com pornhub.com
example.com
archive.org archive.org
https://www.starbucks.com/menu/drinks/refreshers starbucks.com/menu
$NEW is the path to the downloaded WCRFilter-2026-10-05.plist. The whole Tranco top million takes 12 seconds.
What's in it
Claude ran the Tranco top 1M through both filters:
| 2025-04-07 filter | 2026-10-05 filter | |
|---|---|---|
| Entries in the filter | 4,617,705 | 5,907,794 |
| Tranco top-1M domains hit | 20,113 | 22,600 |
| In both | 19,596 | |
| Added since April 2025 | 3,004 | |
| Dropped since April 2025 | 517 |
Expect about 10 to 20 false positives in each column. The two filters use different seeds, so a domain that hits both is almost certainly a real entry.
The UT1 adult blocklist from Université Toulouse Capitole has 4.6 million domains, and 1.14 million of them (25%) are in Apple's filter. Many of the UT1 domains are dead or parked, so the share of active adult sites in Apple's filter is probably higher than 25%.
The highest-ranked hits after archive.org are xhamster, Stripchat, OnlyFans, xvideos, Pornhub and Chaturbate. Most of the hits that aren't porn sites fall into the groups below.
archive.org
The Internet Archive is rank 153 on the web, the highest-ranked site in the filter, and it was added after April 2025.
web.archive.org hits as its own key too. My guess is that it's there because the Wayback Machine serves snapshots of
sites that are blocked.
Dating apps
Tinder (tinder.com and its API domain gotinder.com), Hinge, Bumble, OkCupid, Feeld, Match, Badoo, Plenty of Fish,
eHarmony and OurTime are in both filters, while Grindr is in neither.
Proxies, added in the last year
This is the biggest change between the two files. None of these are in the April 2025 filter, and all are in the new one:
- Residential proxy sellers:
oxylabs.io,decodo.com,soax.com,webshare.io,dataimpulse.com,packetstream.io,iploop.io,geonode.io,nodemaven.com,711proxy.com,piaproxy.net - Web proxies:
croxyproxy.com,proxysite.com,proxyium.com,blockaway.net,anonymouse.org - A few VPNs:
hide.me,goldenfrog.com(VyprVPN),amnezia.org,vpngate2.jp
NordVPN, ExpressVPN, Proton, Mullvad, Surfshark, Windscribe, Tor and Psiphon are not in either filter. Blocking a VPN's website doesn't stop an installed VPN anyway, since the filter only looks at pages loaded in a browser.
Imageboards and art sites
4chan.org, Danbooru (donmai.us), Fur Affinity, e621, Inkbunny, Pixiv Fanbox and the doujin shop Melonbooks are in.
Reddit, X, Tumblr, Discord, Twitch, Patreon, DeviantArt and Pixiv itself are not, at least at the domain level.
What isn't in it
None of these hit: Planned Parenthood, Scarleteen, Sex, Etc., the Trevor Project, GLAAD, Stonewall, The Advocate, Erowid, bet365, PokerStars, DraftKings, Kiwi Farms, Stormfront, and Wikipedia articles like Pornography or Sex education.
The allow list
The allow list holds URLs that should never be blocked, presumably because each one was blocked by mistake at some point. The 2025 list had 334 URLs and the 2026 list has 653, with none in common.
By site, the 2026 list looks like this:
| Site | URLs |
|---|---|
| starbucks.com | 175 |
| findagrave.com | 17 |
| rover.com | 15 |
| asus.com | 10 |
| emojis.com | 9 |
| youtube.com | 8 |
| en.wikipedia.org | 7 |
The Starbucks entries are menu pages: refreshers/pink-drink, espresso/flat-white, espresso/pumpkin-spice-latte,
chocolate/white-hot-chocolate, bakery/almond-croissant. The new bloom filter has starbucks.com/menu as a path
prefix, so every page under it matches, and the allow list exempts 175 of those pages one by one.
Other entries:
- Rover, the dog-sitting site:
can-dogs-eat-baked-beans,bell-training-for-dogs,flower-names-for-dogs. - emojis.com: the pages for the red flag emoji, the bell with slash, and "woman getting haircut".
- Wikipedia: Rise of the Guardians, Nikolaj Coster-Waldau, the baseball player Blake DeWitt, the chemistry term allomone and the 9th-century Indian emperor Amoghavarsha.
- Quora: "How does a fetus breathe" and "What happens if you leave a gas stove on all night".
- ASUS India: ten laptop and support pages.
The third file, WCRAuthenticationSites, is a separate always-allow list of 1,621 URLs: Apple ID sign-in, Apple's
child-safety pages, and child helplines and abuse-reporting sites in dozens of countries, so a filtered device can
always reach them.
Caveats
- The server decides. When a site matches the bloom filter, the device asks Apple's server, and the server can still allow it. Only Apple-signed processes can make that request, so the results here only show which sites get checked. That is most likely Apple's list of flagged sites, plus some false positives.
- False positives. Any single hit from a big list could be noise, at about one domain in 110,000. A hit in both filters is very unlikely to be noise, and the new-only hits named above either come in groups or match on more than one key.
- One Mac. This is macOS 15.6 with the asset it downloaded on 4 November 2026. iOS uses the same framework and asset
type, and the file Retr0id was pointed to is dated
2025-09-29, so devices aren't always on the same version. - Category codes. What 301, 303, 1505 and 501 mean isn't documented anywhere public.
- Private API. All of this can change in any update.
Try it yourself
# Build the checker from above
clang -O2 -fobjc-arc -framework Foundation wcrcheck.m -o wcrcheck
# The fallback filter that ships with macOS
echo archive.org | ./wcrcheck -a
# The current one, after the framework has been loaded once and the asset has downloaded
NEW=$(find /System/Library/AssetsV2/com_apple_MobileAsset_WebContentRestrictions -name 'WCRFilter-*.plist' | sort | tail -1)
echo archive.org | ./wcrcheck -a "$NEW"
# The allow lists are plain XML
plutil -p "$(dirname "$NEW")/../Apple Allow Lists/"*.plist | grep starbucks | head
If you have a UK iPhone in child mode, you can test what the server decides: open the Starbucks menu, archive.org and a proxy site, and see which ones get blocked.
