---
title: Proton Pass Plugin
description: Using Proton Pass with Varlock
---

import { Steps, Icon } from '@astrojs/starlight/components';
import Badge from '@/components/Badge.astro';

<div class="page-badges">
  <Badge npmPackage="@varlock/proton-pass-plugin" />
</div>

Our [Proton Pass](https://proton.me/pass) plugin loads secrets from Proton Pass using declarative instructions within your `.env` files.

It shells out to the official Proton Pass CLI (`pass-cli`) to resolve secret references in the format `pass://vault/item/field`.

## Features

- **Secret references via `pass://` URIs** (`pass://vault/item/field`)
- **Personal access token login** for CI (`PROTON_PASS_PERSONAL_ACCESS_TOKEN`): non-interactive, scoped, no full account credentials
- **Non-interactive password login** using environment variables (`PROTON_PASS_PASSWORD`, `PROTON_PASS_TOTP`, `PROTON_PASS_EXTRA_PASSWORD`)
- **In-session caching** per resolution run
- **Helpful error messages** with resolution tips

## Installation and setup

In a JS/TS project, you may install the `@varlock/proton-pass-plugin` package as a normal dependency.
Otherwise you can just load it directly from your `.env.schema` file, as long as you add a version specifier.

See the [plugins guide](/guides/plugins/#installation) for more instructions on installing plugins.

### Install Proton Pass CLI

You must have `pass-cli` installed:

```bash
curl -fsSL https://proton.me/download/pass-cli/install.sh | bash
```

See: [Proton Pass CLI overview](https://protonpass.github.io/pass-cli/).

### Configure the plugin

In production/CI you typically want a fully non-interactive login. The plugin supports two
authentication methods: a **personal access token** (recommended) or a **username + password**.

:::note[Login credentials are `@internal` by default]
The login credential types (`protonPassPersonalAccessToken`, `protonPassPassword`, `protonPassTotp`, `protonPassExtraPassword`) are marked [`@internal`](/reference/item-decorators/#internal): varlock uses them to authenticate but does **not** inject them into your application. Override with `@internal=false` if needed.
:::

#### Personal access token (recommended)

A [personal access token](https://protonpass.github.io/pass-cli/commands/login/#personal-access-token-login)
is a scoped, non-interactive credential. It never needs a username, password, or TOTP, which makes it
the cleanest option for CI.

Create one with the Proton Pass CLI, then grant it access to the vaults/items it needs:

```bash
# Create a token (the full value is only shown once, so save it somewhere safe)
pass-cli pat create --name "deploy-bot" --expiration 3m
# PROTON_PASS_PERSONAL_ACCESS_TOKEN=pst_xxxx...xxxx::TOKENKEY

# Grant it access to a vault or item
pass-cli pat access grant ...
```

```env-spec title=".env.schema"
# 1. Load the plugin
# @plugin(@varlock/proton-pass-plugin)
#
# 2. Initialize Proton Pass plugin instance
# @initProtonPass(
#   id=prod,
#   personalAccessToken=$PROTON_PASS_PERSONAL_ACCESS_TOKEN
# )
# ---

# @type=protonPassPersonalAccessToken @sensitive @internal
PROTON_PASS_PERSONAL_ACCESS_TOKEN=
```

When a `personalAccessToken` is provided it takes precedence: the plugin runs `pass-cli login`
with the token and ignores `username`/`password`/`totp`/`extraPassword`.

:::note[Token expiration]
Personal access tokens have a **mandatory expiration** set when you create them (`1d`, `1w`, `1m`, `3m`, `6m`, or `1y`)
and do **not** auto-renew. When a token expires, mint a new one with `pass-cli pat renew` (or `pat create`) and update
the value in your config. There is nothing for the plugin to refresh or cache on your behalf.
:::

#### Username + password

```env-spec title=".env.schema"
# @initProtonPass(
#   id=prod,
#   username=$PROTON_PASS_USERNAME,
#   password=$PROTON_PASS_PASSWORD,
#   totp=$PROTON_PASS_TOTP,
#   extraPassword=$PROTON_PASS_EXTRA_PASSWORD
# )
# ---

# @type=protonPassPassword @sensitive @internal
PROTON_PASS_PASSWORD=

# @type=protonPassTotp @sensitive @internal
PROTON_PASS_TOTP=

# @type=protonPassExtraPassword @sensitive @internal
PROTON_PASS_EXTRA_PASSWORD=
```

The `username` is passed as an argument to `pass-cli login --interactive <username>`.
The password/TOTP/extra password are provided to the CLI via environment variables, as supported by Proton Pass CLI login docs:
[login command](https://protonpass.github.io/pass-cli/commands/login/).

:::tip[Already logged in?]
If `pass-cli` is already logged in, you can also omit credentials and just rely on the existing session.

This mostly applies to local development, in CI, you will probably want static credentials.
:::

### Reducing local login prompts

For local development, especially with multi-process task runners like Turbo, these patterns help reduce repeated prompts:

- Store Proton Pass credentials in local config items (for example `.env.local`), wire them into `@initProtonPass(...)`, and encrypt sensitive values using [Varlock local encryption](/guides/local-encryption/).
- Reuse an existing `pass-cli` session when possible (`pass-cli login` once in your terminal before starting your dev tasks).

:::note[TOTP in local config]
If your account requires TOTP, a static value in config will expire quickly. For fully non-interactive workflows, use a [personal access token](#personal-access-token-recommended) instead.
:::

## Loading secrets

Use the `protonPass()` resolver function to fetch a field value from a Proton Pass secret reference.

Secret reference syntax (as documented by Proton Pass CLI):
[secret references](https://protonpass.github.io/pass-cli/commands/contents/secret-references/).

```env-spec title=".env.schema"
# Fetch the `password` field from a secret reference:
DB_PASSWORD=protonPass(pass://Production/Database/password)
```

When you need multiple plugin instances, use the optional first argument to select the `id`:

```env-spec title=".env.schema"
DB_PASSWORD=protonPass(prod, pass://Production/Database/password)
```

### Optional secrets

If a secret might not exist, pass `allowMissing=true`:

```env-spec title=".env.schema"
OPTIONAL_DB_PASSWORD=protonPass(pass://Production/Database/password, allowMissing=true)
```

When allowed, missing secrets resolve to an empty string (`""`).

## Reference

### Root decorators

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

Initialize a Proton Pass plugin instance for `protonPass()` resolver.

**Key/value args:**

- `id` (optional): instance identifier for multiple instances
- `personalAccessToken` (optional): personal access token (maps to `PROTON_PASS_PERSONAL_ACCESS_TOKEN`). When set, takes precedence over username/password.
- `username` (optional): login username/email passed to `pass-cli login --interactive`
- `password` (optional): `pass-cli` password (maps to `PROTON_PASS_PASSWORD`)
- `totp` (optional): `pass-cli` TOTP code (maps to `PROTON_PASS_TOTP`)
- `extraPassword` (optional): `pass-cli` extra password (maps to `PROTON_PASS_EXTRA_PASSWORD`)

```env-spec "@initProtonPass"
# @initProtonPass(
#   id=prod,
#   personalAccessToken=$PROTON_PASS_PERSONAL_ACCESS_TOKEN
# )
```
</div>
</div>

### Resolver functions

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

Fetch a field value from Proton Pass using a secret reference.

**Array args:**

- `secretRef` (required): `pass://vault/item/field`
- `instanceId` (optional, if 2 args are provided): instance identifier

**Key/value args:**

- `allowMissing` (optional): if `true`, returns empty string when the secret is missing

```env-spec /protonPass\\(.*\\)/
# With secret reference (default instance)
DB_PASSWORD=protonPass(pass://Production/Database/password)

# With explicit instance
DB_PASSWORD=protonPass(prod, pass://Production/Database/password)
```
</div>
</div>

### Under the hood

This resolver uses:
- `pass-cli item view --output json <secretRef>` to fetch the requested field
- `pass-cli login` only when an auth error occurs, then retries the original command. With a `personalAccessToken` it runs `pass-cli login` with `PROTON_PASS_PERSONAL_ACCESS_TOKEN` set; otherwise it runs `pass-cli login --interactive <username>` with the password env vars.
