Skip to main content

Organization Isolation

Isolation by Design

Relay is built as a multi-org platform where every resource belongs to exactly one organization. Isolation is enforced at the database level — it's not an afterthought, it's the foundation.

Database-Level Scoping

Every table that contains organization-specific data has an org_id column:

TableColumnsScope
appsapp_id, org_id, name, ...Apps belong to an organization
agentsagent_id, org_id, name, ...Agents belong to an organization
agent_allowlistid, org_id, agent_id, app_id, ...Allowlists are organization-scoped
eventsevent_id, org_id, app_id, agent_id, ...Event logs are organization-scoped
usersuser_id, email, ...Users are global (but their roles are organization-specific)
org_membersid, org_id, user_id, role, ...Roles are per-user per-organization

Query Scoping Example

Every database query includes WHERE org_id = ?:

-- Get all apps for this organization
SELECT * FROM apps
WHERE org_id = $1
ORDER BY created_at DESC;

-- Check if an app can reach an agent
SELECT CASE
WHEN NOT EXISTS (
SELECT 1 FROM agent_allowlist
WHERE org_id = $1 AND agent_id = $2
) THEN true -- No allowlist = open
WHEN EXISTS (
SELECT 1 FROM agent_allowlist
WHERE org_id = $1 AND agent_id = $2 AND app_id = $3
) THEN true -- App is allowlisted
ELSE false -- App is not allowed
END;

-- Get event logs for this organization
SELECT * FROM events
WHERE org_id = $1
ORDER BY created_at DESC
LIMIT 100;

Key point: It's impossible to query another organization's data without WHERE org_id = ? — the org_id parameter is required.

Application-Level Enforcement

Beyond the database, Relay's API enforces organization isolation:

Token Binding to Organization

When an app or agent authenticates:

App sends: Authorization: Bearer rlk_portal_x8k2m9p...

Relay verifies token

Relay looks up: Which organization owns this token?

Relay: "This app belongs to Organization A"

All subsequent operations are scoped to Organization A

If the same app token were somehow used by Organization B (impossible, but hypothetically):

  • ✅ Relay would reject it (wrong token for that organization)
  • ✅ Or accept it (same token, but Organization A owns it), and all operations would be in Organization A's scope

WebSocket Session Isolation

When an app connects via WebSocket:

Connection Handshake:
Authorization: Bearer rlk_portal_x8k2m9p...

Relay: Verifies token belongs to Organization A

Session created: { app_id: "portal", org_id: "orgA", ws: ... }

All messages on this connection are processed in Organization A's scope

A WebSocket session is permanently bound to its organization. There's no way to send a message from one organization on another organization's session.

Isolation Guarantees

What Each Organization Can See

Organization A
├── Apps: Portal, Flow (only these)
├── Agents: Athena, Klyve (only these)
├── Allowlists: Only for its own agents
├── Event Logs: Only events from/to its own apps and agents
├── Members: Only users in Organization A
└── ❌ Cannot see Organization B's anything

Organization B
├── Apps: Studio, Academy (only these)
├── Agents: ResearchBot (only this)
├── Allowlists: Only for its own agents
├── Event Logs: Only events from/to its own apps and agents
├── Members: Only users in Organization B
└── ❌ Cannot see Organization A's anything

Practical Isolation Examples

Example 1: Event Logs

Portal (in Organization A) sends an event to Athena. Can Organization B see this event?

Event Details:
event_id: evt_k9p2
org_id: orgA ← Scoped to Organization A
app_id: portal
agent_id: athena
payload: { message: "..." }

Organization B tries: SELECT * FROM events WHERE event_id = 'evt_k9p2'

Query is rewritten: SELECT * FROM events
WHERE event_id = 'evt_k9p2' AND org_id = 'orgB'

Result: No rows (evt_k9p2 belongs to orgA)

Organization B sees nothing

Example 2: Agent Discovery

Portal (in Organization A) calls discovery. Which agents does it see?

Portal sends: { type: "discover" }

Relay gets Portal's organization: orgA

Relay queries: SELECT * FROM agents
WHERE org_id = 'orgA'

