sapui5content-security-policyamdsap-fioriui5-tooling

How to Fix "String as JavaScript" Errors in FLP? (Async Module Loading)


The "Browser Settings" section from the "Site Settings" page of the launchpad in SAP Build Work Zone has now enabled the "Asynchronous Module Loading" by default for new sites since May 16th 2024.

SAP Fiori launchpad Asynchronous Module Loading switch

When enabled, some of the SAPUI5 applications or FLP plugins fail to start. The browser reports in the console:

Failed to execute '<JavaScript module>.js': 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-scr * data: blob:".

How is the CSP topic related to the launchpad's "Asynchronous Module Loading" setting? What can I adapt in my existing SAPUI5 code to make it conform to the CSP enforced by SAP?


Solution

  • Cause

    If the "Asynchronous Module Loading" is activated, not only does SAP Fiori launchpad (FLP) bootstrap SAPUI5 with data-sap-ui-async="true" but serves also its HTML document with the content-security-policy (CSP) response header that contains a set of CSP directives omitting unsafe-eval and unsafe-inline from script-src. UI5 contents, that cause calling eval or contain inline scripts, violate the CSP and therefore won't be processed by the browser.

    In the legacy UI5 code, eval is called typically due to JS modules being synchronously fetched via deprecated APIs (Internally requireSync or loadSyncXHR). The response text has to be then processed by eval in order to evaluate the JS code string at runtime which poses a security threat.

    Resolution

    It is highly recommended to keep the "Asynchronous Module Loading" setting enabled which improves the overall UI5 performance (Less blocked main thread) and the security (Stricter CSP).

    UI5 has already deprecated legacy/synchronous APIs and — with the 1.96 release — largely improved the support for strict CSP. For more detailed information about the current state of CSP in UI5 and which restrictions there are, see the documentation topic Content Security Policy in addition to following the topic Best Practices for Developers.

    The table below summarizes all documented best practices on making UI5 more CSP-compliant.

    ❌ UI5 content violating CSP
    ✅ Making UI5 content more CSP-compliant
    Application's initial HTML document bootstrapping UI5 without data-sap-ui-async="true" or with the debug mode activated. Ensure that UI5 bootstraps with data-sap-ui-async="true" and that no debug mode is activated unnecessarily.
    Defining styles and scripts inline:
    • <style>/*...*/</style>
    • <div style="..."></div>
    • <script>/*...*/</script>
    Comply with the style-src and script-src directives that exclude unsafe-inline by defining:
    • Style in a separate .css file.
    • Script in a separate file.

      <script src="..." ...></script>
      <script id="sap-ui-bootstrap"
      data-sap-ui-onInit="module:..." *
      data-sap-ui-xx-nosync="true"
      * E.g. module:sap/ui/core/ComponentSupport

    Using deprecated APIs and libs such as
    jQuery.sap.*, Core#loadLibrary, Core#createComponent, sap.ui.component, sap.ui.*view, sap.ui.controller, sap.ui.*fragment, sap.ui.extensionpoint, sap.ui.commons, sap.ca.scfld.md, sap.ca.ui, sap.ui.suite, sap.landvisz, sap.ui.vtm, sap.zen.*, sap.ui.ux3, sap.makit, sap.ui.model.odata.ODataModel, sap.ushell.Container.getService, etc. and/or fetching resources still synchronously despite using non-deprecated APIs.

    Review the reports by UI5 linter and console logs. You might have to increase the log level e.g. via the URL parameter sap-ui-logLevel=ALL to see the relevant logs. Refer to the API reference to learn about newer APIs that replace the deprecated ones.

    Note: Some APIs are only partially deprecated. UI5 linter can help detect the use of deprecated options.

    Accessing module exports via global names:
    sap.m.MessageBox/*...*/;
    new myRequiredLib.SomeControl();
    Also in XML definitions:
    <MyControl
    someProperty="{
    path: '...',
    formatter: 'my.global.format',
    type: 'sap.ui...'
    }"
    someEvent="some.global.handler"
    />
    Address modules only via:
    • sap.ui.define when defining a module as part of its dependency list.
    • sap.ui.require when simply accessing existing modules without defining a new one.

    See Best Practices for Loading Modules on how to access modules in JS.

    For XML: Require Modules in XML View and Fragment and Handling Events in XML Views.

    Creating the component content such as the root view, routed views, and nested views synchronously at runtime despite having them defined declaratively. Add the marker interface "sap.ui.core.IAsyncContentCreation" to the Component.js definition to enable creating the component content asynchronously and other benefits.
    Renderer of certain controls being fetched synchronously. Require the renderer module and assign it to the renderer in the control definition.
    Control.extend("my.demo.MyControl", {
    metadata: {/*...*/},
    renderer: MyRequiredRenderer
    });
    In library development, having dependencies incorrectly maintained in:
    • library.js
    • Lib.init's dependencies *
    • .library / manifest.json
    • ui5.yaml
    * If a list of library dependencies is specified in the info object, those libraries will be loaded synchronously if they haven't been loaded yet.

    In accordance with the Lib.init description, ensure that all dependencies are declared properly. E.g.:
    (Without cycle dependencies)

    Only eager dependencies in library.js:

    sap.ui.define([
    "sap/ui/core/Lib",
    // ...,
    "sap/m/library", // eager dependency
    ], (Lib/*, ...*/) => Lib.init({
    // No lazy or transitive dependencies
    dependencies: ["sap.m"/*, "sap.f"*/]
    }));

    Eager+lazy dependencies in .library, ui5.yaml:

    <!-- .library -->
    <dependency>
    <libraryName>sap.m</libraryName>
    </dependency>
    <dependency>
    <libraryName>sap.f</libraryName>
    <lazy>true</lazy>
    </dependency>
    The generated manifest.json should then contain the correct /sap.ui5/dependencies/libs.

    Bundles such as Component-preload.js or library-preload.js containing JS modules that are in string. For example, due to the bundle having been generated by outdated Grunt build tasks. Or due to using var, let, const, and function outside of sap.ui.define. Result:

    "my/ModuleA.js":'sap.ui.define([...'
    "my/ModuleB.js":'var myGlobal...'

    During the build time, UI5 Tooling logs a warning:

    Module [...] requires top level scope and can only be embedded as a string (requires 'eval')

    Or an error since specVersion 4 or higher:

    Module [...] requires top level scope and can only be embedded as a string (requires 'eval'), which is not supported with specVersion 4.0 and higher.

    JS module definitions in the generated preload bundles must not result in string.

    If the project still relies on Grunt: migrate to SAP Fiori tools / UI5 Tooling and keep your development toolchain up-to-date. Older SAP Fiori tools versions include utils/locate-reuse-lib.js which should be excluded from the deployment chain.

    When defining a module, do not include any global declaration such as var, let, const, and function outside of sap.ui.define.

    If the module has to depend on a global:

    • Replace e.g. var myGlobal with globalThis.myGlobal, wrap the module definition in an IIFE, or export the global as a separate module that can be required.
    • Some third party libraries, such as SheetJS, have to define a variable globally or must be exempted from being modified due to license reasons. In such cases, exclude the third party libraries from the bundle (Sample).
    • In UI5 library development, one can also set requiresTopLevelScope="false" to the affected raw-module in .library.

    Related Q&As

    Other resources that might help identifying CSP issues in UI5

    UI5 Tooling middleware csp

    1. Build the app e.g. with ui5 build --all or ui5 build self-contained -a.
    2. Serve the built app from the dist folder (Refer to SAP/ui5-tooling issue #300).
      E.g. with ui5 serve --config ui5-dist.yaml.
    3. Run the app with the following URL parameter:
      index.html?sap-ui-xx-csp-policy=sap-target-level-<1 to 3>:report-only
      • The higher sap-target-level-<n>, the stricter the CSP.
      • Remove :report-only if the browser should actually apply the CSP level.
    4. Observe any CSP violation reported in the browser console.

    ⚠️ Currently doesn't work in combination with the custom middleware ui5-middleware-index.

    Chrome DevTools

    F12F1 → Select the "Show CSP Violations view" checkbox from the Experiments section.


    If the CSP violation is found in the code maintained by SAP, reach out to support to raise the issue.