fix(agents): prevent malformed HTML entities from breaking tool calls (#99564)

* Reject surrogate HTML entities in tool args

Co-Authored-By: Claude <noreply@anthropic.com>

* docs(changelog): note tool argument entity fix

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
mikasa
2026-07-06 09:56:11 +08:00
committed by GitHub
parent 49ae7ec065
commit b6127ef0d8
3 changed files with 11 additions and 3 deletions
+1
View File
@@ -19,6 +19,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- **Source build portability:** keep tsdown configuration self-contained so builds do not depend on resolving the tsdown package from unrun's temporary module directory.
- **Agent tool-call decoding:** preserve surrogate-range numeric HTML entities as literal text while still decoding valid supplementary-plane values, preventing malformed model output from injecting lone UTF-16 surrogates into tool arguments. (#99564) Thanks @mikasa0818.
- **Gateway event dispatch:** catch and log lazy subscriber setup and handler failures instead of leaking unhandled promise rejections. (#100401) Thanks @cxbAsDev.
- **Diffs rendering:** render viewer and image output from one SSR preload, preserve language-pack highlighting through hydration, normalize language hints case-insensitively, skip identical before/after inputs with an explicit `changed` result, report truthful file-render and input errors, cache hash-pinned viewer runtimes, and prefer canonical file settings over stale aliases. (#100487)
- **Remote browser reliability:** bound persistent Playwright tab enumeration by the existing remote CDP timeout budget and retire timed-out connection attempts so late completions cannot restore a stuck connection. (#80147, #58968) Thanks @HemantSudarshan and @KeaneYan.
@@ -11,11 +11,13 @@ describe("decodeHtmlEntitiesInObject", () => {
expect(
decodeHtmlEntitiesInObject({
query: "Rock &amp; Roll &#65; &#39;ok&#39; &#x27;hex&#x27;",
emoji: "ok &#x1F600;",
args: ["--flag=&quot;value&quot;", "&lt;input&gt;"],
nested: { deep: "a &amp; b" },
}),
).toEqual({
query: "Rock & Roll A 'ok' 'hex'",
emoji: "ok 😀",
args: ['--flag="value"', "<input>"],
nested: { deep: "a & b" },
});
@@ -32,10 +34,10 @@ describe("decodeHtmlEntitiesInObject", () => {
it("preserves invalid numeric HTML entities", () => {
expect(
decodeHtmlEntitiesInObject({
query: "bad &#x110000; and &#9999999999;",
query: "bad &#x110000; and &#9999999999; and &#xD800; and &#55296;",
}),
).toEqual({
query: "bad &#x110000; and &#9999999999;",
query: "bad &#x110000; and &#9999999999; and &#xD800; and &#55296;",
});
});
});
@@ -17,7 +17,12 @@ const HTML_ENTITY_RE = /&(?:amp|lt|gt|quot|apos|#39|#x[0-9a-f]+|#\d+);/i;
function decodeHtmlEntities(value: string): string {
const decodeNumericEntity = (raw: string, radix: 10 | 16): string => {
const codePoint = Number.parseInt(raw, radix);
return Number.isFinite(codePoint) && codePoint >= 0 && codePoint <= 0x10ffff
const isValidCodePoint =
Number.isInteger(codePoint) &&
codePoint >= 0 &&
codePoint <= 0x10ffff &&
(codePoint < 0xd800 || codePoint > 0xdfff);
return isValidCodePoint
? String.fromCodePoint(codePoint)
: `&#${radix === 16 ? "x" : ""}${raw};`;
};