You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 3eaca67
Browse filesBrowse the repository at this point in the historyBrowse files
fix(eve): keep a turn's principal fixed while it runs
Another principal's typed text could answer a running turn's ctx.ask,
ask_question or relayed child prompt and drive the turn under its auth, and
a mixed delivery's stale answer let that sender stand in as the turn's
caller. Only the turn's own principal now joins its turn by text, with
refreshed claims; another principal's text waits for its own turn, each
answer to an open approval carries its responder, and another principal's
answer to a request that is no longer open is dropped.
Signed-off-by: ruiconti <1834568+ruiconti@users.noreply.github.com>
A turn's principal no longer changes while it runs. Only the turn's own principal can answer a pending `ctx.ask()` or `ask_question` by typing, whether a workflow tool, a subagent or a remote agent asked; another person's text waits and runs as their own turn instead of answering the question under the asker's auth. A typed answer updates the claims on `ctx.session.auth.current`, so its `delivery.consumed` hook and the rest of the turn see the request that sent it. Someone else's button answer still settles the request as its responder.
Copy file name to clipboardExpand all lines: docs/concepts/security-model.md
+4Lines changed: 4 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -75,6 +75,10 @@ A [channel](../channels/overview) is your agent's front door, so authenticating
75
75
76
76
A custom channel that accepts dashboard-style webhooks should follow the same shape: authenticate the raw body with an HMAC, compare signatures in constant time, and trust any body-supplied principal only after the signature verifies.
77
77
78
+
## A turn runs as one principal
79
+
80
+
A turn's principal (its authenticator, issuer, principal type and id) never changes while the turn runs; only its claims may update. Only that principal's messages steer the turn or answer its pending questions by text. In a shared thread, another person's message waits for the turn to end and then runs as their own turn, so their text never drives the turn's credentials and the turn never continues under their identity. Another person can still settle a pending request with a button or structured answer; they are recorded as its responder, and the turn keeps its caller. See [What reaches `ctx.session.auth`](../guides/auth-and-route-protection#what-reaches-ctxsessionauth).
81
+
78
82
## Authored markdown is data
79
83
80
84
[Skill](../skills) and [schedule](../schedules) files are markdown with YAML frontmatter, and eve treats that frontmatter strictly as data. The code-capable engines (`---js` / `---javascript`, which would `eval()` the frontmatter body the moment the file is parsed) are disabled, so such a fence throws rather than running. Frontmatter has to parse to a plain YAML object.
Copy file name to clipboardExpand all lines: docs/guides/auth-and-route-protection.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -298,6 +298,7 @@ Inside runtime code, `ctx.session.auth` carries the result of the channel's rout
298
298
-`auth.current`: the caller on the active inbound turn.
299
299
-`auth.initiator`: the caller that started the durable session.
300
300
- A follow-up message updates `auth.current` but leaves `auth.initiator` alone. When a different caller follows up on the same session, `auth.current` tracks the new caller for that turn while `auth.initiator` stays pinned to whoever started it.
301
+
- Within a turn the principal is fixed; only its claims may update. A message from the turn's own principal, including a typed answer to a pending `ctx.ask()` or `ask_question`, joins the turn and updates `auth.current` with its claims. A message from anyone else waits for the turn to end and runs as its own turn. Someone else can still answer a pending request with a button or structured response; that settles the request with them as its responder, and the turn keeps its `auth.current`.
301
302
-`auth.current` is whatever the channel passed as `auth` when it dispatched the message. Route auth either accepts the request or returns `401`, but a route handler or channel hook can still dispatch with `auth: null`, such as `source.send(message, { auth: null })` or a Slack `onMessage` hook that returns `{ auth: null }`. On a new session, both values are then `null`. On a continuation, `auth.current` is `null` while `auth.initiator` stays pinned to the session's original caller. Internal runtime paths, such as subagents, can also have no caller auth.
302
303
303
304
Use the principal on `auth.current` (or `auth.initiator`) to scope tools, resolve [dynamic capabilities](./dynamic-capabilities) per principal, or enforce tenant boundaries. There's no second per-session ownership ACL stacked on top of route auth. Access is decided at the HTTP boundary, and the durable session carries the caller snapshot forward into your runtime code.
Copy file name to clipboardExpand all lines: docs/guides/hooks.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -251,7 +251,7 @@ export default defineHook({
251
251
252
252
The remaining subscribers for the event still run. Then eve cancels the turn the same way [`session.cancel()`](./client/streaming) does: in-flight model and tool work is aborted, delegated child turns are cancelled, and the turn settles with `outcome: "cancelled"` and `cause: { hook }` naming the hook. No failure is recorded. A cancel from `turn.started` or `model.requested` takes effect before that model call. In a conversation, the next message starts a new turn. A delegated task reports the cancellation to its caller.
253
253
254
-
A plain-text message that answers a pending question, such as one from `ask_question`, is recorded as `delivery.consumed` before eve delivers it. Calling `ctx.cancel()` from that event's hook cancels the turn instead: the question settles as `interrupted`, and the waiting tool call never receives the text.
254
+
A plain-text message that answers a pending question, such as one from `ask_question`, is recorded as `delivery.consumed` before eve delivers it. Only the turn's own principal can answer by text; anyone else's text waits and runs as its own turn. Like a follow-up message, the answer updates the claims on `ctx.session.auth.current` for that event's hooks and the rest of the turn. Calling `ctx.cancel()` from that event's hook cancels the turn instead: the question settles as `interrupted`, and the waiting tool call never receives the text.
255
255
256
256
The same goes for the `call.progress` a workflow tool reports while the turn waits on it: `ctx.cancel()` from its hook cancels the turn, and the run's result never reaches the model.
0 commit comments