Document signing companion HTTP requests - #3556
Conversation
|
👋 Thanks for your contribution! I see you have provided all the required changelog information in your PR description. To complete the process, you'll need to create a changelog file in your branch. Please create a file named You can do this by either:
Once you've committed and pushed the file to your PR branch, the checks will pass automatically. |
|
Thanks again for pointing us back toward the existing composition and signing surfaces. I have now released Axismundi ActivityPub Bridge 0.0.16 Alpha, which works with stock ActivityPub 9.0.2 and no longer depends on the integration fork. The transport spool was also changed from the provisional private CPT to a dedicated delivery table rather than simply copying
The released Bridge instead uses one site-prefixed delivery table with a unique Activity URI hash, one status field, a conditional Current companion releases:
At this point the companion stack includes FEP-b2b8 Article projection, an FEP-1311-based standalone media rendition application, and Like/Undo plus Announce/Undo. FEP-044f quote authorization work is in progress. Once that layer is complete, I plan to run the real Article Create and end-to-end interaction scenarios against public Mastodon/Misskey instances and report the observed interoperability results. |
|
Follow-up interoperability update: The FEP-044f consent and authorization path is now implemented in the Axismundi companion stack. This includes per-Article I cannot yet honestly mark the complete Mastodon quote round trip as passed. During one deployment the Actor document was briefly cached without its Bridge 0.0.20 closes that deployment race: As an independent control, the same Actor identity, key material, stock ActivityPub signing hook, and Bridge delivery table successfully completed Follow/Accept and Announce/repost interoperability against uri.life. That instance does not expose quote authoring, so it could not exercise FEP-044f, but it confirms that the stock signing/composition path works outside the mastodon.social cache condition. Current relevant releases: I will post the final mastodon.social QuoteRequest -> Accept -> QuoteAuthorization result once that cache no longer masks the corrected Actor representation. |
|
Final interoperability update: The outbound FEP-044f Quote path is now complete in the Axismundi companion stack, including the held Create lifecycle for remote consent:
The projection emits the FEP-044f I have now verified the completed flow against both public implementations:
The generic Bridge delivery path is also covered end to end for outbound QuoteRequest, automatic Accept, and revocation Delete. The full quote path no longer requires Quote-specific transport code. Current releases: |
|
I know I mentioned the FEDERATION.md, but I think (because the FEDERATION.md is about generics) we should have a dedicated doc in the docs folder instead. What do you think? |
|
Yes, agreed. I moved the companion signing/delivery material out of Separately, I am currently implementing |
|
Companion-stack progress update The Axismundi theme and the current companion-plugin releases are now at a point where the integrated federated-object experience is genuinely usable, while still clearly pre-release and incomplete.
Since the earlier QuoteAuthorization work, the main advances are on the receiving and presentation side:
There is still substantial work ahead: pagination and richer discovery filters, fuller media/object presentation, notification and inbox UX, additional interaction views, reconciliation work, and more real-world interoperability coverage. But the pieces now form a useful end-to-end baseline rather than isolated protocol experiments. The companion code continues to compose with the stock ActivityPub plugin at the verified inbound-handler and outbound HTTP-signing seams documented by this PR; it does not replace the plugin's signature verification or signer. |
pfefferle
left a comment
There was a problem hiding this comment.
Thanks for moving this out of FEDERATION.md. Two structural notes and one correction to the prose, suggested inline where GitHub lets me.
Placement. docs/developer-docs.md already exists for exactly this audience. It opens by saying it is for developers building a complementary plugin and documents the available hooks, so a companion signing seam belongs there as a section rather than as a new top-level file. docs/readme.md currently indexes only the user-facing FAQ and How-To sections and does not link developer-docs.md at all, so a one-item "Extension points" heading there ends up promoting this single seam above the general developer documentation.
Filename. "External Activity delivery" reads as though the plugin delivers something. The doc correctly says the companion owns recipient selection, the queue and retries, so what the plugin actually contributes is the signature. Under a "Signing Outbound Requests" heading that mismatch goes away.
One correction worth making. The bullet about not persisting key material understates what happens. key_id and private_key deliberately stay in the request arguments after signing, because Signature::maybe_double_knock() is hooked on http_response and needs them to re-sign in the draft format and retry when an RFC 9421 request comes back 4xx (see includes/class-signature.php:214). They are therefore readable by any other http_request_args or http_response callback on the site, and by anything that logs request arguments. A companion cannot prevent that, so the doc should say so plainly instead of implying that careful handling on the caller's side is enough.
Here is the section to add to docs/developer-docs.md, with - [Signing Outbound Requests](#signing-outbound-requests) added to its Table of Contents:
## Signing Outbound Requests
A companion plugin that constructs and delivers its own Activities can reuse the plugin's HTTP-signing implementation, without adopting the Outbox model. Pass the sender's public key identifier and private key as the `key_id` and `private_key` request arguments, and the plugin's `http_request_args` filter adds the signature headers before WordPress sends the request:
```php
$response = wp_safe_remote_post(
$recipient_inbox,
array(
'body' => wp_json_encode( $activity ),
'headers' => array( 'Content-Type' => 'application/activity+json' ),
'data_format' => 'body',
'key_id' => $sender_key_id,
'private_key' => $sender_private_key,
)
);
```
The plugin chooses the signature format itself, based on the site's RFC 9421 setting and whether the recipient is known to support it. When an RFC 9421 request is answered with a 4xx, the plugin re-signs it in the older draft format and retries once. Callers do not select the format and should not depend on which one a given request uses.
Both arguments stay in the request arguments after signing, because that retry needs them again once the response comes back. They are therefore visible to any other callback on `http_request_args` and `http_response`, and to anything on the site that logs request arguments. Resolve key material only for the duration of the send, and treat the request arguments as readable by other plugins.
The companion remains responsible for:
- validating recipient URLs;
- selecting recipients;
- owning its delivery queue and retry policy; and
- resolving private key material only while sending, without persisting it in transport rows or logs.| ## Extension points | ||
|
|
||
| - [External Activity delivery](./external-activity-delivery.md) documents the supported HTTP-signing seam for companion plugins that own their outbound delivery flow. | ||
|
|
There was a problem hiding this comment.
This index lists only the user-facing FAQ and How-To sections today, and does not link developer-docs.md. Adding a single extension point here puts it above the general developer docs. Suggest dropping it and adding the content to docs/developer-docs.md instead.
| ## Extension points | |
| - [External Activity delivery](./external-activity-delivery.md) documents the supported HTTP-signing seam for companion plugins that own their outbound delivery flow. |
|
|
||
| ## How-To | ||
|
|
||
| For guides on configuring specific setups (caching, reverse proxies, subdirectory installs), check out the [How-To section](./how-to) or use [the support forums on WordPress.org](https://wordpress.org/support/plugin/activitypub/). |
There was a problem hiding this comment.
Unrelated to the change: this drops the file's trailing blank line. Worth restoring so the diff stays limited to the section above.
| For guides on configuring specific setups (caching, reverse proxies, subdirectory installs), check out the [How-To section](./how-to) or use [the support forums on WordPress.org](https://wordpress.org/support/plugin/activitypub/). | |
| For guides on configuring specific setups (caching, reverse proxies, subdirectory installs), check out the [How-To section](./how-to) or use [the support forums on WordPress.org](https://wordpress.org/support/plugin/activitypub/). | |
| @@ -0,0 +1,25 @@ | |||
| # External Activity delivery | |||
There was a problem hiding this comment.
This file can be removed once the content lands in docs/developer-docs.md, which is already the home for companion-plugin extension points. The full section, with the signature-format and key-visibility notes added, is in the review comment above.



What
Document the existing companion-plugin signing surface in
FEDERATION.md.A caller can pass
key_idandprivate_keytowp_safe_remote_post()and reuse the plugin's existinghttp_request_argssignature implementation without adopting the official Outbox model.The new section also makes the ownership boundary explicit: the caller owns recipient validation, recipient selection, queueing, and retries, and should resolve private key material only while sending.
Why
This follows the maintainer guidance in #3548. The capability already exists and works with stock 9.0.2, but it was not documented as an extension point. A small documentation contract is preferable to adding a parallel delivery API.
Resolution of #3533
No new domain-persistence or delivery API is required. The inbound half already composes from the existing post-verification controller actions (
activitypub_inboxandactivitypub_inbox_shared), behavior-level handler registration throughactivitypub_register_handlers, and conditionalactivitypub_skip_inbox_storageclaiming. A companion can therefore consume verified traffic and suppress duplicate default domain persistence without replacing signature verification.For outbound traffic, the companion owns recipient selection, its private spool, and retry policy. This PR documents the remaining supported boundary: reusing the official
http_request_argssigner with transientkey_idandprivate_keyrequest arguments. Together these existing seams satisfy the inbound and outbound requirements described in #3533 while keeping the official plugin authoritative for protocol verification and HTTP signing.Validation
Changelog
Changelog Entry Details
Significance
Type
Message
Document how companion plugins can reuse the existing outbound HTTP signing extension point.
Closes #3533