Skip to content

Handle FetchError in getSignedKeyOwner() gracefully#589

Merged
dahlia merged 2 commits intofedify-dev:mainfrom
dahlia:bugfix/get-signed-key-owner-fetch-error
Feb 26, 2026
Merged

Handle FetchError in getSignedKeyOwner() gracefully#589
dahlia merged 2 commits intofedify-dev:mainfrom
dahlia:bugfix/get-signed-key-owner-fetch-error

Conversation

@dahlia
Copy link
Member

@dahlia dahlia commented Feb 26, 2026

Summary

When a remote server has authorized fetch enabled, fetching the key owner actor requires a signed request—but getSignedKeyOwner() made that fetch unsigned by default. If the remote server returned HTTP 401, the resulting FetchError propagated uncaught up through handleActor(), causing Fedify to return a 500 Internal Server Error to the requester instead of treating them as unauthenticated.

This bug broke all interaction with GoToSocial (and any other server with authorized fetch enabled) whenever an AuthorizePredicate was attached to an actor dispatcher, even a trivial one that allows all requests.

Changes

  • RequestContext.getSignedKeyOwner() now catches FetchError from the key owner lookup, logs a warning via LogTape, and returns null (i.e., treats the request as unauthenticated) instead of throwing. All other errors are still re-thrown.
  • Added a regression test that simulates a remote server returning HTTP 401 for the actor URL and asserts that getSignedKeyOwner() returns null.
  • Updated the Instance actor section of the authorized fetch docs to explain both failure modes (unauthenticated appearance and infinite loop) and document the new null-return behavior.

Notes

The proper solution for full interoperability with authorized-fetch servers is to use the instance actor pattern: pass an authenticated document loader to getSignedKeyOwner({ documentLoader }) so that the key owner fetch is signed. This is already supported and documented. This fix ensures that without the instance actor, the server no longer crashes with 500—it simply treats such requests as unauthenticated.

Fixes #473.

When a remote server has authorized fetch enabled and returns HTTP 401
for actor lookups, `RequestContext.getSignedKeyOwner()` previously let
the `FetchError` propagate uncaught, resulting in a 500 Internal Server
Error instead of treating the request as unauthenticated.

This commit wraps the `getKeyOwner()` call in a try/catch so that a
`FetchError` causes `getSignedKeyOwner()` to log a warning and return
`null` (unauthenticated), while any other error is still re-thrown.
A regression test and documentation updates are included.

Fixes fedify-dev#473

Co-Authored-By: Claude <claude@anthropic.com>
@dahlia dahlia added this to the Fedify 2.1 milestone Feb 26, 2026
@dahlia dahlia requested a review from Copilot February 26, 2026 18:59
@dahlia dahlia added type/bug Something isn't working activitypub/interop Interoperability issues activitypub/gotosocial GoToSocial compatibility labels Feb 26, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @dahlia, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves a critical bug where Fedify servers would return a 500 Internal Server Error when interacting with remote servers that require authorized fetch (such as GoToSocial) and respond with an HTTP 401 during the key owner lookup. The implemented fix ensures that RequestContext.getSignedKeyOwner() now catches these FetchError instances, logs a warning, and returns null. This change prevents server crashes, treating such requests as unauthenticated rather than causing an internal server error, thereby improving interoperability and robustness with authorized fetch mechanisms.

Highlights

  • Error Handling: RequestContext.getSignedKeyOwner() now gracefully handles FetchError (e.g., from HTTP 401 responses) by returning null instead of throwing, preventing 500 Internal Server Errors when interacting with authorized fetch servers.
  • Testing: A new regression test was added to confirm that getSignedKeyOwner() returns null when a remote server responds with HTTP 401 for the actor URL, simulating authorized fetch failures.
  • Documentation: The documentation for authorized fetch was updated to explain the new null-return behavior of getSignedKeyOwner() and clarify the failure modes (unauthenticated appearance and infinite loop).
Changelog
  • CHANGES.md
    • Fixed RequestContext.getSignedKeyOwner() to return null instead of throwing an error when the remote server requires authorized fetch and returns 401 Unauthorized for the key owner lookup, which previously caused a 500 Internal Server Error.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request gracefully handles FetchError in getSignedKeyOwner() by catching the error, logging a warning, and returning null instead of letting it propagate and cause a 500 Internal Server Error. This fixes an interoperability issue with servers that use authorized fetch. The change is well-implemented with a new regression test to cover the failure scenario and corresponding documentation updates to explain the new behavior. The code is clear, correct, and follows best practices.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a federation error-handling bug where RequestContext.getSignedKeyOwner() could throw an uncaught FetchError (notably on HTTP 401 from authorized-fetch servers), leading to a 500 response instead of treating the requester as unauthenticated.

Changes:

  • Catch FetchError during key-owner lookup in getSignedKeyOwner(), log a warning, and return null.
  • Add a regression test covering the 401/FetchError case.
  • Update authorized-fetch documentation and add a changelog entry for the fix.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
packages/fedify/src/federation/middleware.ts Handles FetchError from key-owner fetch by logging and returning null instead of throwing.
packages/fedify/src/federation/middleware.test.ts Adds regression test ensuring getSignedKeyOwner() returns null on FetchError.
docs/manual/access-control.md Documents authorized-fetch failure modes and the new null behavior.
CHANGES.md Adds release note entry referencing the fix and linked issue/PR.

Include the key owner URL ({keyOwner}) as a separate field in the
warning log message, in addition to the key ID ({keyId}), to make
it clearer which actor URL the fetch failed for when debugging
authorized-fetch interoperability issues.

fedify-dev#589 (comment)

Co-Authored-By: Claude <claude@anthropic.com>
@dahlia dahlia merged commit 6c060f4 into fedify-dev:main Feb 26, 2026
15 checks passed
@codecov
Copy link

codecov bot commented Feb 26, 2026

Codecov Report

❌ Patch coverage is 94.44444% with 1 line in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
packages/fedify/src/federation/middleware.ts 94.44% 1 Missing ⚠️
Files with missing lines Coverage Δ
packages/fedify/src/federation/middleware.ts 95.52% <94.44%> (-0.02%) ⬇️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

activitypub/gotosocial GoToSocial compatibility activitypub/interop Interoperability issues type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Having an AuthorizePredicate on an actor dispatcher breaks interoperability with GoToSocial

2 participants