Sessions let an agent hold a conversation across calls. Durable pause and resume let it stop for approval and continue later, from state saved in the session.
That saved, resumable state, keyed by an id, is genuinely useful. It also brings two of the oldest web bugs along with it, and both matter more when the state holds a pending privileged action.
Bug one: the session id is an access-control decision
A session is found by its session_id. Whoever presents the id gets the session: its full conversation
history, its saved state, its pending actions.
So the id is not just a lookup key. It is an authorisation token, and it is only as strong as two things: whether it can be guessed, and whether anything checks that the caller is allowed to use it.
A sequential or short id can be guessed or enumerated. And if resume(session_id, ...) loads the
session without checking that the caller owns it, then knowing an id is enough to read someone's
conversation, or to answer the approval prompt on their pending action. That is broken access control,
the plainest kind, now guarding an agent's memory and its pending privileged operations rather than a
row in a database.
# the quiet vulnerability: no owner check
async def resume(self, session_id: str, decision: str):
session = self.sessions.get(session_id) # anyone with the id gets it
...
A session id is an authorisation token. Without an owner check, knowing the id is access.
Bug two: the gap between approve and execute
Durable pause/resume separates, in time, two things the blocking version kept together: the human's approval and the tool's execution. The agent saves a pending action, a human approves it later, and the agent then runs it. Between the save and the run, that pending action sits in storage.
If anything can modify it in that window, the human approves one action and the agent executes a different one.
pause: save pending = delete_file("old_backup.txt")
[ the record sits in the session store ]
tamper: pending -> delete_file("production.db")
approve: human sees "old_backup.txt", clicks approve
resume: agent executes the CURRENT pending -> deletes production.db
The human reviewed a safe action and authorised it in good faith. The agent executed whatever the pending record said at resume time. This is a time-of-check to time-of-use gap, a classic, and durable pause/resume creates it by design, because the check (approval) and the use (execution) are now deliberately far apart.
Why the persistence makes both worse
The blocking approval gate from earlier held everything in one process, in memory, for seconds. Durable pause/resume writes the state to a store and keeps it for as long as the human takes. That is the whole feature, and it widens both exposures.
A longer-lived, persisted, id-addressable record is a bigger target than a few seconds of process memory. More time to guess an id, more time to tamper with a pending action, and a store that other code, or another tenant, might reach. The durability that makes the feature useful is the same property that gives an attacker room to work.
Closing both gaps
The controls are standard web-security discipline, applied to the session store and the resume path.
| Control | What it stops |
|---|---|
| Use unguessable session ids | Enumeration; an id cannot be stumbled onto |
| Check the caller owns the session on every access | Broken access control; the id alone is not enough |
| Bind the approval to the exact action, cryptographically | Approve-A-execute-B; a changed action invalidates the approval |
| Re-validate the pending action at resume | A pending record that changed since pause is refused |
| Isolate and access-control the session store | Another tenant's code cannot read or alter pending state |
| Expire pending actions | A stale approval window closes instead of waiting forever |
Unguessable ids, an owner check on every access, and an approval bound to the exact action.
The third row is the specific cure for bug two. Bind the approval to a fingerprint of the exact action, the tool name and its arguments, so that approving "delete old_backup.txt" produces a token valid only for that call. If the pending record is altered, the fingerprint no longer matches and the resume refuses. The human's decision is then tied to what they actually saw, not to whatever the record holds later.
What this touches
For the frameworks, this sits on the MAESTRO Agent Frameworks and Deployment layers. Session access without an owner check maps to T3 Privilege Compromise, which the pack defines as weaknesses in permission management leading to unauthorised actions. The approve-A-execute-B gap maps to T2 Tool Misuse, the agent driven to run an unintended action. In the OWASP Agentic Top 10 the anchors are ASI03 and ASI02.
On the regulatory side, framed as scope rather than a citation: access control, authorisation and segregation of user data are foundational obligations across GCC rulebooks. A session whose id alone grants access, or an approval that does not bind to the action taken, is an access-control and authorisation failure those rules already govern, even though they pictured a user account and a signed form rather than a resumable agent state. Which obligations apply depends on your regulator and architecture, and that mapping is an advisory estimate until someone checks it properly.
The part worth keeping
Sessions and durable pause/resume are the right way to make an agent conversational and to keep a human in the loop without holding a request open. The previous posts built them for good reasons, and nothing here says drop them.
The point is that a resumable, persisted, id-addressable state is a security object, not just a convenience. Give it unguessable ids, check ownership on every access, and bind every approval to the exact action it approved. Do that and pause/resume stays the durable, safe control it is meant to be. Skip it and you have a system where knowing a number reads a stranger's conversation, and approving one action runs another.