2026-10-01 11:23:03 +02:00
# Federation
PrivaPub is an ActivityPub server written in C#. This document follows
[FEP-67ff ](https://codeberg.org/fediverse/fep/src/branch/main/fep/67ff/fep-67ff.md ) and describes how it federates.
## Supported federation protocols and standards
- [ActivityPub ](https://www.w3.org/TR/activitypub/ ) (server-to-server)
- [WebFinger ](https://webfinger.net/ )
- [HTTP Signatures ](https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures ), `rsa-sha256` / `hs2019` with RSA keys
- [NodeInfo ](https://nodeinfo.diaspora.software/ ) 2.0 and 2.1
## Supported FEPs
- [FEP-67ff: FEDERATION.md ](https://codeberg.org/fediverse/fep/src/branch/main/fep/67ff/fep-67ff.md )
- [FEP-f1d5: NodeInfo in Fediverse Software ](https://codeberg.org/fediverse/fep/src/branch/main/fep/f1d5/fep-f1d5.md )
- [FEP-2c59: Discovery of a WebFinger address from an ActivityPub actor ](https://codeberg.org/fediverse/fep/src/branch/main/fep/2c59/fep-2c59.md )
Planned: FEP-1b12 (communities), FEP-8fcf (followers synchronisation), FEP-5feb (`indexable` ), FEP-7628 (Move),
FEP-044f (quotes).
## Actors
Every account is an *avatar* : one private login can own several, and they are deliberately unlinkable. Nothing in an
actor document, a collection, NodeInfo or a delivery relates two avatars of the same login.
| Thing | Address |
|---|---|
| Actor (Person, Group, Application) | `/peasants/{name}` (`/users/{name}` redirects) |
| Inbox | `/peasants/{name}/mouth` |
| Outbox | `/peasants/{name}/anus` |
| Shared inbox | `/human-centipede` |
| Followers / following | `/peasants/{name}/groupies` , `/peasants/{name}/stalking` |
| Objects | `/peasants/{name}/scribbles/{id}` |
| Activities | `/peasants/{name}/grunts/{id}` |
| Direct-message context | `/peasants/{name}/whispers/{id}` |
| Profile and post pages | `/@{name}` , `/@{name}/{id}` |
The names are the project's own and are stable; resolve actors through WebFinger, not by guessing a path.
- The key is `{actor}#main-key` , RSA 2048, served as SPKI PEM with `owner` set to the actor.
- `published` on an actor is truncated to the day. `indexable` is `false` .
- The instance actor is `/peasants/privapub` (type `Application` ). It signs every fetch PrivaPub makes, so no avatar's key
is used to read another server's content.
## Groups
A group is either a **community** or a **circle** .
2026-10-01 12:30:57 +02:00
- A **community** follows [FEP-1b12 ](https://codeberg.org/fediverse/fep/src/branch/main/fep/1b12/fep-1b12.md ). Posts
addressed to it (in `to` , `cc` or `audience` ) are accepted according to its posting policy (followers, anyone, or
moderators only) and the group `Announce` s the whole activity, with `audience` set, to its followers. A new post is
also announced as an object so Mastodon shows it. Updates and deletes of community content are announced too. A
top-level post is a `Page` with a `name` . Members are counted at `/flock` ; moderators are listed at `/wardens` , which
`attributedTo` does not yet point to. A mention of a community posts into it.
- A **circle** is private. Its actor is not discoverable and every follow is a request. Its posts are addressed to the
circle and its members collection, delivered to each member's own inbox and never announced. They are served only
to a signed request from a member, or from the instance actor of a member's server; anyone else gets 404. A
Mastodon member's replies reach only the people they mention.
- Announces from **remote** groups (Lemmy communities) are followed through to the activity: the object is fetched
from its own origin, never taken from the announce.
2026-10-01 11:23:03 +02:00
## Activities
Received:
| Activity | Effect |
|---|---|
| `Follow` | follows an avatar or community; `Accept` is sent unless the community approves members by hand |
2026-10-01 11:40:02 +02:00
| `Accept{Follow}` , `Reject{Follow}` | completes or ends a follow an avatar requested |
| `Undo{Follow, Like, Announce}` | reverses it |
| `Create{Note, Article, Page, Question, …}` | stored when a local avatar follows the author, is addressed or mentioned, when it replies to a local post, or when it is addressed to a community the author follows; a public parent is fetched to complete the thread |
2026-10-01 11:23:03 +02:00
| `Update{Note}` | replaces the content; the previous version is kept |
| `Update{Person}` | refetches the actor |
2026-10-01 11:40:02 +02:00
| `Like` | counted and notified, on posts the liker could see |
| `Announce` | counted and notified for local posts; shown to followers of the announcer, with the original refetched from its origin |
2026-10-01 11:23:03 +02:00
| `Delete` | deletes the object, or the actor and its follows |
2026-10-01 12:22:08 +02:00
| `Flag` | becomes a report for this server's moderators |
2026-10-01 11:23:03 +02:00
2026-10-01 11:40:02 +02:00
Sent: `Follow` , `Undo{Follow}` , `Create{Note}` , `Update{Note}` , `Update{Person}` , `Delete{Tombstone}` , `Accept{Follow}` ,
2026-10-01 12:22:08 +02:00
`Reject{Follow}` , `Like` , `Announce` and their `Undo` , `Flag` . A deleted post answers 410 with a `Tombstone` .
- **Attachments** are `Document` s with `mediaType` , `name` (alt text), `blurhash` , `focalPoint` , `width` and `height` .
Uploaded files have all metadata removed.
- **Pinned posts** are the actor's `featured` collection (`/trophies` ); `featuredTags` is `/tattoos` .
- **Blocks are never sent.** A blocked account is sent `Reject{Follow}` if it followed, and is unfollowed.
- **Reports** are sent as `Flag` by the instance actor, never by the reporting account.
2026-10-01 11:23:03 +02:00
A `Create` 's `Note` carries Mastodon's `content` , `contentMap` , `summary` and `sensitive` , plus `Mention` and `Hashtag`
tags. A post's title becomes `name` and is also the first, bold line of `content` , because Mastodon does not show
`name` . A content warning without its own text uses the title, or "Content warning".
Visibility is expressed in `to` /`cc` the way Mastodon does it: public, unlisted, followers-only and direct. Inbound
followers-only posts are recognised by the author's own `followers` collection.
2026-10-01 12:30:57 +02:00
## Local-only posts
Posts with a location (shown to nearby users of this server) never leave the server, in any form.
2026-10-01 11:23:03 +02:00
## Security rules a peer will notice
- **Signatures.** Inbox POSTs must be signed over `(request-target)` , `host` , `digest` and `date` (or `(created)` ). The
date may be at most one hour old and fifteen minutes ahead. A bad signature gets 401, malformed input 400, an accepted
activity 202, too many requests 429. Activities are processed after the 202.
- **Origins.** An actor document is accepted only from the address it names as its `id` . A key only if its actor lists
it with `owner` set to the actor and on the actor's origin. An activity's `id` , and any object it creates, updates or
deletes, must be on its actor's origin. An embedded object from another origin is fetched from that origin.
- **Fetching.** All fetches are signed by the instance actor. They go only to public addresses, follow at most three
redirects and read at most 1 MB.
- **HTML.** Received HTML is sanitised to Mastodon's allowlist.
- **Delivery.** Failed deliveries are retried with Mastodon's backoff (16 attempts). A host that keeps failing is paused,
starting at an hour and growing to a week.
## Known limitations
2026-10-01 12:22:08 +02:00
- Polls and custom emoji are not implemented.
- Remote media is fetched through this server's proxy when a local client displays it.
2026-10-01 11:23:03 +02:00
- Collections expose counts, not members.
- Only `rsa-sha256` -style keys are verified. RFC 9421 signatures are planned.