Summary
The AG-UI workflow checkpoint resume path validated request ownership only when the checkpoint carried pending request-info events. A checkpoint with an owner but no pending events skipped the check entirely, so a caller could resume another Snapshot Scope's clean checkpoint and have subsequent saves re-stamp ownership to that caller.
Where
python/packages/ag-ui/agent_framework_ag_ui/_workflow.py — the resume path used if checkpoint_pending_ids and checkpoint_owner != request_owner:. The empty-pending_ids case short-circuited the owner comparison. Combined with _OwnedWorkflowCheckpointStorage.save stamping ownership on every save, the clean-checkpoint path carried an owner that the resume check never read.
Class
Incorrect authorization / credential binding (CWE-287 / CWE-346) — a confused-deputy on cross-scope checkpoint resume.
Fix
Validate whenever the checkpoint carries an owner: if checkpoint_owner is not None and checkpoint_owner != request_owner:. Legacy ownerless checkpoints continue to resume; anonymous callers are handled. Resolved by #8011 (merged).
AI-assisted.
Summary
The AG-UI workflow checkpoint resume path validated request ownership only when the checkpoint carried pending request-info events. A checkpoint with an owner but no pending events skipped the check entirely, so a caller could resume another Snapshot Scope's clean checkpoint and have subsequent saves re-stamp ownership to that caller.
Where
python/packages/ag-ui/agent_framework_ag_ui/_workflow.py— the resume path usedif checkpoint_pending_ids and checkpoint_owner != request_owner:. The empty-pending_idscase short-circuited the owner comparison. Combined with_OwnedWorkflowCheckpointStorage.savestamping ownership on every save, the clean-checkpoint path carried an owner that the resume check never read.Class
Incorrect authorization / credential binding (CWE-287 / CWE-346) — a confused-deputy on cross-scope checkpoint resume.
Fix
Validate whenever the checkpoint carries an owner:
if checkpoint_owner is not None and checkpoint_owner != request_owner:. Legacy ownerless checkpoints continue to resume; anonymous callers are handled. Resolved by #8011 (merged).AI-assisted.