glas-harness/history/2026-10-24-agent-session-identity-and-tool-boundary-assessment.md

166 lines
11 KiB
Markdown
Raw Permalink Normal View History

# 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.
## Recommended order
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.