Tag: SPIRE

SPIFFE: Prove the Configuration Rejects Abuse: Negative Security Testing

A security architecture should be judged by rejection behavior, not only by a successful demonstration. Each test below states a precondition, an enforcement point, and evidence to collect. The exact HTTP status or OAuth error depends on the failing stage and implementation; do not require a universal error code where the standards or product do not prescribe one.

Test environment and positive controls

Use isolated non-production identities and resources. Establish a successful standalone call and a successful Lisa/XYZ delegated call first. Capture the issued token’s approved claims without copying credentials into ordinary logs. Confirm the actual policy version and key material in use. Change one condition per test where possible, and retain request correlation so failures can be attributed to the intended control.

Where a deployment does not implement a proposed control, mark the corresponding test as an identified gap or accepted risk, not a false pass. Sender-constraint tests require a genuinely supported profile. Task-authorization tests require the resource server to implement that policy.

ID Input or precondition Enforcement point Expected outcome/evidence
01 Actor token from untrusted issuer STS actor validation Reject; log issuer-policy failure
02 Actor JWT-SVID with wrong STS audience STS actor validation Reject; intended-recipient failure
03 Human JWT substituted as actor evidence STS profile validation Reject; cross-profile failure
04 Workload JWT substituted as human subject STS subject validation Reject; subject-profile failure
05 Unsigned or disallowed-algorithm JWT STS/API signature policy Reject; algorithm/signature failure
06 Token with attacker-controlled key URL Configured key-resolution boundary No outbound trust in supplied URL; reject invalid signature
07 Expired input token STS time validation Reject; expiration failure
08 Input not yet valid outside allowed skew STS time validation Reject; temporal-policy failure
09 Output token presented to wrong API Resource audience validation Reject; audience mismatch
10 Forged act claim on signed token Resource signature validation Reject; modified token
11 Caller actor override differs from verified identity STS mapping/policy Reject or ignore override under explicit policy; no forged attribution
12 Client/actor pairing forbidden by policy STS relationship policy Reject; pairing decision recorded
13 XYZ paired with ineligible Lisa delegation STS approval/pairing policy Reject; subject/actor eligibility failure
14 Unauthorized output audience requested STS target policy Reject; disallowed exchange edge
15 Scopes exceed approved grant STS scope policy Reject or explicitly downscope; record granted set
16 Missing actor on delegated-only API API delegated-profile policy Reject; required-actor failure
17 Malformed nested actor structure STS/API schema validation Reject; malformed identity context
18 Nested actor has privileged identity API authorization engine No privilege union; deny operation not allowed by top-level context
19 Valid token mutates wrong case API object authorization Reject; resource mismatch
20 Valid token changes forbidden field API mutation authorization Reject; actual-body constraint failure
21 Approval expired before exchange STS approval service Reject; stale grant
22 Approval revoked after token issuance API current-grant control if configured Reject within measured distribution/cache window
23 One-time authorization reused API transactional grant control No second mutation; atomic use evidence
24 Stolen bearer token presented elsewhere Resource authentication May succeed absent sender constraint; document residual risk
25 Wrong certificate for bound token mTLS/token-binding verifier Reject; certificate-thumbprint mismatch
26 Wrong DPoP key or token hash DPoP-capable resource Reject; proof/token binding failure
27 Replayed DPoP proof DPoP replay/freshness verifier Reject per deployed replay policy; proof evidence
28 Unknown kid during discovery outage STS/API key validation Reject unknown key; bounded refresh attempts
29 Planned key rollover with old/new tokens STS/API verification caches Valid intended tokens pass during planned overlap
30 Disabled actor uses cached valid JWT Resource deny/revocation controls Measure rejection latency; no unsupported instant-revocation claim
31 Workload does not match registration selectors SPIRE workload issuance No XYZ credential; attestation/issuance evidence
32 Unrelated pod chooses XYZ service account Admission/RBAC plus SPIRE policy Blocked deployment or deny issuance; otherwise record privilege gap
33 Unauthorized node attempts enrollment SPIRE node attestation No accepted eligible agent identity
34 Queued job runs after delegation revoked Worker authorization boundary Reject execution; log job and approval IDs
35 Tenant/resource identifier substitution STS and resource tenant policy Reject cross-tenant access
36 Policy/STS outage triggers retry fallback Application/worker failure path No anonymous or broader-authority fallback

 

Acceptance criteria

  • Every failure occurs at a documented boundary and emits useful non-secret audit evidence.
  • No test silently changes a delegated request into impersonation or anonymous access.
  • No requested audience or scope becomes trusted merely because it was requested.
  • Resource-level constraints are checked against the actual operation and canonical object.
  • Revocation and outage tests report measured windows, not assumed guarantees.
  • Concurrent one-time execution tests demonstrate atomic behavior.
  • Known valid controls still pass after policy tightening and planned key rotation.

Adversarial execution and evidence

Repeat tests with concurrency, stale caches, alternate replicas, and retrying workers. Exercise non-happy-path serialization and identifier normalization. Confirm that every API instance uses the same intended policy version. Capture both denial and business-state evidence: a rejected response is insufficient if a mutation occurred before the rejection.

Takeaway: a useful test report says where abuse was blocked, why, what residual exposure remains, and how quickly changes reach every enforcement point.

SPIFFE: Rotation Without Outages: Credential and Trust-Material Lifecycle

Automatic rotation removes manual renewal work, not lifecycle complexity. Your acceptance tests must cover planned rotation, unknown signing keys, transient outages, and the interaction between credentials issued at different layers.

Separate the trust-material paths

X.509-SVID verification uses certificate trust material. JWT-SVID verification uses the corresponding JWT signing public keys. An OIDC discovery/JWKS integration can expose JWT verification metadata for consumers, but an X.509 bundle is not interchangeable with a JWKS document. Document exactly which discovery component is deployed and how PingFederate trusts it.

Review discovery issuer association, TLS trust, permitted metadata endpoints, cache limits, and key rollover behavior. A kid identifies a candidate key; it does not make a key trustworthy. Do not follow arbitrary jku or x5u values from untrusted token input. Bind verification material to configured issuers.

Plan overlap and bounded refresh

Recommended sequence: publish the new verification key, allow intended verifiers to acquire it, begin signing with it, and retain old verification material for the remaining lifetime of valid old tokens plus permitted skew and bounded propagation delays. Confirm the actual signing-key publisher behavior rather than assuming it follows this sequence. Emergency compromise rollover requires a different plan because retaining the old key preserves trust in forged tokens.

On unknown kid, perform a bounded, issuer-specific refresh if supported. If the key remains unknown, deny the token. Rate-limit and coalesce refreshes to prevent attacker-controlled kid values from turning verification into a discovery-service denial of service. Valid cached material may remain usable within a documented policy; this is not permission to extend token expiration or trust unknown keys.

Failure and recovery matrix

