Communitygithub.com

Boogy 授權(服務內授權)

用於構建 Boogy 服務的智慧技能 — 涵蓋設計、資料模型、認證、任務、MCP/REST 和平台限制。

Boogy 授權(服務內授權) 是什麼?

此技能詳細說明了 Boogy 平台的三層授權模型,包含入口(Ingress)模式、路由守衛(Guards)和資料列範圍輔助(Helpers),確保開發者無需自行實作安全邏輯,而是透過 SDK 提供的驗證輔助函式來維持安全不變性。技能特別強調了「拒絕原則:通過存在性遮蔽」(deny-by-existence-mask),即資源不存在與不屬於呼叫者都回傳 404 而非 403,以防止列舉攻擊。此外,它提供了 API 金鑰的整合配方,支援雙重憑證(session bearer 或 sk_* 金鑰)並自動統一為 `current_principal()`,使既有所有權檢查功能無須修改。此技能適用於 Claude Code、Cursor 等開發代理,支援 Boogy 服務中的認證與授權設計,並以 Rust/Toml 配置為例展示實作方式。

相容平台~Claude Code~Codex CLI~Cursor
npx skills add Boogy-ai/boogy-superpowers

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

Boogy 授權(服務內授權) 是做什麼的?

Boogy 的授權分為三層。請不要自行實作任何一層—— SDK 會產生經過驗證的輔助函式,以保持安全不變性。

三層模型

  1. 入口准入 — 平台的入口模式決定請求是否到達你的服務。 當你的程式碼執行時,呼叫者已經是已知的 主體(principal) 或匿名者。
  2. 守衛守護路由 — 在處理器(handler)執行前進行每路由檢查: 要求登入,或要求對被定址資源的所有權。
  3. 輔助函式限定資料列範圍 — 處理器僅列出/載入屬於呼叫者的資料列。

使用 auth::current_principal() -> Option<String> 解析呼叫者。 主體是 不透明的 — 切勿解析、移除前綴或假設其為 UUID;只將其用作擁有者資料欄的值以及 auth::* 的輸入。

每路由入口:受限服務中的公開路由

第 1 層(入口)通常是一個服務範圍的模式。但有時候一個路由必須能被服務其餘部分拒絕的呼叫者訪問—— 典型的例子是在一個原本為 authenticated(或 internal/mixed)服務中設定一個公開的 /webhook 接收端。 透過每路由覆寫將其獨立出來;服務的其餘部分維持受限:

[ingress]
mode = "authenticated"          # 服務範圍預設值 — 其他所有路由

[[ingress.routes]]
path = "/webhook"               # 服務相對路徑;ALL 方法
mode = "public"                 # 任何人都可以訪問 /webhook

安全契約(請仔細閱讀 — 它是預設拒絕的):

