如何根治 Script Error.

語言: CN / TW / HK

作者:盧峰(清銳)

本文簡要介紹了 Script Error 問題的來龍去脈,但也不侷限於 Script Error,對於通用的系統性問題,應該找到系統性解決方案,進而治標治本。

Script Error 原因與當前解法

受瀏覽器同源策略限制,未知跨域指令碼執行錯誤時,丟擲的錯誤資訊為 "Script error.",導致開發者無法定位具體錯誤。為了獲取詳細錯誤資訊及堆疊,一般解法是給 Script 標籤配置 crossorigin 屬性,同時對應指令碼服務端需配置 Access-Control-Allow-Origin 響應頭。

另外還有一些 hack 解法,對瀏覽器原生 API 做代理,將業務程式碼放在 Try Catch 作用域中執行,但寫好代理方法是不容易的,粗製濫造的代理方法會製造很多隱藏 Bug,並且大量 Try Catch 在一些 JS Engine 中也存在額外效能損耗,為了解決 Script Error 採用此方案得不償失。

還有什麼問題

  1. crossorigin 不好加

非同步載入指令碼套娃,A 載入 B,B 載入 C,以至於不知道載入了哪些外部指令碼

需要服務端配合設定響應頭 Access-Control-Allow-Origin

  1. crossorigin 加不了

外部注入程式碼,如瀏覽器外掛、定製 Webview 容器(xx 瀏覽器)

  1. 無效 Script Error 資料,難以評估對業務實際影響,並且耗費監控資源

溯源:為什麼是 Script Error

從 2006 年一篇安全漏洞文章說起:I know if you're logged-in, anywhere

在那個年代大量網站都是服務端渲染,服務端根據使用者登入態返回不同頁面內容,黑客通過 Script 載入目標站點,使用者已登入、未登入返回的 Response 內容不同,報錯資訊也會有差異,這樣就可以通過報錯資訊區分使用者是否登入,進一步展開針對性的攻擊。

<script src=” http://mail.google.com/mail/”></script> 

已登入:

**未登入: **

對於其他站點也是類似,錯誤資訊中總會有差異,比如亞馬遜登入和未登入,報錯的 LineNo 不同。

基於此,WHATWG 對錯誤資訊透出制定了規範:

Chrome 實現:

《I know if you're logged-in, anywhere》地址:https://blog.jeremiahgrossman.com/2006/12/i-know-if-youre-logged-in-anywhere.html

Script Error 規範是否能調整

通過以上資訊,我們可以理解 Script Error 的設計初衷以及其合理性,但我也有疑問,在今天瀏覽器同源策略比較完善的情況下,是否有必要遮蔽所有資訊(error message、lineno、colno、url)?能否將發生 Script Error 的指令碼 url 暴露出來,以便開發者收集到錯誤資訊時快速定位錯誤來源,這樣也方便評估影響面,比如明顯是注入的指令碼錯誤,直接忽略即可。

翻閱 WHATWG Github 歷史 issue,發現已經有過相關討論,很明確答案是 No,大概原因是當前的同源策略已經很全面(複雜),不想在挖坑。以至於對 unhanlderejection,連 Script Error 都不願意報。

相關討論地址:https://github.com/whatwg/html/issues/2440 <br><br> unhanlderejection地址:https://github.com/whatwg/html/issues/5051

其他大廠如何處理 Script Error

我在幾個大廠網站上做了測試,載入一個第三方指令碼,第三方指令碼一定會報錯,看看對應站點如何處理。

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);

  1. Google:常規處理,直接上報 Script Error

  2. Twitter: 通過 CSP 策略攔截了未知指令碼載入,包括 Github、FaceBook 都採用類似方案

  3. QQ 影片:除了上報 Script Error,並監控上報非同步載入的指令碼

面向未來我們應該如何處理 Script Error

面向未來看問題,我們不能與標準背道而馳,同源策略是當前解決 Web 安全問題的重要手段,在未來只會更完善,我們應該積極瞭解與應用。當前國內網際網路對同源策略的瞭解與應用大多止步於 Access-Control-Allow-Origin: *,這是遠遠不夠的。

因此,面向未來 Script Error 問題 Twitter 的處理方式相對合理,只允許站點載入白名單指令碼,對白名單指令碼逐個做 CrossOring 等配置,同時也杜絕了外部指令碼注入。對於淘寶來說,受限於業務體量以及歷史包袱,做這種改造難度可想而知,但我們應該朝這個方向努力,而不是讓開發者面對 Script Error 手足無措,靠猜測或是加錯誤過濾解決問題。

回到當下,短期的解決方案要增強跨域指令碼的感知能力,可以配置 CSP Report Only 上報跨域指令碼,也可以通過原始手段統計,進而對相關指令碼做跨域配置,對於明顯的跨域指令碼如埋點、喚端、以及安全系列指令碼,缺少 crossorigin 的儘快修復。

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,對於通用的系統性問題,應該找到系統性解決方案,進而治標治本。

參考文件

  1. what is script error(地址:https://blog.sentry.io/2016/05/17/what-is-script-error)

  2. Cryptic "Script Error." reported in Javascript in Chrome and Firefox(地址:https://stackoverflow.com/questions/5913978/cryptic-script-error-reported-in-javascript-in-chrome-and-firefox)

  3. 解決 "Script Error" 的另類思路(地址:https://juejin.cn/post/6844903727820718094#heading-6)

  4. iOS Privacy: Instagram and Facebook can track anything you do on any website in their in-app browser(地址:https://krausefx.com/blog/ios-privacy-instagram-and-facebook-can-track-anything-you-do-on-any-website-in-their-in-app-browser)

  5. I know if you're logged-in, anywhere(地址:https://blog.jeremiahgrossman.com/2006/12/i-know-if-youre-logged-in-anywhere.html)

  6. HTML Spec: Runtime script errors(地址:https://html.spec.whatwg.org/multipage/webappapis.html#runtime-script-errors)

  7. "Script error." message in window.onerror makes bad DevExp trade off(地址:https://github.com/whatwg/html/issues/2440)

  8. unhandledrejection should fire even for muted scripts(地址:https://github.com/whatwg/html/issues/5051)

  9. Content-Security-Policy-Report-Only(地址:https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only)