Penv Cloud
Start free
Docs
Guidesince cloud@2026-09-10

Enroll a bare host

Bind a keypair to your VPS when it has no signed metadata. Every login advances a counter you and we both hold; a copied key locks both out.

Hetzner, DigitalOcean, Linode, Vultr and OVH serve unsigned metadata over plain HTTP and expose no secure element. Your host there holds an Ed25519 key instead, and we hold the public half. Both sides keep a generation number that every successful login advances by one.

This detects a copy after the fact. A stolen key still authenticates once; that login advances our number, so your real host arrives stale on its next login and we lock both out.

Enroll the host

Issue the enrollment secret

Open Machine Identities then All Identities, open the identity, and enroll a bound keypair. This needs machine:issue and step-up. We show a one-time pce_ secret once.

The dialog also prints a shell script from an older setup. Ignore it.

Enroll

penv machine enroll pce_example
enrolled 3b1f...c204
the key stays in this host's keychain and is never printed

penv machine enroll generates the key, sends the public half, and prints the credential id. The private key lives in the operating system keychain, never in a file and never in output.

Verify

penv run -- <command>

penv run reads the environment the identity is bound to.

RefusalCauseFix
no_keychainYour host has no keychain, so the key would not survive the processSet PENV_TOKEN, or run where the OS keychain opens
no_secretThe enrollment secret was emptyCopy the pce_ secret from the console

The pce_ prefix keeps an enrollment secret from being sent as a bearer credential, and we spend it in the statement that reads it. Codes are listed in error codes.

Login sequence

  1. penv requests a nonce. We issue it with a 120-second lifetime.
  2. penv signs four lines: a fixed prefix, the credential id, the nonce, the current number.
  3. We burn the nonce on our replay guard before touching the database.
  4. We check the signature, then the expiry, then the number.
  5. On success we advance the number and return a fifteen-minute credential.
  6. penv writes the new number to the keychain before using the credential.
Design choiceEffect
Nonce issued by usSomeone with brief access to your host cannot stash pre-signed challenges
Nonce burned firstAn immediate replay costs one hash
Signature checked before expiry and numberA caller with no key cannot lock out a host or enumerate credential ids

Refusals

AnswerMeaningRevoked
409 clonedThe presented number differs from oursThe keypair and every credential it minted
401 replayedThe same request arrived twice; not a copyNothing

A clone stops the host:

{
  "error": "cloned",
  "message": "this machine's identity was used from a second machine, so the server locked it.",
  "fix": "Create a new enrolment secret in the console, then run penv machine enroll <secret>."
}

A replay prints unauthorized at the command line. We revoke nothing, and the next login with a fresh nonce works.

On a lockout we write a credential.cloned audit entry with both numbers and email the members chosen for security alerts (alerts).

Recover from a lockout

You cannot restore a revoked keypair. Find where the key was copied first: a fresh key on a host someone else still reaches buys one more lockout.

  1. Filter the audit log for credential.cloned and read both numbers.
  2. Find how the key was copied.
  3. Revoke the identity and enroll a new key on a clean host.

See penv machine, machine identity, and a credential leaked.

A credential leaked