[CODE]

一個 OAuth、五個廣告帳戶,與那道拒絕改錯帳戶的防線

一個共用的管理員帳戶 token,讓每個站台的流程都能存取每個客戶的廣告帳戶。解法不是多開憑證,而是把「該動哪些帳戶」的決定收到單一個地方,並把退路從靜默擴張改成大聲出錯。

1 min read AI 生成
typescript google-ads security automation

三個客戶的程式碼庫,共用一個 Google Ads 管理員帳戶的 OAuth token。這個 token 照設計就能存取它所管理的每一個帳戶——這正是它方便的原因,也正是它危險的原因。

症狀以「被污染的狀態檔」現身。某客戶的 ads-state.json 快照裡,出現了另一個客戶的活動和客戶 ID。還沒有任何東西被改錯,但餵給每週健檢的那份快照,讀到的是錯帳戶的資料——健檢會去分析甲民宿的花費,然後把結論算到乙民宿頭上。在一套整個價值主張就是「每個帳戶是獨立的,絕不跨帳戶建議」的系統裡,這不是表面的小 bug。

為什麼那個方便的 token 就是隱患

原本決定「該查哪些帳戶」的邏輯是這樣:有設定就用站台設定的帳戶 ID;沒有就退回去用 OAuth token 能搆到的所有 ID。退路就是陷阱。一個共用的管理員 token 能搆到每一個客戶的帳戶。所以任何一條「站台設定 ID 是空的、漏了、或在設定載入前就被讀到」的程式路徑,都會悄悄擴張成整個客戶組合。同一個讓你用一份憑證就能管五個帳戶的 token,也讓你能輕易地讀錯——或寫錯——一個帳戶。

這個模式在四支腳本裡各複製了一份:健檢抓取、快照寫入、客戶組合彙整、以及改設定的 mutate 腳本。每支都有自己一份「站台設定 ID 優先,否則退回 OAuth ID」的邏輯,其中一份的篩選條件還略有不同。一個跟安全相關的決定有四份副本,就是四個出錯的機會,也是發現出錯後要修四個地方。

第一道修法:收成一處,寫路徑硬性中止

第一步是把四份副本收成一個函式——siteCustomerIds(site, oauth)——由它獨佔這條規則:站台設定 ID 為準;OAuth 退路只在站台確實完全沒有設定帳戶時才適用。每支腳本現在都呼叫同一個函式,不管是讀快照還是彙整客戶組合,解析行為都一模一樣。

讀路徑之外,寫路徑需要更強的保護。讀錯產生的是令人困惑的報告;寫錯改的是付費客戶正在跑的活動。所以 mutate 腳本加了一道硬性中止:在動任何東西之前,先確認解析出來的客戶 ID 確實在這個站台的設定裡。如果不在,腳本印出合法的 ID,以非零碼結束。

同一時間,render.tsloadAdsState() 加了一道 ownIds 過濾器:即便 ads-state.json 已經被污染,資料在送到 LLM 分析之前,會先篩掉不屬於本站台的帳戶記錄。防線上了兩道——第一道守在查詢,第二道守在渲染。

退路本身是個洞

這個架構看起來夠用了。但有個細節讓整條防線出現缺口。

mutate 腳本的防衛條件需要 accounts.length > 0 才會執行帳戶比對。如果站台設定裡一個帳戶都沒有,這個條件直接短路——防衛略過。同時,siteCustomerIds() 的 OAuth 退路就會踢進來:mutate 腳本會拿到 MCC 底下的所有帳戶 ID,用第一個當作目標,然後安靜地繼續跑,沒有任何警告。

一個「設定漏掉 accounts 欄位」的配置錯誤,本來應該被立刻抓到,卻讓讀路徑靜默拿到所有客戶的資料,也讓寫路徑的防衛因為條件短路而完全失效。問題不在防衛本身寫錯了——問題在於退路提供了一條讓防衛永遠不會被觸發的路。

把退路改成大聲出錯

修法是把退路拿掉。siteCustomerIds() 現在不再接受 OAuth 退路:如果站台設定沒有帳戶,函式直接丟出例外,腳本停住。_oauth 參數留著只是為了介面相容,實際上一行都沒用到。

站台設定漏掉帳戶 → 函式拋出例外 → 腳本停住 → 任何東西都動不了。讀路徑和寫路徑現在都拒絕在設定不齊全的情況下執行。mutate 防衛條件的邏輯缺口也因此消失:accounts.length === 0 的情況永遠到不了防衛檢查,因為程式在那之前就已經中止了。

這個不對稱從一開始就是刻意的:對讀路徑來說,一個悄悄放寬範圍的行為,產生的是你事後在審查時還抓得到的爛資料;對寫路徑來說,同樣的行為是不可逆的,而且瞄準的是別人的錢。最後的結論是,這個不對稱本身就不應該存在——讀路徑的靜默降級同樣不可接受。

重點是結構,不是事件本身

清理時也重跑了被污染的快照,讓提交進去的狀態檔重新對上自己的帳戶。那是看得見的部分。能撐久的部分有兩層。

第一層,「某站台可以動哪些帳戶」這個決定現在剛好只活在一個函式裡,不是四份各有差異的副本。第二層,那個函式不再允許設定空白時的靜默降級——它要麼回傳站台自己的帳戶 ID,要麼讓你知道設定壞了,沒有第三條路。

那個共用的管理員 token 不會消失——它正是讓「用小生意的預算幫五館跑廣告」這件事一開始就划得來的東西。要拿掉的從來不是這個方便,而是它身後那個會把設定漏洞靜默轉換成跨站操作的退路。