Skip to content

fix(server): materialize cloud providers before the first workspace exists - #3625

Open
benjaminshafii wants to merge 7 commits into
devfrom
fix/cloud-provider-sync-before-first-workspace
Open

fix(server): materialize cloud providers before the first workspace exists#3625
benjaminshafii wants to merge 7 commits into
devfrom
fix/cloud-provider-sync-before-first-workspace

Conversation

@benjaminshafii

Copy link
Copy Markdown
Member

Problem

Signing in to OpenWork Cloud on a clean profile — before any workspace exists — made every cloud provider sync fail with workspace_missing. Managed models never materialized until an app restart.

Fix

CloudProviderSync.apply() no longer requires an engine workspace: providers and credentials materialize into the global runtime config and env store immediately, and the engine runtime-config write + reload are deferred (reloadPending) until the first workspace exists. The first post-workspace sync (or the embedded-server restart that workspace creation triggers on desktop) delivers the providers and auth.

Also in this branch, required to build/validate at all:

  • fix(deps): pin esbuild to 0.25.12 — 0.28.x refuses to lower plain destructuring for Vite's browser-target lists, killing pnpm dev on fresh installs (pre-existing on dev, reproduced locally and in every Daytona sandbox).
  • fix(evals): trust rotated Daytona preview origins (Den seed sign-in 403 INVALID_ORIGIN), detach the Daytona install gate from the exec channel (observed wedging 40+ min).
  • den-api: drop stale sharp .d.ts path mapping (crashed tsx-run Den boots; tsc --noEmit clean without it).

Regression coverage

  • apps/server/src/cloud-provider-sync.e2e.test.ts: sync with zero workspaces returns noop, materializes providers globally, performs no engine calls; after the first workspace appears the next run is applied with exactly one reload and PUT /auth/<provider>.
  • evals/specs/cloud-provider-before-workspace.slow.test.ts: real Den + desktop app; signs in before any workspace, proves sync succeeds, creates the first workspace through the product, and proves the managed model is selectable without a restart.

Verification (agent-first, real Daytona)

Verdict: Passed (on PR head 3f1b9c7)

  • bun test src/cloud-provider-sync.e2e.test.ts (apps/server): 2 pass / 0 fail, 55 expects.
  • pnpm --dir apps/server typecheck: clean. tsc -p ee/apps/den-api/tsconfig.json --noEmit: clean.
  • RED counterfactual — same spec, same lane, server fix reverted (repro/cloud-provider-before-workspace-unfixed = dev + infra fixes only):
    OPENWORK_EVAL_APP_SPECS=1 OPENWORK_EVAL_DAYTONA=1 OPENWORK_EVAL_REF=repro/cloud-provider-before-workspace-unfixed pnpm --dir evals exec vitest run --config vitest.config.ts --project stack specs/cloud-provider-before-workspace.slow.test.ts
    → 1 failed, exactly on the target assertion: {"status":"failed","message":"workspace_missing"}.
  • GREEN — same command with OPENWORK_EVAL_REF=fix/cloud-provider-sync-before-first-workspace (cold boot, no reuse):
    → 1 passed (270s); tape 4/4 facts passed.

Evidence tape for the PR head follows in the sticky comment.

…xists

Cloud provider sync threw workspace_missing when a Den session landed
before any workspace was created (fresh sign-in on a clean profile).
Materialize providers and credentials globally right away and defer the
engine runtime-config write + reload until the first workspace appears.

Also drop the stale sharp .d.ts path mapping in den-api's tsconfig: it
pointed at a path removed in sharp 0.35, which crashed tsx-run Den API
boots that resolve tsconfig paths at runtime.

Adds a server regression test and an app-driving testkit spec that signs
in before any workspace exists, proves sync succeeds, then creates the
first workspace and proves the managed model is selectable without a
restart.
Daytona now mints a fresh preview hostname on every preview-url call, so
origins baked into DEN_BETTER_AUTH_TRUSTED_ORIGINS at boot go stale
immediately and the testkit's Den seed proof fails with INVALID_ORIGIN.
Trust the preview proxy domain by wildcard; local hosts are unaffected.
…nstalls

