varsvars
Encryption & Security

How Encryption Works

AES-256-GCM with deterministic IVs — the crypto behind vars.

Your .vars file is safe to commit. Here's how.

SOPS-style inline encryption

vars uses a single-file model. There is no separate vault file and no unlocked copy to manage. Secret values are encrypted inline — the file structure (variable names, schemas, metadata, comments, use directives, check blocks) stays plaintext. Only the values are encrypted.

Committed state (config.vars):

env(dev, staging, prod)

public APP_NAME = "my-app"
STRIPE_KEY : z.string() {
  dev     = "sk_example_placeholder"
  staging = enc:v2:aes256gcm-det:jkl...:mno...:pqr...
  prod    = enc:v2:aes256gcm-det:abc...:def...:ghi...
}

Working state after vars show (config.unlocked.vars):

env(dev, staging, prod)

public APP_NAME = "my-app"
STRIPE_KEY : z.string() {
  dev     = "sk_example_placeholder"
  staging = "sk_test_sTgKyExAmPlE32ChArAcTeRs"
  prod    = "sk_live_aBcDeFgHiJkLmNoPqRsTuVwX"
}

vars show decrypts and renames config.varsconfig.unlocked.vars. vars hide encrypts and renames back. The file extension is the primary indicator of lock state.


AES-256-GCM

Every secret value is encrypted independently with AES-256-GCM.

AES-256-GCM uses a 256-bit key and Galois/Counter Mode. GCM adds an authentication tag to each ciphertext, so corrupted or modified data is rejected rather than silently accepted.

Each encrypted value looks like:

enc:v2:aes256gcm-det:<iv>:<ciphertext>:<tag>
enc:v2:aes256gcm-det:owner=<name>:<iv>:<ciphertext>:<tag>
SegmentWhat it is
encLiteral marker
v2Format version
aes256gcm-detAES-256-GCM with HMAC-derived deterministic IV
owner=<name>Optional — which owner's key encrypted this value
<iv>12-byte initialization vector
<ciphertext>The encrypted value
<tag>16-byte authentication tag

The auth tag catches tampering. If anyone modifies the ciphertext, even one byte, decryption throws an error instead of booting your app with corrupted data.


Deterministic IVs

v2 uses HMAC-derived deterministic IVs instead of random IVs:

IV = HMAC-SHA256(key, "VAR_NAME@env:" + plaintext)[0:12]

The same key + variable name + environment + plaintext always produces the same IV, and therefore the same ciphertext. In practice:

  • If a value hasn't changed, running vars hide again produces identical ciphertext. Your git diff shows only what actually changed.
  • git blame is preserved on unchanged lines.
  • Two branches that both encrypt the same unchanged value produce the same bytes, so merge conflicts only happen on real conflicts.

The nonce reuse is safe because the nonce is derived from the plaintext itself: identical nonces always correspond to identical plaintexts. It is standard AES-256-GCM — not AES-GCM-SIV — with HMAC-derived nonces instead of random nonces. The -det suffix in the algorithm name indicates deterministic nonce derivation.