glas-harness/history/2026-10-24-agent-session-identity-and-tool-boundary-assessment.md
tegwick b386b796e3
Some checks failed
ci / validate (push) Failing after 2m0s
GLAS-WP-0018: assess session-scoped agent identity and tool boundary; propose workplan
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 328925@bnt-lap001
Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed
2026-10-24 23:49:27 +02:00

11 KiB

Assessment: session-scoped agent identity and tool boundary

Date: 2026-10-24. Status: assessment only; no harness behavior changed. Request: net-kingdom hub message 14ea093a (standard service-identity-privilege_v0.1.md draft-3 SI-14, SI-15; companion section 7; SEC-DSM DSM-AGENT-002, -007, -008). key-cape assesses the issuing side in parallel. Plan: workplans/GLAS-WP-0018-session-agent-identity-and-tool-boundary.md.

Verdict

Feasible for the sessions glas-harness actually launches, in three separable steps. Glas is the right place for (a) carriage and (b) attribution, and for declaring (c); enforcement of (c) belongs to the rein, the sandbox and the token's audience. Glas cannot control a person's interactive Claude Code, Codex or Grok terminal session; for those it can only offer a shared contract and helper. Be explicit in the declaration that such sessions stay at SI-14's "holds the person's reach".

What exists today

Evidence is from src/glas_harness/{gateway,contract,hub}.py, the reins and profiles.

Need Today Gap
Actor type ExecutionRequest.actor is adm/agt/atm, validated into sand-boxer Consumer; run_id is the request id. Docs state attribution is "not caller authentication". A label, not an IAM principal. No principal_type=agent, no act.sub, no token.
Per-run identity request_id (uuid) names the run and the sandbox consumer. Sandbox is per run and destroyed after. Nothing binds request_id to an issuer-signed subject.
Credentials credential_route_refs are labels passed to the sandbox owner (SandboxExecRequest.credential_route_refs), applied only to the workload command; secret-looking values are refused in profiles. A KeyCape agent token is a new credential class with no route. The owner (sand-boxer / credential owner) must deliver it.
Tool boundary Profile field tool_profile (for example green-commit-only) is passed as --tool-profile to rein-aharness / rein-openweights; the rein enforces it. Sandbox profile (bwrap, egress deny-by-default) bounds filesystem and network. Glas does not know what a tool profile allows; it is an opaque string. No Glas-side check that the profile is narrower than the person's reach. Tool profiles do not name credentials or hub write scope.
Attribution record ExecutionEvidence (actor, project, profile, rein, model, sandbox_id, commit_sha, timings, tool_events_count and completeness, refs to assignment/role/duty). Posted to the Hub as a gateway_run progress event with hard-coded author: agt-glas-harness. Aggregate per run, not per state-changing action. Tool events are complete only when the rein streams them, parsed after the subprocess ends; unavailable for open-weight. No on_behalf_of, no session identity, no evidence location/hash. Hub author is unauthenticated free text (the declared net-kingdom gap). Hub post is best-effort, no outbox.
Commit trailers Commits inside the sandbox are made by the rein. Glas neither requires nor verifies Assistant, Assistant-Session, Assistant-Process.
Limits Token, turn, USD budgets and per-command timeout passed to reins that support them. No aggregate wall clock; nothing ties session lifetime to token lifetime (60 minute ceiling).
Readiness No committed profile is ready. Any identity feature starts on unverified/blocked profiles, so it cannot yet claim production assurance.

(a) Per-session agent identity

Design sketch (not implemented):

  1. Add an optional identity section to ExecutionRequest / profile: required on_behalf_of (the person, becomes act.sub), mode (agent), requested ttl_seconds (profile ceiling 3600), audience list.
  2. Before rein.start_session, the gateway asks a pluggable AgentIdentityIssuer (interface in Glas, KeyCape-backed adapter) for a token whose subject is the run (agent:<request_id>), principal_type=agent, act.sub the person, expiry min(profile ttl, remaining timeout, 3600). Fail closed: no token, no identity-requiring profile run (new failure_stage such as identity).
  3. Deliver the token to the workload as a sandbox credential (stdin or a sandbox-owner delivery route), never in argv, task file, evidence, Hub detail or logs; never in the person's password safe (SI-9). Redaction of the token pattern in tool output before it enters ToolResult.
  4. Carry through tool calls: Glas passes a token handle; the rein presents it to downstream services. Reins must accept and use it (a rein change), and downstream services must verify it (their change). Glas alone can only guarantee issue, scoped delivery, expiry and revocation at teardown.
  5. Teardown: discard the token, request revocation if the issuer supports it (dependency on key-cape), record issuance and revocation in evidence by token id (jti) only.

Dependencies: KeyCape agent-token issuance (principal_type claim, act, 60 minute cap, per-run subject, revocation) and the sandbox/credential owner for delivery. Without them Glas can stub the issuer for tests but must not claim identity.

Effort: contract + issuer interface + fail-closed gateway wiring + tests, roughly 3 to 5 days in Glas; real issuer adapter and delivery 3 to 5 days after key-cape and sand-boxer land their sides; rein acceptance of the token is a separate rein-aharness task.

(b) Attribution per state-changing action

Today's evidence answers "which run, profile, model, sandbox, outcome, commit". SI-15 also wants "on whose authority" and "where the evidence is kept" per action.

Proposal:

  • Add identity_ref (subject, act.sub, token jti, issuer, expiry; no token), session_id and authority_ref (assignment/role/duty already exist as refs) to ExecutionEvidence.
  • Define an ActionRecord (actor, on_behalf_of, session, operation, target, result, evidence_location, evidence_sha256, timestamp). Glas emits one per commit it can see and one per tool event when the rein streams events; where the rein cannot report, the record says events_completeness: unavailable rather than implying coverage. This honestly keeps SI-15's "reconstructable" as partial.
  • Write a hashed evidence receipt file (sandbox-external, append-only location chosen with the custodian), and put its hash in the Hub event. Use a durable local outbox so the Hub post is not best-effort only.
  • Require and verify commit trailers (Assistant, Assistant-Session, Assistant-Process) on sandbox commits; refuse or flag a run whose commits lack them.
  • Replace the hard-coded Hub author with the run identity once the Hub can authenticate it; until then record it in detail and keep the gap stated.

Effort: 4 to 6 days. Risk: tool-event coverage is rein-dependent; ActionRecords from parsed streams are after-the-fact, not tamper-evident, unless the receipt is stored outside the agent's reach.

(c) Session-scoped tool boundary

Facts: the boundary today is sandbox profile (filesystem, egress) plus rein tool_profile. For a harness-launched session the person's ambient reach is already mostly absent: the run is in a bwrap sandbox with a deny-by-default network and no person credentials; credential_route_refs: [] in the local profile. The risk SI-14 names is the interactive terminal session, which Glas does not launch.

Proposal for launched sessions:

  • Make the boundary declarative and checkable: a boundary block in the profile (tool profile id and hash, sandbox profile, egress hosts, credential route refs, hub write scope, max lifetime) that Glas records in evidence, and a catalog validation that every profile with a non-empty credential route or identity requirement names a non-default tool profile.
  • Resolve tool_profile against a rein-published manifest (rein-aharness profiles.py registry) so Glas can state, not just forward, what the tools can reach, and fail when the rein reports a different profile than requested (the rein already echoes tool_profile in its result; Glas currently does not compare).
  • Effective authority statement: tools reachable x token audience x sandbox egress. Emit it per run as evidence (DSM-AGENT-008).
  • Bind token audience and scopes to the boundary, so a tool that is callable but outside the boundary is rejected by the downstream service, not by the model's good manners (companion section 7 rule 2).

Effort: 3 to 4 days in Glas plus a rein-aharness manifest export. Risk: a tool profile that allows shell lets the agent reach whatever the sandbox network and credentials allow; the sandbox, not the profile name, is the real boundary.

Claude Code, Codex and Grok

Session type Glas control Identity Tool boundary Attribution
Harness-launched via rein-aharness (Claude CLI inside sandbox) Full outer lifecycle Feasible once KeyCape + delivery exist; rein must present the token Sandbox + tool profile, enforced by sand-boxer and the rein Run evidence now; per-action partial via streamed events
Harness-launched via rein-openweights Full outer lifecycle Same, if the rein accepts it Same Weaker: tool events unavailable
Interactive Claude Code in a person's terminal None Could use a helper script that requests a token at session start; person must run it Claude Code permission and hook settings, set by the person; not Glas Commit trailers and hub notes only
Codex No rein exists. Needs a new rein (CLI adapter) to be harness-launched As above once a rein exists Codex sandbox/approval modes, outside Glas Trailers already used by Codex commits (Assistant: codex)
Grok No rein exists Same Not known; unverified Not verified

Honest scope: Glas can only deliver SI-14 conformance for sessions it launches. For the rest it can publish a small, provider-neutral "session identity contract" (what a session must obtain, carry, record) that the person or a wrapper implements. Whether Codex and Grok expose hooks sufficient for per-call attribution is not established here and needs checking before any claim.

Risks and open questions

  1. Token in the sandbox: the agent can read and exfiltrate its own token within its lifetime. Mitigation is narrow audience, short TTL, egress deny-by-default, revocation at teardown; it is not eliminated.
  2. Identity over a still-blocked runtime: all profiles are non-ready, so this should be built behind unverified profiles and proven in isolation first.
  3. Evidence that the agent can write is not independent evidence; the receipt location must be outside the sandbox and the verifier independent (SI-15 step 4).
  4. Hub authorship stays unauthenticated until the Hub changes (owner: state-hub).
  5. actor values adm/agt/atm predate this standard; mapping agt to IAM principal_type agent needs an agreed table with net-kingdom.
  6. Scope creep: Glas must not become an identity provider or policy engine; it carries and records. Authorization stays with flex-auth and the issuer.
  1. Contract fields and evidence (b, identity_ref placeholders) and the boundary declaration with tool-profile echo check (c): no external dependency, immediately useful.
  2. Issuer interface with a test double; fail-closed gateway path.
  3. Real KeyCape adapter and delivery once key-cape and sand-boxer agree.
  4. Interactive-session contract and a Codex/Grok feasibility note.