Unclassified

Round 2 — Environment and Tenancy Isolation Manifest

Date: 2026-07-29
Status: Code-level isolation exists; live verification needed


Three Runtime Profiles — Isolation Matrix

Dev Internal

DimensionValueEnforcement
Tenant IDLocal dev tenant (e.g., dev-local).env or seed auth
DatabaseLocal Neon/Docker PostgresDATABASE_URL env var
WorkersLocal via turbo devLocal process
ConnectorsFull set, test accounts, sandbox providersNo credential validation
AuthDev Clerk keys or mock.env
SecretsLocal .env filesGitignored
RoutinesFull diagnostic routes, seed toolsTenant-gated by tenant_id
IsolationProcess-level (local machine)N/A

Customer Zero

DimensionValueEnforcement
Tenant IDiw-customer-zeroHardcoded check in clerk-auth.ts via isCustomerZeroEmail()
DatabaseDedicated Neon database or schemaSeparate DATABASE_URL binding
WorkersDedicated or tagged Cloudflare WorkersIW_CUSTOMER_ZERO=true env
ConnectorsReal production connectors, real providersFull credential set in Secret Store
AuthClerk production, @integratewise.ai domainClerk org iw-customer-zero
SecretsCUSTOMER_ZERO_* namespace in Secret StoreCloudflare Workers secrets
RoutinesFull diagnostic routes, internal tools, seed controlsTenant ID gate
IsolationTenant-level (dedicated DB, dedicated Workers)Gateway tenant resolution

External Product

DimensionValueEnforcement
Tenant IDAuto-generated <tenant-slug>Created at marketplace installation
DatabaseShared Neon + strict RLSUSING (tenant_id = current_setting('app.tenant_id')::text)
WorkersShared stateless Cloudflare WorkersSame deployment as Customer Zero
ConnectorsSame binaries; tenant-scoped credentialsSecret Store TENANT_<slug>_*
AuthClerk production, external domains + marketplace JWTClerk org per tenant
SecretsTENANT_<slug>_* namespace in Secret StoreCloudflare Workers secrets
Routines19-tool public contract only; no diagnosticsTenant ID gate + profile check
IsolationRow-level (shared DB, strict RLS)Database-level enforcement

Code-Level Isolation Points

1. Gateway Tenant Resolution

File: services/gateway/src/index.ts

// Tenant resolution from verified identity
const tenantId = verifiedPrincipal.tenantId;

// Trusted header overwrite (prevents spoofing)
if (isTrustedOrigin(request)) {
  request.headers.set("x-tenant-id", tenantId);
}

2. MCP Connector Tool Gating

File: services/mcp-connector/src/continuity-bridge.ts

const isCustomerZero = actor.tenant_id === "iw-customer-zero";

if (isCustomerZero) {
  // 19 public + 4 internal tools
  tools: [
    ...PUBLIC_CONTINUITY_BRIDGE_TOOL_DEFINITIONS,
    ...INTERNAL_CONTINUITY_BRIDGE_TOOL_DEFINITIONS,
  ];
} else {
  // 19 public tools only
  tools: PUBLIC_CONTINUITY_BRIDGE_TOOL_DEFINITIONS;
}

3. Gateway Internal Route Gating

File: services/gateway/src/index.ts

// Internal routes blocked for external tenants
if (!isInternalTenant(tenantId) && isInternalRoute(path)) {
  return new Response("Forbidden", { status: 403 });
}

4. Neon Query Tenant Isolation

File: services/spine/src/neon-workbench.ts

// Every Neon query includes tenant_id as first bind parameter
const result = await neon.query("SELECT * FROM workbench WHERE tenant_id = $1", [tenantId]);

5. Frontend Server-Resolved Identity

File: apps/web/src/lib/platform/index.ts

// Frontend calls /api/v1/workspace/discovery first
const discovery = await fetch(`${NEXT_PUBLIC_IW_API_URL}/api/v1/workspace/discovery`);
// Renders from server-resolved identity, not client inference

Verification Checklist

CheckCommandExpected
Dev Internal: local DBDATABASE_URL=... pnpm --filter @integratewise/spine devConnects to local DB
Customer Zero: dedicated DBQuery Neon: SELECT current_database()Returns Customer Zero DB name
External: RLS enforcedQuery Neon as external tenantReturns only rows matching tenant_id
Gateway: tenant isolationcurl -H "x-tenant-id: iw-customer-zero" ...Returns Customer Zero data
Gateway: external tenantcurl -H "x-tenant-id: acme-corp" ...Returns Acme Corp data only
Gateway: cross-tenant denialcurl -H "x-tenant-id: acme-corp" .../iw-customer-zero/...Returns 403
MCP Connector: tool gatingMCP list_tools as external tenantReturns 19 tools only
MCP Connector: internal toolsMCP list_tools as iw-customer-zeroReturns 23 tools
Frontend: no direct DBGrep apps/web for postgres:// or neon.techZero hits
Frontend: no direct providerGrep apps/web for provider API URLsZero hits