From 0fcba7d3a70a4545aa1c4d7fcb3a1ecf4ea96132 Mon Sep 17 00:00:00 2001 From: Faye Date: Wed, 8 Apr 2026 16:24:37 +0200 Subject: [PATCH 1/2] fix: adjust expiration time for token generation GitHub recently tightened JWT validation for GitHub App authentication as the `exp` claim must now be strictly less than 10 minutes from `iat` --- src/VCS/Adapter/Git/GitHub.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VCS/Adapter/Git/GitHub.php b/src/VCS/Adapter/Git/GitHub.php index b2ae68e0..06fa6b9f 100644 --- a/src/VCS/Adapter/Git/GitHub.php +++ b/src/VCS/Adapter/Git/GitHub.php @@ -600,7 +600,7 @@ protected function generateAccessToken(string $privateKey, ?string $appId): void $appIdentifier = $appId; $iat = time(); - $exp = $iat + 10 * 60; + $exp = $iat + 9 * 60; $payload = [ 'iat' => $iat, 'exp' => $exp, From cb3fba5cf6792e8468418f85aa9cba3d2aa3fd74 Mon Sep 17 00:00:00 2001 From: Faye Date: Wed, 8 Apr 2026 17:55:46 +0200 Subject: [PATCH 2/2] fix: use GITHUB_APP_JWT_EXPIRY instead of hardcoding values --- src/VCS/Adapter/Git/GitHub.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/VCS/Adapter/Git/GitHub.php b/src/VCS/Adapter/Git/GitHub.php index 06fa6b9f..56bf657d 100644 --- a/src/VCS/Adapter/Git/GitHub.php +++ b/src/VCS/Adapter/Git/GitHub.php @@ -21,6 +21,15 @@ class GitHub extends Git public const CONTENTS_FILE = 'file'; + /** + * GitHub App JWT expiry in seconds. GitHub allows a maximum of 10 minutes; + * we use 9 minutes to leave a 1-minute safety margin for clock drift. + * + * @see https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-json-web-token-jwt-for-a-github-app + * "The time must be no more than 10 minutes into the future." + */ + public const GITHUB_APP_JWT_EXPIRY = 60 * 9; + protected string $endpoint = 'https://api.github.com'; protected string $accessToken; @@ -60,7 +69,8 @@ public function initializeVariables(string $installationId, string $privateKey, { $this->installationId = $installationId; - $response = $this->cache->load($installationId, 60 * 9); // 10 minutes, but 1 minute earlier to be safe + // Cache for 1 minute less than the JWT expiry so we refresh before the token actually expires. + $response = $this->cache->load($installationId, self::GITHUB_APP_JWT_EXPIRY - 60); if ($response == false) { $this->generateAccessToken($privateKey, $appId); @@ -600,7 +610,7 @@ protected function generateAccessToken(string $privateKey, ?string $appId): void $appIdentifier = $appId; $iat = time(); - $exp = $iat + 9 * 60; + $exp = $iat + self::GITHUB_APP_JWT_EXPIRY; $payload = [ 'iat' => $iat, 'exp' => $exp,