security Intermediate

Pyvorin in Air-Gapped Environments

Compilation needs no network at all. Licensing degrades slowly: offline-valid leases keep passing, and grace runs from 24 hours to 90 days by tier.

Published Feb 12, 2026

An air-gapped machine is the test that separates documentation from behaviour, because every "works offline" claim eventually meets a machine with no route out. This page describes what pyvorin-native 1.0.9 does on such a machine, verified against the installed artefact: what needs a network and when, how long each licence tier keeps working after connectivity disappears, and what the client actually does — not what a marketing page wishes it did.

What needs a network, and when

Almost nothing. Compilation is entirely local: the compiler lowers Python to native code in-process, the compile path contains no HTTP client, and the compile cache lives on local disk. A process that only compiles and runs code makes zero network requests.

The complete list of network behaviour in the shipped binary is short:

TrafficTriggerWithout network
POST api.pyvorin.com/api/v1/licenses/check carrying {license_key, device_fingerprint}pyvorin activate, plus periodic revalidation from the licence gateActivation cannot complete; existing leases validate locally
POST api.pyvorin.com/v1/usage/events/bulk — sampled event metadataFlush at 50 events, 300 seconds, or exitEvents buffer and are silently dropped; 5-second timeout, no retries, no queue buildup beyond the 1,000-event ring
Product-CLI login/logout endpointsOnly pyvorin login / logoutIrrelevant unless someone logs in

There is no update check anywhere in the package — no URL, no version-comparison code path — so nothing on an offline machine will start failing because it cannot phone home about a new release. The correction to an earlier draft belongs here too: that draft claimed "no API calls, no telemetry, no remote licence checks". The accurate version is: no calls are required for compilation, telemetry degrades to a no-op, and licence checks happen when a route exists but are not needed for an offline-valid licence to pass. Precision matters in this environment more than anywhere, because your reviewers will read every sentence against a packet capture.

How the licence behaves offline

The design goal, stated plainly in the licensing code, is that a flaky or absent network degrades licence status slowly and never suddenly. Three mechanisms deliver it.

Local lease validation. Activation writes ~/.pyvorin/license.json (mode 0600) containing the key, a server-signed Ed25519 lease, expiry times, tier and features, plus an HMAC over the whole payload. Validation checks the signature against the embedded public key, binds the file to the machine fingerprint, and applies a monotonic anti-rollback watermark. All of that runs locally. An offline machine with a valid lease is indistinguishable from an online one at gate time.

Fail-soft gate semantics. The hard gate's rule, verified by runtime probe: if the licence validates offline, the gate passes — even when the online revalidation attempt fails. Only an offline-invalid licence forces the online check to succeed, and when neither works the gate fails with both reasons. Practically: a sealed machine with a good lease compiles happily through total network loss.

Grace after expiry. When a lease does expire, the entitlement cache keeps the tier working for a fixed window:

TierOffline grace after lease expiry
demo, trial24 hours (86,400 s)
basic, professional72 hours (259,200 s)
enterprise90 days (7,776,000 s)

Enterprise leases can additionally carry a server-issued offline_permit — an expiry plus an offline-hours budget — which is the mechanism built for exactly this scenario: a sealed site whose machines see the licensing server only during scheduled maintenance windows.

The activation problem, solved the boring way

The one operation that genuinely needs connectivity is first activation, and the workflow for a sealed site is deliberately unglamorous. Activate the machine on a connected segment — one POST, one response, the lease written locally — then move it behind the air gap. The lease validates offline from that point. Renewal is the same manoeuvre at the maintenance window, well inside the 90-day enterprise grace. No licence file smuggling, no manual clock tricks: the anti-rollback watermark (with five minutes of clock tolerance) and the hours counter with its anti-replay nonce mean that winding a VM's clock backwards is detected, not rewarded.

If your site policy forbids even that much contact, the environment overrides are the escape valve: PYVORIN_LICENSE_URL redirects the licensing endpoint, and PYVORIN_TELEMETRY_URL redirects telemetry. Point the first at a licensing relay on your own network — anything that speaks the same request and response shape — and activation traffic never crosses the gap at all. This is also the natural place for a judgment call: an endpoint override is a redirection of trust, so treat these variables as configuration to be locked down, not conveniences to leave set. An attacker who controls PYVORIN_LICENSE_URL controls where your licence keys go.

Cache behaviour without connectivity

Local caches behave identically offline, with one deployment fact to plan around. The disk compile cache lives inside the install tree at <site-packages>/.pyvorin_cache/disk_compile — compiled .so files keyed by SHA256 of source, function, options and toolchain timestamps, LRU-capped at 500 entries, each entry checksum-validated before use. Sealed environments are usually read-only-for-users installs, so decide at provisioning time which account owns the install tree or pre-warm the cache during the connected phase. A tool check confirms the shape of things:

# Report cache file count and disk usage for the CLI's cache directory
python -m pyvorin cache status

Importing the package also creates ~/.pyvorin_cache/columnar_kernels/ even before any licence activity — a harmless write, but "no writes until activated" would be the kind of false claim that costs credibility in a security review, so now you have the accurate one.

What an offline deployment cannot do

Honesty cuts both ways, and the limits deserve equal airtime. A machine that has never been activated cannot activate itself — the licence check requires one successful round trip (or a relay, as above). Once grace truly expires, the gate fails closed; there is no backdoor switch in production wheels, and the developer bypass (PYVORIN_SKIP_LICENSE=1) is honoured only when a _dev_build.marker ships with the package, which production wheels exclude. Unsupported constructs still fall back to honest CPython execution offline exactly as they do online — the fallback path is local by construction. Plan the renewal cadence to the grace windows rather than around them, and the whole page reduces to one discipline: connect on your schedule, not the tool's.

Proving it before you deploy

Air-gap programmes usually want evidence gathered before the machines are sealed, and everything on this page is reproducible with tools you already have. On a staging host: activate while connected, then cut the route and run pyvorin check — it prints License gate OK and exits 0 from local validation alone. Capture a packet trace across a compile-and-run session and confirm the expected nothing: zero packets beyond any DNS noise your resolver makes. Run pyvorin status on the sealed machine to show the JSON summary is assembled locally. These three artefacts — gate output, empty capture, local status — are the difference between a claim in a review packet and a demonstrated behaviour, and they take a single afternoon to produce, once, per release.

One planning note from the field: seal-time evidence ages. Re-run the packet capture after every upgrade, because the honest way to read this page is not "no network code exists in 1.0.9" but "1.0.9 was verified to have none" — a statement about a specific artefact, which is exactly the kind of statement an air-gapped accreditation wants you to keep current.

Where to go next

Last reviewed 11 February 2026 against pyvorin-native 1.0.9 installed at /root/pvfinal: gate semantics probed at runtime including the offline-valid case, grace constants read from the entitlement-cache implementation, cache locations confirmed on a clean HOME. No network calls were made during verification.