渾儀ARMILLARY

安全 · VERIFY

不必相信我們,
打開瀏覽器自己看。

〈安全〉說:前端只向本站要東西、不用 cookie、不在你的瀏覽器裡存資料。這一頁教你用瀏覽器內建的開發者工具,親手檢查這幾句話。不必安裝任何東西,大約十分鐘。

你會看到三件事:算一次盤,瀏覽器送出哪些請求、每一個送到哪個網域;瀏覽器裡有沒有留下 cookie 或其他儲存;伺服器的回應帶了哪些安全標頭。從瀏覽器看不到的部分——伺服器收到之後做了什麼——最後一節照實說。

01開始之前

  • 用桌機或筆電上的 Chrome、Edge、Firefox 或 Safari。手機的做法在第 06 節。
  • 開一個無痕(私密)視窗,並確認擴充功能沒有在裡面執行。擴充功能自己的請求也會出現在清單裡,會讓你分不清哪些是本站的。
  • 不要用真實的出生資料。計算頁有一顆「載入合成範例(不是真實生辰)」按鈕,下面的步驟都用它。
  • 開發者工具的介面語言可能是中文,按鈕名稱會和下面寫的英文不同;位置是一樣的。

02打開 Network 分頁,保留記錄

Network(網路)分頁會列出頁面向伺服器要的每一樣東西。「保留記錄」讓清單在重新載入頁面時不被清空,這樣頁面一打開就送出的請求也看得到。

Chrome 與 Edge

  1. 打開計算頁 https://projectarmillary.com/zh-TW/calculate。
  2. 按 F12(Mac 按 ⌥⌘I),或在頁面上按右鍵選「檢查」。
  3. 點上方的 Network 分頁(圖中 ①)。
  4. 勾選 Preserve log(圖中 ②)。
  5. 在清單的欄位標題上按右鍵,勾選 Domain,清單會多出一欄網域(圖中 ③)。
  6. 按 Ctrl+R(Mac 按 ⌘R)重新載入頁面。

Firefox

  1. 打開計算頁,按 F12(Mac 按 ⌥⌘I)。
  2. 點 Network 分頁。
  3. 點 Network 分頁右上角的齒輪,勾選 Persist Logs——這是 Firefox 對「保留記錄」的叫法。
  4. Firefox 的清單預設就有 Domain 欄;沒有的話在欄位標題上按右鍵加上。
  5. 重新載入頁面。

Safari

  1. 先打開開發者功能:Safari 選單 → 設定 → 進階 → 勾選「顯示網頁開發者功能」(較舊的版本叫「在選單列中顯示『開發』選單」)。
  2. 打開計算頁,按 ⌥⌘I 開啟網頁檢閱器。
  3. 點 Network 分頁,打開分頁上方的 Preserve Log。
  4. 需要網域欄時,在欄位標題上按右鍵加上 Domain。
  5. 重新載入頁面。

各瀏覽器的開發者工具會隨版本改版,按鈕的位置或名稱可能和這裡寫的略有不同;要找的東西一樣是:請求清單、保留記錄、網域欄。

03算一次合成範例,看清單

  1. 在計算頁按「載入合成範例(不是真實生辰)」,再按「計算」。
  2. 看 Domain 欄:每一列都應該是 projectarmillary.com。只要有一列不是,就是我們說錯了——請寫信告訴我們(第 07 節最後)。
  3. 點名稱是 chart、方法是 POST 的那一列(圖中 ④),看 Payload(Firefox 叫 Request,Safari 在 Headers 底下):送出去的就是表單上的日期、時間、座標、時區與選項,沒有別的。Response 是算好的星盤。
projectarmillary.com/zh-TW/calculate 計算頁 載入合成範例 計算 Elements Console Sources Network Application 1 Preserve log Disable cache 2 Name Method Status Domain Type 3 calculate GET 200 projectarmillary.com document calculate.css?v=4c973a… GET 200 projectarmillary.com stylesheet labels.js?v=88adc5… GET 200 projectarmillary.com script client-context.js?v=23db4e… GET 200 projectarmillary.com script exporters.js?v=221154… GET 200 projectarmillary.com script privacy-lifecycle.js?v=20ce16… GET 200 projectarmillary.com script options-catalogue.js?v=d3c7cf… GET 200 projectarmillary.com script request-input.js?v=e53557… GET 200 projectarmillary.com script timezone-names.js?v=b31b78… GET 200 projectarmillary.com script location-receipt.js?v=116f3a… GET 200 projectarmillary.com script chart-glyphs.js?v=d2fdb1… GET 200 projectarmillary.com script chart-wheel-model.js?v=b021c6… GET 200 projectarmillary.com script chart-wheel.js?v=1dfea9… GET 200 projectarmillary.com script calculate.js?v=12b8ab… GET 200 projectarmillary.com script view-model.js?v=0ab1ac… GET 200 projectarmillary.com script chart POST 200 projectarmillary.com fetch 4
示意圖,不是截圖:各瀏覽器的版面不同,清單的列與本頁下表相同。

每一次計算都會出現的請求一共 16 個;加上瀏覽器可能另外要的圖示,以及只有你做了某件事才會出現的請求,全部列在下面三張表。這幾張表不是手抄的:網站每次更新時,由程式從計算頁實際載入的檔案重新產生;另有自動測試用真的瀏覽器跑一次合成範例,觀察到的請求和這幾張表不一致,測試就失敗。

每一次計算都會出現的請求
方法位址是什麼什麼時候快取(Cache-Control)
GET/zh-TW/calculate頁面:計算頁本身打開計算頁no-cache
GET/zh-TW/style/calculate.css?v=4c973a9631f7eb59樣式:這一頁的樣式包打開計算頁public, max-age=31536000, immutable
GET/zh-TW/labels.js?v=88adc5a2d80f17ec程式:代碼的中文名稱,結果與匯出共用打開計算頁public, max-age=31536000, immutable
GET/zh-TW/client-context.js?v=23db4ebf0a6525af程式:把錯誤代碼換成看得懂的說明與下一步打開計算頁public, max-age=31536000, immutable
GET/zh-TW/exporters.js?v=221154fb879eb824程式:複製與下載檔案的內容,在你的瀏覽器裡產生打開計算頁public, max-age=31536000, immutable
GET/zh-TW/privacy-lifecycle.js?v=20ce1699ec628a49程式:按「清除」或離開頁面時丟掉結果、中止請求打開計算頁public, max-age=31536000, immutable
GET/zh-TW/options-catalogue.js?v=d3c7cf37aebb8acc程式:進階選項的清單打開計算頁public, max-age=31536000, immutable
GET/zh-TW/request-input.js?v=e53557bace55f8e9程式:檢查欄位、組出要送出的請求內容打開計算頁public, max-age=31536000, immutable
GET/zh-TW/timezone-names.js?v=b31b78912878f1f3程式:服務接受的時區名稱,送出前先在本機檢查打開計算頁public, max-age=31536000, immutable
GET/zh-TW/location-receipt.js?v=116f3addf1ded50f程式:地點收據:你選的地點與座標打開計算頁public, max-age=31536000, immutable
GET/zh-TW/chart-glyphs.js?v=d2fdb19952333d46程式:星盤圖的符號(取自開源字型的輪廓,不另載字型)打開計算頁public, max-age=31536000, immutable
GET/zh-TW/chart-wheel-model.js?v=b021c6cdb8c3ba87程式:把回應裡的位置排成星盤圖,在你的瀏覽器裡計算版面打開計算頁public, max-age=31536000, immutable
GET/zh-TW/chart-wheel.js?v=1dfea902136328a7程式:畫出星盤圖、放大檢視與 SVG/PNG 下載,在你的瀏覽器裡產生打開計算頁public, max-age=31536000, immutable
GET/zh-TW/calculate.js?v=12b8ab2f8d8f9488程式:頁面本身的程式:收表單、送出請求、畫出結果打開計算頁public, max-age=31536000, immutable
GET/zh-TW/view-model.js?v=0ab1aca524220e6d程式:把回應整理成畫面;第一次按「計算」時才載入第一次按「計算」public, max-age=31536000, immutable
POST/api/chart計算/查詢:出生日期時間、座標、時區與你選的選項;回應是星盤與計算卷宗按下「計算」no-store
瀏覽器可能另外要的圖示
方法位址是什麼什麼時候快取(Cache-Control)
GET/favicon.ico圖示:網站圖示瀏覽器自己決定要不要拿public, max-age=300
GET/zh-TW/favicon.svg?v=c95686a2f029d369圖示:網站圖示瀏覽器自己決定要不要拿public, max-age=31536000, immutable
GET/apple-touch-icon.png圖示:網站圖示瀏覽器自己決定要不要拿public, max-age=300
只有你做了某件事才會出現的請求
方法位址是什麼什麼時候快取(Cache-Control)
POST/api/places/search計算/查詢:你輸入的搜尋字串;回應是內建地名目錄裡的候選地點用地名搜尋地點no-store
POST/api/time/convert計算/查詢:出生日期、時刻與時區(不含座標);回應是這個鐘面時間在當地的 UTC 偏移、規則出處與時區資料庫版本在換算助手按「核對這個鐘面時間」no-store
POST/api/chart?projection=detailed計算/查詢:與上一次「計算」相同的內容,請伺服器再算一次並附上完整驗算資料打開「技術細節與驗算資料」,或匯出時選「運算結果+驗算資料」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。

