Dev.to Security 🔐 Cybersecurity 👁 0 📖 8 min read

Can Someone See Who You Blocked on Bluesky? Yes. Here's How to Check

Every account you've blocked on Bluesky is sitting in a public JSON endpoint. So is every post you've liked, everyone you follow, and every list you've added someone to. You don't need an account, an API key or a scraper

Every account you've blocked on Bluesky is sitting in a public JSON endpoint. So is every post you've liked, everyone you follow, and every list you've added someone to. You don't need an account, an API key or a scraper to read it, just one curl.

So can someone see who you blocked on Bluesky? Yes, and that includes the person you blocked. Is Bluesky private? For your social graph, no. Most people who came over from X assume blocks and likes work like they did there, visible to you and the platform and nobody else. On AT Protocol they're records in your public repository, and anyone can download that repository.

This came up again on October 29, when Melanie Kat published Do At-Proto Users Know This Is Public?. She looked at Skyreader, an RSS reader built on atproto, which stores each feed you subscribe to as a record in your repo. Without logging in anywhere, she pulled over 1,300 feed URLs, including someone's private Club MacStories feed, paywalled 404 Media full-text feeds, Patreon backer-only feeds and private GitLab repo feeds. With feeds like these the URL works as the password, and Kat says the MacStories one gave her full access to the paid content. The Lobsters thread got heated fast. One commenter described the privacy model as "none".

What anyone can read from your PDS with curl

Your account lives on a PDS (Personal Data Server). For most people that's a server run by Bluesky, but the data model is the same everywhere. Your repo is split into collections, and each collection holds records. Here's how you look yourself up:

HANDLE=yourname.bsky.social

# handle -> DID
DID=$(curl -s "https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=$HANDLE" | jq -r .did)

# DID -> which PDS hosts you (works for did:plc, which is almost everyone)
PDS=$(curl -s "https://plc.directory/$DID" | jq -r '.service[] | select(.id=="#atproto_pds") | .serviceEndpoint')

# what collections exist in your repo
curl -s "$PDS/xrpc/com.atproto.repo.describeRepo?repo=$DID" | jq .collections

# everyone you've blocked
curl -s "$PDS/xrpc/com.atproto.repo.listRecords?repo=$DID&collection=app.bsky.graph.block&limit=100" \
  | jq -r '.records[] | "\(.value.createdAt) \(.value.subject)"'

That last command prints a timestamp and a DID for every block (the first 100 of them; the response includes a cursor if there are more). Swap the collection for app.bsky.feed.like and you get every post you've liked, with dates.

If you'd rather grab the whole thing at once:

curl -s -o repo.car "$PDS/xrpc/com.atproto.sync.getRepo?did=$DID"

The official docs for com.atproto.sync.getRepo list one required parameter, the DID. You get back an application/vnd.ipld.car archive of your entire repo, and the call usually needs no auth.

If clicking is more your thing, pdsls.dev does the same in a browser. Type a handle and browse every collection. That's the tool Kat used.

Reading one person's repo is the small version of the problem. A relay can also answer "which accounts have any records in collection X?" through com.atproto.sync.listReposByCollection:

curl -s "https://bsky.network/xrpc/com.atproto.sync.listReposByCollection?collection=app.bsky.graph.block&limit=2"

That's the kind of relay query Kat describes. It took her from a few reviewers' handles to every DID with Skyreader subscriptions, and then she pulled each one's records from their PDS. She says a few Discord clones store every message openly too, and that you can watch and search chat across Stream.place. Third-party sites like ClearSky build on the same data to show who is blocking whom.

Public vs private on Bluesky

Data Where it lives Who can read it
Posts, reposts app.bsky.feed.post, app.bsky.feed.repost Anyone
Likes app.bsky.feed.like Anyone
Follows app.bsky.graph.follow Anyone
Blocks app.bsky.graph.block Anyone
Lists you made, and who's on them app.bsky.graph.list, app.bsky.graph.listitem Anyone
Blocklists you subscribe to app.bsky.graph.listblock Anyone
Records from third-party atproto apps Whatever lexicon the app picked Anyone, unless the app keeps it off your PDS
Mutes Bluesky App, not your PDS You
Bookmarks Bluesky App, not your PDS You
Preferences (muted words, saved feeds) Your PDS, behind auth You
DMs Bluesky's chat service You, the other person, and Bluesky

