Skip to content

Security: RavenOfTime/proxkey

SECURITY.md

Security

proxkey holds a Proxmox API token and installs SSH keys into root accounts. This document states what it protects, what it does not, and how to report a problem.

Reporting a vulnerability

Please report security issues privately through GitHub's private vulnerability reporting on this repository, rather than opening a public issue.

Include the version (proxkey version), your Proxmox version, and the smallest reproduction you can manage. Expect an acknowledgement within a week.

Threat model

What proxkey protects

Your SSH private key never exists as a file. It is generated in, and never leaves, the Secure Enclave. proxkey only ever handles the public key. Copying ~/.ssh/ off a stolen laptop yields nothing usable, and there is no passphrase to brute-force. Every SSH authentication is gated by Touch ID or the device password, enforced in hardware.

The API token is never written to disk in plaintext. It lives in the macOS Keychain, stored through the Security framework rather than by shelling out to /usr/bin/security — so the secret never appears in a process argument list, and the resulting Keychain item's access control list names the proxkey binary itself. Other programs must prompt you before they can read it. Rebuilding proxkey changes its code identity and will prompt once more; that is the mechanism working.

Configuration and SSH config are written 0600 in 0700 directories, and the managed SSH block lives in its own ~/.ssh/config.d/proxkey.conf rather than being spliced into your hand-written config.

The API connection is authenticated. Proxmox ships a self-signed certificate, so proxkey setup shows you the certificate's SHA-256 fingerprint alongside the command that prints the same fingerprint on the server, and pins it once you confirm. After that, a certificate that does not match is refused. This matters because the token travels in a plain Authorization header: an unauthenticated TLS connection hands your token to anyone on the network path.

SSH to a PVE node fails closed on an unknown host key. Node addresses come from the cluster API, so blindly accepting a new host key would let a bad API response redirect the connection. You verify the fingerprint once, or opt into trust-on-first-use per endpoint with "trust_new_hosts": true.

What proxkey does not protect against

A compromised Mac. If an attacker is running code as you, they can ask the Secure Enclave to sign (subject to your Touch ID prompt), prompt you for Keychain access, or simply wait for you to authenticate. proxkey raises the cost of key theft; it does not survive a compromised endpoint.

A malicious or compromised Proxmox server. proxkey trusts the API it is pointed at. A hostile server can report false IP addresses and cause you to write SSH config entries pointing somewhere unexpected. Certificate pinning ensures you are talking to the server you pinned, not that the server is honest.

Anything the token can do. The token is a real credential. Scope it with proxkey token, which emits a least-privilege recipe, and revoke it with pveum user token remove if a machine is lost.

Keys already installed. Revoking the API token stops further provisioning but does not remove keys from guests. Use proxkey revoke first.

Deliberate design decisions

The browser extension requests broad optional host permissions

optional_host_permissions is https://*/* because Proxmox runs on arbitrary private hostnames that cannot be enumerated in advance. Two things bound it:

  • Permission is requested for one specific origin at a time, from a user gesture in the popup — never for the whole pattern.
  • The background worker refuses to register the content script on any origin that is not one of the endpoints in your own config.json.

The extension is intended to be loaded unpacked from this repository. It is not published on the Chrome Web Store.

The content script is treated as untrusted

Content scripts run inside the Proxmox web UI. The background service worker accepts exactly one message type from them (wizard-armed) and rejects every other, including all native-host RPC. It also derives the target endpoint from the sender's own origin rather than from anything the message claims.

Auto-keyify is bounded

Arming the create-wizard checkbox starts a watcher that is scoped to a single VM ID where the wizard exposes one, and otherwise to one guest and a 15 minute TTL. An unbounded watcher would install your key on any guest created in that window, including guests created by a colleague or by automation. Guests outside the scope are reported as skipped and left untouched.

agent/exec is optional

The least-privilege token recipe deliberately omits VM.GuestAgent.Unrestricted, which permits arbitrary command execution inside guests. proxkey uses it only to create /root/.ssh when missing; without it that single call fails and provisioning continues.

Hardening checklist

  • Run proxkey token and use the scoped token it describes — not root@pam.
  • Confirm proxkey doctor reports a pinned certificate for every endpoint.
  • Keep insecure_tls unset. If doctor warns about it, run proxkey trust <endpoint>.
  • Scope the token to a pool rather than /vms if you only manage some guests.
  • Set a token expiry: pveum user token add ... --expire <unix-timestamp>.
  • Before decommissioning a Mac, run proxkey revoke for each managed guest, then remove the token with pveum user token remove.

Supported versions

Security fixes are applied to the latest release only.

There aren't any published security advisories