This is an automated email from the ASF dual-hosted git repository.
vatsrahul1001 pushed a commit to branch v3-2-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/v3-2-test by this push:
new 05d328404bc [v3-2-test] Return raw import-error stacktrace when file
has no registered Dag (#67465) (#67478)
05d328404bc is described below
commit 05d328404bc1ce6e8f7d4ae20cc83b0f56978a70
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Mon May 25 21:52:08 2026 +0530
[v3-2-test] Return raw import-error stacktrace when file has no registered
Dag (#67465) (#67478)
The Import Errors API used to fall back to a redaction message for files
that have no `DagModel` row yet (parse failed before any Dag was defined,
or all Dags removed). The fallback was a placeholder, not a real
authorization decision -- it left admins unable to read the actual
stacktrace, and it did not respect multi-team isolation.
Restore the previous behavior of returning the raw stacktrace in this
case until a proper admin-only path is in place. The dedicated
permission and multi-team scoping are tracked in
https://github.com/apache/airflow/issues/67461.
The other changes from the per-file authorization work -- matching on
`relative_fileloc + bundle_name` and splitting the list-endpoint CTE so
the per-file authorization check sees the full Dag set -- stay in
place.
(cherry picked from commit 93a078a20de7cda3a4aa64782da03e5a820e7d35)
Co-authored-by: Jarek Potiuk <[email protected]>
Co-authored-by: Rahul Vats <[email protected]>
---
.../core_api/routes/public/import_error.py | 26 +++-----
.../core_api/routes/public/test_import_error.py | 72 ++++++++++++----------
2 files changed, 46 insertions(+), 52 deletions(-)
diff --git
a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/import_error.py
b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/import_error.py
index a46fe0d1c29..8f8fcca42b9 100644
---
a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/import_error.py
+++
b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/import_error.py
@@ -55,11 +55,6 @@ from airflow.models import DagModel
from airflow.models.errors import ParseImportError
REDACTED_STACKTRACE = "REDACTED - you do not have read permission on all Dags
in the file"
-REDACTED_STACKTRACE_NO_DAG = (
- "REDACTED - no Dag has been registered for this file yet "
- "(parse may have failed before any Dag was defined; "
- "if you have Dag read access, check the dag-processor logs for the raw
error)"
-)
import_error_router = AirflowRouter(tags=["Import Error"],
prefix="/importErrors")
@@ -103,13 +98,10 @@ def get_import_error(
# No Dags matched for this file -- either the file genuinely contains
# no Dags (parse failed before any Dag was defined), or the name keys
- # did not resolve. Redact the stacktrace rather than returning the raw
- # error, so the response stays on the deny-by-default side of the
- # authorization check. The message distinguishes this case from the
- # per-Dag scope mismatch below so callers (especially admins) don't
- # mistake "file has no Dag yet" for "you lack permission".
+ # did not resolve. Return the raw error in this case; a proper
+ # admin-only path for unregistered files is tracked in a follow-up
+ # issue (see https://github.com/apache/airflow/issues/67461).
if not file_dag_ids:
- error.stacktrace = REDACTED_STACKTRACE_NO_DAG
return error
# Can the user read any Dags in the file?
@@ -235,15 +227,11 @@ def get_import_errors(
# No Dags matched for this file -- either the file genuinely has
# no Dags yet (parse failed before any Dag was defined), or the
- # name keys did not resolve. Redact the stacktrace before
- # appending so the response stays on the deny-by-default side of
- # the authorization check. The message distinguishes this case
- # from the per-Dag scope mismatch below so callers (especially
- # admins) don't mistake "file has no Dag yet" for "you lack
- # permission".
+ # name keys did not resolve. Append the raw error in this case;
+ # a proper admin-only path for unregistered files is tracked in
+ # a follow-up issue
+ # (see https://github.com/apache/airflow/issues/67461).
if not dag_ids:
- session.expunge(import_error)
- import_error.stacktrace = REDACTED_STACKTRACE_NO_DAG
import_errors.append(import_error)
continue
diff --git
a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_import_error.py
b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_import_error.py
index 0e16148b16b..79121e1b86a 100644
---
a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_import_error.py
+++
b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_import_error.py
@@ -23,10 +23,7 @@ from unittest import mock
import pytest
from airflow.api_fastapi.auth.managers.models.resource_details import
DagDetails
-from airflow.api_fastapi.core_api.routes.public.import_error import (
- REDACTED_STACKTRACE,
- REDACTED_STACKTRACE_NO_DAG,
-)
+from airflow.api_fastapi.core_api.routes.public.import_error import
REDACTED_STACKTRACE
from airflow.models import DagModel
from airflow.models.dagbundle import DagBundleModel
from airflow.models.errors import ParseImportError
@@ -280,14 +277,12 @@ class TestGetImportError:
@mock.patch("airflow.api_fastapi.core_api.routes.public.import_error.get_auth_manager")
def test_get_import_error__no_dag_in_dagmodel(self, mock_get_auth_manager,
test_client, import_errors):
- """Import error is returned with the no-Dag redaction message when no
Dag
- exists in ``DagModel`` for the file.
+ """Import error is returned with the raw stacktrace when no Dag exists
+ in ``DagModel`` for the file.
- When the file-to-Dag set resolves empty there is no Dag anchor to
- authorize against, so the stacktrace is redacted. The message must be
- the no-Dag variant, not the per-Dag-scope variant, so callers with
- full Dag read access (admins) understand the redaction is about the
- file's parse state and not their permissions.
+ Proper handling of unregistered files (separate admin-only permission,
+ multi-team isolation) is tracked as follow-up work; for now the
endpoint
+ returns the raw error rather than redacting it.
"""
import_error_id = import_errors[0].id
set_mock_auth_manager__get_authorized_dag_ids(mock_get_auth_manager,
set())
@@ -299,7 +294,7 @@ class TestGetImportError:
"import_error_id": import_error_id,
"timestamp": from_datetime_to_zulu_without_ms(TIMESTAMP1),
"filename": FILENAME1,
- "stack_trace": REDACTED_STACKTRACE_NO_DAG,
+ "stack_trace": STACKTRACE1,
"bundle_name": BUNDLE_NAME,
}
@@ -525,8 +520,12 @@ class TestGetImportErrors:
@mock.patch("airflow.api_fastapi.core_api.routes.public.import_error.get_auth_manager")
def test_get_import_errors__no_dag_in_dagmodel(self,
mock_get_auth_manager, test_client, import_errors):
- """Import errors are returned with the no-Dag redaction message when
no Dag
- exists in ``DagModel`` for the file."""
+ """Import errors are returned with their raw stacktraces when no Dag
+ exists in ``DagModel`` for the file.
+
+ Proper handling of unregistered files is deferred to a follow-up issue
+ that introduces a dedicated permission and respects multi-team
isolation.
+ """
set_mock_auth_manager__get_authorized_dag_ids(mock_get_auth_manager,
set())
response = test_client.get("/importErrors")
@@ -534,12 +533,14 @@ class TestGetImportErrors:
assert response.status_code == 200
response_json = response.json()
assert response_json["total_entries"] == 3
- filenames = [error["filename"] for error in
response_json["import_errors"]]
- assert FILENAME1 in filenames
- assert FILENAME2 in filenames
- assert FILENAME3 in filenames
- for entry in response_json["import_errors"]:
- assert entry["stack_trace"] == REDACTED_STACKTRACE_NO_DAG
+ stacktrace_by_filename = {
+ error["filename"]: error["stack_trace"] for error in
response_json["import_errors"]
+ }
+ assert stacktrace_by_filename == {
+ FILENAME1: STACKTRACE1,
+ FILENAME2: STACKTRACE2,
+ FILENAME3: STACKTRACE3,
+ }
class TestImportErrorFileAuthorization:
@@ -666,23 +667,24 @@ class TestImportErrorFileAuthorization:
assert response.status_code == 403
@mock.patch("airflow.api_fastapi.core_api.routes.public.import_error.get_auth_manager")
- def test_single_endpoint_redacts_when_file_has_no_known_dags(
+ def
test_single_endpoint_returns_raw_stacktrace_when_file_has_no_known_dags(
self,
mock_get_auth_manager,
test_client,
import_errors,
):
- """Single endpoint must redact the stacktrace with the no-Dag
- message when the ``ParseImportError`` refers to a file with no
- matching ``DagModel`` rows at all -- for example a file that
- failed to parse before any Dag was defined.
+ """Single endpoint returns the raw stacktrace when the
+ ``ParseImportError`` refers to a file with no matching ``DagModel``
+ rows at all -- for example a file that failed to parse before any
+ Dag was defined. Proper handling of this case (dedicated permission,
+ multi-team isolation) is tracked as follow-up work.
"""
set_mock_auth_manager__get_authorized_dag_ids(mock_get_auth_manager,
set())
response = test_client.get(f"/importErrors/{import_errors[0].id}")
assert response.status_code == 200
body = response.json()
assert body["filename"] == FILENAME1
- assert body["stack_trace"] == REDACTED_STACKTRACE_NO_DAG
+ assert body["stack_trace"] == STACKTRACE1
@mock.patch("airflow.api_fastapi.core_api.routes.public.import_error.get_auth_manager")
def
test_list_endpoint_redacts_mixed_file_with_colocated_dag_outside_callers_scope(
@@ -721,21 +723,25 @@ class TestImportErrorFileAuthorization:
assert mixed_entries[0]["stack_trace"] == REDACTED_STACKTRACE
@mock.patch("airflow.api_fastapi.core_api.routes.public.import_error.get_auth_manager")
- def test_list_endpoint_redacts_when_file_has_no_known_dags(
+ def test_list_endpoint_returns_raw_stacktrace_when_file_has_no_known_dags(
self,
mock_get_auth_manager,
test_client,
import_errors,
):
- """List endpoint must redact the stacktrace with the no-Dag
- message for import errors whose file has no matching ``DagModel``
- rows -- closing the ``if not dag_ids:
import_errors.append(import_error)``
- fall-through that previously returned the raw error.
+ """List endpoint returns the raw stacktrace for import errors whose
+ file has no matching ``DagModel`` rows. Proper handling of this case
+ (dedicated permission, multi-team isolation) is tracked as follow-up
+ work.
"""
set_mock_auth_manager__get_authorized_dag_ids(mock_get_auth_manager,
set())
response = test_client.get("/importErrors")
assert response.status_code == 200
body = response.json()
assert body["total_entries"] == 3
- for entry in body["import_errors"]:
- assert entry["stack_trace"] == REDACTED_STACKTRACE_NO_DAG
+ stacktrace_by_filename = {entry["filename"]: entry["stack_trace"] for
entry in body["import_errors"]}
+ assert stacktrace_by_filename == {
+ FILENAME1: STACKTRACE1,
+ FILENAME2: STACKTRACE2,
+ FILENAME3: STACKTRACE3,
+ }