Designer → Backend
webflow.getIdToken() → POST /auth/resolve-id-token → JWT HS256, 60-min TTL. assertSiteMatch blocks cross-site (IDOR) access; silent re-auth on 401.
Webflow → CF Worker
HMAC-SHA256 over timestamp:rawBody; headers x-webflow-signature + x-webflow-timestamp. Constant-time crypto.subtle.verify.
CF Worker → Backend
x-shared-secret header on every internal call; 100 req/s limiter on /internal.
Force-reindex gate
5-min HS256 confirmation token minted by the sync-cost-preview endpoint, plus a 5-min per-mapping cooldown.
Browser exposure
Published site sees only the public search-only Algolia key; the extension JWT lives in memory; zero secrets in the CDN script.
Fail-fast env
Zod schema validates env at boot — JWT_SECRET ≥ 32 chars, ENCRYPTION_KEY exactly 64 hex chars; missing vars stop boot.
AES-256-GCM
Webflow OAuth token + Algolia admin key encrypted as v1:iv:tag:ciphertext. Boot guard assertCredentialsEncrypted fails startup if plaintext rows exist; API responses + logs redact secrets.
ci.yml — per PR
lint · typecheck · reconcile gate · build · backend / extension / shared-types / script tests · security audit.
publish-script.yml
Manual dispatch: semver bump → npm publish @latest → git tag + GitHub release. jsDelivr resolves @major natively — no dist-tag step.
deploy-docs.yml
Docusaurus build → Cloudflare Pages.
cf-worker.yml
Worker tests; the deploy job is a disabled stub — deploys are manual wrangler deploy --env <env>.