Session-Bound Invocation Context
Selected milestone backlog for replacing caller-selected endpoint identity without continuing the Service Object Identity Migration.
The detailed design lives in
docs/proposals/session-bound-invocation-context-proposal.md.
Design Target
The final model has one live session context per process:
Process.session_contextis immutable after spawn.- Endpoint calls deliver privacy-preserving caller session metadata to the server. Subject details are not disclosed unless the caller explicitly asks for disclosure through the service call and a broker/service disclosure scope allows the requested fields.
- Broker-granted capabilities decide which service roots/facets a process may invoke.
- Services key user-facing state by caller session plus service-local records.
- Request payload fields are data and cannot select authority.
- Cross-session raw transfer is governed by cap transfer scope:
same_session,cross_session_shareable, orservice_regrant_only. If a cap crosses sessions, the receiver session supplies the future invocation subject context.
The existing service-object routing proof remains historical coverage for receiver-cookie spoofing, lifecycle, and transfer behavior. It is not the application authority model.
Gate 1: Process Session Invariant
Visible proof: a focused QEMU session/process smoke shows a spawned shell and child process have exactly one immutable session context, inherited by default, while an attempt to inject or use a second independent invocation subject fails.
Implementation scope:
- Add process-owned session context metadata with explicit system/service session support.
- Make
ProcessSpawnerselect the child session context through inherit or a trusted broker/session-manager path. - Prevent ordinary processes from holding or using multiple independent
UserSessionvalues as ambient invocation subjects. - Keep
SessionContextinternal to process/session mechanics. Do not expose principal, profile, account, role, tenant, auth-factor, external-claim, or display fields through endpoint defaults or proof-only shortcuts. - Add host tests for spawn/session validation and QEMU proof output for child inheritance.
- Hostile proof cases must show that copied
UserSessioncaps, payload data, shell strings, and manifest grant data cannot install a second process session or select another child session outside the trusted broker/session path. - Define the fail-closed freshness rule used by later endpoint work: normal endpoint calls from dead, revoked, or stale workload sessions fail except explicit recovery, logout, or renewal caps.
- Preserve existing anonymous/operator shell behavior while making guest shell behavior explicitly manifest-gated and narrow.
Verification gate:
make fmt-checkcargo test-config- relevant host tests for session metadata
- focused QEMU process/session proof
- one existing login or shell proof touched by the session path
Status 2026-04-28 17:01 UTC: the kernel now gives each process an immutable
SessionContext, ProcessSpawner inherits the caller context by default, and
trusted broker/session paths can mint launchers fixed to a validated child
context. make run-session-context proves a copied UserSession cap cannot
relabel the child invocation context and that a broker profile mismatch fails
closed. It also proves an expired guest session cannot refresh a broker shell
bundle. Endpoint-delivered caller-session metadata, payload spoofing, and
field-granular disclosure remain Gate 2 work.
Gate 2: Endpoint Caller Session Metadata And Disclosure
Visible proof: an endpoint server receives only an opaque service-scoped caller
session reference by default, rejects payload attempts to spoof user,
session, role, or participant identity, and receives bounded subject
details only when the caller explicitly requests disclosure and a
broker/service disclosure scope permits the requested fields.
Implementation scope:
- Extend endpoint delivery metadata with an opaque service-scoped caller session reference and minimal freshness/liveness information.
- Add an explicit disclosure mechanism for bounded subject fields, such as a
per-call disclosure flag or a
SessionDisclosurecap, and require a matching broker/service disclosure scope before fields are delivered. - Decide the first freshness enforcement point needed to close the open session expiry review finding.
- Keep endpoint receiver metadata internal and non-authority-bearing.
- Add hostile endpoint tests proving request bytes cannot override caller session context or force subject disclosure.
- Add transfer-scope tests proving a
same_sessioncap cannot cross into another session, while across_session_shareablecap invokes under the receiver’s session context after transfer. - Default transfer scope is fail-closed for cross-session movement:
user/session-local caps use
same_sessionorservice_regrant_only, whilecross_session_shareablemust be explicitly chosen by the service or broker. - Add expiry/revocation cases before shared-service migration: broker refuses fresh bundles for stale sessions, stale normal endpoint invocations fail or report the documented freshness failure, and service-scoped session refs cannot be replayed as authority.
Verification gate:
make fmt-checkcargo test-libcargo test-config- focused endpoint/session QEMU proof
make run-spawn
Status 2026-04-28 17:43 UTC: commit 687511a implements the first Gate 2
slice. Endpoint delivery includes only a service-scoped opaque caller-session
reference, epoch, and live/stale flags by default; it does not expose principal,
profile, account, role, tenant, auth-factor, external-claim, display-name, or
source-network fields. Normal endpoint calls from stale process sessions fail
closed before transfer preparation or enqueue. make run-session-context
proves a live child endpoint call carries nonzero opaque metadata despite
spoofed user, session, and role payload labels, then proves the same
child cannot invoke the endpoint after its session expires.
Status 2026-04-28 18:38 UTC: commit f0cb74b implements Gate 2 transfer-scope
enforcement. Cap holds now distinguish same_session,
cross_session_shareable, and service_regrant_only transfer policy.
Endpoint IPC, endpoint returns, and spawn grants reject cross-session movement
unless the scope permits it; fixed-session broker/launcher paths can regrant
service_regrant_only caps. make run-session-context proves same-session
spawn denial, raw IPC denial for a service-regrant-only UserSession, and
receiver-session invocation after an allowed endpoint-cap transfer. Remaining
Gate 2 work at that checkpoint was the explicit field-granular disclosure
mechanism.
Status 2026-04-28 19:33 UTC: commit 0f92d77 completes the Gate 2 explicit
disclosure mechanism. CALL SQEs carry a field-granular disclosure request
mask, capability holds carry service/broker disclosure scope, and endpoint
delivery exposes only the requested-and-allowed subject fields. The focused
QEMU proof covers all three privacy cases: request without scope exposes no
fields, scope without request exposes no fields, and request plus matching
scope exposes only allowed fields while narrowing broader requests. Gate 3 is
the chat session-keyed migration.
Gate 3: Chat Session-Keyed Migration
Visible proof: make run-chat shows chat membership keyed by an opaque
service-scoped caller session reference and broker-granted chat capability,
with no user-facing badge or receiver selector. Payload identity spoofing,
unauthorized subject disclosure, and unauthorized cross-session participant id
reuse fail closed.
Implementation scope:
- Replace legacy chat receiver-selected member identity with session-keyed records.
- Treat
ChatRootpossession plus caller session context as sufficient for join, subject to broker/profile policy. - Keep global principal/account metadata private by default. If chat needs display name or guest/operator class, obtain it through explicit disclosure with a matching disclosure scope. If it only needs narrower behavior, use a broker-granted chat facet that encodes policy without revealing subject fields.
- Add a narrower moderator facet if moderator behavior is needed; do not use payload roles or generic rights bits.
- If chat supports multiple participant records per session, make returned participant ids server data scoped to the caller session, not transferable authority.
- Decide chat cap transfer scope explicitly. Plain
ChatRootmay be same-session or broker-shareable; participant-like state must not raw-transfer across sessions unless chat defines a share/regrant method. If chat accepts a share, future calls use the receiver session as the invocation subject. - Update shell examples and chat docs.
Verification gate:
make fmt-checkcargo test-configcargo test-libmake run-chat- hostile chat spoofing QEMU coverage
Status 2026-04-28 20:06 UTC: commit dc7ece4 implements the Gate 3 chat
session-keyed migration. chat-server now serves with endpoint caller
metadata, derives an opaque live caller-session key, and uses that key for
member records, channel membership, sends, leaves, and polls. Calls without a
live session key fail closed. system-chat.cue no longer assigns static chat
badges to the shell or bot, and make run-chat proves normal chat runs through
operator-session chat-client processes while the attempted delegated endpoint
relabel remains rejected. The handle join field is request data only, not
membership authority. After review, chat-visible sender labels are also
service-assigned member-N values, so request handles do not drive displayed
sender identity.
Gate 4: Shared-Service And Legacy Cleanup
Visible proof: normal shared-service demos no longer expose caller-selected service-visible identity, and service-object identity planning is retired from the active path.
Implementation scope:
- Apply the session-keyed model to adventure player/NPC state and
terminal/stdio child bridges where they currently use legacy receiver
metadata as identity.
- Stdio bridges should bind parent-side servicing to opaque live endpoint caller-session metadata and reject a bridge that later changes caller session, without asking the child to disclose global subject fields.
- Remove normal shell and manifest syntax that lets a caller select a badge or receiver selector.
- Keep low-level receiver metadata only as internal endpoint transport state or hostile-test fixture.
- Update
docs/capability-model.md,docs/architecture/ipc-endpoints.md,docs/security/trust-boundaries.md, demos, and status pages.
Verification gate:
make fmt-checkcargo test-libcargo test-configmake run-smokemake run-spawnmake run-chatmake run-adventuremake docs
Status 2026-04-28 20:48 UTC: the guest-bundle cleanup slice narrows one Gate 4
identity/policy leak without touching adventure content. SessionManager.guest
now requires an explicit manifest guest seed, AuthorityBroker.shellBundle
returns no default guest service endpoints, and guest launchers use a
resource-profile launcherProfile instead of the full manifest binary list.
The default guest profile has an empty launcher; the session-context proof uses
a dedicated one-binary guest profile for session-context-child; and the
default smoke proof covers manifest-without-guest-seed denial.
Status 2026-04-28 21:36 UTC: the session-expiry review finding is closed for
current shell/broker authority. Endpoint CALLs already required live caller
sessions. Retained broker-issued non-endpoint bundle caps now expire at their
bound session boundary: RestrictedLauncher rejects spawn/list calls after the
minted session expires, and broker-issued SystemInfo caps are session-bound
wrappers. Raw process-spawner, capability-manager, and process-handle control
caps opt into live caller-session dispatch for any path that still exposes
them. make run-local-users proves an expired operator shell cannot keep
launcher authority through an already-issued bundle, and make run-session-context proves the narrow guest proof launcher also fails closed
after expiry.
Status 2026-04-28 22:02 UTC: the normal shell parser now rejects explicit
client @... badge N grants and preserves delegated client endpoint identity
when badge syntax is omitted. Default MOTD and adventure docs use omitted-badge
launches, while hostile selector fixtures remain in low-level smoke coverage.
Status 2026-04-29 05:59 UTC: the focused chat manifest now routes the same
kernel singleton chat_endpoint through init to the resident chat server that
the broker facets into operator shell bundles. The focused chat shell no longer
receives the resident chat-server export directly from system-chat.cue; the
normal shell path uses the broker-issued operator bundle chat endpoint, while
the resident bot keeps its manifest service grant.
Status 2026-04-29 06:17 UTC: terminal output is now behind the same live caller
session dispatch gate as terminal input. Both UART-backed TerminalSession
and socket-backed SocketTerminalSession require a live caller session for
write, writeLine, and readLine, so stale shell sessions cannot keep a
terminal bridge useful through write-only calls. TcpSocket.intoTerminalSession
continues to return a move-only terminal cap, but the result hold is explicitly
cross-session shareable because the Telnet gateway converts an accepted socket
in its service session and then grants the terminal to the broker-minted shell
session.
Status 2026-04-29 09:00 UTC: shell-serviced stdio bridge waits now bind to
opaque live endpoint caller-session metadata during the active child wait and
reject mismatched callers without asking the child to disclose global subject
fields. Normal StdIO.close exits cleanly, rejected calls drain transferred
caps before returning, and make run-session-context covers a transfer-bearing
cross-session rejection. demos/service-common no longer exposes a
badge-serving helper or badge field on EndpointCaller; new shared endpoint
loop code uses EndpointUserData, with the old badge-named user-data alias
kept for checked-in adventure compatibility while peer-owned adventure work
migrates.
Status 2026-04-29 09:44 UTC: the non-adventure endpoint caller-session
reference is widened to 128 bits while keeping scoped_ref as the low 64-bit
compatibility half and adding scoped_ref_hi as the high half. Endpoint
delivery fills both halves from independently domain-separated, nonzero hashes,
keeps epoch separate, and non-adventure service/session-context/stdout bridge
guards now require and compare both halves. This remains proof-grade opaque
reference derivation; a true keyed secret, scope-key rotation, and rotation
lifetime policy are still deferred.
Status 2026-04-29 10:20 UTC: endpoint caller-session references now use an
entropy-backed boot secret and HMAC-SHA256 over a non-reused endpoint
service-scope id plus the kernel session id. scoped_ref remains the low ABI
field, but it is no longer value-compatible with the old unkeyed low-half hash;
scoped_ref_hi is the high ABI field of the same keyed opaque reference.
epoch stays a separate field and is also domain-separated under the boot key
so stale/freshness audit correlation rotates with boot-key and endpoint-scope
changes. References rotate on reboot and endpoint object replacement. Stable
service-audit identity across service upgrades remains future work.
Status 2026-04-29 11:00 UTC: the session-context QEMU proof now calls two distinct endpoint service scopes from one child process/session before expiry and asserts their opaque caller-session reference tuples differ while both remain live. This covers endpoint-object replacement/scope changes at the demo-proof level; stable service-audit identity across upgrades remains future work.
Deferred Work
- Remote capability transport and network transparency.
- Durable account store and external identity binding persistence.
- Full quota service and scheduling-context donation policy.
- Explicit cross-session sharing UX and audit workflow.
- Stable service-audit identity for endpoint caller-session references across intentional service replacement or upgrade.
- Delegated-subject / act-on-behalf-of context. See
docs/proposals/delegated-subject-context-proposal.md.