Condition Recommended behavior Evidence to collect
SPIRE unavailable No fabricated credentials; use only still-valid material under policy Issuance failures and remaining lifetime
STS unavailable Do not bypass exchange or broaden authority Exchange failures and job/backoff behavior
Discovery unavailable Bounded cache use; reject unknown keys Cache age and refresh outcomes
Clock skew Enforce reviewed bounded leeway Clock offset and time-validation failures
Old signing key removed Observe rejected old credentials; follow planned policy Affected kid and issuer
Policy distribution unavailable Explicit fail behavior, never implicit permit Policy version and stale-state duration

 

Sender binding complicates credential rotation

RFC 8705 certificate-bound access tokens use the thumbprint of a specific client certificate. Binding to an X.509-SVID certificate therefore binds that token to that particular certificate, not merely to its stable SPIFFE ID. Coordinate token renewal and certificate rotation, and avoid a gap where only a replacement certificate remains but the access token expects the old one.

DPoP is an alternative sender-constraint mechanism only where the issuer and resource server support the agreed profile. Its key lifecycle is separate from SPIRE certificate rotation. Do not imply that configuring SPIRE automatically enables DPoP in PingFederate or a downstream API.

Connections and authorization lifetime

Established TLS connections and application sessions need their own lifecycle review. A certificate rollover is not guaranteed to trigger a new authentication handshake on every existing connection. Decide whether token authorization is evaluated per request, once per connection, or when a session is created; verify the implementation. Connection draining and maximum session duration may be needed for predictable containment.

  • Exercise a planned rotation under normal load.
  • Test new-key visibility before switching signers.
  • Measure clock offsets and verification-cache behavior.
  • Simulate dependency outages without allowing permissive fallbacks.
  • Prove emergency issuer compromise response separately from routine rollover.

Takeaway: rotation must be tested as a distributed protocol, including consumers and their caches.

SPIFFE: Can We Reconstruct What Happened? Audit and Investigation

A useful audit trail must reconstruct whose authority applied, which software acted, what was approved, and what actually changed. A single username field cannot represent that model.

Create a verified request-scoped identity

At authentication, construct an immutable context from validated claims or trusted introspection: subject with issuer, current actor, prior actor history, OAuth client, transport peer, delegation reference, token identifier, granted constraints, and policy version. Propagate it through authorization and business-event logging. Do not use a mutable global user context across concurrent requests or workers.

For delegated tokens, the outermost act identifies the current actor; nested actors represent earlier actors. Prior actors are informational rather than an additional permission source. Preserve history for traceability without authorizing from a union of actor privileges.

A proposed structured business event

{
"event": "case.update",
"identity_mode": "user_delegation",
"effective_subject": {
"issuer": "https://identity.example.org",
"id": "user:lisa"
},
"current_actor": {
"id": "spiffe://agents.example.org/agent/xyz"
},
"transport_peer": "spiffe://services.example.org/tool/case-editor",
"oauth_client_id": "agent-xyz-client",
"delegation_id": "approval-81a6",
"token_id": "token-4b781",
"request_id": "req-a740",
"agent_run_id": "run-93a2",
"resource": "case/4821",
"authorization": {
"decision": "allow",
"policy_version": "case-policy-17"
},
"outcome": "committed"
}

These field names are recommendations, not a standards-defined event schema. A run ID helps correlation but does not prove identity. A token ID helps join events but does not make a token single-use. One-time semantics require protected state and atomic consumption at the appropriate transaction boundary.

One approval, five API calls

Call Observed behavior Expected investigation conclusion
1 Read case/4821 under approved read authority Authorized read, if included in approval
2 Update status on case/4821 Authorized mutation and committed result
3 Update another field on case/4821 Denied field-level violation
4 Update case/9920 Denied resource mismatch
5 Repeat one-time status update Denied replay or recorded idempotent retry under policy

 

Join the approval event, STS decision, API decision, and business commit. A 200 response in an agent trace does not independently prove a database commit. An authorization allow does not prove the operation succeeded. Conversely, a failed response after a commit can cause a retry; the audit model must distinguish this from deliberate duplicate execution.

Background work and provenance

Queue messages should reference a protected grant and carry authenticated provenance. Re-evaluate permission when the worker executes. Record the originating agent and current worker separately, because the worker may become the current actor in a new token exchange. Preserve request and job correlation without trusting identity fields supplied by arbitrary producers.

Mark log fields by provenance: token-verified, authorization-service-issued, infrastructure-derived, or application-supplied. This prevents an agent-provided display name from being mistaken for the verified SPIFFE actor. Use stable identifiers for joins and display names only for presentation.

Logging controls

  • Never log bearer tokens, private keys, or full authorization headers.
  • Redact business payloads and approval details according to sensitivity.
  • Record denied attempts as well as successful operations.
  • Protect log transport, administrative access, and retention configuration.
  • Synchronize clocks and retain event time separately from ingestion time.
  • Version audit schemas and ensure downstream parsers preserve nested identity context.
  • Record configuration/policy changes so investigators can reconstruct the decision in force.

Takeaway: attribution becomes useful only when it survives all the way to the committed business event.

SPIRE Setup Documentation

Overview

This setup deploys SPIRE as follows:

  • SPIRE Server on <SPIRE_SERVER_HOST>
  • SPIRE Agent on <SPIRE_AGENT_HOST>
  • Trust domain: <TRUST_DOMAIN>
  • Server/agent communication port: <SERVER_PORT>/tcp

How it works

SPIRE provides machine and workload identity.

The SPIRE Server on <SPIRE_SERVER_HOST> is the trust authority for the trust domain <TRUST_DOMAIN>.

The SPIRE Agent on <SPIRE_AGENT_HOST> attests to the server using x509pop with an X.509 certificate issued by an enterprise/internal CA.

Applications on <SPIRE_AGENT_HOST> do not talk directly to the SPIRE Server. They talk to the local SPIRE Agent over the local workload API socket <AGENT_SOCKET_PATH>.

The agent returns an X.509-SVID representing the workload identity.

Installation Instructions

  1. Install SPIRE binaries

Run on both hosts:

mkdir -p /opt/spire
cd /tmp
wget https://github.com/spiffe/spire/releases/download/v1.15.2/spire-1.15.2-linux-amd64-musl.tar.gz
tar zxf spire-1.15.2-linux-amd64-musl.tar.gz
cp -r spire-1.15.2/. /opt/spire/

On the SPIRE Server host:

ln -sf /opt/spire/bin/spire-server /usr/bin/spire-server

On the SPIRE Agent host:

ln -sf /opt/spire/bin/spire-agent /usr/bin/spire-agent

  1. Configure SPIRE Server

Create directories:

mkdir -p /opt/spire/conf
mkdir -p /opt/spire/data/server
mkdir -p /opt/spire/conf/x509pop

Place the CA bundle at:

/opt/spire/conf/x509pop/bundle.pem

Contents:

—–BEGIN CERTIFICATE—–
<REDACTED CA CERTIFICATE>
—–END CERTIFICATE—–
—–BEGIN CERTIFICATE—–
<REDACTED CA CERTIFICATE>
—–END CERTIFICATE—–
—–BEGIN CERTIFICATE—–
<REDACTED CA CERTIFICATE>
—–END CERTIFICATE—–

Create /opt/spire/conf/server.conf:

server {
bind_address = “0.0.0.0”
bind_port = “<SERVER_PORT>”
trust_domain = “<TRUST_DOMAIN>”
data_dir = “/opt/spire/data/server”
log_level = “INFO”
}

plugins {
DataStore “sql” {
plugin_data {
database_type = “sqlite3”
connection_string = “/opt/spire/data/server/datastore.sqlite3”
}
}

NodeAttestor “x509pop” {
plugin_data {
ca_bundle_path = “/opt/spire/conf/x509pop/bundle.pem”
}
}

KeyManager “memory” {
plugin_data {}
}
}

health_checks {
listener_enabled = true
bind_address = “127.0.0.1”
bind_port = “<HEALTH_PORT>”
}

Create /etc/systemd/system/spire-server.service:

[Unit]
Description=SPIRE Server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/opt/spire/bin/spire-server run -config /opt/spire/conf/server.conf
Restart=on-failure
RestartSec=5
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

Start server:

systemctl daemon-reload
systemctl enable –now spire-server
systemctl status spire-server –no-pager

Validate server:

spire-server healthcheck

  1. Configure SPIRE Agent

Create directories:

mkdir -p /opt/spire/conf
mkdir -p /opt/spire/data/agent
mkdir -p /opt/spire/sockets
mkdir -p /opt/spire/conf/x509pop

Issue a certificate through your enterprise PKI platform. Download as OpenSSL format and split CRT/KEY files. Copy the node certificate to:

/opt/spire/conf/x509pop/agent.crt

Copy the node private key to:

/opt/spire/conf/x509pop/agent.key

The private key must be unencrypted PEM.

Set permissions:

chmod 700 /opt/spire/conf/x509pop
chmod 600 /opt/spire/conf/x509pop/agent.key
chmod 644 /opt/spire/conf/x509pop/agent.crt

Create /opt/spire/conf/agent.conf:

agent {
data_dir = “/opt/spire/data/agent”
log_level = “INFO”
trust_domain = “<TRUST_DOMAIN>”
server_address = “<SPIRE_SERVER_HOST>”
server_port = “<SERVER_PORT>”
socket_path = “<AGENT_SOCKET_PATH>”
insecure_bootstrap = true
}

plugins {
KeyManager “disk” {
plugin_data {
directory = “/opt/spire/data/agent”
}
}

WorkloadAttestor “unix” {
plugin_data {}
}

NodeAttestor “x509pop” {
plugin_data {
private_key_path = “/opt/spire/conf/x509pop/agent.key”
certificate_path = “/opt/spire/conf/x509pop/agent.crt”
}
}
}

Create /etc/systemd/system/spire-agent.service:

[Unit]
Description=SPIRE Agent
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/opt/spire/bin/spire-agent run -config /opt/spire/conf/agent.conf
Restart=on-failure
RestartSec=5
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

Start agent:

systemctl daemon-reload
systemctl enable –now spire-agent
systemctl status spire-agent –no-pager

  1. Validate x509pop agent attestation

On the SPIRE Server host:

spire-server agent list

Expected result:

  • Agent attestation type is x509pop
  • Can re-attest is true
  • Parent ID format resembles:

spiffe://<TRUST_DOMAIN>/spire/agent/x509pop/<AGENT_HASH>

  1. Create workload registration entry

Use the current x509pop agent SPIFFE ID from spire-server agent list.

On the SPIRE Server host:

spire-server entry create \
-spiffeID spiffe://<TRUST_DOMAIN>/workload/<WORKLOAD_NAME> \
-parentID spiffe://<TRUST_DOMAIN>/spire/agent/x509pop/<AGENT_HASH> \
-selector unix:uid:0

This authorizes a root-owned process on the SPIRE Agent host.

  1. Fetch workload identity on the SPIRE Agent host

/opt/spire/bin/spire-agent api fetch x509 -socketPath <AGENT_SOCKET_PATH>

Expected SPIFFE ID:

spiffe://<TRUST_DOMAIN>/workload/<WORKLOAD_NAME>

  1. Write certs to disk for testing

Create destination directory:

mkdir -p /etc/spire/svid/test
chmod 700 /etc/spire/svid/test

Write files:

/opt/spire/bin/spire-agent api fetch x509 \
-socketPath <AGENT_SOCKET_PATH> \
-write /etc/spire/svid/test

Inspect output:

ls -l /etc/spire/svid/test

openssl x509 -in /etc/spire/svid/test/svid.0.pem -text -noout

  1. Reboot persistence validation

Reboot the SPIRE Agent host.

After reboot, validate:

systemctl status spire-agent –no-pager

ls -l <AGENT_SOCKET_PATH>

/opt/spire/bin/spire-agent api fetch x509 -socketPath <AGENT_SOCKET_PATH>

Expected behavior:

  • spire-agent starts automatically
  • Workload API socket exists
  • X.509-SVID fetch succeeds
  1. Operational notes
  • Current architecture: <SPIRE_SERVER_HOST> = SPIRE Server; <SPIRE_AGENT_HOST> = SPIRE Agent
  • Current trust domain: <TRUST_DOMAIN>
  • Current server/agent path: <SPIRE_AGENT_HOST> to <SPIRE_SERVER_HOST> on TCP <SERVER_PORT>
  • Current Workload API socket: <AGENT_SOCKET_PATH>
  • Current example workload selector: unix:uid:0
  • Current example workload identity: spiffe://<TRUST_DOMAIN>/workload/<WORKLOAD_NAME>

JWT

Create JWT registration on the SPIRE Server host:

spire-server entry create \
-spiffeID spiffe://<TRUST_DOMAIN>/workload/<JWT_WORKLOAD_NAME> \
-parentID spiffe://<TRUST_DOMAIN>/spire/agent/x509pop/<AGENT_HASH> \
-selector unix:uid:0

Fetch JWT-SVID from the SPIRE Agent host:

/opt/spire/bin/spire-agent api fetch jwt \
-socketPath <AGENT_SOCKET_PATH> \
-audience <JWT_AUDIENCE> \
-spiffeID spiffe://<TRUST_DOMAIN>/workload/<JWT_WORKLOAD_NAME>

Example output:

token(spiffe://<TRUST_DOMAIN>/workload/<JWT_WORKLOAD_NAME>):
<REDACTED JWT-SVID>

bundle(spiffe://<TRUST_DOMAIN>):
<REDACTED JWKS BUNDLE>

SPIRE OIDC Discovery Provider

On the SPIRE Server host:

mkdir -p /opt/spire-extras
cd /tmp
wget https://github.com/spiffe/spire/releases/download/v1.15.2/spire-extras-1.15.2-linux-amd64-musl.tar.gz
tar zxf spire-extras-1.15.2-linux-amd64-musl.tar.gz
cp -r spire-extras-1.15.2/ /opt/spire-extras/

ln -sf /opt/spire-extras/bin/oidc-discovery-provider /usr/bin/oidc-discovery-provider
mkdir -p /opt/spire-extras/conf/oidc-discovery-provider

Create certificate and key files:

/opt/spire-extras/conf/oidc-discovery-provider/tls.crt
/opt/spire-extras/conf/oidc-discovery-provider/tls.key

Set permissions:

chmod 644 /opt/spire-extras/conf/oidc-discovery-provider/tls.crt
chmod 600 /opt/spire-extras/conf/oidc-discovery-provider/tls.key

Create /opt/spire-extras/conf/oidc-discovery-provider/oidc-discovery-provider.conf:

log_level = “INFO”

domains = [“<OIDC_DISCOVERY_DOMAIN>”]

server_api {
address = “unix://<SPIRE_SERVER_API_SOCKET>”
}

serving_cert_file {
cert_file_path = “/opt/spire-extras/conf/oidc-discovery-provider/tls.crt”
key_file_path = “/opt/spire-extras/conf/oidc-discovery-provider/tls.key”
}

Create /etc/systemd/system/spire-oidc-discovery-provider.service:

[Unit]
Description=SPIRE OIDC Discovery Provider
After=network-online.target spire-server.service
Wants=network-online.target

[Service]
Type=simple
ExecStart=/opt/spire-extras/bin/oidc-discovery-provider -config /opt/spire-extras/conf/oidc-discovery-provider/oidc-discovery-provider.conf
Restart=on-failure
RestartSec=5
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

Ping Integration

PingFederate Integration Note for SPIRE JWT Validation

Purpose

Configure PingFederate to trust and validate JWTs issued from the SPIRE environment.

SPIRE issuer details

Use these values:

  • Issuer: https://<OIDC_DISCOVERY_DOMAIN>
  • OIDC discovery URL: https://<OIDC_DISCOVERY_DOMAIN>/.well-known/openid-configuration
  • JWKS URL: https://<OIDC_DISCOVERY_DOMAIN>/keys

Trust model

PingFederate should validate JWT signatures using the JWKS published by the SPIRE OIDC Discovery Provider.

Ping does not need to call the SPIRE server directly for every token validation. It should use the discovery/JWKS metadata from the OIDC Discovery Provider.

Expected JWT characteristics

Issuer

Ping should require:

iss = https://<OIDC_DISCOVERY_DOMAIN>

Audience

Recommended audience value:

<PING_AUDIENCE>

Clients requesting JWT-SVIDs from SPIRE should request them with this audience.

Subject

The workload identity will be in:

sub

Example:

spiffe://<TRUST_DOMAIN>/workload/<WORKLOAD_NAME>

This is the primary identity claim Ping should use to identify the calling workload.

Recommended validation rules in Ping

Validate:

  • JWT signature against SPIRE JWKS
  • iss matches https://<OIDC_DISCOVERY_DOMAIN>
  • aud contains <PING_AUDIENCE>
  • token is within validity window (exp, iat)
  • sub is an allowed SPIFFE ID or matches allowed policy rules

Example workload identity currently in use

Current example SPIFFE ID:

spiffe://<TRUST_DOMAIN>/workload/<WORKLOAD_NAME>

Client-side JWT retrieval model

A workload on the SPIRE Agent host should obtain its JWT from the local SPIRE agent, not from the SPIRE server directly.

Local agent socket:

<AGENT_SOCKET_PATH>

Example operational flow

  1. Workload on the SPIRE Agent host requests a JWT-SVID from the local SPIRE agent.
  2. JWT-SVID is issued with:
  • issuer = https://<OIDC_DISCOVERY_DOMAIN>
  • audience = <PING_AUDIENCE>
  • subject = workload SPIFFE ID
  1. Workload presents JWT to PingFederate.
  2. PingFederate validates the JWT using SPIRE OIDC discovery/JWKS.
  3. PingFederate maps the SPIFFE workload identity to access policy, token issuance, or downstream application authorization.

 

 

SPIFFE: Stopping an Agent: Expiry, Revocation, and Incident Containment

Disabling an agent is not one operation. The agent may possess an SVID, an OAuth token, an established connection, a queued job, and a refresh-capable authorization context. Incident containment must address each separately.

Credential lifecycle is not a revocation chain

Token exchange does not inherently consume the input credential. It also does not guarantee that revoking the subject or actor token invalidates every token previously issued from it. Build explicit relationships if downstream invalidation is required, and test them.

Disabling future issuance differs from rejecting existing credentials. A resource server validating a self-contained token from cached public keys may not learn that XYZ was disabled until some separate policy update reaches it. Introspection can expose current authorization-server state, but cache duration and resource-server behavior still determine enforcement latency. Revocation endpoint behavior must be understood for the actual token type and deployment.

An illustrative containment timeline

Time Action What may remain usable
T+0 Disable agent deployment and isolate compromised runtime Stolen credentials elsewhere; active requests
T+1 minute Deny XYZ at STS and stop relevant identity issuance Previously issued tokens and SVIDs
T+2 minutes Push resource-server denial for actor/delegation Caches and requests beyond authorization check
T+3 minutes Cancel jobs and review active sessions/connections Committed changes; remote services not yet updated
T+token lifetime Remaining token expiry reached Long-lived sessions if independently authorized

 

Times are examples, not product guarantees. A hypothetical five-minute access token with a one-minute cache does not imply six-minute universal containment: an already-authorized long-running operation or independently created session may last longer. Measure the actual behavior.

Containment runbook

  • Preserve relevant identities, token IDs, approvals, policy versions, and execution evidence; do not collect raw credentials into ordinary logs.
  • Isolate the runtime and prevent replacement workloads from reacquiring the same identity.
  • Deny further exchange for the affected actor and revoke appropriate approval/refresh contexts.
  • Apply API-level actor or delegation denial where supported; distribute it to every enforcement point.
  • Suspend queued jobs and prevent a retry path from resurrecting authority.
  • Determine whether active requests, sessions, or connections require termination.
  • Review completed mutations and trigger business rollback or compensating actions where needed.
  • Verify containment using previously issued credentials, not only new login attempts.

Avoid issuer-wide collateral damage

Removing an entire trust bundle or issuer can disrupt every workload under that authority and may still fail to stop cached or already-authorized activity immediately. Prefer a targeted actor/delegation deny mechanism when the issuer itself is not compromised. If signing authority is compromised, broader trust removal and key replacement may be justified; that is a different incident class.

An emergency deny-list is a proposed supplemental control. Define its authenticated distribution, cache lifetime, availability behavior, administrative ownership, and effect on already-running operations. A deny-list that is unavailable must not silently disappear from policy.

Recovery criteria

Recover the underlying workload and deployment authority before re-enabling issuance. Verify that the compromise cannot satisfy selectors again, clear unauthorized approvals, restore reviewed configuration, and rotate compromised keys where relevant. A new run identifier is not a remediation boundary if the same compromised workload can obtain the same identity.

Takeaway: the practical containment window is determined by every credential and enforcement cache, not by the shortest advertised token lifetime.

SPIFFE: Authenticated Agent, Malicious Instructions: Identity Versus Agent Safety

An authenticated agent can still be manipulated. Identity controls must work even when the workload holds valid credentials and makes a harmful request. The useful threat model is therefore not only an outsider forging XYZ’s token; it is XYZ itself attempting an operation Lisa did not approve.

A worked prompt-injection scenario

Lisa asks XYZ to resolve case 4821. XYZ retrieves a document containing instructions to export customer records and send them to another service. The agent’s runtime, SPIFFE identity, and signing authority are unchanged. If its delegated token permits broad exports, authentication can succeed throughout the attack. The failure is excessive or insufficiently enforced authority, not a broken signature.

There are three independent constraints to examine: the target application, the operation within that application, and the specific object or mutation. An audience restriction can prevent use at payroll while doing nothing to stop a broad export at the authorized case API. A write scope may permit more than Lisa’s intended single-field update.

Task authorization outside the agent’s assertions

Recommended pattern: a trusted authorization interaction records Lisa’s approval, the authorized agent, target, resource, permitted actions, field-level constraints, expiration, and any use count. At exchange, consult that record rather than accepting an agent-supplied statement that Lisa approved. At execution, compare the actual request with the approved constraints. Approval must not be editable through the same untrusted content channel that proposes tool actions.

{
    "authorization_details": [
    {
        "type": "case_operation",
        "actions": [
             "update"
        ],
        "case_ids": [
            "4821"
        ],
        "allowed_fields": [
            "status"
        ],
        "allowed_status_values": [
            "resolved"
        ]
    }
  ]
}

The array structure illustrates RFC 9396 authorization_details. The case_operation type and its fields are custom; issuer and API must agree on semantics. Rich Authorization Requests do not automatically make these constraints native to RFC 8693 exchange or to every IdP release. A protected approval identifier plus server-side enforcement may be more practical than embedding all constraints.

Threats, controls, and residual risk

Threat Recommended control Residual risk
Injected tool instructions Independent task/resource authorization Allowed actions can still be abused within their bounds
Requested target substitution STS audience-transition allowlist Approved target might itself be overprivileged
Confused deputy tool Bind delegated context to actual operation Tool implementation bugs
Stolen bearer credential Supported sender-constrained token Key theft or compromised authorized runtime
Agent manufactures approval Approval issued outside agent authority Approval-service compromise or deceptive consent UI
Repeated approved mutation Atomic use-count enforcement Retry and transaction semantics require design

 

What possession proofs do not prove

Sender constraint makes token use depend on a presenting key. It does not prove that Lisa requested the operation or that the key holder has safe reasoning. DPoP binds proofs to HTTP method and URI and, for protected-resource calls, the token; it does not generally bind the business request body. Application authorization must validate the actual mutation independently.

Full-rights delegation is a conscious risk decision. If XYZ can exercise every operation Lisa can exercise in the application, a surprising request may still be authorized by policy. Neither an actor claim nor additional authentication can recover a narrower intention never represented in the grant.

Engineering acceptance questions

  • Can injected content change approval records, audiences, or requested scopes?
  • Does the API validate the actual object and body rather than only token claims?
  • Which destructive actions require fresh approval or step-up?
  • Are tool-provided identifiers canonicalized before policy comparison?
  • Can an authorized agent repeatedly execute a nominally one-time task?

Takeaway: workload identity authenticates the execution boundary. Task authorization constrains what that boundary may do.

SPIFFE: Trusted Issuer Does Not Mean Trusted for Everything: Validation and Claim Mapping

A trusted signature answers a narrow question: did an accepted signing authority authenticate this token’s contents? It does not answer whether this token type belongs at this endpoint, whether its subject is eligible for exchange, or whether the requested target should trust the resulting authority.

Build explicit token-processing contracts

Use separate input contracts for human subject evidence and agent actor evidence. For JWT inputs, establish allowed issuers, issuer-associated verification keys, algorithms, expected profile, intended audience, required claims, and temporal rules. Do not select trust using an arbitrary key URL from the submitted token. JWT security guidance stresses algorithm verification, issuer/audience validation, and mutually exclusive rules where different token kinds could otherwise be confused.

An actor JWT-SVID and a human access token may both be signed JWTs, but they are not interchangeable. A user JWT should not satisfy the agent processor because it has a sub field. A workload credential should not become a human subject merely because its signature verifies.

IdP configuration review

Review the actual subject processor, actor processor, exchange policy, access token manager, and OAuth client configuration together. Verify the installed release’s behavior and extension capabilities. This guide does not prescribe unverified administrator UI paths or claim that all task constraints are natively implemented in an access token manager.

Verified input Policy decision Output
User issuer and stable subject Eligible user and tenant Issuer-qualified user subject mapping
Actor issuer, SPIFFE subject, intended audience Approved agent identity act.sub from verified actor evidence
Authenticated OAuth client Permitted client/actor relationship client_id under issuer control
Requested audience/resource Allowed exchange edge Approved target audience
Requested scopes Allowed operations and delegation limits Granted scopes, never blind copying
Protected approval record Task/resources still approved Reference or enforced task constraints

 

An illustrative policy contract

accept actor evidence only from configured agents.example.org authority
map verified SPIFFE subject → current actor
require approved (client, actor, user, tenant) relationship
permit exchange only to reviewed resource identifiers
grant permissions within subject rights AND delegation constraints
require current approval for high-impact tasks
reject caller-provided actor overrides
record policy decision and output token identifier

Input audiences and output validation

An exchange can legitimately transform a credential intended for one context into authority for another. Therefore, define which input contexts the STS accepts and which output targets each exchange relationship permits. Do not accept arbitrary audiences simply because the token was issued by a trusted issuer. Do not assume an API-only token is automatically an eligible exchange credential.

Resource servers must independently validate the issued access token. If using the JWT access-token profile, RFC 9068 supplies recipient validation requirements, including token typing and audience checks. An opaque-token deployment instead depends on its agreed introspection profile. Neither approach removes application authorization.

The act claim records delegated actor attribution. may_act expresses actor eligibility, not universal permission or task approval. A submitted actor name must never override the identity obtained from verified evidence.

Review evidence

  • Export effective configuration, not only screenshots of one processor.
  • Demonstrate rejection of cross-profile tokens and unauthorized audience transitions.
  • Test omitted, malformed, and unexpected actor claims.
  • Verify scopes are granted by policy rather than echoed from requests.
  • Identify which component enforces resource-level and approval-level restrictions.

Takeaway: verification, mapping, and authorization are separate operations. Each requires a testable contract.

SPIFFE: OAuth Token Exchange: Verified Identity Is Not Unlimited Permission

An agent presents two credentials: one representing me and one identifying Agent XYZ. A token service returns an access token stating that Agent XYZ is acting on my behalf.

What prevents the agent from taking that token somewhere I never authorized?

Not the actor claim alone. Secure delegation requires verified input credentials, controlled issuance, restricted output tokens, and enforcement by the receiving application. OAuth token exchange supplies the protocol; it does not automatically determine what I intended.

The RFC behind token exchange

RFC 8693, OAuth 2.0 Token Exchange, defines an OAuth-based Security Token Service, or STS. A client submits existing security tokens and requests a new token suitable for a particular purpose. The exchange can change token format, target audience, or authorization context, including representing delegation or impersonation.

The distinction matters:

  • Impersonation: the recipient sees the subject’s identity without necessarily distinguishing the intermediary.
  • Delegation: the token preserves the subject and identifies the party acting for that subject.

For our agent scenario, delegation is the useful model: Lisa remains the authorization subject, while Agent XYZ is recorded as the actor.

How an exchange works

An illustrative request to an STS token endpoint contains:

grant_type=urn:ietf:params:oauth:grant-type:token-exchange

subject_token=<Lisa’s eligible access token>

subject_token_type=urn:ietf:params:oauth:token-type:access_token

actor_token=<Agent XYZ’s supported identity token>

actor_token_type=urn:ietf:params:oauth:token-type:jwt

requested_token_type=urn:ietf:params:oauth:token-type:access_token

audience=case-management-api

scope=cases.read cases.update

 

The subject_token provides the subject’s security context; the optional actor_token identifies the proposed actor. audience, resource, and scope describe requested targets and permissions. These are requests, not issuer obligations. Client authentication is a separate consideration from submitting either token.

The conceptual flow is:

Existing credentials + requested target and permissions

|

v

Security Token Service

├─ Validate credentials

├─ Evaluate exchange policy

├─ Establish delegated authority

└─ Issue a constrained token

|

v

Resource API

├─ Validate issued token

└─ Authorize this operation

 

An exchange bridges security contexts; it is not simply a JWT re-signing operation. RFC 8693 explicitly limits its scope to the exchange protocol rather than defining every deployment’s trust and authorization policy.

The exchanger must verify—not believe—the inputs

The STS should not accept:

subject = Lisa

actor = Agent XYZ

trust_me = true

 

It must establish those identities from credentials validated under an explicitly configured trust relationship. Token-validation rules depend on the accepted token profile; the API must also validate the resulting token independently. For JWT access tokens using RFC 9068, validation includes the expected token type, issuer, audience, signature, and expiration. (datatracker.ietf.org)

For this deployment, I would require the following issuance controls:

Boundary Required decision
Input trust Is this issuer permitted to supply this kind of identity evidence?
Credential validity Is the token authentic, current, and acceptable in this exchange context?
Actor identity Which actor does the verified credential actually identify?
Exchange eligibility May this client or actor exchange this subject’s credential?
Target authorization May this delegation reach the requested API?
Permission authorization Which requested operations may the output token permit?
Output binding Must the resulting token be bound to a particular presenting key?

 

These are recommended policy requirements—not defaults guaranteed by enabling RFC 8693. Its may_act claim can express actor eligibility, but identifying an eligible actor is still different from authorizing every requested target or operation.

Crucially, the resulting token is normally issued for the resource server to validate, not merely for the exchanger to recognize later. A resource server that trusts the STS must still enforce the token’s intended use.

A signed actor claim is attribution, not consent

An illustrative delegated token might contain:

{

“iss”: “https://identity.example.org”,

“sub”: “user:lisa”,

“aud”: “case-management-api”,

“scope”: “cases.read cases.update”,

“act”: {

“sub”: “spiffe://agents.example.org/agent/xyz”

}

}

 

The issuer asserts that Lisa is the subject and Agent XYZ is the current actor. The signature protects that assertion against modification. The actor claim does not, by itself, establish that Lisa approved a specific case update or requested access to another application.

This is the central distinction:

Who is acting, whose authority applies, and which action was authorized are separate facts.

If the deployment intentionally gives Agent XYZ all of Lisa’s application rights, then an unexpected operation within those rights may be policy-authorized even though Lisa did not intend it. No token format can recover a narrower instruction that the authorization system never captured.

Preventing the “Lisa never asked for that” failure

There are several different failures hiding inside that example. They require different controls.

  1. Using the token at another application

Suppose Agent XYZ receives a token for the case-management API, then presents it to payroll.

Payroll must reject it because payroll is not its intended audience. Sharing an issuer or signing key does not make a token valid for every application. RFC 9700 recommends audience-restricted access tokens to limit this kind of reuse.

For this architecture, prefer one narrowly defined target per token and exact audience validation at each API.

  1. Exchanging it again for an unauthorized application

Audience validation does not help if the agent can ask an STS to convert its case-management token into a payroll token.

The recommended issuance policy is to authorize each permitted exchange edge explicitly:

Lisa delegation + Agent XYZ → case-management: allowed

Lisa delegation + Agent XYZ → payroll:         denied

 

Changing audience is a legitimate purpose of exchange, so the defense is not “never change audience.” It is never let possession of an input token alone authorize an arbitrary audience transition. This follows from the protocol’s separation between the requested target and issuer policy.

  1. Performing an unintended action within the correct API

Audience restriction cannot distinguish “update case 4821” from “update every case Lisa can access.” A broad cases.update scope may be equally insufficient.

RFC 9396, Rich Authorization Requests, defines authorization_details for expressing finer-grained authorization requirements using structured JSON. The application-specific authorization type must define what those details mean and how they are enforced.

For example, a proposed—not standardized—authorization object could be:

{

“type”: “case_operation”,

“actions”: [“update”],

“case_ids”: [“4821”],

“allowed_fields”: [“status”],

“allowed_status_values”: [“resolved”]

}

 

In this design, Lisa approves that operation through a trusted authorization interaction. The STS issues authority no broader than the approved operation, and the API checks the actual resource and requested mutation against it.

RFC 9396 does not automatically add this object to RFC 8693 exchanges. Supporting it requires an agreed issuer/API profile and enforcement implementation. Structured authorization is useful only when the resource server understands and checks it.

  1. Stealing and replaying the token

A bearer token can be used by whoever possesses it. A signed act claim can therefore attribute a stolen token to Agent XYZ even when another party presents it.

Sender-constrained tokens address that problem by requiring proof of possession of an associated key. RFC 9700 recommends these alongside audience restrictions.

RFC 9449, DPoP, provides application-layer sender constraint. The client supplies signed proofs, and the recipient validates the proof and its relationship to the access token.

DPoP proofs include request method and URI information, token binding through ath for protected-resource requests, and freshness/replay controls. But DPoP does not ordinarily bind the business request body or establish Lisa’s approval of its contents.

A legitimate—or compromised—agent holding the bound key can still generate proofs for operations its token permits. Sender constraint prevents a different presenter from simply replaying the token; it does not make the authorized presenter trustworthy.

A practical task-bound delegation design

For the Lisa/Agent XYZ scenario, I would implement the following:

  1. Capture approval outside the agent’s assertions. Store an approved delegation identifying Lisa, Agent XYZ, target application, operations, resources, and expiry.
  2. Require that approval during exchange. Validate both credentials and check the requested output against the stored grant.
  3. Issue narrowly. Restrict audience, operations, object identifiers, lifetime, and presenting key where supported.
  4. Enforce at the API. Validate the token, then compare the actual operation and affected object with the grant.
  5. Control further exchange. Deny unapproved target transitions and privilege expansion.
  6. Record execution. Log subject, actor, delegation identifier, target, resource, authorization decision, and outcome.
  7. Use state where necessary. For one-time approval, atomically consume the authorization at the operation boundary; a unique token identifier alone does not make a token single-use.

This is a proposed deployment architecture combining token exchange, constrained access tokens, and fine-grained authorization—not a claim that RFC 8693 implements these controls automatically.

The takeaway

Token exchange can turn verified credentials into a token trusted by another system. It must not turn identity evidence into unrestricted authority.

For the threat in question, insist on four distinct properties:

  • Verified subject and actor.
  • Authorized issuance for the requested target.
  • Permissions bounded by the approved delegation.
  • Resource-server enforcement of those bounds.

“Agent XYZ acting for Lisa” explains attribution. “Agent XYZ may perform this operation on this resource for Lisa” is the authorization boundary.

SPIFFE: Follow One Request: The Complete Trust-Boundary Walkthrough

An end-to-end walkthrough reveals mistakes that component diagrams hide. Document one successful delegated request, including every credential accepted, every identity transformation, and every independent authorization decision.

The delegated request sequence

1 Lisa → human authorization flow: authenticate and approve delegation
2 XYZ → local SPIRE Workload API: obtain agent credential
3 XYZ → PingFederate STS: subject token + actor evidence + target
4 STS: validate inputs; check pairing, approval, target, permissions
5 STS → XYZ: access token for the tool service
6 XYZ → tool: present token and any required possession proof
7 Tool: validate token; authorize requested operation
8 Tool → STS/API: use an explicitly permitted downstream flow
9 API: independently validate and authorize case/4821 mutation
10 Each boundary: preserve verified attribution and outcome

The published IdP integration separates subject and actor token processing, exchange policy, and access-token-manager mapping. Treat those as distinct review points rather than assuming that one successful token validation authorizes the complete workflow.

Credential-validation matrix

Credential Recipient Required deployment contract
Lisa’s eligible subject token STS subject-token processor Trusted user issuer/profile and exchange eligibility
XYZ JWT-SVID STS actor-token processor Trusted SPIFFE authority; intended STS audience; actor mapping
OAuth client authentication STS client-authentication boundary Authorized client; explicit relationship to actor if required
Tool access token Tool service Its own audience; permitted subject/actor; operation constraints
API access token Resource API API audience; resource and mutation authorization
Transport certificate or DPoP proof Supporting recipient Required key/token binding and proof validation

 

A JWT-SVID identifies its subject using the SPIFFE ID and includes an audience for the intended verifier. It is not automatically an OAuth access token for every API. Acceptance at an STS requires an agreed validation and trust profile.

Trace the identities without collapsing them

{
"sub": "user:lisa",
"aud": "case-management-api",
"client_id": "agent-xyz-client",
"scope": "cases.read cases.update",
"act": {
"sub": "spiffe://agents.example.org/agent/xyz"
},
"delegation_id": "approval-81a6"
}

This is an illustrative output payload, not a complete token profile. delegation_id is an application-defined extension. The outer subject represents Lisa; act identifies the current actor. Client identity identifies the OAuth client. The authenticated transport peer may be a tool service rather than the original agent. Retain those distinctions in policy and logs. RFC 8693 provides delegation semantics; application approval is a separate deployment concern. [6]

Standalone and asynchronous alternatives

For standalone work, authorize XYZ under an agent-specific policy rather than borrowing Lisa’s authority. The API may directly accept SPIFFE authentication or accept an STS-issued token whose subject is the agent. This choice depends on the API’s supported credential profiles, not on a universal SPIFFE requirement.

If the tool receives an opaque token, use trusted introspection and check its result according to the agreed profile. Introspection is not a reason to omit audience or operation checks. Its response and any caching policy become part of the authorization boundary. [7]

For queued work, protect the job’s identity and approval context from modification, then re-evaluate authorization at execution time. A string saying user:lisa in a message is not a credential. Record both the originating agent and the executing worker; do not silently pretend that the worker is still the original transport caller.

Takeaway: each hop must know what evidence it receives, what authority it establishes, and what authority it is allowed to pass onward.

SPIFFE: Who Is Acting? Identity and Delegated Authority for Agents

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.

“Agent XYZ modified this resource” identifies a machine principal. “Agent XYZ modified this resource using Lisa’s authority” identifies both an authorization subject and an acting party.

SPIFFE supplies the agent’s verifiable workload identity. OAuth token exchange can connect that identity to delegated user authority while preserving the distinction between whose permissions apply and which software exercised them. These are complementary layers, not capabilities SPIFFE automatically combines.

Start with the agent’s workload identity

For this example, Agent XYZ runs as a workload registered with SPIRE:

spiffe://agents.example.org/agent/xyz

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—not to an agent name supplied in a prompt or HTTP header.

That distinction suggests an important design rule: identify the execution boundary you actually control.

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’s caller-identification model.

Model one: the agent acts as itself

For autonomous work, the application authenticates Agent XYZ and authorizes the agent principal directly.

There are two common integration patterns:

  • Direct SPIFFE authentication: the API accepts an X.509-SVID over mTLS, or a JWT-SVID intended for that API.
  • Identity-to-token exchange: a token service validates the agent’s SPIFFE credential and issues an application access token representing the agent.

A proposed audit record for this model could look like:

{
     "event": "resource.update",
     "identity_mode": "standalone_agent",
     "effective_subject": "spiffe://agents.example.org/agent/xyz",
     "current_actor": "spiffe://agents.example.org/agent/xyz",
     "authorization_principal": "agent-xyz",
     "resource": "case/4821",
     "outcome": "success",
     "request_id": "req-7ec15",
     "agent_run_id": "run-93a2"
}

 

Here, no user’s authority is involved. The application should evaluate its agent-specific policy, not silently substitute the permissions of the person who originally scheduled the job.

Model two: Lisa’s authority, Agent XYZ’s actions

Now suppose Lisa asks Agent XYZ to update a resource.

The desired application semantics are:

Authorization subject: Lisa
Current actor:         Agent XYZ
Permission source:     Lisa’s rights for this application
Audit attribution:     Lisa, acting through Agent XYZ

 

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’s subject.

“Actor token” and “actor claim” are different things

The actor_token is an input to token exchange: evidence identifying the proposed actor.

The act claim is information in the resulting token: the issuer’s assertion identifying the current actor.

In this design:

  • The subject token represents Lisa.
  • The actor token represents Agent XYZ.
  • The issued access token retains Lisa as sub and identifies Agent XYZ in act.sub.

A JWT-SVID can serve as actor evidence if the token service explicitly supports and trusts that profile. Its subject is the agent’s 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.

The exchange

The architecture is:

Lisa’s authorized delegation
+
Agent XYZ’s verified SPIFFE identity
|
v
Token service / STS
|
v
Access token: subject=Lisa, actor=XYZ
|
v
Resource application

 

An illustrative token-exchange request contains these form fields; client authentication is handled separately under the token service’s configured policy:

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
subject_token=<Lisa's eligible subject token>
subject_token_type=urn:ietf:params:oauth:token-type:access_token
actor_token=<Agent XYZ's JWT-SVID>
actor_token_type=urn:ietf:params:oauth:token-type:jwt
requested_token_type=urn:ietf:params:oauth:token-type:access_token
audience=case-management-api
scope=cases.read cases.update

 

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.

For this implementation, require the STS to check:

  • Lisa’s token is valid and eligible for this exchange.
  • The agent credential is valid for the STS and maps to an approved actor.
  • Agent XYZ may act for Lisa in this application.
  • The requested audience and permissions are permitted.
  • The resulting actor identity comes from verified evidence, never a caller-supplied display name.

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.

The resulting access token

An illustrative decoded payload—not a complete access-token profile—is:

{
     "iss": "https://identity.example.org",
     "sub": "user:lisa",
     "aud": "case-management-api",
     "client_id": "agent-xyz-client",
     "scope": "cases.read cases.update",
     "iat": 1793325600,
     "exp": 1793325900,
     "jti": "delegated-token-4b781",
     "act": {
          "sub": "spiffe://agents.example.org/agent/xyz"
     }
}

 

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.

Can it have all of Lisa’s rights?

Yes—as an explicit application policy, not as an inherent property of act.

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.

A useful implementation policy is:

Effective permissions =
Lisa’s current application permissions
∩ token-authorized operations
∩ delegation policy

 

To preserve all of Lisa’s 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.

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.

Programming applications to retain the actor

Issuing the claim is only half the implementation. The application must preserve it through authentication, authorization, execution, and audit.

The following is a recommended application architecture.

  1. Build a verified request identity

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’s validation requirements rather than merely decoding a JWT.

Construct an immutable, request-scoped identity object with fields such as:

effective_subject
current_actor
prior_actors
oauth_client_id
token_issuer
token_id
authorized_scopes
authenticated_transport_peer
delegation_id

 

Recommended rules:

  • Extract the actor only from validated token claims or trusted introspection results.
  • Keep issuer-qualified user identity; a bare sub is not necessarily globally unique.
  • Preserve the SPIFFE trust domain in actor identifiers.
  • Reject malformed actor structures.
  • Do not infer “human request” merely because act is absent.
  • On delegated-only endpoints, require the expected actor information.

These are application controls designed to make the RFC’s subject/actor distinction operational.

  1. Separate permission evaluation from attribution

Business logic should receive both identities, even when authorization intentionally uses only Lisa’s permissions.

For example:

Permission lookup:
subject = user:lisa

Activity attribution:
subject = user:lisa
actor   = spiffe://agents.example.org/agent/xyz

 

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.

This separation preserves the purpose of the act claim: distinguishing delegated execution from acting directly as the subject.

  1. Emit structured business events

A proposed event schema:

{
     "event": "case.update",
     "identity_mode": "user_delegation",
     "effective_subject": {
          "issuer": "https://identity.example.org",
          "id": "user:lisa"
     },
     "current_actor": {
          "id": "spiffe://agents.example.org/agent/xyz"
     },
     "oauth_client_id": "agent-xyz-client",
     "token_id": "delegated-token-4b781",
     "delegation_id": "delegation-81a6",
     "request_id": "req-a740",
     "agent_run_id": "run-93a2",
     "action": "case.update",
     "resource": "case/4821",
     "authorization": {
          "decision": "allow",
          "permission_source": "subject",
          "policy_version": "case-policy-17"
     },
     "outcome": "success"
}

 

The field names are a suggested schema, not SPIFFE or OAuth standards.

For this schema, I would require:

  • Both successful operations and denied attempts carry the identity context.
  • The event records the affected business object and committed outcome.
  • Tokens and private keys are excluded from logs.
  • Agent-supplied run IDs are marked as correlation metadata, not authentication evidence.
  • Database audit columns retain both modified_by_subject and modified_by_actor.

A human-readable rendering can then say:

Lisa updated case 4821 through Agent XYZ.

 

The structured event retains the identifiers needed for investigation.

  1. Carry the identity into background execution

Do not let request identity disappear when work moves to a queue or worker.

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 “user”: “lisa” as permission to act.

Also avoid retaining Lisa’s identity in a mutable global variable: concurrent jobs must not share authorization or audit context.

  1. Preserve delegation across service boundaries

For a chain such as:

Lisa → Agent XYZ → Tool service → Resource API

 

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.

An illustrative claim at the resource API might be:

{
     "sub": "user:lisa",
     "act": {
          "sub": "spiffe://services.example.org/tool/case-editor",
          "act": {
               "sub": "spiffe://agents.example.org/agent/xyz"
          }
     }
}

 

For this design, log the current actor and retained history, and correlate each hop. Never authorize by taking the union of every actor’s permissions.

Attribution is not proof that the actor presented the token

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.

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.

With rotating SPIFFE certificates, that binding is to the specific certificate, 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’s certificate-binding mechanism.

The takeaway

For agent identity, keep three questions separate:

  1. Which workload is this? SPIFFE provides the verifiable identity.
  2. Whose authority is being exercised? Delegated tokens preserve the user as subject.
  3. Which software exercised that authority? The actor claim preserves attribution.

Then make the application record all three at the business-event boundary.

The goal is not merely an access token containing Agent XYZ’s name. It is an authorization and audit model that can reliably distinguish Lisa acting directly, Agent XYZ acting independently, and Agent XYZ exercising Lisa’s delegated authority.