Skip to content

Multi-Team: list endpoints return resources from all teams regardless of user's team #71277

Description

@wilsonjefferson

Under which category would you file this issue?

Providers

Apache Airflow version

3.3.0

What happened and how to reproduce it?

With core.multi_team=True and the Keycloak auth manager, GET /api/v2/dags, GET /api/v2/variables and GET /api/v2/connections return resources belonging to every team, regardless of the requesting user's team membership.

The docs state that Multi-Team is for organisations that "need resource isolation (Variables, Connections, Secrets, etc) between teams at the UI and API level" and that a use case is "You want separate views per team in the Airflow UI"
(Multi-Team → When to Use Multi-Team Mode).

The Work in Progress section lists "Some UI elements may not be fully team-aware" as a known 3.4+ gap. This report concerns the API layer — the leak reproduces against the REST endpoints directly, not only through the UI — so it appears to fall outside that caveat.

Write operations and Task Execution API access are correctly scoped; only list/read appears to be unfiltered.

Setup

Two teams, team-a and team-b, created via airflow teams create and airflow keycloak-auth-manager create-team. Per-team Dag bundles:

AIRFLOW__DAG_PROCESSOR__DAG_BUNDLE_CONFIG_LIST:
[{"name": "team_a_dags", "classpath": "airflow.dag_processing.bundles.local.LocalDagBundle",
"kwargs": {"path": "/opt/airflow/dags/team-a"}, "team_name": "team-a"},
{"name": "team_b_dags", "classpath": "airflow.dag_processing.bundles.local.LocalDagBundle",
"kwargs": {"path": "/opt/airflow/dags/team-b"}, "team_name": "team-b"},
{"name": "shared_dags", "classpath": "airflow.dag_processing.bundles.local.LocalDagBundle",
"kwargs": {"path": "/opt/airflow/dags/shared"}}]

airflow dags list confirms the association:
dag_id | bundle_name check_dag_running | team_a_dags test_owner_dag | team_b_dags test_dag | shared_dags

user-a is a member of Keycloak group /team-a only, with realm role Admin (explicitly not SuperAdmin).

Decoded access token:

"realm_access": { "roles": ["default-roles-airflow", "offline_access", "uma_authorization", "Admin"] }, "groups": ["team-a"]

Steps to reproduce

  1. Configure two teams with per-team Dag bundles as above.
  2. Log in as user-a (member of team-a only).
  3. GET /api/v2/dags

Observed: all three Dags returned, including test_owner_dag from team_b_dags. Same in the UI Dags list.

Expected: only check_dag_running (team-a) and test_dag (global).

GET /api/v2/variables behaves the same way: user-a sees entries with "team_name": "team-b", including their values where the key name does not happen to match the sensitive-keyword masker.

What IS correctly scoped

Write operations — user-a cannot edit or delete team-b Variables or Connections.

Task Execution API, Variables — a task in a team-a Dag:
BLOCKED user-b-var: AirflowRuntimeError: VARIABLE_NOT_FOUND READ OK shared_var = everyone

Task Execution API, Connections — a SQLExecuteQueryOperator using a team-b connection succeeds in a team-b Dag, and the identical task in a team-a Dag fails:

AirflowNotFoundException: The conn_id user-b-conn-postgres isn't defined
File ".../airflow/sdk/execution_time/context.py", line 230 in _get_connection

So enforcement is working at the Execution API and for writes, but not for list/read.

What you think should happen instead?

List endpoints should filter by the requesting user's team, consistent with the documented "resource isolation ... at the UI and API level".

As it stands, a user in team-a can enumerate team-b's Dag IDs and Variable keys via the REST API, and read Variable values whose names don't match the sensitive-keyword list.

Execution-layer isolation is working correctly, so this looks like a bounded gap in the list/read endpoints rather than a design problem. Filing separately from the known "some UI elements may not be fully team-aware" caveat because it reproduces against the API directly.

Operating System

No response

Deployment

None

Apache Airflow Provider(s)

No response

Versions of Apache Airflow Providers

No response

Official Helm Chart version

Not Applicable

Kubernetes Version

No response

Helm Chart configuration

No response

Docker Image customizations

Based on apache/airflow:3.3.0 with apache-airflow-providers-keycloak==0.8.2. No changes to core Airflow.

Anything else?

Keycloak 26 auth manager, CeleryExecutor, Postgres 16 metadata DB, Python 3.13. Reproduces consistently.

Note: the Keycloak client's resource-server decision strategy must be set to Affirmative — under the default Unanimous, role-specific permissions veto access and every endpoint returns 403.

Are you willing to submit PR?

  • Yes I am willing to submit a PR!

Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:APIAirflow's REST/HTTP APIkind:bugThis is a clearly a bugneeds-triagelabel for new issues that we didn't triage yetsecuritySecurity issues that must be fixed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions