安全 · VERIFY
不必相信我們,
打開瀏覽器自己看。
〈安全〉說:前端只向本站要東西、不用 cookie、不在你的瀏覽器裡存資料。這一頁教你用瀏覽器內建的開發者工具,親手檢查這幾句話。不必安裝任何東西,大約十分鐘。
你會看到三件事:算一次盤,瀏覽器送出哪些請求、每一個送到哪個網域;瀏覽器裡有沒有留下 cookie 或其他儲存;伺服器的回應帶了哪些安全標頭。從瀏覽器看不到的部分——伺服器收到之後做了什麼——最後一節照實說。
01開始之前
- 用桌機或筆電上的 Chrome、Edge、Firefox 或 Safari。手機的做法在第 06 節。
- 開一個無痕(私密)視窗,並確認擴充功能沒有在裡面執行。擴充功能自己的請求也會出現在清單裡,會讓你分不清哪些是本站的。
- 不要用真實的出生資料。計算頁有一顆「載入合成範例(不是真實生辰)」按鈕,下面的步驟都用它。
- 開發者工具的介面語言可能是中文,按鈕名稱會和下面寫的英文不同;位置是一樣的。
02打開 Network 分頁,保留記錄
Network(網路)分頁會列出頁面向伺服器要的每一樣東西。「保留記錄」讓清單在重新載入頁面時不被清空,這樣頁面一打開就送出的請求也看得到。
Chrome 與 Edge
- 打開計算頁 https://projectarmillary.com/zh-TW/calculate。
- 按 F12(Mac 按 ⌥⌘I),或在頁面上按右鍵選「檢查」。
- 點上方的 Network 分頁(圖中 ①)。
- 勾選 Preserve log(圖中 ②)。
- 在清單的欄位標題上按右鍵,勾選 Domain,清單會多出一欄網域(圖中 ③)。
- 按 Ctrl+R(Mac 按 ⌘R)重新載入頁面。
Firefox
- 打開計算頁,按 F12(Mac 按 ⌥⌘I)。
- 點 Network 分頁。
- 點 Network 分頁右上角的齒輪,勾選 Persist Logs——這是 Firefox 對「保留記錄」的叫法。
- Firefox 的清單預設就有 Domain 欄;沒有的話在欄位標題上按右鍵加上。
- 重新載入頁面。
Safari
- 先打開開發者功能:Safari 選單 → 設定 → 進階 → 勾選「顯示網頁開發者功能」(較舊的版本叫「在選單列中顯示『開發』選單」)。
- 打開計算頁,按 ⌥⌘I 開啟網頁檢閱器。
- 點 Network 分頁,打開分頁上方的 Preserve Log。
- 需要網域欄時,在欄位標題上按右鍵加上 Domain。
- 重新載入頁面。
各瀏覽器的開發者工具會隨版本改版,按鈕的位置或名稱可能和這裡寫的略有不同;要找的東西一樣是:請求清單、保留記錄、網域欄。
03算一次合成範例,看清單
- 在計算頁按「載入合成範例(不是真實生辰)」,再按「計算」。
- 看 Domain 欄:每一列都應該是 projectarmillary.com。只要有一列不是,就是我們說錯了——請寫信告訴我們(第 07 節最後)。
- 點名稱是 chart、方法是 POST 的那一列(圖中 ④),看 Payload(Firefox 叫 Request,Safari 在 Headers 底下):送出去的就是表單上的日期、時間、座標、時區與選項,沒有別的。Response 是算好的星盤。
每一次計算都會出現的請求一共 16 個;加上瀏覽器可能另外要的圖示,以及只有你做了某件事才會出現的請求,全部列在下面三張表。這幾張表不是手抄的:網站每次更新時,由程式從計算頁實際載入的檔案重新產生;另有自動測試用真的瀏覽器跑一次合成範例,觀察到的請求和這幾張表不一致,測試就失敗。
| 方法 | 位址 | 是什麼 | 什麼時候 | 快取(Cache-Control) |
|---|---|---|---|---|
| GET | / | 頁面:計算頁本身 | 打開計算頁 | no-cache |
| GET | / | 樣式:這一頁的樣式包 | 打開計算頁 | public, max-age=31536000, immutable |
| GET | / | 程式:代碼的中文名稱,結果與匯出共用 | 打開計算頁 | public, max-age=31536000, immutable |
| GET | / | 程式:把錯誤代碼換成看得懂的說明與下一步 | 打開計算頁 | public, max-age=31536000, immutable |
| GET | / | 程式:複製與下載檔案的內容,在你的瀏覽器裡產生 | 打開計算頁 | public, max-age=31536000, immutable |
| GET | / | 程式:按「清除」或離開頁面時丟掉結果、中止請求 | 打開計算頁 | public, max-age=31536000, immutable |
| GET | / | 程式:進階選項的清單 | 打開計算頁 | public, max-age=31536000, immutable |
| GET | / | 程式:檢查欄位、組出要送出的請求內容 | 打開計算頁 | public, max-age=31536000, immutable |
| GET | / | 程式:服務接受的時區名稱,送出前先在本機檢查 | 打開計算頁 | public, max-age=31536000, immutable |
| GET | / | 程式:地點收據:你選的地點與座標 | 打開計算頁 | public, max-age=31536000, immutable |
| GET | / | 程式:星盤圖的符號(取自開源字型的輪廓,不另載字型) | 打開計算頁 | public, max-age=31536000, immutable |
| GET | / | 程式:把回應裡的位置排成星盤圖,在你的瀏覽器裡計算版面 | 打開計算頁 | public, max-age=31536000, immutable |
| GET | / | 程式:畫出星盤圖、放大檢視與 SVG/PNG 下載,在你的瀏覽器裡產生 | 打開計算頁 | public, max-age=31536000, immutable |
| GET | / | 程式:頁面本身的程式:收表單、送出請求、畫出結果 | 打開計算頁 | public, max-age=31536000, immutable |
| GET | / | 程式:把回應整理成畫面;第一次按「計算」時才載入 | 第一次按「計算」 | public, max-age=31536000, immutable |
| POST | / | 計算/查詢:出生日期時間、座標、時區與你選的選項;回應是星盤與計算卷宗 | 按下「計算」 | no-store |
| 方法 | 位址 | 是什麼 | 什麼時候 | 快取(Cache-Control) |
|---|---|---|---|---|
| GET | / | 圖示:網站圖示 | 瀏覽器自己決定要不要拿 | public, max-age=300 |
| GET | / | 圖示:網站圖示 | 瀏覽器自己決定要不要拿 | public, max-age=31536000, immutable |
| GET | / | 圖示:網站圖示 | 瀏覽器自己決定要不要拿 | public, max-age=300 |
| 方法 | 位址 | 是什麼 | 什麼時候 | 快取(Cache-Control) |
|---|---|---|---|---|
| POST | / | 計算/查詢:你輸入的搜尋字串;回應是內建地名目錄裡的候選地點 | 用地名搜尋地點 | no-store |
| POST | / | 計算/查詢:出生日期、時刻與時區(不含座標);回應是這個鐘面時間在當地的 UTC 偏移、規則出處與時區資料庫版本 | 在換算助手按「核對這個鐘面時間」 | no-store |
| POST | / | 計算/查詢:與上一次「計算」相同的內容,請伺服器再算一次並附上完整驗算資料 | 打開「技術細節與驗算資料」,或匯出時選「運算結果+驗算資料」 | no-store |
- 位址裡 ?v= 後面那串是檔案內容的雜湊。網站更新後它會變,你看到的值和上表不同時,先重新整理這一頁再比。
- 第二次載入時,有些列可能顯示 (disk cache)、(memory cache) 或 304:那是瀏覽器用了自己暫存的檔案,沒有新的下載。
- 清單裡如果出現 chrome-extension://、moz-extension:// 之類的列,那是擴充功能,不是本站——回到第 01 節,換一個沒有擴充功能的視窗。
04瀏覽器裡沒有留下東西
算完之後,看瀏覽器替這個網站存了什麼:
- Chrome 與 Edge:開發者工具的 Application 分頁,左側 Storage 底下。
- Firefox 與 Safari:Storage 分頁。
逐一展開 Cookies、Local storage、Session storage、IndexedDB、Cache storage,點 https://projectarmillary.com:每一個都應該是空的(圖中 ⑤)。也可以回到 Network,點任一列看 Headers:回應裡沒有 Set-Cookie,請求裡也沒有 Cookie。
這裡檢查的是本站要瀏覽器存下的東西,結果是沒有。瀏覽器自己仍會留下兩樣:瀏覽紀錄裡的網址,以及樣式、程式檔的暫存(〈安全〉第 05 節說過,暫存裡沒有出生資料,也沒有計算結果)。計算結果在你關掉分頁或按「清除」之前,留在這個分頁的記憶體裡。
05看回應標頭
在 Network 清單點任一列,打開 Headers,往下找 Response Headers。本站的每一個回應都帶著下面這一組,值應該一字不差:
| 標頭 | 值 | 意思 |
|---|---|---|
| Content-Security-Policy | default-src 'self'; base-uri 'none'; object-src 'none'; frame-ancestors 'none'; script-src 'self'; style-src 'self'; connect-src 'self'; form-action 'self'; require-trusted-types-for 'script'; trusted-types 'none' | 瀏覽器只准這個頁面從本站載入程式、樣式、圖片與連線('self'),connect-src 'self' 讓頁面上的程式連不到其他網域;require-trusted-types-for 'script' 與 trusted-types 'none' 讓任何程式都不能把一段字串變成 HTML 或程式網址 |
| X-Content-Type-Options | nosniff | 瀏覽器照標示的檔案類型處理,不自己猜 |
| Referrer-Policy | no-referrer | 你從本站點出去時,瀏覽器不告訴對方你從哪一頁來 |
| Permissions-Policy | camera=(), microphone=(), geolocation=() | 這個站不得使用相機、麥克風與定位,連詢問都不會跳出 |
| Cross-Origin-Opener-Policy | same-origin | 別的網站開的視窗拿不到這個頁面的參照 |
| Cross-Origin-Embedder-Policy | require-corp | 頁面只能嵌入明確允許被嵌入的資源 |
| Cross-Origin-Resource-Policy | same-origin | 本站的檔案不給其他網站的頁面載入 |
- Cache-Control 依回應的種類不同,第 03 節的表逐列寫了應該是哪一個值。重點是 /api/ 底下的回應一律 no-store:出生資料只出現在這些回應裡,它們不進任何快取。
- X-Request-ID 是伺服器每次隨機產生的編號,和日誌那一行的請求 ID 相同,用來對應錯誤回報;它不是從你的 IP 或瀏覽器算出來的。
- 線上的網站另由反向代理加上 Strict-Transport-Security,要求瀏覽器之後只用 HTTPS 連線。
06在手機上檢查
老實說,手機比較難。手機瀏覽器沒有內建的請求清單,要接上一台電腦才看得到:
- iPhone、iPad:需要一台 Mac。在手機的「設定 → Safari → 進階」(較新的 iOS 在「設定 → App → Safari → 進階」)打開「網頁檢閱器」,用線接上 Mac;Mac 的 Safari 照第 02 節打開開發者功能後,在「開發」選單裡選你的手機與這個分頁,就會得到同樣的 Network 與 Storage 分頁。
- Android:需要一台裝了 Chrome 的電腦。在手機的「開發人員選項」打開「USB 偵錯」,用線接上電腦;在電腦的 Chrome 網址列打開 chrome://inspect,找到手機上的分頁按 inspect。
- 沒有電腦:本站沒有另一個手機版,手機載入的是同一個網址、同一組檔案,所以在電腦上看到的清單也就是手機會要的清單。但這是推論,不是你在手機上親眼看到的。
07這個檢查證明不了什麼
開發者工具只看得到你的瀏覽器送出什麼、收到什麼。請求抵達伺服器之後發生的事,從這裡一樣也看不到。
清單裡多了一列?
那就是我們錯了。
請寫信到 privacy@projectarmillary.com,附上那一列的位址與網域;不要附上任何人的真實出生資料。設定與攻擊面的完整數值在〈安全〉。