feast-dev / feast-dev/feast

Multi-Tenancy Data Isolation Vulnerability

Open
#5,940 0 comments 3 reactions 0 assignees View on GitHub
kind/bug priority/p2
Dominant language
Python
Stars
7.3k
Forks
1.4k
Avg merge
1d 21h
Merged PRs (30d)
15

Description

## 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:**
```python
# 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:

```python
# 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])
```

```python
# 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

3. User from Tenant A creates store with Tenant B's project name:

```python
# 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!
```

4. 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

```yaml
# 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

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.