Offline & air-gapped activation¶
GRCFlow never contacts a license server. A license key is an Ed25519-signed claim that the backend verifies locally against a public key shipped with the product, so an install with no route to the internet activates exactly like one that is online.
There is no activation call, no phone-home, no periodic re-check, and nothing that expires because a network was unreachable.
What that means in practice¶
| Network needed to activate | No |
| Network needed to keep running | No |
| Bound to a machine / MAC / CPU | No — move it between hosts freely |
| Where the key is stored | /etc/sovereign-grc/license.key |
| Survives restart | Yes — read from disk at startup |
Because keys are not hardware-bound, you can buy on a laptop, receive the key by email, and carry it to the air-gapped server on whatever media your policy allows.
Activating with no internet¶
- Buy from any internet-connected device. Checkout emails the signed key to the address on the order.
- Move the key to the air-gapped host. It is a single line of text — copy it via the same channel you use for any other config material.
- Apply it, either way:
Through the UI: Setup → License → paste the key.
Or with curl, from the host itself:
curl -X POST http://localhost:8000/api/v1/setup/license \
-H "Content-Type: application/json" \
-d '{"license_key":"<paste the key here>"}'
- Confirm it took. The response reports the tier, seat count and expiry. It is also
written to
/etc/sovereign-grc/license.keyand reloaded on every restart.
If the key is rejected, the error distinguishes the cases: Invalid signature means the
key was altered or is not ours; License expired means the term has ended;
System clock appears to have been tampered with is covered below.
Why it works without a network¶
The backend holds only the public half of the signing key:
LICENSE_PUBLIC_KEY_HEX=302a300506032b6570032100bd52efaf3c8a0c8940a09d0dc5de5d7cd43802ec4a0d12b65a67b01cce9a27fe
That value ships in deploy/public/.env.example and as the default in
deploy/public/docker-compose.yml, so a stock install already has it. Publishing it is
safe and deliberate — a public key can verify a signature but cannot create one. Only
the vendor's private half, held in the mint service, can sign a license.
Do not blank this value
If LICENSE_PUBLIC_KEY_HEX is unset, the backend cannot verify signatures and
rejects every license with "License validation unavailable — public key not
configured" — including keys you have paid for. A legitimate key then fails
indistinguishably from a forged one.
Clock integrity on air-gapped hosts¶
An offline host has no NTP, so its clock can drift or be set backwards — accidentally or
to extend an expired term. GRCFlow keeps a high-water mark at
/etc/sovereign-grc/hwm.dat recording the furthest point in time it has seen. If the
system clock later reads meaningfully earlier than that mark, validation returns
CLOCK_TAMPER rather than silently accepting the license.
If you hit this after a legitimate clock correction — a CMOS battery replacement, or a
VM restored from an old snapshot — set the clock correctly and remove hwm.dat; it is
rebuilt on the next validation.
Air-gapped hosts still need a key¶
There is no keyless mode. GRCFlow is trial-then-buy, so an install with no key — online or
offline — is unlicensed: writes return 403 license_required while existing data stays
readable and exports keep working.
What air-gapping does not require is connectivity at validation time: the key is verified locally against the vendor public key that ships in the deployment files, with no phone-home and no hardware binding. So the only online step is obtaining the key, and it is a one-off. Request a free 30-day trial key (or buy a paid key for the seat tier you need) from a networked machine at grc.defendflow.xyz, carry the resulting string to the air-gapped host on removable media, and paste it at Admin Settings → License. Renewal works the same way: a new key, carried in by hand. See Pricing.