#1071: reject invalid XML element names in XML.toString (CWE-91) - #1072
#1071: reject invalid XML element names in XML.toString (CWE-91)#1072mechko wants to merge 3 commits into
Conversation
XML.toString emitted JSONObject keys verbatim as tag names, so a key containing '<', '>' or '/' broke out of its element and injected arbitrary sibling structure into the output. Per stleary#294/stleary#123 the agreed approach is to throw on invalid input rather than mangle it. - Add mustBeXmlName / isXmlNameStart / isXmlNameChar implementing the XML 1.0 (5th ed.) Name production, code-point aware. - Validate tagName at method entry and each key at the top of the key loop (skipping the cDataTagName sentinel). - Rewrite XMLTest.shouldHandleIllegalJSONNodeNames and XMLConfigurationTest.shouldHandleIllegalJSONNodeNames (previously documenting the pass-through behaviour) to assert the throw. - Add XMLTest.toStringRejectsElementInjectionInKey covering the stleary#1071 payload and an invalid caller-supplied tagName. - Add XMLTest.toStringAcceptsValidXmlNames covering hyphen/dot/ underscore/colon, Latin-1 letters, and the cDataTagName sentinel. Fixes stleary#1071. Also resolves the long-standing well-formedness question in stleary#166 / stleary#294 / stleary#308. Co-Authored-By: Claude <noreply@anthropic.com>
isXmlNameStart's alternating &&/|| chain scored cognitive complexity 28. Extracting inRange(cp, lo, hi) collapses it to a flat || sequence and keeps the range list 1:1 with the XML 1.0 NameStartChar production. isXmlNameChar updated the same way. No behaviour change. Co-Authored-By: Claude <noreply@anthropic.com>
|
@mechko Sorry for the long wait, will get to this before next week. While reviewing the PR, I found that strict mode is not filtering incoming text for valid JSON chars. I will fix this for strict mode, but keep the current behavior for non-strict mode. |
|
@mechko Thanks for the PR. Can you please restrict this fix to the security issues mentioned in #1071. Enforcing well-formed tags could break backwards compatibility. Fixing the security issues also does this, but is justified because the fix prevents an injection attack.
|
Per review on stleary#1072: reject only < > & " ' / in element names to close the CWE-91 injection vector, and drop the full XML 1.0 Name validation to preserve backwards compatibility for callers that emit non-well-formed but non-injecting tag names. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MTGnYg5v1QxaqqfDKHKTVr
|
|
@stleary I adjusted the fix according to your comment, please let me know if you still want anything to be changed. |



Fixes #1071. Implements the throw-on-invalid-name approach discussed in #294 / #123, which also resolves the long-standing well-formedness question in #166 / #308.
Problem
XML.toStringemits JSONObject keys verbatim as XML tag names. A key containing<,>or/breaks out of its element and injects arbitrary sibling structure:Values already go through
escape(); keys/tagNamedo not.Fix
Validate every key and
tagNameagainst the XML 1.0 (5th ed.)Nameproduction before emitting; throwJSONExceptionon mismatch.mustBeXmlName(String)— throws if the string is null/empty or contains a code point outside theNameproductionisXmlNameStart(int)/isXmlNameChar(int)— the two productions, code-point aware (handles supplementary planes)tagNameat the top of the privatetoStringoverload, and once perkeyat the top of the key loop (thecDataTagNamesentinel is skipped since it never becomes a tag)<,>,/, whitespace,",&,@are all outsideNameChar, so the injection vector is closed. Keys that are already valid XML Names are unaffected —mustBeXmlNameis a no-op for them.Behaviour change
XML.toStringnow throws for keys that are not valid XML Names, where previously it emitted malformed XML:"a/><injected>…"JSONException"123foo"<123foo>(invalid XML)JSONException"has space"<has space>(invalid XML)JSONException"foo@bar"<foo@bar>(invalid XML)JSONException"ns:name","a-b.c_d","élément"The two existing
shouldHandleIllegalJSONNodeNamestests already documented the old output as "invalid XML" / "possible bug"; they've been rewritten to assert the throw.Verification
mvn test: 791 run, 0 fail, 0 error, 6 skipped (pre-existing).Tests
XMLTest.shouldHandleIllegalJSONNodeNames/XMLConfigurationTest.shouldHandleIllegalJSONNodeNames— rewritten to assertJSONExceptionfor123IllegalNodeandIllegal@nodeXMLTest.toStringRejectsElementInjectionInKey— the XML.toString: unescaped keys allow XML element injection (CWE-91) — proposal to throw per #294 #1071 payload throws; invalid caller-suppliedtagNamethrowsXMLTest.toStringAcceptsValidXmlNames— hyphen/dot/underscore/colon, Latin-1 letters, andcDataTagNamesentinel still serialise🤖 Generated with Claude Code