diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 4912f27f..5b1f84bb 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -35,9 +35,16 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ['8.2'] - databases: ['sqlite'] - server-versions: ['master', 'stable33'] + # Pair each server branch with a PHP version it actually supports. A plain + # cross-product put PHP 8.2 against master, which requires 8.3 or newer, so + # `occ maintenance:install` aborted before any test ran. + include: + - php-versions: '8.2' + databases: 'sqlite' + server-versions: 'stable33' + - php-versions: '8.3' + databases: 'sqlite' + server-versions: 'master' name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a15e6b8..6fef68f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## 3.2.8 + +### Fixed + +- Update `league/commonmark` to 2.10.0, closing four high-severity denial-of-service + advisories and an `AttributesExtension` unsafe-link filter bypass on the converter + used to render issue and pull request bodies. +- Update bundled JavaScript dependencies to close the remaining high-severity + advisories (dompurify, js-yaml, nanoid). + ## 3.2.7 - 2026-08-18 ### Added diff --git a/composer.lock b/composer.lock index 5ef2334b..e2a85313 100644 --- a/composer.lock +++ b/composer.lock @@ -140,16 +140,16 @@ }, { "name": "league/commonmark", - "version": "2.8.3", + "version": "2.10.0", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "1902f60f984235023acbe03db6ad614a37b3c3e7" + "reference": "d2d1aa8b35e072966c89bc0c66cf926e56767dc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/1902f60f984235023acbe03db6ad614a37b3c3e7", - "reference": "1902f60f984235023acbe03db6ad614a37b3c3e7", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d2d1aa8b35e072966c89bc0c66cf926e56767dc4", + "reference": "d2d1aa8b35e072966c89bc0c66cf926e56767dc4", "shasum": "" }, "require": { @@ -186,7 +186,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.9-dev" + "dev-main": "2.11-dev" } }, "autoload": { @@ -243,7 +243,7 @@ "type": "tidelift" } ], - "time": "2026-07-12T15:29:16+00:00" + "time": "2026-08-11T16:06:25+00:00" }, { "name": "league/config", @@ -329,16 +329,16 @@ }, { "name": "nette/schema", - "version": "v1.3.5", + "version": "v1.3.6", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002" + "reference": "c54350438cd6914616f790a49cb424605f421562" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/f0ab1a3cda782dbc5da270d28545236aa80c4002", - "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002", + "url": "https://api.github.com/repos/nette/schema/zipball/c54350438cd6914616f790a49cb424605f421562", + "reference": "c54350438cd6914616f790a49cb424605f421562", "shasum": "" }, "require": { @@ -390,9 +390,9 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.3.5" + "source": "https://github.com/nette/schema/tree/v1.3.6" }, - "time": "2026-02-23T03:47:12+00:00" + "time": "2026-08-16T21:58:41+00:00" }, { "name": "nette/utils", diff --git a/package-lock.json b/package-lock.json index bf6e670f..7dc9a817 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6970,9 +6970,9 @@ } }, "node_modules/dompurify": { - "version": "3.4.12", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.12.tgz", - "integrity": "sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==", + "version": "3.4.13", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.13.tgz", + "integrity": "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -9868,9 +9868,9 @@ "peer": true }, "node_modules/js-yaml": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", - "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", "dev": true, "funding": [ { @@ -11050,9 +11050,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.16", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", - "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", "funding": [ { "type": "github", diff --git a/package.json b/package.json index 355df169..389a7c04 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,11 @@ "node": "^24.0.0", "npm": "^11.3.0" }, + "overrides": { + "dompurify": "^3.4.13", + "js-yaml": "^4.3.1", + "nanoid": "^3.3.18" + }, "dependencies": { "@alchemyalcove/rgb-to-hsl": "^1.0.5", "@highlightjs/vue-plugin": "^2.1.0", diff --git a/tests/integration/GitHubHtml.php b/tests/integration/GitHubHtml.php index b046d182..81ee0b57 100644 --- a/tests/integration/GitHubHtml.php +++ b/tests/integration/GitHubHtml.php @@ -64,6 +64,27 @@ public static function findTwoFactorCheckupDelayForm(DOMXPath $selector): ?DOMEl ]); } + /** + * Whether this is GitHub's "Verify your two-factor authentication (2FA) settings" + * checkup page, regardless of whether a dismissable delay form is present. + * + * The page is sometimes served with only a client-rendered + * `/settings/two_factor_checkup` form carrying no named inputs, which cannot be + * posted back. Detecting it separately from the delay form lets the caller report + * that specifically instead of failing with a generic "no form found". + */ + public static function isTwoFactorCheckupPage(DOMXPath $selector, string $url = ''): bool { + if (str_contains($url, 'two_factor_checkup')) { + return true; + } + + $checkupForm = self::findForm($selector, [ + '//form[contains(@action, "two_factor_checkup")]', + ]); + + return $checkupForm !== null; + } + public static function findTotpAlternativeUrl(DOMXPath $selector): ?string { $linkSelectors = [ '//a[contains(@href, "two-factor/app")]', diff --git a/tests/integration/GithubOauthIntegrationTest.php b/tests/integration/GithubOauthIntegrationTest.php index fdf55f60..a485768a 100644 --- a/tests/integration/GithubOauthIntegrationTest.php +++ b/tests/integration/GithubOauthIntegrationTest.php @@ -249,6 +249,18 @@ private function interpretAuthenticatedResponse(string $body, string $finalUrl, ]; } + // The same checkup, but served with only a client-rendered form carrying no + // named inputs. There is nothing to post back, so it cannot be dismissed from + // here; report it distinctly so the caller can skip rather than fail on what is + // account state rather than a regression. + if (GitHubHtml::isTwoFactorCheckupPage($selector, $finalUrl)) { + return [ + 'status' => 'two_factor_checkup_blocked', + 'checkup_url' => $finalUrl, + 'body' => $body, + ]; + } + $isTwoFactorPage = GitHubHtml::findTwoFactorForm($selector) !== null || str_contains($finalUrl, 'two-factor') || str_contains($title, 'Two-factor authentication'); @@ -506,6 +518,14 @@ public function testOAuthLogin(): array { $loginResult = $this->dismissTwoFactorCheckup($loginResult['body'], $loginResult['checkup_url'] ?? $authorizeUrl); } + if ($loginResult['status'] === 'two_factor_checkup_blocked') { + $this->markTestSkipped( + 'GitHub is showing the two-factor authentication checkup page for the CI account at ' + . ($loginResult['checkup_url'] ?? $authorizeUrl) . ' and served no dismissable delay form. ' + . 'Sign in as the CI account once and complete or postpone the checkup to re-enable this test.' + ); + } + if ($loginResult['status'] === 'invalid_credentials') { $this->fail('Invalid GitHub credentials'); }