Trust Model — DevPay / Seller / Buyer
Reading guide
The platform controls its license authority, sellers control their application servers, and buyers control their devices. Each party has a different trust boundary.
Read this before promising source secrecy, tamper-proof clients, or metering of execution on an unmanaged seller server.
Status: PROPOSAL. Normative threat assumptions live in
experiments/auto-protection/docs/THREAT_MODEL.mdandexperiments/secure-browser-licensing/threat-model.md; this file is the product-level responsibility split derived from them and from the actual code.
1. Three parties
DevPay (the platform)
DevPay is the license + entitlement authority:
- Issues license keys at purchase time (
checkout_impl.rs—NG-…key minted inside the order/payment/ledger/audit/outbox D1 batch). - Signs entitlements with its own ES256 key (
iss="devpay-lic"on the Worker —be-cloudflare/license-crypto/src/lib.rs:28;iss="devpay"default on the generic SDK —sdk/license/server). - Meters only what DevPay observes.
license_usagerows are written only by DevPay-hosted/licenses/execute(license_security.rs). Executions inside a seller's BYOS server are invisible to DevPay unless the seller reports them — there is no usage-reporting endpoint today (see §3). - Runs the verification surface: public
licenses/verify, guardedlicenses/status, ledger/audit integrity endpoints (/ledger/verify,/audit/verify). - Contracted SLA scope: availability and integrity of the authority endpoints and of DevPay's own D1 records. DevPay's SLA does not cover seller-server uptime, correctness of seller-side paid features, or buyer devices.
Seller (software owner)
- Owns the software and the paid features; defines products and the feature namespace (
products.features_jsonis the sole entitlement source of truth —license_security.rscomments at lines 106/128). - BYOS: operates the enforcement server (embeds
@yetdapay/license-server, owns itsLicenseStore, keeps its own signing/verification configuration). - Final responsibility for server-side paid-feature correctness: if the seller's server returns the wrong result, fails, or enforces a weaker policy than the license implies, that is the seller's defect, not DevPay's.
- Customer-facing sales & support: refunds are seller-initiated (
refund_impl.rsrequires seller auth + ownership scope); buyer disputes go to the seller.
Buyer
- Receives a license key after a successful purchase; activates it on a device by presenting a public JWK → receives a device-bound entitlement.
- May use the licensed product within limits: license status (
active/suspended/revoked/expired),max_devicesbinding slots, feature set, and expiry. The buyer is assumed potentially hostile at the client (client_fully_controlled—spec/threat-model enum).
2. Explicit limits (non-negotiable honesty clauses)
- BYOS means the seller can remove the SDK checks. A seller controlling their own server can delete or bypass
@yetdapay/license-servercalls. There is no technical prevention — BYOS enforcement protects the buyer-facing contract (unentitled callers are refused by an honest seller server), it does not make the seller's code tamper-proof for DevPay. If the seller wants DevPay to control enforcement, they must use DevPay-hosted execution — which today runs exactly one built-in operation (premium_convert). - DevPay cannot meter executions inside seller servers it does not observe.
license_usage/security_eventsonly see DevPay-hosted calls. BYOS metering exists inside the SDK (LicenseStoreconsumption ledger, runtimemetering.mjs) but is seller-local and seller-controlled — single-process in-memory in the PoC runtime (SUPPORTED-SCOPE item 5). - A cooperating entitled session is a legal oracle for others (cooperative relay, PARTITION-MODEL T3) — mitigated by capability single-use + metering, recorded as residual
R-relay-in-place. - Delivered-result redistribution (analog hole) — once a buyer receives
y, nothing stops resharing. Out of scope (UNIFIED-ARCHITECTURE §6 item 8). - DevPay-hosted proof is not RFC 9449 DPoP — the
jktderivation,htu(pathname+query, no origin), payload-jwk, and custom body-hashhare a DevPay-specific versioned contract (results-class audit:experiments/secure-browser-licensing/docs/p5-sdk-contract-audit.md). node:vmis not a security boundary — a novel escape yields a same-uid process (honest limit, UNIFIED-ARCHITECTURE §6 item 2).
3. Security guarantee table
| Goal | Achievement conditions (what must hold) | Residual risk |
|---|---|---|
| Client tamper resistance | Paid logic executes server-side; client bundle contains no paid algorithm or secret; entitlement/proof verified per request (guard() in license_security.rs). | Client-side checks are UX gates only — anything shipped to the client can be modified; ad-UI hiding class protections are unprotectable (R-ad-ui-hiding). XSS can use a non-extractable key while resident. |
| Unauthorized server execution | Every protected call: valid entitlement (ES256, iss, exp) + license status re-queried live + DPoP signature/htm/htu/iat + jkt match + jti atomic consume + body-hash + feature in product namespace + one-time capability. yp-license mode in generated servers (runtime auth.mjs). | dev-open mode authenticates nothing (documented placeholder). BYOS enforcement depends on seller integration correctness. Worker entitlement carries no aud/tenant_id — product-scope, not general resource authorization (audit finding). |
| Clone resistance | Value must require a server action the server refuses for the unentitled (partition-model T0/T1/T2); oracle budget bounded by metering + capability. | Public/pure-client computation is byte-exact cloneable (exp1, exp3 v1, exp5 v1). Delivered results reshareable (analog hole). F_k reconstruction within budget B is a bound, not impossibility. |
| Entitlement bypass | Revocation/expiry/product-mismatch re-checked against the license row on every guarded call, independent of token lifetime (guard() step 2). Features resolve from products.features_json only; product-lookup failure → fail closed (current code; earlier fallback defect recorded in audit was corrected to fail-closed per effective_features error paths). | Signing-key compromise would mint valid entitlements (key custody = deploy-time concern). No kid/rotation overlap — rotating LICENSE_SIGNING_JWK invalidates live entitlements (120-s window bounds impact). |
| Capability reuse | execution_capabilities consumed by a single UPDATE … WHERE consumed=0 AND exp>? binding cap_id+lic_id+product_id+jkt+feature (license_security.rs); replay → 409 CAPABILITY_CONSUMED + security_events row. | Capabilities bind to device jkt, not to a person — a shared device keypair is a shared capability. Cross-session entitlement replay in track-b is NOT_VERIFIED (cross-track report residual). |
| Server secret protection | Generated partition servers: node:vm per job, process facade reduced, provisioning globals deleted, secrets reachable only via __partitionRequireSecret, child env forwards only PARTITION_SECRET_* (runtime/); signing keys via wrangler secret put / .dev.vars, never committed. | node:vm escape → same-uid read of co-located files (don't co-locate caller-forbidden secrets with partition workers — documented). setsid descendant escape. Secret custody post-rotation is a deploy audit, statically unprovable → planner routes SERVER_SECRET_DEPENDENT to AGENT, never AUTO. |
| Usage limits | max_devices enforced by single-statement atomic slot insert (INSERT…SELECT…WHERE count<max); entitlement TTL 120 s; capability TTL 60 s; per-subject quotas in runtime metering (reserve-at-begin/refund-at-end). | Runtime metering is single-process in-memory — resets on restart, not shared across instances; multi-instance bound B is NOT_VERIFIED. Worker license_usage writes are best-effort (not transactional with execution). |
| Tenant isolation | seller_id scoping on orders/products/projects/subscriptions/licenses queries; seller_scope_db re-reads role/seller_id from DB (claim over-trust removed); SDK tenantId/expectedTenantId + resourceOwnership hooks; /api/usage hidden under yp-license (cross-tenant metering leak closed). | Worker entitlement has no tenant_id claim — cross-service token confusion is unguarded unless the consumer checks product_id. Seller-membership model is single-owner (role='owner'); multi-user seller orgs beyond owner scope are unimplemented. |
| Ledger atomicity | Order/payment/subscription/license/ledger/audit/outbox writes in one db.batch (atomic); refund_attempts pre-records attempts, caps partial refunds at amount_krw, needs_review on uncertain PG results; orders.refunded_krw is a read projection of succeeded attempts; ledger/audit integrity-hash chains + `/ledger | audit/verify` endpoints. |
4. What each party may NOT claim
- DevPay may not claim to prevent all cloning, to meter seller-internal executions, or to make
node:vm/browser storage a hard boundary. - Seller may not claim DevPay backs their uptime, feature correctness, or support obligations; BYOS sellers may not attribute their own enforcement removals to DevPay.
- Buyer may not claim license transfer rights, perpetual access beyond
expires_at, or device slots beyondmax_devices.