Repository navigation
Fix 400 and 500s on POST /connections/defaults in multi-team mode - #74615
Merged
vincbeck merged 1 commit intoOct 13, 2026
Merged
Conversation
Pushkal-Gupta
requested review from
bugraoz93,
choo121600,
ephraimbuddy,
henry3260,
jason810496,
pierrejeambrun,
rawwar and
shubhamraj-git
as code owners
October 12, 2026 20:35
fat-catTW
approved these changes
Oct 13, 2026
fat-catTW
left a comment
Contributor
There was a problem hiding this comment.
Thanks for the PR
LGTM!
vincbeck
approved these changes
Oct 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
With
core.multi_teamturned on,POST /connections/defaultsalways fails with a 400 - even for a perfectly valid call. Three kinds of malformed body also come back as a 500 instead of a 400. Both have the same cause.What's going on
In multi-team mode, authorization needs to know which team a request is about, so
_collect_teams_to_checkreadsteam_nameout of the request body on POST and PUT. It only catchesJSONDecodeErroraround that read.That looked safe, and #66504 said why when it added the guard: every POST/PUT route in core_api declares a Pydantic body model, so FastAPI parses the body and returns a 422 long before the auth dependency ever runs.
One route doesn't declare a body model, though:
FastAPI only parses a body when the route asks for one. So for
/connections/defaults, nothing has touched the body by the time the dependency runs — that read is the first and only parse. The route has been around since #45363 (January 2025), sixteen months before the guard landed.Two things follow, both only when multi-team is on.
The endpoint stops working. It takes no body by design, but the guard insists on a parseable one, so a correct call gets
400 Request body is not valid JSONinstead of204. Switch multi-team off and the same call returns 204, which is why this slipped by.Three malformed bodies become 500s.
json.loadsdoesn't only raiseJSONDecodeError- it also raisesUnicodeDecodeError, a plainValueErrorwhen a number exceedsint_max_str_digits, andRecursionErroron deeply nested input. None of those were caught.POST /connections/defaultsint_max_str_digitsHow much is affected
Only this one route. Every other POST/PUT route behind
requires_access_pool/requires_access_connection/requires_access_variabledeclares a body model, so FastAPI gets there first and answers 422 or 400 before the dependency runs. I fired all five payloads at both kinds of route to check.The fix
Two small changes: skip the read when the body is empty, since a request with no body isn't naming a team anyway, and catch any parse failure rather than just
JSONDecodeError.Neither idea is new here.
requires_access_backfill, a few hundred lines up in the same file, already does exactly this and even names the three extra exception types in its comment.parse_login_bodyin the simple auth manager does the same._collect_teams_to_checkjust never caught up.Fail-closed behaviour is unchanged. A non-empty body that can't be read is still rejected with a 400 before any authorization decision. An empty body reaches the authorization check with
team_name=None- the same thing a GET presents - and routes that genuinely need a body still get FastAPI's 422, exactly as before. Nothing becomes authorized that wasn't already.Worth saying plainly: both broken cases denied the request, so there was no authorization bypass here. It's a wrong status code and a leaked traceback, not a vulnerability, so it's raised as an ordinary bug.
Tests
Three new ones:
team_name=NonePOST /connections/defaultsreturns 204 in multi-team mode - the actual reported symptomAll eight cases fail without the change to
security.py; I checked by stashing just that file and re-running. The fourteen existing_collect_teams_to_checktests mockedrequest.jsonbut notrequest.body, so they now mock both andstill assert what they always did.
mypy and ruff are clean, and the full
airflow-core/tests/unit/api_fastapi/suite passes at 4395.No newsfragment — this is a bug fix, not a breaking or user-facing change.
One thing for a committer to weigh in on: the alternative would be to stop routing
POST /connections/defaultsthrough team scoping altogether, since creating default connections has no team dimension. I fixed the helper instead so it holds up for any future bodyless route, but I'm happy to go the other way if you'd prefer.This affects
v3-2-testtoo, where #66504 was backported as #67182.Was generative AI tooling used to co-author this PR?