Communitygithub.com

Boogy-ai/boogy-superpowers

Boogy 서비스를 구축하기 위한 에이전트 스킬 — 설계, 데이터 모델링, 인증, 작업, MCP/REST 및 플랫폼 제한

boogy-superpowers란 무엇인가요?

이 스킬은 Boogy 플랫폼에서 서비스를 구축할 때 필요한 인증(auth) 메커니즘을 상세히 설명합니다. Boogy의 인증은 3계층 모델(수신 승인, 라우트 가드, 행 스코프)로 구성되어 있으며, SDK에서 제공하는 검증된 헬퍼를 사용하여 보안 불변성을 유지해야 합니다. 특히 서비스 전체의 수신 모드를 특정 라우트에서 재정의하는 방법(예: 인증된 서비스 내에 공개 웹훅 엔드포인트 만들기)과 소유자 전용 관리자 서브트리를 구현하는 방법을 다룹니다. 또한 리소스 소유권 확인(`owns_resource`, `find_owned`, `load_owned`), API 키 관리(`api_keys_glue!`), 그리고 '존재 여부 마스킹'(deny-by-existence-mask)과 같은 보안 불변 사항을 강조합니다. 이 스킬은 Boogy 서비스의 설계, 데이터 모델링, 작업, MCP/REST API, 플랫폼 제한 등 다른 Boogy 스킬과 함께 사용됩니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add Boogy-ai/boogy-superpowers

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

Boogy 인증 (서비스 내부 인증)

Boogy의 인증은 세 계층으로 구성됩니다. 그중 어떤 것도 직접 구현하지 마십시오 — SDK는 보안 불변성을 유지하는 검증된 헬퍼를 제공합니다.

세 계층 모델

  1. 수신 승인(Ingress admits) — 플랫폼의 수신 모드는 요청이 서비스에 도달할지 여부를 결정합니다. 코드가 실행될 때쯤이면 호출자는 이미 알려진 주체(principal) 이거나 익명 상태입니다.
  2. 가드가 라우트를 보호(Guards gate routes) — 라우트별 검사가 핸들러 전에 실행됩니다: 로그인 요구 또는 요청 리소스의 소유권 요구.
  3. 헬퍼가 행을 스코핑(Helpers scope rows) — 핸들러는 호출자의 행만 나열/로드합니다.

호출자는 auth::current_principal() -> Option<String>로 확인합니다. 주체는 불투명합니다 — 절대 파싱, 접두사 제거, UUID 추측을 하지 말고; 소유자 열 값과 auth::*의 입력으로만 사용하십시오.

라우트별 수신: 제한된 서비스 내의 공개 라우트

1계층(수신)은 일반적으로 서비스 전체에 하나의 mode입니다. 하지만 때때로 하나의 라우트는 서비스의 나머지가 거부하는 호출자에게도 접근 가능해야 합니다 — 전형적인 예는 공개 /webhook 수신기를 나머지 서비스가 authenticated(또는 internal/mixed)인 경우입니다. 라우트별 재정의(per-route override)로 분리하십시오; 서비스의 나머지는 제한된 상태로 유지됩니다:

[ingress]
mode = "authenticated"          # 서비스 전체 기본값 — 그 외 모든 것

[[ingress.routes]]
path = "/webhook"               # 서비스 상대 경로; 모든 메서드
mode = "public"                 # 누구나 /webhook에 접근 가능

보안 계약 (읽으십시오 — 실패 시 폐쇄(fail-closed)입니다):

