https://gnocchi-internal.googlesource.com/third_party/echarts, branch main). Mirrored as appsheet-third-party/echarts, which is the URL actually resolved by Nirvana/Content/package.json.5.4.0 (upstream commit cb722aecad5b44476a5c2f4c343b699cfa834b0e, per METADATA; last_upgrade_date 2022-11-16). Declared in package.json and src/core/echarts.ts:version.zrender pinned to a pre-release nightly (npm:zrender-nightly@^5.4.1-dev.20221104) rather than a stable release.jeenee/Nirvana/Content). It renders bar, line, area, pie, doughnut, scatter, KPI and graph charts for (a) end-user-facing AppSheet application chart views and (b) the AppSheet Admin Console. Scope covers the TypeScript sources under src/ and the pre-built bundles checked into dist/, which are shipped to browsers.4e53257, 1bd413c, 60d53ba, 967afa1), all of which modify src/component/tooltip/TooltipHTMLContent.ts (and regenerated dist/ bundles) to suppress Trusted Types enforcement violations. No upstream security patches have been backported.dist/*.min.js artifacts are committed to this repository)innerHTML, contains an intentionally pass-through Trusted Types policy added by the fork, performs dynamic code evaluation via new Function in GeoJSON parsing, and writes unvalidated CSS into inline style attributes)TooltipHTMLContent.setContent creates Trusted Types policies named TooltipHTMLFix1, TooltipHTMLFix2, and TooltipHTMLFix3. TooltipHTMLFix2 is defined as createHTML: (string) => string — an identity function that launders arbitrary strings into TrustedHTML and assigns them to el.innerHTML. Treat this as a Trusted Types bypass, not a fix: it converts a browser-enforced XSS guardrail into a no-op while satisfying the violation reporter. Flag every string reaching this sink. Also flag that createPolicy is invoked on every setContent call (i.e. on every tooltip render) rather than once at module init — under a CSP trusted-types allow-list without allow-duplicates this throws a TypeError on the second call, and it is an unbounded policy-creation hot path.innerHTML data flow. Trace TooltipView.prototype._showTooltipContent → tooltipContent.setContent → TooltipHTMLContent.setContent → el.innerHTML. Note the asymmetry: a string formatter has its {a}/{b}/{c} placeholders escaped via formatTpl(html, params, true) → encodeHTML, but the template shell itself is raw HTML; a function formatter return value is inserted with no escaping whatsoever. The default (no-formatter) markup path in src/component/tooltip/tooltipMarkup.ts does apply encodeHTML to names and values and is the safe baseline — verify no regression there.extraCssText. In TooltipHTMLContent.prototype.show, tooltipModel.get('extraCssText') is concatenated raw into el.style.cssText with no validation or delimiter escaping. Check for injection of additional CSS declarations, and for exfiltration primitives such as background:url(...) attribute selectors.src/coord/geo/GeoJSONResource.ts:parseInput falls back to new Function('return (' + source + ');')() when JSON.parse is unavailable. Confirm this fallback is unreachable in supported browsers and that registerMap callers never pass attacker-controlled strings; any reach is direct arbitrary JavaScript execution.innerHTML / document.write sinks outside the tooltip. The fork patched only the tooltip. Audit the following, none of which are wrapped in a Trusted Types policy and all of which will hard-fail or bypass under TT enforcement:src/component/toolbox/feature/DataView.ts — header.innerHTML from lang[0] || model.get('title'), viewMain.innerHTML = htmlOrDom from the caller-supplied optionToContent callback, and closeButton.innerHTML / refreshButton.innerHTML from lang.src/component/toolbox/feature/SaveAsImage.ts — tab.document.write(html) and doc.write(bstr) build markup by string concatenation, interpolating url and lang[0] into an <img src=... title=...> attribute context.src/core/echarts.ts — getSvgRoot().innerHTML = content in the connected-charts SVG export path.TooltipHTMLContent.setContent — arrowEl.innerHTML = arrow in the HTMLElement[] content branch is not routed through any policy, unlike the sibling string branch.src/ and dist/ (supply-chain integrity). The committed bundles are inconsistent with each other and with the sources — see the table under Privileged Actions. dist/echarts.simple.js / .simple.min.js contain none of the fork patches, and dist/echarts.esm.js / .esm.min.js are stale at the 4e53257 state (only the empty-clear TooltipHTMLFix1 policy; missing the TooltipHTMLFix2 string-content path). Verify which artifact AppSheet actually ships and whether dist/ can be reproduced from src/.formatter values that return ECharts rich-text markup ({styleName|text}) interpolate data values into the template. Unescaped {, }, or | characters in a data value can break out of the intended token and select a different rich style. This is canvas/SVG-rendered rather than an HTML sink, so treat it as a UI-spoofing and rendering-integrity issue rather than XSS.zrender advisories affecting 5.4.0 and the zrender-nightly@5.4.1-dev.20221104 pre-release, and check whether each fix is absent here.All entry points are library APIs invoked by AppSheet frontend code; the library performs no authentication of its own.
| Entry Point | Type | Trusted? | Validation |
|---|---|---|---|
echartsInstance.setOption(option) — the primary API; receives the full chart config | Public API / config object | No | No schema validation or sanitization; option values are mapped onto component models and rendered |
tooltip.formatter (function) — e.g. ChartView.tsx, bar_chart_builder.ts, scatter_chart_builder.ts | Callback returning HTML | No | None. Return value flows to el.innerHTML via the pass-through TT policy |
tooltip.formatter (string template) | HTML template with {a}/{b}/{c} | No | Placeholders escaped by formatTpl(..., encode=true) → encodeHTML; the surrounding template is raw HTML |
Default tooltip markup (no formatter) | Internal markup builder | No | tooltipMarkup.ts applies encodeHTML to series names and values — safe baseline |
tooltip.extraCssText | Raw CSS string | No | None. Concatenated directly into el.style.cssText |
series.label.formatter / axisLabel.formatter / legend formatter | String or callback | No | Rendered as canvas/SVG text, not HTML; rich-text `{style |
series[].name, series.data[].name, xAxis.data[], title.text | Config strings sourced from customer data | No | Escaped in the default tooltip path; passed verbatim into custom formatters |
graphic component elements | zrender shape descriptors | No | Rendered via zrender (canvas/SVG), not HTML. type: 'image' accepts an arbitrary style.image URL → outbound request from the AppSheet origin |
echarts.registerMap(name, geoJson) | GeoJSON object or string | No | GeoJSONResource.parseInput uses JSON.parse, falling back to new Function(...) |
toolbox.dataView.optionToContent | Callback returning HTML/DOM | No | None. String return assigned to viewMain.innerHTML |
echarts.registerTheme(name, theme) — used by ChartView.tsx (darkTheme()) | Theme object | Yes | First-party static theme definition |
queryRowValues and are passed into chart options.localStorage, in-page APIs).ChartView.tsx, chart option builders) → ECharts option object → tooltip/label rendering → browser DOM. The critical crossing is the last one, where data leaves the React-escaped world and enters raw HTML string construction.| Data Type | Source | Destination | Protection |
|---|---|---|---|
| Customer record values (row/cell data) | AppSheet table query (table.queryRowValues) | Chart series data, axis categories, pie slice names, tooltip HTML | Access-controlled server-side; unescaped once inside custom formatter callbacks |
| Column display names / chart titles | App creator's saved view definition (ChartConfig) | Axis names, series names, title.text, tooltip label prefixes | Creator-authored; treated as trusted markup by the library |
| Customer app names and creator email addresses (PII) | AppSheet Admin Console APIs | Admin Console tooltip HTML via the shared tooltipFormatter | Interpolated into an HTML string with no escaping |
| Customer-supplied app logo URLs | App metadata | rich[...].backgroundColor.image on axis labels | Loaded as an image resource from the AppSheet origin |
| Chart bitmap / SVG export | Rendered canvas or SVG root | toDataURL, download anchor, or a new browser tab | Contains whatever customer data is on-screen; written via document.write in the fallback path |
| Action | Location | Guard |
|---|---|---|
| HTML injection into the live DOM | TooltipHTMLContent.setContent → el.innerHTML | Pass-through Trusted Types policy TooltipHTMLFix2 (createHTML: s => s) — no effective guard |
| Trusted Types policy creation (per render) | TooltipHTMLContent.setContent (TooltipHTMLFix1/2/3) | None; requires the policy names to be CSP-allow-listed, and duplicates to be permitted |
| Inline CSS injection | TooltipHTMLContent.show → el.style.cssText (extraCssText) | None |
| Unpoliced HTML injection | TooltipHTMLContent.setContent → arrowEl.innerHTML (DOM-content branch) | None |
| Dynamic code evaluation | GeoJSONResource.ts:parseInput → new Function(...) | Reachable only when JSON.parse is unavailable |
| HTML injection (toolbox) | DataView.ts → header.innerHTML, viewMain.innerHTML, closeButton.innerHTML, refreshButton.innerHTML | None; feature is opt-in via toolbox.feature.dataView |
| New-window document write | SaveAsImage.ts → tab.document.write(html), doc.write(bstr) | None; markup built by string concatenation |
| SVG DOM replacement | echarts.ts → (zr.painter as SVGPainter).getSvgRoot().innerHTML = content | None; connected-charts export path |
| Outbound network request | graphic type: 'image' / rich[...].backgroundColor.image | None; arbitrary URL fetched from the AppSheet origin |
| Shipping pre-built browser bundles | dist/echarts*.js, dist/echarts*.min.js | Committed artifacts; not verifiably reproducible from src/ (see below) |
Fork-patch coverage across committed bundles — the patches applied to src/ are not uniformly present in dist/:
| Bundle | TooltipHTMLFix1 | TooltipHTMLFix2 | TooltipHTMLFix3 | State |
|---|---|---|---|---|
echarts.js / echarts.min.js | yes | yes | yes | Matches src/ (HEAD) |
echarts.common.js / .common.min.js | yes | yes | yes | Matches src/ (HEAD) |
echarts.esm.js / .esm.min.js | yes (×2) | no | no | Stale at commit 4e53257 |
echarts.simple.js / .simple.min.js | no | no | no | Unpatched upstream code |
package.json sets main: dist/echarts.js (patched) and module: index.js (compiled from patched src/), so the default resolution paths are current. AppSheet imports the library as import('echarts') (full bundle, not tree-shaken), which resolves to main/module. The esm and simple bundles remain divergent and should be regenerated or removed.
Recorded here because it determines exploitability of the sinks above.
Nirvana/Content/package.json declares "echarts": "git+https://appsheet-third-party.googlesource.com/echarts" with no #ref and no semver constraint — it tracks the fork‘s default branch. package-lock.json resolves it to #967afa1ae54628568880206a569b42d40189f566 with no integrity hash (npm omits subresource integrity for git dependencies). A push to the fork’s main therefore lands in AppSheet on the next lockfile refresh, with no hash pinning.Nirvana/Content/components/app/primary/ChartView.tsx renders end-user-facing chart views. Chart configuration is read from the app creator's saved view definition (ChartConfig); row data is pulled via table.queryRowValues and passed into the option builders under src/editor/charts/helpers/create_chart_option/.ChartView.tsx (scatter tooltip, joins value[i] with <br/>), bar_chart_builder.ts (getBarLineAreaTooltips), and scatter_chart_builder.ts all concatenate row values and creator-authored column display names into HTML strings with no escaping.components/admin/console/overview/echarts_options.ts exports a shared tooltipFormatter that interpolates data.name and data.value into a <p>/<span>/<b> HTML string. Its callers pass customer-authored app names (PopularAppsCard.tsx) and creator email addresses (MostAppsOwnedCard.tsx).pie_chart_builder.ts and kpi_helper_functions.ts build {bold|...} / {header|...} rich-text tokens from row values and series names.renderMode, appendToBody, appendTo, or graphic configuration appears anywhere in the AppSheet frontend, so tooltips use the default renderMode: 'html' — i.e. the innerHTML path above — and the tooltip DOM stays within the chart container.TooltipHTMLContent.setContent — the fork's createHTML: (string) => string identity policy launders arbitrary strings into TrustedHTML, silencing the violation while removing the protection; this is the single highest-value finding in the repository and should be replaced with real sanitization.formatter → innerHTML DOM-XSS chain — AppSheet's tooltip formatters concatenate end-user record values and creator-authored column names into HTML with no escaping, and the library performs none on the callback return path, giving a direct customer-data-to-script-execution route in the AppSheet origin.extraCssText raw CSS injection — tooltipModel.get('extraCssText') is concatenated straight into el.style.cssText, permitting arbitrary declarations and CSS-based data exfiltration if it is ever fed configuration-derived text.DataView.ts, SaveAsImage.ts, the connected-charts SVG export in echarts.ts, and the arrowEl.innerHTML branch received no fork hardening and will either bypass or break under Trusted Types enforcement.new Function evaluation in GeoJSONResource.parseInput — a dynamic code-execution primitive reachable from registerMap input whenever JSON.parse is unavailable; confirm unreachability or remove the fallback.dist/ bundle divergence and unpinned dependency resolution — the committed esm and simple bundles do not correspond to src/, the bundles are unverifiable build artifacts shipped to browsers, and AppSheet consumes the fork from a branch reference with no integrity hash, so this is a supply-chain integrity gap in addition to a patch-coverage gap.5.4.0 (November 2022) and a zrender nightly pre-release, with no rebase since; any upstream ECharts or zrender security fix released after that date is presumed missing.{, }, and | in row values can break out of {style|text} templates in the pie and KPI label formatters, causing label spoofing and rendering-integrity issues (not XSS, as these are canvas-rendered).Teams can fill out this section with any known filed vulnerability bugs that they've determined should be out of scope from future Fortify bug filing.