feat(cloud): sign each person's own agent in for their cloud workspaces

Closed
SA
2mo ago
+24040-65

Why

Every cloud sandbox runs on the organization's Anthropic and OpenAI keys, so all agent usage lands on our bill with no per-organization attribution or cap. docs/cloud-sandbox-considerations.md marks that unshippable the moment someone outside the team can create a sandbox — it is one of the things that has to exist before the gate comes off, not after.

This lets a person connect their own agent credential once. The cloud workspaces they start then run as them.

What changes

Storage. agent_credentials, one row per person per agent, encrypted at rest. The AES-GCM envelope that environment secrets already used is extracted to packages/trpc/src/lib/secret-box.ts so there is one implementation; the AAD string and envelope layout are byte-identical, so existing secrets keep decrypting. Credentials bind their ciphertext to the person and agent instead of an environment row. Scoped to the user, not the organization — a subscription is personal and should follow someone between organizations.

Validation happens before storage. agentCredential.set calls the provider first and only writes if the provider accepts, so one call both verifies and saves and a bad key never reaches the database. Each refusal has its own catalogue key.

Injection. provisionCloudWorkspace resolves the starter's credential for the agent being launched and appends it to workspaceEnv, after the environment's own keys so a personal sign-in wins. The new variables are added to INHERITED_IDENTITY_ENVS, so promoting a sandbox to a golden blanks them and no golden carries the promoter's credential, and to SANDBOX_AGENT_CREDENTIAL_KEYS so the in-sandbox host-service forwards them to the agent process.

Settings. A new Cloud group in the settings sidebar holds Environments (moved out of Organization) and Agents. The old /settings/agents page — enabled agents, launch commands, hooks — is relabelled Agent commands, since it is a different thing and two entries called "Agents" would be unreadable. The whole group is v2-only, so it hides itself when cloud is off.

A subscription here is the agent CLI's own long-lived token (claude setup-token), which needs no refreshing on our side. That is why there is no OAuth broker in this PR.

Three things the design does not do

  • No token reveal. The server never returns a stored secret, so the connected view names the account rather than showing a masked value behind an eye toggle.
  • No "Sign in with Claude" button. The mock faked an OAuth round trip. The paste path is the real flow, so the button is gone rather than lying about what it does.
  • Custom providers are the Vercel AI Gateway only. A credential row holds one value plus a base URL. Bedrock needs an access key, a secret, a session token and a region, so it is not shipped rather than half-wired.

Verified

Driven over CDP against the real API and a Neon branch, not mocked:

  • Bad key → Anthropic rejected this API key., dialog stays open, zero rows written.
  • Real key → verified and saved, one row, 184-byte ciphertext, nothing stored in plaintext, last_validated_at set, list reads "Connected with API key".
  • Remove → row gone.
  • Encryption compatibility: ciphertext written by the pre-refactor code still decrypts, and a value moved to another row is refused.

The gateway check was wrong in three ways and is fixed: the base URL already carries its version segment and the probe appended another (404 on every key, good ones included); it sent Anthropic's header to an endpoint speaking the OpenAI convention; and the gateway's model list answers 200 with no credential at all, so fixing only the path would have made the check accept everything. Gateway keys now probe /v1/credits, which authenticates. Measured:

EndpointNo credentialBad key
api.anthropic.com/v1/models401401
api.openai.com/v1/models401401
ai-gateway.vercel.sh/v1/models200200
ai-gateway.vercel.sh/v1/credits401401

Not verified

  • No cloud workspace has run on a personal credential yet. The injection path is written and reviewed but never exercised end to end. This is the main thing to test before merging.
  • A valid gateway key was never accepted, only bad ones refused — I do not have one. The credits endpoint authenticates, so a good key should pass, but that is inference.

Before merging

The cloud image has to be rebuilt. packages/host-service is bundled into superset-hostsvc by scripts/sandbox/image.ts, and no application deploy workflow runs sandbox:release. Until bun run sandbox:release runs without --skip-base, sandboxes keep the old host-service bundle and will not forward the new credential variables — the credential would be stored and injected into the sandbox environment but never reach the agent process. Merging without this is not harmful, just inert.

Notes

  • Migration 0111_agent_credentials, applied only to a personal Neon branch. No shared or production database was touched.
  • No tests, per the standing preference; say the word if you want them for the validation probes.

https://claude.ai/code/session_01XKiU6G6KYav29vnCvkWD45


Summary by cubic

Lets a person connect their own agent credential once, so cloud workspaces they start run as them instead of on the organization's shared keys.

What changed

  • agent_credentials stores one row per user per agent, encrypted at rest; the envelope environment secrets use is now shared in packages/trpc/src/lib/secret-box.ts, byte-identical so existing secrets keep decrypting.
  • Saving probes the provider before writing, so a bad key never reaches the database; each refusal has its own error message.
  • The probe refuses restricted addresses (loopback, private, link-local, carrier-grade NAT) and does not follow redirects, so a caller-supplied URL cannot reach internal services.
  • Provisioning injects the starter's credential after the environment's own env vars, so a personal sign-in wins, and promotion to golden blanks them.
  • The gateway probe had three faults (doubled path segment, wrong auth header, model list answering 200 with no credential) and now checks /v1/credits.
  • A new Cloud settings group holds Environments and Agents; the old Agents page is now "Agent commands", and the group is v2-only.
  • The server never returns a stored secret, and custom providers are gateway-only — Bedrock needs more fields than one value plus a base URL.
  • The provider is stored and returned, so a base URL alone no longer makes a key look custom; a gateway always carries its endpoint, must be an API key, and base URLs must be https so the key never travels in cleartext. The copy button beside the setup-token command now copies.

Before merging

  • Migrations 0111_agent_credentials and 0112_agent_credential_provider exist only on a personal Neon branch, not shared or production; 0111 was restored after a rewrite failed the deploy, so the provider column arrives in 0112.
  • The injection path has not run end to end; no cloud workspace has used a personal credential yet.
  • A valid gateway key was never accepted, only bad ones refused — the credits probe should pass one, but that's inference.

Summary by CodeRabbit

  • New Features

    • Added an Agents section in Settings for configuring Claude and Codex access.
    • Connect agents using subscriptions, API keys, or custom providers, including Vercel AI Gateway.
    • Added credential verification, connection status, account labels, setup guidance, copying the Claude setup command, and disconnect options.
    • Saved credentials are available in cloud workspaces and sandboxes.
  • Localization

    • Added translations and provider authentication messages across supported languages.
  • Security

    • Credentials are encrypted and validated before saving.
    • Custom endpoints require HTTPS and public accessibility.