Implement automatic team scoping to prevent IDOR vulnerabilities
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 39.9k
- Forks
- 3.4k
- Avg merge
- 6h 51m
- Merged PRs (30d)
- 232
Description
Implement automatic team scoping to prevent IDOR vulnerabilities
Problem
PostHog is a multi-tenant application where IDOR (Insecure Direct Object Reference) vulnerabilities can occur when queries don't include proper team scoping. Currently, we rely on:
- Developer discipline to always include
team_idfilters - Semgrep rules to catch missing filters (with false positives requiring
# nosemgrepcomments)
This is error-prone and doesn't prevent the vulnerability class fundamentally.
Related discussion: #46779
Proposed Solution
Implement automatic team scoping using Python's ContextVar. The approach:
- Middleware sets the current
team_idin a context variable from the authenticated user - TeamScopedManager automatically filters queries by the current team
.unscoped()provides an explicit escape hatch for intentional cross-team queriesteam_scope()context manager allows background jobs to set team context
Example Usage
# In request context (automatic via middleware):
FeatureFlag.objects.all() # Auto-filtered to current team
# Explicit cross-team query (escape hatch):
FeatureFlag.objects.unscoped().all() # All teams
# In background jobs:
with team_scope(team_id):
FeatureFlag.objects.all() # Filtered to specified team
Proof of concept: #46874
Implementation Stages
Stage 1: Foundation
Ship core infrastructure without changing any model behavior.
- Create
posthog/models/scoping/module:- ContextVar for current team (
_current_team_id) - Helper functions:
get_current_team_id(),set_current_team_id() - Context managers:
team_scope(team_id),unscoped()
- ContextVar for current team (
- Create
TeamScopedManagerthat auto-filters by current team - Create
BackwardsCompatibleTeamScopedManagerthat also supports explicitfilter(team_id=X) - Create middleware to set team context from
request.user.current_team_id - Enable middleware in
settings.py(afterAuthenticationMiddleware) - Add Celery helper decorator (
@with_team_scope) - Add semgrep rule requiring
team_scope()orunscoped()in Celery tasks - Documentation: migration guide and pattern examples
Stage 2: Pilot - FeatureFlag Model
Migrate FeatureFlag to automatic scoping and validate the approach.
- Switch
FeatureFlagtoBackwardsCompatibleTeamScopedManager - Update Celery tasks:
check_flags_to_rollback()→ use.unscoped()(intentional cross-team)check_feature_flag_rollback_conditions()→ addteam_idparam with@with_team_scopesync_feature_flag_last_called()→ verify behavior (already filters by team_ids)
- Add
.unscoped()to intentional cross-team queries:FeatureFlagEvaluationTag.get_team_ids_using_tag()- Weekly digest query in
temporal/weekly_digest/queries.py
- Update tests to use
team_scope()context where needed - Manual verification: IDOR protection works in UI
Stage 3: Pilot - Related Models
Migrate remaining feature flag-related models.
- Migrate
TeamDefaultEvaluationTag(already has team FK) - Evaluate
FeatureFlagEvaluationTag(decide: add team FK or rely on join?) - Skip deprecated/special models:
FeatureFlagHashKeyOverride(persons DB, cross-database)FeatureFlagOverride(deprecated)FeatureFlagRoleAccess(deprecated)
Stage 4: Broader Rollout
Migrate remaining team-scoped models across the codebase.
- Identify all models using
RootTeamMixin - Group by feature area/team ownership
- Migrate in batches:
- TBD: List models as we identify them
Stage 5: Remove Backwards Compatibility
- Audit remaining
filter(team_id=X)usage - Switch all models from
BackwardsCompatibleTeamScopedManagertoTeamScopedManager - Update semgrep rules to enforce new patterns
- Remove backwards compatibility code
Acceptance Criteria
- All team-scoped models automatically filter by current team in request context
- Cross-team queries require explicit
.unscoped()call - Background jobs use
team_scope()or explicit.unscoped() - CI enforces patterns via semgrep
- Existing tests pass
- No IDOR vulnerabilities possible without explicit
.unscoped()
Labels
security, enhancement, team/feature-flags
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the proposed posthog/models/scoping/ module, then inspect settings.py and the FeatureFlag model and its listed Celery tasks. Review the middleware, TeamScopedManager, team_scope(), unscoped(), and semgrep requirements before attempting a stage. Done means scoped queries, explicit cross-team escapes, background-job coverage, documentation, and passing existing tests across the staged rollout.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend, database, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100