Refused to evaluate script because it violates the following Content Security Policy directive: "script-src 'self'" - google-chrome

I am learning to develop a Browser Action extension for Google Chrome, and have split up javascript functionality into multiple files. In the popup.html file, script resources are defined like
<script src="js/Identity.js"></script>
<script src="js/View.js"></script>
View.js needs to call into methods of the object exposed from Identity.js, and passes a callback function to be notified when the process is completed. However, it appears Chrome would break execution.
Refused to evaluate script because it violates the following Content
Security Policy directive: "script-src 'self'"
From what i understand, Google states that policy is to prevent arbitrary strings to be evaluated into an executable block of logic. However I am passing actual functions between my objects so i'm not too sure what must be corrected here?
IdentityObj.Process = function (params, callback) {
doSomeWork();
setTimeout(callback(true), 1000); // break here
};
From the View object, an example would be
View.loginClick = function(event) {
event.preventDefault();
this.loggingInState();
var emailAddr = $('#emailAddr').val();
var password = $('#password').val();
IdentityObj.login(emailAddr, password, this.loginCallback.bind(this));
};
View.loginCallback = function(success) {
if (success) { this.usageState(); }
else { this.errorState(); }
};

My colleague sported the problem and explained it, so now I understand what you were referring to.
I was executing the callback function direct in the setTimeout() definition, so setTimeout() receives the result of callback(true) instead of the callback itself. That would then require an eval and thus triggering the Chrome security policy.
The execution of callback() has to be wrapped in a function declaration.
IdentityObj.Process = function (params, callback) {
doSomeWork();
setTimeout(function(){callback(true)}, 1000); // break here
};

Related

Apps Script webhooks and Access-Control-Allow-Origin header missing

I have a Google Apps Script project acting as a webhook. When calling the endpoint using a library like htmx, the preflight check fails and the request subsequently fails. When calling directly with fetch or XMLHttpRequest, it works fine.
I have a sample endpoint with a simple doPost for testing:
const doPost = (request = {}) => {
const { postData: { contents, type } = {} } = request;
return ContentService.createTextOutput(contents);
};
This Codepen sample shows how requests with HTMX fail while fetch and XHRHttpRequest are successful.
Some things I've learned:
The OPTIONS header sent in a preflight results in a 405 error, aborting the request entirely. You can mimic this by sending an OPTIONS request via Postman (or similar) to the web app URL.
The error doesn't include Access-Control-Allow-Origin header, which is what shows as the failure reason in the console.
HTMX sends non-standard headers, which trigger preflight requests in modern browsers. However, you can strip all headers out, which should bypass the preflight, but doesn't. See this related discussion in the repo.
In this kind of situation, what is the best method for debugging? I'm not really sure what else to try to get this working.
This is an issue with HTMX, that requires modification of its source code. The source of the problem is that HTMX adds some event listeners to xhr.upload that makes the browsers mark the request as "not simple", triggering the CORS preflight request:
If the request is made using an XMLHttpRequest object, no event listeners are registered on the object returned by the XMLHttpRequest.upload property used in the request; that is, given an XMLHttpRequest instance xhr, no code has called xhr.upload.addEventListener() to add an event listener to monitor the upload
The specific part of HTMX source code:
forEach(['loadstart', 'loadend', 'progress', 'abort'], function(eventName) {
forEach([xhr, xhr.upload], function (target) {
target.addEventListener(eventName, function(event){
triggerEvent(elt, "htmx:xhr:" + eventName, {
lengthComputable:event.lengthComputable,
loaded:event.loaded,
total:event.total
});
})
});
});
Sadly, the addEventListener uses anonymous functions, so there's no way to remove them with removeEventListener AFAIK.
But if you are willing to use a custom HTMX script in your app until the authors fix this issue (e.g. add ability to prevent event listener creation on xhr.upload), just remove the xhr.upload from the list in the second row:
forEach(['loadstart', 'loadend', 'progress', 'abort'], function(eventName) {
forEach([xhr], function (target) {
target.addEventListener(eventName, function(event){
triggerEvent(elt, "htmx:xhr:" + eventName, {
lengthComputable:event.lengthComputable,
loaded:event.loaded,
total:event.total
});
})
});
});
With this modification your original suggestion of removing non-standard HTMX-specific headers via evt.detail.headers = [] will work, since now this request becomes "simple", so no more CORS preflight is made by the browsers.
Note: the modification may break the HTMX file upload, I did not test it.

How to capture $compile or $digest error? (AngularJS directive with templateUrl)

I'm writing a unit test of an AngularJS 1.x directive.
If I use "template" it works.
If I use "templateUrl" it does not work (the directive element remains the same original HTML instead of being "compiled").
This is how I create the directive element to test in Jasmine:
function createDirectiveElement() {
scope = $rootScope.$new();
var elementHtml = '<my-directive>my directive</my-directive>';
var element = $compile(elementHtml)(scope);
scope.$digest();
if (element[0].tagName == "my-directive".toUpperCase()) throw Error("Directive is not compiled");
return element;
};
(this does not actually work, see Update for real code)
I'm using this workaround to use the $httpBackend from ngMockE2E (instead of the one in ngMock). In the browser developer "network" tab I don't see any request to the template file. It seems to work because I solved the error "Object # has no method 'passThrough'".
I know that the call to the template is done asynchronously using the $httpBackend (this means $compile exit before the template is really applied).
My question is:
obviously $compile is not doing what I expect. How can I trap this error?
If I use a wrong address in the templateUrl I don't receive any error.
How can I found the problem happened when I called $compile(directive) or scope.$digest() ?
Thanks,
Alex
[Solution]
As suggested by #Corvusoft I inject $exceptionHandler and I check for errors after every test.
In the end this is the only code I have added:
afterEach(inject(function ($exceptionHandler) {
if ($exceptionHandler.errors.length > 0)
throw $exceptionHandler.errors;
}));
Now I can clearly see the errors occurred in the Jasmine test result (instead of search for them in the console), example:
Error: Unexpected request: GET /api/category/list
No more request expected,Error: Unexpected request: GET /api/category/list
No more request expected thrown
And, most important, my tests does not pass in case there are some errors.
[Update to show real example case]
Actually the real code to make templateUrl work use asynchronous beforeEach ("done") and a timeout to wait the end of compile/digest.
My directive use some prividers/services and the template contains other directives which in turn use their templateUrl and make calls to some APIs in the link function().
This is the current (working) test code:
// workaround to have .passThrough() in $httpBackend
beforeEach(angular.mock.http.init); // set $httpBackend to use the ngMockE2E to have the .passThrough()
afterEach(angular.mock.http.reset); // restore the $httpBackend to use ngMock
beforeEach(inject(function (_$compile_, _$rootScope_, _$http_, $httpBackend, $templateCache, $injector) {
$compile = _$compile_;
$rootScope = _$rootScope_;
$http = _$http_;
$httpBackend.whenGET(/\/Scripts of my app\/Angular\/*/).passThrough();
$httpBackend.whenGET(/\/api\/*/).passThrough(); // comment out this to see the errors in Jasmine
}));
afterEach(inject(function ($exceptionHandler) {
if ($exceptionHandler.errors.length > 0)
throw $exceptionHandler.errors;
}));
beforeEach(function(done) {
createDirectiveElementAsync(function (_element_) {
element = _element_;
scope = element.isolateScope();
done();
});
});
function createDirectiveElementAsync(callback) {
var scope = $rootScope.$new();
var elementHtml = '<my-directive>directive</my-directive>';
var element = $compile(elementHtml)(scope);
scope.$digest();
// I haven't found an "event" to know when the compile/digest end
setTimeout(function () {
if (element.tagName == "my-directive".toUpperCase()) throw Error("Directive is not compiled");
callback(element);
}, 0.05*1000); // HACK: change it accordingly to your system/code
};
it("is compiled", function () {
expect(element).toBeDefined();
expect(element.tagName).not.toEqual("my-directive".toUpperCase());
});
I hope this example helps someone else.
$exceptionHandler
Any uncaught exception in AngularJS expressions is delegated to this
service. The default implementation simply delegates to $log.error
which logs it into the browser console.
In unit tests, if angular-mocks.js is loaded, this service is overridden by mock $exceptionHandler which aids in testing.
angular.
module('exceptionOverwrite', []).
factory('$exceptionHandler', ['$log', 'logErrorsToBackend', function($log, logErrorsToBackend) {
return function myExceptionHandler(exception, cause) {
logErrorsToBackend(exception, cause);
$log.warn(exception, cause);
};
}]);

Chrome Packaged App with SQLite?

I was trying to integrate sql.js(JS based SQLite https://github.com/kripken/sql.js/) into my chrome app but as I launch my app, console shows the following errors:
Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "default-src 'self' chrome-extension-resource:". Note that 'script-src' was not explicitly set, so 'default-src' is used as a fallback.
Uncaught 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: "default-src 'self' chrome-extension-resource:".
My manifest file looks like this:
{
"manifest_version": 2,
"name": "Chrome App",
"description": "This is the test app!!!",
"version": "1",
"icons": {
"128": "icon_128.png"
},
"permissions": ["storage"],
"app": {
"background": {
"scripts": ["background.js"]
},
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"
},
"minimum_chrome_version": "28"
}
#MarcRochkind I would like to add some knowledge to your book for integrating SQL.js in Chrome Apps.
It is very well possible with very little effort (considered the obedience of policies and rules).
In order to integrate anything that uses eval, you need to sandbox that particular part of the script. In case of SQL.js, it's the entire library.
This can be done with an iframe which needs to be set in the main .html document that's called for creating a (or the main) window, e.g. chrome.app.window.create('index-app.html', { ..
The base of communication between the main document and the iframe will be by using postMessage for sending and receiving messages.
Let's say the source of this iframe is called /iframes/sqljs-sandboxed.html.
In the manifest.json you need to specify sqljs-sandboxed.html as a sandbox. A designated sandbox makes it possible to run eval and eval-like constructs like new Function.
{
"manifest_version": 1,
"name": "SQL.js Test",
..
"sandbox": {
"pages": [
"iframes/sqljs-sandboxed.html",
]
}
}
The sqljs-sandboxed.html uses an event listener to react on an event of type message. Here you can simply add logic (for simplicity sake I used a switch statement) to do anything structured with SQL.js.
The content of sqljs-sandboxed.html as an example:
<script src="/vendor/kripken/sql.js"></script>
<script>
(function(window, undefined) {
// a test database
var db = new SQL.Database();
// create a table with some test values
sqlstr = "CREATE TABLE hello (a int, b char);";
sqlstr += "INSERT INTO hello VALUES (0, 'hello');";
sqlstr += "INSERT INTO hello VALUES (1, 'world');";
// run the query without returning anything
db.run(sqlstr);
// our event listener for message
window.addEventListener('message', function(event) {
var params = event.data.params,
data = event.data.data,
context = {};
try {
switch(params.cmd) {
case '/do/hello':
// process anything with sql.js
var result = db.exec("SELECT * FROM hello");
// set the response context
context = {
message: '/do/hello',
hash: params.hash,
response: result
};
// send a response to the source (parent document)
event.source.postMessage(context, event.origin);
// for simplicity, resend a response to see if event in
// 'index-app.html' gets triggered a second time (which it
// shouldn't)
setTimeout(function() {
event.source.postMessage(context, event.origin);
}, '1000');
break;
}
} catch(err) {
console.log(err);
}
});
})(window);
</script>
A test database is created only once and the event listener mirrors an API using a simple switch. This means in order to use SQL.js you need to write against an API. This might be at, first glance, a little uncomfortable but in plain sense the idea is equivalent when implementing a REST service, which is, in my opinion, very comfortable in the long run.
In order to send requests, the index-app.html is the initiator. It's important to point out that multiple requests can be made to the iframe asynchronously. To prevent cross-fire, a state parameter is send with each request in the form of an unique-identifier (in my example unique-ish). At the same time a listener is attached on the message event which filters out the desired response and triggers its designated callback, and if triggered, removes it from the event stack.
For a fast demo, an object is created which automates attachment and detachment of the message event. Ultimately the listen function should eventually filter on a specific string value, e.g. sandbox === 'sql.js' (not implemented in this example) in order to speed up the filter selection for the many message events that can take place when using multiple iframes that are sandboxed (e.g. handlebars.js for templating).
var sqlRequest = function(request, data, callback) {
// generate unique message id
var hash = Math.random().toString(36).substr(2),
// you can id the iframe as wished
content_window = document.getElementById('sqljs-sandbox').contentWindow,
listen = function(event) {
// attach data to the callback to be used later
this.data = event.data;
// filter the correct response
if(hash === this.data.hash) {
// remove listener
window.removeEventListener('message', listen, false);
// execute callback
callback.call(this);
}
};
// add listener
window.addEventListener('message', listen, false);
// post a request to the sqljs iframe
content_window.postMessage({
params: {
cmd: request,
hash: hash
},
data: data
}, '*');
};
// wait for readiness to catch the iframes element
document.addEventListener('DOMContentLoaded', function() {
// faking sqljs-sandboxed.html to be ready with a timeout
setTimeout(function() {
new sqlRequest('/do/hello', {
allthedata: 'you need to pass'
}, function() {
console.log('response from sql.js');
console.log(this.data);
});
}, '1000');
});
For simplicity, I'm using a timeout to prevent that the request is being send before the iframe was loaded. From my experience, it's best practice to let the iframe post a message to it's parent document that the iframe is loaded, from here on you can start using SQL.js.
Finally, in index-app.html you specify the iframe
<iframe src="/iframes/sqljs-sandboxed.html" id="sqljs-sandbox"></iframe>
Where the content of index-app.html could be
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<iframe src="/iframes/sqljs-sandboxed.html" id="sqljs-sandbox"></iframe>
<h1>Hello, let's code with SQL.js!</h1>
<script src="/assets/js/sqljs-request.js"></script>
</body>
</html>
"content_security_policy" is not a documented manifest property of Chrome Apps.
To my knowledge, sql.js is not compatible with Chrome Apps, as your error message indicates.
A variation of SQLite, Web SQL, is specifically documented as not working with Chrome Apps.
IndexedDB does work with Chrome Apps, but (a) it's not SQL-based, and (b) it's of limited utility because it's sandboxed and data is not visible to other apps, not even other Chrome Apps.
Your reference to "Chrome Packaged Apps" may mean that you're thinking of legacy "packaged apps," which operate under different rules than the newer Chrome Apps. However, packaged apps are no longer supported by Google and should not be developed. Perhaps you were looking at documentation or examples of package apps, not Chrome Apps.

How do you detect that a script was loaded *and* executed in a chrome extension?

I've been tracking down a bug for days... then I realized the bug was me. :/
I had been using webRequest.onComplete, filtered for scripts. My error was that I made the incorrect association between the scripts being loaded and being executed. The get loaded in a different order than they get executed, and thus the timing of the events is not in the order I need them in. I need to inject between certain scripts so I need an event right after a file has been executed and before the next one.
The only solution I can think of at the moment is to alter the JS being loaded before it gets executed. But it makes my stomach turn. And the bfcache would wreak even more havoc, so not a great solution either.
I would use the HTML5 spec's afterscriptexecute, but that is not implemented in Chrome. Is there another API, perhaps an extension API that I can use?
Note: This method no longer works as of Chrome 36. There are no direct alternatives.
Note: The answer below only applies to external scripts, i.e. those loaded with <script src>.
In Chrome (and Safari), the "beforeload" event is triggered right before a resource is loaded. This event allows one to block the resource, so that the script is never fetched. In this event, you can determine whether the loaded resource is a script, and check whether you want to perform some action
This event can be used to emulate beforescriptexecute / afterscriptexecute:
document.addEventListener('beforeload', function(event) {
var target = event.target;
if (target.nodeName.toUpperCase() !== 'SCRIPT') return;
var dispatchEvent = function(name, bubbles, cancelable) {
var evt = new CustomEvent(name, {
bubbles: bubbles,
cancelable: cancelable
});
target.dispatchEvent(evt);
if (evt.defaultPrevented) {
event.preventDefault();
}
};
var onload = function() {
cleanup();
dispatchEvent('afterscriptexecute', true, false);
};
var cleanup = function() {
target.removeEventListener('load', onload, true);
target.removeEventListener('error', cleanup, true);
}
target.addEventListener('error', cleanup, true);
target.addEventListener('load', onload, true);
dispatchEvent('beforescriptexecute', true, true);
}, true);
The dispatch times are not 100% identical to the original ones, but it is sufficient for most cases. This is the time line for the (non-emulated) events:
beforeload Before the network request is started
beforescriptexecute Before a script executes
afterscriptexecute After a script executes
onload After the script has executed
Here's an easy way to see that the events are working as expected:
window.addEventListener('afterscriptexecute', function() {
alert(window.x);
});
document.head.appendChild(document.createElement('script')).src = 'data:,x=1';
document.head.appendChild(document.createElement('script')).src = 'data:,x=2';
The demo can be seen live at http://jsfiddle.net/sDaZt/
I'm not familiar with Chrome Extensions (only browser javascript), but I think that you will unfortunately have to edit your loaded JS so that is calls a function of your choice when it is executed, if you want to do this nicely. This it what Google does for asynchronously loading its Maps Javascript file:
function loadScript() {
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "http://maps.googleapis.com/maps/api/js?sensor=false&callback=executed";
document.body.appendChild(script);
}
function executed() {
/* Google maps has finished loading, do awesome things ! */
}
If you really don't want to edit your loaded JS files, you could have a setInterval (or a recursive function with setTimeout) checking regularly if some functions or variables are initialized.
Have you tried script loading using Modernizr.js?
I had a similar issue, where the timing of script loading was causing conflict. I used Modernizr.js, which includes the library yepnope.js by default. Below is an example of some scripts I loaded conditionally. You can include a test clause, or simply load them in the order you prefer, with the guarantee that they will load and execute in the order you wish due to the callback.
Here is an example with a conditional clause:
Modernizr.load({
test: false, //Or whatever else you'd like. Can be conditional, or not so conditional
yep: {
'script1': 'MyJavascriptLibrary1.js'
},
nope: {
'script2': 'MyJavascriptLibrary2.js',
'script3': 'MyJavascriptLibrary3.js'
},
callback: {
'script1': function (url, result, key) {
console.log('MyJavascriptLibrary1.js loaded'); //will not load in this example
},
'script2': function (url, result, key) {
console.log('MyJavascriptLibrary2.js loaded first');
},
'script3': function (url, result, key) {
console.log('MyJavascriptLibrary3.js loaded second');
}
}
});
If triggering false, MyJavascriptLibrary2.js and MyJavascriptLibrary3.js will load in the appropriate order, no matter what elements influence how they would behave normally (file size, connection speed, etc.). In these callbacks, you may fire additional javascript as well, in the order you wish to do so. Example:
'script2': function (url, result, key) {
alert('anything in here will fire before MyJavascriptLibrary3.js executes');
},
Note this can be done without Modernizr.load({...
but using simply yepnope({...
For more documentation, check out the yepnope.js API

Using jQuery.getJSON in Chrome Extension

I need to do a cross-domain request in a chrome extension. I know I can it via message passing but I'd rather stick to just jQuery idioms (so my javascript can also work as a <script src="">).
I do the normal:
$.getJSON("http://api.flickr.com/services/feeds/photos_public.gne?tags=cat&tagmode=any&format=json&jsoncallback=?", function(data) {
console.log(data);
});
but in the error console I see:
Uncaught ReferenceError: jsonp1271044791817 is not defined
Is jQuery not inserting the callback function correctly into the document? What can I do to make this work?
(If I paste the code into a chrome console, it works fine, but if I put it as the page.js in an extension is when the problem appears.)
Alas, none of these worked, so I ended up doing the communication via the background.html.
background.html
<script src="http://code.jquery.com/jquery-1.4.2.js"></script>
<script>
function onRequest(request, sender, callback) {
if (request.action == 'getJSON') {
$.getJSON(request.url, callback);
}
}
chrome.extension.onRequest.addListener(onRequest);
</script>
javascripts/page.js
chrome_getJSON = function(url, callback) {
console.log("sending RPC");
chrome.extension.sendRequest({action:'getJSON',url:url}, callback);
}
$(function(){
// use chrome_getJSON instead of $.getJSON
});
If you specify "api.flickr.com" in your manifest.json file you will not need to use the JSONP callback, script injection style of cross domain request.
For example:
"permissions": ["http://api.flickr.com"],
This should work beautifully in you code. I would remove the querystring parameter "&jsoncallback" as there is no JSONP work needed.
The reason why your current code is not working is your code is injecting into pages DOM, content scripts have access to the DOM but no access to javascript context, so there is no method to call on callback.
My impressions it that this fails because the jQuery callback function is being created within the 'isolated world' of the Chrome extension and is inaccessible when the response comes back:
http://code.google.com/chrome/extensions/content_scripts.html#execution-environment
I'm using Prototype and jQuery for various reasons, but my quick fix should be easy to parse:
// Add the callback function to the page
s = new Element('script').update("function boom(e){console.log(e);}");
$$('body')[0].insert(s);
// Tell jQuery which method to call in the response
function shrink_link(oldLink, callback){
jQuery.ajax({
type: "POST",
url: "http://api.awe.sm/url.json",
data: {
v: 3,
url: oldLink,
key: "5c8b1a212434c2153c2f2c2f2c765a36140add243bf6eae876345f8fd11045d9",
tool: "mKU7uN",
channel: "twitter"
},
dataType: "jsonp",
jsonpCallback: callback
});
}
// And make it so.
shrink_link('http://www.google.com', "boom");
Alternatively you can try using the extension XHR capability:
http://code.google.com/chrome/extensions/xhr.html
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://api.example.com/data.json", true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
// JSON.parse does not evaluate the attacker's scripts.
var resp = JSON.parse(xhr.responseText);
}
}
xhr.send();
The syntax is a little off. There's no need for the callback( bit. This works flawlessly. Tested in the javascript console of Chrome on this StackOverflow page (which includes jQuery):
$.getJSON("http://api.flickr.com/services/feeds/photos_public.gne?tags=cat&tagmode=any&format=json&jsoncallback=?", function(data) {
console.log(data);
});
As many of you will know, Google Chrome doesn't support any of the handy GM_ functions at the moment.
As such, it is impossible to do cross site AJAX requests due to various sandbox restrictions (even using great tools like James Padolsey's Cross Domain Request Script)
I needed a way for users to know when my Greasemonkey script had been updated in Chrome (since Chrome doesn't do that either...). I came up with a solution which is documented here (and in use in my Lighthouse++ script) and worth a read for those of you wanting to version check your scripts:
http://blog.bandit.co.nz/post/1048347342/version-check-chrome-greasemonkey-script