Skip to content

Normalize durable workflow inputs (port of agent-framework#7205) - #36

Open
ahmedmuhsin wants to merge 1 commit into
mainfrom
fix/normalize-durable-workflow-inputs
Open

Normalize durable workflow inputs (port of agent-framework#7205)#36
ahmedmuhsin wants to merge 1 commit into
mainfrom
fix/normalize-durable-workflow-inputs

Conversation

@ahmedmuhsin

Copy link
Copy Markdown
Contributor

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_markers was 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 reach deserialize_value(), which is a pickle-deserialization (RCE) risk.

This closes the gap on both durable hosts:

  • durabletaskstrip_pickle_markers on the input_type is None branch of _coerce_initial_input (mirrors the typed-input path right below it).
  • azurefunctionsstrip_pickle_markers at the start_workflow_orchestration client_input boundary, which already stripped subworkflow markers.

strip_pickle_markers was 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

  • Pyright (source): 0 errors, both packages
  • mypy (tests): 0 issues, both packages
  • Ruff: clean
  • Unit tests: full suite passes, including 3 new tests

Notes

  • Opened as a draft.
  • Faithful 1:1 port of upstream #7205 (+186/-1). Scope intentionally matches upstream: the other return raw_value paths in _coerce_initial_input are not the same risk, since strings cannot be marker dicts and the dict case is json.dumps'd.

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)
Copilot AI review requested due to automatic review settings July 21, 2026 20:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 the start_workflow_orchestration HTTP 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
ahmedmuhsin marked this pull request as ready for review July 21, 2026 20:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants