fix(cloud): attachments reach cloud workspaces, scoped to the box they belong to
Summary
- Attaching images to a cloud workspace (desktop new-workspace modal, mobile composer, mobile running session) never reached the box on the deployed API. Three breaks, fixed together.
- A box may now fetch exactly the files attached to its own workspace, and nothing wider.
- Renames the attachment parent kind
cloud_workspace_prompt→cloud_workspace(migration 0132,RENAME VALUE).
Why / Context
Reproduced on prod with the calls the clients make: a cloud create with an image in the prompt came up with Claude saying "I don't see an image attached". Inside the box, host-service had SUPERSET_SANDBOX_AGENT and _PROMPT but no _ATTACHMENTS. Mobile's running-session path (attachments.materializeIntoWorkspace on the box) failed with Failed to mint JWT: 401. Desktop's running-session paste/drop (filesystem.writeFile) worked and is unchanged.
- Create path.
/api/cloud-workspaces/provisionre-validatedlaunchwith a schema lackingattachmentFileIds; zod stripped it. A local API calls the provisioner directly, skipping QStash, which is why #7622 looked verified. - Box → API auth. host-service in a box ran
JwtApiAuthProviderwithAUTH_TOKEN=sandbox;/api/auth/tokenanswers 401, so every call the box makes to the API failed,attachment.resolveincluded. - Permission. Even with the firewall's workspace credential,
attachment.resolvewasn't onSANDBOX_ALLOWED_PROCEDURES. - Boot script.
superset-bootforwards an allowlist of identity variables to host-service andSUPERSET_SANDBOX_AGENT_ATTACHMENTSwasn't on it: the ids reached/etc/superset/sandbox.confand stopped. Found by booting a real box on this branch.
How It Works
cloudAgentLaunchSchemain@superset/shared/cloud-agent-launchis the one definition of a launch; the job route validates with it andCloudAgentLaunchis inferred from it.SandboxApiAuthProvider(host-service, sandbox mode) sends no credential; the firewall addsx-superset-sandbox-credential. The relay keeps the JWT provider.attachment.resolvefor a box caller adds one condition: the file is attached tocloud_workspace/ the caller's workspace id. Access to the box = access to its files.- Create already attaches the prompt's uploads to the workspace (#7932). For running sessions,
cloudWorkspace.attachFiles(a person's call, checked withloadVisibleWorkspace) attaches them first; mobile's terminal composer calls it for cloud workspaces before asking the box to pull.
Manual QA
Local API + local DB (workspace Neon branch) + minio, with a box credential signed by the local gate secret:
- Person
attachFiles→ rowcloud_workspaceon the workspace - Box resolves its own file and downloads the bytes
- Box resolving an unattached file → NOT_FOUND
- Box resolving another box's file → NOT_FOUND
- Box calling
attachFiles→ FORBIDDEN (not allowlisted) - Person
resolveunchanged - No credential / forged credential → UNAUTHORIZED
Real box, this branch end to end: local API behind a cloudflared tunnel as NEXT_PUBLIC_API_URL (so the firewall adds the workspace credential for it), minio behind a second tunnel, a bundle built from this branch (host-service asset rebuilt from source + the boot fix) on a dev environment, cloudWorkspace.create with an image, then the job POSTed to /api/cloud-workspaces/provision (the dev QStash token belongs to no region, and the route skips the signature in development):
- Create path: the id reaches host-service's env, the box imports the image through
attachment.resolvewith its own credential (sha256 matches the upload), and the agent is launched with the "Attached files" block pointing at it - Running-session path: person
attachFiles, then boxattachments.materializeIntoWorkspacewrites.superset/attachments/mobile.png(sha256 matches) - Box
materializeIntoWorkspacefor a file not attached to it → "Attachment not found" - Claude reading the image back: the dev image has no agent CLIs (
claude not found in PATH), so the last hop is the prompt, not the reply. Prod check owed after release.
Testing
bun turbo run typecheckfor shared, trpc, host-service, api, mobile, db, desktopbiome checkon changed filesbun test packages/shared/src/cloud-agent-launch.test.ts(new: the job schema and env round-trip keep attachment ids)- Full
bun testin trpc/host-service: 7 failures inlib/sandbox/credentials.test.ts(passes alone; untouched) and 6 timing-based git watcher tests in host-service; not caused by this diff, not confirmed against main
Migration (0132)
drizzle-kit generated a text cast + DROP TYPE / CREATE TYPE + cast back: it rewrites attachments twice and fails on prod's existing cloud_workspace_prompt row. The SQL is replaced with ALTER TYPE ... RENAME VALUE, a catalog-only change with the same end state as the generated snapshot (enum order unchanged). Applied on the workspace branch; a follow-up drizzle-kit generate reports no drift. Not rehearsed under load: it touches no table.
Risks / Rollout
- Ordering: the API now writes
cloud_workspace, so the migration must apply before the new API serves traffic. - Box side needs a sandbox release. Boxes run the host-service tarball pinned in
packages/sandbox/bundle/assets.json(currently1.29.0+bfc1ec72a), not host-service frommain, and environments pinbundleSha. Merging changes nothing in boxes: the host-service asset must be rebuilt (bun run assets host-service) and a bundle released to environments for the auth fix and thesuperset-bootfix to reach them. Until then boxes still cannot pull attachments (as today). - Allowlist widening:
attachment.resolvefor boxes is limited to files attached to the calling workspace. - Rollback: revert the code; the enum rename can stay (or
RENAME VALUEback).
Summary by cubic
Fixes cloud workspace attachments never reaching the box on the deployed API — files now arrive from desktop create, mobile composer, and running sessions, and a box can fetch only files attached to its own workspace, nothing wider.
Bug fixes
- The provision job re-validated
launchwith a schema missingattachmentFileIds, so zod stripped them; the sharedcloudAgentLaunchSchemain@superset/shared/cloud-agent-launchis now the single definition. - host-service in a box got 401 from
/api/auth/token, failing every box→API call; sandbox mode now sends no credential and the firewall's workspace credential identifies it. attachment.resolveis on the box allowlist, scoped to files attached to the caller's workspace.- Mobile composer attaches uploads via
cloudWorkspace.attachFilesbefore the box pulls them. anchorAttachmentsnow marks a file ready and inserts its attachment in one transaction, so a failed insert no longer leaves a resolved-but-orphaned file.superset-bootnow passesSUPERSET_SANDBOX_AGENT_ATTACHMENTSto host-service; previously the ids stopped at sandbox.conf and the first-boot launch never imported them.
Migration
- Renames the attachment parent kind
cloud_workspace_prompt→cloud_workspace(migration 0132, viaALTER TYPE ... RENAME VALUE, replacing drizzle-kit's table-rewriting text cast); apply before the new API serves traffic. - The box-side change ships in a sandbox bundle; pinned
bundleShaenvironments keep current behavior until they carry it.
Summary by CodeRabbit
- New Features
- Attach files to cloud workspaces and use them in the workspace terminal. Include up to 10 attachments when launching a cloud agent.
- Cloud workspace attachments are available through the workspace attachment flow.
- Bug Fixes
- Improved attachment handling so files are associated with the correct cloud workspace and remain available.
- Cloud agent launch details now receive consistent validation, including checks for supported agents, prompt length, and valid attachment IDs.