potiuk commented on code in PR #68511:
URL: https://github.com/apache/airflow/pull/68511#discussion_r3711649182


##########
airflow-core/src/airflow/serialization/serialized_objects.py:
##########
@@ -664,9 +701,14 @@ def deserialize(cls, encoded_var: Any) -> Any:
             kwargs = deser["kwargs"]
             del deser
             if type_ == DAT.AIRFLOW_EXC_SER:

Review Comment:
   Yes — #68662. It removes the encode side entirely and makes decode 
legacy-only, returning `str(BaseException(*args))` without resolving or calling 
anything.
   
   That is a stronger position than this PR, which still resolves a 
payload-supplied name — constrained to loaded `AirflowException` subclasses — 
and then calls it. The cost is that a legacy node deserializes to a string 
rather than an exception object.
   
   So the real question is whether anything still needs a real exception object 
back. If not, #68662 is the better fix and this should close in its favour. If 
something does, this keeps round-trip fidelity and #68662 breaks it.
   
   No attachment to this one either way — flagging it so the two do not both 
land.
   
   ---
   Drafted-by: Claude Code (Opus 5); reviewed by @potiuk before posting
   



##########
airflow-core/src/airflow/serialization/serialized_objects.py:
##########
@@ -242,6 +242,43 @@ def _decode_priority_weight_strategy(var: str) -> 
PriorityWeightStrategy:
     return priority_weight_strategy_class()
 
 
+# Builtin exceptions a ``BASE_EXC_SER`` node can be rebuilt into. The encode 
side matches
+# ``KeyError`` / ``AttributeError`` *and their subclasses* while storing the 
concrete class name,
+# so a user-defined subclass serializes to a name that is absent here and 
cannot be rebuilt --
+# which was equally true when the name was imported, since ``builtins`` does 
not hold it either.
+# Resolving against this map keeps ``builtins.eval`` / ``builtins.exec`` out 
without importing.
+_DESERIALIZABLE_BUILTIN_EXCEPTIONS: dict[str, type[BaseException]] = {
+    "KeyError": KeyError,
+    "AttributeError": AttributeError,
+}
+
+
+def _resolve_airflow_exception(exc_cls_name: str) -> type[AirflowException]:
+    """
+    Resolve a serialized ``AirflowException`` class name to the loaded class, 
without importing it.
+
+    The module part is looked up in ``sys.modules`` and the class is read out 
of that module's
+    namespace, so a name in the stored blob can never cause an import: a 
module that is not already
+    loaded simply fails to resolve. The result must be an ``AirflowException`` 
subclass, so an
+    attacker's ``subprocess.check_output`` is rejected even when 
``subprocess`` is loaded.
+
+    The namespace is read directly rather than through ``getattr`` so that a 
module-level
+    ``__getattr__`` -- which Airflow uses for deprecation shims and lazy 
provider re-exports -- stays
+    out of the path, since those hooks do import on access.
+
+    Resolving the name instead of matching it against a prebuilt map is also 
what keeps blobs
+    written by older versions readable: these exceptions moved to 
``airflow.sdk.exceptions`` in
+    3.2.0 and are re-exported from ``airflow.exceptions``, so a 3.0/3.1 blob 
naming the old module
+    still resolves, exactly as it did when the name was imported.
+    """

Review Comment:
   All five taken. One character changed from the suggestion: `wont` → `won't`, 
since codespell rejects the contraction without the apostrophe.
   
   ---
   Drafted-by: Claude Code (Opus 5); reviewed by @potiuk before posting
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to