Invite-only End-to-end encrypted · Federated · Zero idle cost

Eject. Social.

Leave Big Social. Keep your friends, your photos and your conversations on a node you own.

Chat, a feed and photo albums for a handful of people you actually know. Everything you write or upload lives in one small serverless stack. Friends on other nodes get a pointer, never a copy, so when you delete something it is gone for everyone.

Early software, reviewed once internally. Read the status before trusting it with anything sensitive. Already in? Sign in.

eject.social · alice on dev-a
The Photos screen in Eject with an album named Lake weekend showing six photos.
Built on AWS LambdaDynamoDBS3 or any S3-compatible storeAppSyncWebCryptoEd25519
The one idea

Content lives only in its author's node. Everything else follows.

Most networks copy your words and pictures onto every recipient's server. This one hands out references instead, and reads the original from you, on demand, every time.

It stays with you

Your messages, posts and photos are stored once, in your own node's database and bucket. No other node ever persists them. Deleting removes the only copy.

Friends get a pointer

What arrives on a recipient's node is provenance and ordering: who wrote, on which node, into which thread. Never the body, a preview or a thumbnail.

Nobody reads it, not even your node

Every message and every photo is sealed in your browser to your recipients' devices. Your node stores an envelope it cannot open.

How it works

One photo, from Alice's browser to Bob's.

Two nodes, each a small serverless stack in its owner's cloud account, in their own colours as they appear in the app. Solid lines carry content. Dashed lines carry only pointers.

content pointer / provenance readable only in a browser
  1. Alice attaches a photo. Her browser encrypts it in 256 KB chunks and uploads the ciphertext straight into her node's bucket with a short-lived presigned URL.
  2. She sends. Message and media key are sealed to every recipient device, and the envelope becomes a row in her node's table. Her node cannot read it.
  3. A pointer travels. Her node signs the request with its Ed25519 identity and delivers provenance and ordering to Bob's node. No text, no preview, no thumbnail.
  4. Bob's node writes only the pointer and pushes it to his open tab, so his chat renders an ordered, attributed skeleton from local data at once.
  5. Reading hydrates from the owner. Bob's browser asks his node, which asks Alice's. Access is decided by Alice's audience at that moment. The answer is relayed, never stored.
  6. The photo comes from Alice's bucket with a presigned URL and is decrypted in Bob's browser with the key from inside the envelope.
  7. Alice deletes. The object and the row are removed. Bob's pointer resolves to "deleted" because there is nothing left to serve.
What you get

Ordinary things, on unusual terms.

A small social network for people who know each other. Nothing in it needs an account with anyone but the person who runs your node.

The same album on a phone: the Eject top bar above a grid of photos.

One stream. Albums point into it.

Every photo is encrypted on your device and stored once. An album only refers to it, so a photo can sit in as many albums as you like.

Encrypted

Chats and groups

Sealed per message to every recipient device. New members see what comes after they joined, and nothing before. Safety numbers let you verify a contact in person.

Feed

Posts and comments

A post is a conversation keyed by its own id, so comments are ordinary messages owned by whoever wrote them. Likes are counted, never attributed: a name is never stored.

Photos

Share without copying

Sharing an album photo into a chat points at the same bytes with a new audience. Delete it from the album and it disappears everywhere it was shared.

Photos

Keep what you were sent

Saving a shared photo into your album is a real copy, re-encrypted under your rules, so it survives the sender's delete. Keeping means holding your own bytes.

On-device

Sorted by when it was taken

EXIF dates and a perceptual hash are read in the browser and stored inside the encrypted envelope. Duplicates are skipped before they are uploaded.

Devices

New devices wait at the door

The first device is trusted on sight. Every later one reads nothing until a device you already hold signs it in, so a stolen password alone adds no reader.

Recovery

A code that opens history

Lose a device and a 160-bit recovery code, shown once, brings your messages back on the next one and lets it in, even with no other device left. Your node holds only a wrapped blob.

Push

Notifications without content

Sent by your own node, carrying only who wrote, where. No other node learns your device endpoints.

Photos

What always-on encryption costs, honestly

Every photo
Storage provider can see photosNo
ThumbnailsMade on your device at upload
Subject and face detectionOn your device only
Lose every device and the codeGone forever
Safety

Designed to be safe. Honest about where that stops.

Both panels matter. A promise without its boundary is marketing.

What the design guarantees

  • End-to-end encryption. One random content key per message, sealed to every recipient device with ECDH P-256 + HKDF + AES-256-GCM. All WebCrypto; private keys are non-extractable and never leave the device.
  • Media is always encrypted. Files are sealed in 256 KB chunks, each authenticating its own index, so nothing can be reordered, dropped or silently truncated.
  • No silent downgrade. If nobody in a conversation has published a device key, sending fails instead of falling back to plaintext.
  • Nodes are keypairs. A node's identity is an Ed25519 key. Federation requests are signed and nonce-protected. Nodes never need each other's cloud account ids.
  • No registry. Pairing begins only with an invite passed out of band, single use, seven-day expiry.
  • Likes are counted, never attributed. The owner stores an opaque per-item token, never a name.
  • Recovery without a server secret. The recovery code never reaches the node and is not derived from anything you chose.

