---
title: Local encryption
description: Use varlock() to secure local untracked secrets with cross-platform encryption backends
---

Varlock includes a built-in [`varlock()` function](/reference/functions/#varlock) that lets you secure local untracked secrets (typically in git-ignored env files like `.env.local`).

This allows you to keep **everything out of plaintext** - even temporary local overrides, or a "secret-zero" which is needed by some plugins to load the rest of your sensitive data.

Sensitive values will be stored _encrypted_, with the key linked to your local device, and requiring no extra configuration. The encryption mechanism varies per platform, but as it is tied to your device, **these values are not meant to be shared or committed to git**.

```diff(lang=env-spec) title=".env.local" del="shh-im-secret" add=/(varlock\\(.*\\))/
PLAINTEXT=shh-im-secret            # 🚨 danger
SECURED=varlock(local:abc123...)  # ✅ secured at rest
```

:::tip[Keychain also available on macOS]
For mac users, you can also use [keychain()](/plugins/macos-keychain/). The experience will be similar, but encrypted values are stored in the system keychain.
:::

## Quick start - existing secrets

You likely already have some plaintext secrets in a `.env.local` file. If not you can create one, and add some. Ensure those items are marked as [`@sensitive`](/reference/item-decorators/#sensitive) in your schema. Then you can use [`varlock encrypt`](/reference/cli-commands/#encrypt) to encrypt them in-place:

1. Run `varlock encrypt --file .env.local` to encrypt them in-place
2. Sensitive plaintext values are replaced with `varlock("local:<***encrypted***>")`
3. Decryption happens automatically during `varlock load` / `varlock run`

Only plaintext values of `@sensitive` items are encrypted, so you may run it multiple times.

## Using `varlock(prompt)` resolver

When you need to edit a value or add a new sensitive item, just set the value to `varlock(prompt)` and run `varlock load`. You will be prompted for the new value in a secure input prompt, and the encrypted value will be written back to the file automatically.

```env-spec title=".env.local" add="varlock(prompt)"
EXISTING_ITEM=varlock(local:abc123...)
NEW_ITEM=varlock(prompt) # will prompt you for new value
```

## Using `varlock encrypt` CLI

You can also call the [`varlock encrypt`](/reference/cli-commands/#encrypt) CLI command to get a secure prompt to encrypt a single value. It will spit out an item you can copy/paste into your file:

```text title="$ varlock encrypt"
◇  Enter the value you want to encrypt
│  ▪▪▪▪▪▪▪▪

Copy this into your .env.local file and rename the key appropriately:

SOME_SENSITIVE_KEY=varlock("local:ABC123...")
```

#### `--file` option
As outlined above, you can also run `varlock encrypt --file .env.local` to encrypt all sensitive plaintext values in a file in-place. This is a great way to quickly encrypt many secrets at once.


## Core commands

Use [`varlock encrypt`](/reference/cli-commands/#encrypt) to create encrypted payloads:

```bash
# Interactive: encrypt a single value
varlock encrypt
# Batch: encrypt all sensitive plaintext values in .env.local
varlock encrypt --file .env.local
```

Use [`varlock reveal`](/reference/cli-commands/#reveal) to inspect decrypted values safely:

```bash
varlock reveal                 # interactive - select and reveal
varlock reveal API_KEY         # securely reveal specific item
varlock reveal API_KEY --copy  # copy to clipboard
```

Use [`varlock lock`](/reference/cli-commands/#lock) to invalidate biometric session cache when stepping away:

```bash
varlock lock
```

## Backend selection overview

Varlock chooses the best available backend automatically:

| Platform | Backend | Key Storage | Biometric |
|----------|---------|-------------|-----------|
| macOS | Secure Enclave | Hardware Secure Enclave | Touch ID |
| Windows | NCrypt TPM + Windows Hello | TPM (when available), else DPAPI | Windows Hello (face/fingerprint/PIN) |
| Linux | TPM2 / Secret Service | TPM2 and/or system key store | Yes (when configured via polkit/PAM) |
| All platforms | File-based fallback | `~/.varlock/` directory | No |

If native capabilities are unavailable, varlock falls back to file-based local encryption.

## Unattended decryption (headless servers & CI)

Local encryption isn't just for laptops. On a headless host with a TPM (a home server, a CI runner, a container host), varlock seals the key to the machine's TPM and decrypts automatically at load with **no prompt**. That makes it a good way to store a "secret-zero" (like a 1Password service account token) that bootstraps the rest of your secrets:

```env-spec title=".env.schema"
# @type=opServiceAccountToken @sensitive @internal
OP_TOKEN=varlock("local:...")
```

On Linux this requires `tpm2-tools` and a TPM (see [Linux setup](#linux) below). The sealed value survives reboots and is bound to that specific machine. It can't be copied to another host.

This unattended path applies to backends whose key is unsealed to the host: Linux (TPM2) and Windows (NCrypt/TPM). **macOS is different:** Secure Enclave keys never leave the enclave, and every decrypt requires user presence (Touch ID or password), so there is no unattended decrypt on macOS. A headless Mac or CI runner falls back to file-based encryption instead.

### What this protects (and what it doesn't)

Hardware-backed encryption protects your secrets **at rest**: a stolen disk, a leaked backup, or an env file committed by mistake can't be decrypted off the machine.

It does **not** protect against an attacker who is already running code as your user on that machine. They can ask the TPM to unseal the value exactly like varlock does. This is the same trade-off as tools like [`systemd-creds`](https://www.freedesktop.org/software/systemd/man/latest/systemd-creds.html), and it's inherent to unattended decryption: if no human is present to approve, anything running as you can decrypt.

If you want a presence gate (fingerprint / password / YubiKey via polkit + PAM) on every decrypt, register the policy:

```bash
sudo varlock-local-encrypt setup --linux-biometrics
```

Once configured, varlock's normal decrypt flow requires user presence, so everyday loads are no longer unattended. Enable this only on interactive machines, not headless servers.

Treat this gate as a **consent prompt**, not an at-rest boundary. On Linux the TPM unseal isn't bound to polkit, so (as noted above) other code already running as your user can still unseal directly. It means "a human approved this load," not "only a human can ever decrypt."

## Platform details & setup

### macOS

- Native Swift helper (Secure Enclave integration)
- Uses system-native secure input / auth prompts
- Includes a menu bar applet flow for native interactions
- Hardware-backed key protection via Secure Enclave with biometric auth where supported

✅ No additional install/setup steps required

### Windows

- Native helper with **TPM-sealed** key protection when a TPM 2.0 chip is available (via NCrypt / Platform Crypto Provider)
- Falls back to **DPAPI** (user-session-scoped) when TPM sealing is unavailable
- **Windows Hello** gates interactive decrypts (fingerprint/face/PIN), separate from at-rest protection, same as before
- Windows native and **WSL** workflows are both supported (WSL uses the Windows `.exe` via `--via-daemon`; Hello + TPM behavior is identical)
- Automated daemon startup/installation behavior is built in for biometric session flows
- WSL decrypt flows use a native bridge to the Windows daemon path

✅ No additional install/setup steps required

#### Upgrading existing keys to TPM

New keys automatically use TPM sealing when available. Existing **DPAPI** keys are **auto-upgraded to TPM sealing on the next successful decrypt** (public key unchanged, no re-encryption of `.env` values). To upgrade without decrypting, use `varlock-local-encrypt rewrap-key --key-id varlock-default`.

#### Windows Defender false positives

`varlock-local-encrypt.exe` is Varlock's official local-encryption helper (bundled with the npm package and standalone CLI). Windows Defender may flag it as `Trojan:Win32/Wacatac.C!ml` or similar, a common machine-learning false positive on new, unsigned executables. The binary is safe when obtained from official Varlock releases.

**Verify the download:** each [GitHub release](https://github.com/dmno-dev/varlock/releases) includes `SHA256SUMS.txt` with hashes for all native helpers. After installing, compare your file:

```powershell
# PowerShell: run from the directory containing the .exe
Get-FileHash varlock-local-encrypt.exe -Algorithm SHA256
```

Match the hash against the `native-bins/win32-x64/varlock-local-encrypt.exe` line in `SHA256SUMS.txt` for your release version.

**If quarantined:** restore the file from Windows Security → Protection history, or add an exclusion for your project directory. Reinstall from npm or the official release if needed.

**Permanent fix:** official builds are moving to Authenticode code signing, which greatly reduces these alerts. Until your installed version is signed, verification via `SHA256SUMS.txt` is the recommended way to confirm authenticity.

### Linux

- Native Linux helper when available
- User-presence verification via polkit/PAM (can support fingerprint/face/password depending system setup)

Common packages/tools:
- `tpm2-tools` (plus distro TPM2 libs such as `tpm2-tss`)
- `polkit` for user-presence authorization flows
- `xclip` or `xsel` for `varlock reveal --copy`

Example installs:

```bash
# Debian/Ubuntu
sudo apt-get update
sudo apt-get install -y tpm2-tools tpm2-tss policykit-1 xclip
```

```bash
# Fedora/RHEL variants
sudo dnf install -y tpm2-tools tpm2-tss polkit xclip
```

If biometric/user-presence prompts are unavailable on Linux, complete policy setup (native helper command):

```bash
sudo varlock-local-encrypt setup --linux-biometrics
```