규칙중요성
서비스 상대 리터럴 경로. 정확히 일치(/webhook) 또는 접두사(/stripe/*, 세그먼트 경계)여야 합니다. {param} 캡처 불가.path는 라우터가 보는 경로이며, 핸들러와 동일한 프레임입니다.
가장 구체적인 일치가 승리하며; 정확히 하나의 모드가 적용됩니다. 정확히 일치가 접두사보다 우선; 긴 접두사가 짧은 것보다 우선.공개 하위 라우트는 형제 라우트를 절대 넓힐 수 없습니다: /webhook 공개가 /data를 느슨하게 하지 않습니다.
기본값 = 서비스 전체 모드. 일치하지 않는 경로(또는 .. 탐색 시도)는 제한된 기본값으로 폴백되며, 절대 가장 관대한 라우트로 폴백되지 않습니다.라우트 나열을 잊어도 보호된 상태로 남고, 노출되지 않습니다.
각 재정의는 자체 모드 + 허용 목록을 가집니다. 재정의는 allowed_agents/allowed_origins를 상속하지 않습니다.라우트 블록에 나열하십시오; 동일한 비어 있지 않음 검증이 적용되므로 오타가 조용히 모두 거부(deny-all)할 수 없습니다.
메서드별이 아닌 경로별입니다(현재). path = "/webhook"는 GET, POST 등에 동일하게 적용됩니다.공개 /webhook은 모든 동사로 접근 가능합니다 — 따라서 여전히 핸들러 내에서 요청을 검증하십시오(잘못된 GET은 아무것도 하지 않아야 합니다).
에지에서 호스트가 강제합니다. 수신은 wasm이 인스턴스화되기 전에 실행됩니다.코드에서 공개 라우트를 자체 게이트하지 않습니다; 하지만 공개 라우트는 누구나 접근할 수 있음을 의미합니다 — 다른 방식(예: HMAC 서명, boogy:boogy-webhooks 참조)으로 인증하십시오.
위임 게이트 + 속도 제한은 서비스 전체로 유지됩니다.공개 분할은 [ingress.delegation] 게이트를 우회할 수 없으며 속도 제한 버킷을 공유합니다.

[[ingress.routes]]가 없는 매니페스트는 이전과 정확히 동일하게 작동합니다 — 이는 순전히 추가 기능입니다.

반대: 개방된 서비스 내의 제한된 서브트리 (소유자 전용 /admin)

반대 방향 — 기본값이 개방된(authenticated) 서비스이지만 /admin/* 서브트리는 서비스 소유자(프로비저너)만 접근할 수 있어야 합니다. 함정: 프로비저닝 가능한 모듈은 누구나 배포하므로, 매니페스트에 신원(@alice, boogy://alice/services/*)을 하드코딩해서는 안 됩니다 — 그 리터럴 소유자는 다른 모든 프로비저너에게 잘못된 값이며, 수신 허용 목록 문자열은 배포 시 대체되지 않습니다. 수신에는 "나와 같은 소유자" 매처가 없습니다.

게이트가 없는 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("operator only"))
}

이것이 올바른 원시 요소인 이유: 인간 소유자는 자신의 토큰으로 /admin을 직접 curl할 수 있습니다(wasm은 에이전트의 핸들을 확인할 수 없지만 호스트는 가능 — 이것이 기능이 하는 일입니다), 그리고 소유자의 백엔드는 워크로드로 작동합니다. 실패 시 폐쇄: 익명, 다른 소유자, 또는 확인 불가능한 호출자 → 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). None = 누락 또는 사용자 소유 아님.
auth::require_scope(scope) -> Guard광범위한 기능 게이트: 익명이면 401, 로그인했지만 스코프가 없으면 403.

소유자 열은 항상 DEFAULT_OWNER_COL ("owner_principal") 입니다 — 절대 owner_id/created_by를 만들지 마십시오. 항목 라우트를 .group([auth::owns_resource("things", DEFAULT_OWNER_COL, "id")], |g| …)로 연결하십시오. 해당 가드 뒤의 핸들러는 ctx에 저장된 행을 읽습니다 — 다시 가져오지 마십시오.

토큰에서 소유자 규칙: 생성 시, 소유자 열을 auth::current_principal()에서 스탬프하십시오. 요청 본문에서 소유자를 절대 읽지 마십시오 — 클라이언트가 다른 사용자의 ID를 주장할 수 있습니다.

철칙: 존재 여부에 의한 거부 마스킹(deny-by-existence-mask)

누락된 것과 존재하지만 사용자 것이 아닌 것 모두 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는 세션 베어러 또는 sk_* 키를 모두 허용; 둘 다 current_principal()로 통합되므로 owns_resource/find_owned가 변경 없이 작동.
키 관리세션 신원이 필요함; sk_* 키는 키를 생성할 수 없음.
에스컬레이션 없음키는 발급자가 이미 보유한 스코프만 전달함(그렇지 않으면 403).
저장소키는 서비스 자체 저장소에 해시되어 저장됨; 절대 자체 테이블을 만들지 마십시오.
형식sk_<env>_<…>_<crc>.

위험 신호(Red flags)

생각현실
"'당신 것이 아님'에 403을 반환하면 사용자에게 이유를 알려줍니다."ID 열거 오라클입니다. 둘 다 404를 반환하고 클라이언트 측에서 설명하십시오.
"핸들러에서 행을 로드한 후 소유권을 확인하겠습니다."owns_resource는 하나의 가드에서 로드 + 확인 + ctx 저장을 수행합니다. 저장된 행을 읽고 확인을 재구현하지 마십시오.
"사용자 정의 api_keys 테이블을 추가하겠습니다."api_keys_glue!는 해시되고, 격리되며, 스코프 인식 테이블을 제공합니다. 사용하십시오.
"요청 본문에서 소유자 ID를 읽습니다."current_principal()에서 스탬프하십시오. 본문은 공격자가 제어합니다.
"공개 [[ingress.routes]] 라우트에는 여전히 wasm 내 인증 검사가 필요합니다."공개는 누구나 접근 가능함을 의미합니다 — 다른 방식(웹훅의 경우 HMAC 서명)으로 인증하십시오. 재정의가 자체 게이트하지 않습니다.

통합

← boogy:designing-boogy-services가 계층 1을 공급하는 수신 모드를 선택합니다. boogy:boogy-account-auth는 주체가 오는 곳입니다(로그인 → 읽는 토큰). 사용자를 대신하여 서비스 간에 행동하는 방법은 boogy:boogy-obo-delegation을 참조하십시오. 서명된 콜백을 위한 공개 라우트별 분할은 boogy:boogy-webhooks의 전반부입니다.

관련 스킬