fix(providers): strip Vertex doc script blocks whose end tag carries junk (#13936)

The Vertex model-docs HTML is converted to plain text before the table
parser reads context-window and token-limit numbers out of the cells.
The script/style removal pass required the end tag to be `</script\s*>`,
but the HTML spec closes the element on `</script\t\n foo>` too. Such a
block survived the pass; the generic `<[^>]+>` strip below then removed
both tags and kept the script BODY, so text that only ever existed inside
a script became cell text the number parser trusts.

Accept any end tag that starts with `</script`/`</style` followed by a
tag-name boundary, matching what a browser does.

CodeQL js/bad-tag-filter, alert #1007.
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-09-16 23:57:14 -03:00
committed by GitHub
parent f89e188a9f
commit 3d5baf13f4
3 changed files with 24 additions and 1 deletions

View File

@@ -0,0 +1 @@
- **fix(providers):** strip `<script>`/`<style>` blocks from the Vertex model-docs HTML even when the end tag carries junk before the `>` (`</script foo>`, which the HTML spec still treats as a close). The old regexp required `</script\s*>`, so such a block survived; the generic tag-stripping pass then removed both tags and kept the script BODY, letting its text reach the table cells the context-window/token-limit parser reads (CodeQL `js/bad-tag-filter`, alert #1007).

View File

@@ -147,7 +147,11 @@ function decodeHtmlEntities(value: string): string {
function htmlFragmentToText(fragment: string): string {
return decodeHtmlEntities(
fragment
.replace(/<(?:script|style)\b[^>]*>[\s\S]*?<\/(?:script|style)\s*>/gi, " ")
// The end tag accepts junk before the `>` — `</script\t\n bar>` closes the element per the
// HTML spec. Matching only `\s*>` left such a block unremoved here; the generic `<[^>]+>`
// pass below then stripped both tags and kept the script BODY, so whatever it contained
// landed in the cell text the number parser reads (CodeQL js/bad-tag-filter, alert #1007).
.replace(/<(?:script|style)\b[^>]*>[\s\S]*?<\/(?:script|style)\b[^>]*>/gi, " ")
.replace(/<(?:br|hr)\b[^>]*\/?\s*>/gi, " ")
.replace(/<[^>]+>/g, " ")
)

View File

@@ -163,3 +163,21 @@ test("Vertex normalization persists context separately while legacy providers re
]);
assert.equal(normalizeDiscoveredModels([metadata], "openrouter")[0]?.inputTokenLimit, 1048576);
});
test("#1007: a spec-legal `</script foo>` end tag does not leak script text into a parsed cell", () => {
// CodeQL js/bad-tag-filter: the script/style stripper matched only `</script>` with optional
// whitespace, but HTML also accepts junk before the `>` — `</script\t\n bar>` closes the element.
// The generic `<[^>]+>` pass then removes both tags, so what survives is the script BODY, which
// lands in the cell text the number parser reads. Docs pages come from docs.cloud.google.com and
// are parsed to TEXT (never rendered), so the impact is a poisoned limit, not injection.
const html = modelDocsHtml(`
<tr><th>Model ID</th><td>gemini-3.7-flash</td></tr>
<tr><th>Token limits</th><td>Context window</td><td><script>999,999,999</script\t\n bar></td></tr>
`);
assert.equal(
parseVertexModelDocsHtml(html, "gemini-3.7-flash"),
null,
"a number that exists only inside a script body must not become the model's context window"
);
});