From 25e2eae0582f35469052354706c5602fdfa9d56c Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 26 Oct 2026 12:08:15 +0100 Subject: [PATCH] fix(webhooks): enforce GitHub app signatures in local environment --- app/Http/Controllers/Webhook/Github.php | 18 ++++++------- tests/Feature/Webhook/WebhookHmacTest.php | 32 +++++++++++++++++++++++ 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/app/Http/Controllers/Webhook/Github.php b/app/Http/Controllers/Webhook/Github.php index 77e2979ccf..e644a68c26 100644 --- a/app/Http/Controllers/Webhook/Github.php +++ b/app/Http/Controllers/Webhook/Github.php @@ -421,17 +421,15 @@ class Github extends Controller return response('Invalid signature.'); } $hmac = hash_hmac('sha256', $request->getContent(), $webhook_secret); - if (config('app.env') !== 'local') { - if (! hash_equals($x_hub_signature_256, $hmac)) { - auditLogWebhookFailure('github', 'invalid_signature', [ - 'mode' => 'app', - 'github_app_id' => $github_app->id, - 'github_app_name' => $github_app->name, - 'installation_target_id' => $x_github_hook_installation_target_id, - ]); + if (! hash_equals($x_hub_signature_256, $hmac)) { + auditLogWebhookFailure('github', 'invalid_signature', [ + 'mode' => 'app', + 'github_app_id' => $github_app->id, + 'github_app_name' => $github_app->name, + 'installation_target_id' => $x_github_hook_installation_target_id, + ]); - return response('Invalid signature.'); - } + return response('Invalid signature.'); } TransferFinishChecklist::recordWebhook('github_app', $github_app->uuid); if ($x_github_event === 'installation' || $x_github_event === 'installation_repositories') { diff --git a/tests/Feature/Webhook/WebhookHmacTest.php b/tests/Feature/Webhook/WebhookHmacTest.php index 7a6a3e533e..00faa9286e 100644 --- a/tests/Feature/Webhook/WebhookHmacTest.php +++ b/tests/Feature/Webhook/WebhookHmacTest.php @@ -487,6 +487,38 @@ describe('GitHub App Webhook HMAC', function () { $response->assertOk(); expect($response->getContent())->toContain('Invalid signature'); }); + + test('rejects push with forged hash when APP_ENV is local', function () { + config()->set('app.env', 'local'); + + $team = Team::factory()->create(); + GithubApp::create([ + 'uuid' => (string) str()->uuid(), + 'name' => 'github-app-webhook-local-test', + 'api_url' => 'https://api.github.com', + 'html_url' => 'https://github.com', + 'app_id' => 1234567890, + 'webhook_secret' => 'app-secret', + 'team_id' => $team->id, + ]); + + $payload = json_encode([ + 'ref' => 'refs/heads/main', + 'repository' => ['id' => 987654321], + 'after' => 'abc1234', + 'commits' => [], + ]); + + $response = $this->call('POST', '/webhooks/source/github/events', [], [], [], [ + 'HTTP_X-GitHub-Event' => 'push', + 'HTTP_X-GitHub-Hook-Installation-Target-Id' => '1234567890', + 'HTTP_X-Hub-Signature-256' => 'sha256='.hash_hmac('sha256', $payload, 'wrong-secret'), + 'CONTENT_TYPE' => 'application/json', + ], $payload); + + $response->assertOk(); + expect($response->getContent())->toContain('Invalid signature'); + }); }); describe('GitLab Manual Webhook HMAC', function () {