---
title: macOS Keychain
description: Using the macOS Keychain as a secrets source with Varlock
---

import { Steps, Icon, Aside } from '@astrojs/starlight/components';

The built-in `keychain()` resolver lets you load secrets directly from the **macOS Keychain** using declarative instructions in your `.env` files. It communicates with the Keychain through Varlock's native Swift daemon, which enforces biometric authentication (Touch ID) and per-session access control.

:::note[Built-in support]
No plugin installation is required. `keychain()` is built into Varlock and available on macOS.
:::

:::tip[Or use `varlock()`]
If you don't already have secrets stored in your macOS Keychain, you may want to use the [`varlock()` resolver](/guides/local-encryption) instead. It provides the same biometric-gated, device-local encryption benefits without needing to manage Keychain items, and it works cross-platform.

See the [local encryption guide](/guides/local-encryption/) for more details.
:::

## Features

- **Built-in**: no plugin or extra dependency needed
- **Biometric gating**: access is protected by Touch ID via Varlock's native daemon
- **Interactive picker**: use `keychain(prompt)` to browse and select items via a native dialog
- **Auto-write-back**: prompt mode writes the resolved reference back to your config file
- **Named or positional syntax** for quick or precise lookups
- **Field selection**: extract specific fields from keychain items
- **Multiple keychain support**: access the login, System, or custom keychains

:::caution
`keychain()` is only supported on **macOS**. It will throw an error on other platforms. If your team includes non-Mac developers, consider wrapping keychain items with a fallback or using a cross-platform secrets source for shared configs.
:::


## Getting started
Rather than wiring up items individually, the easiest way to get started is by using `keychain(prompt)`. This will open a native picker dialog where you can select existing keychain items, or create new ones. After selection, Varlock will automatically write the resolved reference back into your config file for future use.

```env-spec add="keychain(prompt)"
ITEM=keychain(prompt) # Opens a native picker dialog
```

## Import plaintext env files

If you already have sensitive plaintext values in a local `.env` file, import them into macOS Keychain and replace the plaintext with stable `keychain(...)` references. The file to import is the first argument:

```sh
varlock keychain import .env --profile jb
```

By default this **edits the file in place**: each sensitive plaintext value is replaced by its `keychain(...)` ref, so the secret no longer lives on disk. Comments and non-sensitive values are left untouched. To write the refs to a *different* file instead and leave the source as-is, pass `--write-to`:

```sh
varlock keychain import .env --profile jb --write-to .env.jb
```

Import requires an existing `.env.schema` file for the input env file so Varlock knows which input variables are secrets and which are not. It only imports variables marked `@sensitive` in that schema and never prints secret values. Re-running is safe: values already converted to `keychain(...)` refs are skipped. By default, Varlock refuses to overwrite an existing Keychain item (or, with `--write-to`, an existing ref in the target file); pass `--force` to overwrite.

The file you name must be one Varlock loads as part of your env setup. It resolves your normal env graph (from `package.json` `varlock.loadPath`, or the files in the current directory) to read sensitivity from your schema, the same way `varlock encrypt --file` works. An arbitrary file outside that setup can't be imported. The value stored in Keychain is taken from the file you named specifically; an override of the same variable in another file (such as `.env.local`) does not change what gets imported.

Generated refs use `service="varlock"` and account names like `<project>:<profile>:<ENV_VAR>`. The project defaults to the current directory name and can be overridden:

```sh
varlock keychain import .env --profile jb --project my-app
```

## Set one secret manually

To store one secret without putting the value in shell history, run `set` and enter the value at the masked prompt:

```sh
varlock keychain set API_KEY --profile jb --write-to .env.jb
```

This stores the item under `service="varlock"` with account `<project>:<profile>:API_KEY`, then writes the matching `keychain(...)` ref when `--write-to` is provided. If you need to paste a multi-line secret, pipe it through stdin instead of passing it as a command-line argument:

```sh
cat secret.txt | varlock keychain set PRIVATE_KEY --profile jb --write-to .env.jb
```

By default, `set` refuses to overwrite an existing Keychain item or env ref. Pass `--force` to replace both.

## Access management

If VarlockEnclave cannot read an existing Keychain item, grant access without using `/usr/bin/security` directly:

```sh
varlock keychain fix-access --account "my-app:jb:API_KEY"
```

`--service` defaults to `varlock`, but can be overridden for legacy or manually-created Keychain items:

```sh
varlock keychain fix-access --service "com.company.api" --account "admin"
```

You can also fix every explicit `keychain(...)` ref in an env file:

```sh
varlock keychain fix-access --path .env.jb
```

## List Keychain items

To see which Keychain items are available, list them by service name. This shows metadata only (service, account, and keychain) and never reads secret values:

```sh
varlock keychain list           # same as bare `varlock keychain`
varlock keychain list API_KEY   # filter (matches service, account, or label)
```

Pass `--keychain` to search a specific keychain, such as `System`:

```sh
varlock keychain list --keychain System
```

## Reference

<div class="reference-docs">
<div>
#### `keychain()`

Fetch a secret from the macOS Keychain. Communicates with the Keychain through Varlock's native daemon, which enforces biometric (Touch ID) authentication.

**Array args:**
- `service` (optional): Service name of the keychain item (positional shorthand)
- `prompt` (optional): Enter interactive picker mode

**Key/value args:**
- `service` (optional): Service name of the keychain item
- `account` (optional): Account identifier for the keychain item
- `keychain` (optional): Name of a specific keychain to search (e.g., `"System"`)
- `field` (optional): Specific field to extract from the keychain item
- `prompt` (optional): If set, opens a native picker dialog for interactive selection

```env-spec /keychain\\(.*\\)/
# Positional shorthand
DATABASE_PASSWORD=keychain("com.company.database")

# Named service param
API_KEY=keychain(service="com.company.api")

# With account
ADMIN_PW=keychain("com.company.db", account="admin")

# Targeting a specific keychain
CERT=keychain("com.company.cert", keychain="System")

# Field selection
TOKEN=keychain("com.company.auth", field="password")

# Interactive picker mode
NEW_SECRET=keychain(prompt)
```
</div>
</div>

---

## Troubleshooting

### "keychain() is only supported on macOS"
This resolver requires macOS. It is not available on Linux or Windows. If you need cross-platform secret management, consider using one of the [plugin-based secret sources](/plugins/overview).

### Failed to read keychain item
- Verify the item exists in Keychain Access.app
- Check that the service name and account match exactly
- Try `keychain(prompt)` to browse available items and grant VarlockEnclave access via the native dialog

### Selection was cancelled
If you dismiss the native picker without selecting an item, the resolution will fail. Run `varlock` again to retry, or replace `keychain(prompt)` with an explicit `keychain(service="...")` reference.

### VarlockEnclave access
The first time you access a keychain item through Varlock, macOS may prompt you to grant access to VarlockEnclave. Approve this to allow future reads. If you accidentally denied access, you can update the item's Access Control settings in Keychain Access.app.
