<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>credctl | Blog</title><description>Hardware-bound, short-lived cloud credentials.</description><link>https://credctl.com/</link><language>en</language><item><title>Why credctl federates via OIDC instead of IAM Roles Anywhere</title><link>https://credctl.com/blog/oidc-vs-roles-anywhere/</link><guid isPermaLink="true">https://credctl.com/blog/oidc-vs-roles-anywhere/</guid><description>Both OIDC federation and IAM Roles Anywhere eliminate long-lived AWS keys. credctl chose OIDC — for multi-cloud portability and zero customer-side PKI. Here&apos;s the reasoning, and the cases where Roles Anywhere is genuinely the better answer.

</description><pubDate>Mon, 13 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Both AWS OIDC federation and IAM Roles Anywhere can give a workload outside AWS short-lived AWS credentials. It’s the most common design question about credctl’s architecture — and the reasons to pick one over the other aren’t widely written up.&lt;/p&gt;
&lt;p&gt;Here’s what credctl settled on, why — and importantly, when you’d actually pick Roles Anywhere instead. (For the reference-style comparison of how each mechanism works, see the &lt;a href=&quot;https://credctl.com/guides/oidc-vs-roles-anywhere/&quot;&gt;OIDC vs Roles Anywhere guide&lt;/a&gt; — this post is the decision narrative.)&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-two-paths-in-plain-terms&quot;&gt;The two paths in plain terms&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;OIDC federation (&lt;code dir=&quot;auto&quot;&gt;sts:AssumeRoleWithWebIdentity&lt;/code&gt;).&lt;/strong&gt; Your workload produces a signed JWT containing its identity. The signature is verifiable against a JWKS endpoint you host. AWS IAM trusts a specific OIDC provider (identified by JWKS URL + issuer + audience). When the workload calls STS with the JWT, STS fetches the JWKS, validates the signature, checks the claims against the role’s trust policy, and returns temporary credentials. This is the path GitHub Actions, Kubernetes (via IRSA), and most modern CI/CD systems use to authenticate to AWS without long-lived access keys.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;IAM Roles Anywhere.&lt;/strong&gt; Your workload presents an X.509 client certificate signed by a CA. AWS Roles Anywhere has a “trust anchor” pointing at that CA, plus a “profile” describing which IAM roles the cert is allowed to assume. The workload calls Roles Anywhere’s &lt;code dir=&quot;auto&quot;&gt;CreateSession&lt;/code&gt; endpoint with the cert and a signed challenge, gets temporary credentials back, and uses those against AWS. Roles Anywhere validates the cert chain against the trust anchor and the profile policies before issuing.&lt;/p&gt;
&lt;p&gt;Both deliver the same end state: short-lived AWS credentials issued to a non-AWS workload, without anyone holding a long-lived access key. The difference is in &lt;em&gt;what proves identity&lt;/em&gt; — a JWT signed by a key, or an X.509 cert.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;credctls-specific-constraints&quot;&gt;credctl’s specific constraints&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;credctl is a CLI that runs on a developer’s laptop and authenticates to AWS using a hardware-bound key — Secure Enclave on macOS, TPM 2.0 on Linux. The key never leaves the chip. The key signs &lt;em&gt;something&lt;/em&gt; that AWS validates, and AWS returns STS credentials.&lt;/p&gt;
&lt;p&gt;That “something” is the question. It can be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A JWT signed by the hardware key (OIDC path)&lt;/li&gt;
&lt;li&gt;An X.509 challenge signed by the hardware key (Roles Anywhere path)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both are technically achievable. Secure Enclave and TPM 2.0 both hold ECDSA P-256 keys and produce ECDSA signatures over arbitrary payloads.&lt;/p&gt;
&lt;p&gt;But credctl had two other constraints that mattered for the choice:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;We want the same auth flow to work across AWS, GCP, and Azure.&lt;/strong&gt; A developer’s laptop should authenticate to whichever cloud their workload runs in — without per-cloud cert lifecycle management. The same hardware key, the same identity, three clouds.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;We want zero customer-side infrastructure&lt;/strong&gt; beyond the IAM trust setup. No CA to operate. No cert rotation. No revocation list to keep current.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Both push hard toward OIDC.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-oidc-won-for-credctl&quot;&gt;Why OIDC won for credctl&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Multi-cloud portability.&lt;/strong&gt; AWS OIDC federation, GCP Workload Identity Federation, and Azure Federated Identity Credentials all accept the same JWT format — signed with ES256, standard &lt;code dir=&quot;auto&quot;&gt;iss&lt;/code&gt;/&lt;code dir=&quot;auto&quot;&gt;sub&lt;/code&gt;/&lt;code dir=&quot;auto&quot;&gt;aud&lt;/code&gt;/&lt;code dir=&quot;auto&quot;&gt;exp&lt;/code&gt;/&lt;code dir=&quot;auto&quot;&gt;iat&lt;/code&gt; claims, JWKS discoverable via &lt;code dir=&quot;auto&quot;&gt;/.well-known/openid-configuration&lt;/code&gt;. Different endpoints (&lt;code dir=&quot;auto&quot;&gt;AssumeRoleWithWebIdentity&lt;/code&gt; on AWS, the WIF token exchange on GCP, FIC on Azure), but the underlying protocol is interoperable. One identity, three clouds. Roles Anywhere is AWS-only — for the same workload identity on GCP we’d need WIF anyway, so we’d be operating two parallel systems.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No customer-side PKI.&lt;/strong&gt; Roles Anywhere requires the customer to operate a CA that issues client certs to their workloads. Even with public CAs and ACM Private CA, this is a real ongoing operational burden: cert rotation, revocation lists (CRLs or OCSP), CA renewal. For credctl’s “install the CLI and federate” experience, asking customers to run a CA before they can authenticate is a non-starter. OIDC requires hosting a JWKS file (a few hundred bytes of JSON behind a CDN) and registering the OIDC provider in IAM once. That’s it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mature ecosystem.&lt;/strong&gt; OIDC is the cloud auth lingua franca for non-cloud workloads. GitHub Actions, Kubernetes service accounts, CI/CD runners, and SPIFFE all federate via OIDC. We can lean on documented patterns, well-tested IAM examples, and developer familiarity. Roles Anywhere is younger and the ecosystem of examples — especially for “laptop CLI” rather than “server workload” — is thinner.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Richer claims structure.&lt;/strong&gt; JWTs carry arbitrary claims. credctl can put device identifiers, attestation data, or future attributes into the &lt;code dir=&quot;auto&quot;&gt;sub&lt;/code&gt; and standard claims without changing the underlying protocol. X.509 cert SANs and extensions can do something similar but less flexibly, and modifications usually mean cert re-issuance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Existing audit story.&lt;/strong&gt; AWS CloudTrail records the JWT’s &lt;code dir=&quot;auto&quot;&gt;sub&lt;/code&gt; claim on every &lt;code dir=&quot;auto&quot;&gt;AssumeRoleWithWebIdentity&lt;/code&gt; call. Roles Anywhere logs a &lt;code dir=&quot;auto&quot;&gt;CreateSession&lt;/code&gt; event with the cert’s subject — slightly different audit shape, an extra event in the chain, and a different correlation story between issuance and use.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-trade-off-we-accepted&quot;&gt;The trade-off we accepted&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;OIDC’s main cost is that you have to host a JWKS endpoint that the cloud provider can reach over HTTPS. This is a customer-side trust dependency: AWS STS does an HTTP GET against the URL you registered in the OIDC provider, and trusts whatever JWKS comes back.&lt;/p&gt;
&lt;p&gt;For credctl this is fine. The JWKS is small, immutable per device-key, and served from S3 + CloudFront in v1 (and the team broker in v2). It’s not on the credential path latency-wise — STS caches the JWKS aggressively. Outages of the JWKS host don’t break in-progress STS calls; they only prevent &lt;em&gt;new&lt;/em&gt; federations against fresh JWTs that reference keys STS hasn’t seen before.&lt;/p&gt;
&lt;p&gt;Roles Anywhere doesn’t have this requirement — AWS hosts the entire validation flow. If you genuinely cannot or will not host a publicly-reachable JWKS endpoint (some air-gapped environments, some regulated industries), this is a real point in Roles Anywhere’s favour.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;when-youd-actually-pick-roles-anywhere&quot;&gt;When you’d actually pick Roles Anywhere&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;There are cases where Roles Anywhere is the better answer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;You already have a corporate PKI&lt;/strong&gt; that issues device certs as part of MDM (Jamf, Intune). Reusing those certs for AWS auth means zero new identity issuance — the device cert it already has becomes the AWS credential. OIDC would require generating new credctl-shaped keys and hosting a parallel JWKS.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You can’t host a public JWKS endpoint.&lt;/strong&gt; Air-gapped environments. Regulated environments where any public-internet trust dependency is unacceptable. Environments where the JWKS hosting is itself an audit boundary you don’t want to own.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You’re AWS-only&lt;/strong&gt; with no plans to authenticate the same identity to GCP or Azure. The multi-cloud portability argument falls away.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You don’t want the JWT TTL revocation window&lt;/strong&gt; and prefer cert-revocation semantics via CRLs or OCSP — even with the operational cost of that infrastructure.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For credctl’s target users — developers running CLIs on their own laptops, often across multiple clouds, often without enterprise MDM — none of these apply strongly enough to overcome OIDC’s portability and ergonomics.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-honest-summary&quot;&gt;The honest summary&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;OIDC won for credctl because we want one identity that works across three clouds, with zero customer-side PKI, with a mature ecosystem of examples to lean on.&lt;/p&gt;
&lt;p&gt;Roles Anywhere wins if you have an existing PKI you want to reuse, if you can’t host a public JWKS endpoint, or if you’re committed to AWS-only.&lt;/p&gt;
&lt;p&gt;For a workload that looks like credctl’s — a CLI tool, a CI/CD runner, a service mesh, a Kubernetes federation — OIDC is almost always the right call. Roles Anywhere is the right call when the constraints push you that direction explicitly. The default should be OIDC.&lt;/p&gt;
&lt;div&gt; &lt;h3&gt;Stay up to date&lt;/h3&gt; &lt;p&gt;Get notified about new credctl releases, security best practices, and hardware identity insights.&lt;/p&gt; &lt;form action=&quot;https://buttondown.com/api/emails/embed-subscribe/credctl&quot; method=&quot;post&quot; target=&quot;popupwindow&quot;&gt; &lt;div&gt; &lt;input type=&quot;email&quot; name=&quot;email&quot; placeholder=&quot;you@example.com&quot; required aria-label=&quot;Email address&quot;&gt; &lt;button type=&quot;submit&quot;&gt;Subscribe&lt;/button&gt; &lt;/div&gt; &lt;input type=&quot;hidden&quot; name=&quot;tag&quot; value=&quot;blog,architecture&quot;&gt; &lt;/form&gt; &lt;/div&gt; </content:encoded><category>architecture</category><category>oidc</category><category>aws</category><category>decisions</category></item><item><title>Designing credctl&apos;s team broker (v1 RFC)</title><link>https://credctl.com/blog/team-broker-rfc/</link><guid isPermaLink="true">https://credctl.com/blog/team-broker-rfc/</guid><description>credctl is adding a team broker, and v1 is deliberately a registry, not a credential proxy — out of the issuance path entirely. Here&apos;s the design, the trade-off it accepts, and the three questions we want pushback on before we build.

</description><pubDate>Tue, 07 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Every team broker in the credential management space sits between the developer and the cloud. credctl’s team broker, in its v1 design, deliberately doesn’t.&lt;/p&gt;
&lt;p&gt;This post explains why we made that call, walks through the v1 architecture, and points at the parts we’re least sure about. The full RFC is published as a &lt;a href=&quot;https://github.com/credctl/credctl/discussions&quot;&gt;GitHub Discussion&lt;/a&gt; — this is the narrative version. If you’re going to push back on the design (and we’d like you to), the RFC is where the architectural detail lives.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-most-requested-feature-narrowed-down&quot;&gt;The most-requested feature, narrowed down&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Since the credctl CLI shipped, the most consistent demand signal has been a team broker. The pain breaks into three shapes, in roughly this order of frequency:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Policy&lt;/strong&gt; — “only senior engineers can assume the production role”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audit&lt;/strong&gt; — “who assumed what role when, across the org, in one place”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Device fleet management&lt;/strong&gt; — onboarding, off-boarding, lost laptops&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These are real problems. But they aren’t all the &lt;em&gt;same shape&lt;/em&gt; of problem, and that mattered for what v1 should be.&lt;/p&gt;
&lt;p&gt;When we looked closely at each one:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Policy&lt;/strong&gt; at the “X can assume role Y” level is already expressible in AWS IAM trust policies. The hard part is &lt;em&gt;managing&lt;/em&gt; which user maps to which role at scale, not enforcing it at issuance time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audit&lt;/strong&gt; of role assumptions is already in CloudTrail. The hard part is mapping the cryptographic identity (the JWT &lt;code dir=&quot;auto&quot;&gt;sub&lt;/code&gt; claim) back to a person, and aggregating cleanly across clouds.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Device fleet management&lt;/strong&gt; is mostly a question of which public keys are currently trusted, and revoking the ones that aren’t.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All three shapes are addressable by a &lt;em&gt;registry&lt;/em&gt; — a system that knows who’s on the team, which devices they have, and which roles they can assume, and that publishes this information to the cloud’s existing trust machinery. None of them require the broker to actually issue credentials.&lt;/p&gt;
&lt;p&gt;So v1 doesn’t.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-architectural-decision&quot;&gt;The architectural decision&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;In v1, the credctl team broker is &lt;strong&gt;not in the credential issuance path&lt;/strong&gt;. The auth flow is unchanged from single-developer credctl:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Laptop signs a JWT with the hardware-bound key (Secure Enclave or TPM 2.0)&lt;/li&gt;
&lt;li&gt;Laptop calls &lt;code dir=&quot;auto&quot;&gt;sts:AssumeRoleWithWebIdentity&lt;/code&gt; directly against AWS&lt;/li&gt;
&lt;li&gt;AWS STS fetches JWKS from the broker-hosted endpoint (cached at CloudFront)&lt;/li&gt;
&lt;li&gt;AWS STS validates the JWT against the JWKS and returns temporary credentials&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The broker’s only role at runtime is &lt;em&gt;hosting&lt;/em&gt; the JWKS. Its only role at design time is being the source of truth for the team’s roster, and emitting Terraform that keeps IAM trust policies aligned with that roster.&lt;/p&gt;
&lt;p&gt;This is the smallest possible team broker compatible with credctl’s flow.&lt;/p&gt;
&lt;p&gt;It also means the broker can be down for minutes without breaking developer auth — JWKS is served from CloudFront with aggressive TTLs, so STS hits the cache, not the broker. Broker compromise doesn’t directly compromise customer credentials: an attacker who pwned the broker could publish a malicious manifest, but the customer’s &lt;code dir=&quot;auto&quot;&gt;terraform plan&lt;/code&gt; would surface the diff before anything was applied. Broker availability is not on the critical path of credential issuance.&lt;/p&gt;
&lt;p&gt;That’s the bet. Smallest blast radius, smallest surface area, smallest operational footprint compatible with solving the registry-shaped pain.&lt;/p&gt;
&lt;p&gt;It is also &lt;em&gt;very&lt;/em&gt; different from how every other broker in this space is designed. Vault, CyberArk Conjur, Venafi, Keyfactor — even SPIFFE in many deployments — all sit in the credential path. We think the cost of being in-path isn’t justified by the marginal capability it buys for v1.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-v1-ships&quot;&gt;What v1 ships&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;In scope:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A SaaS-hosted registry of users, devices, and per-org role bindings, with GitHub OAuth as the v1 identity provider&lt;/li&gt;
&lt;li&gt;Per-org JWKS hosting, served via CloudFront&lt;/li&gt;
&lt;li&gt;An open-source Terraform module (&lt;code dir=&quot;auto&quot;&gt;credctl/terraform-credctl-aws&lt;/code&gt;, published to the HashiCorp Terraform Registry) that consumes a manifest from the broker and produces IAM trust policy resources&lt;/li&gt;
&lt;li&gt;A minimal admin web dashboard&lt;/li&gt;
&lt;li&gt;A broker-side audit log of admin actions in Postgres — complementary to CloudTrail, which remains the system of record for STS calls&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Explicitly out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Real-time revocation. JWT expiry (~1 hour by default, tunable down to 15 minutes) is the only revocation mechanism — structural to the out-of-path design.&lt;/li&gt;
&lt;li&gt;Policy beyond membership. “Alice can assume role X” — yes. Time-of-day, IP allowlists, posture, attribute-based conditions — no, not in v1.&lt;/li&gt;
&lt;li&gt;Approval workflows. (Planned for Phase 4 of the roadmap.)&lt;/li&gt;
&lt;li&gt;SSO beyond GitHub. (Planned for Phase 5.)&lt;/li&gt;
&lt;li&gt;Self-hosted deployment.&lt;/li&gt;
&lt;li&gt;GCP and Azure role mapping. The data model accommodates them; the per-cloud Terraform modules ship later.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The “explicitly out” list is doing real work in the design. Each item is something some adopter will ask about, and the answer for v1 is “not yet.” Whether any of these eventually flips to “yes” depends on what the next section asks for.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-were-uncertain-about&quot;&gt;What we’re uncertain about&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The RFC names three open questions. The most important is:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When does registry-only stop being enough?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We have candidate Phase 2 drivers — sub-minute revocation, per-request approval, cross-cloud policy, attribute-based conditions, business-context audit. But candidate scenarios aren’t the same as real ones. If your team is somewhere in the space where credctl might be a fit, the single most useful thing you can do is describe the &lt;em&gt;specific&lt;/em&gt; scenario where v1 wouldn’t solve your problem.&lt;/p&gt;
&lt;p&gt;We’d rather hear three concrete scenarios than a hundred abstract opinions.&lt;/p&gt;
&lt;p&gt;The other two open questions are smaller but not trivial:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GitHub orgs/teams as source of truth for membership, or independent of GitHub?&lt;/strong&gt; We’re defaulting to independent — membership lives in the broker’s database, decoupled from GitHub’s data model. Teams already running their access boundaries via GitHub orgs might prefer derived membership. The RFC asks for the concrete case.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Is the &lt;code dir=&quot;auto&quot;&gt;sub&lt;/code&gt; claim format right?&lt;/strong&gt; We’re proposing &lt;code dir=&quot;auto&quot;&gt;user:&amp;#x3C;user-id&gt;/device:&amp;#x3C;device-id&gt;&lt;/code&gt; because it lets IAM trust policies match on the user via &lt;code dir=&quot;auto&quot;&gt;StringLike&lt;/code&gt; while keeping per-device fidelity in CloudTrail. It’s a one-way door once customers depend on it. The RFC asks whether you’d push back before the Terraform module ships v1.0.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-to-do-next&quot;&gt;What to do next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you’d consider adopting a team broker like this, read the full RFC in the &lt;a href=&quot;https://github.com/credctl/credctl/discussions&quot;&gt;GitHub Discussion&lt;/a&gt;. It has the data model, JWKS layout, Terraform module shape, and revocation behaviour in detail. Then push back on the parts you’d hit first.&lt;/p&gt;
&lt;p&gt;Channels for response:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;a href=&quot;https://github.com/credctl/credctl/discussions&quot;&gt;RFC discussion on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Direct: &lt;code dir=&quot;auto&quot;&gt;rfc@credctl.com&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Comment window is ~2 weeks. Implementation begins once the architectural questions settle. The Phase 2 question can stay open longer — we’d rather gather real scenarios than design speculatively.&lt;/p&gt;
&lt;div&gt; &lt;h3&gt;Stay up to date&lt;/h3&gt; &lt;p&gt;Get notified about new credctl releases, security best practices, and hardware identity insights.&lt;/p&gt; &lt;form action=&quot;https://buttondown.com/api/emails/embed-subscribe/credctl&quot; method=&quot;post&quot; target=&quot;popupwindow&quot;&gt; &lt;div&gt; &lt;input type=&quot;email&quot; name=&quot;email&quot; placeholder=&quot;you@example.com&quot; required aria-label=&quot;Email address&quot;&gt; &lt;button type=&quot;submit&quot;&gt;Subscribe&lt;/button&gt; &lt;/div&gt; &lt;input type=&quot;hidden&quot; name=&quot;tag&quot; value=&quot;blog,rfc,team-broker&quot;&gt; &lt;/form&gt; &lt;/div&gt; </content:encoded><category>rfc</category><category>team-broker</category><category>architecture</category><category>design</category></item><item><title>The Death of the Long-Lived Cloud Credential</title><link>https://credctl.com/blog/death-of-the-long-lived-cloud-credential/</link><guid isPermaLink="true">https://credctl.com/blog/death-of-the-long-lived-cloud-credential/</guid><description>Long-lived access keys are the COBOL of cloud authentication — they predate the tools that replaced them and survive on inertia. Here&apos;s why the next five years end them, and what&apos;s arriving in their place.

</description><pubDate>Mon, 25 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In 2014, AWS shipped OIDC federation for STS. That was the moment the long-lived cloud access key became legacy.&lt;/p&gt;
&lt;p&gt;It didn’t die then, of course. Legacy doesn’t work that way. The first production systems to migrate were the CI runners — by around 2021, GitHub Actions shipped native OIDC federation and it became embarrassing to paste an access key into a pipeline. Kubernetes followed, via IRSA on EKS, Workload Identity on GKE, and the equivalent on AKS. Cloud-provider-internal services — Lambda, Cloud Run, App Service — had never needed long-lived keys in the first place.&lt;/p&gt;
&lt;p&gt;That left one stubborn holdout. The developer laptop.&lt;/p&gt;
&lt;p&gt;This post is about why the long-lived access key is dying, what’s replacing it, and why the last place it survives — the developer workstation — is the next domino.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-brief-history-of-cloud-credentials&quot;&gt;A brief history of cloud credentials&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The AWS access key was born in 2006 with S3 and EC2. It was, at the time, a reasonable design. You had one key, you put it in a config file, you called the API. There was no identity federation. There was no OIDC. There was no workload identity. There was barely a cloud, and there definitely weren’t SOC 2 auditors pointing at your IAM console with disapproval.&lt;/p&gt;
&lt;p&gt;For roughly the first decade of cloud, the long-lived access key was how everything worked. Every service, every pipeline, every developer workstation. The story of cloud credential management from 2006 to about 2018 is “how do we encrypt, rotate, and detect the theft of long-lived keys” — a set of defensive patches on a design that predated the right answer.&lt;/p&gt;
&lt;p&gt;The right answer arrived gradually:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;2014 — AWS STS OIDC federation.&lt;/strong&gt; &lt;code dir=&quot;auto&quot;&gt;AssumeRoleWithWebIdentity&lt;/code&gt; landed with OIDC 1.0 support. Any service producing a signed JWT from a trusted issuer could exchange it for short-lived credentials. Nobody noticed for years.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2019 — EKS IRSA.&lt;/strong&gt; Kubernetes pods got native AWS credentials without managing access keys. First widely-deployed OIDC federation in the wild.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2021 — GitHub Actions OIDC.&lt;/strong&gt; CI runners stopped being the worst-offender category overnight. Within 18 months, every serious infra-as-code pipeline had migrated.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2022 — AWS IAM Roles Anywhere.&lt;/strong&gt; The explicit “we acknowledge you have workloads outside AWS” product. Uses X.509 certificates, but the shape is the same: no long-lived access keys.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2023–2024 — Azure Federated Identity Credentials, GCP Workload Identity Federation cross-cloud.&lt;/strong&gt; The remaining big clouds shipped equivalent primitives. They all accept the same OIDC token format.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By 2024, any greenfield production workload authenticating to a cloud provider with a long-lived access key was doing it wrong on purpose. The tools existed. The primitives existed. The cross-cloud standards existed. What remained was migration and muscle memory.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-the-developer-laptop-survived&quot;&gt;Why the developer laptop survived&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The developer laptop is a harder problem than the CI runner, for three reasons.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;There’s no platform root of trust.&lt;/strong&gt; A Kubernetes pod runs in a cluster whose service account is signed by a known issuer. A GitHub Actions job runs in a runner whose token is signed by GitHub. There’s a &lt;em&gt;platform&lt;/em&gt; that vouches for the workload. A laptop doesn’t have one. If a developer says “give me AWS credentials,” there’s no clean answer to “on whose authority?”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The human is in the loop.&lt;/strong&gt; A CI runner can get a fresh JWT every 15 minutes and nobody cares. A developer wants to run &lt;code dir=&quot;auto&quot;&gt;aws s3 ls&lt;/code&gt; and get credentials, not do a browser dance. Friction that’s invisible for services is a showstopper for people.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The old pattern works.&lt;/strong&gt; &lt;code dir=&quot;auto&quot;&gt;~/.aws/credentials&lt;/code&gt; still works. The AWS SDK still reads it. &lt;code dir=&quot;auto&quot;&gt;aws configure&lt;/code&gt; still writes to it. Every tutorial in existence tells you to paste your access key into it. Incremental improvement is available — aws-vault, Granted, AWS SSO — but the baseline pattern refuses to die.&lt;/p&gt;
&lt;p&gt;Each of these has an answer arriving now.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-replacing-it&quot;&gt;What’s replacing it&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The replacement isn’t one thing. It’s a stack.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;1-hardware-as-the-laptops-root-of-trust&quot;&gt;1. Hardware as the laptop’s root of trust&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Every Mac since 2016 has a Secure Enclave — a dedicated chip that generates keys and refuses to export them. Windows 11 ships with TPM 2.0. Modern Linux laptops have TPM 2.0. The hardware already exists on every developer’s machine.&lt;/p&gt;
&lt;p&gt;The shift is in how it’s used. Until recently, these chips were used for Touch ID, BitLocker, and FileVault — consumer features. What’s changing is that the signing primitive (sign a JWT with a non-extractable hardware key) is being wired up to workload identity protocols. Once a laptop can produce a signed JWT from a hardware-bound key, it looks to AWS/GCP/Azure like any other federated workload.&lt;/p&gt;
&lt;p&gt;This is the piece credctl exists to fill. Not novel cryptography — novel plumbing.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;2-oidc-federation-as-the-universal-verb&quot;&gt;2. OIDC federation as the universal verb&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The fact that AWS, GCP, and Azure all landed on OIDC federation is one of those quiet miracles of interop. Same JWT format. Same claims structure. Same JWKS discovery. The identity provider can be a laptop, a CI runner, a cluster, or an enterprise IdP — the cloud doesn’t care, as long as the issuer is trusted and the token validates.&lt;/p&gt;
&lt;p&gt;This is what makes “one identity, all clouds” a real architecture and not a marketing line. A Secure Enclave key on a laptop can federate to AWS, GCP, and Azure from a single issuer. The plumbing is boring. The plumbing being boring is the win.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;3-short-lived-everything&quot;&gt;3. Short-lived everything&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The quiet cultural shift is that “short-lived” has become the default expectation. Kubernetes service account tokens rotate. GitHub Actions JWTs expire in minutes. STS credentials last an hour. The entire mental model of “I have a credential” is being replaced with “I have an identity that can produce fresh credentials on demand.”&lt;/p&gt;
&lt;p&gt;Once “fresh credentials on demand” is the default, long-lived access keys become weird. They’re the thing that doesn’t expire. They’re the thing that works from anywhere. They’re the thing that survives key rotation policies because the rotation window is longer than the attack cycle. They become &lt;em&gt;the anomaly&lt;/em&gt;, not the norm.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-forces-closing-the-last-gap&quot;&gt;The forces closing the last gap&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Three things are ending the long-lived key on the laptop, all at once.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Breaches keep making the argument for us.&lt;/strong&gt; CircleCI, LastPass, Uber, Codecov, Vercel — every year the list gets longer. The pattern is consistent: long-lived credentials on developer machines, stolen, replayed, catastrophic blast radius. Every incident is another data point in favour of “what if there was nothing to steal?”&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Compliance is catching up.&lt;/strong&gt; NIS2 Article 21(2)(j) explicitly calls out cryptographic controls for access management. SOC 2 CC6.1 is being interpreted more strictly. DORA pushes toward demonstrable key lifecycle controls. Auditors are starting to ask the question “why do developers have 90-day access keys?” — and “because we rotate them” is no longer a satisfying answer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Platform defaults are changing.&lt;/strong&gt; Vercel, after April 2026, made &lt;code dir=&quot;auto&quot;&gt;Sensitive&lt;/code&gt; the default for new environment variables. GitHub made OIDC federation the recommended path for cloud deploys. AWS’s own docs now lead with “use IAM Identity Center or federation” before mentioning access keys. When the defaults flip, the population follows.&lt;/p&gt;
&lt;p&gt;None of these forces are decisive on their own. Together, they’re a ratchet. Every incremental step makes the next one easier and the reverse harder.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-after-looks-like&quot;&gt;What “after” looks like&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The world on the other side of this transition has a specific shape.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Every credential is short-lived.&lt;/strong&gt; A human asks for AWS access and gets 60 minutes. A service gets a new JWT every 15 minutes. Long-lived material exists — but it’s a signing key, not a bearer credential, and it lives in hardware that refuses to export it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Every identity has a hardware anchor.&lt;/strong&gt; A workload’s identity is rooted in the platform that runs it (Kubernetes service account, cluster OIDC issuer, Lambda execution role). A human’s identity is rooted in the device they’re using, attested by the Secure Enclave or TPM. There’s no “credential file” category anymore — there’s identity, and there’s ephemeral proof of identity.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“Rotate all the secrets” becomes nonsense.&lt;/strong&gt; The next Vercel-shaped incident still happens — platforms get breached, employees get phished, integrations get compromised. But the cleanup changes. When there are no long-lived bearer secrets to have been stolen, “rotate everything” doesn’t apply. You investigate, contain, fix. You don’t burn down N customer credential surfaces.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Auditors change their questions.&lt;/strong&gt; “When did you last rotate your access keys?” becomes “show me the key-generation attestation.” That’s a better question. It doesn’t have “we rotate every 90 days” as an acceptable answer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The plaintext credentials file stops being a default artefact.&lt;/strong&gt; &lt;code dir=&quot;auto&quot;&gt;~/.aws/credentials&lt;/code&gt; doesn’t disappear — it’ll linger for legacy scripts — but it stops being what new developers are taught to use. The thing that replaces it is &lt;code dir=&quot;auto&quot;&gt;credctl auth&lt;/code&gt; or its equivalent.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;a-specific-prediction&quot;&gt;A specific prediction&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here’s the claim I’ll stake out: by 2030, provisioning a long-lived cloud access key for a human will be seen the way we now see &lt;code dir=&quot;auto&quot;&gt;chmod 777&lt;/code&gt; on a config file.&lt;/p&gt;
&lt;p&gt;Not impossible. Not blocked by the platform. Just evidence that someone didn’t know the better way, or skipped it out of expediency, and it’ll be the first thing a code review or audit flags.&lt;/p&gt;
&lt;p&gt;The signs are already there. AWS buries access keys deeper in the console every year. GCP has made it progressively harder to generate long-lived service account keys. Azure has deprioritised the flow in its docs. The tooling is converging — aws-vault, Granted, credctl, the various SSO solutions — on “don’t have long-lived keys on the machine.” The only question is how long the tail is.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-credctl-fits&quot;&gt;Where credctl fits&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;credctl is one tool in a broader movement. It’s specifically the answer for the macOS developer laptop talking to AWS or GCP — hardware-bound OIDC federation, no server to run, no long-lived keys anywhere. Linux TPM 2.0 support is on the roadmap. Azure FIC is on the roadmap. Team broker functionality is on the roadmap.&lt;/p&gt;
&lt;p&gt;But credctl is not the whole answer. The whole answer is every platform and every tool converging on “short-lived credentials from a hardware-rooted identity.” Vault’s JWT auth method, Granted’s SSO integration, GitHub Actions OIDC, workload identity on every cluster — they’re all facets of the same shift.&lt;/p&gt;
&lt;p&gt;The long-lived cloud access key had a good run. It survived the arrival of every better pattern for nearly two decades. But the combined weight of breach pressure, compliance, platform defaults, and now laptop-level hardware federation is breaking the last thing holding it up.&lt;/p&gt;
&lt;p&gt;It’s going to take a few more years, and a few more breaches, and a few more auditor escalations. But it’s going.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-to-go-from-here&quot;&gt;Where to go from here&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://credctl.com/quickstart/&quot;&gt;credctl quickstart&lt;/a&gt; — 5 minutes to hardware-bound AWS or GCP credentials&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://credctl.com/blog/vercel-context-ai-breach/&quot;&gt;The Vercel breach analysis&lt;/a&gt; — why bearer secrets are the root cost&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://credctl.com/blog/secure-enclave-deep-dive/&quot;&gt;Secure Enclave deep dive&lt;/a&gt; — how hardware-bound signing works&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://credctl.com/blog/why-i-built-credctl/&quot;&gt;Why I built credctl&lt;/a&gt; — the origin story&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt; &lt;h3&gt;Stay up to date&lt;/h3&gt; &lt;p&gt;Get notified about new credctl releases, security best practices, and hardware identity insights.&lt;/p&gt; &lt;form action=&quot;https://buttondown.com/api/emails/embed-subscribe/credctl&quot; method=&quot;post&quot; target=&quot;popupwindow&quot;&gt; &lt;div&gt; &lt;input type=&quot;email&quot; name=&quot;email&quot; placeholder=&quot;you@example.com&quot; required aria-label=&quot;Email address&quot;&gt; &lt;button type=&quot;submit&quot;&gt;Subscribe&lt;/button&gt; &lt;/div&gt; &lt;input type=&quot;hidden&quot; name=&quot;tag&quot; value=&quot;blog,vision&quot;&gt; &lt;/form&gt; &lt;/div&gt; </content:encoded><category>vision</category><category>security</category><category>credentials</category><category>workload-identity</category></item><item><title>credctl vs Granted: Workflow Ergonomics vs Hardware Identity</title><link>https://credctl.com/blog/credctl-vs-granted/</link><guid isPermaLink="true">https://credctl.com/blog/credctl-vs-granted/</guid><description>Granted is best-in-class for AWS SSO role switching and browser-console isolation. credctl replaces long-lived keys with hardware-bound OIDC federation. They address overlapping but distinct pain points.

</description><pubDate>Mon, 18 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://www.granted.dev/&quot;&gt;Granted&lt;/a&gt; is the tool I recommend to anyone juggling twelve AWS accounts through IAM Identity Center. Its browser-profile console UX is the best in the ecosystem — opening three production accounts in three isolated Chrome profiles at once, each with the correct colour-coded badge, is genuinely delightful.&lt;/p&gt;
&lt;p&gt;credctl is doing something different. This post lays out the distinction honestly, including the cases where Granted is a better answer than anything credctl will offer.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-granted-is-built-for&quot;&gt;What Granted is built for&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Granted, built by &lt;a href=&quot;https://commonfate.io/&quot;&gt;Common Fate&lt;/a&gt;, is focused on the day-to-day ergonomics of working across multiple AWS roles and accounts.&lt;/p&gt;
&lt;p&gt;The highlights:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;granted console&lt;/code&gt;&lt;/strong&gt; — opens the AWS console in a named browser profile so that signing into Role A doesn’t log you out of Role B. Each profile is visually distinct (colour, badge) to prevent “I clicked delete in the wrong account” accidents&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;granted exec&lt;/code&gt;&lt;/strong&gt; — like &lt;code dir=&quot;auto&quot;&gt;aws-vault exec&lt;/code&gt;, runs a command with credentials for a given profile injected as environment variables&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SSO / IAM Identity Center first-class support&lt;/strong&gt; — discovery of available accounts and roles, session caching, device authorisation flows&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-account role discovery&lt;/strong&gt; — pulls the list of available roles from your Identity Center directory&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;assume&lt;/code&gt; command&lt;/strong&gt; — ergonomic role switching in the terminal&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Team governance layer&lt;/strong&gt; (Common Fate product, paid) — access requests, approvals, time-bound grants&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The underlying credential mechanism is IAM access keys + STS role assumption, same as aws-vault. The Granted layer is the workflow around it: multi-account sanity, browser isolation, approval workflows.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-credctl-is-built-for&quot;&gt;What credctl is built for&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;credctl’s scope is smaller and deeper. It replaces the long-lived AWS credentials on a developer’s laptop with a hardware-bound identity backed by the macOS Secure Enclave.&lt;/p&gt;
&lt;p&gt;The private key is generated in the Secure Enclave, never leaves the chip, and signs JWTs that AWS accepts via OIDC federation. Credentials are short-lived (1 hour) and require Touch ID per signing operation.&lt;/p&gt;
&lt;p&gt;Granted asks “how do I make multi-account AWS access ergonomic?” credctl asks “why is there a long-lived credential on the laptop at all?”&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;side-by-side&quot;&gt;Side-by-side&lt;/h2&gt;&lt;/div&gt;




























































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;Granted&lt;/th&gt;&lt;th&gt;credctl&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Primary problem&lt;/td&gt;&lt;td&gt;Multi-account AWS workflow ergonomics&lt;/td&gt;&lt;td&gt;Eliminating long-lived creds on the laptop&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Underlying AWS auth&lt;/td&gt;&lt;td&gt;IAM access keys or SSO + AssumeRole&lt;/td&gt;&lt;td&gt;OIDC federation via Secure Enclave JWT&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Long-lived credentials on disk?&lt;/td&gt;&lt;td&gt;Encrypted in OS keychain&lt;/td&gt;&lt;td&gt;None&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Root of trust&lt;/td&gt;&lt;td&gt;OS keychain / SSO session&lt;/td&gt;&lt;td&gt;Hardware (Secure Enclave)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Best feature&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;granted console&lt;/code&gt; (browser profiles)&lt;/td&gt;&lt;td&gt;Non-extractable signing key&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;IAM Identity Center support&lt;/td&gt;&lt;td&gt;First-class&lt;/td&gt;&lt;td&gt;Not applicable (different auth model)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Multi-account role switching&lt;/td&gt;&lt;td&gt;First-class&lt;/td&gt;&lt;td&gt;Possible but manual&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Platform support&lt;/td&gt;&lt;td&gt;macOS, Linux, Windows&lt;/td&gt;&lt;td&gt;macOS today&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Non-AWS clouds&lt;/td&gt;&lt;td&gt;AWS-focused&lt;/td&gt;&lt;td&gt;AWS and GCP today&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Team governance&lt;/td&gt;&lt;td&gt;Yes (paid tier)&lt;/td&gt;&lt;td&gt;No (single user)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;div&gt;&lt;h2 id=&quot;where-granted-clearly-wins&quot;&gt;Where Granted clearly wins&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Multi-account AWS console work.&lt;/strong&gt; If a significant portion of your day is opening the AWS console across multiple accounts and roles, Granted’s browser profile UX is genuinely unmatched. There’s no equivalent in credctl because credctl isn’t trying to solve console access at all.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;IAM Identity Center workflows.&lt;/strong&gt; Granted is built around the Identity Center device authorisation flow, account/role discovery, and session caching. If your org standardised on Identity Center (a sensible choice for most enterprises), Granted fits that world natively. credctl uses a different auth model — direct OIDC federation — that’s orthogonal to Identity Center.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Linux and Windows.&lt;/strong&gt; Granted works across all three platforms today. credctl’s Secure Enclave dependency means macOS-only until Linux TPM 2.0 support ships.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Team access governance.&lt;/strong&gt; Common Fate’s paid tier adds just-in-time access, approval workflows, and time-bound role grants. This is a real problem for larger orgs, and nothing in credctl’s scope addresses it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You already have credentials via SSO.&lt;/strong&gt; If your company hands you access via Identity Center, you don’t have long-lived access keys — the SSO flow issues short-lived ones on each login. Granted makes that flow ergonomic. credctl’s “no long-lived keys” pitch is already satisfied.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-credctl-clearly-wins&quot;&gt;Where credctl clearly wins&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Your threat model includes endpoint compromise.&lt;/strong&gt; Granted’s security model assumes the OS keychain and the SSO session cache are safe. An attacker with enough privilege on the machine can extract both. credctl’s signing key is in hardware — it can’t be stolen, only used while the Touch ID prompt is active.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You’re outside Identity Center.&lt;/strong&gt; Small teams, solo developers, or orgs that use direct IAM federate without Identity Center don’t gain much from Granted’s SSO-centric features. credctl works for any AWS account where you can deploy the OIDC setup.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You want multi-cloud from one identity.&lt;/strong&gt; credctl targets AWS and GCP (Azure on roadmap) with a single Secure Enclave key. Granted is AWS-focused. If you’re moving between AWS and GCP daily, one hardware identity is simpler than two credential systems.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You want hardware attestation per signing, not per session.&lt;/strong&gt; SSO sessions typically last hours. credctl prompts Touch ID on every request. Stronger attestation, more friction — right for high-trust roles, probably too much for day-to-day browsing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You don’t want to depend on an identity provider being up.&lt;/strong&gt; Granted’s SSO flow depends on Identity Center / IdP availability. credctl’s flow only depends on the cloud provider’s STS endpoint.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;can-you-use-them-together&quot;&gt;Can you use them together?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Yes, and it’s a reasonable pattern for larger orgs.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Granted for day-to-day SSO-backed account navigation.&lt;/strong&gt; Console access, multi-account discovery, browser isolation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;credctl for high-trust direct role access.&lt;/strong&gt; Production deploys, infra admin, audit log signing — anywhere the extra hardware-bound attestation is worth the ceremony.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The two tools don’t conflict. They produce the same thing at the end (&lt;code dir=&quot;auto&quot;&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;AWS_SESSION_TOKEN&lt;/code&gt;) via different paths. A developer can use &lt;code dir=&quot;auto&quot;&gt;granted exec&lt;/code&gt; for a sandbox role and &lt;code dir=&quot;auto&quot;&gt;credctl auth --profile prod&lt;/code&gt; for a production role in the same terminal session.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-honest-framing&quot;&gt;The honest framing&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;These tools are not direct competitors. They solve overlapping but distinct problems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Granted makes AWS &lt;em&gt;workflow&lt;/em&gt; better&lt;/li&gt;
&lt;li&gt;credctl makes AWS &lt;em&gt;identity&lt;/em&gt; stronger&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If your pain is “I keep deleting things in the wrong account,” that’s Granted. If your pain is “we audit a lot and someone keeps asking why engineers have 90-day access keys,” that’s credctl.&lt;/p&gt;
&lt;p&gt;Most teams have some of both pains. Pick the one that hurts most first.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-to-go-from-here&quot;&gt;Where to go from here&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://credctl.com/quickstart/&quot;&gt;credctl quickstart&lt;/a&gt; — 5 minutes from install to hardware-bound credentials&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://credctl.com/guides/aws-setup/&quot;&gt;AWS setup guide&lt;/a&gt; — what &lt;code dir=&quot;auto&quot;&gt;credctl setup aws&lt;/code&gt; deploys&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://credctl.com/guides/oidc-vs-roles-anywhere/&quot;&gt;OIDC vs Roles Anywhere&lt;/a&gt; — why credctl uses the federation path&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.granted.dev/&quot;&gt;Granted&lt;/a&gt; — if multi-account ergonomics is your primary pain, start here&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt; &lt;h3&gt;Stay up to date&lt;/h3&gt; &lt;p&gt;Get notified about new credctl releases, security best practices, and hardware identity insights.&lt;/p&gt; &lt;form action=&quot;https://buttondown.com/api/emails/embed-subscribe/credctl&quot; method=&quot;post&quot; target=&quot;popupwindow&quot;&gt; &lt;div&gt; &lt;input type=&quot;email&quot; name=&quot;email&quot; placeholder=&quot;you@example.com&quot; required aria-label=&quot;Email address&quot;&gt; &lt;button type=&quot;submit&quot;&gt;Subscribe&lt;/button&gt; &lt;/div&gt; &lt;input type=&quot;hidden&quot; name=&quot;tag&quot; value=&quot;blog,comparison&quot;&gt; &lt;/form&gt; &lt;/div&gt; </content:encoded><category>comparison</category><category>granted</category><category>security</category><category>credentials</category></item><item><title>credctl vs HashiCorp Vault: Different Problems, Not a Replacement</title><link>https://credctl.com/blog/credctl-vs-hashicorp-vault/</link><guid isPermaLink="true">https://credctl.com/blog/credctl-vs-hashicorp-vault/</guid><description>Vault is a centralised secrets manager for teams and services. credctl replaces the long-lived AWS keys on your laptop with hardware-bound credentials. They solve different problems — and often work well together.

</description><pubDate>Mon, 11 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Every few weeks I see someone on Hacker News say “just use Vault” in response to a post about credential management. It’s usually well-meaning and usually wrong — or at least, it conflates two different problems.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.vaultproject.io/&quot;&gt;HashiCorp Vault&lt;/a&gt; is a centralised secrets management system. credctl replaces the long-lived AWS/GCP credentials on a developer’s laptop with hardware-bound, short-lived ones. Both care about secrets. They operate at different layers of the stack.&lt;/p&gt;
&lt;p&gt;This post lays out what each tool is actually for, where they overlap, and why the right answer for many teams is “both.”&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-vault-is-for&quot;&gt;What Vault is for&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Vault is a server you run (or buy as HCP Vault Cloud) that stores, rotates, and issues secrets for an entire organisation. Services authenticate to it, prove their identity, and request secrets with narrow scopes and short lifetimes.&lt;/p&gt;
&lt;p&gt;The headline features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Static secret storage&lt;/strong&gt; — API keys, database passwords, certificates, SSH keys, encryption keys&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dynamic secrets&lt;/strong&gt; — Vault generates ephemeral credentials on demand (database users, AWS IAM credentials via the AWS secrets engine, signed SSH certs) and revokes them when the lease expires&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Policy engine&lt;/strong&gt; — fine-grained HCL policies mapping identities to the secrets they can access&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audit log&lt;/strong&gt; — every request is logged for compliance&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Many auth methods&lt;/strong&gt; — Kubernetes service accounts, AWS IAM, JWT/OIDC, LDAP, AppRole, cloud auth methods&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PKI, transit encryption, KV stores, totp, SSH CA, and about 30 other backends&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Vault is a platform. It’s what you reach for when your organisation has dozens of services and hundreds of secrets and needs a central control plane over all of it.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-credctl-is-for&quot;&gt;What credctl is for&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;credctl has a much narrower scope: replace the long-lived cloud credentials on a developer’s laptop.&lt;/p&gt;
&lt;p&gt;The private key lives in the macOS Secure Enclave (hardware, non-extractable). &lt;code dir=&quot;auto&quot;&gt;credctl auth&lt;/code&gt; signs a JWT with that key and exchanges it for short-lived AWS STS or GCP Workload Identity Federation credentials via OIDC federation. No server, no agent, no SaaS.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;credctl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;auth&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Touch ID prompt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;ASIA&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;There are no plaintext AWS keys anywhere. There’s no central server to run. There’s nothing to audit beyond what the cloud provider already logs (&lt;code dir=&quot;auto&quot;&gt;AssumeRoleWithWebIdentity&lt;/code&gt; shows up in CloudTrail with the device fingerprint as the subject).&lt;/p&gt;
&lt;p&gt;Scope-wise: credctl manages &lt;em&gt;one identity&lt;/em&gt; (your device), talks to &lt;em&gt;one class of service&lt;/em&gt; (cloud providers that accept OIDC federation), and does &lt;em&gt;one thing&lt;/em&gt; (sign a JWT and exchange it). It’s the laptop counterpart to what GitHub Actions gets from GitHub’s OIDC issuer.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;side-by-side&quot;&gt;Side-by-side&lt;/h2&gt;&lt;/div&gt;























































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;HashiCorp Vault&lt;/th&gt;&lt;th&gt;credctl&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Primary purpose&lt;/td&gt;&lt;td&gt;Organisation-wide secrets management&lt;/td&gt;&lt;td&gt;Developer-laptop cloud credential replacement&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Deployment&lt;/td&gt;&lt;td&gt;Run a server (self-hosted) or buy HCP Vault Cloud&lt;/td&gt;&lt;td&gt;Install a CLI&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Scope&lt;/td&gt;&lt;td&gt;Everything — DB creds, API keys, certs, cloud creds, SSH, PKI&lt;/td&gt;&lt;td&gt;AWS + GCP short-lived credentials&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Auth root-of-trust&lt;/td&gt;&lt;td&gt;Whatever auth method you use to log in&lt;/td&gt;&lt;td&gt;Secure Enclave hardware key&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Audit log&lt;/td&gt;&lt;td&gt;Central, built-in&lt;/td&gt;&lt;td&gt;CloudTrail / GCP audit logs&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Multi-user&lt;/td&gt;&lt;td&gt;Yes, first-class&lt;/td&gt;&lt;td&gt;Single user, per-device&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Requires infrastructure&lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Works offline&lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;td&gt;Yes (for cached credentials)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Platform support&lt;/td&gt;&lt;td&gt;Linux, macOS, Windows, many runtimes&lt;/td&gt;&lt;td&gt;macOS today&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;div&gt;&lt;h2 id=&quot;where-vault-clearly-wins&quot;&gt;Where Vault clearly wins&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Anything beyond cloud credentials.&lt;/strong&gt; Database passwords, third-party API keys, Stripe webhook secrets, TLS certificates — credctl has nothing to say about these. Vault handles all of them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Team and organisation-wide policy.&lt;/strong&gt; If your security team needs to enforce “nobody can read the production database password without going through this policy,” that’s Vault. credctl has no policy surface.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dynamic secrets.&lt;/strong&gt; Vault’s AWS secrets engine can mint a fresh IAM user with a specific policy for each request, revoke it on lease expiry, and audit the whole cycle. credctl does something conceptually similar for STS AssumeRole, but Vault does it across dozens of systems beyond AWS.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Services, not just laptops.&lt;/strong&gt; A Kubernetes pod that needs a database password is a Vault-shaped problem. credctl is specifically aimed at the human-on-a-laptop case.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Compliance evidence.&lt;/strong&gt; Vault’s audit log is a well-understood artefact auditors recognise. credctl’s audit story is “look at CloudTrail,” which is honest but less complete.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-credctl-clearly-wins&quot;&gt;Where credctl clearly wins&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Zero infrastructure.&lt;/strong&gt; A team can adopt credctl today with one &lt;code dir=&quot;auto&quot;&gt;brew install&lt;/code&gt;. Standing up Vault means servers, HA, storage backends, unseal ceremonies, upgrade plans, disaster recovery. For most of what developers actually do with cloud credentials — running &lt;code dir=&quot;auto&quot;&gt;terraform apply&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;aws s3 ls&lt;/code&gt; — that infrastructure is overkill.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hardware-bound identity.&lt;/strong&gt; Vault’s security model ultimately depends on the auth method you use to log in. If a developer authenticates to Vault with a username/password or a long-lived token stored in their shell history, Vault’s policy engine doesn’t help against endpoint compromise. credctl’s identity is in hardware — the device identity can’t be stolen, even with full local privileges.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Offline operation.&lt;/strong&gt; When Vault is down, everything that depends on it is down. credctl’s cached short-lived credentials keep working until they expire, and a fresh &lt;code dir=&quot;auto&quot;&gt;credctl auth&lt;/code&gt; only needs the cloud provider’s STS endpoint to succeed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Touch ID per signing.&lt;/strong&gt; Every &lt;code dir=&quot;auto&quot;&gt;credctl auth&lt;/code&gt; requires a Touch ID confirmation. Vault logins are typically cached for hours or days. Different tradeoff — more friction, more attestation.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;using-them-together&quot;&gt;Using them together&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The cleanest mental model: Vault for everything &lt;em&gt;except&lt;/em&gt; developer-to-cloud authentication, credctl for that last mile.&lt;/p&gt;
&lt;p&gt;Concrete patterns:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Developer laptop → cloud.&lt;/strong&gt; credctl. Hardware-bound, no server, minimal setup.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Service → database.&lt;/strong&gt; Vault. Dynamic credentials, audit log, policy.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Service → third-party API.&lt;/strong&gt; Vault. Static secret storage, rotation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Service → cloud.&lt;/strong&gt; Cloud-native workload identity (EKS IRSA, GKE Workload Identity, App Role for Azure). Skip both tools — the platform does it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Developer → Vault (to fetch team secrets).&lt;/strong&gt; Authenticate to Vault using OIDC federation from credctl’s JWT. The Secure Enclave key becomes the root of trust for Vault access too. Vault supports this via its &lt;a href=&quot;https://developer.hashicorp.com/vault/docs/auth/jwt&quot;&gt;JWT/OIDC auth method&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That last pattern is worth calling out: you can chain them. credctl produces a signed JWT, Vault’s JWT auth method validates it, and Vault issues a token with the appropriate policy. Now your Vault identity is anchored in hardware you can’t extract from.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;when-to-not-bother-with-vault&quot;&gt;When to not bother with Vault&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Small teams with only cloud credential needs often don’t need Vault at all. If your secrets are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AWS credentials (credctl)&lt;/li&gt;
&lt;li&gt;GCP credentials (credctl)&lt;/li&gt;
&lt;li&gt;GitHub Actions cloud deploys (built-in OIDC)&lt;/li&gt;
&lt;li&gt;Kubernetes service accounts (platform-native)&lt;/li&gt;
&lt;li&gt;Database passwords managed by your cloud provider (RDS IAM auth, Cloud SQL IAM)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;…you can get away without running a secrets manager at all. Vault is excellent, but it’s also operationally significant. Don’t take it on unless you’ve identified a class of secret that the platform-native options don’t handle.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;when-to-absolutely-use-vault&quot;&gt;When to absolutely use Vault&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If your organisation has any of these, Vault earns its weight:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dozens of services each needing their own distinct secrets&lt;/li&gt;
&lt;li&gt;Compliance requirements that mandate a central audit log for secret access&lt;/li&gt;
&lt;li&gt;A need for dynamic secrets at scale (per-request database credentials)&lt;/li&gt;
&lt;li&gt;Secrets that must live on-prem or in an air-gapped environment&lt;/li&gt;
&lt;li&gt;A policy engine requirement that the cloud-native options can’t satisfy&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;where-to-go-from-here&quot;&gt;Where to go from here&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;credctl’s scope is narrow and intentional. For the developer-laptop-to-cloud path, hardware-bound + no infrastructure is a strong combination. Vault is the right answer for most of the problems credctl doesn’t touch.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://credctl.com/quickstart/&quot;&gt;credctl quickstart&lt;/a&gt; — 5 minutes from install to working credentials&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://credctl.com/guides/aws-setup/&quot;&gt;AWS setup guide&lt;/a&gt; — what happens during &lt;code dir=&quot;auto&quot;&gt;credctl setup aws&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://credctl.com/blog/secure-enclave-deep-dive/&quot;&gt;Secure Enclave deep dive&lt;/a&gt; — the hardware identity model&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.vaultproject.io/&quot;&gt;HashiCorp Vault&lt;/a&gt; — if you’re at the scale where you need it, you probably already know&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt; &lt;h3&gt;Stay up to date&lt;/h3&gt; &lt;p&gt;Get notified about new credctl releases, security best practices, and hardware identity insights.&lt;/p&gt; &lt;form action=&quot;https://buttondown.com/api/emails/embed-subscribe/credctl&quot; method=&quot;post&quot; target=&quot;popupwindow&quot;&gt; &lt;div&gt; &lt;input type=&quot;email&quot; name=&quot;email&quot; placeholder=&quot;you@example.com&quot; required aria-label=&quot;Email address&quot;&gt; &lt;button type=&quot;submit&quot;&gt;Subscribe&lt;/button&gt; &lt;/div&gt; &lt;input type=&quot;hidden&quot; name=&quot;tag&quot; value=&quot;blog,comparison&quot;&gt; &lt;/form&gt; &lt;/div&gt; </content:encoded><category>comparison</category><category>hashicorp-vault</category><category>security</category><category>credentials</category></item><item><title>credctl vs aws-vault: When to Use Each</title><link>https://credctl.com/blog/credctl-vs-aws-vault/</link><guid isPermaLink="true">https://credctl.com/blog/credctl-vs-aws-vault/</guid><description>aws-vault keeps your long-lived AWS keys encrypted in the OS keychain and hands out short-lived STS sessions. credctl replaces the long-lived keys entirely with hardware-bound OIDC federation. Here&apos;s an honest comparison.

</description><pubDate>Mon, 04 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://github.com/99designs/aws-vault&quot;&gt;aws-vault&lt;/a&gt; is one of the most loved tools in the AWS ecosystem for a reason. It solved a real problem — plaintext AWS keys in &lt;code dir=&quot;auto&quot;&gt;~/.aws/credentials&lt;/code&gt; — back when no one else was solving it, and it solved it well. If you’re using aws-vault today, you’re already ahead of 80% of developers on credential hygiene.&lt;/p&gt;
&lt;p&gt;credctl takes a different approach to the same underlying problem. This post compares the two fairly, and flags the cases where each is the better fit.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-both-tools-are-trying-to-fix&quot;&gt;What both tools are trying to fix&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;A default AWS setup leaves long-lived access keys sitting in &lt;code dir=&quot;auto&quot;&gt;~/.aws/credentials&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[default]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;aws_access_key_id&lt;/span&gt;&lt;span&gt; = AKIAIOSFODNN7EXAMPLE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;aws_secret_access_key&lt;/span&gt;&lt;span&gt; = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Plaintext. Never expires. Usable from any machine once copied. Readable by any process running as your user. This is the root problem both tools address.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-aws-vault-solves-it&quot;&gt;How aws-vault solves it&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;aws-vault encrypts your long-lived AWS keys at rest in the OS keychain (macOS Keychain, gnome-keyring on Linux, Windows Credential Manager). When you run a command, it asks the keychain for the key, calls &lt;code dir=&quot;auto&quot;&gt;sts:GetSessionToken&lt;/code&gt; or &lt;code dir=&quot;auto&quot;&gt;sts:AssumeRole&lt;/code&gt;, and injects the resulting short-lived credentials into the process environment.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;aws-vault&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;exec&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;my-profile&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;aws&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;s3&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ls&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The long-lived keys are in the keychain, not on disk. Short-lived session credentials never touch disk at all — they’re passed via environment variables to a single process and discarded when it exits. MFA via TOTP is supported. Role assumption chains work. It integrates cleanly with AWS IAM Identity Center (SSO).&lt;/p&gt;
&lt;p&gt;This is genuinely good security. It materially raises the bar for an attacker.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-credctl-solves-it&quot;&gt;How credctl solves it&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;credctl eliminates the long-lived AWS keys entirely.&lt;/p&gt;
&lt;p&gt;Your Mac’s Secure Enclave generates an ECDSA P-256 key pair. The private key lives in hardware and cannot be exported — not by malware, not by &lt;code dir=&quot;auto&quot;&gt;root&lt;/code&gt;, not by forensic disk imaging. AWS is configured to trust a static OIDC issuer backed by your public key. When you need credentials, credctl signs a JWT with the hardware key and exchanges it for short-lived STS credentials via &lt;code dir=&quot;auto&quot;&gt;AssumeRoleWithWebIdentity&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;credctl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;auth&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;# Touch ID prompt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;ASIA&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AWS_SESSION_TOKEN&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;There are no long-lived AWS credentials involved. The only long-lived thing in the system is the Secure Enclave key, and that can’t be stolen.&lt;/p&gt;
&lt;p&gt;See the &lt;a href=&quot;https://credctl.com/quickstart/&quot;&gt;quickstart&lt;/a&gt; or the &lt;a href=&quot;https://credctl.com/blog/secure-enclave-deep-dive/&quot;&gt;Secure Enclave deep dive&lt;/a&gt; for how the OIDC federation is wired up.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;side-by-side&quot;&gt;Side-by-side&lt;/h2&gt;&lt;/div&gt;























































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;aws-vault&lt;/th&gt;&lt;th&gt;credctl&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Long-lived AWS keys on the machine?&lt;/td&gt;&lt;td&gt;Yes (encrypted in keychain)&lt;/td&gt;&lt;td&gt;No&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Root of trust&lt;/td&gt;&lt;td&gt;OS keychain (software)&lt;/td&gt;&lt;td&gt;Secure Enclave (hardware)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Can the root-of-trust key be extracted?&lt;/td&gt;&lt;td&gt;Yes, with keychain + system compromise&lt;/td&gt;&lt;td&gt;No, non-extractable by hardware&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Per-use attestation&lt;/td&gt;&lt;td&gt;TOTP MFA (optional)&lt;/td&gt;&lt;td&gt;Touch ID (every signing operation)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AWS integration&lt;/td&gt;&lt;td&gt;IAM access keys + AssumeRole / SSO&lt;/td&gt;&lt;td&gt;OIDC federation + AssumeRoleWithWebIdentity&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Other clouds&lt;/td&gt;&lt;td&gt;AWS only&lt;/td&gt;&lt;td&gt;AWS and GCP today, Azure on roadmap&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Platform support&lt;/td&gt;&lt;td&gt;macOS, Linux, Windows&lt;/td&gt;&lt;td&gt;macOS only today (Linux TPM planned)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Setup&lt;/td&gt;&lt;td&gt;Use existing AWS keys&lt;/td&gt;&lt;td&gt;Deploys OIDC infrastructure on first run&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Ecosystem maturity&lt;/td&gt;&lt;td&gt;10+ years, huge community&lt;/td&gt;&lt;td&gt;New, smaller&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;div&gt;&lt;h2 id=&quot;when-aws-vault-is-the-better-choice&quot;&gt;When aws-vault is the better choice&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;You’re on Linux or Windows.&lt;/strong&gt; credctl relies on the macOS Secure Enclave; Linux TPM 2.0 support is on the roadmap but not yet shipped. aws-vault works everywhere.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You need AWS IAM Identity Center (SSO).&lt;/strong&gt; aws-vault has first-class support for IAM Identity Center flows with the browser redirect, device authorisation, and session caching. credctl’s model is different — it’s OIDC federation against a custom issuer, not SSO.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You already have working AWS keys in a mature workflow.&lt;/strong&gt; If your team’s CI, scripts, and tooling all assume aws-vault, there’s real switching cost. aws-vault is a strict improvement over plaintext keys in &lt;code dir=&quot;auto&quot;&gt;~/.aws/credentials&lt;/code&gt;, and staying on it is a reasonable choice.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You don’t control your AWS account.&lt;/strong&gt; credctl needs to deploy an OIDC identity provider, IAM role, and CloudFormation stack the first time you run &lt;code dir=&quot;auto&quot;&gt;credctl setup aws&lt;/code&gt;. If you’re in an org where you can’t make those changes, aws-vault is a drop-in improvement that doesn’t need account-level config.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Your threat model is stolen disk images and dotfile repos, not targeted malware.&lt;/strong&gt; aws-vault’s keychain encryption covers the common cases. Hardware attestation is overkill if the risk you’re managing is “I synced my &lt;code dir=&quot;auto&quot;&gt;~/.aws&lt;/code&gt; directory to a public GitHub repo.”&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;when-credctl-is-the-better-choice&quot;&gt;When credctl is the better choice&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Your threat model includes compromised endpoints.&lt;/strong&gt; The CircleCI and LastPass breaches both started with malware on developer machines. Keychain-encrypted keys are still &lt;em&gt;decryptable&lt;/em&gt; by a sufficiently privileged attacker with a running session. A Secure Enclave signing key is not. If you’re a target — SOC 2 auditor, crypto team, infra engineer at a high-profile company — this difference matters.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You want no long-lived cloud credentials on the machine at all.&lt;/strong&gt; Even encrypted keychain storage means there’s a long-lived secret that can, under some conditions, become plaintext. credctl’s only long-lived material is a non-extractable hardware key.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You’re multi-cloud.&lt;/strong&gt; One &lt;code dir=&quot;auto&quot;&gt;credctl init&lt;/code&gt; produces a single hardware identity that works with both AWS and GCP today. aws-vault is, by design, AWS-only. If you’re juggling AWS access keys and GCP service account JSON files, credctl collapses both into one hardware-rooted identity.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You want per-use attestation, not per-session.&lt;/strong&gt; aws-vault’s MFA is typically once per session (typical session: hours). credctl prompts Touch ID on every signing operation. That’s stronger attestation but also more friction — decide based on your role.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You’re setting up a new project and can deploy OIDC federation cleanly.&lt;/strong&gt; &lt;code dir=&quot;auto&quot;&gt;credctl setup aws&lt;/code&gt; handles the CloudFormation, IAM, and OIDC wiring automatically. If you’re starting fresh, there’s no reason to provision long-lived keys at all.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;can-you-use-them-together&quot;&gt;Can you use them together?&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Yes, and it’s sometimes the right answer.&lt;/p&gt;
&lt;p&gt;aws-vault and credctl both produce the same thing at the end: &lt;code dir=&quot;auto&quot;&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;AWS_SESSION_TOKEN&lt;/code&gt; environment variables. Any tool that reads those will work with either.&lt;/p&gt;
&lt;p&gt;Practical hybrid setups:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Migrate gradually.&lt;/strong&gt; Keep aws-vault for existing profiles while standing up credctl for new projects or new cloud accounts. Both work side by side.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;credctl for direct role access, aws-vault for SSO.&lt;/strong&gt; If your org uses IAM Identity Center for most access but you want hardware-bound creds for a specific high-trust role (production deploys, infra admin), you can do both.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Different threat models for different accounts.&lt;/strong&gt; Dev sandbox via aws-vault, prod via credctl. Entirely reasonable.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;choosing-based-on-threat-model&quot;&gt;Choosing based on threat model&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The honest framing: aws-vault and credctl represent two different answers to “how much should I trust software?”&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;aws-vault&lt;/strong&gt; trusts the OS keychain. If the keychain is honest and the process reading it is honest, the keys stay protected. This is true the vast majority of the time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;credctl&lt;/strong&gt; doesn’t require that trust. The signing key is in hardware that refuses to cooperate with software trying to extract it. Malware with full user privileges still can’t steal the key — it can only use it while the Touch ID prompt is active.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both are defensible positions. The question is how much of your security budget you want to spend on reducing the “vast majority of the time” to “always.”&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-to-go-from-here&quot;&gt;Where to go from here&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://credctl.com/quickstart/&quot;&gt;Quickstart&lt;/a&gt; — from &lt;code dir=&quot;auto&quot;&gt;brew install&lt;/code&gt; to working AWS credentials in 5 minutes&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://credctl.com/guides/aws-setup/&quot;&gt;AWS setup guide&lt;/a&gt; — what &lt;code dir=&quot;auto&quot;&gt;credctl setup aws&lt;/code&gt; does under the hood&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://credctl.com/blog/secure-enclave-deep-dive/&quot;&gt;Secure Enclave deep dive&lt;/a&gt; — how hardware-bound signing actually works&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://credctl.com/guides/oidc-vs-roles-anywhere/&quot;&gt;OIDC vs Roles Anywhere&lt;/a&gt; — why credctl uses one and not the other&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you’re already using aws-vault, you’ve done the hard part — you’ve accepted that plaintext keys are the enemy. Moving to credctl is a step in the same direction, not a change in philosophy.&lt;/p&gt;
&lt;div&gt; &lt;h3&gt;Stay up to date&lt;/h3&gt; &lt;p&gt;Get notified about new credctl releases, security best practices, and hardware identity insights.&lt;/p&gt; &lt;form action=&quot;https://buttondown.com/api/emails/embed-subscribe/credctl&quot; method=&quot;post&quot; target=&quot;popupwindow&quot;&gt; &lt;div&gt; &lt;input type=&quot;email&quot; name=&quot;email&quot; placeholder=&quot;you@example.com&quot; required aria-label=&quot;Email address&quot;&gt; &lt;button type=&quot;submit&quot;&gt;Subscribe&lt;/button&gt; &lt;/div&gt; &lt;input type=&quot;hidden&quot; name=&quot;tag&quot; value=&quot;blog,comparison&quot;&gt; &lt;/form&gt; &lt;/div&gt; </content:encoded><category>comparison</category><category>aws-vault</category><category>security</category><category>credentials</category></item><item><title>Vercel, Context.ai, and the Case Against Bearer Secrets</title><link>https://credctl.com/blog/vercel-context-ai-breach/</link><guid isPermaLink="true">https://credctl.com/blog/vercel-context-ai-breach/</guid><description>One OAuth token at a third-party AI vendor turned into &apos;rotate all your secrets&apos; for a subset of Vercel customers. The blast radius wasn&apos;t an accident of this breach — it&apos;s the default for extractable, long-lived credentials.

</description><pubDate>Tue, 21 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;On 19 April 2026, Vercel &lt;a href=&quot;https://vercel.com/kb/bulletin/vercel-april-2026-security-incident&quot;&gt;disclosed&lt;/a&gt; that an attacker had accessed a subset of internal systems and customer environment variables. The entry point wasn’t a Vercel product vulnerability. It was an OAuth grant.&lt;/p&gt;
&lt;p&gt;Here’s the chain, as reported:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A Vercel employee had authorised &lt;strong&gt;Context.ai&lt;/strong&gt;, a third-party AI tool, against their Google Workspace account — a normal OAuth integration with broad scopes.&lt;/li&gt;
&lt;li&gt;Context.ai itself was breached.&lt;/li&gt;
&lt;li&gt;The attacker used the existing OAuth grant to take over the Vercel employee’s Google account.&lt;/li&gt;
&lt;li&gt;From there, they pivoted into Vercel’s internal systems and exfiltrated corporate credentials, ~580 employee PII records, and customer environment variables that weren’t flagged &lt;code dir=&quot;auto&quot;&gt;Sensitive&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Vercel’s guidance to affected customers: assume API keys, tokens, database credentials, and signing keys stored as non-Sensitive env vars have been compromised. Rotate them.&lt;/p&gt;
&lt;p&gt;This is the part of the story that matters for anyone thinking about credential architecture.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-blast-radius-is-the-story&quot;&gt;The blast radius is the story&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Vercel did a lot of things right in the disclosure. Fast IOC publication, law-enforcement engagement, coordination with GitHub/Microsoft/npm/Socket, and a product change making &lt;code dir=&quot;auto&quot;&gt;Sensitive&lt;/code&gt; the default for new environment variables going forward.&lt;/p&gt;
&lt;p&gt;But the cleanup cost — the thing landing on thousands of engineering teams right now — is a direct consequence of what the stolen data &lt;em&gt;was&lt;/em&gt;: static, long-lived, extractable bearer secrets sitting in plaintext in a platform database.&lt;/p&gt;
&lt;p&gt;Bearer secrets have a defining property. They work from anywhere. An attacker who reads the secret can replay it from their own infrastructure, at their own pace, against any service that accepts it. There’s nothing about an AWS access key, a Stripe secret, a Postgres URI, or a signing key that cares who is holding it.&lt;/p&gt;
&lt;p&gt;That’s what makes “rotate everything” the only safe response. The team doing the rotation has no way to prove which specific secrets have been used maliciously and which haven’t, because a stolen bearer secret leaves no fingerprint that distinguishes legitimate use from attacker use.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-env-vars-still-look-like-the-right-answer&quot;&gt;Why env vars still look like the right answer&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Environment variables are a 2010s secret-storage pattern that survived into 2026 for one reason: nothing more ergonomic replaced them for the “my web app needs an API key” case.&lt;/p&gt;
&lt;p&gt;The tooling around env vars is genuinely excellent. Vercel’s platform exposes them cleanly at build and runtime, pulls them into local dev, integrates with secret managers, and now defaults to encrypted storage with restricted access. The ergonomics are close to unbeatable.&lt;/p&gt;
&lt;p&gt;The problem is architectural, not tooling. Encryption at rest with platform-held keys is a software mitigation. A sufficiently privileged internal account — or a successful intrusion on the platform itself — can still read the plaintext. The Vercel incident is a reminder that this is not a theoretical risk.&lt;/p&gt;
&lt;p&gt;And the failure mode compounds. Context.ai → Google Workspace → Vercel → &lt;em&gt;N&lt;/em&gt; customers → downstream cloud providers, databases, signing infrastructure, third-party APIs. One OAuth breach exposed the entire upstream credential surface of everyone using the affected platform. The secrets don’t care that they’re on the attacker’s laptop instead of Vercel’s servers.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-breaks-the-chain&quot;&gt;What breaks the chain&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The alternative pattern — already deployed for production workloads in most clouds — is to stop storing bearer secrets altogether.&lt;/p&gt;
&lt;p&gt;There are two flavours of this, and they address different environments.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In the cloud&lt;/strong&gt;, the root of trust is the platform itself. A Kubernetes pod gets a projected service account token signed by the cluster. A GitHub Actions runner gets a JWT signed by GitHub’s OIDC issuer. The cloud provider trusts the issuer, validates the JWT, and returns short-lived credentials via AWS STS, GCP Workload Identity Federation, or Azure Federated Identity Credentials. No signing key on the workload — the platform vouches for it. Stolen short-lived credentials expire in minutes, and there’s nothing long-lived to steal in the first place.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Off the cloud&lt;/strong&gt; — developer workstations, on-prem services, edge devices — there’s no platform to vouch. The signing key has to live somewhere. The answer is hardware: TPM 2.0 on Linux and Windows, Secure Enclave on macOS, dedicated HSMs for higher-assurance workloads. The key is generated in the chip, never leaves it, and can’t be exported by malware or disk imaging. The device becomes its own root of trust, and the same federation protocols issue the same short-lived credentials on top.&lt;/p&gt;
&lt;p&gt;None of this is new. Production services should have been authenticating this way for years.&lt;/p&gt;
&lt;p&gt;For the Vercel case, the hardware-bound version of the same architecture would have broken the chain at two points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Employee side.&lt;/strong&gt; The OAuth session that enabled the initial takeover was itself a long-lived bearer secret on the employee’s device. A hardware-bound session — gated by device attestation and a non-extractable key — couldn’t be silently exfiltrated by compromising a connected SaaS app. The attacker would need the physical device.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Customer side.&lt;/strong&gt; If customer workloads authenticated to AWS/GCP/Stripe/Postgres via federated, short-lived credentials rather than long-lived env-var secrets, the stolen env vars wouldn’t be usable off the issuing workload. There would be nothing to “rotate” because there would be nothing static to steal.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The rotation burn-down that Vercel customers are running this week is the direct cost of the architecture. Hardware-bound, short-lived credentials don’t eliminate incidents — they eliminate the catastrophic replay phase that turns an incident into a cleanup project.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;where-credctl-fits&quot;&gt;Where credctl fits&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;credctl is the developer-workstation version of this pattern. Your Mac’s Secure Enclave generates a non-extractable signing key. &lt;code dir=&quot;auto&quot;&gt;credctl auth&lt;/code&gt; signs a JWT with that hardware-bound key and exchanges it for short-lived AWS or GCP credentials via OIDC federation. Credentials last one hour. The signing key can’t leave the chip.&lt;/p&gt;
&lt;p&gt;It doesn’t solve the whole Vercel problem. Production workloads still need their own identity. Platform vendors still need to rotate their internal OAuth hygiene. SaaS integrations still need scoped, auditable grants.&lt;/p&gt;
&lt;p&gt;But it removes one class of long-lived, extractable credential from the developer’s laptop entirely — the class that shows up in the first line of nearly every postmortem: “the attacker exfiltrated credentials from a developer machine.”&lt;/p&gt;
&lt;p&gt;The next time a platform has a rough week, the teams running hardware-bound credentials will be reading the postmortem with interest rather than with a panicked &lt;code dir=&quot;auto&quot;&gt;grep -r secret&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;sources&quot;&gt;Sources&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://vercel.com/kb/bulletin/vercel-april-2026-security-incident&quot;&gt;Vercel April 2026 security incident bulletin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.bleepingcomputer.com/news/security/vercel-confirms-breach-as-hackers-claim-to-be-selling-stolen-data/&quot;&gt;Vercel confirms breach as hackers claim to be selling stolen data — BleepingComputer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.theregister.com/2026/04/20/vercel_context_ai_security_incident/&quot;&gt;Vercel warns customer creds compromised — The Register&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.gitguardian.com/vercel-april-2026-incident-non-sensitive-environment-variables-need-investigation-too/&quot;&gt;Non-Sensitive Environment Variables Need Investigation Too — GitGuardian&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt; &lt;h3&gt;Stay up to date&lt;/h3&gt; &lt;p&gt;Get notified about new credctl releases, security best practices, and hardware identity insights.&lt;/p&gt; &lt;form action=&quot;https://buttondown.com/api/emails/embed-subscribe/credctl&quot; method=&quot;post&quot; target=&quot;popupwindow&quot;&gt; &lt;div&gt; &lt;input type=&quot;email&quot; name=&quot;email&quot; placeholder=&quot;you@example.com&quot; required aria-label=&quot;Email address&quot;&gt; &lt;button type=&quot;submit&quot;&gt;Subscribe&lt;/button&gt; &lt;/div&gt; &lt;input type=&quot;hidden&quot; name=&quot;tag&quot; value=&quot;blog,breach-analysis&quot;&gt; &lt;/form&gt; &lt;/div&gt; </content:encoded><category>security</category><category>credentials</category><category>breach-analysis</category><category>env-vars</category></item><item><title>credctl Now Supports GCP: One Hardware Identity, Two Clouds</title><link>https://credctl.com/blog/credctl-now-supports-gcp/</link><guid isPermaLink="true">https://credctl.com/blog/credctl-now-supports-gcp/</guid><description>credctl auth --provider gcp exchanges the same hardware-bound JWT for short-lived GCP access tokens via Workload Identity Federation. Same Secure Enclave key. Same OIDC provider. No new infrastructure.

</description><pubDate>Mon, 23 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;When we launched credctl, the pitch was simple: replace the plaintext AWS keys on your laptop with hardware-bound credentials using the Secure Enclave. No keys on disk. Ever.&lt;/p&gt;
&lt;p&gt;Today, we’re extending that to GCP.&lt;/p&gt;
&lt;p&gt;&lt;code dir=&quot;auto&quot;&gt;credctl auth --provider gcp&lt;/code&gt; exchanges the same hardware-bound JWT for short-lived GCP access tokens via Workload Identity Federation. Same Secure Enclave key. Same OIDC provider. No new infrastructure. No GCP SDK dependency.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-this-matters&quot;&gt;Why This Matters&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you work across AWS and GCP — and an increasing number of teams do — you now have a single device identity that works with both clouds. One &lt;code dir=&quot;auto&quot;&gt;credctl init&lt;/code&gt;, one OIDC provider, two clouds.&lt;/p&gt;
&lt;p&gt;The security model is identical to AWS:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Private key lives in the Secure Enclave. Can’t be exported.&lt;/li&gt;
&lt;li&gt;Credentials are short-lived (1 hour).&lt;/li&gt;
&lt;li&gt;Touch ID required for every signing operation.&lt;/li&gt;
&lt;li&gt;Nothing on disk to steal.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The difference is in the exchange mechanism. AWS uses a single &lt;code dir=&quot;auto&quot;&gt;AssumeRoleWithWebIdentity&lt;/code&gt; call. GCP uses a two-step exchange:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sequenceDiagram&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;participant CLI as credctl CLI&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;participant SE as Secure Enclave&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;participant STS as GCP STS&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;participant IAM as GCP IAM Credentials&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CLI-&gt;&gt;SE: Sign JWT (Touch ID)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;SE--&gt;&gt;CLI: Signed JWT&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CLI-&gt;&gt;STS: ExchangeToken (JWT)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;STS--&gt;&gt;CLI: Federated access token&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CLI-&gt;&gt;IAM: GenerateAccessToken&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;IAM--&gt;&gt;CLI: OAuth2 access token (1h)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;The CLI signs a JWT with the Secure Enclave key (same as AWS, but with GCP’s Workload Identity Provider URI as the audience).&lt;/li&gt;
&lt;li&gt;GCP STS validates the JWT against the OIDC provider and returns a federated access token.&lt;/li&gt;
&lt;li&gt;The CLI exchanges the federated token for a service account access token via the IAM Credentials API.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The result: a standard GCP OAuth2 access token, usable with gcloud, Terraform, and all GCP client libraries.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;setup-five-minutes&quot;&gt;Setup: Five Minutes&lt;/h2&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;You need credctl installed and initialised (&lt;code dir=&quot;auto&quot;&gt;credctl init&lt;/code&gt;), and AWS OIDC infrastructure deployed (&lt;code dir=&quot;auto&quot;&gt;credctl setup aws&lt;/code&gt;). GCP reuses the same OIDC provider — the CloudFront-hosted discovery documents that AWS validates are the same ones GCP validates.&lt;/p&gt;
&lt;p&gt;You also need a GCP service account with the roles you want credctl to assume.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;step-1-set-up-gcp-federation&quot;&gt;Step 1: Set up GCP federation&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;credctl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;setup&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;gcp&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--service-account&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;credctl@my-project.iam.gserviceaccount.com&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This single command handles everything: OIDC hosting on GCS, Workload Identity Pool, OIDC Provider, service account binding, and generating the credential config file at &lt;code dir=&quot;auto&quot;&gt;~/.credctl/gcp-credentials.json&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;step-2-use-it&quot;&gt;Step 2: Use it&lt;/h3&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;GOOGLE_APPLICATION_CREDENTIALS&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;~&lt;/span&gt;&lt;span&gt;/.&lt;/span&gt;&lt;span&gt;credctl&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;gcp-credentials&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;gcloud&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;storage&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ls&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Or authenticate gcloud directly:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;gcloud&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;auth&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;login&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;--cred-file=&lt;/span&gt;&lt;span&gt;~&lt;/span&gt;&lt;span&gt;/.credctl/gcp-credentials.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That’s it. Every GCP API call now uses hardware-bound, short-lived credentials.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;terraform-for-teams&quot;&gt;Terraform for Teams&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If your team prefers infrastructure-as-code over CLI setup, there’s a Terraform module at &lt;code dir=&quot;auto&quot;&gt;deploy/terraform-gcp/&lt;/code&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;module &lt;/span&gt;&lt;span&gt;&quot;credctl_gcp&quot;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;source &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;github.com/credctl/credctl//deploy/terraform-gcp&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;project_id         &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;my-project&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;device_fingerprint &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;SHA256:oKz3i9Tg...&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;issuer_url         &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;https://d1234.cloudfront.net&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;service_account_roles &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;roles/storage.objectViewer&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;roles/bigquery.dataViewer&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This creates the same resources as &lt;code dir=&quot;auto&quot;&gt;credctl setup gcp&lt;/code&gt; — Workload Identity Pool, OIDC Provider, Service Account, and IAM bindings — but version-controlled and repeatable.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;no-gcp-sdk&quot;&gt;No GCP SDK&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;One design decision worth highlighting: credctl has zero GCP SDK dependency. The GCP STS token exchange and IAM Credentials API calls are implemented with Go’s &lt;code dir=&quot;auto&quot;&gt;net/http&lt;/code&gt; stdlib. This keeps the binary small, the dependency tree minimal, and the attack surface narrow.&lt;/p&gt;
&lt;p&gt;The same is true for AWS — credctl’s only external Go dependency is &lt;a href=&quot;https://github.com/spf13/cobra&quot;&gt;spf13/cobra&lt;/a&gt; for CLI argument parsing.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-the-oidc-architecture-made-this-easy&quot;&gt;How the OIDC Architecture Made This Easy&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;When we chose OIDC federation over AWS Roles Anywhere (see &lt;a href=&quot;https://credctl.com/blog/secure-enclave-deep-dive/&quot;&gt;the technical deep dive&lt;/a&gt;), cross-cloud portability was a key reason. OIDC is a standard. AWS, GCP, and Azure all accept OIDC tokens for credential exchange. By building on OIDC, adding GCP support meant implementing the GCP-specific exchange protocol — not rebuilding identity infrastructure.&lt;/p&gt;
&lt;p&gt;The only code change to the shared JWT package was making the &lt;code dir=&quot;auto&quot;&gt;audience&lt;/code&gt; claim configurable. AWS uses &lt;code dir=&quot;auto&quot;&gt;sts.amazonaws.com&lt;/code&gt;. GCP uses the Workload Identity Provider URI. Same key, same signature, different audience.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Azure Federated Identity Credentials&lt;/strong&gt; — the third major cloud. Same OIDC architecture, Azure-specific exchange.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Linux TPM 2.0&lt;/strong&gt; — the same multi-cloud identity on Linux workstations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Credential caching daemon&lt;/strong&gt; — avoid redundant STS calls and Touch ID prompts within the credential TTL. (This is already available as an experimental feature: &lt;code dir=&quot;auto&quot;&gt;credctl daemon start&lt;/code&gt;.)&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;try-it&quot;&gt;Try It&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Update to the latest credctl:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;brew&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;upgrade&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;credctl/tap/credctl&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GCP setup guide:&lt;/strong&gt; &lt;a href=&quot;https://credctl.com/guides/gcp-setup/&quot;&gt;credctl.com/guides/gcp-setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href=&quot;https://github.com/credctl/credctl&quot;&gt;github.com/credctl/credctl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Quickstart:&lt;/strong&gt; &lt;a href=&quot;https://credctl.com/quickstart/&quot;&gt;credctl.com/quickstart&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you’re using credctl with both AWS and GCP, we’d love to hear about your experience. Open an issue or start a discussion on &lt;a href=&quot;https://github.com/credctl/credctl/discussions&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt; &lt;h3&gt;Stay up to date&lt;/h3&gt; &lt;p&gt;Get notified about new credctl releases, security best practices, and hardware identity insights.&lt;/p&gt; &lt;form action=&quot;https://buttondown.com/api/emails/embed-subscribe/credctl&quot; method=&quot;post&quot; target=&quot;popupwindow&quot;&gt; &lt;div&gt; &lt;input type=&quot;email&quot; name=&quot;email&quot; placeholder=&quot;you@example.com&quot; required aria-label=&quot;Email address&quot;&gt; &lt;button type=&quot;submit&quot;&gt;Subscribe&lt;/button&gt; &lt;/div&gt;  &lt;/form&gt; &lt;/div&gt; </content:encoded><category>gcp</category><category>security</category><category>credentials</category><category>multi-cloud</category></item><item><title>credctl: Eliminating Plaintext Cloud Keys with the Secure Enclave</title><link>https://credctl.com/blog/why-i-built-credctl/</link><guid isPermaLink="true">https://credctl.com/blog/why-i-built-credctl/</guid><description>86% of breaches involve stolen credentials. Your AWS keys sit in plaintext on disk. The Secure Enclave can fix this — credctl connects the pieces.

</description><pubDate>Sat, 07 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;div&gt;&lt;h2 id=&quot;the-problem-nobody-talks-about&quot;&gt;The Problem Nobody Talks About&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here’s a number that should keep you up at night: 86% of breaches involve stolen credentials. In 2025 alone, infostealer malware exfiltrated 1.8 billion credentials from 5.8 million devices — an 800% surge over recent years.&lt;/p&gt;
&lt;p&gt;And where are your cloud credentials right now?&lt;/p&gt;
&lt;p&gt;If you’re like most developers, the answer is &lt;code dir=&quot;auto&quot;&gt;~/.aws/credentials&lt;/code&gt;. A plaintext file. On your laptop. Readable by any process running as your user.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;[default]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;aws_access_key_id = AKIAIOSFODNN7EXAMPLE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That file is the single most common credential pattern in cloud computing. It never expires. It works from any machine. And it can be stolen by any malware with file access, copied from disk images, committed to dotfile repos, or extracted from a stolen laptop.&lt;/p&gt;
&lt;p&gt;Think about what that means in practice. You install a VS Code extension, a Homebrew package, or a Python library — any of them could read &lt;code dir=&quot;auto&quot;&gt;~/.aws/credentials&lt;/code&gt; silently. You back up your disk, and those keys are in the backup. You sell your old laptop and don’t wipe it properly, and those keys go with it. You share your dotfiles on GitHub and accidentally include your credentials directory.&lt;/p&gt;
&lt;p&gt;The breach data tells the same story, over and over:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CircleCI (2023):&lt;/strong&gt; Malware on a developer laptop exfiltrated a session cookie. The attacker accessed production systems and forced all customers to rotate their secrets.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LastPass (2022):&lt;/strong&gt; A developer workstation was compromised. Credentials from the first breach were used to access cloud storage containing customer vault backups. The result was one of the most catastrophic data breaches in password manager history.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Okta (2023):&lt;/strong&gt; A stolen service account credential, stored in a personal Google account synced from a company device, gave attackers access to every customer’s support case files.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Uber (2022):&lt;/strong&gt; Stolen contractor credentials combined with MFA fatigue bombing compromised internal systems. The former CISO was criminally convicted.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Codecov (2021):&lt;/strong&gt; A modified CI script exfiltrated environment variables — including credentials — from thousands of downstream customers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Every one of these traces back to the same pattern: long-lived credentials accessible on a developer machine. The attackers aren’t breaking encryption or exploiting zero-days. They’re reading files.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-gap-that-shouldnt-exist&quot;&gt;The Gap That Shouldn’t Exist&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here’s what’s strange. Your Mac has had a Secure Enclave since 2016. It’s a hardware security module — a dedicated chip that generates cryptographic keys, stores them in hardware, and never allows them to be exported. The private key physically cannot leave the chip. Not by malware, not by root access, not by forensic disk imaging.&lt;/p&gt;
&lt;p&gt;Your Mac uses the Secure Enclave for Touch ID, Apple Pay, and FileVault. Banks use it. Governments use it. Apple’s corecrypto module is FIPS 140-2 validated.&lt;/p&gt;
&lt;p&gt;Meanwhile, AWS has supported OIDC federation since 2014. Any service that can produce a signed JWT can exchange it for short-lived STS credentials via &lt;code dir=&quot;auto&quot;&gt;AssumeRoleWithWebIdentity&lt;/code&gt;. No long-lived keys required.&lt;/p&gt;
&lt;p&gt;The Secure Enclave can sign JWTs. AWS can accept them. Nothing connected the two.&lt;/p&gt;
&lt;p&gt;That’s the gap credctl fills.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-credctl-does&quot;&gt;What credctl Does&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;credctl uses your Mac’s Secure Enclave to replace long-lived cloud access keys with short-lived, hardware-bound credentials. It works with both AWS and GCP today, with Azure on the roadmap.&lt;/p&gt;
&lt;p&gt;The private key lives in the Secure Enclave — it can never be exported, copied, or stolen. When you need AWS credentials, credctl signs a JWT with that hardware-bound key and exchanges it for short-lived STS credentials via OIDC federation. The credentials last one hour. There’s nothing on disk to steal.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;$ credctl init&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✓ Generated ECDSA P-256 key pair in Secure Enclave&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✓ Exported public key to ~/.credctl/device.pub&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✓ Device ID: SHA256:oKz3i9Tg...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;$ credctl setup aws --policy-arn arn:aws:iam::123456789012:policy/DevAccess&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✓ Deployed CloudFormation stack (S3, CloudFront, OIDC provider, IAM role)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✓ OIDC issuer URL: https://d1234.cloudfront.net&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;$ credctl auth&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✓ Built JWT (iss=https://d1234.cloudfront.net, sub=SHA256:oKz3i9Tg...)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✓ Signed with Secure Enclave (Touch ID)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✓ Exchanged for STS credentials (expires in 1h)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export AWS_ACCESS_KEY_ID=ASIA...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export AWS_SECRET_ACCESS_KEY=...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export AWS_SESSION_TOKEN=...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;That’s it. Five minutes from install to working credentials. No server infrastructure. No SSO portal. No browser redirects.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;how-it-works&quot;&gt;How It Works&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The architecture is straightforward:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sequenceDiagram&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;participant Dev as Developer&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;participant CLI as credctl CLI&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;participant SE as Secure Enclave&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;participant STS as AWS STS&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;participant OIDC as OIDC Provider&amp;#x3C;br/&gt;(S3 + CloudFront)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Dev-&gt;&gt;CLI: credctl auth&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CLI-&gt;&gt;CLI: Build JWT (iss, sub, aud)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CLI-&gt;&gt;SE: Sign JWT (ECDSA P-256 / ES256)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;SE--&gt;&gt;CLI: Signed JWT&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CLI-&gt;&gt;STS: AssumeRoleWithWebIdentity&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;STS-&gt;&gt;OIDC: Fetch JWKS (public key)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;STS-&gt;&gt;STS: Validate signature, assume role&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;STS--&gt;&gt;CLI: Short-lived credentials (1h)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CLI--&gt;&gt;Dev: AWS credentials&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;credctl init&lt;/code&gt;&lt;/strong&gt; generates an ECDSA P-256 key pair inside the Secure Enclave. The private key never leaves the hardware. The public key is exported.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;credctl setup aws&lt;/code&gt;&lt;/strong&gt; creates an S3 bucket to host OIDC discovery documents, an IAM OIDC identity provider that trusts the issuer URL, an IAM role configured for web identity federation, and configures the AWS CLI profile — all in a single command.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;credctl auth&lt;/code&gt;&lt;/strong&gt; builds a JWT with your issuer URL, device ID, and &lt;code dir=&quot;auto&quot;&gt;sts.amazonaws.com&lt;/code&gt; as the audience. The Secure Enclave signs it (Touch ID prompt). The CLI sends it to STS via &lt;code dir=&quot;auto&quot;&gt;AssumeRoleWithWebIdentity&lt;/code&gt;. STS fetches your JWKS, validates the signature, and returns short-lived credentials.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The CLI is written in Go. The Secure Enclave integration uses cgo to call Apple’s Security framework directly (&lt;code dir=&quot;auto&quot;&gt;SecKeyCreateRandomKey&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;SecKeyCreateSignature&lt;/code&gt;). The STS call is a raw HTTP POST — no AWS SDK dependency. The only external dependency is spf13/cobra for CLI argument parsing.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;multi-cloud-aws-and-gcp-today&quot;&gt;Multi-Cloud: AWS and GCP Today&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;credctl’s OIDC architecture was designed for cross-cloud portability from the start. The same Secure Enclave key and OIDC provider work with both AWS and GCP.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GCP setup is just as simple:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;$ credctl setup gcp --service-account credctl@my-project.iam.gserviceaccount.com&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✓ Created Workload Identity Pool&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✓ Created OIDC Provider (using existing issuer URL)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✓ Bound service account to device identity&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;$ credctl setup gcp-cred-file&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;✓ Wrote credential config to ~/.credctl/gcp-credentials.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;$ export GOOGLE_APPLICATION_CREDENTIALS=~/.credctl/gcp-credentials.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;$ gcloud storage ls&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;One hardware identity. Two clouds. No keys on disk for either.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-it-doesnt-do-yet&quot;&gt;What It Doesn’t Do (Yet)&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;To be clear about scope — credctl today is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;macOS only.&lt;/strong&gt; Secure Enclave integration works on Apple Silicon (M1–M4) and Intel Macs with T2 chip. Linux TPM 2.0 support is planned but not yet implemented.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AWS and GCP.&lt;/strong&gt; Azure Federated Identity Credentials are on the roadmap.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Individual use.&lt;/strong&gt; Direct Mode means each developer manages their own OIDC endpoint. A team broker with policy enforcement, audit trails, and device fleet management is planned for a future phase.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;credctl doesn’t replace HashiCorp Vault for secrets management. It replaces the plaintext cloud keys on your laptop. Different problem, different tool.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The roadmap expands in concentric circles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Azure:&lt;/strong&gt; Federated Identity Credentials, completing the three major clouds with one hardware identity.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Linux TPM 2.0:&lt;/strong&gt; The same architecture using TPM 2.0 on Linux workstations. Windows 11 requires TPM 2.0, so the hardware is already there.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Credential broker for teams:&lt;/strong&gt; A centralised broker that sits between developers and cloud providers. Policy enforcement (who can assume which roles, from which devices), approval workflows, and a full audit trail mapped to SOC 2, NIS2, DORA, and ISO 27001 controls.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h2 id=&quot;try-it&quot;&gt;Try It&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;credctl is open source (Apache 2.0).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href=&quot;https://github.com/credctl/credctl&quot;&gt;github.com/credctl/credctl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Quickstart:&lt;/strong&gt; &lt;a href=&quot;https://credctl.com/quickstart/&quot;&gt;Read the quickstart guide&lt;/a&gt; — install to working credentials in under 5 minutes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Technical deep dive:&lt;/strong&gt; &lt;a href=&quot;https://credctl.com/blog/secure-enclave-deep-dive/&quot;&gt;How credctl Uses the macOS Secure Enclave for Cloud Credentials&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Questions and feedback welcome in &lt;a href=&quot;https://github.com/credctl/credctl/discussions&quot;&gt;GitHub Discussions&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt; &lt;h3&gt;Stay up to date&lt;/h3&gt; &lt;p&gt;Get notified about new credctl releases, security best practices, and hardware identity insights.&lt;/p&gt; &lt;form action=&quot;https://buttondown.com/api/emails/embed-subscribe/credctl&quot; method=&quot;post&quot; target=&quot;popupwindow&quot;&gt; &lt;div&gt; &lt;input type=&quot;email&quot; name=&quot;email&quot; placeholder=&quot;you@example.com&quot; required aria-label=&quot;Email address&quot;&gt; &lt;button type=&quot;submit&quot;&gt;Subscribe&lt;/button&gt; &lt;/div&gt; &lt;input type=&quot;hidden&quot; name=&quot;tag&quot; value=&quot;blog,launch&quot;&gt; &lt;/form&gt; &lt;/div&gt; </content:encoded><category>launch</category><category>security</category><category>credentials</category></item><item><title>How credctl Uses the macOS Secure Enclave for Cloud Credentials</title><link>https://credctl.com/blog/secure-enclave-deep-dive/</link><guid isPermaLink="true">https://credctl.com/blog/secure-enclave-deep-dive/</guid><description>A technical walkthrough of credctl&apos;s architecture — from Secure Enclave key generation through cgo, JWT construction, and OIDC federation with AWS STS.

</description><pubDate>Sat, 07 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This post explains the architecture of &lt;a href=&quot;https://github.com/credctl/credctl&quot;&gt;credctl&lt;/a&gt; — a CLI tool that replaces long-lived AWS access keys with short-lived, hardware-bound credentials using the macOS Secure Enclave. For the problem statement and product overview, read &lt;a href=&quot;https://credctl.com/blog/why-i-built-credctl/&quot;&gt;Eliminating Plaintext Cloud Keys&lt;/a&gt;. This post is the “how.”&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-the-secure-enclave-actually-is&quot;&gt;What the Secure Enclave Actually Is&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The Secure Enclave is a hardware security subsystem isolated from the main processor. On Apple Silicon Macs (M1–M4), it’s a dedicated core within the SoC. On Intel Macs with T2, it’s a separate chip. Either way, it has its own boot process, its own encrypted memory, and its own secure storage.&lt;/p&gt;
&lt;p&gt;What matters for credctl:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Key generation happens inside the hardware.&lt;/strong&gt; When you ask the Secure Enclave to create a key pair, the private key is generated inside the chip and never leaves it. There is no API to export it. There is no memory region to read it from. The hardware enforces this — it’s not a software policy.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Signing happens inside the hardware.&lt;/strong&gt; You send data to the Secure Enclave, it signs it with the private key, and returns the signature. The private key is never exposed to the application, the operating system, or the CPU.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;User presence verification.&lt;/strong&gt; The Secure Enclave gates key operations behind biometric (Touch ID) or passcode verification. Even with root access, a process cannot silently use a Secure Enclave key without the user’s explicit approval.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;FIPS 140-2 validated.&lt;/strong&gt; Apple’s corecrypto module, which underpins Secure Enclave operations, has FIPS 140-2 validation. This is the same level of certification used by government and financial systems.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Secure Enclave supports ECDSA with the P-256 curve (also known as secp256r1 or prime256v1). This maps directly to the ES256 algorithm in JWTs — which is exactly what credctl needs.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;credctls-architecture&quot;&gt;credctl’s Architecture&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;credctl is a Go CLI that bridges the Secure Enclave’s cryptographic capabilities to cloud provider credential APIs. The architecture has five components:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;graph TB&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;subgraph &quot;credctl CLI&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CMD[Command Layer&amp;#x3C;br/&gt;cobra]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CFG[Config Manager&amp;#x3C;br/&gt;~/.credctl/config.json]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ENC[Enclave Interface&amp;#x3C;br/&gt;cgo → Security.framework]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;JWT[JWT Builder&amp;#x3C;br/&gt;ES256 tokens]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;STS_C[AWS STS Client&amp;#x3C;br/&gt;AssumeRoleWithWebIdentity]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;subgraph &quot;Hardware&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;SE[Secure Enclave&amp;#x3C;br/&gt;ECDSA P-256]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;subgraph &quot;AWS&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;STS[AWS STS]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;OIDC_P[OIDC Provider&amp;#x3C;br/&gt;S3 + CloudFront]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;end&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CMD --&gt; CFG&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CMD --&gt; ENC&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CMD --&gt; JWT&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CMD --&gt; STS_C&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ENC --&gt; SE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;JWT --&gt; ENC&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;STS_C --&gt; STS&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;STS --&gt; OIDC_P&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h3 id=&quot;the-enclave-interface&quot;&gt;The Enclave Interface&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;This is the critical piece. The Secure Enclave is accessed through Apple’s Security framework, which is an Objective-C/C API. Go doesn’t call C APIs natively, so credctl uses cgo — Go’s foreign function interface for C code.&lt;/p&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;Enclave&lt;/code&gt; interface abstracts hardware operations:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; Enclave &lt;/span&gt;&lt;span&gt;interface&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;Available&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;GenerateKey&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tag&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;ecdsa.PublicKey, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;LoadKey&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tag&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;) (&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;ecdsa.PublicKey, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;DeleteKey&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tag&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;Sign&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tag&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/span&gt;&lt;span&gt;byte&lt;/span&gt;&lt;span&gt;) ([]&lt;/span&gt;&lt;span&gt;byte&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;On macOS, the implementation calls Security framework functions via cgo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;GenerateKey&lt;/code&gt;&lt;/strong&gt; calls &lt;code dir=&quot;auto&quot;&gt;SecKeyCreateRandomKey&lt;/code&gt; with &lt;code dir=&quot;auto&quot;&gt;kSecAttrTokenIDSecureEnclave&lt;/code&gt; to create an ECDSA P-256 key pair inside the Secure Enclave. The key is tagged with a unique identifier so it can be loaded later. The function returns the public key — the private key stays in hardware.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;Sign&lt;/code&gt;&lt;/strong&gt; calls &lt;code dir=&quot;auto&quot;&gt;SecKeyCreateSignature&lt;/code&gt; with the &lt;code dir=&quot;auto&quot;&gt;kSecKeyAlgorithmECDSASignatureMessageX962SHA256&lt;/code&gt; algorithm. The Secure Enclave signs the data and returns the DER-encoded signature. By default (&lt;code dir=&quot;auto&quot;&gt;--biometric=any&lt;/code&gt;), this triggers a Touch ID prompt with passcode fallback. This behaviour is configurable via the &lt;code dir=&quot;auto&quot;&gt;--biometric&lt;/code&gt; flag at &lt;code dir=&quot;auto&quot;&gt;credctl init&lt;/code&gt; time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;LoadKey&lt;/code&gt;&lt;/strong&gt; retrieves a previously generated key by its tag using &lt;code dir=&quot;auto&quot;&gt;SecItemCopyMatching&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Build tags dispatch the implementation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;darwin.go&lt;/code&gt; — the real Secure Enclave implementation via cgo&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;other.go&lt;/code&gt; — a stub that returns clear errors on unsupported platforms&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This cgo dependency is a deliberate architectural choice. It means credctl can’t be cross-compiled with a simple &lt;code dir=&quot;auto&quot;&gt;go build&lt;/code&gt; — it needs Xcode and an Apple provisioning profile. That’s a trade-off: harder distribution in exchange for real hardware security. The binary is distributed as a code-signed, notarised macOS app bundle.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;the-oidc-flow&quot;&gt;The OIDC Flow&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;This is where the Secure Enclave connects to AWS. The flow uses OIDC (OpenID Connect) federation — specifically, AWS STS &lt;code dir=&quot;auto&quot;&gt;AssumeRoleWithWebIdentity&lt;/code&gt;. Here’s how the pieces fit together:&lt;/p&gt;
&lt;div&gt;&lt;h4 id=&quot;setup-one-time&quot;&gt;Setup (one-time)&lt;/h4&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;credctl init&lt;/code&gt;&lt;/strong&gt; generates the key pair in the Secure Enclave. It exports the public key to &lt;code dir=&quot;auto&quot;&gt;~/.credctl/device.pub&lt;/code&gt; and derives a device ID (SHA-256 fingerprint of the public key).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;credctl setup aws&lt;/code&gt;&lt;/strong&gt; deploys a CloudFormation stack that creates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An S3 bucket to host OIDC discovery documents&lt;/li&gt;
&lt;li&gt;A CloudFront distribution in front of S3 (AWS requires OIDC issuers to use HTTPS)&lt;/li&gt;
&lt;li&gt;An IAM OIDC identity provider that trusts the CloudFront URL as an OIDC issuer&lt;/li&gt;
&lt;li&gt;An IAM role with a trust policy that allows &lt;code dir=&quot;auto&quot;&gt;AssumeRoleWithWebIdentity&lt;/code&gt; from the OIDC provider&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The command also generates and uploads the OIDC discovery documents (&lt;code dir=&quot;auto&quot;&gt;.well-known/openid-configuration&lt;/code&gt; and &lt;code dir=&quot;auto&quot;&gt;keys.json&lt;/code&gt; JWKS) automatically.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After setup, you have a self-hosted OIDC provider backed by your device’s hardware identity. AWS trusts this provider because it’s registered as an IAM OIDC identity provider.&lt;/p&gt;
&lt;div&gt;&lt;h4 id=&quot;authentication-every-time&quot;&gt;Authentication (every time)&lt;/h4&gt;&lt;/div&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;sequenceDiagram&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;participant CLI as credctl CLI&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;participant SE as Secure Enclave&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;participant STS as AWS STS&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;participant CF as CloudFront&amp;#x3C;br/&gt;(OIDC Provider)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CLI-&gt;&gt;CLI: Build JWT claims:&amp;#x3C;br/&gt;iss = CloudFront URL&amp;#x3C;br/&gt;sub = device ID (SHA-256)&amp;#x3C;br/&gt;aud = sts.amazonaws.com&amp;#x3C;br/&gt;iat = now&amp;#x3C;br/&gt;exp = now + 5min&amp;#x3C;br/&gt;jti = random UUID&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CLI-&gt;&gt;SE: Sign JWT (ES256)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Note over SE: Touch ID prompt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;SE--&gt;&gt;CLI: Signature&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CLI-&gt;&gt;CLI: Assemble signed JWT&amp;#x3C;br/&gt;(header.payload.signature)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CLI-&gt;&gt;STS: AssumeRoleWithWebIdentity&amp;#x3C;br/&gt;(JWT + role ARN)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;STS-&gt;&gt;CF: GET /.well-known/openid-configuration&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CF--&gt;&gt;STS: Discovery document&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;STS-&gt;&gt;CF: GET /keys.json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;CF--&gt;&gt;STS: JWKS (device public key)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;STS-&gt;&gt;STS: Verify JWT signature&amp;#x3C;br/&gt;against JWKS public key&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;STS-&gt;&gt;STS: Validate claims&amp;#x3C;br/&gt;(issuer, audience, expiry)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;STS-&gt;&gt;STS: Assume IAM role&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;STS--&gt;&gt;CLI: AccessKeyId +&amp;#x3C;br/&gt;SecretAccessKey +&amp;#x3C;br/&gt;SessionToken&amp;#x3C;br/&gt;(valid 1 hour)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code dir=&quot;auto&quot;&gt;credctl auth&lt;/code&gt;&lt;/strong&gt; builds a JWT with these claims:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;iss&lt;/code&gt; — the CloudFront URL (OIDC issuer)&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;sub&lt;/code&gt; — the device ID (SHA-256 fingerprint of the public key)&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;aud&lt;/code&gt; — &lt;code dir=&quot;auto&quot;&gt;sts.amazonaws.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;iat&lt;/code&gt; — current timestamp&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;exp&lt;/code&gt; — current timestamp + 5 minutes (short-lived token)&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;jti&lt;/code&gt; — a random UUID (prevents replay)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The JWT header specifies &lt;code dir=&quot;auto&quot;&gt;alg: ES256&lt;/code&gt; and includes a &lt;code dir=&quot;auto&quot;&gt;kid&lt;/code&gt; (key ID) matching the key in the JWKS.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The CLI sends the JWT payload to the Secure Enclave for signing. With the default biometric policy (&lt;code dir=&quot;auto&quot;&gt;--biometric=any&lt;/code&gt;), this triggers a Touch ID prompt with passcode fallback. The Secure Enclave returns an ECDSA signature.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The CLI assembles the complete JWT (header.payload.signature, base64url-encoded) and calls STS &lt;code dir=&quot;auto&quot;&gt;AssumeRoleWithWebIdentity&lt;/code&gt; with the JWT and the IAM role ARN.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;STS fetches the OIDC discovery document from CloudFront, follows it to the JWKS endpoint, retrieves the device’s public key, and validates the JWT signature.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If validation passes and the role’s trust policy allows it, STS returns temporary credentials — an access key ID, secret access key, and session token valid for one hour (default, configurable up to 12 hours).&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;h3 id=&quot;why-oidc-over-roles-anywhere&quot;&gt;Why OIDC Over Roles Anywhere&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;AWS offers two X.509-based credential mechanisms: &lt;strong&gt;OIDC federation&lt;/strong&gt; (via &lt;code dir=&quot;auto&quot;&gt;AssumeRoleWithWebIdentity&lt;/code&gt;) and &lt;strong&gt;IAM Roles Anywhere&lt;/strong&gt; (via &lt;code dir=&quot;auto&quot;&gt;CreateSession&lt;/code&gt;). credctl uses OIDC federation. Here’s why:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Portability.&lt;/strong&gt; OIDC is a cross-cloud standard. GCP’s Workload Identity Federation and Azure’s Federated Identity Credentials both accept OIDC tokens. By building on OIDC, credctl’s architecture extends to multi-cloud without fundamental changes. Roles Anywhere is AWS-specific.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Simplicity.&lt;/strong&gt; OIDC federation requires hosting two static JSON files. Roles Anywhere requires managing X.509 certificates, certificate authorities, and certificate lifecycle. For a developer workstation tool, the OIDC approach is simpler.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;JWT ecosystem.&lt;/strong&gt; OIDC uses JWTs, which are well-understood, easy to debug (paste into jwt.io), and have mature library support. X.509 certificate handling is more complex and error-prone.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Existing infrastructure.&lt;/strong&gt; The Secure Enclave supports ECDSA P-256 signing, which maps directly to ES256 JWTs. No certificate generation or CA infrastructure needed.&lt;/p&gt;
&lt;p&gt;The trade-off: OIDC requires hosting a discovery endpoint (the S3 + CloudFront stack). Roles Anywhere doesn’t require any hosted infrastructure — it uses the X.509 certificate directly. For credctl’s use case, the simplicity and portability of OIDC outweigh the small infrastructure requirement.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;code-walkthrough-key-generation-to-credential-exchange&quot;&gt;Code Walkthrough: Key Generation to Credential Exchange&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Here’s the flow through credctl’s code, step by step.&lt;/p&gt;
&lt;div&gt;&lt;h4 id=&quot;step-1-key-generation-credctl-init&quot;&gt;Step 1: Key Generation (&lt;code dir=&quot;auto&quot;&gt;credctl init&lt;/code&gt;)&lt;/h4&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;init&lt;/code&gt; command calls the Enclave interface to generate a key:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Enclave.GenerateKey(tag) → *ecdsa.PublicKey&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Under the hood (on macOS), this calls &lt;code dir=&quot;auto&quot;&gt;SecKeyCreateRandomKey&lt;/code&gt; with attributes specifying:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Token: &lt;code dir=&quot;auto&quot;&gt;kSecAttrTokenIDSecureEnclave&lt;/code&gt; (create in Secure Enclave, not software)&lt;/li&gt;
&lt;li&gt;Key type: &lt;code dir=&quot;auto&quot;&gt;kSecAttrKeyTypeECSECPrimeRandom&lt;/code&gt; (ECDSA)&lt;/li&gt;
&lt;li&gt;Key size: 256 bits (P-256 curve)&lt;/li&gt;
&lt;li&gt;Access control: determined by the &lt;code dir=&quot;auto&quot;&gt;--biometric&lt;/code&gt; flag (default: &lt;code dir=&quot;auto&quot;&gt;any&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;--biometric&lt;/code&gt; flag controls the Secure Enclave access control policy for signing:&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;code dir=&quot;auto&quot;&gt;--biometric&lt;/code&gt; value&lt;/th&gt;&lt;th&gt;Access control&lt;/th&gt;&lt;th&gt;Behaviour&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;any&lt;/code&gt; (default)&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;kSecAccessControlPrivateKeyUsage&lt;/code&gt; + &lt;code dir=&quot;auto&quot;&gt;kSecAccessControlUserPresence&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Touch ID with passcode fallback&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;fingerprint&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;kSecAccessControlPrivateKeyUsage&lt;/code&gt; + &lt;code dir=&quot;auto&quot;&gt;kSecAccessControlBiometryCurrentSet&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Touch ID only, no passcode fallback. Key is invalidated if fingerprints change.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;none&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;kSecAccessControlPrivateKeyUsage&lt;/code&gt; only&lt;/td&gt;&lt;td&gt;No user verification. Signing happens silently when the device is unlocked.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The biometric policy is set at key creation time and cannot be changed afterward. To change it, reinitialise with &lt;code dir=&quot;auto&quot;&gt;credctl init --force --biometric=&amp;#x3C;policy&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The public key is exported to &lt;code dir=&quot;auto&quot;&gt;~/.credctl/device.pub&lt;/code&gt; in PEM format. The device ID is the SHA-256 fingerprint of the public key bytes. The config is written to &lt;code dir=&quot;auto&quot;&gt;~/.credctl/config.json&lt;/code&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h4 id=&quot;step-2-jwt-construction-credctl-auth&quot;&gt;Step 2: JWT Construction (&lt;code dir=&quot;auto&quot;&gt;credctl auth&lt;/code&gt;)&lt;/h4&gt;&lt;/div&gt;
&lt;p&gt;The JWT builder constructs the token in three parts:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Header:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;alg&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;ES256&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;typ&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;JWT&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;kid&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&amp;#x3C;key-id-matching-jwks&gt;&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Payload:&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;iss&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;https://d1234.cloudfront.net&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;sub&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;sha256:a1b2c3d4e5f6...&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;aud&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;sts.amazonaws.com&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;iat&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1709800000&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;exp&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1709800300&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;jti&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;550e8400-e29b-41d4-a716-446655440000&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The header and payload are base64url-encoded and concatenated with a dot: &lt;code dir=&quot;auto&quot;&gt;base64url(header).base64url(payload)&lt;/code&gt;. This becomes the signing input.&lt;/p&gt;
&lt;div&gt;&lt;h4 id=&quot;step-3-hardware-signing&quot;&gt;Step 3: Hardware Signing&lt;/h4&gt;&lt;/div&gt;
&lt;p&gt;The signing input bytes are sent to the Secure Enclave:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Enclave.Sign(tag, signingInput) → []byte (DER-encoded signature)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The Secure Enclave computes the ECDSA-SHA256 signature and returns it. The DER-encoded signature is converted to the raw &lt;code dir=&quot;auto&quot;&gt;r || s&lt;/code&gt; format that JWTs expect, then base64url-encoded.&lt;/p&gt;
&lt;p&gt;The final JWT: &lt;code dir=&quot;auto&quot;&gt;base64url(header).base64url(payload).base64url(signature)&lt;/code&gt;&lt;/p&gt;
&lt;div&gt;&lt;h4 id=&quot;step-4-credential-exchange&quot;&gt;Step 4: Credential Exchange&lt;/h4&gt;&lt;/div&gt;
&lt;p&gt;The CLI calls AWS STS &lt;code dir=&quot;auto&quot;&gt;AssumeRoleWithWebIdentity&lt;/code&gt; via a raw HTTP POST (no AWS SDK):&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;POST https://sts.amazonaws.com/&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Content-Type: application/x-www-form-urlencoded&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;Action=AssumeRoleWithWebIdentity&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x26;RoleArn=arn:aws:iam::123456789012:role/credctl-role&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x26;RoleSessionName=credctl-session&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x26;WebIdentityToken=&amp;#x3C;signed-jwt&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&amp;#x26;Version=2011-06-15&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div aria-live=&quot;polite&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;STS validates the JWT by:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fetching &lt;code dir=&quot;auto&quot;&gt;https://d1234.cloudfront.net/.well-known/openid-configuration&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Following the &lt;code dir=&quot;auto&quot;&gt;jwks_uri&lt;/code&gt; to &lt;code dir=&quot;auto&quot;&gt;https://d1234.cloudfront.net/keys.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Matching the &lt;code dir=&quot;auto&quot;&gt;kid&lt;/code&gt; in the JWT header to a key in the JWKS&lt;/li&gt;
&lt;li&gt;Verifying the ES256 signature against the public key&lt;/li&gt;
&lt;li&gt;Checking claims: issuer matches, audience is &lt;code dir=&quot;auto&quot;&gt;sts.amazonaws.com&lt;/code&gt;, token is not expired&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If valid, STS assumes the IAM role and returns temporary credentials.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;security-properties&quot;&gt;Security Properties&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;credctl’s architecture provides several security guarantees:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Private key never leaves hardware.&lt;/strong&gt; The Secure Enclave enforces non-exportability at the hardware level. Even with root access, an attacker cannot read the private key. They would need physical possession of the specific device AND the user’s biometric (or passcode) to sign a JWT.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Short-lived credentials.&lt;/strong&gt; The STS credentials returned by &lt;code dir=&quot;auto&quot;&gt;AssumeRoleWithWebIdentity&lt;/code&gt; are temporary — one hour by default, configurable up to 12 hours. There are no long-lived credentials on disk.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Replay protection.&lt;/strong&gt; Each JWT includes a unique &lt;code dir=&quot;auto&quot;&gt;jti&lt;/code&gt; (JWT ID) and a short &lt;code dir=&quot;auto&quot;&gt;exp&lt;/code&gt; (5 minutes). Even if a JWT were intercepted, it would expire before it could be meaningfully reused.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;User presence required (default).&lt;/strong&gt; With the default biometric policy (&lt;code dir=&quot;auto&quot;&gt;--biometric=any&lt;/code&gt;), every signing operation requires Touch ID or passcode verification. Malware cannot silently use the Secure Enclave key to obtain credentials. This can be disabled with &lt;code dir=&quot;auto&quot;&gt;--biometric=none&lt;/code&gt; at &lt;code dir=&quot;auto&quot;&gt;credctl init&lt;/code&gt; time for headless environments, at the cost of losing user-presence protection.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Revocation via JWKS.&lt;/strong&gt; To revoke a device’s access, remove its public key from the JWKS and delete the IAM OIDC identity provider trust. STS will no longer validate JWTs from that device.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No credential caching on disk.&lt;/strong&gt; credctl does not cache STS credentials to disk in plaintext. The credentials are output to stdout or set as environment variables for the current session.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;what-this-doesnt-protect-against&quot;&gt;What This Doesn’t Protect Against&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;No security architecture is complete without stating its limitations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A compromised device with the user present.&lt;/strong&gt; If an attacker has remote access to a device and the user approves a Touch ID prompt (social engineering or confusion), the attacker gets valid short-lived credentials. The mitigation is that the credentials are short-lived and the attack window is narrow.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Credential misuse after issuance.&lt;/strong&gt; Once STS credentials are issued, they’re standard AWS temporary credentials. If they’re logged, written to a file, or used in a shared terminal, they can be used by anyone until they expire.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OIDC provider compromise.&lt;/strong&gt; If an attacker gains write access to the S3 bucket hosting the JWKS, they could add their own public key and sign JWTs that STS would accept. The CloudFormation stack configures bucket access controls, but the OIDC provider is a critical trust boundary. The brokered mode (planned) eliminates this risk by centralising OIDC management.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;h3 id=&quot;comparison-to-alternative-approaches&quot;&gt;Comparison to Alternative Approaches&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;It’s worth comparing credctl’s security properties to common alternatives:&lt;/p&gt;








































&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Approach&lt;/th&gt;&lt;th&gt;Key Storage&lt;/th&gt;&lt;th&gt;Exportable?&lt;/th&gt;&lt;th&gt;Credential Lifetime&lt;/th&gt;&lt;th&gt;Device Binding&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code dir=&quot;auto&quot;&gt;~/.aws/credentials&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Plaintext file&lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;td&gt;Permanent&lt;/td&gt;&lt;td&gt;None&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1Password Shell Plugins&lt;/td&gt;&lt;td&gt;Encrypted software vault&lt;/td&gt;&lt;td&gt;Yes (if vault decrypted)&lt;/td&gt;&lt;td&gt;Permanent (stored key)&lt;/td&gt;&lt;td&gt;None&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AWS Identity Center&lt;/td&gt;&lt;td&gt;Browser session cookie&lt;/td&gt;&lt;td&gt;Yes (cookie theft)&lt;/td&gt;&lt;td&gt;8–12 hours&lt;/td&gt;&lt;td&gt;None&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;credctl&lt;/td&gt;&lt;td&gt;Secure Enclave hardware&lt;/td&gt;&lt;td&gt;No (hardware-enforced)&lt;/td&gt;&lt;td&gt;1 hour (STS)&lt;/td&gt;&lt;td&gt;Hardware-bound&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The fundamental difference is the transition from software-stored credentials (which can be copied) to hardware-bound credentials (which physically cannot be copied). This is not an incremental improvement — it’s an architectural change in where trust is anchored.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;performance&quot;&gt;Performance&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;Credential issuance involves three operations:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;JWT construction:&lt;/strong&gt; Microseconds. String formatting and base64 encoding.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secure Enclave signing:&lt;/strong&gt; Typically 20–50ms for the cryptographic operation, plus Touch ID interaction time (user-dependent).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;STS API call:&lt;/strong&gt; 100–300ms depending on network latency.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Total time from &lt;code dir=&quot;auto&quot;&gt;credctl auth&lt;/code&gt; to credentials: under one second of compute, plus Touch ID interaction. This is faster than browser-based SSO flows, which typically require 5–15 seconds of page loads, redirects, and MFA prompts.&lt;/p&gt;
&lt;p&gt;STS caches the OIDC discovery document and JWKS, so subsequent credential requests in the same session don’t require CloudFront fetches. The signing operation itself is constant-time — the Secure Enclave processes ECDSA signatures at the same speed regardless of the data being signed.&lt;/p&gt;
&lt;div&gt;&lt;h3 id=&quot;whats-next&quot;&gt;What’s Next&lt;/h3&gt;&lt;/div&gt;
&lt;p&gt;The architecture is designed to extend in two directions:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Platform expansion.&lt;/strong&gt; The &lt;code dir=&quot;auto&quot;&gt;Enclave&lt;/code&gt; interface abstracts hardware operations, so adding Linux TPM 2.0 support means implementing the same interface against &lt;code dir=&quot;auto&quot;&gt;go-tpm&lt;/code&gt; instead of Apple’s Security framework. The JWT builder, OIDC generator, and STS client remain unchanged. The same applies to Windows TPM via a Windows-specific build.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Multi-cloud.&lt;/strong&gt; The OIDC-based architecture was chosen specifically because GCP Workload Identity Federation and Azure Federated Identity Credentials accept OIDC tokens. Adding GCP support means implementing a GCP token exchange client; adding Azure means implementing an Azure AD token exchange client. The hardware identity, JWT construction, and OIDC provider infrastructure remain the same.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Brokered mode.&lt;/strong&gt; For teams, the CLI will authenticate to a centralised credctl broker instead of directly to cloud providers. The broker verifies device identity, evaluates policy (who can assume which roles from which devices), brokers credentials from cloud providers, and logs everything for audit. The hardware identity foundation is the same — the broker trusts the device’s signed assertion because it has the device’s public key in its registry.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;further-reading&quot;&gt;Further Reading&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/credctl/credctl&quot;&gt;credctl GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://credctl.com/quickstart/&quot;&gt;Quickstart guide&lt;/a&gt; — install to working credentials in 5 minutes&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://support.apple.com/guide/security/secure-enclave-sec59b0b31ff/web&quot;&gt;Apple Secure Enclave documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html&quot;&gt;AWS AssumeRoleWithWebIdentity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://openid.net/specs/openid-connect-core-1_0.html&quot;&gt;OIDC Core specification&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt; &lt;h3&gt;Stay up to date&lt;/h3&gt; &lt;p&gt;Get notified about new credctl releases, security best practices, and hardware identity insights.&lt;/p&gt; &lt;form action=&quot;https://buttondown.com/api/emails/embed-subscribe/credctl&quot; method=&quot;post&quot; target=&quot;popupwindow&quot;&gt; &lt;div&gt; &lt;input type=&quot;email&quot; name=&quot;email&quot; placeholder=&quot;you@example.com&quot; required aria-label=&quot;Email address&quot;&gt; &lt;button type=&quot;submit&quot;&gt;Subscribe&lt;/button&gt; &lt;/div&gt; &lt;input type=&quot;hidden&quot; name=&quot;tag&quot; value=&quot;blog,technical&quot;&gt; &lt;/form&gt; &lt;/div&gt; </content:encoded><category>architecture</category><category>secure-enclave</category><category>deep-dive</category></item></channel></rss>