Skip to content

feat: confine source requests to a destinations policy - #240

Closed
abnegate wants to merge 1 commit into
mainfrom
feat/source-destinations
Closed

abnegate wants to merge 1 commit into
mainfrom
feat/source-destinations

Conversation

@abnegate

Copy link
Copy Markdown
Member

What

Target::setDestinations(Destinations) confines a target's requests to a Utopia\Client\Destinations policy. The Appwrite source also applies it to its SDK client, through Sources\Appwrite\Client, a subclass of the SDK 30 client that adds the policy to the HTTP client the SDK builds (createHttpClient()).

Unset, nothing changes: requests connect anywhere, as before.

Why

appwrite/appwrite#14034 checks an Appwrite migration source's endpoint before it connects. The source then resolves the host again on every request and follows redirects, so a host that rebinds, or redirects, still reaches the internal network.

utopia-php/client already solves this. Its curl adapter checks every connection and redirect hop against the address curl actually connected to, and refuses proxies when the policy restricts addresses. On 3.x every source request goes through utopia-php/client, either Target::call() or the SDK 30 client. So the library only has to pass the caller's policy through.

This replaces the resolver hook in #229 and #230. Those predate the move to utopia-php/client: they pinned CURLOPT_RESOLVE entries and re-implemented the SDK's curl call.

Tests

tests/Migration/Unit/General/TargetDestinationsTest.php runs a local HTTP server on 127.0.0.1 and covers:

  • a target with no policy reaching it;
  • PublicInternet refusing it;
  • PublicInternet with a 127.0.0.0/8 range admitting it;
  • a policy set after a request applying to the next one;
  • the SDK client refusing and admitting it in the same way;
  • the Appwrite source passing its policy to the SDK client.

6 of the 7 fail without the change. The default case passes either way.

Locally, Pint passes and the full unit suite passes (161 tests). PHPStan level 3 reports no errors in the changed files. The only errors it reports are 10 that MigrationCLITest already has on main.

Not verified

  • The E2E suite. It needs the Docker stack, which I did not run.

🤖 Generated with Claude Code

Appwrite validates a migration source's endpoint before it connects, but
the source then resolves the host again for every request and follows
redirects anywhere, so a rebinding host or a redirect reaches the internal
network. utopia-php/client already checks each connection and redirect hop
against the address it actually reached through Destinations; this lets a
caller hand a target that policy instead of re-implementing pinning.

Target::setDestinations() applies to the target's own requests, and the
Appwrite source passes it on to its SDK client through a thin subclass that
adds it to the HTTP client the SDK builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
abnegate added a commit to appwrite/appwrite that referenced this pull request Oct 15, 2026
Main moved to utopia-php/migration 3.x, whose source requests go through
utopia-php/client, so the 2.0.x resolver branch no longer applies. Require
utopia-php/migration#240 instead, which passes a Destinations policy through.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@abnegate

Copy link
Copy Markdown
Member Author

Superseded by #229, which merged the resolver and redirect controls on main. appwrite/appwrite#14034 now uses that, so nothing depends on this branch. I'm leaving the PR open for a maintainer to close.

@abnegate

Copy link
Copy Markdown
Member Author

Ported to appwrite/appwrite#14508. Migration now lives in packages/migration in appwrite/appwrite (appwrite/appwrite#14503), and this repository is a read-only mirror. The port keeps the resolver hook from #229 alongside setDestinations(), because appwrite/appwrite#14034 still uses it.

@abnegate abnegate closed this Oct 15, 2026
imtia33 pushed a commit to imtia33/appwrite that referenced this pull request Oct 15, 2026
Ports utopia-php/migration#240. Target::setDestinations() hands a
Utopia\Client\Destinations to the client Target::call() builds, and the
Appwrite source applies it to its SDK client as well. utopia-php/client
checks every connection and redirect hop against the address curl
actually reached, and refuses proxies under a restricting policy, so a
host that rebinds or redirects cannot reach an address the caller did
not admit. Unset, requests connect anywhere, as before.

The resolver controls from appwrite#229 stay: appwrite#14034 uses
setResolver() today, and the two compose. The changelog gains a 4.0.0
section for this and for what landed since 3.1.2 without an entry,
including the redirect default that makes it a major.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant