diff --git a/CHANGELOG.md b/CHANGELOG.md index d1b76fe6560..5a2520bf6a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -118,6 +118,7 @@ Docs: https://docs.openclaw.ai - Telegram/Outbound API proxy env: keep the Node 22 `autoSelectFamily` global-dispatcher workaround while restoring env-proxy support by using `EnvHttpProxyAgent` so `HTTP_PROXY`/`HTTPS_PROXY` continue to apply to outbound requests. (#26207) Thanks @qsysbio-cjw for reporting and @rylena and @vincentkoc for work. - Browser/Security: fail closed on browser-control auth bootstrap errors; if auto-auth setup fails and no explicit token/password exists, browser control server startup now aborts instead of starting unauthenticated. This ships in the next npm release. Thanks @ijxpwastaken. - Sandbox/noVNC hardening: increase observer password entropy, shorten observer token lifetime, and replace noVNC token redirect with a bootstrap page that keeps credentials out of `Location` query strings and adds strict no-cache/no-referrer headers. +- Security/External content marker folding: expand Unicode angle-bracket homoglyph normalization in marker sanitization so additional guillemet, double-angle, tortoise-shell, flattened-parenthesis, and ornamental variants are folded before boundary replacement. (#30951) Thanks @benediktjohannes. - Docs/Slack manifest scopes: add missing DM/group-DM bot scopes (`im:read`, `im:write`, `mpim:read`, `mpim:write`) to the Slack app manifest example so DM setup guidance is complete. (#29999) Thanks @JcMinarro. - Slack/Onboarding token help: update setup text to include the “From manifest” app-creation path and current install wording for obtaining the `xoxb-` bot token. (#30846) Thanks @yzhong52. - Slack/Bot attachment-only messages: when `allowBots: true`, bot messages with empty `text` now include non-forwarded attachment `text`/`fallback` content so webhook alerts are not silently dropped. (#27616) Thanks @lailoo. diff --git a/src/security/external-content.test.ts b/src/security/external-content.test.ts index 6bbe5e65d86..4c573b7d3c3 100644 --- a/src/security/external-content.test.ts +++ b/src/security/external-content.test.ts @@ -187,6 +187,13 @@ describe("external-content security", () => { ["\u2039", "\u203A"], // single angle quotation marks ["\u27E8", "\u27E9"], // mathematical angle brackets ["\uFE64", "\uFE65"], // small less-than/greater-than signs + ["\u00AB", "\u00BB"], // guillemets (double angle quotation marks) + ["\u300A", "\u300B"], // CJK double angle brackets + ["\u27EA", "\u27EB"], // mathematical double angle brackets + ["\u27EC", "\u27ED"], // white tortoise shell brackets + ["\u27EE", "\u27EF"], // flattened parentheses + ["\u276C", "\u276D"], // medium angle bracket ornaments + ["\u276E", "\u276F"], // heavy angle quotation ornaments ]; for (const [left, right] of bracketPairs) { diff --git a/src/security/external-content.ts b/src/security/external-content.ts index e1fd9335d7d..9fd4c0bc164 100644 --- a/src/security/external-content.ts +++ b/src/security/external-content.ts @@ -116,6 +116,20 @@ const ANGLE_BRACKET_MAP: Record = { 0x27e9: ">", // mathematical right angle bracket 0xfe64: "<", // small less-than sign 0xfe65: ">", // small greater-than sign + 0x00ab: "<", // left-pointing double angle quotation mark + 0x00bb: ">", // right-pointing double angle quotation mark + 0x300a: "<", // left double angle bracket + 0x300b: ">", // right double angle bracket + 0x27ea: "<", // mathematical left double angle bracket + 0x27eb: ">", // mathematical right double angle bracket + 0x27ec: "<", // mathematical left white tortoise shell bracket + 0x27ed: ">", // mathematical right white tortoise shell bracket + 0x27ee: "<", // mathematical left flattened parenthesis + 0x27ef: ">", // mathematical right flattened parenthesis + 0x276c: "<", // medium left-pointing angle bracket ornament + 0x276d: ">", // medium right-pointing angle bracket ornament + 0x276e: "<", // heavy left-pointing angle quotation mark ornament + 0x276f: ">", // heavy right-pointing angle quotation mark ornament }; function foldMarkerChar(char: string): string { @@ -135,7 +149,7 @@ function foldMarkerChar(char: string): string { function foldMarkerText(input: string): string { return input.replace( - /[\uFF21-\uFF3A\uFF41-\uFF5A\uFF1C\uFF1E\u2329\u232A\u3008\u3009\u2039\u203A\u27E8\u27E9\uFE64\uFE65]/g, + /[\uFF21-\uFF3A\uFF41-\uFF5A\uFF1C\uFF1E\u2329\u232A\u3008\u3009\u2039\u203A\u27E8\u27E9\uFE64\uFE65\u00AB\u00BB\u300A\u300B\u27EA\u27EB\u27EC\u27ED\u27EE\u27EF\u276C\u276D\u276E\u276F]/g, (char) => foldMarkerChar(char), ); }