Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

getScript requires 'unsafe-inline' CSP rule #3969

Closed
koczkatamas opened this issue Feb 7, 2018 · 29 comments · Fixed by #4763
Closed

getScript requires 'unsafe-inline' CSP rule #3969

koczkatamas opened this issue Feb 7, 2018 · 29 comments · Fixed by #4763

Comments

@koczkatamas
Copy link

jQuery should be able to load scripts via $.getScript without causing unsafe-inline CSP violations.

Look at the following test case:

index.html

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://code.jquery.com 'sha256-DOU0/sVUxitk2MNb5zXL6o3XIe+ZQ5oKG5jmGDO9PmQ='">
<script src="https://code.jquery.com/jquery-3.3.1.js"></script>
<input id="btnJQuery" type="button" value="Load script via jQuery" />
<input id="btnScriptTag" type="button" value="Load script via script tag" />

<script>
  console.log("start");
  
  $("#btnJQuery").on("click", function() {
    $.getScript("payload.js");
  });
  
  $("#btnScriptTag").on("click", function() {
    let scriptEl = document.createElement("script");
    scriptEl.onload = function() { console.log('onload', arguments); };
    scriptEl.onerror = function() { console.log('onerror', arguments); };
    scriptEl.src = "payload.js";
    document.head.appendChild(scriptEl);  
  });
</script>

payload.js

console.log("OK");

If you click on the Load script via jQuery button the following error is shown in the browser's console (Chrome in this case):

jquery-3.3.1.js:111 Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self' https://code.jquery.com 'sha256-DOU0/sVUxitk2MNb5zXL6o3XIe+ZQ5oKG5jmGDO9PmQ='". Either the 'unsafe-inline' keyword, a hash ('sha256-oKrRnv4NSECe1z+2Q8HSC47J9uP6ALT0+UguOrbK7UU='), or a nonce ('nonce-...') is required to enable inline execution.

On the other hand if you click on the Load script via script tag button then the script is loaded without issues and the text OK is shown in the console.

Note: I am reporting this issue, because third-party libraries use $.getScript() and they consider this a jQuery issue (rightly) and won't fix this by their own because they expect the fix from jQuery.

For example see this issue: jackocnr/intl-tel-input#541

Security considerations

Using constructs which require unsafe-inline or unsafe-eval are generally not recommended and the security community considers them as bad practice.

Mozilla MDN states the following about unsafe-inline:

Disallowing inline styles and inline scripts is one of the biggest security wins CSP provides.

Google recommends the same in their Web Fundamentals guide

If you must have inline script and style, you can enable it by adding 'unsafe-inline' as an allowed source in a script-src or style-src directive. You can also use a nonce or a hash (see below), but you really shouldn't. Banning inline script is the biggest security win CSP provides, and banning inline style likewise hardens your application.

you can enable them by adding 'unsafe-eval' as an allowed source in a script-src directive, but we strongly discourage this.

So I hope you will consider changing the current behavior for the sake of a safer web (taking into account how widespread your library is).

@mgol
Copy link
Member

mgol commented Feb 7, 2018

Thanks for the report. This looks like a duplicate of #3541 so I'm closing it.

@mgol mgol closed this as completed Feb 7, 2018
@koczkatamas
Copy link
Author

I am not sure that the linked PR in #3541 resolves this problem. It is similar problem, but I am not sure it's the same that's why I opened this issue.

@dmethvin
Copy link
Member

dmethvin commented Feb 7, 2018

Yeah i don't think it's the same. You can avoid the problem by adding a nonce or hash but we should discuss whether to always inject a script tag for external files.

@dmethvin dmethvin reopened this Feb 7, 2018
@koczkatamas
Copy link
Author

It would be great workaround for me if you could add a global flag for jQuery or an ajaxSetup setting that if enabled, then jQuery would use the script tag + src method for every request. It could be disabled by default of course.

AFAIS this could be a workaround for those who disabled the unsafe-inline usage in their CSP.

@mgol
Copy link
Member

mgol commented Feb 7, 2018

Ideally, as I suggested in #3541 (comment), we'd feature detect script.onerror support and use the script tag + src approach if it's supported. If it's not possible to feature detect that, though, then maybe we should introduce a flag that enables CSP support and avoid stuff that violates it even if it makes some very old browsers unusable.

Feature detection ideas welcome!

@mgol
Copy link
Member

mgol commented Feb 7, 2018

I'll add this to the 3.4.0 milestone, this sounds like something we should resolve.

@mgol mgol added this to the 3.4.0 milestone Feb 7, 2018
@mgol
Copy link
Member

mgol commented Feb 7, 2018

Hmm, instead of detecting script.onerror support we could detect if CSP is enabled by:

var cspMode = ( function() {
  try {
    new Function( "" );
    return false;
  } catch ( e ) {
    return true;
  }
} )();

This is similar to what e.g. AngularJS does. What do you think?

@dmethvin
Copy link
Member

dmethvin commented Feb 7, 2018

Agreed on 3.4.0 and landing this with gh-3782 would be nice in order to deal with all CSP issues in one release. I think it would be better and more consistent to always use a script tag regardless of CSP, if we have to make an exception for Android 4.0 to work then we'd only get/eval there.

@koczkatamas In general we've sworn off global flags because they make it very difficult for a large page full of plugins written at different times by different people to depend on consistent behavior. The jQuery.ajax documentation is already pretty difficult to understand because of all the options and the way they interact. Changing the rules retroactively makes it worse. Subtle behavior changes like this are also difficult for jQuery Migrate to detect and warn about.

Keep in mind that this won't help the original case you linked to since they were using jQuery 1.12 which has not been maintained for a while. They would need to update to 3.4.0 to get this fix.

@koczkatamas
Copy link
Author

@dmethvin If you can auto-detect onerror support then I consider that a superior solution, so feel free to forget my 'global flag' idea. :)

The issue I linked was opened by somebody else, we are using jQuery 3 and we are prepared to switch to latest jQuery at any time. (Especially if the CSP fix lands.)

@mgol please note that new Function( "" ); technically tests for unsafe-eval and not unsafe-inline and AFAIK it will send a warning to the report-uri endpoint configured in the CSP which can cause unnecessary traffic.

I am not sure how, but I think Angular (at least the latest versions) solves this otherwise. We use Angular and we don't get any unsafe-* errors from it.

@mgol
Copy link
Member

mgol commented Feb 7, 2018

I think it would be better and more consistent to always use a script tag regardless of CSP, if we have to make an exception for Android 4.0 to work then we'd only get/eval there.

@dmethvin It's not just Android 4.0 but all <=4.3. How would you make an exception for those Android Browsers without UA-sniffing?

@dmethvin
Copy link
Member

dmethvin commented Feb 8, 2018

@mgol yeah it looks like although Android sets the property to a function if you create an attribute, it doesn't run the function on a 404. Maybe it does on a script error but I didn't try.
https://jsfiddle.net/dmethvin/cxby538z/

@gibson042
Copy link
Member

To document what was discussed in the meeting today, we SHOULD support restrictive CSP configuration but MUST NOT break currently-working strict ordering of script evaluation in domManip (property 1 in #2126 (comment) ). A proper fix will probably adopt script elements for even same-site content, but also implement manual serialization similar to the suggestion (leveraging something like rel=preload if performance is an issue).

@timmywil
Copy link
Member

Per last meeting, the workaround here is to set crossDomain: true in the options. This will force a script tag. This may not get changed until 4.0 because of the behavior change switching from sync to async.

@timmywil timmywil reopened this Feb 26, 2018
@graingert
Copy link

@timmywil is it possible to set crossDomain: true in https://api.jquery.com/load/ or https://api.jquery.com/html/

@dmethvin
Copy link
Member

@graingert instead of $.load just use $.ajax and pass crossDomain: true in the options. There isn't a documented way to affect how .html() loads scripts but it's not a good idea to do it that way.

@timmywil timmywil modified the milestones: 3.4.0, 4.0.0 Mar 19, 2018
@dmethvin
Copy link
Member

Just noting here that when gh-3782 lands it will be possible to use the scriptAttrs option in a $.ajax() call to force the use of the script transport. That allows use of attributes like nonce in the tag and perhaps a little more intuitive than setting crossDomain.

Note that because things like $.getScript and $.load were meant to be simpler APIs with fewer options and more assumptions about what you wanted, this new functionality won't be exposed for those APIs. You could use $.get() since it accepts a full options object in 3.0+ but personally I'd just stick with $.ajax().

@jfoclpf
Copy link

jfoclpf commented Apr 23, 2018

just a question @dmethvin

how do I now use a $.getScript() with a defined nonce?
shall I use instead $.ajax()?

Thanks

@dmethvin
Copy link
Member

When gh-3782 lands you would use $.ajax() and add to the options { dataType: "script", scriptAttrs: { nonce: "1239...abc" } } to the options with your nonce. I'd expect this to ship in 3.4.0.

@jfoclpf
Copy link

jfoclpf commented Apr 24, 2018

@dmethvin perfect, I see that such commit was already merged into the main branch, and I wait then to the next version. Thank you

@graingert

This comment has been minimized.

@dmethvin

This comment has been minimized.

@graingert

This comment has been minimized.

@pir8radio
Copy link

So, trying to follow progress. what was done to resolve jquery from returning an inline script? Or whatever fix was done to permit a secure CSP?

@jfoclpf
Copy link

jfoclpf commented Feb 9, 2019

@pir8radio it seems from jquery 3.2.0, you can use something like this:

$.ajax({
  dataType : "script",
  url      : "https://some/path",
  attrs    : { nonce: "EDNnf03nceIOfn39fn3e9h3sdfa" },
  success  : callback
});

@jfoclpf

This comment has been minimized.

@dmethvin

This comment has been minimized.

@jquery jquery locked as resolved and limited conversation to collaborators Feb 9, 2019
@mgol mgol added the Ajax label Jul 23, 2020
@mgol
Copy link
Member

mgol commented Jul 23, 2020

I've just looked at this issue and I'm a bit puzzled by @gibson042's comment:

To document what was discussed in the meeting today, we SHOULD support restrictive CSP configuration but MUST NOT break currently-working strict ordering of script evaluation in domManip (property 1 in #2126 (comment) ). A proper fix will probably adopt script elements for even same-site content, but also implement manual serialization similar to the suggestion (leveraging something like rel=preload if performance is an issue).

Maybe I'm missing something but this seems to concern synchronous script execution that we do during DOM manipulation but this issue is about $.getScript which is already asynchronous. As far as I understand, the only blocker is that some legacy browsers like Android Browser can't do error handling for non-inline scripts as they lack the onerror attribute support. Does that mean we can just flip the behavior of $.getScript for 4.0 without all that complexity? The issue about synchronous script execution in DOM manip is already tracked in #1895.

@mgol mgol added the Discuss in Meeting Reserved for Issues and PRs that anyone would like to discuss in the weekly meeting. label Jul 23, 2020
@timmywil timmywil removed the Discuss in Meeting Reserved for Issues and PRs that anyone would like to discuss in the weekly meeting. label Jul 27, 2020
@mgol mgol self-assigned this Jul 27, 2020
mgol added a commit to mgol/jquery that referenced this issue Jul 29, 2020
Until now, the AJAX script transport only used a script tag to load scripts
for cross-domain requests or ones with `scriptAttrs` set. This commit makes
it also used for all async requests to avoid CSP errors arising from usage
of inline scripts. This also makes `jQuery.getScript` not trigger CSP errors
as it uses the AJAX script transport under the hood.

For sync requests such a change is impossible and that's what `jQuery._evalUrl`
uses. Fixing that is tracked in jquerygh-1895.

The commit also makes other type of requests using the script tag version of the
script transport set its type to "GET", namely async scripts & ones with
`scriptAttrs` set in addition to the existing cross-domain ones.

Fixes jquerygh-3969
@mgol
Copy link
Member

mgol commented Jul 29, 2020

PR: #4763.

@mgol
Copy link
Member

mgol commented Aug 24, 2020

Adding the Behavior Change label per @dmethvin's comment: #4763 (review)

mgol added a commit to mgol/jquery that referenced this issue Aug 24, 2020
Until now, the AJAX script transport only used a script tag to load scripts
for cross-domain requests or ones with `scriptAttrs` set. This commit makes
it also used for all async requests to avoid CSP errors arising from usage
of inline scripts. This also makes `jQuery.getScript` not trigger CSP errors
as it uses the AJAX script transport under the hood.

For sync requests such a change is impossible and that's what `jQuery._evalUrl`
uses. Fixing that is tracked in jquerygh-1895.

The commit also makes other type of requests using the script tag version of the
script transport set its type to "GET", namely async scripts & ones with
`scriptAttrs` set in addition to the existing cross-domain ones.

Fixes jquerygh-3969
mgol added a commit that referenced this issue Aug 25, 2020
Until now, the AJAX script transport only used a script tag to load scripts
for cross-domain requests or ones with `scriptAttrs` set. This commit makes
it also used for all async requests to avoid CSP errors arising from usage
of inline scripts. This also makes `jQuery.getScript` not trigger CSP errors
as it uses the AJAX script transport under the hood.

For sync requests such a change is impossible and that's what `jQuery._evalUrl`
uses. Fixing that is tracked in gh-1895.

The commit also makes other type of requests using the script tag version of the
script transport set its type to "GET", namely async scripts & ones with
`scriptAttrs` set in addition to the existing cross-domain ones.

Fixes gh-3969
Closes gh-4763
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Development

Successfully merging a pull request may close this issue.

8 participants