SQLpage icon indicating copy to clipboard operation
SQLpage copied to clipboard

Error loading javascript in project root

Open srcarvalho12 opened this issue 1 year ago • 15 comments

It is possible to get around this:

Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self' https://cdn.jsdelivr.net". Either the 'unsafe-inline' keyword, a hash ('sha256-XkxQ/UEKO+cl+Q7H6G/04zQC/hALRvbbVW9YE8/SpZw='), or a nonce ('nonce-...') is required to enable inline execution .

srcarvalho12 avatar Jun 26 '24 12:06 srcarvalho12

Hello ! Did you try to inline a script in a custom component ?

The (restrictive) content-security policy currently cannot be disabled. Its goal is to prevent you from accidentally create components that are vulnerable to XSS attacks. The easiest workaround is to write your code in a separate javascript file, and to include it with a script tag, and using the javascript property of the shell component.

In my_component.handlebars, replace

<script>
  my_custom_code("{{ my_data }}");
</script>

with

<script src="/script.js" data-something="{{ my_data }}"></script>

lovasoa avatar Jun 26 '24 12:06 lovasoa

Hi, in PostgreSQL (not sure about other db) you can do something like this:

shell.sql:

WITH nonce AS (
    SELECT generate_nonce() AS value
)
-- Set the CSP header with the generated nonce
SELECT 
    'http_header' AS component,
    CONCAT(
        'script-src ''self'' https://*.openstreetmap.org https://cdn.jsdelivr.net https://*.vimeocdn.com https://*.vimeo.com https://*.soundcloud.com https://*.sndcdn.com https://*.cloudflare.com ''nonce-', 
        (SELECT value FROM nonce), 
        ''''
    ) AS "Content-Security-Policy",
    (SELECT value FROM nonce) AS nonce_value;

SELECT 'shell' AS component,
[your shell code here...]

CREATE EXTENSION IF NOT EXISTS pgcrypto;

CREATE OR REPLACE FUNCTION public.generate_nonce()
RETURNS text
LANGUAGE plpgsql
AS $function$
BEGIN
    RETURN encode(gen_random_bytes(16), 'base64');
END;
$function$;

amrutadotorg avatar Jun 26 '24 13:06 amrutadotorg

@amrutadotorg , do you really need the nonce ? If you just generate a nonce, put it the header, and then immediately forget about it, you could just as well generate a Content-Security-Policy without a nonce, right ?

lovasoa avatar Jun 26 '24 13:06 lovasoa

I would like to use EmulatoJS to add emulators to my website and I need to import the EmulatorJS library, I have already downloaded the files and I am trying to access them through the project root

srcarvalho12 avatar Jun 26 '24 13:06 srcarvalho12

@lovasoa right, so what would be the best way to deal with CSP when using external sources like

https://.openstreetmap.org https://cdn.jsdelivr.net https://.vimeocdn.com https://.vimeo.com https://.soundcloud.com https://.sndcdn.com https://.cloudflare.com ?

like here:

WITH soundcloud_data AS (
    SELECT
        meta_value::jsonb AS audio_data
    FROM
        post_meta
    WHERE
        post_id = $id::int AND meta_key = 'soundcloud'
)
SELECT
    audio_data->>'url' AS embed,
    'autoplay' AS allow,
    'iframe' AS embed_mode,
    '300' AS height,
    'audio'   as id
FROM
    soundcloud_data
WHERE
    audio_data->>'url' IS NOT NULL;

amrutadotorg avatar Jun 26 '24 13:06 amrutadotorg

@amrutadotorg : You could just remove the nonce from the CSP, since it looks like you are not using it anywhere. Keep the other sources and remove the nonce- part.

@srcarvalho12 : can you show us what you are doing ? If you just want to include external js sources, you can download them and include them with a local path using the javascript attribute of the shell component

lovasoa avatar Jun 26 '24 13:06 lovasoa

Yes yes, I'm adding the local files part, I'm adding the following code: (The files are inside the root where index.sql is called)

<div style="width:100%;height:100%;max-width:100%">
    <div id="gameview"></div>
</div>
<script>
    EJS_player = "#gameview";
    EJS_core = "gba";
    EJS_pathtodata = "/data/";
    EJS_gameUrl = "https://allancoding-website-files.on.drv.tw/ROM_FILE_NAME_&_PATH.zip";
</script>
<script src="/data/loader.js"></script>

srcarvalho12 avatar Jun 26 '24 13:06 srcarvalho12

Oh, so you are serving a .html file directly without executing any sql ? You can move the inline script to a separate file:

<div style="width:100%;height:100%;max-width:100%">
    <div id="gameview"></div>
</div>
<script src="/mygame.js"></script>
<script src="/data/loader.js"></script>

and in mygame.js:

    EJS_player = "#gameview";
    EJS_core = "gba";
    EJS_pathtodata = "/data/";
    EJS_gameUrl = "https://allancoding-website-files.on.drv.tw/ROM_FILE_NAME_&_PATH.zip";

lovasoa avatar Jun 26 '24 13:06 lovasoa

Ahhhhh wonderful, that's right! And I believe that to pass parameters the following can be done:

<script src="mygame.js" data-param1="value1" data-param2="value2"></script>

And in mygame.js I can receive it like this:

const scriptTag = document.currentScript;
const param1 = scriptTag.getAttribute('data-param1');
const param2 = scriptTag.getAttribute('data-param2');

srcarvalho12 avatar Jun 26 '24 13:06 srcarvalho12

I sincerely thank you for your help!

srcarvalho12 avatar Jun 26 '24 13:06 srcarvalho12

Thank you, I changed to:

-- Set the CSP header without generating a nonce
SELECT 
    'http_header' AS component,
    'script-src ''self'' https://*.openstreetmap.org https://cdn.jsdelivr.net https://*.vimeocdn.com https://*.vimeo.com https://*.soundcloud.com https://*.sndcdn.com https://*.cloudflare.com' AS "Content-Security-Policy";

amrutadotorg avatar Jun 26 '24 14:06 amrutadotorg

But anyway I get when trying to run the emulator:

emulator.min.js:1 Refused to create a worker from 'blob:http://localhost:3000/eb69bfd7-132f-4c8c-8f67-dbb4e565856d' because it violates the following Content Security Policy directive: "script-src 'self 'https://cdn.jsdelivr.net". Note that 'worker-src' was not explicitly set, so 'script-src' is used as a fallback.

[email protected]:1
[email protected]:1
load (asynchronous)
[email protected]:1
(anonymous) @emulator.min.js:1
[email protected]:1
[email protected]:1
[email protected]:1
[email protected]:1
(anonymous) @emulator.min.js:1
Promise.then (asynchronous)
[email protected]:1
[email protected]:1
emulator.min.js:1 Uncaught DOMException: Failed to construct 'Worker': Access to the script at 'blob:http://localhost:3000/eb69bfd7-132f-4c8c-8f67-dbb4e565856d' is denied by the document's Content Security Policy.
    at downloadFile.responseType (http://localhost:3000/data/emulator.min.js:1:231400)
    at a.onload (http://localhost:3000/data/emulator.min.js:1:221028)

srcarvalho12 avatar Jun 26 '24 14:06 srcarvalho12

const param1 = scriptTag.getAttribute('data-param1');
const param2 = scriptTag.getAttribute('data-param2');

You can use the dataset object

const {param1, param2} = scriptTag.dataset;

But anyway I get when trying to run the emulator: emulator.min.js:1 Refused to create a worker from 'blob:http://...' because it violates the following Content Security Policy directive: "script-src 'self 'https://cdn.jsdelivr.net". Note that 'worker-src' was not explicitly set, so 'script-src' is used as a fallback.

Indeed. I'll add a way to customize the CSP in future versions. In the meantime, you can create a custom component.

sqlpage/templates/emulator.handlebars

<div style="width:100%;height:100%;max-width:100%">
    <div id="gameview"></div>
</div>
<script src="/mygame.js" data-gameurl="{{ gameurl }}"></script>
<script src="/data/loader.js"></script>

game.sql

SELECT 'http_header' AS component, 'script-src ''self'' https://*.openstreetmap.org https://cdn.jsdelivr.net; worker-src ''self'' blob:' AS "Content-Security-Policy";

select 'emulator' as component, 'http://my-url' as gameurl;

lovasoa avatar Jun 26 '24 14:06 lovasoa

All good! Thank you for your help, from the bottom of my heart!

srcarvalho12 avatar Jun 26 '24 14:06 srcarvalho12

I used this to http_header and it worked:

SELECT 'http_header' AS component, 'script-src ''self'' ''unsafe-eval'' blob: https://*.openstreetmap.org https://cdn.jsdelivr.net https://cdn.emulatorjs .org; worker-src ''self'' blob:' AS "Content-Security-Policy";

srcarvalho12 avatar Jun 26 '24 14:06 srcarvalho12

SQLPage v0.26 makes it easier to include scripts.

There is no more restriction on loading scripts from a hardcoded .html file.

From a custom component, you can now load external scripts with

<script nonce="{{@csp_nonce}}" src="https://example.com/example.js" />

You can also easily customize (or remove entirely) the content-security policy header from the sqlpage.json file : https://github.com/lovasoa/SQLpage/blob/main/configuration.md

lovasoa avatar Aug 12 '24 13:08 lovasoa

Is the csp_nonce also available in SQL files?

My use case is as follows. I have a PostgreSQL function returning an XML. The XML is sent to the browser as it is. The XML can have a xml-stylesheet processing instruction pointing to a static XSL file in the same folder as the SQL file. The XSLT template produces the HTML rendered by the browser. The generated HTML contains inline CSS and JS, but the javascript is not executed because the script tag lacks the required nonce attribute. I can easily add it to the generated HTML adding a XSLT parameter to the generated XML, but I don't know how to get the nonce in the SQL page.

Here is the code of the sql page:

select
  'http_header' as component
, 'application/xml' as "Content-Type"
;
select
  'shell-empty' as component
, a_function_returning_an_xml(
    cast($param1 as int)
  , $param2
  , case
    when $flag1 is not null then 'flag1.xsl'
    when $flag2 is not null then 'flag2.xsl'
    end
  , sqlpage.csp_nonce() -- WHAT SHOULD I PUT HERE?
  ) as html
;

marcenuc avatar Jan 14 '25 08:01 marcenuc

Hi ! There is currently no way to retrieve the randomly generated nonce from SQL. However, you can overwrite it with a value you choose yourself:

set nonce = sqlpage.random_string(10);
select 'http_header' as component, 'script-src ''self'' ''nonce-' || $nonce || '''' as "Content-Security-Policy";
select 'html' as component, '<script nonce="' || $nonce || '">alert("Hello, world!");</script>' as html;

Or, (easier, but less secure in the face of untrusted user input), you can allow inline scripts without a nonce:

select 'http_header' as component, 'script-src ''self'' ''unsafe-inline''' as "Content-Security-Policy";
select 'html' as component, '<script>alert("Hello, world!");</script>' as html;

lovasoa avatar Jan 14 '25 08:01 lovasoa

I've opted for overriding the nonce, because I'm not entirely sure of what's in the generated HTML.

I like how easy it is to override default behavior for a single page, it makes me feel confident the tool is flexible enough to adapt for corner cases like this one.

I'm curious whether having all the same variables available in Handlebars templates and SQL pages would enhance flexibility even further.

Thank you very much for the great and quick help!

marcenuc avatar Jan 14 '25 10:01 marcenuc