What it does not cover

  • No forward secrecy. A stolen device key opens the past messages that device received. Fixing that means a ratchet, which is its own project.
  • Metadata is visible. Who talks to whom, and when, stays visible to node operators. Encryption never fixed that.
  • Trust on first use. Keys come from the recipient's own node, so a dishonest node could offer one it controls. Comparing safety numbers out of band is what catches it.
  • Not DRM. A recipient's browser renders the content and could screenshot it. The guarantee is "no copy at rest on their node", which is real but bounded.
  • Stories end, screenshots do not. A story is gone from every node a day later. Anyone who looked before then may have kept a picture of it.
  • Today goes through your node. Weather, "on this day" and the feeds you follow are fetched by your own node, so the source never sees you. Your node does see which addresses it fetched. Nothing from outside runs as code, and nothing reaches your friends unless you share it.
  • The recovery code is everything. Holding it equals holding every message. Lose every device and the code, and it is gone.
  • Open review findings. Several guarantees above are not yet fully upheld by the implementation. See the status below.
Status · September 2026

Early software, honest about it.

Everything on this page is built and verified against real nodes, including a test suite in which a paired node deliberately misbehaves. An internal security review in September 2026 shaped the last three rounds of work. What has not happened yet is an independent review.

Use it with people you know. Do not treat it as a public service until someone outside the project has looked at it.

Where the details are

  • Security policy · how to report, what is in scope, what is known and accepted.
  • Threat model · each promise, whom it holds against, and where it stops.
  • Incident process · what an operator does when a peer, an account, a device or a key is compromised.
  • Review and stage notes · the findings and what changed, in the repository.

All of it lives next to the code, at the repository.

A

Private test operation · done

Authorisation and pairing fixed; account separation and sign-out corrected; plaintext paths closed; deletion repaired; URL checks and input limits added.

Done.

B

Growing private network · done

Device approval and revocation, key-change warnings, two-factor sign-in, blocking people and nodes, per-account quotas, retrying delivery, opt-in alarms.

Done.

C

Public product · code done

Device-bound changes, one-time signup links, operator role, reports with chosen evidence, export, account deletion, incident process, threat model, load test.

Open: an independent review, when there are users beyond the people who built this.

Get started

One stack, a few scripts, your own node.

A node is one CloudFormation stack in your cloud account. Deploy it, give it a keypair, create the people who belong there, and add friends with an invite link.

Get an account

Invite-only, by design.

1

Ask someone who runs a node. There is no sign-up form anywhere. Accounts exist only when an operator creates them.

2

They create you with one command, create-user.ts, and hand you the password out of band. You sign in to that node and nowhere else.

3

Add friends on other nodes by opening the invite link they send you, or by scanning its QR code with your phone camera. Eject asks you to confirm. It works once, expires in seven days, and no third node learns it happened.

4

Or run your own. A node for a handful of people costs nothing while idle. The steps are on the right.

AWS accountNode.js 22+npx cdk bootstrap once~10 minutes
deploy-node.sh
# credentials for the account that will own the node
export AWS_PROFILE=me AWS_REGION=eu-central-1
npm install

# one stack per node; nodeId becomes its name everywhere
cd packages/node-infra
npx cdk deploy -c nodeId=home --require-approval never
cd ../..

# identity and push keys live in SSM, never in the repo
npx tsx scripts/init-node-key.ts home
npx tsx scripts/init-push-keys.ts home

# the people who belong on this node
npx tsx scripts/create-user.ts home alice --email alice@example.com

# point the web client at it and run it
npx tsx scripts/write-web-config.ts home
npm run web                       # http://localhost:5173

# optional: keep media at Hetzner, R2 or any S3-compatible store
# set PN_STORAGE_* then: scripts/set-storage-credentials.ts, -c storage=external
  • The stack hosts the API, not the web client. Serve the built client yourself, or run it locally.
  • On iPhone and iPad, notifications need the app added to the Home Screen.
  • Add -c retainData=true for a node whose data must survive stack deletion.
  • Pair only with people you know. A paired node is trusted to deliver pointers into shared conversations; it is not trusted with anything else.
What it costs

Nothing while idle. Stored bytes when not.

Only services with no standing charge are used, because a fixed cost per node multiplies across the whole mesh.

$0

Per month, idle

  • Lambda, API Gateway, AppSyncper request
  • DynamoDB on-demandper request
  • SQS, EventBridgeper message
  • Cognito, LITE planfree tier
  • SSM parametersfree
Bytes

The one running line

  • Media in S3per GB stored
  • Rows in DynamoDBper GB stored
  • Logs, one week retentionper GB stored
  • Or an external storetheir price
Left out

On purpose

  • Customer-managed KMS key~$1/mo
  • Route53 zone, custom domain$0.50/mo
  • WAF web ACL~$5/mo
  • CloudWatch alarms~$0.10 each

A few alarms fit the CloudWatch free tier; they are opt-in.

Press eject

A network you can hold in one stack.

No registry, no central service, no third party in the middle. Just the people you invited, on nodes they own.