規則為何重要
服務相對的 literal 路徑。 精確(/webhook)或前綴(/stripe/*,邊界為路徑段)。不支援 {param} 捕獲。path 是你的路由器看到的相同路徑,也就是你的處理器所處的框架。
最特定匹配勝出;只應用一種模式。 精確勝過前綴;較長前綴勝過較短。公開的子路由永遠不會 擴大 兄弟路由:/webhook 公開不會放開 /data。
預設值 = 服務範圍模式。 未匹配的路徑(或 .. 跳躍嘗試)會落入受限的預設值,永遠不會落入權限最大的路由。忘記列出路由會使其保持受保護,而非暴露。
每個覆寫攜帶自己的模式 + 允許清單。 覆寫不會繼承 allowed_agents/allowed_origins。在路由區塊上列出它們;同樣的非空驗證會套用,因此筆誤不會無聲地全部拒絕。
按路徑,而非按方法(目前)。 path = "/webhook" 同樣適用於 GET、POST 等。公開的 /webhook 可由任何 HTTP 動詞訪問——因此仍須在處理器內驗證請求(無關的 GET 應不做任何事)。
在邊緣由主機強制執行。 入口在 wasm 實例化之前執行。你不應在程式碼中自行關閉公開路由;但公開路由意味著任何人都能訪問它——需以其他方式驗證(例如 HMAC 簽章;請參閱 boogy:boogy-webhooks)。
委派閘門 + 速率限制保持服務範圍。公開路由的例外無法繞過 [ingress.delegation] 閘門,且共用速率限制桶。

一個沒有 [[ingress.routes]] 的 manifests 行為與之前完全相同——這完全是新增性的。

反過來:開放服務中的受限子樹(擁有者專用的 /admin)

另一種方向——服務預設為開放(authenticated)但 /admin/* 子樹只能由服務擁有者(佈建者)訪問。 陷阱:一個可佈建的模組是由任何人部署的,因此你不能在 Manifest 中硬編碼身分(@alice、boogy://alice/services/*)—— 該 literal 擁有者對其他每個佈建者都是錯誤的,且入口允許清單字串在部署時不會被替換。入口沒有「與我相同的擁有者」匹配器。

使用未閘門的 caller_is_service_owner() 能力——主機證明呼叫者是否為此服務的擁有者(他們的代理權杖,在主機端根據代理註冊表解析,或是他們自己的工作負載)。不需要每路由入口,也不需要硬編碼身分:

[ingress]
mode = "authenticated"          # 所有路由;處理器自行關閉 /admin
// require_operator(): 主機證明,無硬編碼。
fn require_operator() -> Result<(), ApiError> {
    if caller_is_service_owner() { return Ok(()); }   // 擁有者:他們的代理或自己的工作負載
    // OBO: 擁有者的後端代表使用者 — 經證明的執行者是工作負載。
    let id = current_identity();
    let actor_owner = id.and_then(|i| i.actor).and_then(workload_owner);
    if actor_owner == Some(self_identity().owner) { return Ok(()); }
    Err(ApiError::forbidden("僅限操作員"))
}

為什麼這是正確的原語:人類擁有者可以直接使用自己的權杖 curl /admin(wasm 無法解析代理的 handle,但主機可以——這就是能力的作用),而且擁有者的後端可以作為工作負載運作。預設拒絕:匿名、不同的擁有者或無法解析的呼叫者 → false。(請參閱 resend-base 目錄模組。)早期的「internal + 相同擁有者工作負載」模式也能運作,但排除人類直接 curl——對於僅限擁有者的介面,偏好使用 caller_is_service_owner。

守衛與輔助函式快速參考(已驗證)

項目用途
auth::required() -> Guard若匿名則回傳 401。用於集合路由(list、create)。
auth::owns_resource(table, owner_col, id_param) -> Guard用於項目路由(GET/DELETE /things/{id})。載入資料列,若不存在或不屬於你則回傳 404,將資料列存入 req.ctx。多個載入可使用 .slot("name")。
auth::find_owned(table, owner_col) -> Result<Vec<Row>, _>用於索引端點的主體範圍列表。匿名時回傳 401。
auth::load_owned(table, owner_col, id) -> Result<Option<Row>, _>用於 MCP/JSON-RPC(id 在 body 中而非路徑)的單一載入 + 所有權檢查。None = 不存在或不屬於你。
auth::require_scope(scope) -> Guard粗粒度能力閘門:匿名回傳 401,已登入但缺乏 scope 則回傳 403。

擁有者資料欄始終為 DEFAULT_OWNER_COL("owner_principal")——切勿自行發明 owner_id/created_by。將項目路由連接為 .group([auth::owns_resource("things", DEFAULT_OWNER_COL, "id")], |g| …)。 該守衛後方的處理器應讀取存放在 ctx 中的資料列——不要重新提取。

權杖中的擁有者規則: 在建立時,從 auth::current_principal() 蓋印擁有者資料欄。永遠不要從請求 body 讀取擁有者—— 客戶端可能聲稱另一個使用者的 id。

鐵律:拒絕原則:透過存在性遮蔽

不存在和存在但不屬於你都回傳 404 — 永遠不回傳 403。 這是平台安全不變性,而非風格選擇。403 與 404 的分離是一個列舉預言機:已驗證的使用者可以探測 id,並得知哪些存在(403)與哪些不存在(404)。SDK 守衛強制執行此規則。

「但 UI 想告訴使用者原因。」 請在客戶端處理:統一將 404 視為「對你不可用」並渲染該訊息;線路狀態保持 404。請不要分支或修補 SDK 守衛來拆開遮蔽。(require_scope 的 403 是不同的軸——粗粒度能力,而非資源所有權——而且是正確的。)

程式化呼叫者的 API 金鑰(已驗證配方)

在 wit_glue! 旁邊呼叫 api_keys_glue!(bindings),接著:

  1. 在 init_tables 中呼叫 api_key_routes::install_table()。
  2. 透過 ApiKeyRoutes 擴展特徵掛載管理路由: Router::new().with_api_key_routes()(/_keys)或 .with_api_key_routes_at("/admin/keys")。
  3. 關閉你的路由:.group([api_key_routes::guard], |g| ...)。
事實詳細資訊
雙重憑證guard 接受 session bearer 或 sk_* 金鑰;兩者統一為 current_principal(),因此 owns_resource/find_owned 無需修改即可運作。
管理金鑰需要 session 身分;sk_* 金鑰不能建立金鑰。
無權限提升金鑰僅攜帶建立者已持有的 scope(否則回傳 403)。
儲存金鑰以雜湊形式存在於服務自己的儲存中;切勿自行建立表格。
格式sk_<env>_<…>_<crc>。

紅旗

想法現實
「403 '不屬於你' 能告訴使用者原因。」這是 id 列舉預言機。對兩者都回傳 404;在客戶端解釋。
「我將在處理器中載入資料列後檢查所有權。」owns_resource 在一個守衛中同時完成載入 + 檢查 + 存入 ctx。讀取已存放的資料列;不要重新實作檢查。
「我會新增自訂的 api_keys 表格。」api_keys_glue! 提供一個已雜湊、隔離、具 scope 感知的表格。請使用它。
「從請求 body 讀取擁有者 id。」從 current_principal() 蓋印。body 是由攻擊者控制的。
「公開的 [[ingress.routes]] 路由仍需在 wasm 內進行驗證檢查。」公開意味著任何人都能訪問它——需以其他方式驗證(webhook 的 HMAC 簽章)。覆寫不會自行關閉。

整合

← boogy:designing-boogy-services 選擇為第 1 層提供輸入的入口模式。boogy:boogy-account-auth 是主體的來源(登入 → 你讀取的權杖)。關於跨服務代表使用者行動,請參閱 boogy:boogy-obo-delegation。用於簽署回呼的公開每路由例外是 boogy:boogy-webhooks 的前半部分。

相關技能