Syed Hasibur Rahman24 min read

Secret Zero: How SPIFFE and the IETF's WIMSE Drafts Kill the Credential That Never Expires

Every distributed system accumulates credentials that outlive the services they were minted for: an API key in a CI variable, a cloud access key in an environment file, a service account that three rewrites later still has database write access. SPIFFE solved this inside the cloud-native world by replacing secrets with attested, short-lived identity documents. The IETF's WIMSE working group is now standardizing the same idea for everyone else — a URI-shaped workload identifier, a JWT credential with a proof-of-possession key, and three wire profiles for using it. This post walks the specs field by field, shows the working code, and is honest about what the model costs to operate.

The credential was four years old. It lived in a CI variable called DEPLOY_KEY, it had been minted by an engineer who left in 2022, and the service it was created for had been rewritten twice and renamed once. Nobody could say what it still had access to, because the only way to find out was to revoke it and wait for something to break. So it stayed. It was still there when the repository it was scoped to was archived, and it was still valid when someone finally checked.

This is not an unusual story. It is the ordinary condition of most production systems, and it has a measurable shape. GitGuardian’s State of Secrets Sprawl 2026 found 28.65 million new hardcoded secrets added to public GitHub commits in 2025 alone — a 34% year-over-year increase — and, more damning, that as of January 2026 64% of the valid secrets it had detected back in 2022 were still active and exploitable1. Four years. Not “unrotated” — still working.

We have spent a decade getting serious about human identity. Passwords became MFA became passkeys. Sessions got short. Tokens got sender-constrained. Meanwhile the machines — the services, the jobs, the sidecars, the cron containers, the thing that copies a bucket at 3 a.m. — kept authenticating with a long string in an environment variable, because that was easy and because nobody’s threat model had a face attached to it.

The fix is not a better secrets manager. A secrets manager relocates the problem: now you need a credential to get the credential. That is secret zero, and it is the actual hard part. The fix is to stop issuing secrets to workloads at all, and instead let a workload prove what it is to something that can see it directly — the kernel, the orchestrator, the hypervisor, the TPM — and receive a short-lived, cryptographically verifiable identity document in return.

SPIFFE has done this in the cloud-native world since 2018. What is new is that the IETF has picked the problem up. In 2024 it chartered WIMSE — Workload Identity in Multi System Environments — and as of this writing the working group has six active drafts plus one already sitting with the IESG2. This post walks the whole model: the problem, SPIFFE’s answer, the platform-specific answers and why they do not compose, and then the WIMSE specs field by field. There is working code. There is also a section about what this costs you, because the honest version of this story includes the operational bill.


Part 1: What a static service credential actually is

Strip away the tooling and a service credential is a bearer secret with three properties that make it uniquely bad:

It has no natural expiry. A human session expires because a human is not there. A service runs continuously, so the credential is issued long-lived to avoid waking someone at 3 a.m. Long-lived means “valid until someone remembers.”

It is copied by design. To be used, it must be readable by the process. So it lands in an environment variable, which lands in a process listing, a crash dump, a container image layer, a log line from a well-meaning debug statement, a CI job’s exported environment, and an observability vendor’s ingestion pipeline. Each copy is fully authoritative.

Its identity is asserted, not observed. The credential says “I am the billing service.” Nothing checks whether the thing presenting it is the billing service. It is a claim, and possession of the string is the entire proof.

The WIMSE working group’s survey document, Workload Identity Practices, states the consequence plainly: secrets “need to be provisioned and rotated, which requires either automation to be built, or periodic manual effort,” and they “may be stolen and used by attackers to impersonate the workload”3. That is the whole indictment in two clauses. The provisioning burden is why they end up long-lived; the impersonation property is why that matters.

Note that this is the same argument the OAuth community made against bearer tokens, which ended in RFC 9449 and sender-constrained tokens4. Workload identity is that argument applied one layer down, to the credential the service uses to be itself rather than to act for a user.


Part 2: Secret zero, stated properly

The standard rebuttal is: put the secret in a vault. But a vault is a service that authenticates its callers, so the caller needs a credential to authenticate with. Call it VAULT_TOKEN. You have now moved the problem, not solved it — and you have concentrated it, because that one credential unlocks all the others.

There are only three honest ways out of this recursion:

  1. A hardware root. A TPM, a secure enclave, or an HSM holds a key that never leaves. The workload proves possession of it. This works, and it is the strongest option, but it binds identity to a machine, not to a workload, and machines run many workloads.
  2. A platform root. The thing that started the workload vouches for it. The kubelet knows which pod it launched. The hypervisor knows which VM it booted. The CI system knows which repository and which job. This is the practical answer, and it is what nearly everything below is built on.
  3. Nothing. Ship the secret and hope. This is the incumbent.

Option 2 has a name: attestation. The workload does not present a credential; it presents itself, and an agent that has privileged visibility into the platform determines what it is. Crucially, the workload never holds a bootstrap secret, because the evidence is not something it can carry — it is a property of where it is running.

That is the pivot the rest of this post rests on. Once you accept attestation as the root, every downstream credential can be short-lived, because reissuing one is free and automatic.


Part 3: SPIFFE — identity as a URI

SPIFFE (Secure Production Identity Framework For Everyone) is the mature implementation of this idea. It and its reference runtime SPIRE were accepted into the CNCF in 2018, moved to incubation in 2020, and the CNCF announced their graduation on 20 September 20225. It is deployed inside service meshes, in banks, and — relevant to anyone building a platform — inside other people’s platforms whether they know it or not.

SPIFFE has three moving parts, and they are unusually well factored.

The SPIFFE ID

An identity is a URI:

spiffe://<trust domain>/<workload identifier>

for example spiffe://acme.com/billing/payments6. The authority component is the trust domain — the root of trust, one organization or one independent SPIFFE deployment. The path is whatever hierarchy you want: namespace, service, instance.

This is a deliberately boring choice and it is the best decision in the whole design. A URI is a string every language can parse, every certificate can carry (as a SAN), every JWT can hold (as sub), and every policy engine can pattern-match. There is no new encoding to implement.

The SVID

An SVID — SPIFFE Verifiable Identity Document — is “the document with which a workload proves its identity to a resource or caller.” It carries exactly one SPIFFE ID and comes in two formats: an X.509 certificate (recommended where possible) and a JWT (used when you must, for example through an L7 proxy that terminates TLS — and the docs are explicit that the JWT form is “vulnerable to replay attacks”)6.

That parenthetical is the single most important sentence in the SPIFFE documentation, and it is the reason half the WIMSE work exists. An X.509-SVID is inherently proof-of-possession: to use it you must complete a TLS handshake with the private key. A JWT-SVID is a bearer token. It is the same bearer problem, one layer down, and SPIFFE is honest enough to say so in its own docs.

The lifetime posture is the other half: “All private keys (and corresponding certificates) are short lived, rotated frequently and automatically”6. SPIRE’s defaults are on the order of an hour, and rotation is a background concern the workload never sees.

The Workload API

This is where attestation lands. The SPIFFE Workload API is a gRPC service, SpiffeWorkloadAPI, exposed to workloads over a local endpoint (in SPIRE, a Unix domain socket). Its methods are:

ProfileRPCs
X.509-SVIDFetchX509SVID, FetchX509Bundles
JWT-SVIDFetchJWTSVID, FetchJWTBundles, ValidateJWTSVID

7

And the defining property: “the Workload API does not require that a calling workload have any knowledge of its own identity, or possess any authentication token when calling the API”6. You do not tell it who you are. You connect, and the agent on the other side works it out — reading the peer credentials off the socket, walking up to the process, asking the kubelet or the container runtime what that process belongs to. The spec is candid that this identification happens through “out-of-band authenticity checks” rather than client authentication7.

The streaming shape matters too. FetchX509SVID returns a stream, not a single response: the agent pushes a new SVID down the same call before the old one expires. A correctly written client never polls, never handles a rotation error, and never has a certificate expire under it. Rotation stops being an operational event.

Here is the whole client, in Go, for a server that will only accept peers from one identity:

package main

import (
	"context"
	"crypto/tls"
	"net/http"

	"github.com/spiffe/go-spiffe/v2/spiffeid"
	"github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig"
	"github.com/spiffe/go-spiffe/v2/workloadapi"
)

func main() {
	ctx := context.Background()

	// Connects to the Workload API socket. No credentials are passed:
	// the agent attests this process and decides what it is.
	source, err := workloadapi.NewX509Source(ctx)
	if err != nil {
		panic(err)
	}
	defer source.Close()

	// The source keeps rotating in the background for the process lifetime.
	caller := spiffeid.RequireFromString("spiffe://acme.com/billing/payments")

	server := &http.Server{
		Addr:      ":8443",
		TLSConfig: tlsconfig.MTLSServerConfig(source, source, tlsconfig.AuthorizeID(caller)),
	}
	_ = server.ListenAndServeTLS("", "")
}

There is no key file, no certificate path, no secret, and no rotation logic. AuthorizeID is the authorization decision, expressed as an identity rather than as a network location — which is the actual point of the exercise.


Part 4: The platform answers, and why they don’t compose

SPIFFE is not the only implementation of “attest, then issue short-lived.” Every major platform independently built one. The WIMSE practices draft catalogues them: Kubernetes issuing JWTs from the control plane via service accounts, SPIFFE issuing SVIDs via the Workload API, cloud providers issuing credentials through instance metadata endpoints, CI/CD systems issuing platform identities to pipelines, and service meshes issuing X.509 certificates to sidecars3.

Kubernetes made the transition in public. Before v1.22, “Kubernetes provides a long-lived, static token to the Pod as a Secret.” From v1.22, “Kubernetes gets a short-lived, automatically rotating token using the TokenRequest API and mounts the token as a projected volume,” and “projected tokens expire automatically, and the kubelet rotates the token before it expires”8. The projected volume source takes an expirationSeconds that “defaults to 1 hour and must be at least 10 minutes (600 seconds)” and an audience that “defaults to the identifier of the API server”9:

volumes:
  - name: workload-identity
    projected:
      sources:
        - serviceAccountToken:
            audience: vault.internal   # NOT the API server
            expirationSeconds: 3600
            path: token

The audience field is the interesting one. It turns the token from a general-purpose credential into one that is only meaningful to a named verifier, which means a token stolen from a pod that talks to Vault cannot be replayed against the Kubernetes API. Setting it is free and almost nobody does.

GitHub Actions does the same thing for CI. Instead of storing a cloud access key as a repository secret, the workflow requests an OIDC token from GitHub and exchanges it for short-lived cloud credentials. The token’s issuer is https://token.actions.githubusercontent.com, and its sub encodes the exact context — repo:octo-org/octo-repo:environment:prod — so the cloud role’s trust policy can pin to a repository, a branch, or an environment. Each token “is only valid for a single job, and then automatically expires”10.

AWS IAM Roles Anywhere closes the loop from the other direction, for workloads outside AWS. You register your CA as a trust anchor, your workload presents an X.509 certificate issued by it, and it receives temporary IAM credentials; a profile names the assumable roles and can carry a session policy that further narrows them. AWS states the goal directly: “you don’t need to manage long-term AWS credentials for workloads running outside of AWS”11. Note the shape — X.509 in, temporary credentials out. This is exactly the SVID model with an AWS-specific trust anchor.

So the primitive is solved five times. The problem is that none of these five talk to each other. The Kubernetes token is a JWT with a Kubernetes-shaped sub and an issuer only the cluster’s OIDC discovery document explains. The GitHub token has a GitHub-shaped sub. The SPIFFE credential has a URI. A workload in one system that needs to call a workload in another has to translate — and the translation layer is invariably a static credential, which is where you came in.

That gap is the entire justification for WIMSE.


Part 5: What WIMSE is standardizing

WIMSE is an IETF working group with six active working-group drafts and one document already with the IESG2. The architecture document — Workload Identity in a Multi System Environment (WIMSE) Architecture, draft-ietf-wimse-arch-08, 6 July 2026, by Salowey, Rosomakho, and Tschofenig — frames the goal as standardized “protocols and payloads for conveying workload identity and security context information” in distributed systems12.

The pieces, and their state as of this writing:

DraftTitleVersion / date
draft-ietf-wimse-archArchitecture-08, 2026-07-06
draft-ietf-wimse-identifierWorkload Identifier-03, 2026-07-06
draft-ietf-wimse-workload-credsWorkload Credentials-02, 2026-07-02
draft-ietf-wimse-wptWorkload Proof Token-02, 2026-08-27
draft-ietf-wimse-http-signatureAuth with HTTP Signatures-06, 2026-08-04
draft-ietf-wimse-mutual-tlsAuth using Mutual TLS-02, 2026-07-06
draft-ietf-wimse-workload-identity-practicesPractices (Informational)-06, 2026-08-11, at IESG

2

These are Internet-Drafts, not RFCs. Nothing here is stable enough to hard-code into a product you cannot upgrade. But the shape has clearly converged, and the shape is what you should be designing toward.

The trust domain and the identifier

The architecture defines a trust domain as a logical grouping of systems under common security controls, named by a fully qualified domain name for global uniqueness, and serving as the scope within which identities are issued and validated12. A workload identity is a unique name within that domain, and it “may represent a logical workload, service, or specific instance depending on deployment policy” — a deliberate refusal to legislate granularity.

The identifier draft makes this concrete. A Workload Identifier “MUST be an absolute URI” with “a non-empty authority component that identifies the trust domain,” and it “MUST NOT contain a query component, a fragment component, user information, or a port component”13. Implementations must support identifiers of at least 2048 bytes. The draft registers a wimse scheme:

wimse://<trust-domain>/<path>

and its examples sit spiffe:// and wimse:// side by side without ceremony:

spiffe://incubation.example.org/ns/experimental/analytics/ingest
wimse://trust.corp.example.com/workload/af3e86cb-7013-4e33-b717-11c4edd25679
wimse://trust.example.com/service/payment

13

Read that as the diplomatic core of the effort. WIMSE is not replacing SPIFFE IDs; it is generalizing the pattern SPIFFE proved so that a Kubernetes-issued identity, a CI-issued identity and a mesh-issued identity can all be named the same way and validated by the same code. The scheme becomes a detail of who issued it.


Part 6: The Workload Identity Token, field by field

The credentials draft, draft-ietf-wimse-workload-creds-02, defines two concrete formats. The JWT one is the Workload Identity Token (WIT).

A WIT is a signed JWT whose JOSE header carries an asymmetric alg ("none" is prohibited) and "typ": "wit+jwt", following the explicit-typing advice of the JWT best-practices RFC14. Its claims:

ClaimRequirementMeaning
subMUSTThe single Workload Identifier
cnfMUSTConfirmation claim holding a jwk object with the workload’s public key; the jwk MUST include an alg
expMUSTExpiration; lifetimes SHOULD be short, “typically on the order of hours”
issRECOMMENDEDThe issuing Identity Server
jtiOPTIONALUnique token identifier

15

Concretely:

{
  "iss": "https://identity.trust.example.com",
  "sub": "wimse://trust.example.com/service/payment",
  "exp": 1788000000,
  "jti": "sJqK2v-9xR",
  "cnf": {
    "jwk": {
      "kty": "OKP",
      "crv": "Ed25519",
      "alg": "EdDSA",
      "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"
    }
  }
}

The cnf claim is the whole design. It is the same confirmation-key mechanism DPoP uses to bind an OAuth access token to a client key4 — except here it binds an identity document to a key the workload holds. The WIT on its own is not sufficient to authenticate: it says “the holder of this key is the payment service,” and the holder must then demonstrate it holds that key. A stolen WIT is a public statement about someone else’s key. It is inert.

The spec is strict about the binding: “each WIMSE credential carries exactly one Workload Identifier,” and for a WIT that is the sub claim15. One document, one identity — no multi-identity certificates, no ambiguity about which name a policy engine should match.

The X.509 sibling is the Workload Identity Certificate (WIC), and the rule there is equally sharp: the identifier “MUST appear in exactly one SubjectAltName extension of type URI,” and the certificate “MUST NOT contain multiple URI SANs carrying workload identifiers”15. If you have implemented SPIFFE X.509-SVIDs you have already built this; the encoding is identical.


Part 7: Three ways to prove you hold the key

A WIT plus a key is an identity. Turning it into an authenticated request needs a proof mechanism, and WIMSE deliberately specifies three, because the deployment realities differ.

Mutual TLS

draft-ietf-wimse-mutual-tls-02 covers the case where both ends speak TLS directly to each other. The certificate is the proof; the handshake is the challenge-response. This is the strongest option and it is what you should use when you can. It is also what SPIFFE recommends, and what every service mesh already does. The reason WIMSE cannot stop here is that a great many real deployments terminate TLS at a load balancer or an L7 proxy, at which point the peer certificate is gone and the application sees a plain HTTP request.

The Workload Proof Token

draft-ietf-wimse-wpt-02 is the application-layer answer, and it will look extremely familiar to anyone who has read RFC 9449. A WPT is a short-lived JWT, typed "typ": "wpt+jwt" (media type application/wpt+jwt), signed with the private key whose public half is in the WIT’s cnf. The draft requires that the WPT’s alg match the algorithm named in the WIT’s confirmation key16. Its claims:

ClaimMeaning
aud“The HTTP target URI of the request to which the WPT is attached, without query or fragment parts”
expExpiry — “WPT lifetimes MUST be short, e.g., on the order of minutes or seconds”
jtiUnique per proof, with “negligible probability that the same value will be assigned to any other WPT”
wth“Hash of the Workload Identity Token” — base64url SHA-256 of its bytes
tthTransaction token hash, if one is present in the request
oth“Hash(es) of other token(s) in the request that convey end-user identity and/or authorization context”

16

wth is the load-bearing claim: it staples this proof to that specific WIT, so a captured WPT cannot be replayed alongside a different identity document. tth and oth extend the same logic to the user-context tokens travelling in the same request — a captured proof cannot be reused with a different end user’s authorization attached to it. DPoP’s ath claim does the identical job for an OAuth access token4; WPT generalizes it to every token in the message.

Building one is unremarkable, which is the idea:

import hashlib, json, secrets, time, base64
import jwt  # PyJWT

def b64u(raw: bytes) -> str:
    return base64.urlsafe_b64encode(raw).rstrip(b"=").decode()

def make_wpt(wit: str, target_uri: str, private_key, lifetime_s: int = 30) -> str:
    """Build a Workload Proof Token bound to `wit` for a single request."""
    now = int(time.time())
    claims = {
        "aud": target_uri.split("?")[0].split("#")[0],
        "exp": now + lifetime_s,
        "jti": secrets.token_urlsafe(16),
        "wth": b64u(hashlib.sha256(wit.encode("ascii")).digest()),
    }
    return jwt.encode(
        claims,
        private_key,                       # the key whose public half is in wit's cnf
        algorithm="EdDSA",                 # MUST match the alg in the WIT's cnf.jwk
        headers={"typ": "wpt+jwt"},
    )

Thirty seconds of validity, a fresh jti per call, and a hash that ties it to one identity document. There is nothing here worth stealing.

HTTP Message Signatures

The third profile, draft-ietf-wimse-http-signature-06, builds on RFC 9421 HTTP Message Signatures17 and goes further than WPT: instead of proving possession alongside the request, it signs the request itself. The WIT travels in a Workload-Identity-Token header, which must be covered by the signature when present. Required signature coverage on a request is the derived components "@method" and "@request-target", plus, when present, Content-Type, Content-Digest, Authorization, Txn-Token and Workload-Identity-Token18.

The draft also introduces three signature metadata parameters:

  • wimse-aud — the audience binding, defaulting to the request’s target URI without query or fragment
  • wimse-sign-response — a boolean by which the client “requires the server to sign the HTTP response”
  • wimse-req-nonce — set on a response to the request’s nonce, “preventing replaying responses to the wrong client”18

That last pair is the genuinely new capability. mTLS authenticates a channel; WPT authenticates a request. HTTP signatures with wimse-sign-response authenticate both directions of an exchange, end to end, across every proxy in between. If your architecture has three hops of L7 infrastructure between caller and callee, this is the only one of the three that survives the trip intact — and it is the only one that lets the client verify it was talking to the right server all along.

The cost is real: covering Content-Digest means buffering or streaming-hashing the body, and canonicalization bugs are the classic failure mode of every message-signing scheme ever deployed. Choose this profile when you need it, not by default.


Part 8: The user still has to get through

Workload identity answers “which service is calling.” It does not answer “on whose behalf.” In a call chain of six services, service six needs to know that the human who started this was a specific user with a specific entitlement — and it cannot take service five’s word for it, because service five is exactly what an attacker would compromise.

The OAuth working group’s Transaction Tokens draft handles this. draft-ietf-oauth-transaction-tokens is at version 11 (30 July 2026), authored by Tulshibagwale, Fletcher and Kasselman, Standards Track, and in the state “WG Consensus: Waiting for Write-Up” — meaning the technical argument is over and the paperwork is not19. Its claims include txn (a unique transaction identifier), sub (the principal, unique within the aud trust domain), aud (the trust domain in which the token is valid), scope (the purpose of this specific transaction, “as narrowly as possible”), and the recommended rctx and tctx for request and transaction context. Exchange is via OAuth token exchange with the issued type urn:ietf:params:oauth:token-type:txn_token19.

The composition is the point. In a fully specified WIMSE request you have three independent assertions travelling together:

POST /v1/settlements HTTP/1.1
Host: ledger.internal.example.com
Content-Type: application/json
Workload-Identity-Token: eyJhbGciOiJFZERTQSIsInR5cCI6IndpdCtqd3QifQ...
Txn-Token: eyJhbGciOiJSUzI1NiIsInR5cCI6InR4bl90b2tlbitqd3QifQ...
Workload-Proof-Token: eyJhbGciOiJFZERTQSIsInR5cCI6IndwdCtqd3QifQ...

The WIT says what is calling. The Txn-Token says for whom, and for what narrow purpose. The WPT proves the caller holds the WIT’s key and — through tth — that this proof was minted for this exact transaction token. Compromise any one of the three in isolation and it buys you nothing.

This is a materially better position than the industry’s current default, which is an internal call with a shared API key and a X-User-Id header that every service downstream trusts absolutely.


Part 9: What this actually costs

Every honest engineering post has this section, so here is mine.

You now run a CA, or you depend on one. Short-lived certificates mean continuous issuance. A SPIRE deployment is a real distributed system with a datastore, an HA story, and a bad day when it is down — and when it is down, nothing can rotate, so you have roughly one credential lifetime of grace before the fleet stops talking. Sizing that grace period is the single most consequential parameter you will pick. One hour is fashionable. One hour is also exactly how long you have to restore the control plane at 3 a.m.

Trust bundle distribution is the new secret zero. The credential is gone but the root is not. Every verifier needs the trust domain’s bundle, it must be updated before the old root retires, and pushing it is a real distribution problem — the same one you had with secrets, except a bundle is public, so a leak is uninteresting. That is a genuine reduction in risk, not an elimination of work.

Clocks matter now. A WPT with a 30-second lifetime is unusable on a host whose clock is 90 seconds off. Every deployment of this pattern eventually discovers a machine with broken NTP, usually via an outage that looks like an authorization bug. Budget skew explicitly, monitor it, and reject rather than silently widen.

Revocation is still unsolved, and short lifetimes are the answer. There is no CRL story here worth the name. The model’s response to a compromised workload is that its credential expires in an hour and does not get reissued, because attestation now fails. That is a good answer, but it is an eventual one — and it means your attestation policy is the real access-control system. If node attestation can be spoofed, everything above it is theatre.

Granularity is a policy decision nobody will make for you. The architecture draft explicitly allows an identifier to name a logical service or a specific instance12. Per-instance identities give beautiful audit trails and a policy engine that has to cope with churn measured in thousands of identities per hour. Per-service identities are operable and mean a compromised pod is indistinguishable from its healthy siblings. Most teams should start per-service and add instance granularity where an incident argued for it.

And these are drafts. -02 and -03 documents change. Build the shape — attested issuance, short lifetimes, URI identifiers, proof-of-possession — behind an interface you control, and treat the specific claim names as a serialization detail you will revise.


Part 10: What to do on Monday

In order, and each step is worth doing even if you never take the next one:

  1. Inventory what never expires. Not “what secrets do we have” — every team has a vault inventory. Ask instead: which credentials have no expiry, and what would break if they vanished? If you cannot answer the second half, that is the finding.
  2. Set audience on your Kubernetes projected tokens. It is one YAML line, it is free, and it converts a general-purpose credential into one that is meaningless outside its intended verifier9.
  3. Kill CI’s static cloud keys. GitHub Actions OIDC to a federated cloud role is a well-trodden path with vendor documentation on both ends, and it deletes the single most commonly leaked credential class in the industry101.
  4. Pick a trust domain name and start writing identifiers as URIs, even if today they are only strings in a config file and a hand-rolled check. When you later adopt SPIFFE or WIMSE, the naming — the part that touches every policy, every log line, and every dashboard — is already done.
  5. Deploy SPIFFE where you have a mesh, and use the Workload API’s streaming rotation rather than reimplementing it.
  6. Read the drafts, and follow WIMSE. The architecture document is 33 pages and the identifier draft is 132. That is an afternoon, and it will change how you specify your next internal service.

Closing

The uncomfortable thing about the four-year-old DEPLOY_KEY is that nothing was misconfigured. Nobody was careless. The credential was created correctly, stored correctly in a secrets manager, injected correctly by CI, and used correctly by a service — and it was still, four years later, a fully valid authorization for a system nobody could enumerate. The failure was not in the operation of the model. It was the model.

What SPIFFE demonstrated, and what WIMSE is now writing down for everyone outside the cloud-native tent, is that the fix is not more hygiene applied to secrets. It is removing the secret from the workload entirely, and replacing “I know a string” with “I can be observed to be what I claim, right now, by something that can actually see me.” Everything downstream — the hour-long certificate, the thirty-second proof, the URI that a policy engine can match — follows from that one substitution.

The specs are not finished. -02 drafts are not a foundation, and anyone telling you to ship against them is selling something. But the direction has stopped being contested: the identifier is a URI, the credential is short-lived and carries a confirmation key, and possession is proved per-request rather than asserted per-string. Those four properties are not going to be reversed by a working-group last call.

The credential that never expires is not a hard problem anymore. It is a legacy one. That is a much better kind of problem to have — as long as you actually go and find yours.


References


  1. GitGuardian, “The State of Secrets Sprawl 2026”, March 2026. https://blog.gitguardian.com/the-state-of-secrets-sprawl-2026/ ↩︎ ↩︎

  2. IETF, “Workload Identity in Multi System Environments (wimse)” working group documents. https://datatracker.ietf.org/group/wimse/ ↩︎ ↩︎ ↩︎ ↩︎

  3. IETF WIMSE WG, “Workload Identity Practices”, draft-ietf-wimse-workload-identity-practices-06, 11 August 2026 (Informational, submitted to the IESG). https://datatracker.ietf.org/doc/html/draft-ietf-wimse-workload-identity-practices-06 ↩︎ ↩︎

  4. Fett, D., Campbell, B., Bradley, J., Lodderstedt, T., Jones, M., and Waite, D., “OAuth 2.0 Demonstrating Proof of Possession (DPoP)”, IETF RFC 9449, September 2023. https://www.rfc-editor.org/rfc/rfc9449 ↩︎ ↩︎ ↩︎

  5. Cloud Native Computing Foundation, “SPIFFE and SPIRE Projects Graduate from Cloud Native Computing Foundation Incubator”, 20 September 2022. https://www.cncf.io/announcements/2022/09/20/spiffe-and-spire-projects-graduate-from-cloud-native-computing-foundation-incubator/ ↩︎

  6. SPIFFE, “SPIFFE Concepts”. https://spiffe.io/docs/latest/spiffe-about/spiffe-concepts/ ↩︎ ↩︎ ↩︎ ↩︎

  7. SPIFFE, “SPIFFE Workload API” standard. https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE_Workload_API.md ↩︎ ↩︎

  8. Kubernetes, “Service Accounts”. https://kubernetes.io/docs/concepts/security/service-accounts/ ↩︎

  9. Kubernetes, “Projected Volumes”. https://kubernetes.io/docs/concepts/storage/projected-volumes/ ↩︎ ↩︎

  10. GitHub, “About security hardening with OpenID Connect”. https://docs.github.com/en/actions/concepts/security/openid-connect ↩︎ ↩︎

  11. Amazon Web Services, “What is AWS Identity and Access Management Roles Anywhere?”. https://docs.aws.amazon.com/rolesanywhere/latest/userguide/introduction.html ↩︎

  12. Salowey, J., Rosomakho, Y., and Tschofenig, H., “Workload Identity in a Multi System Environment (WIMSE) Architecture”, draft-ietf-wimse-arch-08, 6 July 2026. https://datatracker.ietf.org/doc/draft-ietf-wimse-arch/ ↩︎ ↩︎ ↩︎

  13. IETF WIMSE WG, “Workload Identifier”, draft-ietf-wimse-identifier-03, 6 July 2026. https://datatracker.ietf.org/doc/html/draft-ietf-wimse-identifier-03 ↩︎ ↩︎

  14. Sheffer, Y., Hardt, D., and Jones, M., “JSON Web Token Best Current Practices”, IETF RFC 8725, February 2020. https://www.rfc-editor.org/rfc/rfc8725 ↩︎

  15. IETF WIMSE WG, “WIMSE Workload Credentials”, draft-ietf-wimse-workload-creds-02, 2 July 2026. https://datatracker.ietf.org/doc/html/draft-ietf-wimse-workload-creds-02 ↩︎ ↩︎ ↩︎

  16. IETF WIMSE WG, “WIMSE Workload Proof Token”, draft-ietf-wimse-wpt-02, 27 August 2026. https://datatracker.ietf.org/doc/html/draft-ietf-wimse-wpt-02 ↩︎ ↩︎

  17. Backman, A., Richer, J., and Sporny, M., “HTTP Message Signatures”, IETF RFC 9421, February 2024. https://www.rfc-editor.org/rfc/rfc9421 ↩︎

  18. IETF WIMSE WG, “WIMSE Workload-to-Workload Authentication with HTTP Signatures”, draft-ietf-wimse-http-signature-06, 4 August 2026. https://datatracker.ietf.org/doc/html/draft-ietf-wimse-http-signature-06 ↩︎ ↩︎

  19. Tulshibagwale, A., Fletcher, G., and Kasselman, P., “Transaction Tokens”, draft-ietf-oauth-transaction-tokens-11, 30 July 2026. https://datatracker.ietf.org/doc/draft-ietf-oauth-transaction-tokens/ ↩︎ ↩︎