Skip to content

Fix FAB session cookie TypeError with Werkzeug 3.0+#67141

Closed
Sriniketh24 wants to merge 3 commits into
apache:mainfrom
Sriniketh24:fix/fab-session-cookie-bytes-64921
Closed

Fix FAB session cookie TypeError with Werkzeug 3.0+#67141
Sriniketh24 wants to merge 3 commits into
apache:mainfrom
Sriniketh24:fix/fab-session-cookie-bytes-64921

Conversation

@Sriniketh24

@Sriniketh24 Sriniketh24 commented May 18, 2026

Copy link
Copy Markdown

Root Cause

Werkzeug 3.0 removed automatic bytes-to-str coercion in dump_cookie(). When session_backend=securecookie, the itsdangerous signing serializer may return bytes, which Werkzeug 3.0+ rejects with TypeError: cannot use a string pattern on a bytes-like object.

Fix

Override save_session() in AirflowSecureCookieSessionInterface to wrap response.set_cookie() with bytes-to-str coercion. If the signed session value is bytes, it is decoded to str before being passed to Werkzeug.

The database session serializer (_LazySafeSerializer) is not modified — msgpack encoding produces proper binary bytes for LargeBinary columns and works correctly.

Tests

Added providers/fab/tests/unit/fab/www/test_session.py covering:

  • _LazySafeSerializer roundtrip, LazyString handling, bytes output, encode/decode aliases
  • AirflowSecureCookieSessionInterface.save_session bytes-to-str coercion
  • String values pass through unchanged

closes: #64921


Was generative AI tooling used to co-author this PR?
  • Yes — Claude Code (Opus 4.6)

Generated-by: Claude Code (Opus 4.6) following the guidelines

@boring-cyborg

boring-cyborg Bot commented May 18, 2026

Copy link
Copy Markdown

Congratulations on your first Pull Request and welcome to the Apache Airflow community! If you have any issues or are unsure about any anything please check our Contributors' Guide
Here are some useful points:

  • Pay attention to the quality of your code (ruff, mypy and type annotations). Our prek-hooks will help you with that.
  • In case of a new feature add useful documentation (in docstrings or in docs/ directory). Adding a new operator? Check this short guide Consider adding an example Dag that shows how users should use it.
  • Consider using Breeze environment for testing locally, it's a heavy docker but it ships with a working Airflow and a lot of integrations.
  • Be patient and persistent. It might take some time to get a review or get the final approval from Committers.
  • Please follow ASF Code of Conduct for all communication including (but not limited to) comments on Pull Requests, Mailing list and Slack.
  • Be sure to read the Airflow Coding style.
  • Always keep your Pull Requests rebased, otherwise your build might fail due to changes not related to your commits.
    Apache Airflow is a community-driven project and together we are making it better 🚀.
    In case of doubts contact the developers at:
    Mailing List: dev@airflow.apache.org
    Slack: https://s.apache.org/airflow-slack

@potiuk

potiuk commented May 19, 2026

Copy link
Copy Markdown
Member

@Sriniketh24 Converting to draft — this PR doesn't yet meet our Pull Request quality criteria.

  • Provider tests — Failing: Low dep tests: providers / All-prov:LowestDeps:14:3.10:common.compat...fab, provider distributions tests / Compat 3.0.6:P3.10:, provider distributions tests / Compat 3.1.8:P3.10:, provider distributions tests / Compat 3.2.1:P3.10:. See docs.

See the linked criteria for how to fix each item, then mark the PR "Ready for review". This is not a rejection — just an invitation to bring the PR up to standard. No rush.


Note: This comment was drafted by an AI-assisted triage tool and may contain mistakes. Once you have addressed the points above, an Apache Airflow maintainer — a real person — will take the next look at your PR. We use this two-stage triage process so that our maintainers' limited time is spent where it matters most: the conversation with you.


Drafted-by: Claude Code (Opus 4.7); reviewed by @potiuk before posting

@potiuk
potiuk marked this pull request as draft May 19, 2026 01:26
@Sriniketh24
Sriniketh24 marked this pull request as ready for review May 22, 2026 05:50
@Sriniketh24
Sriniketh24 force-pushed the fix/fab-session-cookie-bytes-64921 branch from a3d8050 to 7386c21 Compare May 23, 2026 20:07
Root cause: Werkzeug 3.0 removed automatic bytes-to-str coercion in
set_cookie(). When session_backend=securecookie, the itsdangerous
signing serializer may return bytes, causing a TypeError in
dump_cookie().

Fix: override save_session() in AirflowSecureCookieSessionInterface
to wrap response.set_cookie() with bytes-to-str coercion. The
database session serializer (_LazySafeSerializer) is left unchanged
since msgpack encoding is correct for LargeBinary columns.

closes: apache#64921

Authored with AI assistance (Claude Code).
@Sriniketh24
Sriniketh24 force-pushed the fix/fab-session-cookie-bytes-64921 branch from 7386c21 to 02104f9 Compare May 23, 2026 20:26
@Sriniketh24

Copy link
Copy Markdown
Author

Reworked the approach based on the CI failures:

Previous approach (broken): Changed _LazySafeSerializer from msgpack to JSON encoding. This caused TypeError: memoryview: a bytes-like object is required, not 'str' in compat tests because JSON bytes (valid UTF-8) were being coerced to str somewhere in the flask-session → SQLAlchemy pipeline, breaking the LargeBinary column insert.

New approach: Leave the database serializer (_LazySafeSerializer) unchanged — msgpack is correct for LargeBinary columns. Instead, fix the actual bug site: override save_session() in AirflowSecureCookieSessionInterface to wrap response.set_cookie() with bytes-to-str coercion, since Werkzeug 3.0+ removed bytes support in dump_cookie().


Drafted-by: Claude Code (Opus 4.6); reviewed by @Sriniketh24 before posting

@potiuk

potiuk commented May 24, 2026

Copy link
Copy Markdown
Member

@Sriniketh24 A few things need addressing before review — see our Pull Request quality criteria.

  • CI fails: Low dep tests: providers / All-prov:LowestDeps:14:3.10:common.compat...fab (and possibly other checks — see the Checks tab for the full list).

No rush.


Note: This comment was drafted by an AI-assisted triage tool and may contain mistakes. Once you have addressed the points above, an Apache Airflow maintainer — a real person — will take the next look at your PR. We use this two-stage triage process so that our maintainers' limited time is spent where it matters most: the conversation with you.


Drafted-by: Claude Code (Opus 4.7); reviewed by @potiuk before posting

The tests patched `__mro__[1]` (SessionExemptMixin) but
`AirflowSecureCookieSessionInterface.save_session()` calls `super()`
which chains through SessionExemptMixin before reaching
SecureCookieSessionInterface.  This caused two failures:

1.  SessionExemptMixin.save_session accesses `flask.request.path` —
    added a `patch("flask.request")` context manager.
2.  `patch.object` replaces the class method with a MagicMock whose
    `side_effect` does NOT receive `self` — changed the lambda
    signature to `*args, **kwargs`.
3.  Changed patch target from `__mro__[1]` to `__mro__[2]` to patch
    SecureCookieSessionInterface directly, letting SessionExemptMixin
    run its real check so the bytes-to-str wrapper is exercised.
@Sriniketh24

Copy link
Copy Markdown
Author

Thanks for the heads-up! I've pushed a fix for the CI failures — the test mocks had two issues:

  1. Wrong MRO target: patch.object was patching __mro__[1] (SessionExemptMixin) instead of __mro__[2] (SecureCookieSessionInterface). This meant SessionExemptMixin.save_session() — which accesses flask.request.path — was bypassed entirely, and the bytes-to-str wrapper in AirflowSecureCookieSessionInterface.save_session() wasn't being exercised through the real call chain.

  2. Lambda signature mismatch: patch.object replaces the class method with a MagicMock whose side_effect does not receive self when called via super(). The lambda expected (self, app, session, response) but only received (app, session, response).

The fix patches __mro__[2] directly, adds patch("flask.request") so SessionExemptMixin can check request.path, and uses lambda *args, **kwargs: for the side_effect.


Drafted-by: Claude Code (Opus 4.7) (no human review before posting)

@potiuk
potiuk marked this pull request as draft May 26, 2026 00:45
@potiuk

potiuk commented May 26, 2026

Copy link
Copy Markdown
Member

@Sriniketh24 Converting to draft — this PR doesn't yet meet our Pull Request quality criteria.

  • Provider tests. See docs.

See the linked criteria for how to fix each item, then mark the PR "Ready for review". This is not a rejection — just an invitation to bring the PR up to standard. No rush.


Note: This comment was drafted by an AI-assisted triage tool and may contain mistakes. Once you have addressed the points above, an Apache Airflow maintainer — a real person — will take the next look at your PR. We use this two-stage triage process so that our maintainers' limited time is spent where it matters most: the conversation with you.


Drafted-by: Claude Code (Opus 4.7); reviewed by @potiuk before posting

session.py imports request via 'from flask import request', binding
the name in the module's own namespace at import time.  Patching
flask.request has no effect on that already-bound reference — the
LocalProxy kept in airflow.providers.fab.www.session.request is what
SessionExemptMixin.save_session reads.  Use that path instead so the
mock is visible during the call.
@Sriniketh24

Copy link
Copy Markdown
Author

Pushed another fix — identified the actual root cause from the CI logs.

Root cause: session.py binds request in its own namespace via from flask import request at import time. Patching flask.request has no effect on that already-evaluated reference — SessionExemptMixin.save_session reads from airflow.providers.fab.www.session.request (the local binding), not from flask.request.

Fix: Changed both mock targets from patch("flask.request") to patch("airflow.providers.fab.www.session.request"). The mock now intercepts the exact name the code uses, so request.path == "/any" works correctly and no RuntimeError: Working outside of request context is raised.

All other logic (MRO[2] target, lambda *args, **kwargs) remains correct. The fix is purely this one-line patch-target correction (plus an explanatory comment).

Would you be able to move this back out of draft once CI passes? Happy to address any other feedback.


Drafted-by: Claude Code (claude-sonnet-4-6) (no human review before posting)

@ashwani4588

Copy link
Copy Markdown

This is already fixed and released as part of Airflow providers fab 3.6.4 under pr#66565. Already tested and working fine. Thank you. Earlier with 3.6.1 i had used explicit type conversion in webserver_config.py to handle the error. Thanks a lot guys for working on this issue.

@Sriniketh24

Copy link
Copy Markdown
Author

Closing this in favour of #66565 which was merged 2026-05-08 and addresses the same issue at the root cause level.

#66565 moved self.serializer = _LazySafeSerializer() from SessionExemptMixin.__init__ into AirflowDatabaseSessionInterface.__init__ only, so AirflowSecureCookieSessionInterface now uses Flask's default string-based serializer and the bytes-vs-str mismatch never occurs. That is a cleaner fix than the bytes-to-str coercion in save_session() that this PR applied.

Thanks @potiuk for the review and @ashwani4588 for pointing out the existing fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FAB 3.6.0 can't create a cookies. TypeError: cannot use a string pattern on a bytes-like object

3 participants