A host that proves nothing
A plain VPS signs no metadata, so it holds a key instead and both sides count every login. A copied key locks both out.
Some hosts can attest nothing. Hetzner, DigitalOcean, Linode, Vultr and OVH all serve unsigned metadata over plain HTTP and expose no secure element, so there is no signature to check and nobody to vouch for the host. penv gives it a key of its own and counts every login instead.
How the counter works
The host generates an Ed25519 private key. Penv Cloud keeps the public half. Both sides remember a number, and every successful login advances it by one.
A stolen key still authenticates once. Doing so advances the number on the server, so the real host arrives with a stale one on its next login and both are locked out.
This detects a copy after the fact. It does not stop the copy from working the first time, and penv says so everywhere it offers this.
Enroll the host
Open Machine Identities then All Identities, open the identity this host should be, and
enroll a bound keypair. Confirming it is you is required, and so is machine:issue. The console
shows a one-time enrollment secret starting with pce_. It is shown once.
penv machine enroll pce_examplepenv generates the key and sends the public half, then prints the credential id:
enrolled 3b1f...c204
the key stays in this host's keychain and is never printedThe private key lives in the host's operating system keychain and is never written to a file and never printed. A host with no keychain is refused, because an enrolled key that did not survive the process would be worse than no enrollment at all:
{
"error": "no_keychain",
"message": "this host has no keychain, so an enrolled key would not survive the process.",
"fix": "Give the host a PENV_TOKEN, or run it where the OS keychain opens."
}An empty secret is refused the same way, with no_secret and a fix naming the console.
The enrollment secret carries its own pce_ prefix so it can never be handed over as a bearer
credential by mistake, and the server spends it in the statement that reads it.
What one login looks like
- penv asks for a nonce. The server issues it and it lives 120 seconds.
- penv signs four lines with the private key: a fixed prefix, the credential id, the nonce, and the current number.
- The server checks the nonce and burns it on its replay guard before it touches the database, so an immediate replay costs one hash.
- It checks the signature next, then the expiry, then the number.
- On success it advances the number and returns a credential that lasts fifteen minutes.
- penv writes the new number to the keychain before it uses the credential.
The nonce comes from the server rather than the host, so somebody with brief access cannot walk away with a stash of pre-signed challenges. The signature is checked before the expiry and before the number, so a caller with no key cannot lock out a host and cannot learn which credential ids exist.
Run one penv per host. Two processes re-authenticating at the same moment present the same number twice, which is what a copy looks like, and the server locks the credential rather than guess.
The two refusals, and how to tell them apart
| Answer | What happened | What is revoked |
|---|---|---|
409 cloned | The number the host presented is not the one the server holds | The keypair, and every credential it minted |
401 replayed | The same request arrived twice. The number is behind, and this is not a copy | Nothing |
A clone stops the host. penv says so in one line:
{
"error": "cloned",
"message": "this host's key was seen on another machine, so the identity is locked.",
"fix": "Issue a new enrolment secret in the console and run penv machine enroll again."
}A replay reads as unauthorized at the command line, with the message that the credential is not
accepted by this server. Nothing was revoked, and the next login with a fresh nonce works.
The lockout writes a credential.cloned entry naming the number the caller presented beside the
number the server held, which is the only place anyone will read what happened. An owner also gets
a security email.
After a lockout
There is no un-revoke. Find where the key was copied before you enroll again, because a fresh key on a host somebody still has access to buys you one more lockout and nothing else.
Do it in order
- Open the identity in Machine Identities then All Identities and enroll a bound keypair.
- Copy the
pce_secret. It is shown once. - On the host, run
penv machine enroll pce_...and read back the credential id. - Run
penv run -- <your command>and confirm it reads the environment the identity is bound to. - Filter the audit log for
credential.clonedwhen a host starts failing, and read both numbers. - Find how the key was copied, then revoke the identity and enroll a new key on a clean host.