sAT Protocol (s@) is a decentralized social networking protocol based on static sites.
Each user owns a static website storing all their data in encrypted JSON stores.
A WASM client running in the browser aggregates feeds and publishes posts.
It does not rely on any servers or relays.
In plain terms, s@ is designed for you and your friends, and no one else.
This applies to both the technical implementation and the user experience.
At the technical level, data only moves from your own website to your friend's browser.
There are no servers (like Mastodon) or relays (like the AT Protocol) in the middle.
And unlike almost all social media platform today,
s@ is not designed for influencers.
To see a friend's post or to have a friend see your post, you must follow each other1.
Of course it is still possible for a malicious actors to crawl and follow everyone,
but they would probably prefer a different platform anyways.
See Setup to deploy a sample implementation using GitHub Pages.
A user's identity is their domain name. Identity is authenticated by HTTPS/TLS - fetching content from a domain proves the domain owner published it.
A s@-enabled site exposes a discovery document at:
GET https://{domain}/.well-known/satproto.json
{
"satproto_version": "0.1.0",
"handle": "alice.com",
"display_name": "Alice",
"bio": "Hello world",
"public_key": "<base64-encoded X25519 public key>",
"sat_root": "/sat/"
}All user data is stored in an encrypted JSON store. Only users in the owner's follow list can decrypt it.
- Each user generates an X25519 keypair. The public key is published in the discovery document. The private key is stored in the browser's localStorage.
- A random content key (256-bit symmetric key) encrypts post data with XChaCha20-Poly1305.
- The content key is encrypted per-follower using libsodium sealed boxes
(
crypto_box_sealwith the follower's X25519 public key).
When the user unfollows someone:
- Generate a new content key
- Re-encrypt all posts with the new key
- Re-create key envelopes for all remaining followers
- The unfollowed user's old key no longer decrypts anything
When Bob visits Alice's site:
- Fetch Alice's
/.well-known/satproto.jsonto get her public key and sat_root - Fetch
sat/keys/bob.example.com.json - Decrypt the content key using Bob's private key
- Fetch
sat/posts/index.jsonto get the list of post IDs - Fetch and decrypt individual posts from
sat/posts/{id}.json.enc
Each post is stored as an individually encrypted file. The post index
(sat/posts/index.json) is a plaintext JSON file listing post IDs
newest-first, allowing clients to lazily load only recent posts.
A post object:
{
"id": "20260309T141500Z-a1b2",
"author": "alice.com",
"created_at": "2026-03-09T14:15:00Z",
"text": "Hello, decentralized world!",
"reply_to": null,
"reply_to_author": null,
"repost_of": null,
"repost_of_author": null
}Post IDs are {ISO8601-compact-UTC}-{4-hex-random}, e.g. 20260309T141500Z-a1b2.
The timestamp prefix gives natural sort order; the random suffix prevents collisions.
A repost is a post with repost_of and repost_of_author set. The text
field may be empty (pure repost) or contain commentary (quote repost).
When a client encounters a repost, it fetches and decrypts the original post from the original author's site. This means:
- The original content is always authenticated by the original author's TLS and encryption — reposters cannot forge content.
- If the original author deletes or edits the post, the repost reflects that.
- If the viewer doesn't have access to the original author's data (the original author doesn't follow them), they see the repost attribution without content.
The follow list is stored as a plain JSON file (unencrypted, since the key envelopes already reveal follows):
GET https://{domain}/sat/follows/index.json
{
"follows": ["bob.example.com", "carol.example.com"]
}The WASM client builds a feed by:
- Reading the user's follow list
- For each followed user, fetching their discovery document
- For each followed user, decrypting their posts (using the key envelope the followed user published for this user)
- Merging all posts, sorted by
created_atdescending
A reply is a post with reply_to and reply_to_author set. Replies are
aggregated the same way as regular posts. A user only sees replies from people
they follow — this is the spam prevention mechanism.
When viewing a post, the client scans followed users' posts for entries where
reply_to matches the post ID and reply_to_author matches the post's author.
The WASM client publishes posts by:
- Creating a new post with a unique ID
- Encrypting the post JSON with the content key
- Pushing the encrypted post as
sat/posts/{id}.json.encvia the GitHub Contents API - Updating
sat/posts/index.jsonto include the new post ID
The GitHub personal access token is stored in localStorage alongside the private key.
{domain}/
.well-known/
satproto.json # Discovery + profile + public key
sat/
posts/
index.json # Post ID list (plaintext, newest first)
{id}.json.enc # Individually encrypted post files
follows/
index.json # Follow list (unencrypted)
keys/
{domain}.json # Encrypted content key per follower
app/
index.html # WASM client shell
satproto_bg.wasm # Compiled WASM module
satproto.js # wasm-bindgen glue
style.css # Minimal styling
🚧🚧 This app is meant to demonstrate the main ideas of s@ 🚧🚧
🚧🚧 and is not (yet) a robust implementation. In particular, 🚧🚧
🚧🚧 each interaction is slow because it's literally making a 🚧🚧
🚧🚧 commit to GitHub and waiting for the page to update. 🚧🚧
Below are steps to set up a sample implementation of s@ using GitHub.
The protocol itself is agnostic to how the site is hosted,
and there is plan to support other hosts in the future.
- Create a new repo from the satellite template
- Enable GitHub Pages on the repo (use GitHub Actions as the source)
- Create a GitHub personal access token with
Contents: Read and writepermission on your repo - Visit
https://yourdomain/app/ - Enter your domain, GitHub repo (
owner/repo), and token - Click Save & Initialize — this pushes your profile, follow list, and empty post index to your repo
- Start posting!
If you want to build the WASM client yourself instead of using the template:
git clone https://github.com/remysucre/satproto.git
cd satproto
wasm-pack build crates/satproto-wasm --target web --out-dir ../../satellite/app/pkgEnter their domain in the follow input and click Follow. This:
- Fetches their public key from their
/.well-known/satproto.json - Encrypts your content key for them (so they can read your posts)
- Updates your follow list
After GitHub Pages propagates (~1 min), refresh to see their posts in your feed.
cargo test -p satproto-coreFootnotes
-
How do you ask a friend to follow? Idk, text them. Or just ask them in person. You're friends, right? ↩