Elements Console Network Application Storage ▸ Local storage ▸ Session storage ▸ IndexedDB ▾ Cookies projectarmillary.com ▸ Cache storage Name Value 沒有資料 5
示意圖,不是截圖。其他四類展開後也是同樣的空表。
邊界

這裡檢查的是本站要瀏覽器存下的東西,結果是沒有。瀏覽器自己仍會留下兩樣:瀏覽紀錄裡的網址,以及樣式、程式檔的暫存(〈安全〉第 05 節說過,暫存裡沒有出生資料,也沒有計算結果)。計算結果在你關掉分頁或按「清除」之前,留在這個分頁的記憶體裡。

05看回應標頭

在 Network 清單點任一列,打開 Headers,往下找 Response Headers。本站的每一個回應都帶著下面這一組,值應該一字不差:

標頭值意思
Content-Security-Policydefault-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-Optionsnosniff瀏覽器照標示的檔案類型處理,不自己猜
Referrer-Policyno-referrer你從本站點出去時,瀏覽器不告訴對方你從哪一頁來
Permissions-Policycamera=(), microphone=(), geolocation=()這個站不得使用相機、麥克風與定位,連詢問都不會跳出
Cross-Origin-Opener-Policysame-origin別的網站開的視窗拿不到這個頁面的參照
Cross-Origin-Embedder-Policyrequire-corp頁面只能嵌入明確允許被嵌入的資源
Cross-Origin-Resource-Policysame-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這個檢查證明不了什麼

開發者工具只看得到你的瀏覽器送出什麼、收到什麼。請求抵達伺服器之後發生的事,從這裡一樣也看不到。

你的瀏覽器 開發者工具看得到: 請求與回應的內容 標頭 cookie 與瀏覽器儲存 看得到的邊界 HTTPS 反向代理 → 應用程式 從瀏覽器看不到: 日誌寫了什麼、記憶體、主機 要靠:日誌欄位表、出口限制、 可以逐行核對的公開原始碼
虛線左邊,這一頁的步驟讓你親眼看到;虛線右邊,只能靠公開的設定與原始碼核對。
  • 伺服器收到之後做了什麼,這個檢查證明不了。我們能給的是可以核對的東西:日誌每一行的完整欄位(〈安全〉第 03 節)、容器沒有對外路由(同頁第 04 節),以及公開的原始碼。
  • 線上跑的版本不必然等於公開原始碼的最新版。要確認你用的是哪一版,看計算卷宗裡的版本識別碼。
  • 反向代理看得到你的 IP 位址,否則回應送不回來;它拿來做限流計數,不寫進日誌,也不轉給應用程式(〈安全〉第 06 節)。這一點從瀏覽器同樣無法驗證。
  • 這張清單是本站頁面要求瀏覽器做的事。瀏覽器本身(安全瀏覽檢查、更新、擴充功能)另外送出的請求不在其中,也不一定會出現在開發者工具裡。

清單裡多了一列?
那就是我們錯了。

請寫信到 privacy@projectarmillary.com,附上那一列的位址與網域;不要附上任何人的真實出生資料。設定與攻擊面的完整數值在〈安全〉。

打開計算頁 →