s@: social networking over static sites simple *
static * social networking
self-reliant *
satellite)main branch).https://username.github.io/satellite/)While this sample implementation uses GitHub, the protocol is agnostic to the hosting service.
Using a custom repo name:
by default, the client looks for data at https://{domain}/satellite/.
If you already have a satellite/ path for something else, add a satproto_root.json
file to the root of your main site (e.g. the username.github.io repo)
pointing to the actual repo:
{ "sat_root": "my-custom-repo" }
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 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 middle1.
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 other2.
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}/satellite/satproto.json
The discovery document simply contains the protocol version and the user’s public key:
{
"satproto_version": "0.1.0",
"public_key": "<base64-encoded X25519 public key>"
}
By convention, the client looks under /satellite/ by default.
If that path is already taken, place a satproto_root.json file at the domain
root containing { "sat_root": "my-custom-repo" } — the client checks this first.
All user data is stored in an encrypted JSON store. Only the user and users in the owner’s follow list can decrypt it.
crypto_box_seal with the follower’s X25519 public key)
and stored at keys/{follower-domain}.json.keys/_self.json)The user’s content key and publishing secrets (e.g. GitHub access tokens)
are bundled into a single sealed box (crypto_box_seal with the user’s own public key)
and stored at keys/_self.json. Only the user’s private key can open it.
This allows a user to sign back in on a new device or after clearing browser storage — they only need their domain and private key.
When the user unfollows someone:
keys/_self.json with the new content keyWhen Bob visits Alice’s site:
satproto_root.json or the default /satellite/)keys/bob.example.com.jsoncrypto_box_seal_open)posts/index.json to get the list of post IDsposts/{id}.json.enc
(XChaCha20-Poly1305 with the content key)Each post is stored as an individually encrypted file. The post index
(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
}
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.
The follow list is stored as a plain JSON file (unencrypted, since the key envelopes already reveal follows):
GET https://{domain}/satellite/follows/index.json
{
"follows": ["bob.example.com", "carol.example.com"]
}
The client builds a feed by:
created_at descendingA reply is a post with reply_to and reply_to_author set.
Replies are grouped as flat threads under the original post — nested replies
(replying to a reply) are not supported; you can only reply to top-level posts.
Threads are positioned in the timeline by the original post’s created_at;
replies within a thread are sorted by their own created_at ascending.
If the original post is inaccessible (e.g. the viewer doesn’t follow the author), the reply is hidden entirely. A user only sees replies from people they follow — this is the spam prevention mechanism.
The client publishes posts by:
posts/{id}.json.enc to user’s static site (e.g. via the GitHub Contents API)posts/index.json to include the new post IDAny secrets needed for publishing (e.g. GitHub token)
is encrypted in keys/_self.json (see Self Key).
{domain}/satellite/
satproto.json # Discovery + profile + public key
posts/
index.json # Post ID list (plaintext, newest first)
{id}.json.enc # Individually encrypted post files
follows/
index.json # Follow list (unencrypted)
keys/
_self.json # Sealed box: content key + credentials (owner only)
{domain}.json # Sealed box: content key for follower