Markdown Chart is designed for chart JSON that may have been produced by an untrusted model.
- Canonical and documented renderer bodies are parsed with
JSON.parse. The canonical path never evaluates JavaScript. - Canonical
datais validated independently from renderer-ownedspec; inline rows may contain only JSON scalar arrays or scalar-valued objects. - The ECharts renderer uses a strict top-level option allowlist plus a recursive
denylist. It rejects functions, non-JSON values, prototype-related keys,
non-string or unsafe
formattervalues,tooltip.extraCssText,toolbox/dataView, custom series,renderItem,graphic, image options, URL-like strings, CSSurl(...), and HTML-like markup/entities. Safe string formatter templates such as{@conversionRate}%are allowed; formatter code is not. - URL-bearing option keys such as
link,sublink,href,src, andurlare rejected even when their values are relative or obfuscated. Unsafe ASCII C0 controls are rejected; permitted line/tab whitespace is removed for the protocol check, preventing split-protocol forms such asjava\nscript:andjava\tscript:. Legacyvbscript:is rejected too. - ECharts dataset transforms are disabled. In particular, untrusted
dataset.transform.config.regvalues never reach the ECharts transform engine, avoiding attacker-controlled regular-expression execution. - The ECharts renderer itself performs no
fetch, XHR, or dynamic URL loading. - Referenced datasets require an explicit host resolver and an optional host reference validator.
- Markdown-it placeholders contain only generated identifiers. Raw chart JSON is kept in an environment side channel and is not embedded in HTML.
- Parse depth, JSON size, node count, dataset size, and series count are bounded.
- Keep Markdown HTML disabled, or sanitize the complete rendered Markdown when enabling raw HTML. This is separate from chart placeholder safety.
- Treat
resolveDataRefas a privileged boundary. Validate schemes and authorization, honor itsAbortSignal, and avoid returning secrets in errors. - Prefer the shared
createLegacySandboxClientfor new legacy integrations. Its host-ownedLegacySandboxTransportis a privileged authenticated data boundary: preserveAbortSignal, authorize every list/read operation, bound responses, and classify HTTP/network/envelope failures without exposing credentials. Matching, retry, request-to-session fallback, and caching stay in the shared client rather than being reimplemented in the transport. - Supply a stable non-secret
cacheScopeKeyfor the current principal (for example,tenantId:userId) and create a new client when authentication identity changes. Never use bearer tokens, cookies, session secrets, or hashes of those values as cache identity, and never fall back to sessionId. - Apply a Content Security Policy suitable for the surrounding application.
- Use a trusted ECharts runtime and keep it patched.
- Do not add an "unsafe" option that evaluates formatters or
renderItemcode.
The strict profile intentionally supports a conservative subset of top-level ECharts options. Additions to that allowlist require a security review of every nested string and callback-like surface reachable from the option.
The deprecated ChatBI migration adapter is deliberately separate from the
canonical parser. It parses CSV with UTF-8 byte, row, column, and cell limits,
then creates a hidden outer iframe with only sandbox="allow-scripts".
Omitting allow-same-origin gives that iframe a unique origin. The iframe runs
only a trusted bootstrap: legacy source and CSV rows are sent to a Blob-backed
dedicated Worker and are never embedded in srcdoc or evaluated in the iframe
Window. Its inline CSP starts with default-src 'none', permits only the inline
bootstrap and worker-src blob:, and sets connect-src 'none' while also
disabling images, objects, media, forms, and child frames.
The Worker has no DOM or iframe navigation surface. Before evaluating source it
shadows known network and worker-expansion APIs, including fetch, XHR,
WebSocket, EventSource, importScripts, Worker, and SharedWorker. This is a
defense-in-depth restriction in addition to the iframe CSP, not a source-regex
security boundary. The compatibility sanitizer is not treated as isolation.
The parent accepts a response only from the created iframe's contentWindow
and only for an unpredictable per-execution request id. Abort, result, error,
load failure, and timeout paths remove listeners, timers, and the iframe; the
bootstrap also terminates the Worker on result or error. Removing the iframe
terminates a still-running dedicated Worker, so the timeout can stop a
synchronous loop without blocking the parent Window. Worker and iframe errors
cross the boundary only as fixed categories without exception messages or
stacks. The Worker serializes only the generated option through JSON; the
parent then applies the normal JSON and ECharts safety validation.
This is defense in depth for a temporary browser compatibility path, not a
proof that arbitrary JavaScript is safe. Termination does not prevent transient
CPU or memory pressure before the timeout, and large allocations can still
exhaust browser resources. Hosts should enable this deprecated path only for
trusted ChatBI streams and remove it when that format is retired. Canonical
markdown-chart remains JSON-only and does not depend on the legacy directory.
Please report vulnerabilities privately to the DataFE maintainers. Include a minimal reproduction, affected package versions, and impact. Do not include production credentials or private datasets.