Skip to content

feat(http): HMAC-verified GitHub webhook route (3/5) - #14

Merged
TOMOKI977 merged 3 commits into
mainfrom
feat/github-alerts-route
Sep 24, 2026
Merged

TOMOKI977 merged 3 commits into
mainfrom
feat/github-alerts-route

Conversation

@TOMOKI977

Copy link
Copy Markdown
Contributor

Summary

Third PR of the github-alerts chain. It adds POST /github/webhook with signature verification. Event mapping, routing and Telegram delivery arrive in PR 4. Until then, verified events are acknowledged, logged and dropped.

  • Signature: verifyGithubSignature computes HMAC-SHA256 over the raw body bytes and compares it with X-Hub-Signature-256 in constant time. The JSON is parsed only after the signature passes.
  • Fail closed: this deploys before the operator sets GITHUB_WEBHOOK_SECRET. If the secret is unset or empty, every request gets a 500 and a ConfigError reason in the log. The Telegram route and /health are unaffected, and there is a test for that.
  • Shared comparison: the Telegram secret check and the GitHub signature check now use a single timingSafeCompare helper (length check, then timingSafeEqual).

Status policy

Case Response
Missing or wrong signature 401
Secret not configured, or unreadable body (transient, before auth) 500, logged
ping 200
Malformed JSON or non-object payload 200, logged
Any other verified event (until PR 4) 200, logged as ignored:not-yet-routed

The payload, signature and secret are never logged. A test asserts this against the actual log output.

Size exception: about 670 lines. Production code is about 140 of them; the rest is tests and apply-progress notes.

Review

Full review with four lenses, since this is an authentication path. There were no blocker or critical findings. The risk and reliability lenses found nothing. The warnings were fixed:

  • non-routed events were not logged;
  • the body read was unguarded;
  • the constant-time comparison was duplicated;
  • a test helper was duplicated.

The scoped fix-delta validator escalated one defect caused by that correction: an unreadable body was answered 200 before authentication. A failed read is a transient transport failure, not malformed content, so the design's policy calls for a 500 that GitHub shows as failed and lets you redeliver. The maintainer authorized a second correction: the test was changed to expect 500 and failed on the old code, then the route was fixed.

Test plan

  • npx vitest run: 287/287 pass
  • npx tsc --noEmit: no errors

After merge

Operator step (tasks.md 6.1): npx wrangler secret put GITHUB_WEBHOOK_SECRET.

Extracts the length-checked timingSafeEqual comparison into timingSafeCompare so the Telegram secret check and the GitHub signature check use one implementation.
POST /github/webhook verifies X-Hub-Signature-256 over the raw body before parsing. It fails closed with 500 when GITHUB_WEBHOOK_SECRET is unset or the body cannot be read, returns 401 on a bad signature, and acknowledges ping, malformed payloads and not-yet-routed events with a logged 200.
@TOMOKI977 TOMOKI977 added the size:exception PR over the 400-line review budget, accepted explicitly label Sep 24, 2026
@TOMOKI977
TOMOKI977 merged commit 76a5daa into main Sep 24, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:exception PR over the 400-line review budget, accepted explicitly

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant