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
- Configure two teams with per-team Dag bundles as above.
- Log in as
user-a (member of team-a only).
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?
Code of Conduct
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=Trueand the Keycloak auth manager,GET /api/v2/dags,GET /api/v2/variablesandGET /api/v2/connectionsreturn 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-aandteam-b, created viaairflow teams createandairflow keycloak-auth-manager create-team. Per-team Dag bundles:airflow dags listconfirms the association:dag_id | bundle_name check_dag_running | team_a_dags test_owner_dag | team_b_dags test_dag | shared_dagsuser-ais a member of Keycloak group/team-aonly, with realm roleAdmin(explicitly notSuperAdmin).Decoded access token:
Steps to reproduce
user-a(member ofteam-aonly).GET /api/v2/dagsObserved: all three Dags returned, including
test_owner_dagfromteam_b_dags. Same in the UI Dags list.Expected: only
check_dag_running(team-a) andtest_dag(global).GET /api/v2/variablesbehaves the same way:user-asees 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-acannot edit or deleteteam-bVariables or Connections.Task Execution API, Variables — a task in a
team-aDag:BLOCKED user-b-var: AirflowRuntimeError: VARIABLE_NOT_FOUND READ OK shared_var = everyoneTask Execution API, Connections — a
SQLExecuteQueryOperatorusing ateam-bconnection succeeds in ateam-bDag, and the identical task in ateam-aDag fails: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-acan enumerateteam-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?
Code of Conduct