esbuild 0.28.1 (pinned repo-wide in #3588) refuses to lower plain
destructuring for browser-name target lists, which are exactly Vite's
optimizeDeps defaults — so every fresh install since the pin boots a dev
server that dies with ~3000 esbuild errors and a blank renderer. Stale
node_modules kept older 0.25.x binaries, which is why local checkouts
still worked while every Daytona sandbox failed its Vite prewarm gate.

0.25.12 keeps the >=0.25.0 dev-server CORS fix that motivated the pin.
…t workspace

On the welcome route with zero workspaces the renderer never pushes its
Den session to the local server, so the spec now performs the same
PUT /den-session the desktop runtime does and asserts the sync run
directly: pre-fix it returns failed/workspace_missing, post-fix
applied|noop.
location.reload() detached the CDP session and every later evaluate
timed out. The server already emits onWorkspacesChanged, so re-navigate
to the session route in a bounded loop until the composer appears.
An API-seeded workspace is a state the product never produces (no engine,
no model catalog) and the session route never becomes the task UI. After
sign-in the paved createAndSelectWorkspace path drives organization
onboarding and the app's own workspace.create action.
A long pnpm install streaming progress over a single daytona exec
session was observed wedging past every local timeout, leaving the
install gate stuck for 40+ minutes on a sandbox whose install had
already finished. Run the install detached with its output in a file
and poll a childless exit sentinel instead — the same shape as the
first-boot and Vite prewarm gates.
@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
openwork-app Ready Ready Preview Aug 7, 2026 5:23pm
openwork-den Ready Ready Preview Aug 7, 2026 5:23pm
openwork-den-worker-proxy Ready Ready Preview Aug 7, 2026 5:23pm
openwork-diagnostics Ready Ready Preview Aug 7, 2026 5:23pm
openwork-landing Ready Ready Preview, v0 Aug 7, 2026 5:23pm

@benjaminshafii

Copy link
Copy Markdown
Member Author

Photo roll — managed-models-survive-sign-in-before-the-first-workspace-exists — 0/0 frames passed · 4 facts

0/0 frames passed · 4 facts · 4 expectations passed · 0 failed

ℹ️ FACT — 1. A sync with zero workspaces no longer fails with workspace_missing

Run result: {"status":"noop"}

  • PASS A sync with zero workspaces no longer fails with workspace_missing — Run result: {"status":"noop"}

ℹ️ FACT — 2. Provider sync succeeds before a workspace exists

Sync status: {"hasSession":true,"lastRun":{"at":"2026-08-07T17:15:25.761Z","status":"noop"},"providers":[{"cloudProviderId":"lpr_01kzekcxrne3kar0jt7sz92782","providerId":"lpr_01kzekcxrne3kar0jt7sz92782","sourceProviderId":"first-workspace-models","name":"First Workspace Models","source":"custom","updatedAt":"2026-08-07T17:12:42.005Z","modelIds":["first-workspace-proof-model"],"importedAt":1786122924642}]}

  • PASS Provider sync succeeds before a workspace exists — Sync status: {"hasSession":true,"lastRun":{"at":"2026-08-07T17:15:25.761Z","status":"noop"},"providers":[{"cloudProviderId":"lpr_01kzekcxrne3kar0jt7sz92782","providerId":"lpr_01kzekcxrne3kar0jt7sz92782","sourceProviderId":"first-workspace-models","name":"First Workspace Models","source":"custom","updatedAt":"2026-08-07T17:12:42.005Z","modelIds":["first-workspace-proof-model"],"importedAt":1786122924642}]}

ℹ️ FACT — 3. The first workspace receives the already-synced model without restarting

Selectable model: {"id":"first-workspace-proof-model","name":"First Workspace Proof Model","providerName":"First Workspace Models","selected":false,"selectable":true}

  • PASS The first workspace receives the already-synced model without restarting — Selectable model: {"id":"first-workspace-proof-model","name":"First Workspace Proof Model","providerName":"First Workspace Models","selected":false,"selectable":true}

ℹ️ FACT — 4. Workspace creation does not reintroduce the sync failure

Final sync status: {"hasSession":false,"lastRun":null,"providers":[]}

  • PASS Workspace creation does not reintroduce the sync failure — Final sync status: {"hasSession":false,"lastRun":null,"providers":[]}

Roll created 2026-08-07T17:11:25.569Z · Source: evals/results/rolls/2026-08-07T17-11-25-568Z-managed-models-survive-sign-in-before-the-first-workspace-exists/roll.json · Repro: pnpm fraimz:publish -- --pr 3625 --roll 2026-08-07T17-11-25-568Z-managed-models-survive-sign-in-before-the-first-workspace-exists

PREVIEW_PROXY_WILDCARD="https://*.${PREVIEW_PROXY_HOST#*.}"
;;
esac
export DEN_BETTER_AUTH_TRUSTED_ORIGINS="${DEN_BETTER_AUTH_TRUSTED_ORIGINS:-$CORS_ORIGINS${PREVIEW_PROXY_WILDCARD:+,$PREVIEW_PROXY_WILDCARD}}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wildcard trusts attacker-controlled Daytona preview origins

Deriving a wildcard from the preview host trusts every HTTPS subdomain of the shared Daytona domain, allowing an attacker with their own preview URL to bypass Better Auth origin checks and perform cross-site authenticated requests against another sandbox.

Evidence
  • The changed default sets DEN_BETTER_AUTH_TRUSTED_ORIGINS to https://*.${PREVIEW_PROXY_HOST#*.} for non-local multi-label hosts.
  • ee/apps/den-api/src/auth.ts passes these values directly to Better Auth as trustedOrigins.
  • Daytona preview URLs are minted per sandbox, while other users can obtain preview subdomains under the same parent domain.
  • Better Auth therefore treats an attacker-owned Daytona preview origin as trusted for authenticated requests; use the exact rotated origin or a dedicated isolated suffix instead of a shared-domain wildcard.

Identified by Warden · diff-security-review · LPZ-RD6

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant