{"id":12628,"date":"2026-07-07T21:21:45","date_gmt":"2026-07-08T02:21:45","guid":{"rendered":"https:\/\/www.rushworth.us\/lisa\/?p=12628"},"modified":"2026-10-30T10:30:20","modified_gmt":"2026-10-30T15:30:20","slug":"who-is-acting-spiffe-identity-and-delegated-authority-for-agents","status":"publish","type":"post","link":"https:\/\/www.rushworth.us\/lisa\/?p=12628","title":{"rendered":"Who Is Acting? SPIFFE Identity and Delegated Authority for Agents"},"content":{"rendered":"<p>An agent can perform an operation under its own authority or perform the same operation on behalf of a person. Those are different security events.<\/p>\n<p>\u201cAgent XYZ modified this resource\u201d identifies a machine principal. \u201cAgent XYZ modified this resource using Lisa\u2019s authority\u201d identifies both an authorization subject and an acting party.<\/p>\n<p>SPIFFE supplies the agent\u2019s verifiable workload identity. OAuth token exchange can connect that identity to delegated user authority while preserving the distinction between <strong>whose permissions apply<\/strong> and <strong>which software exercised them<\/strong>. These are complementary layers, not capabilities SPIFFE automatically combines.<\/p>\n<p><strong>Start with the agent\u2019s workload identity<\/strong><\/p>\n<p>For this example, Agent XYZ runs as a workload registered with SPIRE:<\/p>\n<pre>spiffe:\/\/agents.example.org\/agent\/xyz<\/pre>\n<p>SPIRE attests the execution environment and local workload, matches its evidence against registration policy, and delivers the appropriate SVID through the Workload API. The identity belongs to the attested workload\u2014not to an agent name supplied in a prompt or HTTP header.<\/p>\n<p>That distinction suggests an important design rule: <strong>identify the execution boundary you actually control<\/strong>.<\/p>\n<p>If a single orchestrator process runs ten logically different agents with access to the same credentials, labeling them with ten names does not create ten independently attested security principals. Where strong separation matters, give the agents distinct workload boundaries and issuance policies; otherwise, treat their logical names and run IDs as application metadata rather than independent cryptographic identities. This follows from the Workload API\u2019s caller-identification model.<\/p>\n<p><strong>Model one: the agent acts as itself<\/strong><\/p>\n<p>For autonomous work, the application authenticates Agent XYZ and authorizes the agent principal directly.<\/p>\n<p>There are two common integration patterns:<\/p>\n<ul>\n<li><strong>Direct SPIFFE authentication:<\/strong> the API accepts an X.509-SVID over mTLS, or a JWT-SVID intended for that API.<\/li>\n<li><strong>Identity-to-token exchange:<\/strong> a token service validates the agent\u2019s SPIFFE credential and issues an application access token representing the agent.<\/li>\n<\/ul>\n<p>A proposed audit record for this model could look like:<\/p>\n<pre>{\r\n     \"event\": \"resource.update\",\r\n     \"identity_mode\": \"standalone_agent\",\r\n     \"effective_subject\": \"spiffe:\/\/agents.example.org\/agent\/xyz\",\r\n     \"current_actor\": \"spiffe:\/\/agents.example.org\/agent\/xyz\",\r\n     \"authorization_principal\": \"agent-xyz\",\r\n     \"resource\": \"case\/4821\",\r\n     \"outcome\": \"success\",\r\n     \"request_id\": \"req-7ec15\",\r\n     \"agent_run_id\": \"run-93a2\"\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p>Here, no user\u2019s authority is involved. The application should evaluate its agent-specific policy, not silently substitute the permissions of the person who originally scheduled the job.<\/p>\n<p><strong>Model two: Lisa\u2019s authority, Agent XYZ\u2019s actions<\/strong><\/p>\n<p>Now suppose Lisa asks Agent XYZ to update a resource.<\/p>\n<p>The desired application semantics are:<\/p>\n<pre>Authorization subject: Lisa\r\nCurrent actor:\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Agent XYZ\r\nPermission source:\u00a0\u00a0\u00a0\u00a0 Lisa\u2019s rights for this application\r\nAudit attribution:\u00a0\u00a0\u00a0\u00a0 Lisa, acting through Agent XYZ<\/pre>\n<p>&nbsp;<\/p>\n<p>OAuth 2.0 Token Exchange, defined in RFC 8693, provides a protocol for requesting tokens with delegation semantics. Its act claim distinguishes the acting party from the token\u2019s subject.<\/p>\n<p><strong>\u201cActor token\u201d and \u201cactor claim\u201d are different things<\/strong><\/p>\n<p>The <strong>actor_token<\/strong> is an input to token exchange: evidence identifying the proposed actor.<\/p>\n<p>The <strong>act claim<\/strong> is information in the resulting token: the issuer\u2019s assertion identifying the current actor.<\/p>\n<p>In this design:<\/p>\n<ul>\n<li>The subject token represents Lisa.<\/li>\n<li>The actor token represents Agent XYZ.<\/li>\n<li>The issued access token retains Lisa as sub and identifies Agent XYZ in act.sub.<\/li>\n<\/ul>\n<p>A JWT-SVID can serve as actor evidence <strong>if the token service explicitly supports and trusts that profile<\/strong>. Its subject is the agent\u2019s SPIFFE ID, and its audience should identify the token service accepting it. A JWT-SVID is not, merely because it is a JWT, a user-delegated OAuth access token.<\/p>\n<p><strong>The exchange<\/strong><\/p>\n<p>The architecture is:<\/p>\n<pre>Lisa\u2019s authorized delegation\r\n+\r\nAgent XYZ\u2019s verified SPIFFE identity\r\n|\r\nv\r\nToken service \/ STS\r\n|\r\nv\r\nAccess token: subject=Lisa, actor=XYZ\r\n|\r\nv\r\nResource application<\/pre>\n<p>&nbsp;<\/p>\n<p>An illustrative token-exchange request contains these form fields; client authentication is handled separately under the token service\u2019s configured policy:<\/p>\n<pre>grant_type=urn:ietf:params:oauth:grant-type:token-exchange\r\nsubject_token=&lt;Lisa's eligible subject token&gt;\r\nsubject_token_type=urn:ietf:params:oauth:token-type:access_token\r\nactor_token=&lt;Agent XYZ's JWT-SVID&gt;\r\nactor_token_type=urn:ietf:params:oauth:token-type:jwt\r\nrequested_token_type=urn:ietf:params:oauth:token-type:access_token\r\naudience=case-management-api\r\nscope=cases.read cases.update<\/pre>\n<p>&nbsp;<\/p>\n<p>The request asks for delegation; it does not grant it. RFC 8693 also defines may_act, which can express eligibility to become an actor, but the issuer must still apply its delegation policy.<\/p>\n<p>For this implementation, require the STS to check:<\/p>\n<ul>\n<li>Lisa\u2019s token is valid and eligible for this exchange.<\/li>\n<li>The agent credential is valid for the STS and maps to an approved actor.<\/li>\n<li>Agent XYZ may act for Lisa in this application.<\/li>\n<li>The requested audience and permissions are permitted.<\/li>\n<li>The resulting actor identity comes from verified evidence, never a caller-supplied display name.<\/li>\n<\/ul>\n<p>These are proposed deployment requirements, not automatic behavior implied by accepting the exchange grant. They implement the issuer-side policy boundary described by RFC 8693.<\/p>\n<p><strong>The resulting access token<\/strong><\/p>\n<p>An illustrative decoded payload\u2014not a complete access-token profile\u2014is:<\/p>\n<pre>{\r\n     \"iss\": \"https:\/\/identity.example.org\",\r\n     \"sub\": \"user:lisa\",\r\n     \"aud\": \"case-management-api\",\r\n     \"client_id\": \"agent-xyz-client\",\r\n     \"scope\": \"cases.read cases.update\",\r\n     \"iat\": 1793325600,\r\n     \"exp\": 1793325900,\r\n     \"jti\": \"delegated-token-4b781\",\r\n     \"act\": {\r\n          \"sub\": \"spiffe:\/\/agents.example.org\/agent\/xyz\"\r\n     }\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p>The outer subject remains Lisa. The actor identifies Agent XYZ. client_id identifies the OAuth client and should not be assumed interchangeable with the actor identity.<\/p>\n<p><strong>Can it have all of Lisa\u2019s rights?<\/strong><\/p>\n<p>Yes\u2014as an <strong>explicit application policy<\/strong>, not as an inherent property of act.<\/p>\n<p>For the requested model, the application can evaluate its normal user authorization rules against Lisa while retaining Agent XYZ for attribution. The token must still permit the relevant resource and operation; adding an actor claim does not manufacture permissions or bypass audience restrictions.<\/p>\n<p>A useful implementation policy is:<\/p>\n<pre>Effective permissions =\r\nLisa\u2019s current application permissions\r\n\u2229 token-authorized operations\r\n\u2229 delegation policy<\/pre>\n<p>&nbsp;<\/p>\n<p>To preserve all of Lisa\u2019s application rights, the token and delegation policy must allow that set. For narrower automation, restrict them to the task. OAuth security guidance favors audience-restricted, least-privilege tokens rather than broadly reusable authority.<\/p>\n<p>Full-rights delegation has a deliberate consequence: compromising the authorized agent can expose the same application operations Lisa could perform. Actor attribution improves investigation; it is not a privilege boundary.<\/p>\n<p><strong>Programming applications to retain the actor<\/strong><\/p>\n<p>Issuing the claim is only half the implementation. The application must preserve it through authentication, authorization, execution, and audit.<\/p>\n<p>The following is a recommended application architecture.<\/p>\n<ol>\n<li><strong> Build a verified request identity<\/strong><\/li>\n<\/ol>\n<p>At the authentication boundary, validate the access token using the configured issuer, signature policy, intended audience, and temporal checks. Where the API adopts RFC 9068, apply that profile\u2019s validation requirements rather than merely decoding a JWT.<\/p>\n<p>Construct an immutable, request-scoped identity object with fields such as:<\/p>\n<pre>effective_subject\r\ncurrent_actor\r\nprior_actors\r\noauth_client_id\r\ntoken_issuer\r\ntoken_id\r\nauthorized_scopes\r\nauthenticated_transport_peer\r\ndelegation_id<\/pre>\n<p>&nbsp;<\/p>\n<p>Recommended rules:<\/p>\n<ul>\n<li>Extract the actor only from validated token claims or trusted introspection results.<\/li>\n<li>Keep issuer-qualified user identity; a bare sub is not necessarily globally unique.<\/li>\n<li>Preserve the SPIFFE trust domain in actor identifiers.<\/li>\n<li>Reject malformed actor structures.<\/li>\n<li>Do not infer \u201chuman request\u201d merely because act is absent.<\/li>\n<li>On delegated-only endpoints, require the expected actor information.<\/li>\n<\/ul>\n<p>These are application controls designed to make the RFC\u2019s subject\/actor distinction operational.<\/p>\n<ol start=\"2\">\n<li><strong> Separate permission evaluation from attribution<\/strong><\/li>\n<\/ol>\n<p>Business logic should receive both identities, even when authorization intentionally uses only Lisa\u2019s permissions.<\/p>\n<p>For example:<\/p>\n<pre>Permission lookup:\r\nsubject = user:lisa\r\n\r\nActivity attribution:\r\nsubject = user:lisa\r\nactor\u00a0\u00a0 = spiffe:\/\/agents.example.org\/agent\/xyz<\/pre>\n<p>&nbsp;<\/p>\n<p>Avoid collapsing this into one username field. In the proposed implementation, authorization receives the verified subject and relevant token constraints; audit receives the complete verified identity context.<\/p>\n<p>This separation preserves the purpose of the act claim: distinguishing delegated execution from acting directly as the subject.<\/p>\n<ol start=\"3\">\n<li><strong> Emit structured business events<\/strong><\/li>\n<\/ol>\n<p>A proposed event schema:<\/p>\n<pre>{\r\n     \"event\": \"case.update\",\r\n     \"identity_mode\": \"user_delegation\",\r\n     \"effective_subject\": {\r\n          \"issuer\": \"https:\/\/identity.example.org\",\r\n          \"id\": \"user:lisa\"\r\n     },\r\n     \"current_actor\": {\r\n          \"id\": \"spiffe:\/\/agents.example.org\/agent\/xyz\"\r\n     },\r\n     \"oauth_client_id\": \"agent-xyz-client\",\r\n     \"token_id\": \"delegated-token-4b781\",\r\n     \"delegation_id\": \"delegation-81a6\",\r\n     \"request_id\": \"req-a740\",\r\n     \"agent_run_id\": \"run-93a2\",\r\n     \"action\": \"case.update\",\r\n     \"resource\": \"case\/4821\",\r\n     \"authorization\": {\r\n          \"decision\": \"allow\",\r\n          \"permission_source\": \"subject\",\r\n          \"policy_version\": \"case-policy-17\"\r\n     },\r\n     \"outcome\": \"success\"\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p>The field names are a suggested schema, not SPIFFE or OAuth standards.<\/p>\n<p>For this schema, I would require:<\/p>\n<ul>\n<li>Both successful operations and denied attempts carry the identity context.<\/li>\n<li>The event records the affected business object and committed outcome.<\/li>\n<li>Tokens and private keys are excluded from logs.<\/li>\n<li>Agent-supplied run IDs are marked as correlation metadata, not authentication evidence.<\/li>\n<li>Database audit columns retain both modified_by_subject and modified_by_actor.<\/li>\n<\/ul>\n<p>A human-readable rendering can then say:<\/p>\n<pre>Lisa updated case 4821 through Agent XYZ.<\/pre>\n<p>&nbsp;<\/p>\n<p>The structured event retains the identifiers needed for investigation.<\/p>\n<ol start=\"4\">\n<li><strong> Carry the identity into background execution<\/strong><\/li>\n<\/ol>\n<p>Do not let request identity disappear when work moves to a queue or worker.<\/p>\n<p>In the proposed architecture, enqueue a protected job context referencing the verified subject, actor, delegation, and original request. At execution time, obtain or validate authority appropriate to the worker and operation; do not treat an arbitrary queue field such as &#8220;user&#8221;: &#8220;lisa&#8221; as permission to act.<\/p>\n<p>Also avoid retaining Lisa\u2019s identity in a mutable global variable: concurrent jobs must not share authorization or audit context.<\/p>\n<ol start=\"5\">\n<li><strong> Preserve delegation across service boundaries<\/strong><\/li>\n<\/ol>\n<p>For a chain such as:<\/p>\n<pre>Lisa \u2192 Agent XYZ \u2192 Tool service \u2192 Resource API<\/pre>\n<p>&nbsp;<\/p>\n<p>RFC 8693 permits nested actor claims. The outermost actor is the current actor; nested actors represent earlier actors. Previous actors are informational and must not become an alternative source of permissions.<\/p>\n<p>An illustrative claim at the resource API might be:<\/p>\n<pre>{\r\n     \"sub\": \"user:lisa\",\r\n     \"act\": {\r\n          \"sub\": \"spiffe:\/\/services.example.org\/tool\/case-editor\",\r\n          \"act\": {\r\n               \"sub\": \"spiffe:\/\/agents.example.org\/agent\/xyz\"\r\n          }\r\n     }\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p>For this design, log the current actor and retained history, and correlate each hop. Never authorize by taking the union of every actor\u2019s permissions.<\/p>\n<p><strong>Attribution is not proof that the actor presented the token<\/strong><\/p>\n<p>A signed act claim says the issuer associated Agent XYZ with the delegated token. A bearer token can nevertheless be presented by whoever possesses it. Actor attribution alone does not prevent replay.<\/p>\n<p>For stronger attribution, use sender-constrained access tokens where supported. RFC 8705 binds a token to an mTLS client certificate through cnf.x5t#S256; the resource server verifies that the presenting certificate matches the binding.<\/p>\n<p>With rotating SPIFFE certificates, that binding is to the <strong>specific certificate<\/strong>, not simply its SPIFFE ID. The implementation must coordinate access-token renewal and certificate rotation. TLS termination must also preserve a trustworthy validation boundary; an untrusted forwarded certificate header is not equivalent to an authenticated peer. These follow from RFC 8705\u2019s certificate-binding mechanism.<\/p>\n<p><strong>The takeaway<\/strong><\/p>\n<p>For agent identity, keep three questions separate:<\/p>\n<ol>\n<li><strong>Which workload is this?<\/strong> SPIFFE provides the verifiable identity.<\/li>\n<li><strong>Whose authority is being exercised?<\/strong> Delegated tokens preserve the user as subject.<\/li>\n<li><strong>Which software exercised that authority?<\/strong> The actor claim preserves attribution.<\/li>\n<\/ol>\n<p>Then make the application record all three at the business-event boundary.<\/p>\n<p>The goal is not merely an access token containing Agent XYZ\u2019s name. It is an authorization and audit model that can reliably distinguish <strong>Lisa acting directly<\/strong>, <strong>Agent XYZ acting independently<\/strong>, and <strong>Agent XYZ exercising Lisa\u2019s delegated authority<\/strong>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>An agent can perform an operation under its own authority or perform the same operation on behalf of a person. Those are different security events. \u201cAgent XYZ modified this resource\u201d identifies a machine principal. \u201cAgent XYZ modified this resource using Lisa\u2019s authority\u201d identifies both an authorization subject and an acting party. SPIFFE supplies the agent\u2019s &hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2243],"tags":[2246,2244,2216,2245],"class_list":["post-12628","post","type-post","status-publish","format-standard","hentry","category-spiffe","tag-agent-identity","tag-spiffe","tag-spire","tag-workload-identity"],"_links":{"self":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts\/12628","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=12628"}],"version-history":[{"count":2,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts\/12628\/revisions"}],"predecessor-version":[{"id":12631,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts\/12628\/revisions\/12631"}],"wp:attachment":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=12628"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=12628"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=12628"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}