diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml index 7e3c4355..3cde0028 100644 --- a/.github/workflows/mutation.yml +++ b/.github/workflows/mutation.yml @@ -70,6 +70,6 @@ jobs: - name: Run infection. run: | - vendor/bin/infection --threads=2 --ignore-msi-with-no-mutations + vendor/bin/infection --threads=1 --ignore-msi-with-no-mutations env: STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cad46e0..c832d2fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Enh #460: Remove unnecessary files from Composer package (@mspirkov) - Enh #461: Add `ext-pdo_mysql` to `require` section of `composer.json` (@Tigrov) - Enh #462: Remove `ext-ctype` from `require` section of `composer.json` (@Tigrov) +- Bug #463: Fix SQL injection in `Schema::findViewNames()` (@darkspock, @vjik) ## 2.0.0 December 05, 2025 diff --git a/src/Schema.php b/src/Schema.php index a191e956..3b625978 100644 --- a/src/Schema.php +++ b/src/Schema.php @@ -187,17 +187,20 @@ protected function findTableNames(string $schema = ''): array protected function findViewNames(string $schema = ''): array { - $sql = match ($schema) { - '' => << << $schema]; + } /** @var string[] */ - return $this->db->createCommand($sql)->queryColumn(); + return $this->db->createCommand($sql, $params)->queryColumn(); } /** diff --git a/tests/SchemaTest.php b/tests/SchemaTest.php index 0978f02c..4c364b61 100644 --- a/tests/SchemaTest.php +++ b/tests/SchemaTest.php @@ -264,8 +264,43 @@ public function testGetViewNames(): void }; $this->assertSame($viewExpected, $views); + } - $db->close(); + public function testGetViewNamesWithSchema(): void + { + $db = TestConnection::create(); + + $schema1 = 'test_get_view_names_with_schema1'; + $schema2 = 'test_get_view_names_with_schema2'; + + try { + $db->createCommand("CREATE DATABASE IF NOT EXISTS `$schema1`")->execute(); + $db->createCommand("CREATE DATABASE IF NOT EXISTS `$schema2`")->execute(); + + $db->createCommand("CREATE OR REPLACE VIEW `$schema1`.`view_a` AS SELECT 1")->execute(); + $db->createCommand("CREATE OR REPLACE VIEW `$schema1`.`view_b` AS SELECT 1")->execute(); + $db->createCommand("CREATE OR REPLACE VIEW `$schema2`.`view_c` AS SELECT 1")->execute(); + + $schema = $db->getSchema(); + + $this->assertSame(['view_a', 'view_b'], $schema->getViewNames($schema1)); + $this->assertSame(['view_c'], $schema->getViewNames($schema2)); + } finally { + $db->createCommand("DROP DATABASE IF EXISTS `$schema1`")->execute(); + $db->createCommand("DROP DATABASE IF EXISTS `$schema2`")->execute(); + $db->close(); + } + } + + public function testGetViewNamesSqlInjection(): void + { + $db = $this->getSharedConnection(); + $this->loadFixture(); + + $schema = $db->getSchema(); + $views = $schema->getViewNames("' OR ''='"); + + $this->assertSame([], $views); } #[DataProviderExternal(SchemaProvider::class, 'constraints')]