From e35cfa928a3b26e930bf18189a0fe73fcfaecf9f Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Fri, 22 May 2026 09:22:01 +0100 Subject: [PATCH] Verify apiKey ownership to safely take DB fast path for SourceAppwrite --- src/Appwrite/Platform/Workers/Migrations.php | 24 ++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Workers/Migrations.php b/src/Appwrite/Platform/Workers/Migrations.php index b37251faf6..3a0a9d7acf 100644 --- a/src/Appwrite/Platform/Workers/Migrations.php +++ b/src/Appwrite/Platform/Workers/Migrations.php @@ -204,17 +204,37 @@ class Migrations extends Action if (! empty($credentials['projectId'])) { $this->sourceProject = $this->dbForPlatform->getDocument('projects', $credentials['projectId']); + // For Appwrite -> Appwrite, "project exists locally with this id" is not + // enough to take the DB fast path: a destination project deliberately + // created with the source's projectId (to preserve cross-references like + // createdBy/userId) would collide. Verify the user-supplied apiKey actually + // belongs to the local project — only then is it safe to read direct. + $isLocalSource = false; + if ( + $source === SourceAppwrite::getName() + && $destination === DestinationAppwrite::getName() + && !$this->sourceProject->isEmpty() + && !empty($credentials['apiKey']) + ) { + $keyDoc = $this->dbForPlatform->findOne('keys', [ + Query::equal('secret', [$credentials['apiKey']]), + Query::equal('projectInternalId', [$this->sourceProject->getSequence()]), + ]); + $isLocalSource = $keyDoc !== false && !$keyDoc->isEmpty(); + } + $sourceRegion = $this->sourceProject->getAttribute('region', 'default'); $destinationRegion = $this->project->getAttribute('region', 'default'); $useAppwriteApiSource = $source === SourceAppwrite::getName() && $destination === DestinationAppwrite::getName() - && ($this->sourceProject->isEmpty() || $sourceRegion !== $destinationRegion); + && (!$isLocalSource || $sourceRegion !== $destinationRegion); + if (! $useAppwriteApiSource) { if ($this->sourceProject->isEmpty()) { throw new Exception(Exception::MIGRATION_SOURCE_PROJECT_NOT_FOUND); } $projectDB = call_user_func($this->getProjectDB, $this->sourceProject); - } elseif ($this->sourceProject->isEmpty()) { + } elseif (!$isLocalSource) { // External source — processMigration defaults missing endpoint/apiKey to this // instance, which would silently self-call with the destination's key. Require // explicit credentials so the failure mode is clear.