Skip to content

Extract GitHub webhook route from app.main#368

Open
TUPM96 wants to merge 1 commit into
ramimbo:mainfrom
TUPM96:codex/bounty-320-webhook-route-clean
Open

Extract GitHub webhook route from app.main#368
TUPM96 wants to merge 1 commit into
ramimbo:mainfrom
TUPM96:codex/bounty-320-webhook-route-clean

Conversation

@TUPM96
Copy link
Copy Markdown
Contributor

@TUPM96 TUPM96 commented May 26, 2026

Refs #320

Summary

  • Move the /webhooks/github route registration, GitHub header normalization, and webhook status-code mapping into app.webhooks.routes.
  • Keep app.main responsible only for app wiring by registering the webhook route with database URL, webhook secret, and accepted labeler settings.
  • Add route-level regression tests that verify GitHub headers are normalized through FastAPI and bad signatures still return 401.
  • Preserve the existing OAuth encoded-backslash next-path safety expectation required by the test suite.

Complexity reduced

  • app.main no longer owns GitHub webhook request-body/header handling or HTTP response mapping. Webhook transport wiring now lives beside the webhook domain handler, making webhook behavior easier to review independently from page, MCP, ledger, and admin routes.

Tests

  • uv run --extra dev python -m pytest tests/test_webhook_routes.py tests/test_wallet_api.py::test_github_login_stores_safe_default_for_backslash_next -q -> 3 passed.
  • uv run --extra dev python -m pytest -q -> 337 passed.
  • uv run --extra dev python -m mypy app -> passed.
  • uv run --extra dev ruff check app/main.py app/webhooks/routes.py tests/test_webhook_routes.py -> passed.
  • uv run --extra dev ruff format --check app/main.py app/webhooks/routes.py tests/test_webhook_routes.py -> passed.
  • uv run --extra dev python scripts/docs_smoke.py -> docs smoke ok.
  • git diff --check HEAD~1..HEAD -> clean.
  • Hosted Quality, readiness, docs, and image checks -> passed.
  • CodeRabbit -> passed.

No private keys, seed material, secrets, deployment credentials, private vulnerability details, payout credentials, or MRWK price claims are included.

Copilot AI review requested due to automatic review settings May 26, 2026 02:14
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 26, 2026

Warning

Review limit reached

@TUPM96, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 3 minutes and 21 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7e899e80-5f9b-4b42-bd13-404fab84caf7

📥 Commits

Reviewing files that changed from the base of the PR and between ac99d79 and 1073cc4.

📒 Files selected for processing (3)
  • app/main.py
  • app/webhooks/routes.py
  • tests/test_webhook_routes.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR refactors the GitHub webhook endpoint into a dedicated routes module, adds tests for webhook behavior, and hardens OAuth redirect “next” path validation against encoded bypasses.

Changes:

  • Extracted GitHub webhook route registration + header normalization into app/webhooks/routes.py.
  • Updated create_app to use the new route registration helper.
  • Added tests covering success/ignored behavior and bad-signature unauthorized responses.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
tests/test_webhook_routes.py Adds integration tests for the GitHub webhook endpoint responses and persistence.
app/webhooks/routes.py Introduces a reusable GitHub webhook route registrar + header normalization utilities.
app/main.py Switches to the new webhook route registrar and tightens _safe_next_path validation using URL-decoding.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +26 to +28
"X-GitHub-Delivery": "delivery-route-ignored",
"X-GitHub-Event": "issues",
"X-Hub-Signature-256": _signature("secret", body),
Comment thread app/main.py
Comment on lines 280 to 294
def _safe_next_path(next_path: str | None) -> str:
decoded_next_path = unquote(next_path) if next_path else ""
if (
not next_path
or not next_path.startswith("/")
or next_path.startswith("//")
or len(next_path) > 2048
or "\\" in next_path
or decoded_next_path.startswith("//")
or "\\" in decoded_next_path
or any(ord(char) < 32 or 127 <= ord(char) < 160 for char in next_path)
or any(ord(char) < 32 or 127 <= ord(char) < 160 for char in decoded_next_path)
):
return "/me"
return next_path
Copy link
Copy Markdown
Contributor

@prettyboyvic prettyboyvic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blockers from my review.

Evidence checked:

  • Inspected the diff from origin/main to 1073cc4: app/main.py, app/webhooks/routes.py, and tests/test_webhook_routes.py.
  • Verified /webhooks/github is still registered from create_app, reads the raw request body, preserves the existing handle_github_webhook() domain handler, and keeps the same unauthorized -> HTTP 401 mapping.
  • Checked github_webhook_headers() with mixed/lowercase header input; it returns the canonical keys expected by handle_github_webhook().
  • Checked _safe_next_path() rejects protocol-relative paths plus encoded slash/backslash ambiguity while preserving normal in-app paths like /bounties?status=open.
  • Confirmed the cleaned successor PR no longer includes the app/ledger/service.py CursorResult typing removal that made superseded PR #367 fail mypy app.

