Repository navigation
Refuse a trailing newline with fullmatch in the control plane's validators and both UI relays - #243
Merged
Merged
Conversation
…match ## What `re.match` with a `$`-anchored pattern accepts one trailing "\n". The egress label check applies `_LABEL_RE` per label after splitting on ".", and `_normalize_pattern` strips only the ends of the whole pattern, so `example\n.com`, `a.b\n.com` and `.example\n.com` passed and were stored as allows or blocks that match nothing. Task 37, plan 13. - **`policy._rule_error`'s label check** uses `fullmatch`: the task's bug, live through `create_rule` and `edit_rule`. - **The module's sibling validators** do too: `_server_name_error`, `_auth_descriptor_error`'s header check and `_tool_rule_error`. Their routes strip first, so those paths were latent. The one path that does not strip first is `inventory.record`, which checks the gateway push's server keys as sent. Its only caller pushes roster names stripped and validated at registration, so a key like `mcp-github\n` could only come from a gateway bug, and would be stored as an entry of its own. - **`inventory._clean_tools`** uses `fullmatch` for consistency only. It strips just before the check, so no behaviour changes and no test can show it. The regex text is unchanged, as with `_PATH_RE` and `_PIN_FIELD_RE`, so the gateway's copies of the server and tool patterns need no change. ## Checked In the container: `make lint`, `make consistency` and `make test` pass. New tests: `_rule_error` refuses a newline ending a non-final label for allow and block, `create_rule` answers 400 and writes no row, `edit_rule` answers 400 and changes nothing; each sibling validator refuses a valid value with "\n" appended; an inventory push keyed `ok-one\n` is skipped. Mutation: each of the four `policy.py` sites reverted to `.match` on its own, against its targeted tests, all killed: - the label check fails the `PatternValidationTests` (6 subtests), `CreateRuleTests` and `EditRuleTests` cases; - the server name check fails its validator test and the inventory test; - the header check and the tool check each fail their validator test. Not run: anything needing Docker. Nothing here touches compose, images or the network. Co-Authored-By: Claude <noreply@anthropic.com>
## What
`_relay_allowed` in control-plane-ui/app.py and tasks-ui/app.py matched
its `$`-anchored routes with `re.match`, which accepts one trailing
"\n". Both now use `fullmatch`; the route table's text is unchanged.
Task 37, plan 13, second of two commits.
This changes no behaviour on the live stack today. Starlette's
`{path:path}` catch-all already drops one trailing newline before
`proxy` sees it, so a `%0A` path was, and still is, relayed as the clean
path. The change makes the allowlist mean exactly what it spells,
independent of the router: defence in depth.
## Checked
In the container: `make lint`, `make consistency` and `make test` pass.
New tests: an allowed path with a trailing "\n" is refused 403 and
nothing is sent, in `RelayAllowlistTests` (`api/egress/rules`) and in
tasks-ui's `RelayTests` (`api/repos`, a new method rather than an entry
in the path tuples).
The tests call `proxy` as a function, not through a request, so they
show the allowlist, not the router.
Mutation: each relay reverted to `.match` on its own fails its new test.
The router behaviour was checked by the pre-push review on real FastAPI
in a venv (fastapi 0.141.1, starlette 1.10.0, TestClient): at main and
at this commit, `/api/egress/rules%0A` and tasks-ui's `/api/repos%0A`
are relayed as the clean path, and a newline anywhere else is the
router's 404. The suite does not check it.
Not run: anything needing Docker.
Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
re.matchwith a$-anchored pattern accepts one trailing "\n". The egress label check applies_LABEL_REper label after splitting on ".", and_normalize_patternstrips only the ends of the whole pattern, soexample\n.com,a.b\n.comand.example\n.compassed and were stored as allows or blocks that match nothing. Task 37, plan 13. Two commits, kept apart so each can be judged and reverted on its own.Control plane (4a12f02)
policy._rule_error's label check usesfullmatch: the task's bug, live throughcreate_ruleandedit_rule._server_name_error,_auth_descriptor_error's header check and_tool_rule_error. Their routes strip first, so those paths were latent. The one path that does not strip first isinventory.record, which checks the gateway push's server keys as sent. Its only caller pushes roster names stripped and validated at registration, so a key likemcp-github\ncould only come from a gateway bug, and would be stored as an entry of its own.inventory._clean_toolsusesfullmatchfor consistency only. It strips just before the check, so no behaviour changes and no test can show it.The regex text is unchanged, as with
_PATH_REand_PIN_FIELD_RE, so the gateway's copies of the server and tool patterns need no change.UI relays (150cce9)
_relay_allowedin control-plane-ui/app.py and tasks-ui/app.py matched its$-anchored routes withre.match. Both now usefullmatch; the route table's text is unchanged.This changes no behaviour on the live stack today. Starlette's
{path:path}catch-all already drops one trailing newline beforeproxysees it, so a%0Apath was, and still is, relayed as the clean path. The change makes the allowlist mean exactly what it spells, independent of the router: defence in depth.Checked
In the container:
make lint,make consistencyandmake testpass.New tests:
_rule_errorrefuses a newline ending a non-final label for allow and block;create_ruleanswers 400 and writes no row;edit_ruleanswers 400 and changes nothing.ok-one\nis skipped.RelayAllowlistTests(api/egress/rules) and in tasks-ui'sRelayTests(api/repos). These callproxyas a function, not through a request, so they show the allowlist, not the router.Mutation: each of the six tested sites reverted to
.matchon its own, all killed:PatternValidationTests(6 subtests),CreateRuleTestsandEditRuleTestscases;The pre-push review reran all of these and checked the router on real FastAPI in a venv (fastapi 0.141.1, starlette 1.10.0, TestClient): at main and at this branch,
/api/egress/rules%0Aand tasks-ui's/api/repos%0Aare relayed as the clean path, and a newline anywhere else is the router's 404. The suite does not check it.Not run: anything needing Docker. Nothing here touches compose, images or the network.
Generated with Claude Code