varsvars
Encryption & Security

PINs & Keys

How the two-layer PIN/key model protects your master key.

The PIN/key model

When you run vars init, two things are created:

  1. A master key — 32 bytes of random data. This is what actually encrypts your values.
  2. An encrypted copy of that master key — stored in .varskey (gitignored), locked behind your PIN.

The PIN is never stored anywhere. It goes through Argon2id — a memory-hard, brute-force-resistant KDF — to derive a wrapping key. That wrapping key decrypts the master key, the master key decrypts your values, and then the wrapping key is discarded.

The master key never touches disk in plaintext. The PIN does not derive or recreate it: decryption requires both the PIN and the project-specific encrypted key envelope.

By default, vars prompts for the PIN at the terminal every time it needs to decrypt (vars show, vars hide, vars run). The PIN is not cached or stored by vars.

For trusted automation, PIN-taking commands accept --pin-file <path> and VARS_PIN_FILE; these are preferred because only the file path appears in process arguments or the environment. --key-file <path> and VARS_KEY_FILE select an externally provisioned encrypted envelope.

--pin takes precedence over --pin-file, followed by VARS_PIN and VARS_PIN_FILE. --key-file takes precedence over VARS_KEY_FILE, then vars finds the nearest .varskey. Any PIN or key-file source makes the encrypted envelope take precedence over the raw VARS_KEY compatibility fallback — for ambient PINs, VARS_KEY remains a fallback if the PIN fails to unlock the envelope (vars warns when this happens); explicit --pin/--pin-file failures always surface as errors. Every non-interactive unlock prints its credential source (e.g. vars: unlocked via --pin-file) to stderr so the active source is never invisible; vars doctor reports which ambient credentials are set.

A PIN passed with --pin is visible in process arguments and may be recorded in shell history, agent transcripts, and command logs. Use a mode-0600 PIN file and an owner-scoped PIN whenever possible.

This two-layer design means changing your PIN only re-encrypts the 32-byte master key — not every secret in the file.


The key file

.varskey stores one line per PIN:

pin:v1:aes256gcm:master:<salt>:<iv>:<ct>:<tag>

With multi-pin enabled, owner entries are appended:

pin:v1:aes256gcm:master:<salt>:<iv>:<ct>:<tag>
pin:v1:aes256gcm:owner=backend-team:<salt>:<iv>:<ct>:<tag>
pin:v1:aes256gcm:owner=frontend-team:<salt>:<iv>:<ct>:<tag>

Each line is self-contained. You can delete lines to create scoped key files for sharing — give the backend team a file with only their owner=backend-team line and the PIN. They can decrypt their fields and nothing else.


Key rotation

vars rotate

Generates a new master key, re-encrypts all values in memory, and prompts for a new PIN. Locked files are replaced atomically, and any open *.unlocked.vars editing session is encrypted and closed. During rotation, .varskey temporarily contains encrypted entries for both the old and new master keys so an interruption cannot lose the key needed by either file state. If owner PIN entries exist, vars rotate warns that they'll be invalidated and asks for confirmation. After rotation, re-run vars pin create for each owner.