Skip to content

fix: support formatter functions in ECharts code blocks#6767

Open
octo-patch wants to merge 1 commit intolabring:mainfrom
octo-patch:fix/issue-6536-echarts-formatter-function
Open

fix: support formatter functions in ECharts code blocks#6767
octo-patch wants to merge 1 commit intolabring:mainfrom
octo-patch:fix/issue-6536-echarts-formatter-function

Conversation

@octo-patch
Copy link
Copy Markdown
Contributor

Fixes #6536

Problem

When an ECharts configuration contains formatter (or other function-valued properties), json5.parse() throws a SyntaxError because function literals are not valid JSON5. The error is silently caught, option becomes undefined, and setOption({}) is called — the chart renders as a blank white box with no error shown.

Solution

Add a fallback parser that evaluates the ECharts config as a JavaScript expression using the Function constructor. This allows formatter callbacks and other JS function values to work correctly.

  • json5.parse() is still tried first (safer, handles comments/trailing commas)
  • Only if that fails, the Function constructor fallback is used
  • The buildOption helper is extracted to avoid duplication between the two paths

Testing

Using an ECharts code block with a formatter function (as in the issue report) now renders the chart correctly instead of showing a blank box.

…g#6536)

When an ECharts config contains formatter or other function values,
json5.parse() fails silently and the chart renders as a blank box.

Add a fallback that evaluates the config as a JavaScript expression
using the Function constructor, enabling formatter callbacks to work.
json5.parse() is still attempted first as it is safer for standard
JSON5 configs.
@cla-assistant
Copy link
Copy Markdown

cla-assistant Bot commented Apr 18, 2026

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


octo-patch seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

1 similar comment
@cla-assistant
Copy link
Copy Markdown

cla-assistant Bot commented Apr 18, 2026

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


octo-patch seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@github-actions
Copy link
Copy Markdown

Build Successful - Preview code-sandbox Image for this PR:

registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-pr:code-sandbox_550219c5469555d59cc3c7bbbd23f5fccb2c162a

@github-actions
Copy link
Copy Markdown

Build Successful - Preview fastgpt Image for this PR:

registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-pr:fastgpt_550219c5469555d59cc3c7bbbd23f5fccb2c162a

@github-actions
Copy link
Copy Markdown

Build Successful - Preview mcp_server Image for this PR:

registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-pr:mcp_server_550219c5469555d59cc3c7bbbd23f5fccb2c162a

@c121914yu
Copy link
Copy Markdown
Collaborator

Please confirm whether there is a risk of injection attacks in this approach.

@octo-patch
Copy link
Copy Markdown
Contributor Author

@c121914yu Yes — new Function(...) is eval-equivalent and runs in the page's JS context with access to window/cookies/etc. The current json5.parse() path is safe (no code exec), so this fallback strictly weakens the security posture.

The practical risk depends on the trust model for ECharts code-block content:

  • LLM-only authored → low risk in practice
  • User-pasted / shared / plugin-provided → high risk (attacker-controlled JS in the victim's browser)

If you'd prefer a safer fix, I can switch to one of:

  1. AST guard — accept only function (...) { return ... } shapes and reject calls to global identifiers (window, fetch, eval, XMLHttpRequest, etc.). Covers the common formatter use cases without giving up the eval surface.
  2. Iframe sandbox — render the chart inside sandbox="allow-scripts" (no allow-same-origin), so even if eval runs hostile code it can't reach parent cookies/storage.
  3. Whitelist DSL — replace function support with a tiny ${value} / ${name} template syntax compiled at parse time, never evaluating user-provided code.

Happy to update the PR to whichever direction you prefer, or close it if you'd rather not widen the surface at all. My preference would be option 1 (AST guard) — it covers most formatter callbacks people write while keeping the dangerous eval features off-limits. Let me know which way you'd like to go.

Copy link
Copy Markdown
Collaborator

@c121914yu c121914yu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

前端安全 Review

这个改动不能直接合并。PR 解决的 formatter 空白问题是真实存在的,但当前实现把 Markdown 代码块内容作为 JavaScript 表达式在浏览器里执行,会把普通 Markdown 渲染链路变成任意 JS 执行入口。

阻塞点在 projects/app/src/components/Markdown/img/EChartsCodeBlock.tsx 的新增逻辑:

const userOption = new Function(`return (${code.trim()})`)();

code 来自 echarts Markdown code block,不是可信管理员配置。FastGPT 的 Markdown 组件会渲染聊天回复、引用、通知、插件说明、数据预览等内容;isDisabled 也只是遮罩,不会阻止子组件执行。因此攻击者只要让页面渲染一段 echarts Markdown,就可以在当前登录用户上下文执行任意脚本、读取前端可访问数据并调用同源 API。

建议不要采用 new Function / eval,也不建议做“AST guard 后执行 JS function”。AST guard 很容易在后续 ECharts formatter 复杂化时变成长期绕过面,而且仍然需要维护一套 JavaScript sandbox 语义。

更合适的修复方向:继续只用 JSON/JSON5 解析用户配置,然后提供安全的 formatter preset / DSL。例如支持:

{
  tooltip: {
    trigger: 'axis',
    axisPointer: { type: 'shadow' },
    formatterPreset: 'axisSeriesList'
  }
}

组件内部把 preset 映射到固定函数:

const formatterPresets = {
  axisSeriesList: (params: any) => {
    const list = Array.isArray(params) ? params : [params];
    const title = escapeHtml(String(list[0]?.name ?? ''));

    return [
      `${title}<br/>`,
      ...list.map((item) => {
        const marker = item.marker ?? '';
        const name = escapeHtml(String(item.seriesName ?? ''));
        const value = escapeHtml(String(item.value ?? ''));
        return `${marker}${name}: ${value}<br/>`;
      })
    ].join('');
  }
};

这能覆盖 issue #6536 里的多 series tooltip 展示需求,同时不会执行用户提交的 JS。后续如有更多场景,可以扩展 singleSeriesnameValuepercentPievalueWithUnit 等 preset,或者提供非常有限的字符串模板 DSL,但不要执行函数体。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

echarts 带上 formatter 之后显示空白

2 participants