๐ฅ Discover this trending post from Hacker News ๐
๐ **Category**:
๐ **What Youโll Learn**:
simple *
static * social networking
self-reliant *
Quick start
- Fork this repo (see below if you need a different name from
satellite) - Enable GitHub Pages on your fork (deploy from the
mainbranch). - Visit the GitHub Pages URL (e.g.
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://โก/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 Protocol
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 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 other.
Identity
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.
Discovery
A s@-enabled site exposes a discovery document at:
GET https://๐ฅ/satellite/satproto.json
The discovery document simply contains the protocol version and the userโs 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.
Encryption Model
All user data is stored in an encrypted JSON store.
Only the user and users in the ownerโs follow list can decrypt it.
Keys
- 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)
and stored atkeys/{follower-domain}.json.
Self Key (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.
Key Rotation (Unfollow)
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
- Update
keys/_self.jsonwith the new content key - The unfollowed userโs old key no longer decrypts anything
Decryption Flow
When Bob visits Aliceโs site:
- Resolve Aliceโs data path (via
satproto_root.jsonor the default/satellite/) - Fetch
keys/bob.example.com.json - Decrypt the content key using Bobโs private key (
crypto_box_seal_open) - Fetch
posts/index.jsonto get the list of post IDs - Fetch and decrypt individual posts from
posts/{id}.json.enc
(XChaCha20-Poly1305 with the content key)
Data Schema
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.
Follow List
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"]
}
Feed Aggregation
The client builds a feed by:
- Reading the userโs follow list
- For each followed user, resolving their repo path
- 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
Replies
A 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.
Publishing
The 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
posts/{id}.json.encto userโs static site (e.g. via the GitHub Contents API) - Updating
posts/index.jsonto include the new post ID
Any secrets needed for publishing (e.g. GitHub token)
is encrypted in keys/_self.json (see Self Key).
Static Site Structure
{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
FAQ
Is this just RSS + PGP?
Yes!
Is this just AT Protocol but no firehose?
Yes!
Does this scale?
No! Neither does friendship.
Does the โsโ also stand for โslowโ and โshittyโ?
Absolutely!
Wait so I can self-host this?
Yes, youโll need to enable CORS.
{๐ฌ|โก|๐ฅ} **Whatโs your take?**
Share your thoughts in the comments below!
#๏ธโฃ **#social #networking #static #sites**
๐ **Posted on**: 1773293140
๐ **Want more?** Click here for more info! ๐