Relay returns: [athena, klyve] (only Organization A's agents)

Portal never sees Organization B's ResearchBot

Example 3: Allowlist Restrictions

Klyve in Organization A has an allowlist: [Portal, Flow]. Can Organization B's Studio reach Klyve?

Studio (Organization B) tries: Send event to Klyve (Organization A)

Relay checks: Is Klyve in Organization B? No, it's in Organization A

Relay rejects immediately: AGENT_NOT_FOUND

Studio doesn't even get to the allowlist check

Even if the allowlist had allowed Studio, it doesn't matter — Klyve isn't in Studio's organization.

Cross-Org Isolation (Impossible)

These scenarios are impossible by design:

Can't impersonate another organization

Attacker gets Organization A's app token: rlk_portal_x8k2m9p...

Attacker tries to use it to access Organization B data

Token is bound to Organization A

All operations happen in Organization A's scope

Attacker can't reach Organization B

Can't spoof an org_id

Attacker sends: { org_id: "orgB", ... }

Relay ignores it — org_id comes from the authenticated token, not the message

Organization is determined by the token, not claimed by the client

Can't discover other organizations' agents

App (Organization A) calls discovery: SELECT * FROM agents

Query becomes: SELECT * FROM agents WHERE org_id = 'orgA'

Only Organization A's agents returned

Organization B's agents remain hidden

Can't access other organizations' tokens

All tokens in the database are hashed

Plaintext tokens are shown only at creation, then discarded

Even if a token hash is exposed, it can't be reversed to plaintext

Attacker still can't use it without the plaintext

User Role Isolation

Users can have access to multiple organizations, but their role is per-organization:

User: Alice
├── Organization A: owner (full access)
├── Organization B: member (read-only)
└── Organization C: owner (full access)

When Alice switches to Organization A:
✅ Can register apps/agents
✅ Can manage allowlists
✅ Can view all logs

When Alice switches to Organization B:
✅ Can view dashboard (read-only)
❌ Cannot register apps/agents
❌ Cannot manage anything

When Alice switches to Organization C:
✅ Can do everything (owner in this organization)

Alice's permissions are completely separate in each organization. Being an owner in Organization A doesn't grant any privileges in Organization B.

Testing Isolation

Relay's test suite verifies isolation with tests like:

def test_org_isolation():
# Create two organizations
org_a = create_organization("Acme Corp")
org_b = create_organization("StartupXYZ")

# Create an app in org_a
app_a = create_app(org_a, "Portal")

# Try to access org_a's app as org_b
result = get_app(org_b, app_a.id)

# Should return "not found" (from org_b's perspective)
assert result is None

def test_event_log_isolation():
# Create event in org_a
event = create_event(org_a, app_a, agent_a, payload={...})

# Try to query event as org_b
result = query_events(org_b)

# Should not include event from org_a
assert event not in result

def test_allowlist_isolation():
# Klyve in org_a has allowlist: [portal]
allow_access(org_a, klyve, portal)

# Studio in org_b tries to reach klyve in org_a
can_reach = check_permission(studio, klyve)

# Should be False (klyve is in different organization)
assert can_reach is False

Audit Trail

Every operation that touches resources is logged:

  • Who (user email) performed the action
  • What (registered app, rotated token, invited member)
  • When (timestamp)
  • On which organization (org_id)
  • Result (success or failure)

This audit trail is also scoped by organization — users only see logs for their own organization.

Incident Response

If a breach occurs:

Scenario: Organization A's app token is exposed

  1. Only Organization A is affected — token is bound to Organization A
  2. Rotate the token — new token issued, old is invalidated
  3. Organization B is unaffected — token can't reach Organization B anyway
  4. Review Organization A's logs only — other organizations' logs are unaffected

Scenario: Database is breached

  1. Tokens are hashed — plaintext can't be extracted
  2. Plaintext is lost — attacker still can't authenticate
  3. Allowlists are visible — but don't leak sensitive payloads
  4. Event payloads are exposed — apps should use general context, not secrets

To reduce impact from #4: never send secrets (passwords, API keys, etc.) in event payloads.

Best Practices

1. Don't Share Tokens Across Organizations

Each organization should have its own tokens:

Organization A: rlk_portal_orgA_...
Organization B: rlk_portal_orgB_...

❌ Don't try to use Organization A's token in Organization B

2. Assume Tokens May Be Exposed

Rotate tokens regularly:

  • Even if no breach is suspected
  • Every 90 days is reasonable
  • Immediately if compromised

3. Use Secrets Manager

Don't share tokens as plaintext:

  • Use AWS Secrets Manager, Vault, etc.
  • Audit who accesses tokens
  • Log all access

4. Review Audit Logs Regularly

Check who has done what in your organization:

  • Unexpected user invites?
  • Tokens rotated by someone you don't know?
  • Apps registered without your knowledge?

5. Offboard Users Carefully

When someone leaves:

  1. Remove them from your organization
  2. Rotate any tokens they had access to
  3. Check audit logs for what they did
  4. Review their access was appropriate

Summary

  • All data is scoped by organization at the database level
  • Tokens are bound to organizations — impossible to escape
  • Queries always include WHERE org_id = ?
  • WebSocket sessions are organization-locked
  • Cross-org access is impossible by design
  • Users can have different roles in different organizations
  • Audit logs are organization-scoped
  • Rotate tokens regularly to minimize breach impact

Relay's multi-org architecture ensures that organizations are completely isolated from each other. Your data is yours alone.

Next steps: