v0.3.0 — multi-user MCP control plane
Multi-user MCP control plane. Six phases (A–F) and migrations 0011→0025 turn fluidbox from a single-admin control plane into one that can host many organizations, many users, and many separately-owned credentials without ever letting a model pick an identity.
Every hosted capability is opt-in behind a flag, and the default single-admin Docker deployment is the same product it was in v0.2.0 — with FLUIDBOX_REQUIRE_SSO unset, nothing below is active.
The organizing idea: connector definition ≠ credential-bearing connection ≠ agent connection requirement ≠ per-run resource binding. An agent declares what it requires, never whose credential satisfies it. Run creation resolves each requirement to an explicit, frozen authority source before any model spend. The model picks tools; it can never pick an identity.
Highlights
- Per-organization, IdP-agnostic identity —
FLUIDBOX_REQUIRE_SSO=1confines the admin token to/v1/admin/*as break-glass and introduces three principals: Operator (admin token), User (__Host-fbx_websession cookie), and Pat (fbx_pat_bearer). Any conformant OIDC issuer is configured per org. No IdP configured ⇒ single-admin mode, unchanged. - Tenant isolation with a database floor — every tenant-owned repository method takes a
TenantScope, making isolation a signature requirement rather than a remember-to-filter convention. Migration0018adds row-level security underneath: 37 tablesENABLE+FORCERLS keyed on a transaction-local GUC, withFLUIDBOX_RUNTIME_ROLEsplitting the pool onto a non-owner role. - Connection ownership and per-run resource bindings — brokered MCP tools moved onto four objects, resolved to a tagged authority (
connection|subscription_secret|none) across typed slots before provisioning. A personal-connection approval is decidable only by its owner-who-invoked — no role, admin, or operator override. - Versioned envelope sealing with a real key-retirement path — per-tenant DEKs wrapped by a KEK (
FLUIDBOX_KMS_MODE=off|static|aws), AAD-bound so a sealed blob is untransplantable across tenants or columns. A resumable, CAS-guardedPOST /v1/admin/resealmigrates legacy rows; two boot gates fail closed in both directions. - One hardened egress boundary — a pure
admit_urlpre-flight blocks private/loopback/link-local/metadata address classes at every dial site (reqwest dials an IP literal without consulting a resolver, so the pre-flight is what actually stops169.254.169.254). Broker, delivery callbacks, and both connector-OAuth token legs refuse redirects outright — a 307/308 replays the request body, which would forward an authorization code plus PKCE verifier to the redirect target. - MCP
2025-11-25conformance — per-run upstream sessions; a negotiated version that drifts from the frozen surface denies the call. - Frozen tool schemas enforced server-side — arguments validated against the schema photographed at freeze time, dialect chosen by the snapshot's protocol version. Exactly one new stage in the permission gate; nothing else moved.
- At-most-once brokered dispatch — a durable four-state execution claim per call. Decision idempotency and execution idempotency are now distinct properties.
- Audience-scoped sandbox credentials — the sandbox's single bearer splits into four tokens (
llm|tool|control|workspace). - Replica coordination + durable budget admission — approval emission rides the deciding CAS, orchestrator leases with epoch fencing, per-row delivery claims, and request-keyed LLM reservations.
- Operations — bounded-cardinality metrics at
GET /v1/admin/metrics, durable cross-replica egress governance, S3-compatible archives, and a guarded load harness.
Validation
Five hermetic acceptance suites green against CI-identical throwaway databases — identity 87/0, bindings 104/0, secrets 128/0, hardening 274/0, scale 18/0 = 611/0 — plus live Docker-provider tiers and a second live EKS acceptance on arm64/Graviton with the runtime-role RLS split active and an AWS-audited zero-orphan teardown.
Known limitations
The gated 60/150/300-seat load campaign and the final two rollout gates remain open on #34. This release does not claim a proven 300-run production ceiling. The hosted OAuth Connect flow also carries one documented residual: a deliberately-shared start URL can still route a victim's grant into the initiating connection (docs/hosted/threat-model.md).
Upgrading
Migration 0018 is stop the old binary, migrate, then deploy — not a rolling upgrade. A pre-0018 binary sets no tenant GUC and would see zero rows, and it holds transactions across outbound HTTP that would block the migration's ACCESS EXCLUSIVE locks.
Do not drop FLUIDBOX_CREDENTIAL_KEY when enabling FLUIDBOX_KMS_MODE: run POST /v1/admin/reseal and let boot prove zero remaining v1 rows first. From the moment any v2 row exists, the KEK is the root of custody and losing it is unrecoverable — back it up before enabling.
Full changelog: CHANGELOG.md · Hosted deployment: rollout gates and KMS/RLS runbook
helm install fluidbox oci://ghcr.io/hrishikeshdkakkad/charts/fluidbox --version 0.3.0