@defakto/spiffe
v0.7.0
Published
SPIFFE-compliant TypeScript SDK with extended workload identity and attestation support
Readme
@defakto/spiffe
⚠️ WARNING: Active Development
This SDK is in active development and the API is not yet stable. Breaking changes may occur with every version until we reach 1.0.0. Pin to exact versions and review the changelog carefully before upgrading.
A TypeScript SDK for SPIFFE workload identity. It ships three clients that share the same API surface:
WorkloadAPIClient— smart wrapper that automatically selects the right underlying client based on options or environment variables. Use this as your default import in any environment.LocalWorkloadAPIClient— connects to a local SPIFFE agent (SPIRE or compatible) via Unix socket or TCP. Use this directly in traditional server or container environments where a SPIFFE agent runs alongside the workload.AttestingWorkloadAPIClient— attests the workload at request time and connects directly to Defakto's cloud endpoint. Use this directly in serverless or ephemeral environments where installing a local agent is not practical.
Installation
npm install @defakto/spiffe
# yarn
yarn add @defakto/spiffe
# pnpm
pnpm add @defakto/spiffe
# bun
bun add @defakto/spiffeSmart wrapper (WorkloadAPIClient)
WorkloadAPIClient auto-selects the right underlying client. Use it as your default import — you can switch environments by changing environment variables without touching your application code.
Selection logic (priority order)
SPIFFE_ENDPOINT_SOCKETenv var set → usesLocalWorkloadAPIClientDEFAKTO_ATTESTORSenv var set → usesAttestingWorkloadAPIClient. The endpoint is resolved fromDEFAKTO_SERVER_ADDRESS(required for self-hosted trust domains), falling back toDEFAKTO_TRUST_DOMAIN_ID(Defakto-hosted only). See endpoint resolution below.- Otherwise → throws
SpiffeErrorwith codeSOCKET_PATH_NOT_CONFIGURED
import { WorkloadAPIClient } from "@defakto/spiffe";
const client = new WorkloadAPIClient();Environment-driven attestation (DEFAKTO_ATTESTORS)
Set DEFAKTO_ATTESTORS to a comma-separated list of built-in attestor names to auto-instantiate them with default options. Pair with DEFAKTO_SERVER_ADDRESS (self-hosted trust domains) or DEFAKTO_TRUST_DOMAIN_ID (Defakto-hosted trust domains) to resolve the endpoint. See endpoint resolution below.
| Name | Attestor class | Environment |
| ------------------------ | ------------------ | ---------------------- |
| aws_external_identity | AwsTokenAttestor | AWS (Lambda, EC2, …) |
| azure_managed_identity | AzureMSIAttestor | Azure Managed Identity |
| gcp_service_account | GcpIITAttestor | Google Cloud |
| github | GithubAttestor | GitHub Actions |
DEFAKTO_ATTESTORS=aws_external_identity
# Self-hosted trust domain — always set DEFAKTO_SERVER_ADDRESS:
DEFAKTO_SERVER_ADDRESS=my-server.example.com:443
# Defakto-hosted trust domain — set DEFAKTO_TRUST_DOMAIN_ID instead:
# DEFAKTO_TRUST_DOMAIN_ID=td-0000000
# Serverless Realms (optional): scope issuance to a specific serverless cluster.
# Omit for trust-domain-scoped issuance.
# DEFAKTO_CLUSTER_ID=cl-0000000To target a specific serverless cluster's policy set (Realms, RFD-194), set the DEFAKTO_CLUSTER_ID environment variable — the zero-config WorkloadAPIClient reads it automatically. To set it in code, pass the clusterId option when constructing an AttestingWorkloadAPIClient directly (WorkloadAPIClient takes no options). An empty value selects the trust-domain-scoped policy set, so this is backward-compatible.
// Zero-config: WorkloadAPIClient reads DEFAKTO_CLUSTER_ID from the environment.
// Application code stays the same in every environment.
const client = new WorkloadAPIClient();
// Or set it explicitly by constructing the attesting client directly:
const attesting = new AttestingWorkloadAPIClient({ clusterId: "cl-0000000" });Unrecognised names throw SpiffeError with code NO_ATTESTORS_CONFIGURED.
Standard SPIFFE (LocalWorkloadAPIClient)
LocalWorkloadAPIClient speaks the standard SPIFFE Workload API gRPC protocol. It works with any compliant SPIFFE agent (e.g. SPIRE).
Connection options
The client resolves its endpoint in the following priority order:
- Explicit options passed to the constructor.
SPIFFE_ENDPOINT_SOCKETenvironment variable (supportsunix:///path,unix://path, andunix:pathprefixes).
If neither is provided, the client throws a SpiffeError with code SOCKET_PATH_NOT_CONFIGURED.
import { LocalWorkloadAPIClient } from "@defakto/spiffe";
// Resolved from SPIFFE_ENDPOINT_SOCKET (throws if not set)
const client = new LocalWorkloadAPIClient();
// Explicit Unix socket
const client = new LocalWorkloadAPIClient({
transport: "unix",
socketPath: "/run/spire/agent.sock",
});
// Explicit TCP (e.g. remote SPIRE agent)
const client = new LocalWorkloadAPIClient({
transport: "tcp",
address: "spire-agent.internal",
port: 8081,
insecureSkipTls: false, // default: false (TLS enabled)
});X.509 SVID
const svid = await client.x509.getSVID();
console.log("SPIFFE ID:", svid.id.toString()); // spiffe://example.org/service
console.log("Expires at:", svid.expiresAt.toISOString());
// svid.certificates — DER-encoded certificate chain (Uint8Array[])
// svid.privateKey — CryptoKeyJWT SVID
const jwt = await client.jwt.fetchSVID(["https://api.example.org"]);
console.log("SPIFFE ID:", jwt.id.toString());
console.log("Token:", jwt.token);Watching X.509 context rotations
The Workload API pushes a new context whenever SVIDs are rotated. watchX509Context returns an async iterable that reconnects automatically if the stream ends cleanly.
// Full context (all SVIDs + trust bundles)
for await (const ctx of client.watchX509Context()) {
const svid = ctx.defaultSVID();
console.log("Rotated SVID:", svid.id.toString());
// ctx.svids — all SVIDs issued to this workload
// ctx.bundles — Map<trustDomainName, X509Bundle>
}
// Or watch just the default SVID
for await (const svid of client.x509.watchSVID()) {
console.log("New SVID expires:", svid.expiresAt.toISOString());
}Resource cleanup
All clients implement Symbol.asyncDispose for use with await using (TypeScript 5.2+), and expose an explicit close() method.
// Recommended: automatic cleanup via `await using`
await using client = new LocalWorkloadAPIClient();
const svid = await client.x509.getSVID();
// client.close() is called automatically when the block exits
// Manual cleanup
const client = new LocalWorkloadAPIClient();
try {
const svid = await client.x509.getSVID();
} finally {
await client.close();
}Defakto Serverless (AttestingWorkloadAPIClient)
AttestingWorkloadAPIClient is designed for environments where a SPIFFE agent cannot run alongside the workload (AWS Lambda, Google Cloud Run, Azure Container Apps, GitHub Actions, etc.). Instead of relying on a pre-existing agent, it attests the workload inline — collecting cryptographic evidence from the environment on every request — and sends that evidence to Defakto's trust domain server endpoint to obtain SVIDs.
Endpoint resolution
The endpoint is resolved in the following priority order:
- Explicit
transportoption (same shape asLocalWorkloadAPIClient) — overrides all environment variables. DEFAKTO_SERVER_ADDRESSenvironment variable — acceptshost:portor a barehost(defaults to port 443). Always set this when your trust domain is self-hosted.trustDomainIdoption orDEFAKTO_TRUST_DOMAIN_IDenvironment variable — only applicable when the trust domain is hosted by Defakto. Automatically resolves to<trustDomainId>.agent.spirl.com:443over TLS.
If none of these are configured, AttestingWorkloadAPIClient throws SpiffeError with code SERVER_ADDRESS_NOT_CONFIGURED.
X.509 SVID
const svid = await client.x509.getSVID();JWT SVID
const jwt = await client.jwt.fetchSVID(["https://api.example.org"]);
console.log("ID:", jwt.id);Watching X.509 context rotations
The attesting client re-attests and re-fetches the SVID automatically before it expires (at 85% of the remaining lifetime, minimum 1 second interval).
for await (const ctx of client.watchX509Context()) {
const svid = ctx.defaultSVID();
console.log("Renewed SVID expires:", svid.expiresAt.toISOString());
}Token exchange (TokenExchangeClient)
TokenExchangeClient exchanges a user OIDC ID token for a Defakto JWT-SVID. Each
exchange fetches a fresh agent JWT-SVID and sends it as the RFC 8693
client_assertion; the returned JWT-SVID is verified with the workload JWT
bundle source before it is returned. Exchanged tokens are not cached by the SDK.
The user token can come from any OIDC identity provider trusted by the token
exchange endpoint. clientAssertionAudience is the token exchange service's
OIDC issuer / authorization-server identifier; the SDK fetches the agent
JWT-SVID with exactly that audience.
import { TokenExchangeClient } from "@defakto/spiffe";
const tokenExchange = new TokenExchangeClient({
endpoint: "https://tds.example.org/oauth/token",
clientAssertionAudience: "https://tds.example.org",
});
const svid = await tokenExchange.exchange(userIdToken, "spiffe://example.org/recipient");The returned value is a verified JwtSVID whose audience matches the target
audience passed to exchange. Services that receive these JWT-SVIDs must still
verify them against their normal JWT bundle policy. Exchange/network/protocol
failures throw SpiffeError with code TOKEN_EXCHANGE_FAILED. Advanced
integrations can pass source when they need to provide their own JWT-SVID and
JWT bundle source. Custom sources must fetch agent JWT-SVIDs accepted by the
endpoint as jwt-spiffe client assertions, including exp and jti, and
must resolve JWT bundles used to verify returned JWT-SVIDs. Pass fetch to
inject a custom fetch implementation for tests or custom runtimes.
A service that already holds a delegated JWT-SVID can exchange it for one
targeting a new audience with reExchange. The original user is carried
through unchanged and the calling workload is stamped as the actor:
const next = await tokenExchange.reExchange(delegatedSvid.token, "spiffe://example.org/next");Built-in attestors
AWS (AwsTokenAttestor)
Attests using a signed JWT from AWS STS (GetWebIdentityToken). Works in any environment with an IAM role attached (Lambda, ECS, EC2, etc.).
import { AttestingWorkloadAPIClient, AwsTokenAttestor } from "@defakto/spiffe";
const client = new AttestingWorkloadAPIClient({
trustDomainId: "td-0000000",
attestors: [new AwsTokenAttestor()],
});
const svid = await client.x509.getSVID();
console.log("SPIFFE ID:", svid.id.toString());Options
| Option | Type | Default | Description |
| ------------------ | -------------------- | --------------------------------- | --------------------------------------------------------------- |
| audience | string[] | ["urn:defakto:security:server"] | JWT audience (aud claim) |
| signingAlgorithm | "RS256" \| "ES384" | "RS256" | STS token signing algorithm |
| durationSeconds | number | 60 | Token validity in seconds (60–3600) |
| tags | Tag[] | — | Optional custom STS session tags |
| stsConfig | STSClientConfig | {} | AWS SDK STS client config (region, credentials, endpoint, etc.) |
const attestor = new AwsTokenAttestor({
audience: ["my-service"],
signingAlgorithm: "ES384",
durationSeconds: 300,
stsConfig: { region: "eu-west-1" },
});Azure (AzureMSIAttestor)
Attests using an Azure Managed Identity token. Works on any Azure compute resource with a managed identity assigned (App Service, Container Apps, AKS, VM, etc.).
import { AttestingWorkloadAPIClient, AzureMSIAttestor } from "@defakto/spiffe";
const client = new AttestingWorkloadAPIClient({
trustDomainId: "td-0000000",
attestors: [new AzureMSIAttestor()],
});
const svid = await client.x509.getSVID();
console.log("SPIFFE ID:", svid.id.toString());By default the attestor uses api://AzureADTokenExchange as the audience (a well-known Azure workload identity federation audience). If the managed identity is user-assigned, select it by clientId, principalId, or resourceId:
// System-assigned identity (default)
new AzureMSIAttestor();
// User-assigned identity — select by client ID
new AzureMSIAttestor({ clientId: "00000000-0000-0000-0000-000000000000" });
// User-assigned identity — select by object/principal ID
new AzureMSIAttestor({ principalId: "00000000-0000-0000-0000-000000000000" });
// Custom audience
new AzureMSIAttestor({ audience: "api://my-tenant-id/defakto-server" });Options
| Option | Type | Default | Description |
| ------------- | -------- | ------------------------------ | ---------------------------------------------- |
| audience | string | "api://AzureADTokenExchange" | Azure resource URI for the token |
| clientId | string | — | Select managed identity by client ID |
| principalId | string | — | Select managed identity by object/principal ID |
| resourceId | string | — | Select managed identity by MSI resource ID |
At most one of clientId, principalId, resourceId may be set.
GCP (GcpIITAttestor)
Attests using a GCP Instance Identity Token (IIT) fetched from the metadata service. The token is a signed JWT issued by Google that proves the workload is running on a specific GCE instance, Cloud Run service, GKE pod, etc.
import { AttestingWorkloadAPIClient, GcpIITAttestor } from "@defakto/spiffe";
const client = new AttestingWorkloadAPIClient({
trustDomainId: "td-0000000",
attestors: [new GcpIITAttestor()],
});
const svid = await client.x509.getSVID();
console.log("SPIFFE ID:", svid.id.toString());Options
| Option | Type | Default | Description |
| ------------------- | -------- | ------------------------------- | ----------------------------------------------- |
| audience | string | "urn:defakto:security:server" | Audience for the identity token |
| serviceAccount | string | "default" | Service account to fetch the identity token for |
| identityTokenHost | string | "metadata.google.internal" | Override the metadata service host |
new GcpIITAttestor({
audience: "my-defakto-agent",
serviceAccount: "[email protected]",
});GitHub Actions (GithubAttestor)
Attests a workload running inside a GitHub Actions job using a GitHub Actions OIDC ID token. The token is a signed JWT issued by GitHub (https://token.actions.githubusercontent.com) that proves the workload is executing in a specific repository, workflow, ref, and environment.
The token is fetched on demand via @actions/core's getIDToken, which calls the GitHub Actions runtime to mint a fresh JWT for the requested audience. The token itself is never read from an environment variable or the filesystem.
The workflow must grant id-token: write permission, e.g.:
permissions:
id-token: write
contents: readimport { AttestingWorkloadAPIClient, GithubAttestor } from "@defakto/spiffe";
const client = new AttestingWorkloadAPIClient({
trustDomainId: "td-0000000",
attestors: [new GithubAttestor()],
});
const svid = await client.x509.getSVID();
console.log("SPIFFE ID:", svid.id.toString());Options
| Option | Type | Default | Description |
| -------------- | ------------------------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| audience | string | "defakto-github" | Audience (aud claim) requested for the OIDC token; must match the audience the Defakto signer is configured to accept |
| fetchIdToken | (audience) => Promise<string> | @actions/core.getIDToken | Override the token fetcher (e.g. for testing). Defaults to GitHub's official Actions toolkit, which mints a fresh JWT on each call |
Custom attestors
Implement the Attestor interface to add any attestation source:
import type { Attestor, AttestationEvidence } from "@defakto/spiffe";
import { readFile } from "node:fs/promises";
class KubernetesAttestor implements Attestor {
readonly pluginName = "k8s_sat"; // must match what the endpoint expects
readonly pluginVersion = "1.0.0";
async collectEvidence(): Promise<AttestationEvidence> {
const token = await readFile("/var/run/secrets/kubernetes.io/serviceaccount/token");
return {
pluginName: this.pluginName,
pluginVersion: this.pluginVersion,
payload: token,
};
}
}
const client = new AttestingWorkloadAPIClient({
trustDomainId: "td-0000000",
attestors: [new KubernetesAttestor()],
});When multiple attestors are configured, evidence is collected concurrently and all results are sent together in a single request.
Cloud provider credential integration
Once you have a SPIFFE client, you can use a JWT SVID as a proof of identity to obtain native cloud credentials — no long-lived secrets or service account keys needed. The integrations live in separate sub-path exports to keep peer-dependency footprint minimal.
All four integrations self-bootstrap when no source is supplied: they create a WorkloadAPIClient automatically, reading the socket path or attestor config from environment variables. Pass source explicitly only when you need to share an existing client or use AttestingWorkloadAPIClient.
AWS (fromSpiffe)
fromSpiffe returns an AWS credential provider that assumes an IAM role using the SVID as a web identity token. Pass the result to the credentials option of any AWS SDK client.
import { fromSpiffe } from "@defakto/spiffe/aws";
import { S3Client } from "@aws-sdk/client-s3";
// Zero-arg: WorkloadAPIClient created lazily on first credential refresh.
// roleArn can also be set via the AWS_ROLE_ARN environment variable.
const s3 = new S3Client({
region: "eu-west-1",
credentials: fromSpiffe({ roleArn: "arn:aws:iam::123456789012:role/my-role" }),
});AWS credentials are automatically refreshed: whenever the SDK detects the credentials have expired, fromSpiffe fetches a fresh SVID and exchanges it for a new STS session.
roleArn can be omitted when the AWS_ROLE_ARN environment variable is set — useful for ECS task definitions, EC2 instance env, or Kubernetes env injection:
// AWS_ROLE_ARN=arn:aws:iam::123456789012:role/my-role
const s3 = new S3Client({
region: "eu-west-1",
credentials: fromSpiffe(),
});To use an existing SPIFFE client, pass it via source:
import { LocalWorkloadAPIClient } from "@defakto/spiffe";
import { fromSpiffe } from "@defakto/spiffe/aws";
const spiffe = new LocalWorkloadAPIClient();
const credentials = fromSpiffe({
source: spiffe.jwt,
roleArn: "arn:aws:iam::123456789012:role/my-role",
});Options
| Option | Type | Default | Description |
| ----------------- | ------------------------- | ------------------------------- | ------------------------------------------------------------------------------------ |
| source | JwtSVIDSource | new WorkloadAPIClient().jwt | JWT SVID source; created lazily on first credential refresh if not provided |
| roleArn | string | AWS_ROLE_ARN env var | ARN of the IAM role to assume. Falls back to the AWS_ROLE_ARN environment variable |
| svidAudience | string \| string[] | "urn:defakto:security:server" | Audience(s) for the JWT SVID — must match the IAM identity provider |
| roleSessionName | string | "spiffe-session" | Name for the assumed role session |
| providerId | string | — | FQDN of the identity provider (e.g. "graph.facebook.com") |
| policyArns | { arn: string }[] | — | Managed session policies |
| policy | string | — | Inline session policy JSON |
| durationSeconds | number | 3600 | Role session duration in seconds |
| clientConfig | Record<string, unknown> | — | Config passed to the inner STS client (region, endpoint, etc.) |
Azure (SPIFFECredential)
SPIFFECredential is an Azure TokenCredential that authenticates using a SPIFFE JWT SVID as a client assertion. It enables workload identity federation: no client secrets or certificates — the SVID proves the workload's identity to Azure AD.
import { SPIFFECredential } from "@defakto/spiffe/azure";
import { BlobServiceClient } from "@azure/storage-blob";
// Zero-arg: WorkloadAPIClient created eagerly in constructor.
// tenantId and clientId can also be set via AZURE_TENANT_ID / AZURE_CLIENT_ID.
const credential = new SPIFFECredential({
tenantId: "your-tenant-id",
clientId: "your-app-client-id",
});
const blobClient = new BlobServiceClient(
"https://mystorageaccount.blob.core.windows.net",
credential,
);SPIFFECredential implements the standard TokenCredential interface, so it is a drop-in replacement for DefaultAzureCredential or any other credential type accepted by the Azure SDK.
Environment variable–driven usage — In environments where AZURE_TENANT_ID and AZURE_CLIENT_ID are already injected (AKS, Container Apps, VM env injection), you can omit options entirely:
// AZURE_TENANT_ID and AZURE_CLIENT_ID set in environment
const credential = new SPIFFECredential();To use an existing SPIFFE client, pass it via source:
import { LocalWorkloadAPIClient } from "@defakto/spiffe";
import { SPIFFECredential } from "@defakto/spiffe/azure";
const spiffe = new LocalWorkloadAPIClient();
const credential = new SPIFFECredential({ source: spiffe.jwt, tenantId: "...", clientId: "..." });If AZURE_AUTHORITY_HOST is set in the environment (e.g. for sovereign clouds), it is used as the authority host unless credentialOptions.authorityHost is provided explicitly.
Prerequisites in Azure AD:
- An app registration (or managed identity) with a federated identity credential configured.
- The federated credential's audience must match
svidAudience(default:"api://AzureADTokenExchange"). - The app must have the required RBAC roles or API permissions.
Options
| Option | Type | Default | Description |
| ------------------- | ---------------------------------- | ------------------------------ | ----------------------------------------------------------------------------------- |
| source | JwtSVIDSource | new WorkloadAPIClient().jwt | JWT SVID source; created eagerly in constructor if not provided |
| tenantId | string | AZURE_TENANT_ID | Azure AD tenant ID |
| clientId | string | AZURE_CLIENT_ID | Azure AD application (client) ID |
| svidAudience | string \| string[] | "api://AzureADTokenExchange" | Audience(s) for the JWT SVID |
| credentialOptions | ClientAssertionCredentialOptions | — | Authority host (falls back to AZURE_AUTHORITY_HOST), logging, retry options, etc. |
GCP (SPIFFEIdentityPoolClient)
SPIFFEIdentityPoolClient exchanges a SPIFFE JWT SVID for a GCP access token via Workload Identity Federation. It extends google-auth-library's IdentityPoolClient, so it can be passed directly as authClient to any Google Cloud SDK library (@google-cloud/storage, @google-cloud/bigquery, etc.).
import { SPIFFEIdentityPoolClient } from "@defakto/spiffe/gcp";
import { Storage } from "@google-cloud/storage";
// Zero-arg: WorkloadAPIClient created eagerly in constructor.
// audience can also be set via the GOOGLE_CLOUD_WORKLOAD_IDENTITY_POOL environment variable.
const gcpClient = new SPIFFEIdentityPoolClient({
audience:
"//iam.googleapis.com/projects/123456789/locations/global/" +
"workloadIdentityPools/my-pool/providers/my-provider",
});
// Pass gcpClient directly as authClient to any Google Cloud SDK library.
const storage = new Storage({ authClient: gcpClient });
const [metadata] = await storage.bucket("my-bucket").getMetadata();
console.log(metadata);For setups that use service account impersonation, pass serviceAccountImpersonationUrl:
const gcpClient = new SPIFFEIdentityPoolClient({
audience: "//iam.googleapis.com/projects/.../providers/my-provider",
serviceAccountImpersonationUrl:
"https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/" +
"[email protected]:generateAccessToken",
});Environment variable–driven usage — In environments where GOOGLE_CLOUD_WORKLOAD_IDENTITY_POOL and other GCP variables are already injected (Cloud Run, GKE, etc.), you can omit options entirely:
// GOOGLE_CLOUD_WORKLOAD_IDENTITY_POOL, GOOGLE_CLOUD_PROJECT, etc. set in environment
const gcpClient = new SPIFFEIdentityPoolClient();To use an existing SPIFFE client, pass it via source:
import { LocalWorkloadAPIClient } from "@defakto/spiffe";
import { SPIFFEIdentityPoolClient } from "@defakto/spiffe/gcp";
const spiffe = new LocalWorkloadAPIClient();
const gcpClient = new SPIFFEIdentityPoolClient({
source: spiffe.jwt,
audience: "//iam.googleapis.com/...",
});If CLOUDSDK_AUTH_IMPERSONATE_SERVICE_ACCOUNT is set to a service account email, the impersonation URL is constructed automatically unless serviceAccountImpersonationUrl is provided explicitly.
Prerequisites in GCP:
- A Workload Identity Pool with an OIDC provider configured for your SPIFFE trust domain.
- The provider's audience must match
svidAudience(defaults to the value ofaudience). - IAM bindings granting the pool (or the impersonated service account) access to GCP resources.
Options
| Option | Type | Default | Description |
| -------------------------------- | -------------------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- |
| source | JwtSVIDSource | new WorkloadAPIClient().jwt | JWT SVID source; created eagerly in constructor if not provided |
| audience | string | GOOGLE_CLOUD_WORKLOAD_IDENTITY_POOL | Workload Identity Pool audience string |
| svidAudience | string \| string[] | value of audience | Audience(s) for the JWT SVID |
| serviceAccountImpersonationUrl | string | constructed from CLOUDSDK_AUTH_IMPERSONATE_SERVICE_ACCOUNT | Service account impersonation URL; env var holds SA email, URL is constructed automatically |
| projectId | string | GOOGLE_CLOUD_PROJECT | GCP project ID; required when running outside GCP (Azure, AWS, on-prem) so Cloud SDK clients can skip auto-detect |
| universeDomain | string | "googleapis.com" | GCP universe domain; only needed for non-public GCP universes |
| quotaProjectId | string | GOOGLE_CLOUD_QUOTA_PROJECT | Project used for quota and billing attribution; defaults to projectId |
Anthropic (identityTokenFromSpiffe)
identityTokenFromSpiffe returns an Anthropic IdentityTokenProvider — a () => Promise<string> callable that the Anthropic SDK invokes to fetch a fresh JWT for OIDC Workload Identity Federation. Pass it to oidcFederationProvider({ identityTokenProvider, ... }) from @anthropic-ai/sdk/lib/credentials/oidc-federation. Each refresh fetches a new JWT SVID — no token files on disk, no static sk-ant-... keys.
import Anthropic from "@anthropic-ai/sdk";
import { oidcFederationProvider } from "@anthropic-ai/sdk/lib/credentials/oidc-federation";
import { identityTokenFromSpiffe } from "@defakto/spiffe/anthropic";
// Zero-arg: WorkloadAPIClient created lazily on first token refresh.
const client = new Anthropic({
credentials: oidcFederationProvider({
identityTokenProvider: identityTokenFromSpiffe(),
federationRuleId: "fdrl_...",
organizationId: "00000000-0000-0000-0000-000000000000",
serviceAccountId: "svac_...",
baseURL: "https://api.anthropic.com",
fetch,
}),
});
const message = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello, Claude" }],
});The Anthropic SDK refreshes its access token on a two-tier schedule (advisory at expiry−120s, mandatory at expiry−30s). On each refresh identityTokenFromSpiffe calls JwtSVIDSource.fetchSVID again, so the underlying SVID is always fresh.
To use an existing SPIFFE client, pass it via source:
import { LocalWorkloadAPIClient } from "@defakto/spiffe";
import { identityTokenFromSpiffe } from "@defakto/spiffe/anthropic";
const spiffe = new LocalWorkloadAPIClient();
const identityTokenProvider = identityTokenFromSpiffe({
source: spiffe.jwt,
svidAudience: "https://api.anthropic.com",
});Prerequisites in the Claude Console:
- A registered federation issuer for your SPIFFE trust domain (issuer URL pointing to your SPIRE server's OIDC discovery endpoint).
- A federation rule whose audience match (if configured) equals
svidAudience. - A service account targeted by the rule, added as a member of the workspace you intend to call the API from.
Options
| Option | Type | Default | Description |
| -------------- | -------------------- | ----------------------------- | ----------------------------------------------------------------------- |
| source | JwtSVIDSource | new WorkloadAPIClient().jwt | JWT SVID source; created lazily on first token refresh if not provided |
| svidAudience | string \| string[] | "https://api.anthropic.com" | Audience(s) for the JWT SVID — must satisfy the federation rule's match |
OpenAI (subjectTokenFromSpiffe)
subjectTokenFromSpiffe returns an OpenAI workload identity subject token provider — the object passed as workloadIdentity.provider when constructing the OpenAI client. On each refresh the OpenAI SDK calls getToken() to obtain a short-lived JWT SVID, which it exchanges for an OpenAI access token via OIDC Workload Identity Federation. No OPENAI_API_KEY is needed and there are no token files on disk.
import OpenAI from "openai";
import { subjectTokenFromSpiffe } from "@defakto/spiffe/openai";
// Zero-arg: WorkloadAPIClient created lazily on first token refresh.
const client = new OpenAI({
workloadIdentity: {
identityProviderId: "idp-...",
serviceAccountId: "sa-...",
provider: subjectTokenFromSpiffe(),
},
});
const response = await client.responses.create({
model: "gpt-5.5",
input: "Hello!",
});To use an existing SPIFFE client, pass it via source:
import { LocalWorkloadAPIClient } from "@defakto/spiffe";
import { subjectTokenFromSpiffe } from "@defakto/spiffe/openai";
const spiffe = new LocalWorkloadAPIClient();
const provider = subjectTokenFromSpiffe({
source: spiffe.jwt,
svidAudience: "https://api.openai.com/v1",
});Prerequisites in the OpenAI dashboard:
- A registered workload identity provider for your SPIFFE trust domain (issuer URL pointing to your SPIRE server's OIDC discovery endpoint).
- The provider's audience match (if configured) equals
svidAudience. - A service account targeted by the provider.
Options
| Option | Type | Default | Description |
| -------------- | -------------------- | ----------------------------- | ------------------------------------------------------------------------- |
| source | JwtSVIDSource | new WorkloadAPIClient().jwt | JWT SVID source; created lazily on first token refresh if not provided |
| svidAudience | string \| string[] | "https://api.openai.com/v1" | Audience(s) for the JWT SVID — must satisfy the provider's audience match |
Error Handling
All errors thrown by this SDK are instances of SpiffeError:
import { SpiffeError, SpiffeErrorCode } from "@defakto/spiffe";
try {
const svid = await client.x509.getSVID();
} catch (err) {
if (err instanceof SpiffeError) {
console.error(err.code, err.message, err.cause);
}
}Error codes
| Code | Description |
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| INVALID_SPIFFE_ID | Malformed SPIFFE ID string |
| INVALID_TRUST_DOMAIN | Malformed trust domain |
| BUNDLE_NOT_FOUND | No bundle for the requested trust domain |
| SVID_EXPIRED | The SVID has expired |
| SVID_INVALID | The SVID is structurally invalid |
| SVID_AUDIENCE_MISMATCH | JWT audience does not match |
| SOCKET_PATH_NOT_CONFIGURED | No socket path was configured for LocalWorkloadAPIClient |
| WORKLOAD_API_UNAVAILABLE | Cannot reach the Workload API endpoint |
| WORKLOAD_API_ERROR | Workload API returned an unexpected error |
| JWT_PARSE_ERROR | Failed to parse a JWT |
| JWT_SIGNATURE_INVALID | JWT signature verification failed |
| TOKEN_EXCHANGE_FAILED | Token exchange failed due to network, HTTP, or protocol errors |
| SERVER_ADDRESS_NOT_CONFIGURED | No server address configured: DEFAKTO_SERVER_ADDRESS, DEFAKTO_TRUST_DOMAIN_ID, and transport are all unset |
| INVALID_SERVER_ADDRESS | DEFAKTO_SERVER_ADDRESS could not be parsed as a valid host or host:port |
| NO_ATTESTORS_CONFIGURED | AttestingWorkloadAPIClient requires at least one attestor |
| ATTESTOR_COLLECTION_FAILED | An attestor failed to collect evidence |
| ATTESTATION_FAILED | The endpoint rejected the attestation |
API Reference
Main export (@defakto/spiffe)
| Export | Kind | Description |
| ---------------------------- | --------- | ------------------------------------------------------------ |
| WorkloadAPIClient | class | Smart wrapper — auto-selects local or attesting client |
| LocalWorkloadAPIClient | class | Standard SPIFFE Workload API client (gRPC to local agent) |
| AttestingWorkloadAPIClient | class | Defakto attesting client for serverless environments |
| TokenExchangeClient | class | Token exchange client returning a JWT-SVID |
| TokenExchangeClientOptions | type | Options for TokenExchangeClient |
| TokenExchangeFetch | type | Fetch implementation signature used by TokenExchangeClient |
| AwsTokenAttestor | class | Built-in AWS IAM attestor (uses STS GetWebIdentityToken) |
| AzureMSIAttestor | class | Built-in Azure Managed Identity attestor |
| GcpIITAttestor | class | Built-in GCP Instance Identity Token attestor |
| GithubAttestor | class | Built-in GitHub Actions OIDC attestor |
| SpiffeError | class | Error type thrown by the SDK |
| SpiffeErrorCode | enum | All possible error codes |
| Attestor | interface | Implement to create a custom attestor |
| AttestationEvidence | interface | Evidence payload returned by an attestor |
| ClientOptions | type | Transport options for LocalWorkloadAPIClient |
| AttestingClientOptions | type | Options for AttestingWorkloadAPIClient |
| AwsTokenAttestorOptions | type | Options for AwsTokenAttestor |
| AzureMSIAttestorOptions | type | Options for AzureMSIAttestor |
| GcpIITAttestorOptions | type | Options for GcpIITAttestor |
| GithubAttestorOptions | type | Options for GithubAttestor |
| GithubIdTokenFetcher | type | Token fetcher signature for GithubAttestor |
| X509Context | interface | Snapshot of all X.509 SVIDs and bundles |
| X509SVID | interface | An X.509 SVID with certificate chain and private key |
| JwtSVID | interface | A JWT SVID |
| REQUESTED_TOKEN_TYPE | const | Default requested token type for token exchanges |
AWS sub-path (@defakto/spiffe/aws)
| Export | Kind | Description |
| ------------------- | -------- | --------------------------------------------------------------------- |
| fromSpiffe | function | AWS credential provider using a SPIFFE JWT SVID as web identity token |
| FromSpiffeOptions | type | Options for fromSpiffe |
Azure sub-path (@defakto/spiffe/azure)
| Export | Kind | Description |
| ------------------------- | ----- | ---------------------------------------------------------------------------------- |
| SPIFFECredential | class | Azure TokenCredential backed by a SPIFFE JWT SVID (workload identity federation) |
| SPIFFECredentialOptions | type | Options for SPIFFECredential |
GCP sub-path (@defakto/spiffe/gcp)
| Export | Kind | Description |
| --------------------------------- | ----- | ------------------------------------------------------------------------ |
| SPIFFEIdentityPoolClient | class | GCP auth client using a SPIFFE JWT SVID for Workload Identity Federation |
| SPIFFEIdentityPoolClientOptions | type | Options for SPIFFEIdentityPoolClient |
Anthropic sub-path (@defakto/spiffe/anthropic)
| Export | Kind | Description |
| -------------------------------- | -------- | --------------------------------------------------------------------------------- |
| identityTokenFromSpiffe | function | Anthropic IdentityTokenProvider backed by a SPIFFE JWT SVID (OIDC WIF exchange) |
| IdentityTokenFromSpiffeOptions | type | Options for identityTokenFromSpiffe |
OpenAI sub-path (@defakto/spiffe/openai)
| Export | Kind | Description |
| ------------------------------- | -------- | --------------------------------------------------------------------------- |
| subjectTokenFromSpiffe | function | OpenAI workload identity subject token provider backed by a SPIFFE JWT SVID |
| SubjectTokenFromSpiffeOptions | type | Options for subjectTokenFromSpiffe |
| OpenAISubjectTokenProvider | type | The returned { tokenType: "jwt"; getToken } shape the OpenAI SDK accepts |
