This is an automated email from the ASF dual-hosted git repository.
Lee-W pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 58cd0e05d2e Use a distinct redact message for import errors with no
registered Dag (#66923)
58cd0e05d2e is described below
commit 58cd0e05d2ec5776ba89b549d76e60a2d174ca79
Author: Wei Lee <[email protected]>
AuthorDate: Tue May 19 20:14:19 2026 +0800
Use a distinct redact message for import errors with no registered Dag
(#66923)
---
.../core_api/routes/public/import_error.py | 18 +++++++--
.../core_api/routes/public/test_import_error.py | 43 +++++++++++++---------
2 files changed, 39 insertions(+), 22 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 02055f47d3b..a46fe0d1c29 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,6 +55,11 @@ 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")
@@ -100,9 +105,11 @@ def get_import_error(
# 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.
+ # 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".
if not file_dag_ids:
- error.stacktrace = REDACTED_STACKTRACE
+ error.stacktrace = REDACTED_STACKTRACE_NO_DAG
return error
# Can the user read any Dags in the file?
@@ -230,10 +237,13 @@ def get_import_errors(
# 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 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".
if not dag_ids:
session.expunge(import_error)
- import_error.stacktrace = REDACTED_STACKTRACE
+ 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 d1321e71264..0e16148b16b 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,7 +23,10 @@ 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
+from airflow.api_fastapi.core_api.routes.public.import_error import (
+ REDACTED_STACKTRACE,
+ REDACTED_STACKTRACE_NO_DAG,
+)
from airflow.models import DagModel
from airflow.models.dagbundle import DagBundleModel
from airflow.models.errors import ParseImportError
@@ -277,12 +280,14 @@ 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 a redacted stacktrace when no DAG
+ """Import error is returned with the no-Dag redaction message when no
Dag
exists in ``DagModel`` for the file.
- When the file-to-DAG set resolves empty the endpoint cannot tell
- which DAGs the caller is allowed to see, so the stacktrace is
- redacted rather than returned verbatim.
+ 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.
"""
import_error_id = import_errors[0].id
set_mock_auth_manager__get_authorized_dag_ids(mock_get_auth_manager,
set())
@@ -294,7 +299,7 @@ class TestGetImportError:
"import_error_id": import_error_id,
"timestamp": from_datetime_to_zulu_without_ms(TIMESTAMP1),
"filename": FILENAME1,
- "stack_trace": REDACTED_STACKTRACE,
+ "stack_trace": REDACTED_STACKTRACE_NO_DAG,
"bundle_name": BUNDLE_NAME,
}
@@ -520,7 +525,8 @@ 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):
- """Test import errors are returned when no DAG exists in DagModel."""
+ """Import errors are returned with the no-Dag redaction message when
no Dag
+ exists in ``DagModel`` for the file."""
set_mock_auth_manager__get_authorized_dag_ids(mock_get_auth_manager,
set())
response = test_client.get("/importErrors")
@@ -532,6 +538,8 @@ class TestGetImportErrors:
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
class TestImportErrorFileAuthorization:
@@ -664,18 +672,17 @@ class TestImportErrorFileAuthorization:
test_client,
import_errors,
):
- """Single endpoint must redact the 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. The response must be 200 with
- ``REDACTED_STACKTRACE``, not a 200 with the raw error body.
+ """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.
"""
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
+ assert body["stack_trace"] == REDACTED_STACKTRACE_NO_DAG
@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(
@@ -720,10 +727,10 @@ class TestImportErrorFileAuthorization:
test_client,
import_errors,
):
- """List endpoint must redact the stacktrace 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 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.
"""
set_mock_auth_manager__get_authorized_dag_ids(mock_get_auth_manager,
set())
response = test_client.get("/importErrors")
@@ -731,4 +738,4 @@ class TestImportErrorFileAuthorization:
body = response.json()
assert body["total_entries"] == 3
for entry in body["import_errors"]:
- assert entry["stack_trace"] == REDACTED_STACKTRACE
+ assert entry["stack_trace"] == REDACTED_STACKTRACE_NO_DAG