Angular project blocked after adding CSP header [duplicate] - html

I have created an angular application. which gives the following error in the browser
Refused to execute inline event handler because it violates the following Content Security Policy directive: "script-src 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution. Note that hashes do not apply to event handlers, style attributes and javascript: navigations unless the 'unsafe-hashes' keyword is present.
The reason is that angular injects the following in index.html
<link rel="stylesheet" href="styles.css" media="print" onload="this.media='all'">
Its that onload="this.media='all'"
One solution is to set "optimization" to false, in which case angular doesn't inject this code. But that doesn't sound like the best solution. Any suggestions how to do this?
An other solution, which I think is a little bit better than the previous one, is to create a wrapper component which holds all the styling from styles.scss. In my case, for this to work, I also needed to to set the encapsultaion of that wrapper component to ViewEncapsulation.None

I had to add this on production configuration on angular.json
"optimization": {
"scripts": true,
"styles": {
"minify": true,
"inlineCritical": false
},
"fonts": true
},
Because "optimization": false makes the bundle size too big.

The root cause and fix is covered here: Refused to execute inline event handler because it violates CSP. (SANDBOX) Getting Angular to do it in a better way might be a challenge.
As the error message says, you can allow it with 'unsafe-hashes', but that is a feature of CSP level 3 and only implemented in Chromium browsers.

Related

What does empty href for html <base> tag means?

Suppose my page URL is: http://example.com/path/to/dir/index.html
And I proxy this page via: http://proxyserver.com/path/to/dir/index.html.
Similarly I want all relative URLs in page to be resolved by proxyserver.com instead of example.com. What should be the proper <base> href value?
I want relative URLs on page like
newfile.html to resolve to http://proxyserver.com/path/to/dir/newfile.html
/newfile.html to resolve to http://proxyserver.com/newfile.html
#hash to resolve to http://proxyserver.com/path/to/dir/file.html#hash
Setting <base href="" /> in page does the job correctly but does it have some implication? Does it have different interpretation across browsers? What does empty href value actually mean? Will it work for all frameworks like angular?
I have heard that <base> tag is mandatory for angular apps to initialize and hence removing <base> tag might not work.
NOTE: The website may already contain some <base> tag which I would always like to override.
I also tried <base href="/" /> but it will resolve relative URLs
newfile.html to http://proxyserver.com/newfile.html
and
#hash to http://proxyserver.com/#hash
which is wrong.
Any help is highly appreciated.
<base href="" /> defines the base for all relative paths.
And it obey´s the Highlander principle ("THERE CAN ONLY BE ONE") :-)
Just to check if i understand your aim right. You wrote:
newfile.html to resolve to http://proxyserver.com/path/to/dir/newfile.html
/newfile.html to resolve to http://proxyserver.com/newfile.html
So you do NOT want to have ONE Basepath, but you want that the browser checks multiple paths if there is the document you need?
This is not easily possible (from the Browser perspective). The reason is, that the browser does not know the content of those paths. He has to create requests to those server(s) "Hey nice server, do you have a "newfile.html" in your directory? No? Okay thank you."
Like #Attersson said, if you want to achieve that, you have to create filters and routings in your webserver. That would be no job for the client.
Now about Angular.
In Angular 6, you would write <base href="/"> in your index.html.
AND THEN configure in the Angular.json the base path you really want:
...
"architect": {
"build": {
"builder": "#angular-devkit/build-angular:browser",
"options": {
...
"baseHref": "/myLovedPath/",
...
as a result, Angular will build the application and change <base href="/"> to <base href="/myLovedPath/">
warm regards
The tag specifies the base URL/target for all relative URLs in a document.
There can be at maximum one element in a document, and it must be inside the element.
if you domine like http://proxyserver.com the base url will be / , but if the domine
like this http://proxyserver.com/appname/ so the base url will be like /appname/ and you normaly will set the base url during the deploy with angular cli like this
ng build --prod --bh=/appname/
In this case you have to use **HashLocationStrategy** for routes
app.module.ts
#NgModule({
imports: [
RouterModule.forRoot(routes)
],
exports: [
RouterModule
],
providers: [
{ provide: LocationStrategy, useClass: HashLocationStrategy }
]
})
export class AppRoutingModule { }
{ provide: LocationStrategy, useClass: HashLocationStrategy }
A base URL, or base location, is the unique root URL with which relative URLs can be converted into absolute URLs for a website. Multiple bases are impossible.
More complex, inconsistent policies can be enforced with redirection rules. This duty belongs to the web server. To list few, NginX, Apache and IIS all have the ability to set redirection rules. There you can do whatever you like with regular expressions.
Since this question is tagged Angular, then, since client-wise a page is accessed only after the web server resolves an http request, you would have to create a blank page redirecting to the correct page for every wrong case. Which is of course worse than letting the web server handle redirections.

Content Security Policy with Dynamic Button

I'm implementing Content Security Policy (CSP) on a site. Below is the CSP policy that I'm testing.
Content-Security-Policy: script-src 'self' 'nonce-random' 'strict-dynamic';
The site uses a third party js script library. The script library injects dynamic content on a page. The dynamic content has inline event handlers. Below is a simple HTML page with a script that mimics the site + the third party library's behavior.
<!DOCTYPE html>
<html>
<head>
<title>CSP Test Page</title>
<script nonce="random">
document.addEventListener('DOMContentLoaded', function (event) {
var el = document.createElement('button');
el.innerHTML = 'Click Me';
el.setAttribute('onclick', "doSomething()");
document.body.appendChild(el);
});
function doSomething() {
alert('I did something.');
}
</script>
</head>
<body>
</body>
</html>
The inline event handlers on the dynamically added button triggers the following error message in the Chrome console:
Refused to execute inline event handler because it violates the
following Content Security Policy directive: "script-src 'self'
'nonce-random' 'strict-dynamic'". Either the 'unsafe-inline' keyword,
a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable
inline execution.
Any suggestions on how to address this issue from a CSP standpoint? I can't change the code of the third party library that is adding the dynamically generated content with the inline event handler.
CSP blocks all inline event handlers, including code added by the third-party library, so unfortunately there is no simple way to solve this without refactoring the CSP-incompatible dependency.
In the long term, CSP3 might provide the ability to whitelist trusted scripts inside event handlers via the 'unsafe-hashed-attributes' feature, but this isn't yet shipping in any stable browser.
In the meantime, one possible workaround is manually removing the attribute with the inline event handler after invoking the external library. That is, you could do something like:
var el = document.createElement('button');
library.doStuff(el);
// Now that the library has run we will change the DOM to be compatible with CSP.
el.onclick = function() { doSomething() };
el.removeAttribute('onclick');
Note that assigning a function to the onclick property directly is okay when it comes to CSP, as opposed to setting the onclick attribute on the HTML element, which is blocked by CSP because it converts a string to code. This will work and avoid CSP violations, but it only makes sense if there is a small number of instances of inline event handlers in the library; otherwise this could get quite tedious.
As a side note, your CSP might benefit from fallbacks for older browsers which don't support 'strict-dynamic', similar to this example.

Why can't you create Custom Elements in content scripts?

I attempted to create a custom element in a Chrome extension content script but customElements.define is null.
customElements.define('customElement', class extends HTMLElement {
constructor() {
super();
}
...
});
So apparently Chrome doesn't want content scripts to create custom elements. But why? Is it a security risk?
I can't seem to find anything in Chrome's extension guide that says it's not allowed.
I found the solution reading this page but the information was so cumbersome I wanted to write this answer for future readers (I am using Manifest v3)
Firstly, install the polyfill :
npm install #webcomponents/webcomponentsjs -D
Then add the polyfill in your content_scripts block in your manifest file :
"content_scripts": [{
"matches": [ "..." ],
"js": [
"./node_modules/#webcomponents/webcomponentsjs/webcomponents-bundle.js",
"content.js"
]
}]
(important: you have to load it before your content script of course as the polyfill needs to load before you can use it)
Now it should works. Cheers
Note: the customElements feature is implemented in most modern browsers but for some reasons the interface is not available from a content script because the scripts are run in an isolated environment (not sharing the same window object space from the webpage the extension runs in).
As of now custom element can be used in chrome extensions UI. In Popup ui, option page ui and in the content script as well But it requires a polyfill which is this.
https://github.com/GoogleChromeLabs/ProjectVisBug - this is the one big custom element in the chrome extension.

add multiple chrome inline installations for different link tags same domain

I have a Chrome extension, and a Chrome app. I need inline install for both of them on the same domain.
As per Googles instructions (for one inline install) I add the header link tag:
<link rel="chrome-webstore-item" href="https://chrome.google.com/webstore/detail/itemID">
Then add the onclick function in the body:
<button onclick="chrome.webstore.install()" id="install-button">Add to Chrome</button>
<script>
if (chrome.app.isInstalled) {
document.getElementById('install-button').style.display = 'none';
}
</script>
What I need to know is how to add two instances. One for the extension, and one for the app. Do I add two link tags in the header, then edit the onclick function?
This is what Google says to do for multiple instances, but I don't understand where to edit the onclick function to differentiate between the two.
To actually begin inline installation, the
chrome.webstore.install(url, successCallback, failureCallback)
function must be called. This function can only be called in response
to a user gesture, for example within a click event handler; an
exception will be thrown if it is not. The function can have the
following parameters:
url (optional string) If you have more than one tag on your
page with the chrome-webstore-item relation, you can choose which item
you'd like to install by passing in its URL here. If it is omitted,
then the first (or only) link will be used. An exception will be
thrown if the passed in URL does not exist on the page.
successCallback (optional function) This function is invoked when
inline installation successfully completes (after the dialog is shown
and the user agrees to add the item to Chrome). You may wish to use
this to hide the user interface element that prompted the user to
install the app or extension.
failureCallback (optional function) This
function is invoked when inline installation does not successfully
complete. Possible reasons for this include the user canceling the
dialog, the linked item not being found in the store, or the install
being initiated from a non-verified site. The callback is given a
failure detail string as a parameter. You may wish to inspect or log
that string for debugging purposes, but you should not rely on
specific strings being passed back.
I currently have one link tag in my header for the extension. I need to add another inline installation, on a different page, same domain, but this second onclick code needs to be different so it doesn't refer to the existing link tag in my header.
Many thanks.
<link rel="chrome-webstore-item" href="https://chrome.google.com/webstore/detail/itemID1">
<link rel="chrome-webstore-item" href="https://chrome.google.com/webstore/detail/itemID2">
<button onclick="chrome.webstore.install('https://chrome.google.com/webstore/detail/itemID1')" id="install-button-1">Add App to Chrome</button>
<button onclick="chrome.webstore.install('https://chrome.google.com/webstore/detail/itemID2')" id="install-button-2">Add Extension to Chrome</button>
The very same docs page shows a method for the extensions.
Basically, your extension can inject a <div id="somethingYouExpect"> into the DOM, and the page's script can detect that.
It's a bit clunky though: I was trying to get it to work for test code and didn't manage to do so in a good way, as content scripts are injected either before DOM is constructed at all or after document ready fires. You can bypass that with mutation observers, but meh and your button will be visible for a split second.
You can save yourself some pain, if you're just hiding an element, by injecting a css file hiding it. Or, you can hide the elements from injected code. Either way is somewhat layout-sensitive though.
If you HAVE to be layout-independent and at the same time want something more complex than element hiding, either go the (div inject + mutation observer) route or you can try window.postMessage approach to signal the page to hide the element.
Step by step guide for the extension / CSS variant.
Suppose your extension install UI is contained in an element with id extension-install.
Add a content script to the manifest file:
"content_scripts": [
{
"matches": ["*://yourdomain/*"],
"css": ["iaminstalled.css"],
"run_at": "document_start"
}
],
The CSS:
#extension-install {
display: none !important;
}
So, to recap:
To allow installs of both the app and the extension, you need two <link> tags in the head
To install either you pass the url parameter to chrome.webstore.install
If the app is installed, it will define chrome.app.isInstalled in the page's context. You can check for it from the page to hide the install button.
If the extension is installed, it can inject CSS/JS into page to hide the install button.

Load remote webpage in background page: Chrome Extension

Is it possible to load a remote webpage into a background page using a chrome extension?
"background": {
"page": "local.html"
}
works, but
"background": {
"page": "http://...."
}
fails with the following error:
Could not load background page http://....
No, that's not possible. It is possible since Chrome 22 - see the bottom of the answer.
You can whitelist a https: resource in the manifest file file, so that your background script can manually be constructed. Make sure that you include a fallback resource in your extension, in the case that the network is down:
<!-- ... doctype etc ... (background.html) -->
<script src="https://..../external_bg.js"></script>
<script src="bg.js"></script>
Because of the Content security policy (CSP), you cannot run inline JavaScript, so you have to use external JS files. bg.js may look like:
if (!window.namespace_of_external_bg) {
// Fallback, by defining fallback methods or injecting a new script:
document.write('<script src="fallback_bg.js"></script>');
}
If you want to dynamically construct a page, avoid use of eval-like methods, because these are also forbidden by the CSP. You can write a template, and request external values to populate your template. localStorage can be used to cache variables. For an example on caching external resources, see Chrome extension adding external javascript to current page's html. This answer referred to Content scripts, so the exact method cannot be used to enable caching scripts (because you would need to use eval to load the script). However, the caching technique can still be used.
I have also tried to use the following method, which does not work (included in this answer, so that you don't have to try it yourself):
Create a Blob from the AJAX response, then use webkitURL.createObjectURL to create a temporary URL to load the resource.
// Modification of https://stackoverflow.com/a/10371025
// Instead of `chrome.tabs.executeScript`, use
// x.responseText or x.response (eg when using x.responseType='arraybuffer')
var blob = new Blob([x.responseText], {type: 'application/javascript'});
var url = (window.URL || window.webkitURL).createObjectURL(blob);
var s = document.createElement('script');
s.src = url;
document.head.appendChild(s);
The previous code yields the following error:
Refused to load the script 'blob:chrome-extension%3A//damgmplfpicjkeogacmlgiceidmilllf/96356d24-3680-4188-812e-5661d23e81df' because it violates the following Content Security Policy directive: "script-src 'self' chrome-extension-resource:".
Loading external resources in the background page
Since Chrome 22, it is technically possible (using the unsafe-eval CSP policy) to load non-https resources in the background page. This obviously not recommended because of security concerns (because it's susceptible to the MITM attack, for instance).
Here's an example to load an arbitrary resource and run it in the context of the background script.
function loadScript(url) {
var x = new XMLHttpRequest();
x.onload = function() {
eval(x.responseText); // <---- !!!
};
x.open('GET', url);
x.send();
}
// Usage:
loadScript('http://badpractic.es/insecure.js');
The unsafe-eval CSP policy must be specified.
As usual, to make cross-origin requests, the URL must be whitelisted in the manifest at the permissions section, or the server must enable CORS.
So, the manifest should at least contain:
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"permissions": ["http://badpractic.es/insecure.js"],
"background": {"scripts": ["background.js"] }