The error said no. The session said yes.
Compare the rejected update, effective rule, and actual tool effect.
See the result →A session began with a tool denied. Its state schema rejected a request to allow that tool, and the permission setter reported an error. The live session nevertheless read the tool as allowed. In a local test, the agent then ran it once. A fresh control ran it zero times.
On the tested upstream commit, “Invalid state update” was followed by an effective allow rule and one mock tool execution. With the local repair, the rejected rule stayed deny and the tool stayed unexecuted.
Compare the rejected update, effective rule, and actual tool effect.
See the result →Follow the shared object from the permission setter into the live session.
Follow the state →Read the baseline, direct state-write control, and server handler check.
Inspect the controls →Review the local correction and the exact repeated test results.
Read the reattack →The host supplied a valid deny rule for a tool and a schema that would reject allow. The specialized permission setter raised Invalid state update. A fresh read of the same session then returned allow.
deny.allow.This is a permission validation failure in the live Agent Controller session at the tested Mastra commit. The reported error did not represent the rule later used for tool selection. The same mock agent with a fresh controller and the same deny rule recorded zero executions before the rejected change.
An inert mock tool in a local process. The observed effect is one tool invocation in the test, with no external action or hosted Mastra deployment involved.
The permission setter took permissionRules from a shallow state snapshot. That made a new outer object while retaining the live nested permissions object.
Before the queued state validator decided whether the change could be committed, setForTool() modified that nested object. Validation rejected the requested allow and stopped the normal write. The nested change had already reached the object the session was using. Later approval resolution and tool selection saw allow.
A second test locked the execute category at deny. Its category setter rejected allow while the effective category became allowed. Another test changed the object returned by getRules() and saw the live policy change. These variations point to the shared nested object, rather than an error in the schema itself.
The validator controlled the formal state write. The permission setter had already changed an object the live session retained, so an exception and an allowed rule could appear in the same sequence.
Direct state writes, permission setters, a fresh controller, and the server handler were checked separately on the pinned source.
| Check | Expected rule | Pinned upstream observation |
|---|---|---|
| Fresh session with valid schema | deny | deny; mock tool effect count zero |
Plain session.state.set() asks for allow | deny | Rejected; rule remains deny |
setForTool() asks for allow | deny | Rejected; effective rule becomes allow |
| Next mock agent run | Zero tool effects | One tool execution |
| Server handler setter, then fresh getter | deny | Setter rejects; getter returns allow |
The three-file core baseline recorded four failed new regression assertions and 18 passing existing tests. The four assertions cover the denied tool, denied category, mutation through the rules getter, and actual mock tool execution. The direct rejected state-write control passed inside the tool-policy test.
The focused server baseline called the permission handlers directly: the setter rejected the change, and a getter for the same resource returned allow. Its focused assertion failed on the original built core. That check establishes local handler behavior; it was not a hosted HTTP request through network middleware.
The local patch copies both nested rule maps for readers and builds a new rules object inside the session’s serialized update queue. The validated object becomes live only after the schema accepts it.
The same regression tests were run after the local repair, followed by a second exact replay. Two concurrent valid setter calls were also checked so both updates survive.
The rejected allow request leaves the effective rule allowed. The mock tool runs once, while a fresh control remains at zero.
The rejected request leaves the effective rule denied. The mock tool records zero executions. The server handler’s fresh getter also returns deny.
This correction and its regression tests are a local candidate supplied to Mastra for review. The observed results belong to the pinned checkout and the local repair; this page makes no claim of an upstream merge.
The case requires an existing nested permissionRules object, a host schema that rejects the requested policy, and a caller able to invoke the specialized permission setter.
The server route declares authentication and agent-controller:execute. The mock agent used a mode with the tool available and governed by the controller permission gate. The run proves a local live-session validation bypass and one controlled invocation. Hosted behavior, behavior after a restart, and a separate tool-level approval setting were outside this test.
Kronowave sent the pinned reproduction, evidence bundle, and candidate repair to security@mastra.ai on October 7, 2026. A follow-up on October 14, copied to a founder, asked for a remediation contact and said publication was being prepared. As of October 19, the recorded email thread contains no reply. The research record and test material are available to the maintainers for coordinated review.
The test separates the error returned to the caller from the permission actually used for the next action. Here those two answers diverged: validation rejected allow, while the live agent executed under allow. The local repair validates a separate candidate before it can replace the rule.
The public source links identify the permission setter, shallow state snapshot, approval resolution, tool selection, and server handler at the investigated commit. Local baseline and repair logs were sent privately to Mastra.
We trace the rule a system reports through the state its agent actually uses, then test the correction against the same action.
48-hour Rapid Scan →