Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions Services/ProjectComposerJsonUpdater.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@
*/
class ProjectComposerJsonUpdater
{
/**
* Shopware is not affected because only authenticated administration users can manipulate input rendered by dompdf.
*
* @var array<string, string>
*/
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
Expand Down Expand Up @@ -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<mixed> $config
*
* @return array<mixed>
*/
private function ignoreDompdfAdvisories(array $config): array
{
/** @var list<string>|array<string, array{apply?: string, reason?: string}|string|null> $rawIgnoredAdvisories */
$rawIgnoredAdvisories = $config['config']['audit']['ignore'] ?? [];
/** @var array<string, array{apply?: string, reason?: string}|string|null> $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');
Expand Down
87 changes: 76 additions & 11 deletions Tests/Services/ProjectComposerJsonUpdaterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(
[
Expand All @@ -61,14 +61,59 @@ 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(
$this->json,
'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(
[
Expand Down Expand Up @@ -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(
[
Expand Down Expand Up @@ -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(
[
Expand Down Expand Up @@ -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(
[
Expand Down Expand Up @@ -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(
[
Expand Down Expand Up @@ -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(
[
Expand Down Expand Up @@ -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(
[
Expand Down Expand Up @@ -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(
[
Expand Down Expand Up @@ -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(
[
Expand Down Expand Up @@ -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(
[
Expand Down Expand Up @@ -498,6 +543,26 @@ public function testConflictsRepositoryPreservesKeyedMapForm(): void
);
}

/**
* @return array<mixed>
*/
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 = <<<JSON
Expand Down
Loading