Repository navigation
Studio: a mermaid style statement after a semicolon or mid-line cannot fetch either - #14526
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
The account paying for this security review has reached its Codex usage limits. The payer can check the Codex usage dashboard. For personal accounts, using credits requires enabling “Use credits for security reviews” in Code review settings. If you do not manage the paying account, contact this repository's admins. |
|
@codex review |
1 similar comment
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cea99dfd9a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // A statement can also start after `;`, or mid-line after a space in a stateDiagram, and a | ||
| // stateDiagram classDef runs to the end of the line, so everything from the keyword to the line's | ||
| // end is checked; the text before it stays. Inside a quoted or bracketed label the word is text. | ||
| const STYLE_START = /(^|[;\s])[^\S\n]*(?:classDef|style|linkStyle)\b/gi; |
There was a problem hiding this comment.
Restrict whitespace matches to actual style statements
Matching after every whitespace treats ordinary diagram text as a style statement whenever the later text resembles CSS. For example, sequenceDiagram\n Alice->>Bob: apply style from url(parameter) is valid message text, but the guard now returns only Alice->>Bob: apply ; pipe-delimited flowchart labels are similarly truncated and can become invalid because insideLabel does not recognize those label forms. Limit the whitespace-only case to state-diagram statement positions rather than applying it to every diagram line.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in b63e62c: whitespace-started statements only count in those diagram types; elsewhere a statement must start the line or follow a semicolon outside a quoted or bracketed label. The sequenceDiagram message and the pipe label example are now passed through unchanged (tests added).
| for (const match of line.matchAll(STYLE_START)) { | ||
| const start = match.index + match[1].length; | ||
| if (start > 0 && insideLabel(line.slice(0, start))) continue; | ||
| if (STYLE_FETCH.test(line.slice(start))) return line.slice(0, start); |
There was a problem hiding this comment.
Scan repeated style tokens in linear time
For a line with many non-fetching style tokens, every loop iteration rescans the entire preceding prefix in insideLabel and most of the remaining suffix with STYLE_FETCH, making this quadratic in attacker-controlled diagram length. A roughly 80 KB line containing 10,000 matches took about 12 seconds in a local Node run, and this guard executes synchronously during rendering, so such chat content can freeze the UI before Mermaid even parses it. Track label state while scanning once, or otherwise avoid rescanning the line for each match.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in b63e62c: one pass per line. The last fetch position is found once, and the label state is advanced only up to each match, so a long line of style words is linear (test with 20,000 tokens).
| let quoted = false; | ||
| for (const ch of prefix) { | ||
| if (ch === '"') quoted = !quoted; | ||
| else if (!quoted && "[({".includes(ch)) depth++; |
There was a problem hiding this comment.
Do not treat state ID punctuation as an open label
State-diagram IDs may legally contain ( or [, so a valid source such as A( --> B classDef x fill:url(https://attacker.example/x) parses the suffix as a real classDef, but this counter treats the punctuation in A( as an unclosed label and skips the match. Applying that class to B therefore leaves the remote url(...) intact and restores the silent browser request this guard is intended to prevent. Label detection must follow Mermaid's actual token context rather than counting every delimiter in the prefix.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in b63e62c: in stateDiagram, erDiagram, block and requirementDiagram (whose lexers take mid-line statements and allow bracket punctuation in ids) every keyword counts with no label guessing, so A( --> B classDef x fill:url(...) is cut at the classDef (checked in Chromium, Firefox and WebKit: 1 request before, 0 after).
…them, in one pass [skip ci]
|
@codex review |
1 similar comment
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b63e62cf86
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for (const match of line.matchAll(STYLE_START)) { | ||
| const start = match.index + match[1].length; | ||
| if (start > lastFetch) break; | ||
| if (midLine) return line.slice(0, start); |
There was a problem hiding this comment.
Preserve style-like text in mid-line diagram labels
Fresh evidence after the earlier fix: when midLine is true, this unconditional return still treats keywords inside valid labels as statements. For example, Mermaid 11.17.2 accepts erDiagram\n A {\n string description "apply style url(parameter)"\n }, but this function truncates the attribute to string description "apply , leaving an invalid diagram; quoted state and block labels have the same problem. Continue tracking quoted/bracketed label context for these diagram types instead of returning on every match.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Leaving this fail-closed on purpose. Tracking quotes in these diagram types reopens the fetch: with quote tracking, stateDiagram-v2 'A" --> B classDef x mask:url(...)' (a lone quote in a state id) still made 1 request in Chromium and Firefox and 1 in WebKit, because mermaid lexes the classDef after it; the current head cuts it (0 in all three). The cost is a quoted label in a stateDiagram / erDiagram / block / requirementDiagram line that holds both the word style and url( or a backslash, which shows a parse error and fetches nothing.
|
@codex review |
1 similar comment
|
@codex review |
|
pre-commit.ci run |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b63e62cf86
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| return CSS_FETCH.test(decoded) ? `#lt;${tag}` : whole; | ||
| }) | ||
| .replace(STYLE_LINE, (line) => (STYLE_FETCH.test(line) ? "" : line)) | ||
| .replace(/^.*$/gm, (line) => withoutFetchingStyle(line, midLine)) |
There was a problem hiding this comment.
Carry label state across line boundaries
When a flowchart label spans lines and closes on the same line as a semicolon-separated style statement, invoking withoutFetchingStyle independently for every line resets quoted and depth before the closing delimiters. For example, flowchart LR\n A["hello\n world"]; style A fill:url(https://attacker.example/x) is accepted by Mermaid but passes through this guard unchanged: the closing quote is mistaken for an opening quote, so the semicolon branch skips the real style statement and the browser can request the remote URL. Scan the source with label state carried across lines, or otherwise account for labels opened on earlier lines.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Not changing this. With the mermaid build Studio ships, a flowchart style statement holding url(...) does not parse at all, so it cannot fetch: rendering flowchart LR\n A["hello\n world"]; style A fill:url(https://attacker.example/x) in Chromium gives Parse error on line 3 and no request, and the same happens for //host, quoted, escaped and image-set spellings, on one line or after a label that spans lines. A multi-line label with a plain colour style renders unchanged.
| const STYLE_FETCHES = new RegExp(STYLE_FETCH.source, "gi"); | ||
| // These lexers also take a style statement mid-line after a space (`A --> B classDef x ...`), and | ||
| // their ids may hold brackets, so there every keyword counts. | ||
| const MIDLINE_STYLE = /^[^\S\n]*(?:stateDiagram|erDiagram|block(?:-beta)?|requirementDiagram)\b/im; |
There was a problem hiding this comment.
Detect diagram headers following init directives
When a mid-line-capable diagram header immediately follows an init directive on the same line, Mermaid removes the directive before detecting the diagram, but this anchored regex scans the raw source and leaves midLine false. For example, Mermaid accepts %%{init: {}}%%stateDiagram-v2\n A --> B classDef x fill:url(https://attacker.example/x)\n class B x, while the guard returns it unchanged, allowing the applied class to request the remote URL. Detect the diagram type after removing or blanking leading directives, or allow directives before the header.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Agreed. 3cb4a0f reads the diagram type after blanking init directives, so %%{init: {}}%%stateDiagram-v2 gets the mid-line rule. Checked with real mermaid in Chromium: that source made 1 remote request before and 0 after, and still renders; the case is in the tests.
| else if (!quoted && "[({".includes(ch)) depth++; | ||
| else if (!quoted && ")]}".includes(ch)) depth = Math.max(0, depth - 1); | ||
| } | ||
| if (start === 0 || (match[1] === ";" && !quoted && depth === 0)) return line.slice(0, start); |
There was a problem hiding this comment.
Limit semicolon style detection to supporting diagrams
Fresh evidence after the earlier message-text fix: the semicolon branch still applies to every diagram type, including grammars where a semicolon remains ordinary title text and style statements are unsupported. Mermaid accepts gantt\n title First; style guide url(parameter)\n dateFormat YYYY-MM-DD unchanged, but this guard truncates the title to title First;; the same occurs for pie titles. Gate semicolon-started style detection on diagram types that actually parse these statements so harmless title text containing style and url(...) is preserved.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Not changing this. It only touches a gantt or pie title that has ; style followed by url( or a backslash on the same line; the title is cut at the ; and the chart still renders. That is a rare cosmetic loss next to keeping the semicolon rule the same in every diagram type.
…same line [skip ci]
|
@codex review |
1 similar comment
|
@codex review |
|
Codex Review: Didn't find any major issues. Chef's kiss. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |

Follow-up to #14427.
Problem. The mermaid guard drops a
classDef/style/linkStylestatement that names a remote resource, but it only recognized those statements at the start of a line. Mermaid also accepts them after a;statement separator, and a stateDiagram (also erDiagram, block-beta, requirementDiagram) accepts them mid-line after a space, so a chat reply containingstill made the browser fetch that URL while the diagram rendered, with no click and no approval.
Change. Each line is scanned once for a style keyword. In stateDiagram, erDiagram, block and requirementDiagram, whose lexers take a statement mid-line and allow brackets in ids, every keyword after a space counts. In other diagrams a statement must start the line or follow
;, and a keyword inside a quoted or bracketed label (A["Style transfer\nmodel"],A["see; style C:\temp"]) or in message text is label text and is skipped. From the first statement that names a resource, the rest of the line is removed (a stateDiagramclassDefruns to the end of the line past any;); the text before it stays. Diagrams without one come back unchanged, and running the guard twice gives the same text.Testing.
tests/mermaid-remote-images.test.ts: 45 pass, including semicolon, mid-line, bracketed state id, label, message text and a 20,000-token line (linear time); the new cases fail on main.A --> B; classDef x mask:url(...)A --> B classDef x background-image:url(...)(no;)[*] --> A classDef x mask:url(...)A( --> B classDef x background-image:url(...)Alice->>Bob: apply style from url(parameter)flowchart LR; A-->B; style A fill:#f96; style B fill:#bbfA --> B; classDef x fill:#f96A["Style transfer\nmodel"]A["see; style C:\\temp"]style/classDef/linkStyleblockBefore / after in Studio. The same chat thread, opened in a Studio built from main and one built from this branch. Its stateDiagram has a style statement after
;and one mid-line, each pointing at a local HTTPS image server. The diagram renders the same on both sides; the difference is what the browser fetched: main made 2 requests (?s=semicolon,?s=midline), this branch made 0.