安全與資料處理說明
最後更新:2026-09-10
這份文件說明 SyncPool 的系統架構、資料存放方式、向各平台申請的權限範圍,以及我們如何保護您的授權憑證。內容以實際實作為準,並盡量避免行銷用語。
一、系統架構
瀏覽器 ──HTTPS──▶ Cloudflare Pages(靜態前端,Next.js export)
│
└──HTTPS + JWT──▶ Supabase Edge Functions(Deno)
│
├──▶ Postgres(RLS,東京區域)
└──▶ 露天市集 / 蝦皮 / 自訂商店 / Shopify API
- 前端:純靜態頁面,部署於 Cloudflare Pages。全站強制 HTTPS(TLS 1.2 以上)並啟用 HSTS。前端只持有使用者本人的登入 session,沒有任何平台憑證或服務端金鑰。
- 後端:Supabase Edge Functions 負責所有與平台 API 的互動(憑證交換或簽章、PULL、結算、PUSH)。函式以使用者 JWT 驗證身分,或以工作區 API 金鑰驗證排程與外部觸發。
- 資料庫:Supabase Postgres,位於東京(ap-northeast-1)區域。所有業務資料表皆啟用 Row Level Security(RLS),查詢一律以
workspace_id限定,工作區之間無法互相讀取。
二、憑證與金鑰的保存
- 平台憑證(access / refresh token、API 金鑰):存放於獨立的 secrets 資料表。該表沒有任何允許瀏覽器端(anon / authenticated 角色)讀取的 RLS 政策,只有以 service role 執行的 Edge Functions 可以存取;前端查詢該表會得到空結果。憑證以加密方式寫入,金鑰由伺服器環境變數提供,不存在於程式碼庫。
- 工作區 API 金鑰:為 48 個十六進位字元的隨機 token(192 位元熵),用於排程與外部系統觸發同步。金鑰可在後台「設定」頁面隨時輪換,輪換後舊金鑰立即失效。金鑰只能由工作區擁有者查看。
- 使用者密碼:由 Supabase Auth 以 bcrypt 雜湊儲存,我們無法取得原文。
- 授權機制(依平台而異):
- 露天市集:無 OAuth 轉址流程。API Key、Secret Key 與 Salt Key 由露天核發給賣家本人,賣家在本服務填入後即加密存入 secrets 資料表;每一次 API 請求由 Edge Function 以 HMAC-SHA256 即時產生簽章,帶入
X-RT-Key、X-RT-Timestamp、X-RT-Authorization標頭。Secret / Salt Key 僅存在於伺服器端,不會出現在請求內容中,也不會經過瀏覽器。 - 蝦皮與 Shopify:OAuth 流程。授權回呼皆在 Edge Function 內完成 code 交換與簽章驗證(HMAC),token 不會經過瀏覽器。
- 露天市集:無 OAuth 轉址流程。API Key、Secret Key 與 Salt Key 由露天核發給賣家本人,賣家在本服務填入後即加密存入 secrets 資料表;每一次 API 請求由 Edge Function 以 HMAC-SHA256 即時產生簽章,帶入
三、資料最小化
本服務只為完成庫存同步而處理資料,具體而言:
- 儲存:SKU、商品名稱、規格名稱、外部商品編號、庫存數量、快照數量、入庫單、同步日誌。
- 讀取但不儲存:訂單中的買家姓名、收件地址、電話、Email、付款資訊。以露天市集為例,
/api/v1/order/detail的回應確實包含收件人資訊,但 Edge Function 只擷取訂單編號、狀態、建立時間、結帳總金額與品項(料號 / 名稱 / 規格 / 數量 / 單價),其餘欄位在記憶體中即丟棄,不寫入資料庫也不寫入日誌。其他通道採相同做法。 - 不讀取:商品描述、圖片、價格(露天、蝦皮與 Shopify 的商品 API 若一併回傳,我們不保存)、賣家財務資料、對話訊息。
四、向各平台申請的權限(最小權限原則)
我們只申請完成同步所必需的權限,並在下表說明每一項的用途:
露天市集(RUTEN OPEN API v1,host:partner.ruten.com.tw)
| 權限 | 用途 |
|---|---|
| GET /api/v1/product/list | 列出賣場商品,取得 item_id 以建立通道商品清單。 |
| GET /api/v1/product/item/{id} | 讀取商品明細中的「賣家自用料號」(custom_no,即 SKU)、規格與目前庫存,用於 SKU 對應。不保存描述、圖片、價格。 |
| PUT /api/v1/product/item/stock | 將總池結算後的數量寫回商品或規格的庫存。這是唯一的寫入動作,不修改名稱、價格、描述。 |
| GET /api/v1/order/list | 以時間區間列出訂單編號與狀態,判斷本輪是否有新訂單(防超賣攔截)。 |
| POST /api/v1/order/detail | 取得訂單的品項料號、名稱、規格、數量、單價與結帳總金額以計算售出量。回應中的收件人姓名、電話、地址、Email 於伺服器端即丟棄,不寫入資料庫。 |
蝦皮 Open Platform
| 權限 | 用途 |
|---|---|
| 商品讀取(product) | 取得 item / model 的 SKU、名稱與目前庫存,用於建立 SKU 對應。 |
| 訂單讀取(order) | 取得訂單中的 item / model 與數量,計算各 SKU 售出量。僅擷取品項與數量欄位。 |
| 庫存更新(stock update) | 將總池結算後的數量寫回對應 model 的庫存。 |
自訂商店(REST + Webhook)
| 權限 | 用途 |
|---|---|
| GET /products、GET /orders、PUT /stock | 由您自行實作的端點;SyncPool 以您提供的 API 金鑰呼叫。Webhook 用於訂單成立時通知本服務觸發補跑。 |
Shopify(Admin API)
| 權限 | 用途 |
|---|---|
| read_products / write_products | 讀取商品與變體的 SKU、名稱;寫入僅用於必要時更新變體的庫存追蹤設定,不修改名稱、價格、描述。 |
| read_inventory / write_inventory | 讀取各地點庫存數量、寫回結算後的數量。這是本服務的核心動作。 |
| read_orders | 讀取訂單品項與數量以計算售出量;不儲存買家資料。 |
| read_locations | 取得庫存地點(location)ID,以指定要同步的倉庫。 |
五、同步過程中的防護
- 差額結算而非覆蓋:以「通路目前數量 − 上次快照」計算售出量,避免以舊數字蓋掉其他通路的變化。
- 防超賣攔截(guard):PULL 與 PUSH 之間再次檢查訂單;若有新訂單,該 SKU 本輪略過推播並記錄
guard事件,下一輪自動補跑。 - 安全庫存:推播到通路的數量 = 總池數量 − 安全庫存(下限 0),保留緩衝。
- 限流與重試:遵守各平台 API 速率限制,逾時或 5xx 錯誤採指數退避重試;最終失敗會標記為 partial 並保留在日誌,不會靜默略過。
六、日誌與稽核
- 每次同步建立一筆 run,記錄觸發來源(手動 / 排程 / API)、開始與結束時間、整體狀態與摘要。
- 每個 SKU 的每個動作(delta、inbound、push、guard、skip、error)各記錄一筆 event,包含前後數量、差額與訊息。
- 日誌不包含憑證、token 或買家資料。錯誤訊息會先移除 Authorization 標頭與 token 字串後再寫入。
- 日誌隨工作區刪除而刪除。
七、撤銷授權與刪除資料
- 在後台「通道」頁面刪除通道,會立即刪除該通道的憑證與商品對應。
- 您也可以從平台端終止授權:露天市集可聯繫平台停用或更換 OPEN API 金鑰,蝦皮賣家中心可撤銷授權,Shopify 後台可移除 App。憑證失效後本服務即無法再存取該通路;收到平台的 uninstall / 撤銷通知後我們也會清除對應憑證。
- 刪除工作區會刪除其下所有資料。詳見 隱私權政策。
八、資安事件通報
若您發現本服務的安全漏洞或疑似資料外洩,請來信 [email protected],主旨請註明「Security」。我們會在 2 個工作天內回覆,並在確認影響範圍後通知受影響的使用者與相關平台。