Skip to content

Security/tenant data isolation hardening - #102

Merged
jobbykings merged 2 commits into
Epondia:mainfrom
MerlinTheWhiz:security/tenant-data-isolation-hardening
Jun 22, 2026
Merged

Security/tenant data isolation hardening#102
jobbykings merged 2 commits into
Epondia:mainfrom
MerlinTheWhiz:security/tenant-data-isolation-hardening

Conversation

@MerlinTheWhiz

@MerlinTheWhiz MerlinTheWhiz commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Description

Hardens tenant data isolation across the middleware, service, and query layers to prevent cross-tenant data leakage in multi-institution deployments. The changes add runtime guards, audit logging, an admin override mechanism with explicit scope change, and a comprehensive test suite.

Key changes:

  • backend/src/middleware/tenant.js — Fixed ensureTenantUser to correctly resolve user IDs from JWT payloads; added verifyTenantAccess middleware (returns 403 on URL param mismatch); added adminScopeOverride for super_admin scope switching with full audit trail; added withTenantScope helper to wrap query filters with tenantId
  • backend/src/services/tenantService.js — Added validateTenantAccess guard and wired it into 5 methods (getTenantUsers, updateTenantSettings, updateTenantBranding, updateTenantStatus, deleteTenant)
  • backend/src/services/tenantAnalyticsService.js — Added optional tenantId filter to getGrowthMetrics and passed it from getTenantAnalytics for proper scoping
  • backend/tests/services/tenant.isolation.test.js — New file with 26 tests across 6 suites covering context establishment, query scoping, cross-tenant denial, admin override, audit logging, and end-to-end isolation

Related Issue

Closes #67

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 💥 Breaking change (fix or feature that changes existing behavior)
  • 📚 Documentation update
  • ♻️ Refactor (no functional change)
  • 🧪 Tests
  • 🔧 Chore / tooling

Packages Affected

  • contracts/ (Soroban / Rust)
  • backend/ (Node / Express)
  • frontend/ (Next.js)
  • docs/

How Has This Been Tested?

Ran the new integration test suite (tests/services/tenant.isolation.test.js) — all 26 tests pass:

PASS tests/services/tenant.isolation.test.js
  Tenant Data Isolation
    Tenant context on authenticated requests
      ✓ should set req.tenant and req.tenantId via tenantMiddleware (8 ms)
      ✓ should resolve tenant from X-Tenant-ID header
      ✓ should resolve tenant from query parameter
      ✓ should reject request without tenant info
      ✓ should reject request for inactive tenant
    Database queries scoped by tenant_id
      ✓ should scope getTenantUsers to the given tenantId
      ✓ should not return Tenant B users when querying Tenant A
      ✓ should scope getTenantUsage to the correct tenant
      ✓ should scope getTenantAnalytics to the correct tenant
      ✓ withTenantScope helper should add tenantId filter to queries
      ✓ withTenantScope should return original filter when no tenantId
    Cross-tenant access returns 403
      ✓ verifyTenantAccess should block mismatched tenantId in route param
      ✓ verifyTenantAccess should allow matching tenantId
      ✓ ensureTenantUser should reject user from different tenant
      ✓ cross-tenant data query via service should not leak data
    Admin override with explicit scope change
      ✓ should allow super_admin to override tenant scope via header
      ✓ should ignore override for non-super_admin roles
      ✓ should return 404 if override target tenant does not exist
    Audit log entries for cross-tenant access
      ✓ should log cross-tenant access denied events
      ✓ should log admin scope override events
      ✓ should expose audit log externally
    Tenant A cannot access Tenant B data
      ✓ should not return Tenant B users from Tenant A user query
      ✓ should enforce isolation for updateTenantSettings
      ✓ should enforce isolation for updateTenantBranding
      ✓ should not allow Tenant A analytics to return Tenant B data
      ✓ should prevent deleting other tenant data

Test Suites: 1 passed, 1 total
Tests:       26 passed, 26 total

Checklist

  • My code follows the project's coding standards (see CONTRIBUTING.md)
  • I have run the relevant linters and type checks
  • I have added or updated tests that prove my change works
  • All new and existing tests pass locally
  • I have updated documentation where needed
  • My commits follow the Conventional Commits format
  • I have noted any breaking changes below (or there are none)

Breaking Changes

None

Additional Notes / Screenshots

Audit log output examples

Cross-tenant denied:

[TENANT-AUDIT] {"type":"CROSS_TENANT_DENIED","severity":"WARN","routeParamTenantId":"...","resolvedTenantId":"...","userId":"...","path":"/api/v1/tenants/.../users","method":"GET","timestamp":"..."}

Admin scope override:

[TENANT-AUDIT] {"type":"ADMIN_SCOPE_OVERRIDE","severity":"INFO","adminUserId":"...","originalTenantId":"...","targetTenantId":"...","targetTenantName":"Tenant B","path":"/api/v1/tenants/.../users","method":"GET","timestamp":"..."}
image

@jobbykings
jobbykings merged commit 73b3dc5 into Epondia:main Jun 22, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Harden tenant data isolation for multi-tenant features

2 participants