Normalize durable workflow inputs (port of agent-framework#7205) - #36
Open
ahmedmuhsin wants to merge 1 commit into
Open
Normalize durable workflow inputs (port of agent-framework#7205)#36ahmedmuhsin wants to merge 1 commit into
ahmedmuhsin wants to merge 1 commit into
Conversation
Port of microsoft/agent-framework#7205. Strip pickle markers on the untyped/union initial-input path so untrusted HTTP or client input shaped like reserved serialization markers cannot reach deserialize_value() (pickle RCE hardening). The typed-input path already stripped; this closes the gap on the path where the start executor has no single primary input type. - durabletask orchestrator: strip_pickle_markers on the input_type-None branch in _coerce_initial_input (mirrors the typed-input path) - azurefunctions: strip_pickle_markers at the start_workflow_orchestration client_input boundary - add behavioral tests for both hosts (reserved-marker input neutralized, ordinary JSON preserved)
Contributor
There was a problem hiding this comment.
Pull request overview
Ports upstream security hardening to ensure untrusted initial workflow input is normalized by neutralizing reserved pickle/type marker payloads before any typed reconstruction/deserialization paths can be reached.
Changes:
- Durable Task: apply
strip_pickle_markers()when the start executor has no single primary input type (input_type is None) in_coerce_initial_input. - Azure Functions: apply
strip_pickle_markers()at thestart_workflow_orchestrationHTTP boundary (after existing subworkflow-marker stripping). - Add new behavioral tests covering durabletask union/ambiguous start input handling and Azure Functions workflow-run boundary sanitization.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| python/packages/durabletask/agent_framework_durabletask/_workflows/orchestrator.py | Sanitizes initial input on the input_type is None path to close the marker-stripping gap. |
| python/packages/durabletask/tests/test_durabletask_workflow_initial_input.py | Adds durabletask behavioral coverage for marker-shaped vs ordinary union-typed initial input. |
| python/packages/azurefunctions/agent_framework_azurefunctions/_app.py | Sanitizes workflow HTTP client_input with strip_pickle_markers at the trust boundary. |
| python/packages/azurefunctions/tests/test_azurefunctions_workflow_initial_input.py | Adds Azure Functions behavioral coverage for marker-shaped input scheduled via workflow run route. |
Comment on lines
+56
to
+72
| async def test_workflow_run_route_neutralizes_reserved_marker_shaped_input() -> None: | ||
| """The workflow run route schedules neutralized framework-reserved metadata.""" | ||
| executor = _Start() | ||
| workflow = WorkflowBuilder(name="input_boundary", start_executor=executor, output_from=[executor]).build() | ||
| handler = _capture_run_handler(workflow) | ||
| request = Mock() | ||
| request.get_json.return_value = { | ||
| "__pickled__": "not-checkpoint-data", | ||
| "__type__": "builtins:int", | ||
| } | ||
| request.url = "https://example.test/api/workflow/input_boundary/run" | ||
| client = AsyncMock() | ||
| client.start_new.return_value = "instance-1" | ||
|
|
||
| await handler(request, client) | ||
|
|
||
| assert client.start_new.await_args.kwargs["client_input"] is None |
ahmedmuhsin
marked this pull request as ready for review
July 21, 2026 20:31
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.
Summary
Ports microsoft/agent-framework#7205 ("Normalize durable workflow inputs") into this repo. It is a small security hardening that landed upstream after the initial package sync (#21).
What and why
strip_pickle_markerswas applied on the typed initial-input path but not on the path where the start executor has no single primary input type (for example an ambiguous or union handler like@handler(input=str | dict | None)). On that path, untrusted external input (an HTTP body or client input) shaped like reserved serialization markers,{"__pickled__": "...", "__type__": "..."}, passed through unsanitized and could reachdeserialize_value(), which is a pickle-deserialization (RCE) risk.This closes the gap on both durable hosts:
strip_pickle_markerson theinput_type is Nonebranch of_coerce_initial_input(mirrors the typed-input path right below it).strip_pickle_markersat thestart_workflow_orchestrationclient_inputboundary, which already stripped subworkflow markers.strip_pickle_markerswas already imported in both files, so this is two one-line additions plus tests.Behavior
Non-breaking. Ordinary JSON and trusted child-workflow inputs are unchanged; only input literally shaped like reserved serialization markers is neutralized. New behavioral tests cover both hosts (marker-shaped input neutralized, ordinary JSON preserved).
Validation
Notes
return raw_valuepaths in_coerce_initial_inputare not the same risk, since strings cannot be marker dicts and the dict case isjson.dumps'd.