The Fedify 2.0 migration added assertionMethods = keyPairs.map(k => k.multikey),
which places the RSA Multikey (id: #main-key) into assertionMethod alongside the
Ed25519 Multikey (id: #key-2).
This creates a keyId collision: the RSA CryptographicKey in publicKey and the RSA
Multikey in assertionMethod both use #main-key. Servers that traverse JSON-LD
properties alphabetically (assertionMethod before publicKey) find the Multikey
first — which lacks publicKeyPem — and return "public key not found".
Fix: filter assertionMethods to only Ed25519 keys (Object Integrity Proofs).
RSA keys already have their correct representation in publicKey (HTTP Signatures).
This matches Mastodon's behavior and is semantically correct per the two key systems.
The Fedify 2.0 migration added assertionMethods = keyPairs.map(k => k.multikey),
which places the RSA Multikey (id: #main-key) into assertionMethod alongside the
Ed25519 Multikey (id: #key-2).
This creates a keyId collision: the RSA CryptographicKey in publicKey and the RSA
Multikey in assertionMethod both use #main-key. Servers that traverse JSON-LD
properties alphabetically (assertionMethod before publicKey) find the Multikey
first — which lacks publicKeyPem — and return "public key not found".
Fix: filter assertionMethods to only Ed25519 keys (Object Integrity Proofs).
RSA keys already have their correct representation in publicKey (HTTP Signatures).
This matches Mastodon's behavior and is semantically correct per the two key systems.
Five improvements to strict ActivityPub protocol compliance and
real-world Mastodon interoperability:
1. allowPrivateAddress: true in createFederation (federation-setup.js)
Fixes Fedify's SSRF guard rejecting own-site URLs that resolve to
private IPs on the local LAN (e.g. home-network deployments where
the blog hostname maps to 10.x.x.x internally).
2. Canonical id on Like activities (jf2-to-as2.js)
Per AP §6.2.1, activities SHOULD have an id URI so remote servers
can dereference them. Derives mount path from actor URL and constructs
{publicationUrl}{mount}/activities/like/{post-path}.
3. Like activity object dispatcher (federation-setup.js)
Per AP §3.1, objects with an id MUST be dereferenceable at that URI.
Registers federation.setObjectDispatcher(Like, .../activities/like/{+id})
so fetching the canonical Like URL returns the activity as AP JSON.
Adds Like to @fedify/fedify/vocab imports.
4. Repost commentary in AP output (jf2-to-as2.js)
- jf2ToAS2Activity: only sends Announce for pure reposts (no content);
reposts with commentary fall through to Create(Note) with content
formatted as "{commentary}<br><br>🔁 <url>" so followers see the text.
- jf2ToActivityStreams: prepends commentary to the repost Note content
for correct display in content-negotiation / search responses.
5. GET /api/ap-url public endpoint (index.js)
Resolves a blog post URL → its Fedify-served AP object URL for use by
"Also on Fediverse" widgets. Prevents nginx from intercepting
authorize_interaction requests that need AP JSON.
Special case: AP-likes return { apUrl: likeOf } so authorize_interaction
opens the original remote post rather than the blog's like post.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When the `like-of` URL serves ActivityPub content (detected via content
negotiation with Accept: application/activity+json), deliver a proper
`Like { actor, object, to: Public }` activity to followers.
For likes of regular (non-AP) URLs, fall through to the existing
bookmark-style `Create(Note)` behaviour (🔖 content with #bookmark tag).
- Add `isApUrl()` async helper (3 s timeout, fails silently)
- Make `jf2ToAS2Activity` async; add Like detection before repost block
- Update all four call sites in federation-setup.js and index.js
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Deleted posts (with properties.deleted timestamp) were still served
via the outbox dispatcher and content negotiation catch-all. Now:
- Outbox find() and countDocuments() filter out deleted posts
- Object dispatcher returns null for deleted posts (Fedify 404)
- Content negotiation falls through to Express for deleted posts
Confab-Link: http://localhost:8080/sessions/af5f8b45-6b8d-442d-8f25-78c326190709
federation-bridge.js:
- Buffer application/activity+json and ld+json bodies that Express
doesn't parse (inbox POSTs from Mastodon, PeerTube, etc.)
- Store original bytes in req._rawBody and pass them verbatim to Fedify
so HTTP Signature Digest verification passes; JSON.stringify reorders
keys which caused every Mastodon Like/Announce/Create to be silently
rejected
- Short-circuit PeerTube View (WatchAction) activities with 200 before
Fedify's JSON-LD parser throws on Schema.org extensions
federation-setup.js:
- Accept signatures up to 12 hours old (Mastodon retries with the
original signature hours after a failed delivery)
- Look up AP object URLs with $in [url, url+"/"] to tolerate trailing
slash differences between stored posts and AP object URLs
inbox-listeners.js:
- Register a no-op .on(View) handler so Fedify doesn't log noisy
"Unsupported activity type" errors for PeerTube watch events
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
federation-setup.js:
- Suppress fedify docloader logs below fatal level to reduce noise from
deleted remote actors (404/410)
- Add visibility:unlisted guard to outbox dispatcher, counter, and
resolvePost object dispatcher
controllers/migrate.js:
- Allow clearing alsoKnownAs by detecting submitted empty aliasUrl field
via hasOwnProperty check (previously only set when non-empty)
index.js:
- Add resolveAuthor import
- Skip federation for unlisted posts in syndicate()
- Add likePost(postUrl, collections) — sends AP Like activity to author
- Add boostPost(postUrl, collections) — sends AP Announce to followers
and directly to the post author's inbox
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add OStatus subscribe template to WebFinger responses so remote servers
(WordPress AP, Misskey, etc.) can discover and redirect users to complete
follow interactions. Unauthenticated users are sent to login first, then
redirected to the existing reader profile page with follow/unfollow UI.
Mastodon 4.5's extract_url_from_html requires element['href'] ==
element.text for a field to be verifiable. Using @handle@domain as
display text caused value_for_verification to return nil, making
the field permanently unverifiable.
Change to using the full actor URL as display text so the href and
text content match.
Always include a "Fediverse" PropertyValue attachment in the actor
with the canonical @handle@domain address. This ensures 2+ attachments
when combined with user-defined fields, preventing Fedify's JSON-LD
compaction from collapsing single-element arrays to plain objects
(which Mastodon's update_account_fields silently rejects).
Also fixes the root cause of profile fields not appearing on Mastodon
for existing followers: Update(Person) activities were being sent with
compacted attachment objects that Mastodon ignored.
- Upgrade @fedify/fedify, @fedify/redis to ^2.0.0
- Add @fedify/debugger ^2.0.0 for live federation traffic dashboard
- Move all vocab type imports to @fedify/fedify/vocab (13 files)
- Move crypto imports (exportJwk, importJwk, generateCryptoKeyPair) to @fedify/fedify/sig
- Replace removed importSpki() with local Web Crypto API helper
- Add KvStore.list() async generator required by Fedify 2.0
- Add setOutboxPermanentFailureHandler for delivery failure logging
- Add debugDashboard/debugPassword config options
- Skip manual LogTape configure when debugger auto-configures it
- Fix Express-Fedify bridge to reconstruct body from req.body when
Express body parser has already consumed the stream (fixes debug
dashboard login TypeError)
- Add response.bodyUsed safety check in sendFedifyResponse
- Remove @fedify/express dependency (custom bridge handles sub-path mounting)
ctx.getActor() only exists on RequestContext (inside HTTP handlers), not
on the base Context returned by createContext(). Extracted actor-building
logic into shared buildPersonActor() helper used by both the dispatcher
and broadcastActorUpdate(). Profile link attachments now propagate to
remote instances via Update(Person) activity.
- Add broadcastActorUpdate() method that sends Update(Person) to all
followers so remote servers re-fetch the actor object
- Profile, featured pin/unpin, and featured tags add/remove controllers
now trigger the broadcast after changes
- Wrap URL attachment values in <a rel="me"> HTML for Mastodon rel=me
verification; plain text values pass through unchanged
- Bump version to 1.1.1
- Actor type radio buttons (Person/Service/Organization) in Profile page,
stored in ap_profile and read by federation-setup actor dispatcher
- Profile links (attachments) section with add/remove for rel="me"
verification links, rendered as PropertyValue on the ActivityPub actor
- New locale strings for all new UI elements
Implement all missing Fedify features for full ActivityPub compliance:
- Liked, Featured, Featured Tags collection dispatchers with admin UIs
- Object dispatcher for Note/Article dereferencing at AP URIs
- Instance actor (Application type) for domain-level federation
- Handle aliases (.mapAlias) for profile URL and /@handle resolution
- Configurable actor type (Person/Service/Organization/Group)
- Dynamic NodeInfo version from @indiekit/indiekit package.json
- Context data propagation (handle + publication URL)
- ParallelMessageQueue wrapping RedisMessageQueue (5 workers)
- Collection sync (FEP-8fcf) and ordering keys on sendActivity
- Permanent failure handler stub (deferred to Fedify 2.0)
- Profile attachments (PropertyValue) and alsoKnownAs support
- Strip invalid "type":"as:Endpoints" from actor JSON (Fedify #576)
- Fix .mapAlias() return type ({identifier} not bare string)
- Remove .authorize() predicate (causes 401 loops without auth doc loader)
- Narrow content negotiation router to /nodeinfo/ only
22/22 compliance tests pass (Grade A+). Version 1.0.26.
- Persist Ed25519 key pair to ap_keys collection via exportJwk/importJwk
instead of regenerating on every request (fixes OIP verification failures)
- Use assertionMethods (plural array) per Fedify spec
- Add @fedify/redis + ioredis for persistent message queue that survives
process restarts (falls back to InProcessMessageQueue when no Redis)
- Add Reject inbox listener to mark rejected Follow requests
- Add performance indexes on ap_followers, ap_following, ap_activities
- Wire storeRawActivities flag through to activity logging
- Bump version to 1.0.21
1. mapHandle() — tells Fedify how to resolve WebFinger usernames to
actor identifiers, suppressing the "No actor handle mapper is set"
warning on every WebFinger lookup.
2. Ed25519 key pair — generated alongside the legacy RSA pair so Fedify
can create Object Integrity Proofs on outbound activities. RSA is
kept for HTTP Signatures backward compatibility.
Add AsyncLocalStorage to LogTape configure() to fix the repeated
"Context-local storage is not configured" warning that appeared
before every Fedify log entry. Also remove unused getLogger import.
Two critical fixes for ActivityPub federation:
1. Call federation.startQueue() — without this, ctx.sendActivity() enqueues
delivery tasks but the InProcessMessageQueue never processes them, so
activities are never actually POSTed to follower inboxes.
2. Add setSharedKeyDispatcher on the shared inbox — enables Fedify to make
signed/authenticated GET requests when verifying incoming HTTP Signatures.
Servers with authorized fetch (e.g. hachyderm.io) return 401 on unsigned
requests, which prevented Fedify from fetching sender public keys and
caused all incoming activities to be rejected.
Configure LogTape to route Fedify's internal logs (federation, vocab,
delivery) to console at info level. This makes activity delivery
attempts, HTTP signature issues, and queue processing visible in
container logs.