Feature

Tenant subdomain isolation
enforced in middleware

Every workspace runs on its own subdomain of the portal root. The middleware extracts the tenant from the host header, confirms the workspace exists with the service-role client, blocks the workspace dashboard from the tenant surface, and rewrites portal traffic to the workspace-scoped path. Reserved names are refused at the routing layer, role-aware redirects keep consultants and clients on the right surface, and per-tenant MFA enforcement runs before any portal page renders.

No credit card required. Free plan available forever.

Tenant isolation enforced in middleware, not in policy

Enterprise security buyers, internal AppSec leads, GRC owners, and security architects evaluating a multi-tenant security platform ask the same procurement question almost every time: how is one tenant isolated from another. The answer carries weight because the platform holds findings, scan evidence, encrypted credentials, audit trails, and cross-engagement search results that must never bleed between workspaces. SecPortal answers the question with a routing-layer isolation model. Every workspace runs on its own subdomain of the portal root, every request is resolved to a tenant in middleware before any page renders, and the workspace dashboard and the client portal live on different URLs by design.

The model is enforced in code, not in policy. The tenancy module decides which subdomain corresponds to which workspace; the middleware decides whether the workspace exists; the role on the profile decides where the request lands; the activity log records every privileged action under the resolved tenant. Reserved names cannot resolve to a workspace, unknown subdomains never render a portal page, and per-tenant MFA enforcement runs before any portal layout renders. The isolation contract is the same artefact whether the caller is an internal security operator on the workspace dashboard or a client portal user on a tenant subdomain.

Eight middleware steps that resolve every request

Every request that reaches the application traverses the same middleware pipeline. The pipeline reads the host header, extracts the tenant, confirms the workspace, enforces role and MFA rules, and rewrites the request to the workspace-scoped path. The steps below describe exactly what runs and in what order, so the isolation contract is auditable end to end.

Step 1

Extract the tenant from the host header

Every request lands at the Next.js middleware, which reads the x-forwarded-host or host header and asks the tenancy module for the tenant slug. The extractor strips ports, normalises case, refuses local-like hostnames, and only returns a slug when the hostname is a direct child of the portal root. Apex requests on the portal root return no slug and continue down the consultant workspace path.

Step 2

Refuse reserved subdomain names

The tenancy module keeps a reserved set of subdomains that can never resolve to a workspace: www, app, api, auth, and dashboard. A request that lands on a reserved subdomain returns no tenant slug, so a workspace can never register a slug that collides with the platform control plane.

Step 3

Confirm the workspace exists with the service role

The middleware uses a lazily-initialised service-role Supabase client to look up the workspace by slug. The service role bypasses row-level security so the existence check is not gated by the caller identity, but the lookup is read-only and scoped to a single column. A missing workspace rewrites the request to the workspace-not-found page; a found workspace continues down the portal routing path with the resolved workspace id in hand.

Step 4

Block the workspace dashboard from the tenant surface

Any request to a path starting with /dashboard on a tenant subdomain is redirected to the tenant root, so the workspace dashboard never renders under a tenant URL. The consultant surface lives on the portal root host; the client portal surface lives on the tenant subdomain. The two surfaces never share a URL.

Step 5

Normalise legacy /portal/:slug links

If a request lands on a tenant subdomain with a path that starts with /portal/:slug, the middleware strips the prefix and redirects to the clean path. This protects the tenant URL space from leaking the rewrite-internal path shape into shared links, bookmarks, and emailed report URLs.

Step 6

Enforce role-aware redirects

On a tenant subdomain the middleware enforces a deliberate access shape. A client whose primary workspace differs from the tenant is redirected to their own portal unless the client_users table grants explicit cross-workspace access. A consultant without client_users access on the tenant workspace is redirected back to the main dashboard. The redirect logic never widens access; it only narrows it.

Step 7

Run per-tenant MFA enforcement before any page renders

If the workspace has mfa_required set, the middleware refuses to render any portal page until the session reaches AAL2. An unverified session is sent to /auth/mfa/verify; a session with no factor enrolled on an mfa-required workspace is sent to /auth/mfa/setup. The MFA gate runs on the tenant subdomain before the portal rewrite, so unauthenticated portal traffic cannot bypass it.

Step 8

Rewrite to the workspace-scoped portal path

Once authentication, MFA, and role rules have passed, the middleware rewrites the request path from / to /portal/:slug under the hood. The client sees a clean tenant URL; the application reads the request as a portal route scoped to the workspace. The rewrite is internal, so the tenant URL surface never exposes the workspace-prefix path.

Where the isolation boundaries live

The routing layer is the outer ring. Row-level security on the database, role-based access control on the API, and authenticated encryption on secrets sit underneath. Each ring assumes the next ring will hold, so a failure on one layer does not collapse the whole isolation contract.

Per-workspace subdomain space

Every workspace owns its own subdomain. The portal root carries the consultant control plane and the marketing surface; every tenant subdomain carries the client portal scoped to a single workspace. A request can never reach the workspace dashboard on a tenant subdomain and can never reach the client portal on the portal root, so the two surfaces are isolated by URL rather than by feature flag.

Workspace existence as a routing gate

A request to a subdomain that does not match a workspace lands on the workspace-not-found page, not on an empty portal. The middleware short-circuits before any portal layout, route handler, or database query runs under the unknown slug, so an attacker cannot enumerate the workspace list by probing subdomains for distinguishable response shapes.

Reserved name protection

The reserved set in the tenancy module prevents www, app, api, auth, and dashboard from ever resolving to a workspace, even if a database row exists with one of those slugs. The check sits in the slug extractor itself, so every consumer of the slug receives the same answer regardless of how the request reaches the middleware.

Role-bound cross-workspace access

A client whose primary workspace differs from the tenant is checked against the client_users table before being allowed to read portal pages on the tenant. The same check applies to consultants, who must hold a client_users row on the tenant workspace to read portal pages on a foreign tenant. Cross-tenant access is a deliberate database state, not an accident of routing.

Service-role lookup separated from the caller identity

The workspace existence check uses the service-role Supabase client so the lookup is not gated by the caller identity. This is intentional: a caller who is not signed in still receives a deterministic answer about whether the tenant exists, and a caller who is signed in to one workspace cannot trick the routing layer into routing them through a workspace they cannot see. The service-role lookup is read-only and scoped to a single column.

Row-level security under every portal query

The routing isolation is the outer layer. Every portal query inside the rewritten /portal/:slug path runs against the standard Supabase clients with row-level security on. A caller who somehow reaches a portal layout for a workspace they cannot see still sees no data, because RLS evaluates the caller identity independently of the route the request resolved to.

Enterprise procurement answers in one place

Enterprise security questionnaires and vendor risk assessments ask a predictable set of multi-tenant questions. The answers below are written against the routing rules the middleware actually enforces, so the procurement answer and the production behaviour are the same answer.

How is each tenant isolated from other tenants?

Each workspace runs on its own subdomain of the portal root, and the middleware rewrites tenant traffic to a workspace-scoped path internally. The workspace dashboard is unreachable from a tenant subdomain, and the client portal is unreachable from the portal root. Reserved names cannot resolve to a workspace. Row-level security policies on the Supabase tables sit underneath the routing layer so a request that somehow lands on the wrong tenant still sees no data.

Can one tenant enumerate other tenants?

A subdomain that does not match a workspace rewrites to the workspace-not-found page; no portal layout renders. The existence check happens in middleware with a single-column read against the workspaces table. The response shape is uniform for unknown tenants, and reserved subdomains return no tenant slug at all.

How are reserved names handled?

The reserved set in the tenancy module includes www, app, api, auth, and dashboard. These cannot resolve to a workspace even if a database row exists with a matching slug, so the platform control plane and the workspace surface can never collide on a name.

How is MFA enforced per tenant?

The workspaces table carries an mfa_required flag. The middleware reads this flag for the tenant on every request and refuses to render any portal page until the session reaches AAL2. The same enforcement applies on the workspace dashboard. The MFA gate runs before the portal rewrite, so unauthenticated portal traffic cannot bypass it.

How does a client of one workspace access another?

The client_users table is the explicit allowlist for cross-workspace access. A client whose primary workspace differs from the tenant must hold a client_users row on the tenant workspace to read portal pages; otherwise the middleware redirects them to their own portal. Cross-tenant access is a deliberate operator action, not an accident of routing.

How is the routing isolation audited?

Every privileged action that succeeds writes to the workspace activity log with the actor and timestamp. The log is workspace-scoped, so the audit trail for a tenant is a single query against the activity feed for the workspace id resolved by middleware. Internal audit and SOC 2 access review questions are answered from the same log the operator reads.

How the tenant URL space is shaped

The tenant URL space is deliberately narrow. Direct children of the portal root are the only candidates, the slug extractor refuses anything that does not match that shape, and the canonical URL helper produces the same shape for every email link and in-app redirect.

  • Each workspace lives on a unique subdomain of the portal root, formatted as workspace-slug.portal-root
  • Each tenant URL is a direct child of the portal root; nested subdomains and multi-level slugs never resolve to a workspace
  • The portal root protocol is https in production and falls back to http only for local-like hostnames such as localhost, lvh.me, nip.io, and sslip.io
  • The middleware reads x-forwarded-host first and falls back to host so the same routing rules work behind Vercel, behind a reverse proxy, and in local development
  • The buildPortalUrl helper produces canonical tenant URLs the rest of the application uses for email links, in-app redirects, and download links, so the URL shape is consistent across surfaces
  • A workspace slug change is a deliberate workspace event; the old subdomain no longer routes to a portal because the existence check fails against the new slug

Tenant contexts the model supports

Internal security teams, consulting practices, enterprises with business-unit delivery, and MSSPs all share the same routing primitives. The differences live in how many tenants the workspace runs and how the operator surface is shared with external readers, not in the underlying isolation contract.

Internal security team

A single workspace runs the internal security testing programme. Engineering, AppSec, and vulnerability management members operate on the workspace dashboard at the portal root. The client portal subdomain is used to share evidence packs and remediation status with business-unit owners, internal audit, and external consultants without exposing the workspace dashboard.

Security consulting practice with multiple clients

The consulting workspace lives on the portal root. Each client receives a dedicated subdomain backed by a client tenant on the workspace, so the client portal URL is branded to the client and isolated from every other client. The consulting team operates from the portal root; clients log in to their own subdomain.

Enterprise with multiple business units

The enterprise security workspace runs on a single subdomain. Internal stakeholders read remediation status through the client portal subdomain scoped to their business unit. The workspace dashboard remains the responsibility of the central security team and is never reachable from a business-unit URL.

MSSP delivering managed security across customers

The MSSP workspace lives on the portal root. Each managed customer receives a tenant subdomain that renders only the engagements, findings, and reports tied to that customer. The MSSP operates from the workspace dashboard; the customer reads from their tenant subdomain; the two surfaces never share a URL.

Failure modes the middleware refuses to render

The routing model is designed to refuse before it renders. The cases below are the common ones the middleware short-circuits, so an operator can read the rules and an auditor can read the same rules without reverse-engineering the application.

Unknown subdomain returns a generic page

A request to a subdomain that does not match any workspace rewrites to the workspace-not-found page. The middleware never renders a portal layout for an unknown tenant, so the response shape cannot be used to enumerate the workspace list.

Reserved name attempts

A subdomain in the reserved set never resolves to a workspace, even if a workspace row exists in the database with one of those slugs. The slug extractor returns null for reserved names, so every downstream check sees no tenant.

Workspace dashboard request on a tenant subdomain

Any /dashboard request on a tenant subdomain is redirected to the tenant root. The workspace dashboard never renders under a tenant URL, and the redirect is unconditional so the rule cannot be bypassed by changing query parameters or appending paths.

Client signed in to one workspace visits another tenant

The middleware checks the client_users table for the tenant before allowing portal access. If the client does not hold a row on the tenant workspace, the middleware builds the canonical portal URL for the client primary workspace and redirects them away from the foreign tenant.

Consultant signed in visits a tenant subdomain

A consultant on a tenant subdomain is allowed through only if they hold a client_users row on the tenant workspace. Otherwise the middleware redirects them back to the main dashboard on the portal root, so the workspace operator surface and the client portal surface stay separated.

Unverified MFA factor on an MFA-required workspace

A session at AAL1 on a workspace with mfa_required set is redirected to the MFA verification or setup flow before any portal page renders. The same enforcement applies on the workspace dashboard, so the AAL2 promotion is a precondition for both surfaces.

How tenant isolation composes with the rest of the security model

Tenant subdomain isolation is the outer layer. The layers below it carry their own weight, and the composition is deliberate so a failure on one layer does not collapse the contract.

The client portal is the surface the tenant subdomain renders. The portal is scoped to a single workspace by the middleware rewrite, so a client never reads findings, engagements, or documents from a workspace they cannot see. The portal carries no path into the workspace dashboard; the workspace dashboard carries no path into the portal.

The role-based access control gate runs inside every privileged API route the portal calls. The role lookup is scoped to the tenant workspace, so a member in one workspace cannot exercise their permissions on another workspace, and a client in one tenant cannot reach the workspace role hierarchy of any tenant.

Workspace-enforced multi-factor authentication promotes the session to AAL2 before any portal page renders on an MFA-required workspace. The MFA check runs in the same middleware pipeline as the tenant resolver, so the AAL2 promotion is enforced on the tenant subdomain and on the workspace dashboard with the same rule.

The activity log records every privileged action under the resolved tenant workspace. Internal audit, ISO 27001 surveillance, and SOC 2 access review questions about cross-tenant access are answered from the activity feed scoped to the workspace id the middleware resolved at request time.

Sensitive workspace data composes the routing isolation with its own primitives. Encrypted credential storage for authenticated scanning is workspace-scoped and protected by AES-256-GCM authenticated encryption. Verified domain management is workspace-scoped, so a domain proved in one workspace does not authorise scans in another. Every scanner module reads the same verified-domain table for the resolved tenant, so the authorisation gate cannot be bypassed by switching scanner type.

Where to read next

For the workflow that uses these primitives to run a multi-team security testing programme, see the security testing programme management use case. For the evidence-collection side of the same audit answer, see the compliance audits use case.

The audience pages that map most closely to this isolation model are the internal security teams page, the CISO page, and the security service provider page; each one frames the multi-tenant routing model in the language the respective buyer uses.

Give every workspace its own isolated surface

Tenant isolation is enforced in middleware before any page renders. The host header decides the workspace, the workspace existence check decides whether the surface exists at all, the role decides where the request lands, and the activity log records every privileged action under the resolved tenant.

No credit card required. Free plan available forever.