diff --git a/Services/ProjectComposerJsonUpdater.php b/Services/ProjectComposerJsonUpdater.php index e533787..f780cb6 100644 --- a/Services/ProjectComposerJsonUpdater.php +++ b/Services/ProjectComposerJsonUpdater.php @@ -14,6 +14,20 @@ */ class ProjectComposerJsonUpdater { + /** + * Shopware is not affected because only authenticated administration users can manipulate input rendered by dompdf. + * + * @var array + */ + private const DOMPDF_ADVISORIES = [ + 'CVE-2026-59943' => 'https://github.com/advisories/GHSA-j8qw-6jw8-r297', + 'CVE-2026-59942' => 'https://github.com/advisories/GHSA-f5gf-2cj8-52g2', + 'CVE-2026-59941' => 'https://github.com/advisories/GHSA-8hg6-c449-896m', + 'CVE-2026-56722' => 'https://github.com/advisories/GHSA-cx96-42px-69fm', + 'CVE-2026-55555' => 'https://github.com/advisories/GHSA-7x2p-4jvh-6384', + 'CVE-2026-55554' => 'https://github.com/advisories/GHSA-wvh6-f5jh-8gw4', + ]; + public function __construct(private readonly HttpClientInterface $httpClient) {} public function update(string $file, string $latestVersion): void @@ -58,10 +72,50 @@ public function update(string $file, string $latestVersion): void $composerJson = $this->ensureConflictsRepository($composerJson); $composerJson = $this->configureRepositories($composerJson); + $composerJson = $this->ignoreDompdfAdvisories($composerJson); file_put_contents($file, json_encode($composerJson, \JSON_THROW_ON_ERROR | \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES)); } + /** + * @param array $config + * + * @return array + */ + private function ignoreDompdfAdvisories(array $config): array + { + /** @var list|array $rawIgnoredAdvisories */ + $rawIgnoredAdvisories = $config['config']['audit']['ignore'] ?? []; + /** @var array $normalizedIgnoredAdvisories */ + $normalizedIgnoredAdvisories = []; + + // Normalize input so advisories can always be looked up by array key. + if (array_is_list($rawIgnoredAdvisories)) { + foreach ($rawIgnoredAdvisories as $ignoredAdvisory) { + if (\is_string($ignoredAdvisory)) { + $normalizedIgnoredAdvisories[$ignoredAdvisory] = null; + } + } + } else { + $normalizedIgnoredAdvisories = $rawIgnoredAdvisories; + } + + foreach (self::DOMPDF_ADVISORIES as $advisory => $link) { + if (array_key_exists($advisory, $normalizedIgnoredAdvisories)) { + continue; + } + + $normalizedIgnoredAdvisories[$advisory] = [ + 'apply' => 'block', + 'reason' => 'Shopware is not affected because only authenticated administration users can manipulate input rendered by dompdf. See ' . $link, + ]; + } + + $config['config']['audit']['ignore'] = $normalizedIgnoredAdvisories; + + return $config; + } + private function getVersion(string $latestVersion): string { $nextVersion = Platform::getEnv('SW_RECOVERY_NEXT_VERSION'); diff --git a/Tests/Services/ProjectComposerJsonUpdaterTest.php b/Tests/Services/ProjectComposerJsonUpdaterTest.php index 1da75af..f0d68fc 100644 --- a/Tests/Services/ProjectComposerJsonUpdaterTest.php +++ b/Tests/Services/ProjectComposerJsonUpdaterTest.php @@ -43,7 +43,7 @@ public function testUpdate(): void '6.4.18.0' ); - $composerJson = json_decode((string) file_get_contents($this->json), true, 512, \JSON_THROW_ON_ERROR); + $composerJson = $this->readComposerJsonWithoutDompdfAdvisories(); static::assertSame( [ @@ -61,6 +61,51 @@ public function testUpdate(): void ); } + public function testUpdateIgnoresDompdfAdvisoriesForDependencyBlocking(): void + { + file_put_contents($this->json, json_encode([ + 'require' => [ + 'shopware/core' => '1.2.3', + ], + 'config' => [ + 'audit' => [ + 'ignore' => ['GHSA-existing-advisory'], + ], + ], + ], \JSON_THROW_ON_ERROR)); + + (new ProjectComposerJsonUpdater(new MockHttpClient([$this->getEmptyVersionsResponse()])))->update( + $this->json, + '6.4.18.0' + ); + + $composerJson = json_decode((string) file_get_contents($this->json), true, 512, \JSON_THROW_ON_ERROR); + $ignoredAdvisories = $composerJson['config']['audit']['ignore']; + + static::assertIsArray($ignoredAdvisories); + static::assertArrayHasKey('GHSA-existing-advisory', $ignoredAdvisories); + static::assertNull($ignoredAdvisories['GHSA-existing-advisory']); + unset($ignoredAdvisories['GHSA-existing-advisory']); + + $expectedAdvisories = [ + 'CVE-2026-59943' => 'https://github.com/advisories/GHSA-j8qw-6jw8-r297', + 'CVE-2026-59942' => 'https://github.com/advisories/GHSA-f5gf-2cj8-52g2', + 'CVE-2026-59941' => 'https://github.com/advisories/GHSA-8hg6-c449-896m', + 'CVE-2026-56722' => 'https://github.com/advisories/GHSA-cx96-42px-69fm', + 'CVE-2026-55555' => 'https://github.com/advisories/GHSA-7x2p-4jvh-6384', + 'CVE-2026-55554' => 'https://github.com/advisories/GHSA-wvh6-f5jh-8gw4', + ]; + + static::assertSame(array_keys($expectedAdvisories), array_keys($ignoredAdvisories)); + + foreach ($expectedAdvisories as $cve => $link) { + static::assertSame([ + 'apply' => 'block', + 'reason' => 'Shopware is not affected because only authenticated administration users can manipulate input rendered by dompdf. See ' . $link, + ], $ignoredAdvisories[$cve]); + } + } + public function testUpdateWithRC(): void { (new ProjectComposerJsonUpdater(new MockHttpClient([$this->getEmptyVersionsResponse()])))->update( @@ -68,7 +113,7 @@ public function testUpdateWithRC(): void '6.4.18.0-rc1' ); - $composerJson = json_decode((string) file_get_contents($this->json), true, 512, \JSON_THROW_ON_ERROR); + $composerJson = $this->readComposerJsonWithoutDompdfAdvisories(); static::assertSame( [ @@ -98,7 +143,7 @@ public function testUpdateWithFixVersion(): void unset($_SERVER['SW_RECOVERY_NEXT_VERSION']); - $composerJson = json_decode((string) file_get_contents($this->json), true, 512, \JSON_THROW_ON_ERROR); + $composerJson = $this->readComposerJsonWithoutDompdfAdvisories(); static::assertSame( [ @@ -129,7 +174,7 @@ public function testUpdateWithFixVersionAndBranch(): void unset($_SERVER['SW_RECOVERY_NEXT_VERSION']); - $composerJson = json_decode((string) file_get_contents($this->json), true, 512, \JSON_THROW_ON_ERROR); + $composerJson = $this->readComposerJsonWithoutDompdfAdvisories(); static::assertSame( [ @@ -160,7 +205,7 @@ public function testUpdateWithFixVersionAndBranchSame(): void unset($_SERVER['SW_RECOVERY_NEXT_VERSION']); - $composerJson = json_decode((string) file_get_contents($this->json), true, 512, \JSON_THROW_ON_ERROR); + $composerJson = $this->readComposerJsonWithoutDompdfAdvisories(); static::assertSame( [ @@ -193,7 +238,7 @@ public function testUpdateWithSymfonyRuntimeRequirement(): void '6.6.0.0' ); - $composerJson = json_decode((string) file_get_contents($this->json), true, 512, \JSON_THROW_ON_ERROR); + $composerJson = $this->readComposerJsonWithoutDompdfAdvisories(); static::assertSame( [ @@ -227,7 +272,7 @@ public function testUpdate67RemovesConflict(): void '6.7.0.0' ); - $composerJson = json_decode((string) file_get_contents($this->json), true, 512, \JSON_THROW_ON_ERROR); + $composerJson = $this->readComposerJsonWithoutDompdfAdvisories(); static::assertSame( [ @@ -260,7 +305,7 @@ public function testUpdateConflictPackageGetsAdded(): void '6.6.0.0' ); - $composerJson = json_decode((string) file_get_contents($this->json), true, 512, \JSON_THROW_ON_ERROR); + $composerJson = $this->readComposerJsonWithoutDompdfAdvisories(); static::assertSame( [ @@ -294,7 +339,7 @@ public function testUpdateConflictPackageGetsAddedMinified(): void '6.2.0.0' ); - $composerJson = json_decode((string) file_get_contents($this->json), true, 512, \JSON_THROW_ON_ERROR); + $composerJson = $this->readComposerJsonWithoutDompdfAdvisories(); static::assertSame( [ @@ -328,7 +373,7 @@ public function testUpdateConflictPackageGetsAddedUsesOlderVersionWhenConstraint '6.4.0.0' ); - $composerJson = json_decode((string) file_get_contents($this->json), true, 512, \JSON_THROW_ON_ERROR); + $composerJson = $this->readComposerJsonWithoutDompdfAdvisories(); static::assertSame( [ @@ -369,7 +414,7 @@ public function testWithRecoveryRepository(): void unset($_SERVER['SW_RECOVERY_NEXT_VERSION']); - $composerJson = json_decode((string) file_get_contents($this->json), true, 512, \JSON_THROW_ON_ERROR); + $composerJson = $this->readComposerJsonWithoutDompdfAdvisories(); static::assertSame( [ @@ -498,6 +543,26 @@ public function testConflictsRepositoryPreservesKeyedMapForm(): void ); } + /** + * @return array + */ + private function readComposerJsonWithoutDompdfAdvisories(): array + { + $composerJson = json_decode((string) file_get_contents($this->json), true, 512, \JSON_THROW_ON_ERROR); + + unset($composerJson['config']['audit']['ignore']); + + if ($composerJson['config']['audit'] === []) { + unset($composerJson['config']['audit']); + } + + if ($composerJson['config'] === []) { + unset($composerJson['config']); + } + + return $composerJson; + } + private function getEmptyVersionsResponse(): MockResponse { $json = <<