Repository navigation
Conversation
ValidateName walks the name byte by byte, and reported the offending character as string(name[i]). Converting a byte to a string yields the code point of the same value, so the first byte of "語" (0xE8) was rendered as "è": a caller asking for a Japanese script name got an HTTP 400 INVALID_SCRIPT_NAME telling it to remove a character that is not in the name it sent. A byte that begins no valid rune is not a character at all and is now reported as a byte. The byte-wise loop is the confinement boundary and is unchanged; only the wording of the refusal moves. truncateForMessage, which bounds the echoed name at MaxNameLen+16, also cut at a raw byte offset, so a long non-ASCII name left a partial rune that %q renders as \xNN escapes — the refusal ended in hex noise instead of the name it quotes. It now goes through stringutil.UTF8Prefix (smart-mcp-proxy#1647, smart-mcp-proxy#1664, smart-mcp-proxy#1665). ASCII refusals are byte-for-byte unchanged.
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Dumbris
left a comment
There was a problem hiding this comment.
Verified on a merge with main: build, CI lint config, race tests; cross-reviewed clean.
|
Merged. Thank you, @rudycelekli! The refusal now names the character that is actually in the script name instead of the first byte of it, which is exactly the detail someone hits first when a name is non-ASCII. Contributions like this are what keep MCPProxy moving, and they matter a lot to the project. One small ask, only if you feel like it: a short public note about your change (a tweet, a LinkedIn post, a line in a community you are part of) helps more people discover MCPProxy. No pressure at all, and I would be glad to repost it. |
Pull Request
Description
The refusal names a character that is not in the name
ValidateNamewalks the name byte by byte — correctly, that loop is the confinement boundary — but reported the offending character asstring(name[i]). Converting a byte to a string yields the code point of the same value, so the first byte of語(0xE8) was rendered asè:A caller is told to remove a character it never sent. A byte that begins no valid rune is not a character at all, and is now reported as one:
Reachability.
codescripts.Resolve/ResolveScopedcallValidateNamebefore any filesystem access (codescripts.go:394), andinternal/httpapi/code_exec.go:244maps*InvalidNameErrorstraight to an HTTP 400INVALID_SCRIPT_NAMEwhose message body isinvalidName.Error(). So anycode_executioncall naming a non-ASCII script — i.e. the exact case the validator is there to refuse — returns this message to the agent. The input is one the caller chooses freely; nothing upstream narrows it to ASCII first.Note the order of the checks: the byte-length check fires first, so the mojibake path needs a short non-ASCII name (
語is 3 bytes, under the 64 limit) and the echo path below needs a long one. Both are ordinary requests.The echoed name is cut inside a rune
truncateForMessagebounds the echoed name atMaxNameLen+16bytes with a raw byte slice. For a long non-ASCII name the cut lands inside a rune and%qthen renders the dangling bytes as\xNNescapes, so the refusal ends in hex noise instead of the name it is quoting. It now goes throughstringutil.UTF8Prefix, the helper added in #1664 (see also #1647, #1665, and #1726 / #1727 which finish that sweep elsewhere).The byte-wise validation loop, the confinement property and every ASCII refusal are unchanged.
One thing I did not change
len(name) > MaxNameLenis a byte check, whileMaxNameLenis documented and reported as a character count ("names are 1-64 characters"). They agree for every name the token can accept, since a valid name is ASCII — but a 30-character Japanese name is refused as "name is 90 characters long", which reads as a contradiction against a 64-character limit. Reporting the rune count instead makes it worse ("name is 30 characters long" against a 64-character limit), because the real divergence is in the check, not the message. That is a product decision about whether the limit is bytes or characters, so I left it exactly as it is rather than guess. Happy to follow up either way.Testing
New tests in
internal/codescripts/invalid_name_message_test.go:TestInvalidNameErrorNamesTheRealCharacter語and does not containèTestInvalidNameErrorReportsMalformedByte0x80is reported asbyte 0x80TestInvalidNameErrorEchoIsNotCutInsideARune\xescape survives in the echoTestValidateNameASCIIMessagesUnchangedMutation check. With
internal/codescripts/codescripts.goreverted to the base SHA (14cb288) and the test file kept, the first three fail by name andTestValidateNameASCIIMessagesUnchangedpasses — which is what shows the change is behaviour-preserving for ASCII. With the fix applied all four pass.Commands run (exit status in brackets):
Not run:
-raceand./scripts/run-all-tests.sh(local disk constraints on this machine). Nothing here is concurrent.