fix: reject trailing newline in tool-name and URI-template varname validation (#3076)

Python's $ with re.match also matches just before a single trailing newline, so tool-name validation accepted "name\n" and UriTemplate.parse accepted varnames like "foo\n". Switch both checks to re.fullmatch.

Closes #3084
This commit is contained in:
Otis Cui 2026-07-10 20:56:54 +08:00 • committed by GitHub
commit 1216c53693
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 9 additions and 2 deletions

View file

@ -77,7 +77,7 @@ def validate_tool_name(name: str) -> ToolNameValidationResult:
warnings.append("Tool name starts or ends with a dot, which may cause parsing issues in some contexts")
# Check for invalid characters
if not TOOL_NAME_REGEX.match(name):
if not TOOL_NAME_REGEX.fullmatch(name):
# Find all invalid characters (unique, preserving order)
invalid_chars: list[str] = []
seen: set[str] = set()

View file

@ -813,7 +813,7 @@ def _parse_expression(template: str, body: str, pos: int) -> _Expression:
explode = spec.endswith("*")
name = spec[:-1] if explode else spec
if not _VARNAME_RE.match(name):
if not _VARNAME_RE.fullmatch(name):
raise InvalidUriTemplate(
f"Invalid variable name {name!r} at position {pos}",
template=template,

View file

@ -65,12 +65,17 @@ def test_validate_tool_name_rejects_name_exceeding_max_length() -> None:
("get,user,profile", "','"),
("user/profile/update", "'/'"),
("user@domain.com", "'@'"),
# a single trailing newline slipped past `$` with re.match
("valid_name\n", "'\\n'"),
("a" * 127 + "\n", "'\\n'"),
],
ids=[
"with_spaces",
"with_commas",
"with_slashes",
"with_at_symbol",
"with_trailing_newline",
"max_length_with_trailing_newline",
],
)
def test_validate_tool_name_rejects_invalid_characters(tool_name: str, expected_char: str) -> None:

View file

@ -144,6 +144,8 @@ def test_parse_rejects_operator_without_variable():
# RFC §2.3: dots only between varchars, not consecutive or trailing
"foo..bar",
"foo.",
# a single trailing newline slipped past `$` with re.match
"foo\n",
],
)
def test_parse_rejects_invalid_varname(name: str):