如何根治 Script Error?
Script Error 原因與當(dāng)前解法
受瀏覽器同源策略限制,未知跨域腳本執(zhí)行錯(cuò)誤時(shí),拋出的錯(cuò)誤信息為 "Script error.",導(dǎo)致開發(fā)者無法定位具體錯(cuò)誤。為了獲取詳細(xì)錯(cuò)誤信息及堆棧,一般解法是給 Script 標(biāo)簽配置 crossorigin 屬性,同時(shí)對(duì)應(yīng)腳本服務(wù)端需配置 Access-Control-Allow-Origin 響應(yīng)頭。
另外還有一些 hack 解法,對(duì)瀏覽器原生 API 做代理,將業(yè)務(wù)代碼放在 Try Catch 作用域中執(zhí)行,但寫好代理方法是不容易的,粗制濫造的代理方法會(huì)制造很多隱藏 Bug,并且大量 Try Catch 在一些 JS Engine 中也存在額外性能損耗,為了解決 Script Error 采用此方案得不償失。
還有什么問題
- crossorigin 不好加
- 異步加載腳本套娃,A 加載 B,B 加載 C,以至于不知道加載了哪些外部腳本
- 需要服務(wù)端配合設(shè)置響應(yīng)頭 Access-Control-Allow-Origin
- crossorigin 加不了
- 外部注入代碼,如瀏覽器插件、定制 Webview 容器(xx 瀏覽器)
- 無效 Script Error 數(shù)據(jù),難以評(píng)估對(duì)業(yè)務(wù)實(shí)際影響,并且耗費(fèi)監(jiān)控資源
溯源:為什么是 Script Error
從 2006 年一篇安全漏洞文章說起:I know if you're logged-in, anywhere在那個(gè)年代大量網(wǎng)站都是服務(wù)端渲染,服務(wù)端根據(jù)用戶登錄態(tài)返回不同頁面內(nèi)容,黑客通過 Script 加載目標(biāo)站點(diǎn),用戶已登錄、未登錄返回的 Response 內(nèi)容不同,報(bào)錯(cuò)信息也會(huì)有差異,這樣就可以通過報(bào)錯(cuò)信息區(qū)分用戶是否登錄,進(jìn)一步展開針對(duì)性的攻擊。
<script src=” http://mail.google.com/mail/”></script>
已登錄:
未登錄:
對(duì)于其他站點(diǎn)也是類似,錯(cuò)誤信息中總會(huì)有差異,比如亞馬遜登錄和未登錄,報(bào)錯(cuò)的 LineNo 不同。
基于此,WHATWG 對(duì)錯(cuò)誤信息透出制定了規(guī)范:
Chrome 實(shí)現(xiàn):
《I know if you're logged-in, anywhere》地址:https://blog.jeremiahgrossman.com/2006/12/i-know-if-youre-logged-in-anywhere.html
Script Error 規(guī)范是否能調(diào)整
通過以上信息,我們可以理解 Script Error 的設(shè)計(jì)初衷以及其合理性,但我也有疑問,在今天瀏覽器同源策略比較完善的情況下,是否有必要屏蔽所有信息(error message、lineno、colno、url)?能否將發(fā)生 Script Error 的腳本 url 暴露出來,以便開發(fā)者收集到錯(cuò)誤信息時(shí)快速定位錯(cuò)誤來源,這樣也方便評(píng)估影響面,比如明顯是注入的腳本錯(cuò)誤,直接忽略即可。翻閱 WHATWG Github 歷史 issue,發(fā)現(xiàn)已經(jīng)有過相關(guān)討論,很明確答案是 No,大概原因是當(dāng)前的同源策略已經(jīng)很全面(復(fù)雜),不想在挖坑。以至于對(duì) unhanlderejection,連 Script Error 都不愿意報(bào)。
相關(guān)討論地址:https://github.com/whatwg/html/issues/2440
unhanlderejection地址:https://github.com/whatwg/html/issues/5051
其他大廠如何處理 Script Error
我在幾個(gè)大廠網(wǎng)站上做了測(cè)試,加載一個(gè)第三方腳本,第三方腳本一定會(huì)報(bào)錯(cuò),看看對(duì)應(yīng)站點(diǎn)如何處理。
var s = document.createElement('script');
s.src = 'https://g.alicdn.com/dinamic/h5-tb-cart/5.0.41/index.min.js';
document.body.appendChild(s);
- Google:常規(guī)處理,直接上報(bào) Script Error
- Twitter: 通過 CSP 策略攔截了未知腳本加載,包括 Github、FaceBook 都采用類似方案
- QQ 視頻:除了上報(bào) Script Error,并監(jiān)控上報(bào)異步加載的腳本
面向未來我們應(yīng)該如何處理 Script Error
面向未來看問題,我們不能與標(biāo)準(zhǔn)背道而馳,同源策略是當(dāng)前解決 Web 安全問題的重要手段,在未來只會(huì)更完善,我們應(yīng)該積極了解與應(yīng)用。當(dāng)前國內(nèi)互聯(lián)網(wǎng)對(duì)同源策略的了解與應(yīng)用大多止步于 Access-Control-Allow-Origin: *,這是遠(yuǎn)遠(yuǎn)不夠的。
因此,面向未來 Script Error 問題 Twitter 的處理方式相對(duì)合理,只允許站點(diǎn)加載白名單腳本,對(duì)白名單腳本逐個(gè)做 CrossOring 等配置,同時(shí)也杜絕了外部腳本注入。對(duì)于淘寶來說,受限于業(yè)務(wù)體量以及歷史包袱,做這種改造難度可想而知,但我們應(yīng)該朝這個(gè)方向努力,而不是讓開發(fā)者面對(duì) Script Error 手足無措,靠猜測(cè)或是加錯(cuò)誤過濾解決問題。
回到當(dāng)下,短期的解決方案要增強(qiáng)跨域腳本的感知能力,可以配置 CSP Report Only 上報(bào)跨域腳本,也可以通過原始手段統(tǒng)計(jì),進(jìn)而對(duì)相關(guān)腳本做跨域配置,對(duì)于明顯的跨域腳本如埋點(diǎn)、喚端、以及安全系列腳本,缺少 crossorigin 的盡快修復(fù)。
CSP Report Only地址:https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
document.querySelectorAll('script[src]:not([crossorigin])')
本文簡要介紹了 Script Error 問題的來龍去脈,但也不局限于 Script Error,對(duì)于通用的系統(tǒng)性問題,應(yīng)該找到系統(tǒng)性解決方案,進(jìn)而治標(biāo)治本。?