Skip to content

Reject URL-encoded special characters in account path IDs (#1083)#1169

Open
yanyishuai wants to merge 1 commit into
ramimbo:mainfrom
yanyishuai:bounty-1083-account-path-special-chars
Open

Reject URL-encoded special characters in account path IDs (#1083)#1169
yanyishuai wants to merge 1 commit into
ramimbo:mainfrom
yanyishuai:bounty-1083-account-path-special-chars

Conversation

@yanyishuai

@yanyishuai yanyishuai commented Jun 28, 2026

Copy link
Copy Markdown

Related to #1083

Summary

Reject URL-encoded special characters and other malformed plain account path identifiers on public account routes. Ledger, wallet, and bounty paths already fail closed; this aligns account lookups with the same behavior.

Changes

  • normalized_account() returns HTTP 400 for identifiers outside canonical shapes (github:, mrwk1, reserve:bounty:, treasury:mrwk, or plain [A-Za-z0-9._-]+ legacy names)
  • Tests for %21%40%23%24 on JSON API and HTML routes
  • Agent guide note documenting canonical account identifier shapes

Evidence

pytest tests/test_account_validation.py::test_account_views_reject_url_encoded_special_characters -q
pytest tests/test_account_validation.py::test_account_views_accept_plain_alphanumeric_account -q
ruff check app/accounts.py tests/test_account_validation.py

Wallet: Do4v7foHJvRJLpRRoGaVPWX6DDEjX3yTK7J91gpwUQpE

Summary by CodeRabbit

  • Bug Fixes

    • Account identifiers now reject malformed plain names with invalid characters, returning a clear 400 error instead of being accepted.
    • URL path account lookups now consistently handle malformed or encoded special characters as invalid input.
  • Documentation

    • Updated account identifier guidance to clarify which identifier formats are accepted and what error is returned for invalid ones.
  • Tests

    • Added coverage for rejecting malformed account paths and accepting valid plain account identifiers.

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@yanyishuai, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 54 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

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.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e9969097-0d39-40fa-ab92-525509039f16

📥 Commits

Reviewing files that changed from the base of the PR and between a0066c3 and d412c45.

📒 Files selected for processing (2)
  • app/accounts.py
  • tests/test_account_validation.py
📝 Walkthrough

Walkthrough

app/accounts.py adds a PLAIN_ACCOUNT_RE regex and uses it in normalized_account to raise HTTPException(400) for plain identifiers with disallowed characters. Two new tests cover rejection of URL-encoded special characters and acceptance of valid alphanumeric identifiers. docs/agent-guide.md documents the accepted formats and the 400 response.

Account identifier validation hardening

Layer / File(s) Summary
PLAIN_ACCOUNT_RE constant and normalized_account enforcement
app/accounts.py
Adds PLAIN_ACCOUNT_RE (letters, digits, ., _, -) at module level and inserts a branch in normalized_account that raises HTTPException(400, "account identifier is malformed") when a plain identifier fails that regex.
Tests and documentation
tests/test_account_validation.py, docs/agent-guide.md
Parametrized test asserts HTTP 400 for URL-encoded special characters on both /api/v1/accounts and /accounts routes; second test asserts HTTP 200 for plain-account. Docs specify canonical identifier shapes and the required 400 behavior.

Possibly related issues

Possibly related PRs

  • ramimbo/mergework#388: Introduced the normalized_account function in app/accounts.py that this PR extends with regex validation.
  • ramimbo/mergework#750: Also modifies normalized_account to reject malformed account path inputs and extends tests/test_account_validation.py.
🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Title check ✅ Passed The title is short, concrete, and accurately names the changed account-path validation surface.
Description check ✅ Passed The description covers the change, rationale, routes, and test evidence, with only minor template omissions.
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.
Mergework Public Artifact Hygiene ✅ Passed Public-facing edits are hygiene-safe: docs avoid price/off-ramp claims, and the PR description only states account-validation behavior.
Bounty Pr Focus ✅ Passed PASS: The PR stays within account-path validation/docs/tests; no Bounty/Refs reference or unrelated surfaces were found.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ebeb3e7e-be49-4617-91cf-235031bc3a18

📥 Commits

Reviewing files that changed from the base of the PR and between 3bc87d2 and a0066c3.

📒 Files selected for processing (3)
  • app/accounts.py
  • docs/agent-guide.md
  • tests/test_account_validation.py

Comment thread docs/agent-guide.md Outdated
Comment on lines +173 to +175
URL-encoded special characters (for example `%21%40%23%24`) and other malformed
path IDs return HTTP 400 with `{"detail":"account identifier is malformed"}` on
both `/api/v1/accounts/{account}` and `/accounts/{account}`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Narrow the generic 400 detail to malformed plain identifiers.

Line 173 currently reads as if all malformed account path IDs return {"detail":"account identifier is malformed"}, but normalized_account() still emits prefix-specific 400 details for invalid github:, reserve:, and treasury: identifiers. Reword this to cover malformed plain legacy names or percent-decoded special characters only, so the guide matches the actual API contract. As per path instructions, "Review docs for short, concrete prose" and coding guidelines, "Update examples when API, label, or ledger behavior changes."

Suggested wording
- URL-encoded special characters (for example `%21%40%23%24`) and other malformed
- path IDs return HTTP 400 with `{"detail":"account identifier is malformed"}` on
- both `/api/v1/accounts/{account}` and `/accounts/{account}`.
+ URL-encoded special characters (for example `%21%40%23%24`) and malformed plain
+ legacy names return HTTP 400 with `{"detail":"account identifier is malformed"}`
+ on both `/api/v1/accounts/{account}` and `/accounts/{account}`.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
URL-encoded special characters (for example `%21%40%23%24`) and other malformed
path IDs return HTTP 400 with `{"detail":"account identifier is malformed"}` on
both `/api/v1/accounts/{account}` and `/accounts/{account}`.
URL-encoded special characters (for example `%21%40%23%24`) and malformed plain
legacy names return HTTP 400 with `{"detail":"account identifier is malformed"}`
on both `/api/v1/accounts/{account}` and `/accounts/{account}`.

Sources: Coding guidelines, Path instructions

Comment on lines +362 to +368
def test_account_views_accept_plain_alphanumeric_account(sqlite_url: str) -> None:
client = _setup_app(sqlite_url)

resp = client.get("/api/v1/accounts/plain-account")

assert resp.status_code == 200
assert resp.json()["account"] == "plain-account"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add one positive case for . or _ in legacy names.

Line 362 only proves hyphenated names, but the new contract and docs allow plain legacy identifiers with . and _ too. Add at least one accepted case such as plain.name_1 so this test covers the full advertised character set. As per path instructions, "Focus on whether tests prove the changed behavior and include negative, replay, boundary, or regression cases where relevant," and coding guidelines, "Add or update tests for changed behavior."

Suggested test shape
- def test_account_views_accept_plain_alphanumeric_account(sqlite_url: str) -> None:
+ `@pytest.mark.parametrize`("account", ["plain-account", "plain.name_1"])
+ def test_account_views_accept_plain_legacy_account(sqlite_url: str, account: str) -> None:
      client = _setup_app(sqlite_url)
 
-     resp = client.get("/api/v1/accounts/plain-account")
+     resp = client.get(f"/api/v1/accounts/{account}")
 
      assert resp.status_code == 200
-     assert resp.json()["account"] == "plain-account"
+     assert resp.json()["account"] == account
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_account_views_accept_plain_alphanumeric_account(sqlite_url: str) -> None:
client = _setup_app(sqlite_url)
resp = client.get("/api/v1/accounts/plain-account")
assert resp.status_code == 200
assert resp.json()["account"] == "plain-account"
`@pytest.mark.parametrize`("account", ["plain-account", "plain.name_1"])
def test_account_views_accept_plain_legacy_account(sqlite_url: str, account: str) -> None:
client = _setup_app(sqlite_url)
resp = client.get(f"/api/v1/accounts/{account}")
assert resp.status_code == 200
assert resp.json()["account"] == account

Sources: Coding guidelines, Path instructions

@qingfeng312 qingfeng312 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I found one blocking issue before this is merge-ready.

The validation change itself is focused and the route coverage is useful: normalized_account() now rejects malformed plain account identifiers, while the existing github:, mrwk1, reserve:bounty:, and treasury:mrwk paths remain routed through their stricter branches.

Blocking issue:

CI is failing on formatting. The current head a0066c3b5c7c91d8c0bf2c7ff2fff9c708ec990d passes the full pytest run (910 passed, 1 warning), but ruff format --check . reports:

Would reformat: tests/test_account_validation.py
1 file would be reformatted, 125 files already formatted

Please run the formatter on tests/test_account_validation.py and push the resulting diff. After that, this should be a small re-check because the functional tests are already green.

Validation:

  • Inspected PR #1169 at head a0066c3b5c7c91d8c0bf2c7ff2fff9c708ec990d.
  • Reviewed changed files: app/accounts.py, docs/agent-guide.md, tests/test_account_validation.py.
  • Checked the failed CI log for run 28324649863.
  • Confirmed the failure is formatter-only, not a test failure.

Refs #1009

@yanyishuai yanyishuai force-pushed the bounty-1083-account-path-special-chars branch from 12b63fd to d412c45 Compare July 1, 2026 02:52

@qingfeng312 qingfeng312 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Reviewed current head d412c454e4ec71a362e3470abb1759d144d228d8.

Approved. The current head adds a focused account-identifier guard for decoded special characters and covers both API and HTML account routes with regression tests. Plain alphanumeric account identifiers remain accepted, and the hosted quality check is green. I do not see a remaining blocker in this diff.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants