fix(sse): escape backslash in ChatGPT-web citation link text (#6569) (#6944)

* fix(sse): escape backslash in ChatGPT-web citation link text (#6569)

markdownLinkText() escaped [ and ] but not the backslash itself, so a
citation label ending in (or containing) a backslash produced a broken
Markdown link — e.g. [Path C:\](url), where the trailing \ escapes the
closing bracket and consumes the link. Escape the backslash first, then
the brackets.

Clears the CodeQL js/incomplete-sanitization alerts at
open-sse/executors/chatgpt-web/citations.ts:52 (2 of the 9 new alerts
on the v3.8.47 release PR).

Regression guard: tests/unit/chatgpt-web-citations-escape.test.ts
(trailing backslash, backslash-before-bracket, bracket-only, plain).

* chore(changelog): add changelog.d fragment for #6944

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: brick30llc-ctrl <admin@brick30.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
This commit is contained in:
brick30llc-ctrl
2026-07-12 08:26:21 -05:00
committed by GitHub
parent 7092a1a62b
commit 7ca422d258
3 changed files with 60 additions and 1 deletions

View File

@@ -0,0 +1 @@
- **fix(sse):** escape backslash before brackets in ChatGPT-web citation link text, preventing a citation label containing `\` from corrupting the generated Markdown link ([#6944](https://github.com/diegosouzapw/OmniRoute/pull/6944)) — thanks @brick30llc-ctrl

View File

@@ -49,7 +49,15 @@ function asArray(value: unknown): unknown[] {
}
function markdownLinkText(value: string): string {
return value.replace(/\[/g, "\\[").replace(/\]/g, "\\]").replace(/\n/g, " ").trim();
// Escape the backslash first — otherwise a label ending in (or containing) a
// backslash leaks past the `[…]` escaping and breaks the generated link, e.g.
// `[Path C:\](url)` where the trailing `\` escapes the closing bracket.
return value
.replace(/\\/g, "\\\\")
.replace(/\[/g, "\\[")
.replace(/\]/g, "\\]")
.replace(/\n/g, " ")
.trim();
}
function markdownUrl(value: string): string {

View File

@@ -0,0 +1,50 @@
// ChatGPT-web citation link-text escaping (CodeQL js/incomplete-sanitization,
// PR #6569 release-blocker).
//
// `markdownLinkText()` builds the `[text]` half of a Markdown link from an
// untrusted citation label. It escaped `[` and `]` but NOT the backslash
// itself, so a label ending in (or containing) a backslash produced a broken
// link: e.g. `[Path C:\](url)` — the trailing `\` escapes the closing `]`,
// consuming the link's bracket. The escape character must be escaped first.
import test from "node:test";
import assert from "node:assert/strict";
const { cleanChatGptText } = await import(
"../../open-sse/executors/chatgpt-web/citations.ts"
);
const S = ""; // marker start
const SEP = ""; // marker separator
const E = ""; // marker end
// Build a raw `url` citation marker:  url  <label>  <url> 
const urlMarker = (label: string, url: string) => `${S}url${SEP}${label}${SEP}${url}${E}`;
test("markdownLinkText escapes a trailing backslash in the citation label", () => {
// Label ends in a backslash — without escaping, `[Path C:\](url)` breaks the link.
const text = `See ${urlMarker("Path C:\\", "https://example.com/docs")} here`;
const out = cleanChatGptText(text);
assert.equal(out, "See [Path C:\\\\](https://example.com/docs) here");
// The backslash must be doubled (escaped), never left bare before the `]`.
assert.doesNotMatch(out, /[^\\]\\\]/);
});
test("markdownLinkText escapes a backslash preceding a bracket (no bracket leak)", () => {
const text = urlMarker("a\\[b", "https://example.com");
const out = cleanChatGptText(text);
// `\` → `\\`, then `[` → `\[` : label renders as literal `a\[b`.
assert.equal(out, "[a\\\\\\[b](https://example.com)");
});
test("bracket-only labels keep their existing escaping (regression guard)", () => {
const text = urlMarker("a[b]c", "https://example.com");
const out = cleanChatGptText(text);
assert.equal(out, "[a\\[b\\]c](https://example.com)");
});
test("plain labels with no metacharacters pass through unchanged", () => {
const text = urlMarker("Plain Title", "https://example.com");
const out = cleanChatGptText(text);
assert.equal(out, "[Plain Title](https://example.com)");
});