docs(journal): federation privacy manifest + runbook + honest home blurb (§10)
10.1 Privacy manifest (legal/privacy): - German legal half: Föderation entry under Empfänger/Drittanbieter — what a public profile exposes, what accepted followers' servers receive, the loss-of-control over delivered copies, Art. 6(1)(a) basis, private profiles don't federate. - English manifest half: dedicated Federation (ActivityPub) section — actor object contents, push delivery + retraction semantics (incl. the tombstone caveat), encrypted-at-rest signing keys, remote actor cache, ingested remote content with the followers-only viewer gate, inbox logging/rate limits. PRIVACY_LAST_UPDATED bumped. 10.2 docs/deployment.md federation runbook: enabling per environment, key rotation posture, abuse monitoring, and the troubleshooting checklist distilled from the 2026-06-06/07 soak (IP families first, no outbox backfill, tombstones, the 10s delivery timeout, actor re-fetch). 10.3 Home marketing blurb (en+de) aligned to what actually ships: Mastodon can follow you + trails-to-trails outbound — no more 'follow friends anywhere' overstatement. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
965df640ad
commit
f4d2cf027c
6 changed files with 129 additions and 6 deletions
|
|
@ -16,4 +16,4 @@ export const TERMS_VERSION = "2026-04-19";
|
||||||
* require re-acceptance (the policy is informational, not contract), so this
|
* require re-acceptance (the policy is informational, not contract), so this
|
||||||
* is display-only — not persisted.
|
* is display-only — not persisted.
|
||||||
*/
|
*/
|
||||||
export const PRIVACY_LAST_UPDATED = "2026-04-20";
|
export const PRIVACY_LAST_UPDATED = "2026-06-07";
|
||||||
|
|
|
||||||
|
|
@ -260,6 +260,22 @@ export default function PrivacyPage() {
|
||||||
, der ohne Query-Logs arbeitet. Eine selbst gehostete Instanz ist
|
, der ohne Query-Logs arbeitet. Eine selbst gehostete Instanz ist
|
||||||
geplant.
|
geplant.
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Föderation (ActivityPub)</strong> – Wenn Ihr Profil auf{" "}
|
||||||
|
<code>public</code> steht, ist Ihr Konto über das
|
||||||
|
ActivityPub-Protokoll föderiert: Andere Server (z. B.
|
||||||
|
Mastodon-Instanzen oder andere trails.cool-Instanzen) können Ihr
|
||||||
|
öffentliches Profil (Anzeigename, Nutzername, Bio, öffentlicher
|
||||||
|
Signaturschlüssel) abrufen und Ihnen folgen. An die Server
|
||||||
|
angenommener Follower übermitteln wir Ihre als{" "}
|
||||||
|
<code>public</code> markierten Aktivitäten (Titel, Statistiken,
|
||||||
|
Link). Diese Server liegen außerhalb unserer Kontrolle; was dort
|
||||||
|
mit zugestellten Inhalten geschieht (Speicherung, Anzeige,
|
||||||
|
Weiterverbreitung), richtet sich nach deren Richtlinien.
|
||||||
|
Rechtsgrundlage ist Ihre Einwilligung durch das aktive
|
||||||
|
Veröffentlichen (Art. 6 Abs. 1 lit. a DSGVO). Private Profile
|
||||||
|
föderieren nicht.
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong>BRouter</strong> – Routenberechnung läuft auf einer von uns
|
<strong>BRouter</strong> – Routenberechnung läuft auf einer von uns
|
||||||
selbst gehosteten Instanz. Keine Weitergabe an Dritte.
|
selbst gehosteten Instanz. Keine Weitergabe an Dritte.
|
||||||
|
|
@ -452,6 +468,53 @@ export default function PrivacyPage() {
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section className="mt-8">
|
||||||
|
<h3 className="text-xl font-semibold text-gray-900">
|
||||||
|
Federation (ActivityPub)
|
||||||
|
</h3>
|
||||||
|
<p className="mt-2 text-gray-700">
|
||||||
|
Applies only when your profile visibility is <code>public</code>.
|
||||||
|
Private profiles do not federate at all — no actor object, no
|
||||||
|
WebFinger, no inbox.
|
||||||
|
</p>
|
||||||
|
<ul className="mt-3 list-disc pl-6 text-gray-700 space-y-1">
|
||||||
|
<li>
|
||||||
|
Your actor object (fetchable by any fediverse server) exposes:
|
||||||
|
username, display name, bio, profile link, and your public
|
||||||
|
signing key. Never your email, never private content.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Activities you mark <code>public</code> are pushed to the
|
||||||
|
servers of your accepted remote followers and listed in your
|
||||||
|
public outbox. Once delivered, copies live on those servers
|
||||||
|
under their policies — un-publishing sends a retraction, but
|
||||||
|
remote deletion cannot be guaranteed (and remote servers that
|
||||||
|
processed a retraction will not re-show a later re-publish).
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Signing keys: one RSA keypair per user. The private key is
|
||||||
|
stored encrypted at rest and never leaves this server.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Remote actor cache: for accounts that interact with this
|
||||||
|
instance we store their public profile basics (handle, display
|
||||||
|
name, inbox/outbox URLs, public key) to render follower lists
|
||||||
|
and feeds without re-fetching.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Remote content: public (or followers-only) activities from
|
||||||
|
trails users you follow on other instances are cached here for
|
||||||
|
your feed — followers-only items are shown only to the
|
||||||
|
follower whose follow brought them in.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Inbox traffic (signed requests from other servers) appears in
|
||||||
|
the standard server logs (14-day retention, see section 4) and
|
||||||
|
is rate-limited per source instance.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section className="mt-8">
|
<section className="mt-8">
|
||||||
<h3 className="text-xl font-semibold text-gray-900">Sentry</h3>
|
<h3 className="text-xl font-semibold text-gray-900">Sentry</h3>
|
||||||
<ul className="mt-3 list-disc pl-6 text-gray-700 space-y-1">
|
<ul className="mt-3 list-disc pl-6 text-gray-700 space-y-1">
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,66 @@ curl -sf https://trails.cool/api/health && curl -sf https://staging.trails.cool/
|
||||||
Plan it as a short maintenance window (~2–3 min downtime); don't ship
|
Plan it as a short maintenance window (~2–3 min downtime); don't ship
|
||||||
network-option changes expecting the workflows to apply them.
|
network-option changes expecting the workflows to apply them.
|
||||||
|
|
||||||
|
## Federation runbook
|
||||||
|
|
||||||
|
Federation (ActivityPub via Fedify) is gated by `FEDERATION_ENABLED=true`
|
||||||
|
per environment. Currently: staging **on**, production **off**. The full
|
||||||
|
change history and design live in `openspec/changes/social-federation/`.
|
||||||
|
|
||||||
|
### Enabling on an environment
|
||||||
|
|
||||||
|
1. Ensure `FEDERATION_KEY_ENCRYPTION_KEY` is in `secrets.app.env`
|
||||||
|
(SOPS) — keypair generation fails closed in production without it.
|
||||||
|
2. Set `FEDERATION_ENABLED=true` in the environment's compose env (see
|
||||||
|
`cd-staging.yml` for the staging pattern).
|
||||||
|
3. First boot with the flag on enqueues `backfill-user-keypairs`
|
||||||
|
(generates RSA keys for existing users — idempotent) and registers
|
||||||
|
the federation jobs (`deliver-activity`, `poll-remote-actor`,
|
||||||
|
`poll-remote-outboxes`, `federation-kv-sweep`).
|
||||||
|
4. Smoke: `curl -s "https://<domain>/.well-known/webfinger?resource=acct:<user>@<domain>"`
|
||||||
|
(200 for a public user, 404 otherwise) and `/nodeinfo/2.1`.
|
||||||
|
|
||||||
|
`FEDERATION_LOG_LEVEL=debug` turns on Fedify's per-request signature
|
||||||
|
diagnostics (staging runs debug during soaks; dial back to `info`).
|
||||||
|
|
||||||
|
### Key rotation
|
||||||
|
|
||||||
|
Rotating `FEDERATION_KEY_ENCRYPTION_KEY` requires re-encrypting every
|
||||||
|
`users.private_key_encrypted` (decrypt with old, encrypt with new) —
|
||||||
|
there is no script yet; write one against
|
||||||
|
`apps/journal/app/lib/federation-keys.server.ts` primitives when first
|
||||||
|
needed. Rotating a single user's keypair: NULL their key columns and
|
||||||
|
let the backfill regenerate (remotes pick the new key up from the actor
|
||||||
|
document; deliveries signed with the old key fail until then).
|
||||||
|
|
||||||
|
### Abuse monitoring
|
||||||
|
|
||||||
|
- Inbox is rate-limited 60 req/5 min per source instance
|
||||||
|
(`federationSourceHost`); 429s show in journal logs.
|
||||||
|
- Outbound is paced (1 req/s delivery, 1 req/5 s polling per host).
|
||||||
|
- Watch `docker logs <journal>` for `fedify·federation` lines; Loki
|
||||||
|
picks them up. No per-instance blocklist yet — see
|
||||||
|
`docs/ideas/instance-administration.md` (manual emergency lever:
|
||||||
|
block the source IP/host in Caddy).
|
||||||
|
|
||||||
|
### Troubleshooting deliveries
|
||||||
|
|
||||||
|
Hard-won checklist from the 2026-06-06/07 soak (details in
|
||||||
|
`openspec/changes/social-federation/design.md`):
|
||||||
|
|
||||||
|
1. **Test both IP families from inside the journal container** before
|
||||||
|
suspecting signatures (`family: 6` vs `4` — dangling A records and
|
||||||
|
v6-only instances are common; our containers have IPv6 since
|
||||||
|
#466/#468).
|
||||||
|
2. Remote shows the post count but no posts → remotes never backfill
|
||||||
|
outbox history; only pushed or individually-fetched objects appear.
|
||||||
|
3. Delivered but invisible, no errors anywhere → check the remote's
|
||||||
|
`tombstones` table; a previously-retracted URI is refused forever.
|
||||||
|
4. Mastodon gives deliveries a 10s read timeout — anything synchronous
|
||||||
|
and slow in the inbox path breaks federation silently.
|
||||||
|
5. Actor changes (profile fields, keys) need the remote to re-fetch:
|
||||||
|
`tootctl accounts refresh user@domain` on a Mastodon you control.
|
||||||
|
|
||||||
## cd-brouter manual trigger
|
## cd-brouter manual trigger
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -76,9 +76,9 @@
|
||||||
|
|
||||||
## 10. Privacy + docs
|
## 10. Privacy + docs
|
||||||
|
|
||||||
- [ ] 10.1 Update privacy manifest: federation traffic, remote actor cache, what data is exposed in the actor object (display name, avatar, public key)
|
- [x] 10.1 Update privacy manifest: federation traffic, remote actor cache, what data is exposed in the actor object (display name, avatar, public key)
|
||||||
- [ ] 10.2 `docs/deployment.md`: federation runbook — feature flag, key rotation procedure, abuse monitoring, troubleshooting
|
- [x] 10.2 `docs/deployment.md`: federation runbook — feature flag, key rotation procedure, abuse monitoring, troubleshooting
|
||||||
- [ ] 10.3 Soften the Journal home "Federated by design" blurb on flagship now (or align it to "ActivityPub federation: inbound + trails-to-trails outbound" once this lands)
|
- [x] 10.3 Soften the Journal home "Federated by design" blurb on flagship now (or align it to "ActivityPub federation: inbound + trails-to-trails outbound" once this lands)
|
||||||
|
|
||||||
## 11. Testing
|
## 11. Testing
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@ export default {
|
||||||
},
|
},
|
||||||
federation: {
|
federation: {
|
||||||
title: "Föderiert by design",
|
title: "Föderiert by design",
|
||||||
body: "Deine Journal-Instanz spricht via ActivityPub mit anderen trails.cool-Servern. Folge Freundinnen und Freunden überall.",
|
body: "Teil des Fediverse via ActivityPub: Mastodon-Nutzer:innen können dir folgen und deine öffentlichen Aktivitäten sehen, und du folgst trails-Nutzer:innen auf jeder Instanz.",
|
||||||
},
|
},
|
||||||
ownership: {
|
ownership: {
|
||||||
title: "Deine Daten, immer",
|
title: "Deine Daten, immer",
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@ export default {
|
||||||
},
|
},
|
||||||
federation: {
|
federation: {
|
||||||
title: "Federated by design",
|
title: "Federated by design",
|
||||||
body: "Your Journal instance talks to other trails.cool servers via ActivityPub. Follow friends anywhere.",
|
body: "Part of the fediverse via ActivityPub: Mastodon users can follow you and see your public activities, and you can follow trails users on any instance.",
|
||||||
},
|
},
|
||||||
ownership: {
|
ownership: {
|
||||||
title: "Your data, always",
|
title: "Your data, always",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue