-
Notifications
You must be signed in to change notification settings - Fork 370
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
[security] Unsafe Inline Script #3876
base: master
Are you sure you want to change the base?
Changes from all commits
50a8c2f
d285b08
f0ae67e
15009dd
e9dd536
ab1fe76
6400b70
4c3815b
e5f085e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -142,6 +142,10 @@ | |
# X-Content-Type-Options: nosniff This is a HTTP response header feature that helps prevent attacks based on MIME-type confusion. | ||
## secure_content_security_policy="script-src 'self' 'unsafe-inline' 'unsafe-eval' *.googletagmanager.com *.doubleclick.net data:;img-src 'self' *.doubleclick.net http://*.tile.osm.org *.tile.osm.org *.gstatic.com data:;style-src 'self' 'unsafe-inline' fonts.googleapis.com;connect-src 'self' *.google-analytics.com;frame-src *;child-src 'self' data: *.vimeo.com;object-src 'none'" | ||
|
||
# Enable nonce attribute to remove unsafe-inline and auto remove unsafe-inline from csp | ||
## csp_nonce=True | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: |
||
|
||
|
||
# Strict-Transport-Security HTTP Strict Transport Security(HSTS) is a policy which is communicated by the server to the user agent via HTTP response header field name "Strict-Transport-Security". HSTS policy specifies a period of time during which the user agent(browser) should only access the server in a secure fashion(https). | ||
## secure_ssl_redirect=False | ||
## secure_redirect_host=0.0.0.0 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -182,14 +182,12 @@ class OnePageViewModel { | |
$.ajax({ | ||
url: scriptUrl, | ||
converters: { | ||
'text script': function (text) { | ||
return text; | ||
} | ||
'text script': text => text | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just out of curiosity, was this change needed or just an improvement? |
||
} | ||
}) | ||
.done(contents => { | ||
loadedJs.push(scriptUrl); | ||
deferred.resolve({ url: scriptUrl, contents: contents }); | ||
deferred.resolve({ url: scriptUrl, contents }); | ||
}) | ||
.fail(() => { | ||
deferred.resolve(''); | ||
|
@@ -201,29 +199,28 @@ class OnePageViewModel { | |
const promises = []; | ||
while (scriptUrls.length) { | ||
const scriptUrl = scriptUrls.shift(); | ||
if (loadedJs.indexOf(scriptUrl) !== -1) { | ||
continue; | ||
if (!loadedJs.includes(scriptUrl)) { | ||
promises.push(loadScript(scriptUrl)); | ||
} | ||
promises.push(loadScript(scriptUrl)); | ||
} | ||
return promises; | ||
}; | ||
|
||
const addGlobalCss = function ($el) { | ||
const cssFile = $el.attr('href').split('?')[0]; | ||
if (loadedCss.indexOf(cssFile) === -1) { | ||
if (!loadedCss.includes(cssFile)) { | ||
loadedCss.push(cssFile); | ||
$.ajaxSetup({ cache: true }); | ||
if (window.DEV) { | ||
$el.attr('href', $el.attr('href') + '?dev=' + Math.random()); | ||
$el.attr('href', `${$el.attr('href')}?dev=${Math.random()}`); | ||
} | ||
$el.clone().appendTo($('head')); | ||
$.ajaxSetup({ cache: false }); | ||
} | ||
$el.remove(); | ||
}; | ||
|
||
// Only load CSS and JS files that are not loaded before | ||
// Process headers to load CSS and JS files | ||
self.processHeaders = function (response) { | ||
const promise = $.Deferred(); | ||
const $rawHtml = $('<span>').html(response); | ||
|
@@ -237,36 +234,39 @@ class OnePageViewModel { | |
$allScripts.remove(); | ||
|
||
$rawHtml.find('link[href]').each(function () { | ||
addGlobalCss($(this)); // Also removes the elements; | ||
addGlobalCss($(this)); | ||
}); | ||
|
||
$rawHtml.find('a[href]').each(function () { | ||
let link = $(this).attr('href'); | ||
if (link.startsWith('/') && !link.startsWith('/hue')) { | ||
link = window.HUE_BASE_URL + '/hue' + link; | ||
link = `${window.HUE_BASE_URL}/hue${link}`; | ||
} | ||
$(this).attr('href', link); | ||
}); | ||
|
||
const scriptPromises = loadScripts(scriptsToLoad); | ||
|
||
const evalScriptSync = function () { | ||
const loadScriptSync = function () { | ||
if (scriptPromises.length) { | ||
// Evaluate the scripts in the order they were defined in the page | ||
const nextScriptPromise = scriptPromises.shift(); | ||
nextScriptPromise.done(scriptDetails => { | ||
if (scriptDetails.contents) { | ||
$.globalEval(scriptDetails.contents); | ||
if (scriptDetails.url) { | ||
const script = document.createElement('script'); | ||
script.src = scriptDetails.url; | ||
script.onload = loadScriptSync; | ||
script.onerror = loadScriptSync; | ||
document.head.appendChild(script); | ||
} else { | ||
loadScriptSync(); | ||
} | ||
evalScriptSync(); | ||
}); | ||
} else { | ||
// All evaluated | ||
promise.resolve($rawHtml.children()); | ||
} | ||
}; | ||
|
||
evalScriptSync(); | ||
loadScriptSync(); | ||
return promise; | ||
}; | ||
|
||
|
@@ -369,46 +369,63 @@ class OnePageViewModel { | |
} | ||
|
||
$.ajax({ | ||
url: | ||
baseURL + | ||
(baseURL.indexOf('?') > -1 ? '&' : '?') + | ||
'is_embeddable=true' + | ||
self.extraEmbeddableURLParams(), | ||
beforeSend: function (xhr) { | ||
xhr.setRequestHeader('X-Requested-With', 'Hue'); | ||
}, | ||
url: `${baseURL}${baseURL.includes('?') ? '&' : '?'}is_embeddable=true${self.extraEmbeddableURLParams()}`, | ||
beforeSend: xhr => xhr.setRequestHeader('X-Requested-With', 'Hue'), | ||
dataType: 'html', | ||
success: function (response, status, xhr) { | ||
success: (response, status, xhr) => { | ||
const type = xhr.getResponseHeader('Content-Type'); | ||
if (type.indexOf('text/') > -1) { | ||
if (type.includes('text/')) { | ||
window.clearAppIntervals(app); | ||
huePubSub.clearAppSubscribers(app); | ||
self.extraEmbeddableURLParams(''); | ||
|
||
self.processHeaders(response).done($rawHtml => { | ||
if (window.SKIP_CACHE.indexOf(app) === -1) { | ||
const scripts = $rawHtml.find('script'); | ||
|
||
// Append JSON scripts | ||
scripts.each(function () { | ||
if (this.getAttribute('type') === 'application/json') { | ||
document.head.appendChild(this); | ||
// Disabling linting because initializeEditorComponent is defined in static folder | ||
// as this needs to be defined here | ||
// eslint-disable-next-line no-undef | ||
initializeEditorComponent(); | ||
} | ||
}); | ||
|
||
// Append other scripts | ||
scripts.each(function () { | ||
if (!['application/json', 'text/html'].includes(this.getAttribute('type'))) { | ||
const script = document.createElement('script'); | ||
script.type = 'text/javascript'; | ||
if (this.src) { | ||
script.src = this.src; | ||
document.head.appendChild(script); | ||
} | ||
$(this).remove(); | ||
} | ||
}); | ||
|
||
if (!window.SKIP_CACHE.includes(app)) { | ||
self.embeddable_cache[app] = $rawHtml; | ||
} | ||
$('#embeddable_' + app).html($rawHtml); | ||
$(`#embeddable_${app}`).html($rawHtml); | ||
huePubSub.publish('app.dom.loaded', app); | ||
window.setTimeout(() => { | ||
self.isLoadingEmbeddable(false); | ||
}, 0); | ||
window.setTimeout(() => self.isLoadingEmbeddable(false), 0); | ||
}); | ||
} else { | ||
if (type.indexOf('json') > -1) { | ||
const presponse = JSON.parse(response); | ||
if (presponse && presponse.url) { | ||
window.location.href = window.HUE_BASE_URL + presponse.url; | ||
return; | ||
} | ||
} else if (type.includes('json')) { | ||
const presponse = JSON.parse(response); | ||
if (presponse && presponse.url) { | ||
window.location.href = `${window.HUE_BASE_URL}${presponse.url}`; | ||
return; | ||
} | ||
window.location.href = window.HUE_BASE_URL + baseURL; | ||
} else { | ||
window.location.href = `${window.HUE_BASE_URL}${baseURL}`; | ||
} | ||
}, | ||
error: function (xhr) { | ||
error: xhr => { | ||
console.error('Route loading problem', xhr); | ||
if ((xhr.status === 401 || xhr.status === 403) && app !== '403') { | ||
if ([401, 403].includes(xhr.status) && app !== '403') { | ||
self.loadApp('403'); | ||
} else if (app !== '500') { | ||
self.loadApp('500'); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -518,3 +518,12 @@ def __init__(self, data, encoder=DjangoJSONEncoder, safe=True, | |
kwargs.setdefault('content_type', 'application/json') | ||
data = json.dumps(data, cls=encoder, **json_dumps_params) | ||
super(JsonResponse, self).__init__(content=data, **kwargs) | ||
|
||
|
||
def nonce_attribute(request): | ||
# Assuming 'csp_nonce' is set in the request and CSP_NONCE controls its usage | ||
if desktop.conf.CSP_NONCE.get(): | ||
Comment on lines
+524
to
+525
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of assuming, why not check for it by using |
||
csp_nonce = request.csp_nonce | ||
if csp_nonce: | ||
return f' nonce={csp_nonce}' | ||
return '' | ||
Comment on lines
+527
to
+529
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Try something like this once? return f' nonce={csp_nonce}' if csp_nonce else '' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit:
true