From 3acc86fdef908a09f8a5ff1c6b0335a7916816a7 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 24 Oct 2026 23:49:33 +0200 Subject: [PATCH] KEY-WP-0043: per-run agent identity assessment and proposed workplan Co-Authored-By: Claude Sonnet 5.5 Assistant: claude-code Assistant-Model: sonnet Assistant-Process: 328925@bnt-lap001 Assistant-Session: 14b795fd-c632-4c01-8d28-e7e490c7b0ed --- WORK-RECORDS.md | 5 + ...10-24-per-run-agent-identity-assessment.md | 134 ++++++++++++++++++ .../KEY-WP-0043-per-run-agent-identity.md | 75 ++++++++++ 3 files changed, 214 insertions(+) create mode 100644 docs/proposals/2026-10-24-per-run-agent-identity-assessment.md create mode 100644 workplans/KEY-WP-0043-per-run-agent-identity.md diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 02a6b4c..22b530f 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -53,6 +53,7 @@ | workplan | KEY-WP-0040 | finished | — | workplans/KEY-WP-0040-mfa-attended-run-findings.md | | workplan | KEY-WP-0041 | finished | — | workplans/KEY-WP-0041-sign-in-diagnosis-page.md | | workplan | KEY-WP-0042 | proposed | — | workplans/KEY-WP-0042-online-access-token-status.md | +| workplan | KEY-WP-0043 | proposed | — | workplans/KEY-WP-0043-per-run-agent-identity.md | | task | ADHOC-2026-09-05-T01 | done | — | workplans/ADHOC-2026-09-05.md | | task | ADHOC-2026-09-07-T01 | done | — | workplans/ADHOC-2026-09-07.md | | task | ADHOC-2026-09-27-T01 | done | — | workplans/ADHOC-2026-09-27.md | @@ -226,4 +227,8 @@ | task | KEY-WP-0042-T02 | done | — | workplans/KEY-WP-0042-online-access-token-status.md | | task | KEY-WP-0042-T03 | todo | — | workplans/KEY-WP-0042-online-access-token-status.md | | task | KEY-WP-0042-T04 | done | — | workplans/KEY-WP-0042-online-access-token-status.md | +| task | KEY-WP-0043-T01 | todo | — | workplans/KEY-WP-0043-per-run-agent-identity.md | +| task | KEY-WP-0043-T02 | wait | — | workplans/KEY-WP-0043-per-run-agent-identity.md | +| task | KEY-WP-0043-T03 | wait | — | workplans/KEY-WP-0043-per-run-agent-identity.md | +| task | KEY-WP-0043-T04 | wait | — | workplans/KEY-WP-0043-per-run-agent-identity.md | | intake | KEY-IN-0001 | closed | — | intakes/intakes.md | diff --git a/docs/proposals/2026-10-24-per-run-agent-identity-assessment.md b/docs/proposals/2026-10-24-per-run-agent-identity-assessment.md new file mode 100644 index 0000000..4329a50 --- /dev/null +++ b/docs/proposals/2026-10-24-per-run-agent-identity-assessment.md @@ -0,0 +1,134 @@ +# Per-run agent identity: KeyCape feasibility assessment + +Date: 2026-10-24. Status: assessment only, no code or issuance changed. +Request: net-kingdom hub message b4291a84 (SEC-DSM DSM-AGENT-002/003, SI-14 of +service-identity-privilege_v0.1 draft-3). Work proposal: KEY-WP-0043. +Sources read: net-kingdom canon/standards/iam-profile_v0.3.md ("Agent Principal +Flow", "Core Claims"; read-only), src/internal/server/oidc/{token,discovery, +token_status,token_status_http}.go, server/policy/policy.go, authclient/, +config/validate.go, domain/model.go, docs/openbao-service-auth-contract.md, +SCOPE.md. flex-auth and OpenBao were not inspected; consumer statements below +are derived from KeyCape's side and must be confirmed by those owners. + +## Verdict + +**Feasible with changes. Not feasible today.** KeyCape has no agent principal, +no token exchange, no `act`/`actor_sub`, and no per-run input. The building +blocks (RS256 signer, per-client lifetime 1m-1h, jti + online status/revocation, +tenant and assurance claim builders, static confidential clients) exist, so the +change is an extension of the existing token endpoint, not a new service. + +## What exists + +| Need | Today | +| --- | --- | +| Grants | `authorization_code` (PKCE) and `client_credentials` only; discovery lists exactly those. No RFC 8693 token exchange (`grant_type` other than the two is refused, `token.go`). | +| Service token | `serveClientCredentials`: confidential client, `client_secret_basic`, registration supplies `sub` (ServiceSubject), `tenant`, `roles`, `aud`; `principal_type` is the literal `"service"`; `assurance` = aal1, methods `client_secret`, source `key-cape`. | +| Lifetime | Per-client `tokenLifetime`, validated 1m..1h for client_credentials clients (`config/validate.go`). 15 minutes and the 60-minute ceiling are already expressible and enforced at startup. | +| Tenant | Service: registration. Human: directory, with fail-closed conflict handling and a `tenant_source` provenance claim (GH-DEC-2026-013). | +| Human assurance | `assuranceClaim(mfaVerified, authTime, ...)` gives aal1/aal2 with `at` = authentication time. | +| Status/revocation | `TokenStatusStore` (jti, registration commitment, revoked flag) behind introspection/revocation when enabled. In-memory, bounded capacity. | +| Agent claims | None. `principal_type` is only `human` or `service`. `agent`, `act`, `actor_sub` do not occur in `src/`. | + +Relevant constraint: `TestPrincipalTypeIsNeverSuppliedByConfiguration` pins +`principal_type` to string literals so humanity (and by extension any type) never +becomes configuration data (GH-DEC-2026-016 section 5, A-16). A new `"agent"` +path must be a literal in its own code path; this test will and should force a +conscious edit. Client registrations are static and deployment-owned +(dynamic registration is excluded by SCOPE), loaded at startup. + +## Answer to the claim-by-claim question + +| IAM Profile v0.3 requirement | Verdict | +| --- | --- | +| `principal_type: agent` | Add a third literal path. Small. | +| `agent` object with `id`, `mode` | New claim. `mode` comes from the registration class (a fixed property of the client, not request input); `id` is class plus run id. | +| Delegating person in `act.sub` / `actor_sub` for delegated | Needs a way to authenticate the person. Only token exchange (below) provides it; client_credentials cannot. | +| Tenant | Autonomous: registration. Delegated: must equal the person's tenant, else refuse (reuse the humanTenant conflict rule). | +| Assurance for agent credential and for delegated human authority | Agent credential: aal1 / `client_secret` (existing builder). Delegated authority: copy the verified subject token's assurance as a nested object; this nesting is not in the profile and needs an amendment (see risks). | +| 15 min target, 60 min ceiling | Already supported by per-client lifetime and its 1h validation bound; add a request-time `min(requested, client cap)` and a hard 3600 s cap in code. | + +## Mechanism options + +**A. client_credentials with an agent client class (autonomous mode).** +New `principalType: agent` registration (config enum, emitted only via a +literal in code), `agentClass`, `agentMode: autonomous`, `boundaryPolicy` +reference, `tokenLifetime` default 15m, cap 60m. Per-run identity: optional +`run_id` request parameter (validated charset/length) becomes part of +`agent.id` and `jti` linkage. Cannot carry a delegating person. Effort about +2-3 days including tests, discovery, status-store and verifier updates. + +**B. RFC 8693 token exchange (delegated mode) - recommended for delegated.** +`grant_type=urn:ietf:params:oauth:grant-type:token-exchange`. The agent +launcher authenticates as the agent client (`client_secret_basic`) and presents +the person's KeyCape access token as `subject_token`. KeyCape verifies +signature, issuer, audience, expiry, online status (jti not revoked), the +person not suspended, and subject `principal_type=human`; requires a minimum +assurance (policy: aal2 for any privileged scope); then issues +`principal_type=agent`, `sub` = agent id, `act.sub` (and `actor_sub`) = the +person, `agent={id,mode:delegated,class}`, tenant equal to the person's, +scopes = intersection of request, client allow-list and subject scope, and +`exp = min(requested, agent cap, 3600 s, subject exp)`. Effort about 1-1.5 +weeks: handler, claims, validation, status registration, negative tests. + +**C. Other.** An authorization-code flow naming the agent as client (the person +logs in and consents per run) fits delegation without a new grant, but needs a +browser per run and is poor for headless sessions. Dynamic client registration +per run is rejected by SCOPE and by the A-16/static-registration reasoning. + +## What KeyCape cannot provide alone + +1. **Per-run is only as real as the caller.** KeyCape can attest "client X + authenticated and asked for run R". If the session itself holds the class + secret it can mint tokens for any R, and ambient authority has merely moved. + The launcher (glas-harness) must hold the class credential, mint one token + per run, and hand the session only that token. This is the central design + dependency on glas-harness. +2. **Registrations are static, restart-bound.** Adding a class is a deployment + change owned by NetKingdom/Railiance; runs are not registrations. +3. **Revocation survives only while the process does** (in-memory status store): + a restart forgets revocations, bounded by the short lifetime. Acceptable at + 15 minutes; note it for 60. +4. **Delegation outliving the person's session** is a policy choice + (exp capped at subject exp is the conservative default). + +## Risks + +- Profile gap: nested delegated assurance, `agent.class`, `run_id` and the + boundary-policy reference are not in IAM Profile v0.3; needs a v0.4 + amendment (already a proposed amendment elsewhere) or an explicit extension note. +- Confused deputy / privilege escalation in exchange: scope intersection, never + union, plus refusal when the person's token is itself an agent or service token. +- A-16 provenance: the `agent` type must not be reachable by data; keep the + literal and update the guard test deliberately. +- A downstream consumer that tests only `principal_type != "human"` could treat + agents as services. `token_status_http.go` currently rejects unknown types + (fail closed), as does `policy.Actor` (human only); both must be reviewed, not + loosened by accident. +- Secret custody for the launcher's class client goes through OpenBao routing, + not through KeyCape or chat. + +## Consumer changes (to be confirmed by owners) + +- **OpenBao JWT roles** (railiance-platform): current roles bind exact `iss`, + `aud`, `sub`, `principal_type`, `tenant`, role. Per-run `sub` cannot be exact; + use a stable subject (class) with the run id in a separate claim, or a glob on + `sub`. Bind `principal_type=agent`, `agent.mode`, and `tenant`; cap role token + TTL at or below the agent token lifetime; whether nested claims + (`agent.mode`, `act.sub`) are bindable or need flattened mirror claims is to be + verified by the platform owner. No agent should reach a role built for a service. +- **flex-auth**: must accept `principal_type=agent`, read `agent.*`, `act.sub` + and nested assurance, authorize on the pair (agent, delegator), and apply the + boundary policy; deny by default for unknown shapes. Not inspected here. +- **KeyCape verifiers** (`authclient`): add agent to the expected types and + checks; discovery `claims_supported` adds `act`, `agent`; `grant_types_supported` + adds token exchange when B ships. +- **glas-harness**: hold the class credential, perform the exchange, pass only + the short token, never the person's token or class secret, to the session. + +## Recommended sequence + +1. Owner agreement on claim shape (profile amendment) and mechanism B for + delegated, A for autonomous. 2. Phase 1: A. 3. Phase 2: B. 4. Consumer + changes, then a NetKingdom-gated live registration and rollout (no live step + is proposed here). See KEY-WP-0043. diff --git a/workplans/KEY-WP-0043-per-run-agent-identity.md b/workplans/KEY-WP-0043-per-run-agent-identity.md new file mode 100644 index 0000000..b1e54e4 --- /dev/null +++ b/workplans/KEY-WP-0043-per-run-agent-identity.md @@ -0,0 +1,75 @@ +--- +id: KEY-WP-0043 +type: workplan +title: "Issue short-lived per-run agent tokens (autonomous and delegated)" +domain: infotech +repo: key-cape +status: proposed +owner: claude-code +topic_slug: custodian +created: "2026-10-24" +updated: "2026-10-24" +flavor: extension +depends_on: [] +--- + +Proposed from docs/proposals/2026-10-24-per-run-agent-identity-assessment.md +(verdict: feasible with changes). Serves SEC-DSM DSM-AGENT-002/003 and SI-14 +of net-kingdom's Service Identity and Privilege standard. Proposal only: no +task starts before owner agreement, and nothing here deploys or registers a +client live; registration and rollout stay NetKingdom/Railiance-gated. + +## Agree the agent claim shape and mechanism + +```task +id: KEY-WP-0043-T01 +status: todo +priority: high +``` + +With net-kingdom, glas-harness, flex-auth and railiance-platform: fix `agent` +(`id`, `mode`, `class`, `run_id`), `act.sub`/`actor_sub`, nested delegated +assurance, boundary-policy reference, the 15-minute default and 3600 s cap, and +the launcher-holds-the-credential model. Output: an IAM Profile amendment note. + +## Autonomous agent tokens via client_credentials + +```task +id: KEY-WP-0043-T02 +status: wait +priority: high +``` + +Add `principal_type: agent` as a literal on its own path, agent client class +registration with validation (confidential, env secretRef, tenant, mode, +cap at most 1h), validated `run_id` request parameter, assurance for the +agent credential, deliberate update of the principal-type guard test, +discovery and token-status/verifier support. Tests: claims, cap, refusals. +Depends on T01. + +## Delegated agent tokens via token exchange + +```task +id: KEY-WP-0043-T03 +status: wait +priority: high +``` + +Implement RFC 8693 exchange: authenticate the agent client, verify the human +subject token (signature, status, suspension, human type, minimum assurance), +tenant equality, scope intersection, `act.sub`, nested assurance, expiry at +most the subject expiry and the cap. Negative tests: agent or service subject +token, tenant mismatch, expired or revoked subject, scope widening. Depends on T02. + +## Consumer coordination and rollout packet + +```task +id: KEY-WP-0043-T04 +status: wait +priority: medium +``` + +Hand OpenBao role bindings (including nested-claim bindability) to +railiance-platform, agent authorization input to flex-auth, and the launcher +flow to glas-harness. Prepare a registration and rollout packet for +NetKingdom; perform no live change.