Written with Claude Code
Background
createInstance() builds its Fedify Federation internally by calling Fedify's createFederation({ kv, queue, userAgent }) with a fixed set of options that CreateInstanceOptions controls. Every other option Fedify's createFederation() accepts (allowPrivateAddress, documentLoader, outboxRetryPolicy, etc.) is unreachable from BotKit's public API — there's no field on CreateInstanceOptions that reaches Fedify's option object.
One of those options, allowPrivateAddress, matters for testing: Fedify guards outgoing signed requests against SSRF by rejecting private/loopback addresses by default. That's the right default for production, but it also blocks anything that wants to exercise real HTTP Signature round trips (Follow → Accept, signed Create delivery, etc.) against a local test server bound to 127.0.0.1.
Motivation
We run end-to-end tests that spin up two BotKit-backed servers on 127.0.0.1 and drive real signed federation between them (follow, accept, and delivery of a Create). Without allowPrivateAddress: true, Fedify's SSRF guard rejects the signed requests outright, so this class of test is impossible against createInstance() as-is.
As a workaround we maintain a small Yarn patch (yarn patch) against @fedify/botkit that spreads a federationOptions object from CreateInstanceOptions into the internal createFederation() call. It's a five-line diff, but it has to be re-applied and re-verified on every BotKit upgrade, and it's the kind of thing that's easy to silently drop or let go stale.
This isn't only about testing, either — allowPrivateAddress aside, there's currently no way for a createInstance() caller to pass any Fedify-level option (a custom documentLoader, retry policy, etc.) without patching BotKit.
Related Issues / PRs
None found — searched federationOptions, createInstance, private address, and SSRF across open and closed issues.
Proposed Solutions
💡 If you have a better approach, please comment. Below are the options currently under consideration.
Option 1: Generic federationOptions passthrough
Add a field to CreateInstanceOptions that gets spread into the internal createFederation() call, after BotKit's own required options:
interface CreateInstanceOptions {
// ...existing fields...
readonly federationOptions?: Partial<FederationOptions>;
}
// inside createInstance()
const federation = createFederation({
kv,
queue,
userAgent,
...options.federationOptions,
});
This is essentially the diff we already carry as a local patch.
const instance = createInstance({
kv,
federationOptions:
process.env.ALLOW_PRIVATE_ADDRESS === "true"
? { allowPrivateAddress: true }
: undefined,
});
Pros:
- One addition covers
allowPrivateAddress and any other current or future Fedify option, not just the one we happen to need today.
- Matches the shape of the patch we've already run in production for a while, so it's a known-working diff.
Cons:
- Lets a caller override options BotKit depends on internally (
kv, queue, userAgent) if they spread carelessly, unless BotKit orders the spread to keep those non-overridable (as sketched above) or filters the passthrough.
- Ties
CreateInstanceOptions's type to whichever Fedify version BotKit currently pins.
Option 2: Narrow, named options for specific needs
Instead of a generic passthrough, add only the specific options that have concrete use cases, e.g.:
interface CreateInstanceOptions {
// ...existing fields...
readonly allowPrivateAddress?: boolean;
}
Pros:
- Smaller, more deliberate public surface — no risk of a caller accidentally overriding BotKit-managed options like
kv/queue.
- Each option can get its own doc comment explaining exactly when to use it (e.g. "for tests only").
Cons:
- Every additional Fedify option someone needs later requires a new BotKit API addition and release.
- Doesn't help callers who need an option this narrow list doesn't anticipate (custom
documentLoader, retry policy, etc.).
Already Answered
Q: Should allowPrivateAddress just default to true in test environments somehow?
Autodetecting "this is a test environment" seems fragile and out of scope either way — this issue is about giving callers a way to pass the option through explicitly, not about changing any default. Whatever shape the option takes, it should leave Fedify's own default (rejecting private addresses) untouched unless a caller opts in.
Q: Is this just about testing, or does it matter in production too?
The concrete case that pushed us to file this is testing, but the underlying gap — createInstance() callers can't reach any Fedify-level option — isn't testing-specific. allowPrivateAddress is the one we needed; a custom documentLoader or retry policy seem like plausible production needs for other users, though we haven't hit those ourselves.
Written with Claude Code
Background
createInstance()builds its FedifyFederationinternally by calling Fedify'screateFederation({ kv, queue, userAgent })with a fixed set of options thatCreateInstanceOptionscontrols. Every other option Fedify'screateFederation()accepts (allowPrivateAddress,documentLoader,outboxRetryPolicy, etc.) is unreachable from BotKit's public API — there's no field onCreateInstanceOptionsthat reaches Fedify's option object.One of those options,
allowPrivateAddress, matters for testing: Fedify guards outgoing signed requests against SSRF by rejecting private/loopback addresses by default. That's the right default for production, but it also blocks anything that wants to exercise real HTTP Signature round trips (Follow → Accept, signedCreatedelivery, etc.) against a local test server bound to127.0.0.1.Motivation
We run end-to-end tests that spin up two BotKit-backed servers on
127.0.0.1and drive real signed federation between them (follow, accept, and delivery of aCreate). WithoutallowPrivateAddress: true, Fedify's SSRF guard rejects the signed requests outright, so this class of test is impossible againstcreateInstance()as-is.As a workaround we maintain a small Yarn patch (
yarn patch) against@fedify/botkitthat spreads afederationOptionsobject fromCreateInstanceOptionsinto the internalcreateFederation()call. It's a five-line diff, but it has to be re-applied and re-verified on every BotKit upgrade, and it's the kind of thing that's easy to silently drop or let go stale.This isn't only about testing, either —
allowPrivateAddressaside, there's currently no way for acreateInstance()caller to pass any Fedify-level option (a customdocumentLoader, retry policy, etc.) without patching BotKit.Related Issues / PRs
None found — searched
federationOptions,createInstance,private address, andSSRFacross open and closed issues.Proposed Solutions
Option 1: Generic
federationOptionspassthroughAdd a field to
CreateInstanceOptionsthat gets spread into the internalcreateFederation()call, after BotKit's own required options:This is essentially the diff we already carry as a local patch.
Pros:
allowPrivateAddressand any other current or future Fedify option, not just the one we happen to need today.Cons:
kv,queue,userAgent) if they spread carelessly, unless BotKit orders the spread to keep those non-overridable (as sketched above) or filters the passthrough.CreateInstanceOptions's type to whichever Fedify version BotKit currently pins.Option 2: Narrow, named options for specific needs
Instead of a generic passthrough, add only the specific options that have concrete use cases, e.g.:
Pros:
kv/queue.Cons:
documentLoader, retry policy, etc.).Already Answered
Q: Should
allowPrivateAddressjust default totruein test environments somehow?Autodetecting "this is a test environment" seems fragile and out of scope either way — this issue is about giving callers a way to pass the option through explicitly, not about changing any default. Whatever shape the option takes, it should leave Fedify's own default (rejecting private addresses) untouched unless a caller opts in.
Q: Is this just about testing, or does it matter in production too?
The concrete case that pushed us to file this is testing, but the underlying gap —
createInstance()callers can't reach any Fedify-level option — isn't testing-specific.allowPrivateAddressis the one we needed; a customdocumentLoaderor retry policy seem like plausible production needs for other users, though we haven't hit those ourselves.