Validation run on PR head 1073cc4:

  • uv run --extra dev python -m pytest tests/test_webhook_routes.py tests/test_webhooks.py::test_github_signature_verification tests/test_webhooks.py::test_accepted_label_pays_bounty_once tests/test_wallet_api.py::test_github_login_stores_safe_default_for_backslash_next -q -> 5 passed.
  • uv run --extra dev ruff check app/main.py app/webhooks/routes.py tests/test_webhook_routes.py -> passed.
  • uv run --extra dev ruff format --check app/main.py app/webhooks/routes.py tests/test_webhook_routes.py -> 3 files already formatted.
  • uv run --extra dev python -m mypy app -> success.
  • uv run --extra dev python -m pytest -q -> 337 passed.

No secrets, wallet private keys, payout credentials, private vulnerability details, deployment values, or MRWK price claims were reviewed or disclosed.

Copy link
Copy Markdown

@CharlieLZ CharlieLZ left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blockers found on current head 1073cc4.

Evidence summary:

  • Inspected app/webhooks/routes.py, app/main.py, and tests/test_webhook_routes.py.
  • Verified the extracted /webhooks/github route still reads the raw body before dispatch, maps request headers back to the canonical names expected by handle_github_webhook, preserves the 401 response for failed signatures, and passes settings.github_accepted_labelers through to the webhook handler.
  • Verified the route extraction keeps signature verification in handle_github_webhook before payload parsing or event recording.
  • Checked the adjacent _safe_next_path decoded-path hardening against existing OAuth redirect ambiguity coverage.

Validation:

  • /Users/charliesimmon/tmp/bounty-work/mergework/.venv/bin/python -m pytest tests/test_webhook_routes.py tests/test_webhooks.py tests/test_security.py tests/test_wallet_api.py -q -> 103 passed.
  • /Users/charliesimmon/tmp/bounty-work/mergework/.venv/bin/python -m ruff check app/main.py app/webhooks/routes.py tests/test_webhook_routes.py -> passed.
  • /Users/charliesimmon/tmp/bounty-work/mergework/.venv/bin/python -m ruff format --check app/main.py app/webhooks/routes.py tests/test_webhook_routes.py -> 3 files already formatted.
  • /Users/charliesimmon/tmp/bounty-work/mergework/.venv/bin/python -m mypy app/main.py app/webhooks/routes.py app/webhooks/github.py -> success.
  • git diff --check origin/main...HEAD -> clean.
  • Hosted checks are green: CodeRabbit pass and Quality/readiness/docs/image checks pass.

Copy link
Copy Markdown
Contributor

@newmattock newmattock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blockers found on current head 1073cc4.

Evidence checked:

  • Inspected app/webhooks/routes.py, the route registration call in app/main.py, tests/test_webhook_routes.py, and the existing domain handler in app/webhooks/github.py.
  • Verified /webhooks/github still reads the raw request body before dispatch, normalizes GitHub delivery/event/signature headers back to the canonical names expected by handle_github_webhook(), and passes through the configured database URL, webhook secret, and accepted labeler tuple.
  • Verified route status mapping preserves unauthorized as HTTP 401 while other webhook handler results return HTTP 200.
  • Rechecked the branch's decoded OAuth next hardening via the existing wallet regression.

Validation run locally:

  • uv run --extra dev python -m pytest tests/test_webhook_routes.py tests/test_webhooks.py::test_accepted_issue_event_for_pull_request_does_not_pay_matching_bounty tests/test_webhooks.py::test_webhook_rejects_bad_signature tests/test_wallet_api.py::test_github_login_stores_safe_default_for_backslash_next -q -> 5 passed.
  • uv run --extra dev python -m mypy app/webhooks/routes.py app/main.py -> success.
  • uv run --extra dev python -m ruff check app/webhooks/routes.py app/main.py tests/test_webhook_routes.py -> passed.
  • uv run --extra dev python -m ruff format --check app/webhooks/routes.py app/main.py tests/test_webhook_routes.py -> 3 files already formatted.
  • git diff --check origin/main...HEAD -> clean.

@ramimbo ramimbo added the mrwk:needs-info More information needed label May 26, 2026
@ramimbo
Copy link
Copy Markdown
Owner

ramimbo commented May 26, 2026

Maintainer note after #320 filled: this PR still references closed bounty #320. If you want it considered for the current code-health round, rebase against main, update the bounty reference to #377, and keep the scope distinct from already accepted #328, #337, #364, #375, and #376. This is not accepted or paid as-is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mrwk:needs-info More information needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants