Secret Management with Pyvorin
Pyvorin's secret surface is small: one licence key, stored at 0600, bound to the machine, transmitted only over TLS. What the package does with it, verified.
Published Feb 18, 2026
Pyvorin handles exactly one class of secret on your machines: the licence key. That is a genuinely small secret surface, and it makes this page short for the right reasons — not because sensitive material is managed casually, but because there is little of it and every handling step was verified against the installed artefact. Below: the key's full lifecycle, where it lives on disk, the two environment variables that redirect its network path, and proof by payload class that telemetry cannot leak it.
The licence key lifecycle
Activation is one command and one request:
# Activate this device with a licence key
pyvorin activate
# On success: License activated. tier=… expires=…
The request is a single POST to api.pyvorin.com/api/v1/licenses/check over HTTPS, with a JSON body of two fields: the key and a device fingerprint (sha256(hostname:username:/etc/machine-id) truncated to 32 characters). Nothing else travels: no source, no environment dump, no machine inventory. The server answers with a signed lease — Ed25519, signed server-side, with the verification key embedded in the package and its secret existing only on the entitlement server — and the client writes ~/.pyvorin/license.json.
From that moment the key is a file-protection problem, and the package's defaults are the ones you would choose yourself. The file is written mode 0600. Its integrity is guarded three ways: an HMAC-SHA256 over the payload using a build-time secret, binding to the device fingerprint so the file is useless on another machine, and the signed lease whose tier and feature claims override anything a local edit could change. The practical consequence for an operations team is the useful kind of boring: editing the file edits nothing that matters, copying it to another machine fails validation, and the local secrets manager is the filesystem itself.
File or environment: an actual judgement call
Older drafts recommended keeping the key in an environment variable, piped in from a secrets manager at activation time. That works, but our recommendation runs the other way, and it is worth stating the reasoning rather than the rule. The activated key's resting place is a 0600 file read only by the local gate; an environment variable, by contrast, is visible to every process running as that user, leaks into crash dumps and shell histories, and survives into child processes that have no need of it. The one moment the key needs to be in a variable or a pipeline is the activation command itself — minimise that moment, prefer a secrets-manager CLI that emits straight into the command without an intermediate export, and let the file be the durable store. If your platform mandates environment-injected secrets everywhere, the activation flow still accepts it; just unset the variable afterwards, because the file is what the system actually uses day to day.
One more lifecycle fact that belongs in your rotation runbook: pyvorin status prints the local licence summary as JSON, and pyvorin check runs the hard gate with a pass/fail exit code — both work offline against the local file, so audit scripts can verify licence state on sealed machines without touching the key.
The two environment variables that matter
Only two Pyvorin environment variables redirect anything sensitive. PYVORIN_LICENSE_URL overrides the licensing endpoint; PYVORIN_TELEMETRY_URL overrides the telemetry destination. Neither holds a secret, but both redirect secrets-adjacent traffic — the licence key travels to the first, event batches to the second — which makes them configuration to control, not conveniences. An attacker who can set PYVORIN_LICENSE_URL can exfiltrate every licence key activated on that machine, quietly, over TLS, to infrastructure they control. Treat both variables the way you treat HTTP_PROXY: set them deliberately in managed configuration, flag unexpected values in your environment audits, and treat any occurrence in a developer's shell profile as worth a question. There is no PYVORIN_LICENSE_KEY variable that bypasses the file — earlier material implied one, and it does not exist.
Telemetry cannot carry your secrets
The telemetry stream is the place a secret could leak silently, so the strongest statement on this page is a payload class rather than a promise. The captured batch envelope contains exactly: a version string, a session UUID prefix, the privacy level, a locally computed abuse score, and events with type, timestamp and small parameter objects — compile timing measured in milliseconds, that shape of thing. The fixed event vocabulary is seven names: compile, execute and feature_used sampled at 1%, plus activation, validation_failed, tamper_detected and revoked always recorded. No code, no paths, no identifiers beyond the session prefix, no key material. The licence key appears in exactly one network location — the activation and revalidation body — and nowhere else in the protocol by construction, not by policy.
If your environment's rules say even that stream should not leave, the privacy level can be set to minimal (security events only) or disabled — via an in-process API call, pyvorin._telemetry.set_privacy("disabled"), or developer mode. There is deliberately no environment-variable opt-out, so centralise the choice the way you centralise everything else on managed machines: one wrapper at interpreter start-up, enforced by policy, rather than a per-developer flag.
CI and developer environments
Two edge cases where secret hygiene usually slips. In CI, resist the temptation to share one key across every runner: the device fingerprint binds activation to a machine identity, and a fleet of ephemeral runners sharing a key turns your licence audit into noise. Prefer a small pool of named runners or per-runner activation, and let pyvorin check in the job's setup step fail the build before any compile work starts. For developers, the developer bypass (PYVORIN_SKIP_LICENSE=1) is honoured only when a _dev_build.marker ships with the package — production wheels exclude it — so a plain production install has no silent way around the gate, and any machine where the bypass works is telling you it is not running a production wheel. That is a secret-management signal too: unlicensed builds should be impossible, not merely undocumented.
Rotation and revocation
Rotation is a server-side property of the key, which is where you want it. A key the server marks inactive fails activation and revalidation; the always-on telemetry vocabulary even carries a revoked event, so revocation is a first-class state rather than an absence. The operator workflow: revoke or replace the key via the licensing dashboard at app.pyvorin.com, reactivate affected machines with the replacement during the offline grace window — 24 hours to 90 days by tier — and let the device binding do the rest, since a revoked key's lease cannot be replayed onto new hardware. Because the licence file is a single well-known path per machine, fleet scripts can enumerate installations with an ordinary filesystem walk and feed your secrets-management audit without any Pyvorin-specific tooling.
Where to go next
- Activation and entitlement — the lease, the gate, and the device fingerprint in full.
- Protected component delivery — how the package protects its own integrity, including the licence file.
- Network requirements — every endpoint and the two environment overrides, for your egress review.
- CLI reference —
activate,statusandcheck, exactly as installed.
Last reviewed 17 February 2026 against pyvorin-native 1.0.9 installed at /root/pvfinal: licence-check payload captured at runtime (key plus fingerprint only), telemetry envelope quoted from a captured batch, licence file mode and fields read from a file written during a clean-HOME probe, CLI behaviour executed directly.