diff --git a/src/General/Settings.coffee b/src/General/Settings.coffee index 0b17bfb14..5fe09c685 100644 --- a/src/General/Settings.coffee +++ b/src/General/Settings.coffee @@ -436,7 +436,7 @@ Settings = $.id('lastarchivecheck').textContent = 'never' items = {} - for name in ['archiveLists', 'archiveAutoUpdate', 'captchaLanguage', 'boardnav', 'time', 'backlink', 'fileInfo', 'QR.personas', 'favicon', 'usercss', 'customCooldown'] + for name in ['archiveLists', 'archiveAutoUpdate', 'captchaLanguage', 'boardnav', 'time', 'backlink', 'fileInfo', 'QR.personas', 'favicon', 'usercss', 'customCooldown', 'jsWhitelist'] items[name] = Conf[name] input = inputs[name] event = if name in ['archiveLists', 'archiveAutoUpdate', 'QR.personas', 'favicon', 'usercss'] then 'change' else 'input' diff --git a/src/General/Settings/Advanced.html b/src/General/Settings/Advanced.html index 211f0294d..cc246798d 100644 --- a/src/General/Settings/Advanced.html +++ b/src/General/Settings/Advanced.html @@ -140,3 +140,9 @@ + +
diff --git a/src/config/Config.coffee b/src/config/Config.coffee index d89e6c438..15071db74 100644 --- a/src/config/Config.coffee +++ b/src/config/Config.coffee @@ -792,6 +792,16 @@ Config = """ sjisPreview: false + jsWhitelist: ''' + http://s.4cdn.org + https://s.4cdn.org + http://www.google.com + https://www.google.com + https://www.gstatic.com + 'unsafe-inline' + 'unsafe-eval' + ''' + captchaLanguage: '' time: '%m/%d/%y(%a)%H:%M:%S' diff --git a/src/main/Main.coffee b/src/main/Main.coffee index 2975579a6..06e70ee48 100644 --- a/src/main/Main.coffee +++ b/src/main/Main.coffee @@ -13,21 +13,6 @@ Main = $.ready -> Captcha.fixes.init() return - # Disrupt loading of ads from malicious/irresponsible providers. - $.global -> - nuke = (obj, prop) -> - try - Object.defineProperty obj, prop, - configurable: false - get: -> throw new Error() - set: -> throw new Error() - for prop in ['atOptions', 'adsterra_key', 'EpmadsConfig', 'epmads_key', 'EpomConfig', 'epom_key', 'exoDocumentProtocol', 'supp_key'] - nuke window, prop - return - $.on window, 'beforescriptexecute', (e) -> - host = e.target.src.split('/')[2]?.match(/[^.]+\.[^.]+$/)?[0] - e.preventDefault() if host in ['bnhtml.com', 'ecpmrocks.com', 'advertisation.com', 'exoclick.com', 'n298adserv.com'] - # Detect multiple copies of 4chan X $.on d, '4chanXInitFinished', -> if Main.expectInitFinished @@ -61,11 +46,35 @@ Main = Conf['JSON Navigation'] = true Conf['Oekaki Links'] = true + # Pseudo-enforce default whitelist while configuration loads + $.global -> + {whitelist} = document.currentScript.dataset + whitelist = whitelist.split('\n').filter (x) -> x[0] isnt "'" + oldFun = {} + for key in ['createElement', 'write'] + oldFun[key] = document[key] + document[key] = do (key) -> (arg) -> + s = document.currentScript + if s and s.src and whitelist.indexOf(s.src.split('/')[..2].join('/')) < 0 + throw Error() + oldFun[key].call document, arg + document.addEventListener 'csp-ready', -> + document[key] = oldFun[key] for key of oldFun + , false + , + whitelist: Conf['jsWhitelist'] + # Get saved values as items items = {} items[key] = undefined for key of Conf items['previousversion'] = undefined $.get items, (items) -> + + # Enforce JS whitelist + jsWhitelist = items['jsWhitelist'] ? Conf['jsWhitelist'] + $.addCSP "script-src #{jsWhitelist.replace(/[\s;]+/g, ' ')}" + $.event 'csp-ready' + $.asap docSet, -> # Don't hide the local storage warning behind a settings panel. diff --git a/src/platform/$.coffee b/src/platform/$.coffee index 12087bb12..d2f130701 100644 --- a/src/platform/$.coffee +++ b/src/platform/$.coffee @@ -138,6 +138,18 @@ $.addStyle = (css, id, test='head') -> $.add d.head, style style +$.addCSP = (policy) -> + meta = $.el 'meta', + httpEquiv: 'Content-Security-Policy' + content: policy + if d.head + $.add d.head, meta + $.rm meta + else + head = $.add (doc or d), $.el('head') + $.add head, meta + $.rm head + $.x = (path, root) -> root or= d.body # XPathResult.ANY_UNORDERED_NODE_TYPE === 8 @@ -296,15 +308,16 @@ $.queueTask = do -> taskQueue.push arguments setTimeout execTask, 0 -$.globalEval = (code) -> +$.globalEval = (code, data) -> script = $.el 'script', textContent: code + $.extend script.dataset, data if data $.add (d.head or doc), script $.rm script -$.global = (fn) -> +$.global = (fn, data) -> if doc - $.globalEval "(#{fn})();" + $.globalEval "(#{fn})();", data else # XXX dwb fn()