신뢰 모델 — DevPay / 셀러 / 바이어
읽기 안내
플랫폼은 라이선스 권위 시스템을, 판매자는 자신의 애플리케이션 서버를, 구매자는 자신의 기기를 제어합니다.
브라우저 코드 은닉·완전 변조 방지·판매자 자체 서버의 모든 실행 관측을 보장하기 전에 이 책임 경계를 확인하세요.
상태: PROPOSAL. 규범적 위협 가정은
experiments/auto-protection/docs/THREAT_MODEL.md와experiments/secure-browser-licensing/threat-model.md에 있습니다; 이 파일은 그것들과 실제 코드에서 파생된 제품 수준 책임 분할입니다.
1. 3자
DevPay (플랫폼)
DevPay는 라이선스 + entitlement 권한입니다:
- 구매 시점에 라이선스 키 발급(
checkout_impl.rs— order/payment/ledger/ audit/outbox D1 배치 안에서NG-…키 발행). - 자체 ES256 키로 entitlement 서명(Worker에서는
iss="devpay-lic"—be-cloudflare/license-crypto/src/lib.rs:28; 일반 SDK에서는 기본iss="devpay"—sdk/license/server). - DevPay가 관찰하는 것만 미터링합니다.
license_usage행은 DevPay 호스티드/licenses/execute(license_security.rs)에서만 쓰입니다. 셀러의 BYOS 서버 내부 실행은 셀러가 보고하지 않는 한 DevPay에 보이지 않습니다 — 오늘 사용량 보고 엔드포인트가 없습니다(§3 참조). - 검증 표면을 운영합니다: 공개
licenses/verify, 가드된licenses/status, 원장/감사 무결성 엔드포인트(/ledger/verify,/audit/verify). - 계약된 SLA 범위: 권한 엔드포인트와 DevPay 자체 D1 레코드의 가용성 및 무결성. DevPay의 SLA는 셀러 서버 가동 시간, 셀러 측 유료 기능의 정확성, 바이어 디바이스를 커버하지 않습니다.
셀러 (소프트웨어 소유자)
- 소프트웨어와 유료 기능을 소유; 상품과 기능 네임스페이스를 정의합니다 (
products.features_json이 유일한 entitlement 진실 공급원 —license_security.rs106/128행 주석). - BYOS: 강제 서버를 운영합니다(
@yetdapay/license-server임베드, 자체LicenseStore소유, 자체 서명/검증 설정 유지). - 서버 측 유료 기능 정확성의 최종 책임: 셀러 서버가 잘못된 결과를 반환하거나, 실패하거나, 라이선스가 의미하는 것보다 약한 정책을 강제하면 그것은 DevPay가 아닌 셀러의 결함입니다.
- 고객 대면 판매 및 지원: 환불은 셀러 시작(
refund_impl.rs는 셀러 인증 + 소유권 스코프 필요); 바이어 분쟁은 셀러에게 갑니다.
바이어
- 성공적인 구매 후 라이선스 키를 받음; 공개 JWK를 제시하여 디바이스에 활성화 → 디바이스 바운드 entitlement 수신.
- 제한 내에서 라이선스된 제품을 사용할 수 있습니다: 라이선스 상태 (
active/suspended/revoked/expired),max_devices바인딩 슬롯, 기능 세트, 만료. 바이어는 클라이언트에서 잠재적으로 적대적이라고 가정됩니다 (client_fully_controlled—spec/위협 모델 열거).
2. 명시적 한계 (협상 불가 정직 조항)
- BYOS는 셀러가 SDK 검사를 제거할 수 있음을 의미합니다. 자체 서버를 제어하는 셀러는
@yetdapay/license-server호출을 삭제하거나 우회할 수 있습니다. 기술적 방지는 없습니다 — BYOS 강제는 바이어 대면 계약을 보호합니다(정직한 셀러 서버가 권한 없는 호출자를 거부), DevPay를 위해 셀러 코드를 변조 방지하지 않습니다. 셀러가 DevPay가 강제를 제어하기를 원하면 DevPay 호스티드 실행을 사용해야 합니다 — 오늘 이는 정확히 하나의 내장 연산 (premium_convert)을 실행합니다. - DevPay는 관찰하지 않는 셀러 서버 내부의 실행을 미터링할 수 없습니다.
license_usage/security_events는 DevPay 호스티드 호출만 봅니다. BYOS 미터링은 SDK 내부(LicenseStore소비 원장, 런타임metering.mjs)에 존재하지만 셀러 로컬이고 셀러 제어 — PoC 런타임에서는 단일 프로세스 인메모리입니다(SUPPORTED-SCOPE 항목 5). - 협력하는 entitled 세션은 다른 이들에게 합법적 oracle입니다 (협력 릴레이, PARTITION-MODEL T3) — capability 일회성 + 미터링으로 완화, 잔여물
R-relay-in-place로 기록됨. - 배송된 결과의 재배포 (아날로그 홀) — 바이어가
y를 받으면 재공유를 막을 방법이 없습니다. 범위 밖(UNIFIED-ARCHITECTURE §6 항목 8). - DevPay 호스티드 증명은 RFC 9449 DPoP가 아닙니다 —
jkt유도,htu(pathname+query, origin 없음), 페이로드jwk, 커스텀 body 해시h는 DevPay 고유 버전 계약입니다(results등급 감사:experiments/secure-browser-licensing/docs/p5-sdk-contract-audit.md). node:vm은 보안 경계가 아닙니다 — 신규 탈출은 동일 uid 프로세스를 산출합니다(정직한 한계, UNIFIED-ARCHITECTURE §6 항목 2).
3. 보안 보장 표
| 목표 | 달성 조건 (무엇이 성립해야 하는가) | 잔여 리스크 |
|---|---|---|
| 클라이언트 변조 저항 | 유료 로직은 서버 측에서 실행; 클라이언트 번들에 유료 알고리즘이나 시크릿 없음; 요청당 entitlement/증명 검증(license_security.rs의 guard()). | 클라이언트 측 검사는 UX 게이트일 뿐 — 클라이언트에 배송된 모든 것은 수정 가능; ad-UI 숨김 계열 보호는 보호 불가(R-ad-ui-hiding). XSS는 상주하는 동안 추출 불가 키를 사용할 수 있음. |
| 무인증 서버 실행 | 모든 보호된 호출: 유효 entitlement(ES256, iss, exp) + 라이선스 상태 라이브 재조회 + DPoP 서명/htm/htu/iat + jkt 일치 + jti 원자적 소비 + body 해시 + 상품 네임스페이스의 기능 + 일회성 capability. 생성된 서버의 yp-license 모드(런타임 auth.mjs). | dev-open 모드는 아무것도 인증하지 않음(문서화된 플레이스홀더). BYOS 강제는 셀러 연동 정확성에 의존. Worker entitlement는 aud/tenant_id 없음 — 일반 리소스 인가가 아닌 product-scope(감사 발견). |
| 클론 저항 | 값은 서버가 권한 없는 자에게 거부하는 서버 동작을 요구해야 함(partition-model T0/T1/T2); oracle 예산은 미터링 + capability로 경계. | 공개/순수 클라이언트 계산은 바이트 정확히 복제 가능(exp1, exp3 v1, exp5 v1). 배송된 결과는 재공유 가능(아날로그 홀). 예산 B 내 F_k 재구성은 불가능이 아닌 경계. |
| Entitlement 우회 | 폐기/만료/상품 불일치는 토큰 수명과 무관하게 모든 가드된 호출에서 라이선스 행에 대해 재검사(guard() 단계 2). 기능은 products.features_json에서만 해결; 상품 조회 실패 → fail closed(현재 코드; 감사에 기록된 이전 폴백 결함은 effective_features 오류 경로에 따라 fail-closed로 수정됨). | 서명 키 침해는 유효 entitlement를 발행(키 보관 = 배포 시점 관심사). kid/로테이션 중복 없음 — LICENSE_SIGNING_JWK 로테이션은 라이브 entitlement를 무효화(120초 윈도우가 영향 경계). |
| Capability 재사용 | execution_capabilities는 cap_id+lic_id+product_id+jkt+feature를 바인딩하는 단일 UPDATE … WHERE consumed=0 AND exp>?로 소비(license_security.rs); 리플레이 → 409 CAPABILITY_CONSUMED + security_events 행. | Capability는 사람이 아닌 디바이스 jkt에 바인딩 — 공유된 디바이스 키페어는 공유된 capability. track-b의 크로스 세션 entitlement 리플레이는 NOT_VERIFIED(크로스 트랙 리포트 잔여물). |
| 서버 시크릿 보호 | 생성된 파티션 서버: 잡당 node:vm, process 파사드 축소, 프로비저닝 글로벌 삭제, 시크릿은 __partitionRequireSecret로만 도달, 자식 env는 PARTITION_SECRET_*만 전달(runtime/); 서명 키는 wrangler secret put / .dev.vars로, 절대 커밋 안 함. | node:vm 탈출 → 같은 곳에 위치한 파일의 동일 uid 읽기(호출자 금지 시크릿을 파티션 워커와 같은 곳에 두지 말 것 — 문서화됨). setsid 후손 탈출. 로테이션 후 시크릿 보관은 배포 감사이며 정적으로 증명 불가 → 플래너는 SERVER_SECRET_DEPENDENT를 AUTO가 아닌 AGENT로 라우팅. |
| 사용량 제한 | max_devices는 단일 문장 원자적 슬롯 insert로 강제(INSERT…SELECT…WHERE count<max); entitlement TTL 120초; capability TTL 60초; 런타임 미터링의 주체당 쿼터(시작 시 예약/종료 시 환불). | 런타임 미터링은 단일 프로세스 인메모리 — 재시작 시 리셋, 인스턴스 간 공유 안 됨; 멀티 인스턴스 경계 B는 NOT_VERIFIED. Worker license_usage 기록은 최선 노력(실행과 트랜잭션 아님). |
| 테넌트 격리 | orders/products/projects/subscriptions/licenses 쿼리의 seller_id 스코핑; seller_scope_db가 DB에서 role/seller_id 재독(클레임 과신뢰 제거); SDK tenantId/expectedTenantId + resourceOwnership 훅; /api/usage는 yp-license 아래 숨김(크로스 테넌트 미터링 누출 차단). | Worker entitlement는 tenant_id 클레임 없음 — 소비자가 product_id를 검사하지 않으면 크로스 서비스 토큰 혼동은 무가드. 셀러 멤버십 모델은 단일 소유자(role='owner'); 소유자 범위 너머의 멀티 유저 셀러 조직은 미구현. |
| 원장 원자성 | order/payment/subscription/license/ledger/audit/outbox 쓰기가 하나의 db.batch(원자적); refund_attempts는 시도를 미리 기록, 부분 환불을 amount_krw로 상한, 불확실한 PG 결과에 needs_review; orders.refunded_krw는 성공 시도의 읽기 프로젝션; 원장/감사 무결성 해시 체인 + /ledger|audit/verify 엔드포인트. | D1 배치는 원자적이지만 크로스 PG 최종 상태는 webhook/confirm reconcile에 의존; needs_review는 수동/스케줄 해결 필요(자동화된 재검사 잡 미관찰). Outbox deliver_event는 false를 반환하는 스텁 — 오늘 원장 파생 이벤트를 전달하는 실제 소비자 없음(재시도 후 dead-letter). |
4. 각 당사자가 주장할 수 없는 것
- DevPay는 주장할 수 없습니다 모든 복제 방지, 셀러 내부 실행 미터링,
node:vm/브라우저 저장소를 하드 경계로 만드는 것. - 셀러는 주장할 수 없습니다 DevPay가 자기 가동 시간, 기능 정확성, 지원 의무를 뒷받침한다는 것; BYOS 셀러는 자체 강제 제거를 DevPay에 귀속할 수 없습니다.
- 바이어는 주장할 수 없습니다 라이선스 양도권,
expires_at너머의 영구 접근,max_devices너머의 디바이스 슬롯.