The mutes and bookmarks rows come straight from Bluesky's docs: "Mutes are private — like Bookmarks, they are stored in the Bluesky App, rather than in your PDS." The same page has a short line on blocks: "Blocks are public."

DMs aren't in your repo, though that doesn't make them private. Native Bluesky DMs are not end-to-end encrypted. Coverage of the launch said E2EE was coming "down the line" and that Bluesky's moderation team may need to open your DMs to investigate abuse. If you need E2EE, Germ is a separate MLS-based messenger that shows up inside the Bluesky app.

The official app hides this a little. You can see the Likes tab on your own profile and nobody else's. That's purely a UI choice, and the records are still served to anyone who asks the API.

Why Bluesky blocks are public

The atproto team explained this in June 2023 in Why are blocks on Bluesky public?. The short version: a block has to be enforced by servers you don't control. Your PDS, the AppView, feed generators and any third-party client all need to know that A blocked B, or B's replies still show up under A's posts. As the post puts it, "all servers across the network need to know that they exist in order to respect the user's request."

Mutes skip the problem because a mute only changes what you see, so the client can handle it alone.

The team listed alternatives they considered: Bloom filters, keeping block data on a small set of trusted AppViews, and zero-knowledge proofs like SNARKBlock. Their objection to Bloom filters was that they "don't fully prevent enumerating blocks", on top of the storage and bandwidth cost. In the end they shipped public blocks and said they hadn't ruled the others out. That was more than three years ago, and blocks are still public.

I think the engineering reasoning holds up. The way the app presents it doesn't. The block button looks exactly like the one on X. Nothing tells you that you're writing a timestamped, permanent public record saying "I don't want to hear from this person." For someone blocking a stalker or an ex, that matters, because the person they blocked can find out.

Is Bluesky fixing it? Permissioned data

Sort of. Bluesky is working on "permissioned data", which would let records live on your PDS without being broadcast to the whole network. Daniel Holmgren's February 2026 design diary describes it as data that "isn't broadcasted out over the firehose" and that only users and services with explicit permission can read. His examples include private groups, paywalled content, mutes, bookmarks, drafts and DMs. He rules out end-to-end encryption as the base design, mostly because search, notifications and moderation all need servers that can read the data, and leaves E2EE to individual apps that need it.

Blocks aren't on that list. Even when permissioned data ships, I wouldn't expect block records to move off the public repo, because the enforcement problem from 2023 is still there.

What you can do today

  1. Audit your own repo. Run the commands above or open pdsls.dev and look at every collection. The third-party ones from apps you tried once and forgot are where the surprises are.

  2. Mute when you don't want it to be known. A mute is private and a block is not. If the goal is to stop seeing someone without them being able to find out, mute them.

  3. Treat list membership as public speech. Adding someone to a moderation list publishes that they're on it and that you put them there.

  4. Delete old likes, with realistic expectations. Unliking deletes the record from your repo, so listRecords and getRepo stop returning it. But the like went out over the relay firehose when you made it, and Jetstream rebroadcasts the same records as JSON that anyone can filter by collection or DID (FeatherReader's network notes walk through both). The delete is just another event. Any indexer or archive that was listening keeps its copy unless it chooses to honor the delete. Deleting helps against casual snooping. It won't stop someone who was already collecting.

  5. Check third-party apps before you sign in. In the Lobsters thread, Skyreader's developer said subscription syncing is already off by default "with a big warning that enabling it will make your subscriptions public," and that the real fix is moving subscriptions to atproto spaces once they're ready. Other apps make their own choices. If an app writes custom records to your PDS, assume they're public.

  6. Don't self-host a PDS for privacy. A lot of people suggest it, and it doesn't help. A self-hosted PDS serves the same public repo through the same getRepo endpoint, and it has to, or the rest of the network can't see your posts or enforce your blocks. Self-hosting gives you control over where your data lives and who can take your account down, and that's about it. You'll also need a server with a public IP, DNS and ports 80/443 open (the bluesky-social/pds README covers the setup).

  7. Or accept it. Bluesky is a public broadcast network with a nice moderation UI on top. If you post on the assumption that anyone can read everything you do, including who you've blocked and what you liked at 2am, you won't be surprised by it.

I don't think "everything is public" is a bug. AT Protocol made that trade on purpose, in exchange for account portability and an open firehose, and documented it. The bug is that millions of people signed up through an app that looks like Twitter and never got told.

Did you know your Bluesky blocks were public? And for a social network, do you see "everything is public" as a feature, or did the protocol skip a problem it should have solved on day one?

Sources

📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.