Add signed ActivityPub server federation flows - #40
Conversation
Move the hardcoded single-user demo into examples/single-user-server and leave feder-runtime-server as reusable Axum/Tokio integration. Expose the router/config building blocks, remove default local demo config from the library, and test the reusable routes directly. Assisted-by: Codex:gpt-5.5
Covers invalid inbox targets, unsupported content types, malformed JSON, unsupported activities, and oversized request bodies. Assisted-by: Codex:gpt-5.5
Reject unsigned inbox requests before core handling when signed inbox auth is required, while keeping explicit insecure dev mode for tests and the single-user example. Assisted-by: Codex:gpt-5.5
…ding the runtime inbox auth policy. Assisted-by: Codex:gpt-5.5
Assisted-by: Codex:gpt-5
Assisted-by: Codex:gpt-5.5
Use the RuntimeStore persist_actions method from the inbox handler and initialize the runtime AppState with an in-memory SQLite store for now. Verified with: cargo +1.95.0 check -p feder-runtime-server Assisted-by: Codex:gpt-5.5
Enable rusqlite's bundled SQLite feature so feder-runtime-server tests do not depend on a system libsqlite3 installation. Verified with: cargo +1.95.0 test -p feder-runtime-server Assisted-by: Codex:gpt-5.5
Pin the repository to Rust 1.95.0 so rusqlite 0.40.1 builds consistently. Add focused SQLite store tests for schema initialization, follower persistence, and duplicate follower idempotency. Verified with: cargo test -p feder-runtime-server Assisted-by: Codex:gpt-5.5
Load follower rows from the SQLite store into StoredState and parse stored actor IDs back into ActivityPub IRIs. Add coverage for follower restoration. Verified with: cargo test -p feder-runtime-server Assisted-by: Codex:gpt-5.5
Extend the followers table with inbox fields and persist an embedded follower actor's inbox URL with the follower relationship. This keeps recipient delivery data attached to follower records, matching Fedify's follower-recipient model. Verified with: cargo test -p feder-runtime-server Assisted-by: Codex:gpt-5.5
Represent stored followers as recipient records with optional inbox fields and restore those fields from SQLite. This preserves follower delivery data loaded from embedded actor records. Verified with: cargo test -p feder-runtime-server Assisted-by: Codex:gpt-5.5
Replace the restore-style load_state API with load_followers so runtime storage can expose durable follower data without implying full core state restoration. Assisted-by: Codex:gpt-5.5
Replace the restore-style follower read with actor-scoped list_followers, and add list_follower_recipients for delivery-ready follower inbox lookup. Verified with: cargo test -p feder-runtime-server Assisted-by: Codex:gpt-5.5
Add StorageConfig to choose in-memory or file-backed SQLite storage, and make router construction return storage initialization errors. Cover file-backed storage with a reopen test for persisted follower state. Assisted-by: Codex:gpt-5.5
Add minimal actor endpoints vocabulary support for endpoints.sharedInbox and store embedded follower shared inbox URLs in SQLite follower records. Assisted-by: Codex:gpt-5.5
Persist delivery target actions into follower inbox records, add an index for scoped follower lookups, and avoid holding the core lock during storage writes. Assisted-by: Codex:gpt-5.5
Add the security JSON-LD context and model embedded CryptographicKey values on ActivityPub actors. Cover the publicKey representation with serialization tests. Assisted-by: Codex:gpt-5.6-sol
…r Core. Accept runtime-provided entropy, validate persisted pairs, and zeroize private PEM material. Use fixed fixtures for behavioral tests and exercise all workspace features and targets in CI. Assisted-by: Codex:gpt-5.6-sol
Enable HTTP signature keys in the server runtime and store validated per-actor key pairs in the keys table. Reject accidental replacement and test schema creation, round trips, malformed keys, missing actors, and persistence across reopen. Assisted-by: Codex:gpt-5.6-sol
Move HTTP endpoint and startup tests under the tests directory, with shared fixtures kept private to the integration test crate. Assisted-by: Codex:gpt-5.6-sol
Generate and persist a 4096-bit RSA key pair with operating system entropy when an actor has no stored key, and reuse the persisted pair on restart. Assisted-by: Codex:gpt-5.6-sol
Attach the persisted RSA public key to the local actor as an embedded CryptographicKey using the #main-key identifier and security context. Assisted-by: Codex:gpt-5.6-sol
Deliver SendActivity actions synchronously to recipient inboxes as ActivityPub JSON, report remote failures, and continue attempting later recipients after individual delivery failures. Assisted-by: Codex:gpt-5.6-sol
Create draft-Cavage RSA signatures in the portable core and apply them to outgoing ActivityPub requests using the persisted actor key and matching #main-key identifier. Assisted-by: Codex:gpt-5.6-sol
Block private and special-use inbox destinations using validated DNS resolution, with an explicit option for local development. Configure connection and total request timeouts to prevent stalled deliveries. Assisted-by: Codex:gpt-5.6-sol
…vered inbox, and deliver the signed Accept activity. Reuse outbound network protections and enforce response type, size, and actor ID validation. Add an end-to-end test covering Mastodon-style actor resolution and Accept delivery. Assisted-by: Codex:gpt-5.6-sol
Validate Cavage RSA-SHA256 signatures, signed headers, request dates, body digests, actor ownership, and public-key identity before processing inbox activities. Add tests for valid, tampered, unsigned, and mismatched-actor requests. Assisted-by: Codex:gpt-5.6-sol
Fetch signature key IDs directly and support standalone key documents as well as keys embedded in actor documents. Verify that the activity actor owns and advertises the resolved key before processing the request. Add coverage for signed Follows using an independent key URL. Assisted-by: Codex:gpt-5.6-sol
Default omitted public key types to CryptographicKey for compatibility with Mastodon actor documents. Cover the representation in vocabulary and signed Follow integration tests. Assisted-by: Codex:gpt-5.6-sol
Add Undo vocabulary and core handling for follower removal. Validate that the Undo actor owns the embedded Follow, remove matching in-memory and SQLite follower state, and cover signed unfollow and forged Undo requests. Assisted-by: Codex:gpt-5.6-sol
Add the OrderedCollection vocabulary type and followers property for actors. Advertise the local actor's followers collection URI and preserve the property when resolving remote actors. Assisted-by: Codex:gpt-5.6-sol
Add a storage-backed followers handler and expose it through the runtime router. Return an ActivityPub OrderedCollection with current follower IDs and counts, and cover collection updates, response headers, and unknown users. Assisted-by: Codex:gpt-5.6-sol
Require an explicit ActivityPub-compatible Accept header for actor and followers collection requests. Return 406 for HTML, wildcard-only, missing, or unsupported Accept headers, and mark successful responses with Vary: Accept. Add coverage for supported media types, quality preferences, HTML requests, and requests without an ActivityPub Accept header. Assisted-by: Codex:gpt-5.6-sol
Add Vary: Accept to actor and followers responses that reject requests without an ActivityPub-compatible Accept header, preventing caches from reusing those responses across representations. Assisted-by: Codex:gpt-5.6-sol
Ignore unsupported Accept media types when choosing between HTML and ActivityPub. Compare the best quality in each supported representation group and use header order to resolve equal preferences. Assisted-by: Codex:gpt-5.6-sol
Add to, cc, url, and mediaType fields to the Note vocabulary model. Preserve ActivityStreams scalar-or-array recipient serialization and omit empty or absent values. Assisted-by: Codex:gpt-5.6-sol
Store Note objects emitted through StoreObject actions in SQLite using their IRI, object type, and serialized payload. Add typed object loading, idempotent replacement, and persistence across database reopen. Assisted-by: Codex:gpt-5.6-sol
Add the local post object route with ActivityPub content negotiation, SQLite-backed lookup, and coverage for persistence and error responses. Assisted-by: Codex:gpt-5.6-sol
Add a runtime operation for local Note creation that applies storage actions before synchronously delivering Create activities. Reuse the same action pipeline for inbox processing and cover successful and failed delivery. Assisted-by: Codex:gpt-5.6-sol
Remove the in-memory delivery target cache and resolve current follower inboxes from SQLite when delivering Create activities. Keep delivery synchronous, deduplicate shared inboxes, and refresh inbox data through repeated Follow persistence. Assisted-by: Codex:gpt-5.6-sol
Populate Note addressing and metadata, mirror to and cc onto Create, and use one SendActivity action with typed inbox or follower recipients. Resolve follower recipients into concrete inbox deliveries at runtime. Assisted-by: Codex:gpt-5.6-sol
Assisted-by: Codex:gpt-5.6-sol
Assisted-by: Codex:gpt-5.6-sol
Assisted-by: Codex:gpt-5.6-sol
Assisted-by: Codex:gpt-5.6-sol
📝 WalkthroughWalkthroughAdds authenticated ActivityPub federation support across vocabulary, core state handling, runtime routing, actor resolution, SQLite persistence, signed delivery, inbox verification, object/follower endpoints, integration tests, and a single-user example server. ChangesActivityPub vocabulary and core delivery
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested labels: Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant RemoteActor
participant Inbox
participant ActorResolver
participant FederCore
participant RuntimeStore
participant ActivitySender
participant RemoteInbox
RemoteActor->>Inbox: POST Follow or Undo(Follow)
Inbox->>ActorResolver: resolve actor and signing key
ActorResolver-->>Inbox: verified actor
Inbox->>FederCore: handle received input
FederCore->>RuntimeStore: persist follower action
FederCore-->>Inbox: Accept delivery action
Inbox->>ActivitySender: send resolved activity
ActivitySender->>RemoteInbox: signed POST Accept
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (8)
crates/feder-runtime-server/src/followers.rs (1)
39-56: 🚀 Performance & Scalability | 🔵 TrivialUnpaginated followers collection.
Every request loads and serializes all followers. Fine for the single-user PoC, but ActivityPub consumers expect an
OrderedCollectionwithfirst/nextpages once the follower count grows; worth tracking before this leaves proof-of-concept status.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/feder-runtime-server/src/followers.rs` around lines 39 - 56, Update the followers collection flow around OrderedCollection::new to support paginated ActivityPub responses instead of always loading and serializing every follower. Add first/next page metadata and limit store retrieval to the requested page, while preserving the existing collection ID and total follower count behavior.crates/feder-runtime-server/src/inbox.rs (1)
283-303: 🎯 Functional Correctness | 🔵 TrivialAccept bare signature parameter values while parsing the header.
parse_signature_headerrequires every parameter value to start with a quote, so signatures likealgorithm=rsa-sha256,headers=...,signature=...are still rejected even though the algorithm itself is validated later. Since the current code already checks algorithm and signed-headers case-insensitively and lowercasesalgorithm, accepting non-quoted values for the header parser would not weaken signature semantics.
[low_effort_and_reward]🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/feder-runtime-server/src/inbox.rs` around lines 283 - 303, Update parse_signature_header to accept unquoted parameter values in addition to quoted values, while preserving existing quoted parsing and validation behavior. Reuse the existing algorithm and signed-headers case-insensitive checks and algorithm lowercasing so signature semantics remain unchanged; ensure bare values terminate at the parameter delimiter.crates/feder-runtime-server/tests/cases/followers.rs (1)
28-30: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDuplicated
iri()test helper across three cases files. All three files define the identical private helperfn iri(value: &str) -> Iri { value.parse().expect("valid test IRI") }; each already imports fromcrate::common, so this is a good candidate for a single shared helper.
crates/feder-runtime-server/tests/cases/followers.rs#L28-L30: remove the localiri()and import a shared version fromcrate::common.crates/feder-runtime-server/tests/cases/object.rs#L29-L31: remove the localiri()and import the same shared helper.crates/feder-runtime-server/tests/cases/operation.rs#L23-L25: remove the localiri()and import the same shared helper.♻️ Suggested consolidation (add to tests/common/mod.rs)
pub fn iri(value: &str) -> feder_vocab::Iri { value.parse().expect("valid test IRI") }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/feder-runtime-server/tests/cases/followers.rs` around lines 28 - 30, Consolidate the duplicated iri test helper by adding one shared public iri function to tests/common/mod.rs, then remove the local iri definitions and import the shared helper in crates/feder-runtime-server/tests/cases/followers.rs (lines 28-30), crates/feder-runtime-server/tests/cases/object.rs (lines 29-31), and crates/feder-runtime-server/tests/cases/operation.rs (lines 23-25).crates/feder-runtime-server/tests/cases/send.rs (1)
165-177: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAssert the terminal delivery error variant.
result.is_err()permits an unrelated failure to satisfy this test. AssertUnsuccessfulStatusforfailed_inboxand HTTP 500 to preserve the status-mapping contract incrates/feder-runtime-server/src/send.rs.Proposed test assertion
- assert!(result.is_err()); + assert!(matches!( + result, + Err(SendError::UnsuccessfulStatus { inbox, status }) + if inbox == failed_inbox && status == StatusCode::INTERNAL_SERVER_ERROR + ));🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/feder-runtime-server/tests/cases/send.rs` around lines 165 - 177, Update attempts_later_sends_after_failure to assert that the returned error is the UnsuccessfulStatus variant for failed_inbox and contains HTTP status 500, rather than only checking result.is_err(). Preserve the existing actions and successful follow-up send coverage.crates/feder-runtime-server/src/storage/sqlite.rs (1)
49-75: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueConsider WAL and a busy timeout for the file-backed store.
Connection::opendefaults to rollback journal with no busy timeout, so any second process or future second connection gets immediateSQLITE_BUSY.PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000;ininitis a cheap hedge.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/feder-runtime-server/src/storage/sqlite.rs` around lines 49 - 75, Update the SQLite initialization flow in `init` to configure the file-backed connection with WAL journaling and a 5-second busy timeout before creating the tables. Apply both pragmas through the existing `self.conn` setup while preserving the current schema creation and error propagation.crates/feder-core/src/http_signatures.rs (2)
123-134: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueParsing the PKCS#8 private key on every signature is avoidable work.
Each outbound delivery re-parses a 4096-bit private key. Consider caching a lazily-built
SigningKey<Sha256>insideActorKeyPair(e.g.OnceLock) or exposing aSignerhandle so signing hot paths only pay the RSA operation.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/feder-core/src/http_signatures.rs` around lines 123 - 134, Update ActorKeyPair and sign_draft so the PKCS#8 private key is parsed once into a lazily initialized cached SigningKey<Sha256> (or equivalent signer handle), then reuse that cached signer for subsequent signatures while preserving InvalidPrivateKey error handling during initialization.
173-189: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueHeader values with multiple occurrences are not comma-joined.
Draft-Cavage requires repeated headers to be combined as a single comma-separated line. The base builder emits one line per tuple, so a caller passing a duplicated header name will produce a signature peers cannot reproduce. Worth documenting the caller contract at minimum.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/feder-core/src/http_signatures.rs` around lines 173 - 189, Update draft_cavage_signature_base to group entries with the same header name and combine their trimmed values into one comma-separated line, while preserving lowercase names and existing ordering semantics. Ensure repeated headers no longer produce multiple signature-base lines, and document the expected duplicate-header handling contract if the caller must provide pre-grouped values.crates/feder-runtime-server/src/operation.rs (1)
31-48: 🩺 Stability & Availability | 🔵 Trivial | 🏗️ Heavy liftOutbound delivery has no durable status or retry, so failures are both fatal to the request and partly invisible. Both sites stem from one root cause: delivery is performed inline with no record of per-recipient outcome.
crates/feder-runtime-server/src/operation.rs#L31-L48: stop propagatingsend_actionserrors out ofhandle_input; record delivery outcome instead so a single unreachable inbox does not fail an already-persisted operation.crates/feder-runtime-server/src/send.rs#L57-L69: return or log every per-recipient error rather than only the first, so partial fan-out failure is observable.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/feder-runtime-server/src/operation.rs` around lines 31 - 48, Update crates/feder-runtime-server/src/operation.rs lines 31-48 in handle_input so activity_sender.send_actions failures are recorded or handled without propagating them as a request error after actions are persisted; update crates/feder-runtime-server/src/send.rs lines 57-69 in resolve outbound delivery fan-out to return or log every per-recipient error instead of stopping at the first failure, preserving visibility of partial delivery failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/feder-core/src/lib.rs`:
- Around line 255-275: Update the delivery flow that expands Recipients after
note_recipients so it deduplicates recipients by resolved inbox URL before
enqueueing sends. Preserve note_recipients’ existing address-level
deduplication, but ensure an actor reached directly and through
Recipients::Followers produces only one delivery.
In `@crates/feder-runtime-server/src/inbox.rs`:
- Around line 110-118: In the request verification flow around
verify_request_date and verify_request_digest, validate the request’s signed
host header against the configured handle_host or bind host before accepting the
signature. Reject mismatches with the existing unauthorized response, while
preserving verification for requests addressed to this server.
In `@crates/feder-runtime-server/src/url.rs`:
- Around line 36-66: Add the deprecated IPv6 site-local CIDR "fec0::/10" to
NON_PUBLIC_NETWORK_CIDRS, preserving the existing blocklist structure and
entries.
In `@crates/feder-vocab/src/lib.rs`:
- Around line 228-243: Update CryptographicKeyType deserialization so present
unknown values, including strings such as "Key" and arrays, resolve to the
default CryptographicKey variant instead of failing; preserve the existing
default for missing type and the current enum behavior for recognized values.
- Around line 180-209: Update the Context deserialization used by ActorDocument
so JSON-LD context arrays containing object or other non-IRI entries are
accepted and preserved or safely ignored while IRI entries continue mapping to
Context::Iri or Context::Iris. Extend Context with an ignored term/object
variant or implement a custom deserializer, ensuring Mastodon-style contexts
deserialize without rejecting the actor.
---
Nitpick comments:
In `@crates/feder-core/src/http_signatures.rs`:
- Around line 123-134: Update ActorKeyPair and sign_draft so the PKCS#8 private
key is parsed once into a lazily initialized cached SigningKey<Sha256> (or
equivalent signer handle), then reuse that cached signer for subsequent
signatures while preserving InvalidPrivateKey error handling during
initialization.
- Around line 173-189: Update draft_cavage_signature_base to group entries with
the same header name and combine their trimmed values into one comma-separated
line, while preserving lowercase names and existing ordering semantics. Ensure
repeated headers no longer produce multiple signature-base lines, and document
the expected duplicate-header handling contract if the caller must provide
pre-grouped values.
In `@crates/feder-runtime-server/src/followers.rs`:
- Around line 39-56: Update the followers collection flow around
OrderedCollection::new to support paginated ActivityPub responses instead of
always loading and serializing every follower. Add first/next page metadata and
limit store retrieval to the requested page, while preserving the existing
collection ID and total follower count behavior.
In `@crates/feder-runtime-server/src/inbox.rs`:
- Around line 283-303: Update parse_signature_header to accept unquoted
parameter values in addition to quoted values, while preserving existing quoted
parsing and validation behavior. Reuse the existing algorithm and signed-headers
case-insensitive checks and algorithm lowercasing so signature semantics remain
unchanged; ensure bare values terminate at the parameter delimiter.
In `@crates/feder-runtime-server/src/operation.rs`:
- Around line 31-48: Update crates/feder-runtime-server/src/operation.rs lines
31-48 in handle_input so activity_sender.send_actions failures are recorded or
handled without propagating them as a request error after actions are persisted;
update crates/feder-runtime-server/src/send.rs lines 57-69 in resolve outbound
delivery fan-out to return or log every per-recipient error instead of stopping
at the first failure, preserving visibility of partial delivery failures.
In `@crates/feder-runtime-server/src/storage/sqlite.rs`:
- Around line 49-75: Update the SQLite initialization flow in `init` to
configure the file-backed connection with WAL journaling and a 5-second busy
timeout before creating the tables. Apply both pragmas through the existing
`self.conn` setup while preserving the current schema creation and error
propagation.
In `@crates/feder-runtime-server/tests/cases/followers.rs`:
- Around line 28-30: Consolidate the duplicated iri test helper by adding one
shared public iri function to tests/common/mod.rs, then remove the local iri
definitions and import the shared helper in
crates/feder-runtime-server/tests/cases/followers.rs (lines 28-30),
crates/feder-runtime-server/tests/cases/object.rs (lines 29-31), and
crates/feder-runtime-server/tests/cases/operation.rs (lines 23-25).
In `@crates/feder-runtime-server/tests/cases/send.rs`:
- Around line 165-177: Update attempts_later_sends_after_failure to assert that
the returned error is the UnsuccessfulStatus variant for failed_inbox and
contains HTTP status 500, rather than only checking result.is_err(). Preserve
the existing actions and successful follow-up send coverage.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: e4967f2d-59cc-46d9-8778-e0dd4ca0fa2c
⛔ Files ignored due to path filters (6)
Cargo.lockis excluded by!**/*.lockcrates/feder-core/tests/fixtures/rsa-other-public-key.pemis excluded by!**/*.pemcrates/feder-core/tests/fixtures/rsa-private-key.pemis excluded by!**/*.pemcrates/feder-core/tests/fixtures/rsa-public-key.pemis excluded by!**/*.pemcrates/feder-runtime-server/tests/fixtures/rsa-private-key.pemis excluded by!**/*.pemcrates/feder-runtime-server/tests/fixtures/rsa-public-key.pemis excluded by!**/*.pem
📒 Files selected for processing (39)
Cargo.tomlcrates/feder-core/Cargo.tomlcrates/feder-core/src/http_signatures.rscrates/feder-core/src/lib.rscrates/feder-runtime-server/Cargo.tomlcrates/feder-runtime-server/README.mdcrates/feder-runtime-server/src/actor.rscrates/feder-runtime-server/src/app.rscrates/feder-runtime-server/src/config.rscrates/feder-runtime-server/src/error.rscrates/feder-runtime-server/src/followers.rscrates/feder-runtime-server/src/inbox.rscrates/feder-runtime-server/src/lib.rscrates/feder-runtime-server/src/negotiation.rscrates/feder-runtime-server/src/note.rscrates/feder-runtime-server/src/object.rscrates/feder-runtime-server/src/operation.rscrates/feder-runtime-server/src/send.rscrates/feder-runtime-server/src/storage/mod.rscrates/feder-runtime-server/src/storage/sqlite.rscrates/feder-runtime-server/src/url.rscrates/feder-runtime-server/src/webfinger.rscrates/feder-runtime-server/tests/cases/actor.rscrates/feder-runtime-server/tests/cases/app.rscrates/feder-runtime-server/tests/cases/followers.rscrates/feder-runtime-server/tests/cases/inbox.rscrates/feder-runtime-server/tests/cases/object.rscrates/feder-runtime-server/tests/cases/operation.rscrates/feder-runtime-server/tests/cases/send.rscrates/feder-runtime-server/tests/cases/webfinger.rscrates/feder-runtime-server/tests/common/mod.rscrates/feder-runtime-server/tests/runtime.rscrates/feder-vocab/src/lib.rscrates/feder-vocab/tests/activitypub_serialization.rsexamples/single-user-server/Cargo.tomlexamples/single-user-server/README.mdexamples/single-user-server/src/main.rsmise.tomlrust-toolchain.toml
💤 Files with no reviewable changes (2)
- crates/feder-runtime-server/src/note.rs
- crates/feder-runtime-server/src/webfinger.rs
| fn note_recipients(local_actor: &vocab::Actor, note: &vocab::Note) -> Vec<Recipients> { | ||
| let mut recipients = Vec::new(); | ||
|
|
||
| for address in note.to.iter().chain(note.cc.iter()) { | ||
| let recipient = if address.as_str() == PUBLIC_COLLECTION { | ||
| // Public describes visibility. It cannot receive an activity. | ||
| continue; | ||
| } else if local_actor.followers.as_ref() == Some(address) { | ||
| Recipients::Followers(local_actor.id.clone()) | ||
| } else if address == &local_actor.id { | ||
| continue; | ||
| } else { | ||
| Recipients::Actor(address.clone()) | ||
| }; | ||
|
|
||
| if !recipients.contains(&recipient) { | ||
| recipients.push(recipient); | ||
| } | ||
| } | ||
| recipients | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect runtime recipient expansion for inbox-level dedup
fd -t f 'operation.rs' crates/feder-runtime-server/src | xargs -r ast-grep outline --items all
rg -nP -C5 'Recipients::(Followers|Actor|Inbox)' crates/feder-runtime-server/srcRepository: fedify-dev/feder
Length of output: 4574
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
fd -t f 'operation.rs|lib.rs|send.rs' crates
echo "== operation.rs relevant section =="
sed -n '1,120p' crates/feder-runtime-server/src/operation.rs | cat -n
echo "== resolver definitions/usages =="
fd -t f . crates | rg 'actor|resolver|resolver' | sed -n '1,80p'
rg -n "struct .*Resolver|trait .*Resolver|resolve\\(" crates/feder-runtime-server/src crates/feder-core/src | sed -n '1,120p'
echo "== note_recipients context =="
sed -n '220,290p' crates/feder-core/src/lib.rs | cat -n
echo "== SendActivity recipients construction sites =="
rg -n "SendActivity\\{|recipients:|note_recipients|to_recipients" crates -C 4Repository: fedify-dev/feder
Length of output: 13995
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== runtime send implementation =="
sed -n '1,180p' crates/feder-runtime-server/src/send.rs | cat -n
echo "== Resolve recipients tests =="
sed -n '1,260p' crates/feder-runtime-server/tests/cases/operation.rs | cat -n
echo "== actor resolver relevant section =="
sed -n '1,140p' crates/feder-runtime-server/src/actor.rs | cat -nRepository: fedify-dev/feder
Length of output: 21569
Deduplicate deliveries by resolved inbox before sending.
Recipients::Followers dedupes only within the follower list, while Recipients::Actor resolves each actor directly and adds it to the delivery queue. If bob is directly addressed and also listed as a follower, he gets one delivery from the actor path and another from the followers path unless runtime-level inbox dedup is added after _followers_ expansion.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/feder-core/src/lib.rs` around lines 255 - 275, Update the delivery
flow that expands Recipients after note_recipients so it deduplicates recipients
by resolved inbox URL before enqueueing sends. Preserve note_recipients’
existing address-level deduplication, but ensure an actor reached directly and
through Recipients::Followers produces only one delivery.
| if !["host", "date", "digest"] | ||
| .iter() | ||
| .all(|required| seen_headers.contains(required)) | ||
| { | ||
| return Err(StatusCode::UNAUTHORIZED); | ||
| } | ||
|
|
||
| verify_request_date(&req.headers)?; | ||
| verify_request_digest(&req.headers, &req.body)?; |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
Signed host value is never compared against this server's host.
host is required to be signed, but its value is taken from the request and never checked against the configured handle_host/bind host. A signature captured for another destination remains verifiable when replayed here, since both the signing string and the verification use the attacker-supplied host. Today the blast radius is limited by the follow.object == local_actor.id check, but the binding should be explicit.
🔒 Proposed check
if !["host", "date", "digest"]
.iter()
.all(|required| seen_headers.contains(required))
{
return Err(StatusCode::UNAUTHORIZED);
}
+ let host = req
+ .headers
+ .get("host")
+ .and_then(|value| value.to_str().ok())
+ .ok_or(StatusCode::UNAUTHORIZED)?;
+ if !host.eq_ignore_ascii_case(&app_state.handle_host) {
+ return Err(StatusCode::UNAUTHORIZED);
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if !["host", "date", "digest"] | |
| .iter() | |
| .all(|required| seen_headers.contains(required)) | |
| { | |
| return Err(StatusCode::UNAUTHORIZED); | |
| } | |
| verify_request_date(&req.headers)?; | |
| verify_request_digest(&req.headers, &req.body)?; | |
| if !["host", "date", "digest"] | |
| .iter() | |
| .all(|required| seen_headers.contains(required)) | |
| { | |
| return Err(StatusCode::UNAUTHORIZED); | |
| } | |
| let host = req | |
| .headers | |
| .get("host") | |
| .and_then(|value| value.to_str().ok()) | |
| .ok_or(StatusCode::UNAUTHORIZED)?; | |
| if !host.eq_ignore_ascii_case(&app_state.handle_host) { | |
| return Err(StatusCode::UNAUTHORIZED); | |
| } | |
| verify_request_date(&req.headers)?; | |
| verify_request_digest(&req.headers, &req.body)?; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/feder-runtime-server/src/inbox.rs` around lines 110 - 118, In the
request verification flow around verify_request_date and verify_request_digest,
validate the request’s signed host header against the configured handle_host or
bind host before accepting the signature. Reject mismatches with the existing
unauthorized response, while preserving verification for requests addressed to
this server.
| const NON_PUBLIC_NETWORK_CIDRS: &[&str] = &[ | ||
| "0.0.0.0/8", | ||
| "10.0.0.0/8", | ||
| "100.64.0.0/10", | ||
| "127.0.0.0/8", | ||
| "169.254.0.0/16", | ||
| "172.16.0.0/12", | ||
| "192.0.0.0/24", | ||
| "192.0.2.0/24", | ||
| "192.88.99.0/24", | ||
| "192.168.0.0/16", | ||
| "198.18.0.0/15", | ||
| "198.51.100.0/24", | ||
| "203.0.113.0/24", | ||
| "224.0.0.0/4", | ||
| "240.0.0.0/4", | ||
| "::/128", | ||
| "::1/128", | ||
| "64:ff9b::/96", | ||
| "64:ff9b:1::/48", | ||
| "100::/64", | ||
| "100:0:0:1::/64", | ||
| "2001::/23", | ||
| "2001:db8::/32", | ||
| "2002::/16", | ||
| "3fff::/20", | ||
| "5f00::/16", | ||
| "fc00::/7", | ||
| "fe80::/10", | ||
| "ff00::/8", | ||
| ]; |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
Consider adding deprecated IPv6 site-local fec0::/10.
fc00::/7 covers ULA but not the deprecated site-local range, which some internal networks still use. Cheap to add to the blocklist.
🛡️ Proposed addition
"fc00::/7",
+ "fec0::/10",
"fe80::/10",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const NON_PUBLIC_NETWORK_CIDRS: &[&str] = &[ | |
| "0.0.0.0/8", | |
| "10.0.0.0/8", | |
| "100.64.0.0/10", | |
| "127.0.0.0/8", | |
| "169.254.0.0/16", | |
| "172.16.0.0/12", | |
| "192.0.0.0/24", | |
| "192.0.2.0/24", | |
| "192.88.99.0/24", | |
| "192.168.0.0/16", | |
| "198.18.0.0/15", | |
| "198.51.100.0/24", | |
| "203.0.113.0/24", | |
| "224.0.0.0/4", | |
| "240.0.0.0/4", | |
| "::/128", | |
| "::1/128", | |
| "64:ff9b::/96", | |
| "64:ff9b:1::/48", | |
| "100::/64", | |
| "100:0:0:1::/64", | |
| "2001::/23", | |
| "2001:db8::/32", | |
| "2002::/16", | |
| "3fff::/20", | |
| "5f00::/16", | |
| "fc00::/7", | |
| "fe80::/10", | |
| "ff00::/8", | |
| ]; | |
| const NON_PUBLIC_NETWORK_CIDRS: &[&str] = &[ | |
| "0.0.0.0/8", | |
| "10.0.0.0/8", | |
| "100.64.0.0/10", | |
| "127.0.0.0/8", | |
| "169.254.0.0/16", | |
| "172.16.0.0/12", | |
| "192.0.0.0/24", | |
| "192.0.2.0/24", | |
| "192.88.99.0/24", | |
| "192.168.0.0/16", | |
| "198.18.0.0/15", | |
| "198.51.100.0/24", | |
| "203.0.113.0/24", | |
| "224.0.0.0/4", | |
| "240.0.0.0/4", | |
| "::/128", | |
| "::1/128", | |
| "64:ff9b::/96", | |
| "64:ff9b:1::/48", | |
| "100::/64", | |
| "100:0:0:1::/64", | |
| "2001::/23", | |
| "2001:db8::/32", | |
| "2002::/16", | |
| "3fff::/20", | |
| "5f00::/16", | |
| "fc00::/7", | |
| "fec0::/10", | |
| "fe80::/10", | |
| "ff00::/8", | |
| ]; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/feder-runtime-server/src/url.rs` around lines 36 - 66, Add the
deprecated IPv6 site-local CIDR "fec0::/10" to NON_PUBLIC_NETWORK_CIDRS,
preserving the existing blocklist structure and entries.
| /// A JSON-LD context represented by one or more IRIs. | ||
| #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] | ||
| #[serde(untagged)] | ||
| pub enum Context { | ||
| Iri(Iri), | ||
| Iris(Vec<Iri>), | ||
| } | ||
|
|
||
| impl Context { | ||
| #[must_use] | ||
| pub fn one(context: Iri) -> Self { | ||
| Self::Iri(context) | ||
| } | ||
|
|
||
| #[must_use] | ||
| pub fn many(contexts: impl Into<Vec<Iri>>) -> Self { | ||
| Self::Iris(contexts.into()) | ||
| } | ||
|
|
||
| fn include(&mut self, context: Iri) { | ||
| match self { | ||
| Self::Iri(existing) if existing == &context => {} | ||
| Self::Iri(existing) => { | ||
| *self = Self::Iris(Vec::from([existing.clone(), context])); | ||
| } | ||
| Self::Iris(existing) if !existing.contains(&context) => existing.push(context), | ||
| Self::Iris(_) => {} | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Where are remote actors deserialized into vocab::Actor?
rg -nP -C4 'from_slice::<Actor>|from_value::<Actor>|: Actor =' crates/feder-runtime-server/src
# Confirm the fixture context shape used by inbox tests
rg -nP -C3 '`@context`' crates/feder-runtime-server/tests/cases/inbox.rsRepository: fedify-dev/feder
Length of output: 1813
🏁 Script executed:
#!/bin/bash
set -u
printf '--- files mentioning deserialize Actor/context ---\n'
rg -n 'Actor|Context|Deserialize' crates/feder-vocab crates/feder-runtime-server/src crates/feder-runtime-server/tests/cases/inbox.rs
printf '\n--- actor resolver-related mentions ---\n'
rg -n 'actor|resolve|Deserialize|from_slice|from_value' crates/feder-runtime-server/src crates/feder-vocab/src -g '*.rs'
printf '\n--- vocab Context definition and Actor fields ---\n'
sed -n '30,60p;160,215p' crates/feder-vocab/src/lib.rs
printf '\n--- inbox fixture around 130 ---\n'
sed -n '110,145p' crates/feder-runtime-server/tests/cases/inbox.rsRepository: fedify-dev/feder
Length of output: 46123
🏁 Script executed:
#!/bin/bash
set -u
printf '%s\n' '--- crates/feder-runtime-server/src/actor.rs relevant slices ---'
sed -n '75,102p' crates/feder-runtime-server/src/actor.rs
sed -n '191,218p' crates/feder-runtime-server/src/actor.rs
printf '%s\n' '--- crates/feder-runtime-server/src/inbox.rs accept/follow handling ---'
sed -n '138,172p' crates/feder-runtime-server/src/inbox.rs
sed -n '330,370p' crates/f feder-runtime-server/src/inbox.rs
printf '%s\n' '--- all follow/accept tests in inbox.rs around fixture usage ---'
rg -n -C6 'accepts/follow|inbox/follow|follow-1|follow-2|follows|ActorDocument|ActorType|ActivityType' crates/feder-runtime-server/tests/cases/inbox.rsRepository: fedify-dev/feder
Length of output: 5166
🏁 Script executed:
#!/bin/bash
set -u
printf '%s\n' '--- crates/feder-runtime-server/src/inbox.rs line 330-370 ---'
sed -n '330,370p' crates/feder-runtime-server/src/inbox.rs
printf '%s\n' '--- all follow/accept test bodies around fixture usage ---'
sed -n '100,160p' crates/feder-runtime-server/tests/cases/inbox.rs
sed -n '240,340p' crates/feder-runtime-server/tests/cases/inbox.rsRepository: fedify-dev/feder
Length of output: 7446
🌐 Web query:
JSON-LD 1.1 @contextobject type vocab context object IRI@vocab``
💡 Result:
In JSON-LD 1.1, @vocab is a keyword used within the @context object to define a default vocabulary for the document [1][2]. It simplifies the mapping of terms by establishing a base IRI for properties and types that do not have an explicit mapping [1][3]. Key technical details regarding @vocab: Function: When @vocab is defined in the @context, any term used as a key in a node object (or a value of @type) that is not already defined as a term, not an absolute IRI, and does not contain a colon (i.e., not a compact IRI) is expanded by appending it to the @vocab IRI [1][2]. Context Integration: The @vocab keyword is placed inside a @context block [3]. It cannot be used more than once within the same context object, but it can be changed or shadowed by defining a new @context later in the document or using scoped contexts [3][4]. Relationship to IRIs: - The value of @vocab must be an IRI, a compact IRI, a term, or null [5][6]. - Since JSON-LD 1.1, the value can also be a relative IRI reference, which is resolved against the base IRI (or the vocabulary mapping of the active context) [1]. Distinction from @context: - The @context object is the container used to define all mappings (terms, prefixes, keywords, etc.) [2][7]. - @vocab is a specific configuration key used inside that @context to automate the expansion of vocabulary terms, preventing the need to explicitly define every single property or type name if they all originate from the same source (e.g., schema.org) [3]. Special Usage in @type: In JSON-LD 1.1, you can also set the @type of a property to "@vocab". This indicates that the values associated with that property should be treated as vocabulary items and expanded using the current active @vocab setting, rather than as literal strings or IRIs [4][8].
Citations:
- 1: https://www.w3.org/TR/json-ld11/
- 2: https://www.w3.org/TR/2020/CR-json-ld11-20200417/
- 3: https://stackoverflow.com/questions/30945898/what-is-the-use-of-vocab-in-json-ld-and-what-is-the-difference-to-context
- 4: https://stackoverflow.com/questions/52126765/different-value-prefixes-in-json-ld-context
- 5: https://www.w3.org/TR/json-ld-api/
- 6: https://www.w3.org/TR/2020/CR-json-ld11-20200305/
- 7: https://w3c.github.io/json-ld-syntax/
- 8: https://www.w3.org/ns/json-ld
🌐 Web query:
Mastodon ActivityPub Actor actor.json @context includes "toot"
💡 Result:
Yes, Mastodon's ActivityPub Actor objects typically include a toot namespace definition in their @context to support Mastodon-specific extensions [1][2][3]. The @context field in a Mastodon Actor JSON-LD object is often an array that includes the standard ActivityStreams context, along with a nested object defining the toot prefix [4][5][3]. This definition maps the toot prefix to the Mastodon namespace URI [1][2]: { "@context": [ "https://www.w3.org/ns/activitystreams", { "toot": "http://joinmastodon.org/ns#",... } ] } Within this namespace, Mastodon defines several custom properties used for Actor objects and other ActivityPub entities, such as featured (for pinned toots), featuredTags, blurhash, and discoverable [1][2][4]. By including this mapping in the @context, Mastodon ensures that these extended properties are correctly interpreted by other Fediverse servers [1][2]. Depending on the specific implementation or object type, additional contexts (such as the security context for public keys) may also be included in the @context array [5][3].
Citations:
- 1: https://github.com/mastodon/documentation/blob/main/content/en/spec/activitypub.md
- 2: https://docs.joinmastodon.org/spec/activitypub/
- 3: https://funfedi.dev/support_tables/generated/actor/
- 4: https://mastodon.gitbook.io/mastodon/spec/activitypub
- 5: https://docs-p.joinmastodon.org/spec/activitypub/
Preserve non-IRI JSON-LD context terms when deserializing remote actors.
Mastodon actors use "@context": ["https://www.w3.org/ns/activitystreams", {"toot": "http://joinmastodon.org/ns#", ...}]; the current untagged Context enum rejects the object entry before ActorDocument can be built, so Actor resolution can fail while resolving a federated actor. Add an ignored term/object variant or custom deserializer for the ignored JSON-LD context fields (@vocab, @base, @language, terms), or deserialize to serde_json::Value before mapping to Context.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/feder-vocab/src/lib.rs` around lines 180 - 209, Update the Context
deserialization used by ActorDocument so JSON-LD context arrays containing
object or other non-IRI entries are accepted and preserved or safely ignored
while IRI entries continue mapping to Context::Iri or Context::Iris. Extend
Context with an ignored term/object variant or implement a custom deserializer,
ensuring Mastodon-style contexts deserialize without rejecting the actor.
| #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] | ||
| pub enum CryptographicKeyType { | ||
| #[default] | ||
| CryptographicKey, | ||
| } | ||
|
|
||
| /// A public key published by an ActivityPub actor. | ||
| #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] | ||
| pub struct CryptographicKey { | ||
| pub id: Iri, | ||
| #[serde(rename = "type", default)] | ||
| pub kind: CryptographicKeyType, | ||
| pub owner: Iri, | ||
| #[serde(rename = "publicKeyPem")] | ||
| pub public_key_pem: String, | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Files:"
git ls-files | rg '(^|/)lib\.rs$' || true
echo
echo "Search CryptographicKey:"
rg -n "CryptographicKey|publicKey" crates/feder-vocab/src/lib.rs || true
echo
echo "Relevant lines:"
sed -n '210,255p' crates/feder-vocab/src/lib.rs | cat -n -v
echo
echo "Imports and trait derives context:"
sed -n '1,225p' crates/feder-vocab/src/lib.rs | cat -n -v
echo
echo "Find deserializers for similar types:"
rg -n "deserialize_with|deserialize\(|serde_str|enum .*Default|default =|key" crates/feder-vocab/src/lib.rs || trueRepository: fedify-dev/feder
Length of output: 11094
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Check workspace/crates around activitypub schema if present:"
git ls-files | rg 'activitypub|vocab|feder' || true
echo
echo "Cargo manifests with serde deps:"
rg -n 'serde ="|serde' cargo.toml crates -g 'Cargo.toml' || trueRepository: fedify-dev/feder
Length of output: 2342
🌐 Web query:
W3C ActivityPub Activity Streams 2.0 publicKey type CryptographicKey schema
💡 Result:
In the context of ActivityPub and Activity Streams 2.0, the publicKey property and the CryptographicKey type are core components used primarily for identifying and verifying actor public keys during HTTP Signature operations [1][2][3]. CryptographicKey Schema The CryptographicKey type is defined in the Linked Data Security Vocabulary (often associated with the URI https://w3id.org/security#Key) [4][5]. While implementations vary, a standard CryptographicKey object within an ActivityPub actor document typically includes the following properties [2][3][6]: - id: A unique URI for the key, often represented as a fragment identifier on the actor's profile (e.g., https://example.com/actor#main-key) [2][3]. - owner: The URI of the actor who owns or controls the key [2][3][4]. - publicKeyPem: A PEM-encoded string containing the public key material (e.g., RSA public key) [1][7][3]. Usage and Context - publicKey Property: Actor objects use the publicKey property to expose these key objects [1][8]. Historically, this property was used to attach a single key (or a list of keys) directly to an actor [9][3]. - HTTP Signatures: When an ActivityPub actor sends a signed request, the signature includes a keyId parameter. Receivers use this keyId to fetch the actor's object and locate the corresponding CryptographicKey object via the publicKey field [1][7][3]. Evolution and Deprecation The publicKey property is considered deprecated in newer versions of the Linked Data Security Vocabulary, which favor modern standards like Verifiable Credential Data Integrity [1][7][10]. Current best practices involve the following: - FEP-521a: This proposal (FEP-521a) advocates for representing public keys using the Multikey type and moving them into properties like assertionMethod [2][10]. - Multikey: The Multikey type replaces the older CryptographicKey structure, using a publicKeyMultibase property (encoded with base-58-btc) instead of publicKeyPem [10]. Implementers are increasingly moving toward these newer standards to support key rotation and multiple verification relationships, though support for the legacy publicKey/CryptographicKey model remains widespread across the fediverse for compatibility [1][10].
Citations:
- 1: https://swicg.github.io/activitypub-http-signature/
- 2: https://ap-next.codeberg.page/ap-next/guide.html
- 3: https://funfedi.dev/testing_tools/technical_notes/actor_public_key/
- 4: https://jsr.io/@fedify/fedify@1.10.0/doc/vocab/~/CryptographicKey
- 5: https://github.com/misskey-dev/misskey/blob/a19da125/packages/backend/src/core/activitypub/misc/contexts.ts
- 6: https://github.com/fedi-libs/apmodel/blob/main/docs/index.md
- 7: Describe how to transform a key id into a public key swicg/activitypub-http-signature#8
- 8: https://pixelfed.github.io/docs-next/spec/ActivityPub.html
- 9: https://docs.joinmastodon.org/spec/activitypub/
- 10: https://fediverse.codeberg.page/fep/fep/521a/
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Deserialize attribute for CryptographicKey:"
rg -n -C2 '#\[serde\(\).*key.*\]|derive\(Deserialize|enum CryptographicKeyType|struct CryptographicKey' crates/feder-vocab/src/lib.rs || true
echo
echo "Cargo serde version:"
git ls-files | rg 'workspace\.toml|Cargo\.toml|^Cargo\.lock$' | xargs rg -n 'serde|serde_json' || true
echo
echo "Behavioral probe of derive Deserialize single-variant enum with missing/present type attribute:"
python3 - <<'PY'
import json, re
from pathlib import Path
p = Path("crates/feder-vocab/src/lib.rs")
text = p.read_text()
print("Contains #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] for CryptographicKeyType:",
"#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]\npub enum CryptographicKeyType" in text)
print("CryptographicKeyType has #[default] on single variant:", re.search(r'pub enum CryptographicKeyType\r?\n\[\`#serde`\(untagged\)\]\r?\n\{\}\r?\n\}\\s*\}\\s*#\[\`#default`\]\s*CryptographicKey', text, re.S))
print("kind field has rename + default:", 'rename = "type", default' in text)
cases = {
"missing_type": {"id":"i","type":"Key","publicKeyPem":"p"},
}
for name, j in cases.items():
print(name, json.dumps(j))
PYRepository: fedify-dev/feder
Length of output: 2389
Make CryptographicKey.type tolerant of unknown values.
default only covers a missing type; present values like "Key" or arrays fail deserialization even though kind has no behavior associated with it. Handle unrecognized type as the default CryptographicKey instead of rejecting the actor document.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/feder-vocab/src/lib.rs` around lines 228 - 243, Update
CryptographicKeyType deserialization so present unknown values, including
strings such as "Key" and arrays, resolve to the default CryptographicKey
variant instead of failing; preserve the existing default for missing type and
the current enum behavior for recognized values.
Summary
Build out Feder's server runtime into a functional single-user ActivityPub federation proof of concept. The example project is a microblog called Federog. I follow fedify's microblog tutorial as a guideline.
This adds signed inbox (draft-cavage-http-signature way) and outbound delivery, follower management, persisted Notes, recipient-aware delivery, and ActivityPub object-serving safeguards while keeping protocol decisions in
feder-coreand platform concerns in the runtime.Closes #26
Changes
timeouts, and mismatched actor IDs.
Followand embeddedUndo(Follow)activities.Acceptactivities.to ∪ cc.404for non-public Notes instead of exposing them anonymously.Architecture
feder-coreemits storage and delivery actions without performing network or database operations. The Axum/Tokio runtime resolves those actions using SQLite, remote actor discovery, HTTP signatures(draft-cavage-http-signature), and outbound delivery.AI assistance is disclosed through the 'Assisted-by` trailers on the relevant commits. Changes section's contents are summarised by Codex:gpt-5.6-sol.
Summary by CodeRabbit
New Features
Documentation