Skip to content

Harden OAuth redirects and MCP work-proof guidance#355

Closed
TUPM96 wants to merge 8 commits into
ramimbo:mainfrom
TUPM96:codex/bounty-315-repo-scoped-guidance
Closed

Harden OAuth redirects and MCP work-proof guidance#355
TUPM96 wants to merge 8 commits into
ramimbo:mainfrom
TUPM96:codex/bounty-315-repo-scoped-guidance

Conversation

@TUPM96
Copy link
Copy Markdown
Contributor

@TUPM96 TUPM96 commented May 26, 2026

Summary

  • scope MCP work-proof guidance lookup by repo when an issue number can exist in multiple repos
  • harden OAuth next path validation against percent-encoded redirect/header injection payloads
  • keep ledger update rowcount checks mypy-safe with typed SQLAlchemy cursor results

Refs #315

Tests

  • python -m pytest -q
  • python -m ruff format --check .
  • python -m ruff check .
  • uv run --python 3.12 mypy app

Summary by CodeRabbit

  • Security

    • Strengthened OAuth redirect validation to reject double-slash prefixes, backslashes, and percent-encoded control/header injection patterns.
  • New Features

    • Optional repo selector for work-submission lookups when using issue numbers, narrowing results to a specific repository with legacy-case fallback.
  • Tests

    • Added tests covering encoded path traversal/headerlike inputs and repository-scoped submission selection, plus expanded invalid-argument cases.

Review Change Stack

Copilot AI review requested due to automatic review settings May 26, 2026 01:37
@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 1 minute and 24 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: fb57a114-735f-47be-a64f-5e0881049003

📥 Commits

Reviewing files that changed from the base of the PR and between de8d72f and 40a7904.

📒 Files selected for processing (1)
  • tests/test_security.py
📝 Walkthrough

Walkthrough

The PR hardens OAuth redirect validation by checking both raw and URL-decoded next_path, blocking traversal and header-injection patterns. It also adds optional repo scoping to the MCP submit_work_proof tool, enforcing argument constraints and filtering issue-number lookups by repository with a legacy case-insensitive fallback.

Changes

OAuth Redirect Validation Hardening

Layer / File(s) Summary
Decoded path validation in safe_next_path
app/main.py
Imports unquote and updates _safe_next_path to validate both raw and URL-decoded forms of next_path, blocking double-slash prefixes, backslashes, and control/invalid character ranges.
OAuth path rejection test cases
tests/test_security.py
Extends test_oauth_next_path_rejects_external_or_headerlike_paths with percent-encoded attack vectors (path traversal and %0d%0a header injection) to ensure hardened validation still rejects encoded malicious inputs.

MCP submit_work_proof Repo Scoping

Layer / File(s) Summary
Repo selector argument parsing and validation
app/main.py
Introduces optional_repo_selector_arg() helper to normalize and validate an optional repo argument with bounds enforcement; integrates it into submit_work_proof and enforces that repo requires issue_number while keeping bounty_id and issue_number mutually exclusive.
Bounty lookup with repo filtering and fallback
app/main.py
Updates the issue_number bounty search to filter by exact Bounty.repo when repo is provided, and adds a legacy fallback to case-insensitive func.lower(Bounty.repo) matching when the filtered query returns no results.
MCP tool description and test coverage
app/mcp.py, tests/test_api_mcp.py
Updates submit_work_proof tool description to document optional repo scoping and text/json format support; updates imports and adds tests validating repo-scoped issue selection, legacy mixed-case repo matching, and expanded invalid-selector test cases covering repo argument combinations.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • ramimbo/mergework#287: Both PRs modify the MCP submit_work_proof flow in app/main.py and extend tests/test_api_mcp.py around issue_number behavior.
  • ramimbo/mergework#339: Both PRs modify submit_work_proof MCP handling and tests; this PR adds optional repo scoping while the other adjusts structured JSON output fields.

Suggested labels

mrwk:accepted, mrwk:paid

Suggested reviewers

  • weilixiong
  • g8rr5dg2p7-svg
  • TateLyman
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description covers the main objectives but uses informal section names and lacks the structured template format with Evidence, Test Evidence checklist, and MRWK sections. Reorganize description to match the template: add Evidence section explaining the confusion/bug addressed, convert Test Evidence to checklist format with all required checks, and ensure MRWK section follows the specified format.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the primary changes: hardening OAuth redirects and enhancing MCP work-proof guidance lookup with repository scoping.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/test_security.py (1)

851-874: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add one route-level test for doubly-encoded next payloads.

These cases only hit _safe_next_path() directly. In auth_github_login, FastAPI has already decoded one layer of the query string, so the new unquote() logic is really exercised by inputs like /%255cevil.example/me or /me%250d%250aLocation:%20https://evil.example. Please add one TestClient assertion that /auth/github/login still stores /me for those payloads.

As per coding guidelines, "tests/**/*.py: Add or update tests for changed behavior".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_security.py` around lines 851 - 874, Add a route-level TestClient
test that exercises auth_github_login (in addition to the unit tests for
_safe_next_path) by issuing a GET to /auth/github/login with a doubly-encoded
next value (e.g. "/%255cevil.example/me" and
"/me%250d%250aLocation:%20https://evil.example") and assert the app
stores/returns "/me" as the next path; place the new test alongside
test_oauth_next_path_rejects_external_or_headerlike_paths and use TestClient to
call auth_github_login and inspect the session/cookie or response where the next
value is recorded to confirm it is normalized to "/me".
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@tests/test_security.py`:
- Around line 851-874: Add a route-level TestClient test that exercises
auth_github_login (in addition to the unit tests for _safe_next_path) by issuing
a GET to /auth/github/login with a doubly-encoded next value (e.g.
"/%255cevil.example/me" and "/me%250d%250aLocation:%20https://evil.example") and
assert the app stores/returns "/me" as the next path; place the new test
alongside test_oauth_next_path_rejects_external_or_headerlike_paths and use
TestClient to call auth_github_login and inspect the session/cookie or response
where the next value is recorded to confirm it is normalized to "/me".

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 32ca0a37-97ca-4edf-add3-1327ce190100

📥 Commits

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

📒 Files selected for processing (4)
  • app/main.py
  • app/mcp.py
  • tests/test_api_mcp.py
  • tests/test_security.py

Copy link
Copy Markdown
Contributor

@s0584273828-ctrl s0584273828-ctrl left a comment

Choose a reason for hiding this comment

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

Reviewed PR #355 for the MRWK review bounty.

No blockers found on current head dc08c47078ec8b18cf7497b652b32562d21b0a52.

Evidence checked:

  • app/main.py: _safe_next_path() now rejects percent-decoded protocol-relative redirects, decoded backslashes, and decoded control/header injection payloads before returning the original local path.
  • app/main.py: submit_work_proof now accepts optional repo only with issue_number, normalizes it to lowercase, and keeps the existing ambiguity guard when multiple bounties match an unscoped issue number.
  • app/mcp.py: submit_work_proof tool description now advertises the repo-scoped issue lookup behavior.
  • tests/test_api_mcp.py: coverage proves repo scoping selects the intended bounty when issue numbers overlap across repos, rejects repo without issue_number, rejects repo with bounty_id, rejects non-string repo, and caps repo length.
  • tests/test_security.py: coverage includes encoded //, encoded backslash, and encoded CRLF OAuth next payloads.

Validation run locally:

  • uv run --extra dev python -m pytest tests/test_api_mcp.py tests/test_security.py -q -> 137 passed
  • uv run --extra dev python -m pytest -q -> 343 passed
  • uv run --extra dev python -m mypy app -> no issues in 14 source files
  • uv run --extra dev ruff format --check . -> 48 files already formatted
  • uv run --extra dev ruff check . -> all checks passed
  • git diff --check origin/main...HEAD -> clean

I do not see a release blocker.

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.

Focused follow-up review on current head 40a7904: no blockers found.

Evidence checked:

  • Inspected app/main.py, app/mcp.py, tests/test_api_mcp.py, and tests/test_security.py.
  • Verified the repo-scoped submit_work_proof path only permits repo with issue_number, rejects invalid selector combinations, handles legacy mixed-case repository rows, and keeps the ambiguous-selector guard for unscoped issue numbers.
  • Verified the encoded OAuth next hardening is covered at the route level, not just by direct _safe_next_path() calls: /auth/github/login?next=/%255cevil.example/me and the double-encoded CRLF Location: payload both store /me in the signed OAuth state.
  • Confirmed the ledger rowcount typing paths still type-check.

Validation:

  • uv run --extra dev python -m pytest tests/test_security.py::test_github_login_stores_safe_default_for_encoded_next_route tests/test_api_mcp.py::test_mcp_submit_work_proof_scopes_issue_number_by_repo tests/test_api_mcp.py::test_mcp_submit_work_proof_scopes_legacy_mixed_case_repo tests/test_api_mcp.py::test_mcp_submit_work_proof_rejects_invalid_bounty_selectors -q -> 15 passed.
  • uv run --extra dev python -m mypy app/main.py app/mcp.py app/ledger/service.py -> success.
  • uv run --extra dev python -m ruff check app/main.py app/mcp.py app/ledger/service.py tests/test_api_mcp.py tests/test_security.py -> passed.
  • uv run --extra dev python -m ruff format --check app/main.py app/mcp.py app/ledger/service.py tests/test_api_mcp.py tests/test_security.py -> 5 files already formatted.

@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 #315 and #378 filled: the structured MCP work-proof guidance rounds are closed/filled. This PR is not accepted or paid as-is. If a fresh MCP bounty opens later, rebase against main, address existing review feedback, and reference the new bounty issue.

@ramimbo
Copy link
Copy Markdown
Owner

ramimbo commented May 26, 2026

Closing this stale bounty submission. It still references a closed/filled bounty round and is dirty or unstable against current main after the route/test extraction batches. A future attempt needs a fresh PR against current main, a currently open bounty reference, and scope distinct from work already accepted.

@ramimbo ramimbo added the mrwk:rejected Submission rejected label May 26, 2026
@ramimbo ramimbo closed this May 26, 2026
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 mrwk:rejected Submission rejected

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants