Skip to content

Commit 7273aa4

Browse files
authored
webhook: fix non-matching webhook secrets not being rejected (#601)
Fixes an issue which allowed webhooks to be processed with an incorrect webhook secret. This adds a case to return a `401` when the `verify` function returns false. The `verify` function also always returns false as it is being passed in JSON from a `WorkflowJob` webhook object instead of the request body (which is how GitHub generates it's signitures) meaning the signatures will never match (see https://github.com/yanyongyu/githubkit?tab=readme-ov-file#webhook-verification).
1 parent 74ea1ac commit 7273aa4

File tree

2 files changed

+25
-4
lines changed

2 files changed

+25
-4
lines changed

runner_manager/routers/webhook.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Annotated, Set
22

3-
from fastapi import APIRouter, Depends, Header, HTTPException, Security
3+
from fastapi import APIRouter, Depends, Header, HTTPException, Request, Security
44
from githubkit.versions.latest.models import WebhookPing
55
from githubkit.webhooks import verify
66
from rq import Queue
@@ -18,21 +18,30 @@
1818
}
1919

2020

21-
def validate_webhook(
22-
webhook: AcceptedWebhookEvents,
21+
async def validate_webhook(
22+
request: Request,
2323
settings: Settings = Depends(get_settings),
2424
x_hub_signature_256: Annotated[str | None, Header()] = None,
2525
) -> bool:
2626

27+
body = await request.body()
28+
2729
if settings.github_webhook_secret is None:
2830
return True
2931
elif x_hub_signature_256 is None:
3032
raise HTTPException(status_code=401, detail="Missing signature")
3133
valid: bool = verify(
3234
settings.github_webhook_secret.get_secret_value(),
33-
webhook.json(exclude_unset=True),
35+
body,
3436
x_hub_signature_256,
3537
)
38+
39+
if not valid:
40+
raise HTTPException(
41+
status_code=401,
42+
detail="Signature values do not match - check webhook secret value",
43+
)
44+
3645
return valid
3746

3847

@@ -43,6 +52,7 @@ def post(
4352
valid: bool = Security(validate_webhook),
4453
queue: Queue = Depends(get_queue),
4554
) -> WebhookResponse:
55+
4656
if not isinstance(webhook, WebhookPing):
4757
action = webhook.action
4858
else:

tests/api/test_webhook_router.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,11 @@ def test_workflow_job_hypothesis(workflow_job: WorkflowJobCompleted):
4141
@given(workflow_job=WorkflowJobCompletedStrategy)
4242
def test_webhook_authentication(workflow_job, client, authentified_app):
4343
data = workflow_job.json(exclude_unset=True)
44+
4445
# First request without authentication
4546
response = client.post("/webhook/", content=data)
4647
assert response.status_code == 401
48+
4749
# Second request with authentication
4850
signature = sign("secret", data, method="sha256")
4951
response = client.post(
@@ -53,6 +55,15 @@ def test_webhook_authentication(workflow_job, client, authentified_app):
5355
)
5456
assert response.status_code == 200
5557

58+
# Third request with incorrect authentication
59+
signature = sign("wrong_secret", data, method="sha256")
60+
response = client.post(
61+
"/webhook/",
62+
content=data,
63+
headers={"X-Hub-Signature-256": signature, "X-GitHub-Event": "workflow_job"},
64+
)
65+
assert response.status_code == 401
66+
5667

5768
@given(ping=PingStrategy)
5869
def test_ping_event(ping, client):

0 commit comments

Comments
 (0)