- date
- entry
- 023
- topic
- tooling
你的防護措施從來沒有被觸發過
身分閘門只在身分錯的時候才執行,而那條路徑平常沒人走。故意用錯的身分跑一次,兩分鐘內發現兩件事:比對帳號 ID 會放行 root(root 跟你在同一個帳號裡),而且錯誤訊息本身在 set -u 下爆掉。談負面測試的兩條驗收標準,以及還有哪些程式碼只在失敗時執行。
我做企業內部系統導入,也自己維護這些系統跑在上面的基礎設施。這裡放的是過程中值得寫下來的部分——多半是那種「當下卡住兩小時、事後三句話就講得完」的東西。
身分閘門只在身分錯的時候才執行,而那條路徑平常沒人走。故意用錯的身分跑一次,兩分鐘內發現兩件事:比對帳號 ID 會放行 root(root 跟你在同一個帳號裡),而且錯誤訊息本身在 set -u 下爆掉。談負面測試的兩條驗收標準,以及還有哪些程式碼只在失敗時執行。
第一篇講的目錄 key 陷阱,根源是把 SPA 的錯誤設定套在沒有前端路由的站上。談把 404 說成 200 的三個代價(soft 404、監控失效、騙到自己)、正確的錯誤回應該長什麼樣,以及為什麼私有 S3 bucket 回的是 403 而不是 404。
多頁靜態站的打包工具要你列出每個 HTML 進入點。那份清單是人維護的,漏改不會報錯——build 成功,只是少一頁,而且 dev server 照樣看得到,所以你只在正式輸出裡發現。談怎麼掃檔案系統取代清單、三個實作細節,以及為什麼草稿該用目錄約定隔開而不是用排除清單。
set -e 建立在「中止比繼續安全」這個假設上,而部署腳本剛好不成立——部署到一半的站是壞掉且沒有訊號的狀態。我踩過一次:sync 完成、補目錄 key 到一半 session 過期,於是 invalidation 與全站驗證都被跳過。談分界線該畫在「第一次寫入線上」那一行,以及為什麼 -u 和 pipefail 要留著。
一條 max-age 打天下只有兩種結局:設太長改了沒生效,設太短 CDN 白裝。判準是「檔名會不會隨內容改變」,切下去正好三類。談 immutable 比長 max-age 多做了什麼、max-age=0 為什麼不是「不要快取」,以及 invalidation 清得掉 CDN、清不掉使用者瀏覽器。
Lighthouse 實測,一支 Google Fonts 的 stylesheet 單獨佔掉近 800 毫秒的首次繪製時間。談那 800ms 花在哪(兩個網域、兩輪 DNS+TLS,而且有順序相依)、preload 換 rel 那個 hack 的原理與代價,以及為什麼繁體中文上萬字的規模讓 webfont 從一開始就不划算。
第一篇裡用三句話帶過的那支腳本,展開講。靜態站兩種最常見的壞法——目錄 key 缺失、部署根本沒生效——都回 200,所以只能比對內容。談為什麼用 <title> 當指紋、為什麼刻意不加 set -e、為什麼除了部署時還要每天排一次。
把英文版的 canonical 指向中文版,等於告訴搜尋引擎英文版不必收錄——它就真的不會出現。談 canonical 自指、hreflang 三條雙向對稱、<html lang> 也要換、為什麼 feed 得分兩份,以及手工維護的七個同步點該怎麼用腳本檢查。
access log 由 middleware 在 handler 之後產生,看得到的只有 response——錯誤細節在 return 那一刻就沒了。低流量時靠時間順序還能配對,有負載時「前面幾行」就變成猜測。談 request id 怎麼貫穿並回給客戶端、日誌該結構化到什麼程度,以及哪三類東西寫進去就等於外流。
建容器與發布是兩個呼叫、中間要等,而 reply_to_id 吃的是已發布的 post ID——三個限制疊起來讓一串十篇的 thread 成為嚴格序列。真正的問題是這條鏈沒有交易:第六串失敗時前五串已經公開了。談怎麼把它寫成可續發的狀態機。
查不到資源、沒有錯誤訊息,於是你以為它不存在——其實是半小時前 export 的環境變數還活著,你查的是別的帳號。談憑證解析的四層順序、aws sts get-caller-identity 這道閘門,以及為什麼唯讀跑錯帳號比寫入更難發現。
小團隊把多個客戶的後端放同一台機器是合理決定,風險在於做完之後大家會假設它們互不影響。vhost、容器、log group 是隔離的;磁碟、Docker daemon、nginx reload、核心、出口 IP 不是——後面這五樣就是你的爆炸半徑。附磁碟事故的處理指令與該拆出去的四個時機。
修好了、推上去了、問題還在——這時候「程式沒修好」和「那份程式沒在線上跑」的症狀一模一樣。談用 -ldflags -X 把 git sha 烤進 binary、前端為什麼更需要這件事、以及怎麼把它變成部署腳本最後一步的驗證而不是祈禱。
把一塊實作委派給 coding agent,收到的完成報告描述的是它打算做的事——產生報告跟執行是兩條路徑。談報告與 diff 會差在哪五種地方、我的驗收流程(自己讀 diff、自己重跑 -count=1 的測試、單獨看測試檔的 diff 抓假綠燈),以及為什麼驗收的成本是在寫 brief 那一刻就決定的。
金流商打過來的那個 POST 公開、沒有身分、可以被任何人偽造、而且同一筆會重送——它改的還是金額欄位。以綠界 AIO 為例談三件必做的事:重算 CheckMacValue、看懂非同步付款的兩次回調、用 MerchantTradeNo 做冪等,以及為什麼回應格式寫錯會被一直重打。
一條連線鏈六節,手機端看到的永遠是同一句「連不上」。三個獨立的坑:client library 的 if (secure) 把你的 WebSocket 設定蓋回去、EMQX 把 200 空 body 當成拒絕、Docker default bridge 不解析 service name。共通點是壞掉的那一節都回報自己正常。
後端 log 裡是 column "invoice_no" does not exist。程式是對的、migration 也在 repo 裡,只是 prod 從來沒跑過它。談導入期趕驗收手改資料庫留下的漂移怎麼偵測、為什麼止血時該讓 prod 當基準,以及為什麼要用權限而不是紀律來擋下一次。
.env 的問題不是它不安全,是它會被複製——到 NAS 備份、到 docker build context、到某個人的下載資料夾。談憑證不落盤的實際流程、為什麼 zone id 也不該存,以及什麼情況該直接 rotate 而不是先判斷。
瀏覽器說是 MIME type 的問題,其實是部署腳本刪掉了使用者正要抓的舊 chunk。部署完才開頁面的人一切正常,部署當下開著頁面的人白屏——談這條錯誤訊息中間隔的三層改寫,以及為什麼修法是把 --delete 拿掉。
後端回的是有內容的 500,客戶端收到的卻是 {"status":"ok","message":"Request processed"}。狀態碼對、JSON 格式對,只有內容是假的——做這件事的是 proxy_intercept_errors 加一行 error_page。連帶談怎麼逐層剝出改寫發生在哪一層。
生產環境 agent runtime 的八週事故研究:4,286 個單元測試與 827 項治理檢查沒攔下來的,是 LLM 把工具錯誤改寫成一段流暢可信的完成報告。談 fail-plausible,以及為什麼稽核是防重犯的引擎、不是預測的引擎。
我在抓取腳本裡設了一個保險用的分頁上限。它沒有出錯、沒有報警,只是安靜地讓資料長出一個不存在的成長趨勢——而且方向跟事實相反。連帶談 cat: 為什麼會匹配到交叉列名。
用 REST origin 時,CloudFront 不會把 /blog/ 解析成 blog/index.html,而 SPA fallback 會把 404 蓋成 200 的首頁——最難察覺的一種壞法。連帶談 sync --delete 為什麼會把補好的目錄 key 再刪一次。