Skip to content

Python: Harden FoundryCheckpointStore: apply RestrictedUnpickler allowlist on checkpoint load - #7820

Closed
herdiyanitdev (herdiyana256) wants to merge 1 commit into
microsoft:mainfrom
herdiyana256:fix/foundry-checkpoint-restricted-unpickler
Closed

Python: Harden FoundryCheckpointStore: apply RestrictedUnpickler allowlist on checkpoint load#7820
herdiyanitdev (herdiyana256) wants to merge 1 commit into
microsoft:mainfrom
herdiyana256:fix/foundry-checkpoint-restricted-unpickler

Conversation

@herdiyana256

Copy link
Copy Markdown

Harden FoundryCheckpointStore: apply RestrictedUnpickler allowlist on checkpoint load

Summary

FoundryCheckpointStore (foundry_hosting) is the only checkpoint backend that
calls decode_checkpoint_value(item.value) without allowed_types, so it falls
back to unrestricted pickle.loads. Every other backend threads the allowlist
through:

Backend Call
FileCheckpointStorage (_workflows/_checkpoint.py) decode_checkpoint_value(..., allowed_types=self._allowed_types)
CosmosCheckpointStorage (azure-cosmos/_checkpoint_storage.py) decode_checkpoint_value(cleaned, allowed_types=self._allowed_types)
FoundryCheckpointStore (foundry_hosting/_state_store.py) decode_checkpoint_value(item.value) — no allowlist

This PR aligns FoundryCheckpointStore with the framework's own security model:
the RestrictedUnpickler allowlist (the documented defense-in-depth control) is now
applied on every hosted checkpoint load, restricted-by-default, with an optional
allowed_checkpoint_types escape hatch identical to the other backends.

Rationale

foundry_hosting is the multi-tenant, network-facing hosting path. It is inconsistent
for the one hosted backend to silently drop the allowlist that FileCheckpointStorage
and CosmosCheckpointStorage both enforce. This is a defense-in-depth hardening that
restores parity; it is not a claim that checkpoint storage is a security boundary
(the docs correctly state it is a trusted data source).

Changes

  • FoundryCheckpointStore.__init__ accepts allowed_checkpoint_types: list[str] | None
    and stores self._allowed_types = frozenset(allowed_checkpoint_types or [])
    (same shape as FileCheckpointStorage).
  • load() and list_checkpoints() pass allowed_types=self._allowed_types to
    decode_checkpoint_value. Default (frozenset()) → restricted unpickler
    (built-in safe types + agent_framework.* + openai.types.*).
  • CheckpointStoreProvider.__init__ accepts allowed_checkpoint_types and threads it
    into every store it creates.

Backward compatibility

Legitimate checkpoints produced by encode_checkpoint_value contain only primitives,
collections, framework types, and OpenAI SDK types — all covered by the built-in safe
set — so they continue to round-trip under the restricted default (verified). Apps
persisting custom state types widen the allowlist via allowed_checkpoint_types,
exactly as with the file/cosmos backends.

Tests

  • test_load_defaults_to_restricted_unpickler_and_blocks_unlisted_types — a planted
    pickle envelope resolving to os.system is rejected (WorkflowCheckpointException)
    instead of executing.
  • test_checkpoint_store_provider_threads_allowed_types — provider forwards the
    allowlist to created stores.
  • test_checkpoint_store_defaults_to_empty_allowlist — default is restricted
    (frozenset()), not None (unrestricted).

Copilot AI balanced review requested due to automatic review settings August 22, 2026 15:15
@agent-framework-automation agent-framework-automation Bot added the python Usage: [Issues, PRs], Target: Python label Aug 22, 2026
@github-actions github-actions Bot changed the title Harden FoundryCheckpointStore: apply RestrictedUnpickler allowlist on checkpoint load Python: Harden FoundryCheckpointStore: apply RestrictedUnpickler allowlist on checkpoint load Aug 22, 2026
… load

FoundryCheckpointStore was the only checkpoint backend calling
decode_checkpoint_value(item.value) without allowed_types, falling back to
unrestricted pickle.loads. FileCheckpointStorage and CosmosCheckpointStorage
both thread the RestrictedUnpickler allowlist through.

Thread allowed_checkpoint_types through FoundryCheckpointStore and
CheckpointStoreProvider, and pass allowed_types on both load() and
list_checkpoints(). Default (frozenset()) restricts deserialization to the
built-in safe set plus framework and OpenAI SDK types, matching the other
backends. Legitimate checkpoints round-trip unchanged.

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

Hardens hosted checkpoint deserialization by applying the framework’s restricted unpickler allowlist.

Changes:

  • Adds configurable checkpoint type allowlisting.
  • Applies restrictions to checkpoint loading and listing.
  • Adds security and provider-forwarding tests.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
_state_store.py Enforces and propagates deserialization allowlists.
test_state_store.py Tests restricted defaults and provider propagation.

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +171 to 175
checkpoint = WorkflowCheckpoint.from_dict(
decode_checkpoint_value(item.value, allowed_types=self._allowed_types)
)
if checkpoint.workflow_name == workflow_name:
checkpoints.append(checkpoint)
@herdiyana256

Copy link
Copy Markdown
Author

Closing; will resubmit later.

@herdiyana256
herdiyanitdev (herdiyana256) deleted the fix/foundry-checkpoint-restricted-unpickler branch August 22, 2026 15:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants