← All essays

2026-03-15

Towards Replacing Leaky API Keys and Secrets

Every developer knows the feeling. You open a Slack DM and see a colleague paste an API key.

You grep -r through a monorepo and find a .env.production that was committed by accident three years ago.

You look at your CI dashboard and count twelve secret variables, six of which nobody remembers who created.

API keys are strings. Strings get copied. Copied strings get leaked.

This isn't a minor problem. It's the single most common entry point for supply chain attacks, cloud breaches, and credential theft. The 2024 Internet Bug Bounty report found that credential leaks accounted for more initial-access vectors than all other categories combined.

We think there's a better architecture.

The traditional model

Here's what authentication looks like for a developer who publishes to multiple package registries:

credentialsbearer tokens
Traditional: Bearer Tokens
~/.npmrcnpm_a3f8Kx9...npm registry
~/.pypircpypi_9x2Lm...PyPI
~/.dockerdckr_pat_k7...Docker Hub
CI secretsghp_mN4Qw8...GitHub API
.env files committed to Git
CI secret variables with no rotation
Slack messages and shared vaults
Docker build layers pushed public

4 credentials. 4 rotation policies. 4 attack surfaces. Zero connection between them.

Four credentials. Four configuration files. Four attack surfaces. Zero connection between them. If your npm token leaks, nothing about your PyPI configuration tells you. If your GitHub PAT is compromised, there's no mechanism to automatically invalidate the Docker credential that was created by the same person.

Every location is a separate leak vector. Every token is a bearer credential — whoever holds the string IS authenticated, regardless of who they are or where they're calling from.

The Auths model

credentialsdevice-bound identity
Auths: Device-Bound Identity
Device Keychain

Private key (Ed25519) — never leaves this device

signs
id: <root> · yours
npmPyPIDockerGitHubany registry
Verifiable by anyone (public identifier)
Revocable instantly (one operation, global effect)
Rotatable without losing history (pre-rotation built in)

1 identity. 1 device-bound key. Zero copyable secrets.

Your DID is a public identifier. You can print it on a billboard. Knowing it doesn't let anyone impersonate you, because authentication requires a signature from the private key that's locked in your device's keychain (macOS Keychain, Windows Credential Manager, or a hardware token).

What changes, concretely

No secrets to leak

# Traditional: generate a token, store it somewhere, hope it doesn't leak
$ npm token create
npm_a3f8Kx9mP2...
# Now what? Paste it into CI? Save it in .env? Share it on Slack?
# Auths: the key never leaves your device
$ auths init
[OK] Identity ready: <root>
[OK] Device linked: did:key:z6Mkjj12ctRQPhtRf1Aeuf6Kf54E5GxRDHPEJPhXSrtQivKm
# Nothing to copy. Nothing to paste. The private key is in your OS keychain.

No per-service credentials

# Traditional: different token for every service
$ npm publish # uses ~/.npmrc token
$ twine upload # uses ~/.pypirc token
$ docker push # uses ~/.docker/config.json credential
# Auths: one identity everywhere
$ auths artifact sign package.tar.gz
$ auths artifact publish package.tar.gz --package npm:my-package --registry <registry-url>
$ auths artifact publish package.tar.gz --package pypi:my-package --registry <registry-url>
# Same key. Same identity. Same audit trail.

Rotation doesn't break history

This is the part that GPG and traditional API keys get fundamentally wrong. When you rotate a GPG key, your old signatures can't be verified without the old public key. When you rotate an npm token, there's no connection between the old token and the new one.

Pre-rotation solves this. When you create your identity, you also commit to what your next key will be (via a hash commitment). When you rotate, the new key matches the commitment, proving continuity. Your DID stays the same. Your old signatures remain valid. Your reputation carries forward.

$ auths id rotate
[OK] Key rotated. DID unchanged: <root>
# Every previous signature is still verifiable.
# Every future signature uses the new key.
# The DID — your identity — never changes.

Revocation is instant and global

When an API key leaks, you revoke it on one service. Then you remember you used the same pattern for three other services. Then you check CI. Then you check the shared password manager. Then you hope nobody used it in the 47 minutes between the leak and the revocation.

With Auths, you revoke a device key once in the transparency log. Every verifier that checks the log sees it immediately.

[REVOKE] <root> revoked did:key:z6Mkjj... #35

One operation. Every artifact signed by that device key is now flagged. Every registry that checks the transparency log knows. No chasing tokens across services.

CI/CD without secrets

The obvious question: what about CI runners? They don't have a macOS Keychain or a YubiKey plugged in.

Auths handles this natively. The CLI has a ci profile designed for ephemeral environments:

$ auths init --profile ci
[OK] Identity ready: <ci-root>
[OK] File-based keychain initialized

The ci profile creates a file-based keychain scoped to the runner's lifecycle. The identity is real — it has a DID, it signs artifacts, it appears in the transparency log — but the key material lives in memory or a temporary file, not a hardware keychain.

This means your CI pipeline signs with the same identity model as a human developer. The audit trail is identical. The revocation mechanism is identical. The verification is identical. There's no separate "CI signing" system with different trust properties.

For AI agents, there's an agent profile with capability restrictions — scoped permissions so an automated system can sign artifacts but can't rotate keys or add org members.

What this looks like on a registry

A registry here is a log you can host yourself. Its activity feed shows identity registrations, artifact attestations, and key lifecycle events. Each entry links to an identity; each identity links to platform claims, device keys, and signed artifacts.

Underneath, every operation is append-only, Merkle-tree-backed, and independently verifiable. Self-hosted registries work today.

Try it

brew install auths-dev/auths-cli/auths
auths init

Read the code: github.com/auths-dev/auths

Previous posts: