apexcharts / Blazor-ApexCharts

A blazor wrapper for ApexCharts.js
https://apexcharts.github.io/Blazor-ApexCharts
MIT License
784 stars 91 forks source link

Blazor Server formatters require 'unsafe eval' #400

Closed joadan closed 6 months ago

joadan commented 7 months ago

Right now in Blazor SS with Blazor-ApexCharts, we need to enable unsafe eval in NGINX otherwise the pages with charts crash. Is it planned to also allow .NET Formatters for SS? Or is it possible to use CSP hash for the formatter?

We tried with a hash, but we are getting this error when opening a page with a chart: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self' 'sha256-[HASH]'". Is it because in the published app the formatter script is saved differently from the source code and the hashes don't actually match?

Originally posted by @twojnarowski in https://github.com/apexcharts/Blazor-ApexCharts/issues/37#issuecomment-1932264816

twojnarowski commented 7 months ago

As I have written, for now the only way we manage to run the chart with tooltips on data points is to have unsafe eval in CSP enabled. But there is no way that I now to check if actually the hash we generate from the tooltip formatter written in source is the same as the JS code that ends up in the running application. The formatter also gets only the code part, without <script> tags, so I don't see a way to use nonce in CSP. I think that keeping the unsafe eval for the whole Blazor application just because of only these formatters is not very safe idea...

joadan commented 7 months ago

In blazor server we cannot run the .net formatters because the format function in apexchart.js is synchronously, while we need async in order to call blazor server. In order to set a javascript formatter we need to run javascript eval function to create the formatter function on the javascript side.

Currently we don't have any other solution to this problem.

I'm not familiar with CSP hash, could you explain how that could help.

twojnarowski commented 7 months ago

Using CSP with hash means that the only JS code that can be evaluated at runtime is the code which hash matches the hash specified in the CSP. That way if we hash this code:

function(value, opts) { if (value === undefined) {return '0';} return  value.toFixed(3)}

then only this piece of JS code can be run in the app. But it needs to be exactly the same when generating a hash and then in the deployed code - same whitespace, same capitalization, same everything for it to work. It protects from any kind of possible XSS attacks in the application.

joadan commented 7 months ago

Looking at the code and I believe we should be able to support custom tooltips with out any eval of javascript. Would that help?

joadan commented 7 months ago

Using CSP with hash means that the only JS code that can be evaluated at runtime is the code which hash matches the hash specified in the CSP. That way if we hash this code:

function(value, opts) { if (value === undefined) {return '0';} return  value.toFixed(3)}

then only this piece of JS code can be run in the app. But it needs to be exactly the same when generating a hash and then in the deployed code - same whitespace, same capitalization, same everything for it to work. It protects from any kind of possible XSS attacks in the application.

I get that part, question is how would it work in the component?

twojnarowski commented 6 months ago

We have upgraded the Blazor-ApexCharts nuget to 2.3.2 and currently we have this CSP:

add_header Content-Security-Policy "default-src 'self' http: https: data: blog: 'unsafe-inline'; script-src 'self'; always;";

And this is our custom formatter:

Tooltip = new Tooltip
{
    Y = new TooltipY
    {
        Formatter = @"function(value, opts) { if (value === undefined) {return '0';} return  value.toFixed(3)}"
    },
    HideEmptySeries = false,
},

And still when the chart loads it producess this error:

EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self'".

    at eval (<anonymous>)
    at Object.<anonymous> (https://example.com/_content/Blazor-ApexCharts/js/blazor-apexcharts.js:413:34)
    at JSON.parse (<anonymous>)
    at Object.parseOptions (https://example.com/_content/Blazor-ApexCharts/js/blazor-apexcharts.js:408:21)
    at Object.renderChart (https://example.com/_content/Blazor-ApexCharts/js/blazor-apexcharts.js:202:28)
    at https://example.com/_framework/blazor.server.js:1:3244
    at new Promise (<anonymous>)
    at y.beginInvokeJSFromDotNet (https://example.com/_framework/blazor.server.js:1:3201)
    at Yt._invokeClientMethod (https://example.com/_framework/blazor.server.js:1:61041)
    at Yt._processIncomingData (https://example.com/_framework/blazor.server.js:1:58516)
   at Microsoft.JSInterop.JSRuntime.InvokeAsync[TValue](Int64 targetInstanceId, String identifier, Object[] args)
   at Microsoft.JSInterop.JSObjectReferenceExtensions.InvokeVoidAsync(IJSObjectReference jsObjectReference, String identifier, Object[] args)
   at ApexCharts.ApexChart`1.InvokeVoidJsAsync(String identifier, Object[] args)
   at ApexCharts.ApexChart`1.RenderChartAsync()
   at ApexCharts.ApexChart`1.OnAfterRenderAsync(Boolean firstRender)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)

Is there anything we are doing wrong right now?

joadan commented 6 months ago

Hi,

Sorry for the confusion.

Yes the formatters will require javascript eval.

However you have one option to use the Custom Tooltip function. This will enable you to specify a render fragment for the tooltip in blazor, this will not require a javascript eval. https://apexcharts.github.io/Blazor-ApexCharts/features/tooltip

twojnarowski commented 6 months ago

Ok, thanks! That worked. We have added <ApexPointTooltip> inside <ApexChart> and that allowed us to create the custom tooltip that we need.

So to summarize, if someone stumbles upon this issue: There is no need to define Tooltip in ApexChartsOptions with a JS formatter. Keeping the CSP mentioned above works when just defining a custom tooltip with the ApexPointTooltip component.

P.S. the tooltip seems to be cached in Firefox, so we needed to clear all data to actually see the new one working.

joadan commented 6 months ago

Thanks