Skip to content

createInstance() doesn't pass through Fedify's FederationOptions #41

Description

@moreal

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Effort

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions