fix(charts): confine untrusted chart options to canvas render paths - #6998
Conversation
A `.chart` document is untrusted input — any workspace member authors one, and a public share link renders it to anonymous visitors on the app origin — but `parseChartSpec` forwarded `option` to `setOption` unchanged. ECharts draws through canvas with two exceptions: a `tooltip` left in its default `renderMode: 'html'` assigns its content to `innerHTML`, and a string `formatter` is that content's template verbatim (only substituted values are escaped), while `toolbox` assigns `dataView.lang` to `innerHTML` and fills a `saveAsImage` popup with `document.write`. Force the render mode and drop the toolbox so the document has no DOM sink, rather than filtering the values that flow through one. The walk is deep: `baseOption`, `media[].option` and timeline `options[]` each carry their own tooltip, a tooltip declared only under `media` still instantiates the component in HTML mode, and a `media` entry can override a top-level `renderMode`. Also escape the series name in the pptx chart renderer's bubble tooltip — it comes from the uploaded document and is interpolated into a hand-built `innerHTML` string, where ECharts escapes only the markup it builds itself.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryHigh Risk Overview
Separately, the PPTX bubble-chart tooltip HTML-encodes the uploaded series name before interpolating it into a hand-built formatter. HTML in author formatter strings now shows as literal text. Tests pin both the parse walk and the resolved ECharts model in SSR. Reviewed by Cursor Bugbot for commit b878a46. Configure here. |
Greptile SummaryThe PR confines untrusted
Confidence Score: 5/5The PR appears safe to merge, with no concrete changed-code defect remaining after review. The changed parser closes the identified untrusted tooltip and toolbox DOM paths, while the PPTX formatter now escapes its document-derived series name; the added tests exercise the important nested and resolved-model cases.
|
| Filename | Overview |
|---|---|
| apps/sim/lib/charts/spec.ts | Adds parse-boundary traversal that forces rich-text tooltips, removes toolbox options, and converts traversal overflow into a chart-document validation error. |
| apps/sim/lib/charts/spec.test.ts | Adds comprehensive unit and ECharts model tests covering nested confinement, toolbox removal, dataset preservation, and depth rejection. |
| apps/sim/lib/pptx-renderer/renderer/chart-renderer.ts | Escapes the uploaded bubble-series name before interpolating it into the custom HTML tooltip. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
A[Untrusted chart document] --> B[parseChartSpec]
B --> C[Recursively confine options]
C --> D[Force tooltip renderMode to richText]
C --> E[Remove toolbox]
D --> F[buildChartRenderOption]
E --> F
F --> G[ECharts canvas-safe rendering]
H[Uploaded PPTX bubble series name] --> I[encodeHTML]
I --> J[Custom HTML tooltip]
Reviews (1): Last reviewed commit: "fix(charts): confine untrusted chart opt..." | Re-trigger Greptile
Summary
parseChartSpecforwarded a.chartdocument'soptiontosetOptionunchanged. A.chartfile is untrusted input — any workspace member authors one, and a public share link renders it to anonymous visitors on the app origin — so a stringtooltip.formatterreached ECharts'innerHTMLsink as stored XSS.tooltipleft in its defaultrenderMode: 'html'assigns its content toinnerHTML, and a stringformatteris that content's template verbatim (only substituted values are escaped);toolboxassignsdataView.langtoinnerHTMLand fills asaveAsImagepopup withdocument.write. Both are now closed at the parse boundary, so the document has no DOM sink at all — rather than filtering the values that flow through one.baseOption,media[].optionand timelineoptions[]each carry their own tooltip, a tooltip declared only undermediastill instantiates the component in HTML mode, and amediaentry can override a top-levelrenderMode.innerHTMLstring; every other tooltip there leaves the markup to ECharts, which escapes it. UsedencodeHTMLrather than the render mode because Sim owns that formatter and only the values are untrusted.contentTypeon file creation:resolvePreviewTypefalls back to the.chartextension, so it closes nothing and would reject legitimate types.renderMode: 'richText'is ECharts' own documented injection-safe mode. One behavior note: HTML inside an author's formatter string (<br/>,<b>) now renders as literal text. Templates ({b}: {c}), default tooltips and markers are unaffected — ECharts builds those per-mode.Side effect: a document nested deeply enough to exhaust the stack already crashed the render (
structuredCloneinbuildChartRenderOptionthrows on it). It's now rejected at parse, so it shows the document's error card instead of failing inside the render.Type of Change
Testing
lib/charts/spec.test.ts, including two that render through real ECharts in SSR mode and assert the resolved model — a version bump that breaks therenderModeassumption fails loudly instead of silently reopening the holebun run test lib/charts lib/pptx-renderer app/workspace/[workspaceId]/files— 801 passingtype-check,lint,check:block-registryandcheck:audits(33 audits, incl.check:api-validation:strict) all cleanChecklist