# Use 1Password Developer Environments with Cursor

> Learn how the 1Password Cursor plugin mounts secrets without plaintext .env files, what its skill and validation hook do, and how to use it safely.

Author: [Flavio Copes](https://flaviocopes.com/about/) | Published: 2026-08-28 | Topics: [AI](https://flaviocopes.com/tags/ai/) | Canonical: https://flaviocopes.com/1password-cursor/

1Password has an official Cursor plugin for managing project secrets.

You keep the values in a 1Password Developer Environment. Your application still reads a normal `.env` path, but the plaintext contents are not stored on disk.

If the underlying feature is new to you, start with my [deep dive into 1Password Developer Environments](https://flaviocopes.com/1password-environments/).

Cursor can also create Environments, import an existing `.env` file, list variable names, and mount an Environment inside a project.

Let's see how the plugin works, how to set it up in Cursor, and where the security boundary really is.

## What 1Password Developer Environments are

A [1Password Developer Environment](https://www.1password.dev/environments) is a collection of environment variables for one project or stage.

For example, you might create separate Environments called:

- `my-app-development`
- `my-app-staging`
- `my-app-production`

Each Environment contains key-value pairs such as `DATABASE_URL`, `RESEND_API_KEY`, and `STRIPE_SECRET_KEY`.

The difference from a regular `.env` file is where the values live.

A normal `.env` file stores plaintext secrets on disk. It can be copied, indexed, backed up, or accidentally committed.

1Password stores the values in the Environment and exposes them through a local `.env` path only when a process reads it.

## The local `.env` file is a named pipe

The path looks like a file, but it is a Unix **named pipe**, also called a FIFO.

The flow looks like this:

```text
application reads .env
        ↓
1Password asks for authorization
        ↓
secret values pass through the named pipe
        ↓
application receives the variables
```

The plaintext contents are never written to that path on disk.

This means existing tools can keep using `.env`. Node.js `dotenv`, Docker Compose, `python-dotenv`, and other common libraries can read the mounted path without a special 1Password integration.

You can verify that the path is a pipe:

```bash
test -p .env && echo "1Password mount is active"
```

Do not use `test -f`. A FIFO is not a regular file, so that check fails even when the mount works.

## What the Cursor plugin adds

The [official 1Password plugin for Cursor](https://github.com/1Password/cursor-plugin) bundles three pieces.

### The MCP server

The MCP server gives Cursor tools for managing Developer Environments.

The agent can:

- list Environments
- create and rename an Environment
- list variable names
- add or update variables
- create a local `.env` mount
- list existing mounts

The listing tools return variable names and mount paths, not secret values.

If you want the background on this protocol, read my guide to [how MCP works](https://flaviocopes.com/what-is-mcp/).

### The agent skill

The plugin includes a `1password-environments` skill.

This is important because the MCP tools alone do not describe the complete workflow. The skill tells the agent what order to use, how to handle duplicate names, and how to finish an import.

For example, importing a `.env` file is not complete after Cursor creates the Environment and adds the variables. On macOS and Linux, the skill also mounts the Environment at the original `.env` path and verifies the mount.

The skill also tells Cursor not to reveal values in chat and not to read a mounted FIFO to inspect it. Cursor should use the variable-listing tool instead.

### The validation hook

The plugin registers a `beforeShellExecution` hook.

Before Cursor runs a shell command, the hook checks every relevant 1Password `.env` mount in the workspace.

It verifies that:

- the path exists
- the path is a FIFO
- the mount is enabled in 1Password

If a required mount is missing or disabled, the hook blocks the command and gives the agent an error it can help you fix.

This avoids a common failure: the agent starts a server or test suite without its required environment and then wastes time debugging unrelated errors.

## Requirements

You need:

- a 1Password subscription
- the 1Password desktop app
- Cursor
- macOS or Linux
- `sqlite3` in your `PATH` for the validation hook
- the MCP Server experiment enabled in 1Password Labs

macOS already includes `sqlite3`. On Linux, install it with your package manager if it is missing.

The complete workflow is not supported on Windows. Local `.env` mounts and the desktop MCP server are unavailable there. The Windows validation hook allows commands without checking mounts.

The plugin skill recommends using `op run --environment` on Windows instead of mounting a file.

## Install the plugin in Cursor

First, open the 1Password desktop app.

Go to **Settings → Labs** and enable **MCP Server**. You can also open `onepassword://settings/labs` on a computer with 1Password installed.

Then open Cursor and go to **Settings → Plugins**. Search for `1password` and install the plugin.

You can also open the command palette and choose **Plugins: Install Plugin**.

Or ask Cursor directly:

```text
/add-plugin 1password
```

The plugin installs the MCP configuration, skill, and hook together. Do not add only the MCP command by hand, because you would miss the import workflow and mount validation.

Finally, open **Cursor Settings → MCP** and confirm that the `1password` server is connected.

Under the hood, the configuration is tiny:

```json
{
  "mcpServers": {
    "1password": {
      "command": "1password-mcp"
    }
  }
}
```

The `1password-mcp` command comes from the 1Password desktop app. Cursor starts it on the same computer where the editor is running.

## Create an Environment

You can create the first Environment in the 1Password app.

Open **Developer → View Environments**, select **New environment**, give it a name, and save it.

You can then add variables manually or import an existing `.env` file.

After the plugin is connected, Cursor can do the same work through the agent:

```text
Create a new 1Password Environment called my-app-development
```

The skill first lists existing Environments. If the name already exists, it stops and asks whether to use it, choose another name, or cancel.

That pause matters. Silently reusing an Environment could update the wrong secrets.

## Import an existing `.env` file

To move an existing project into 1Password, ask Cursor:

```text
Import .env into a new 1Password Environment called my-app-development and mount it here
```

The workflow is:

1. Read the existing `.env` file.
2. Authenticate with the local 1Password app.
3. Check whether the Environment name already exists.
4. Create or select the Environment.
5. Add the variables.
6. Mount the Environment at the original `.env` path.
7. Verify the mount.

Importing is a migration step. Cursor must read the existing plaintext values to pass them to 1Password, so do this only with a local agent you trust.

If the `.env` file is tracked by Git, the skill stops before mounting.

Delete the tracked file and commit that deletion first. Otherwise Git still expects a regular file at the same path and operations such as `git status` become confusing.

The mounted FIFO cannot be staged with its secret contents, but removing the old tracked file is still required.

## Mount an existing Environment

You can also mount an Environment that already exists:

```text
Mount my staging Environment as .env in this repo
```

When the application first reads `.env`, 1Password shows an authorization prompt.

Approve it, and the application receives the values once through the pipe. Authorization remains active until 1Password locks.

You can have up to ten enabled local `.env` mounts per device.

## Choose which mounts the hook validates

By default, the hook finds every 1Password mount related to the current workspace.

You can make the list explicit with `.1password/environments.toml`:

```toml
mount_paths = [".env", "billing.env"]
```

Now the hook checks only those two paths.

An empty list disables validation:

```toml
mount_paths = []
```

I would keep the default unless a repository uses several mounts and only some are required for every command.

## The security boundary

This setup removes plaintext secrets at rest. It does not make secrets impossible for local processes to read.

Once you authorize the mounted `.env` file, every process on your computer can read it until 1Password locks or you disable the mount. 1Password does not distinguish between your application, Cursor, and another local process.

Do not ask an agent to run `cat .env`. Let the application read the file.

The validation hook is also **fail open**. If 1Password is missing, its database is unavailable, or `sqlite3` cannot run, the hook allows the shell command.

That is a sensible choice for development. A broken helper should not block every terminal command. But it means the hook is a setup guard, not a security sandbox.

There are two more practical limitations:

- named pipes are not designed for several simultaneous readers
- tools that watch `.env` may react to pipe activity as if the file changed

1Password specifically warns that Vite can enter a restart loop. If that happens, exclude the mounted path from the watcher:

```js
import { defineConfig } from 'vite'

export default defineConfig({
  server: {
    watch: {
      ignored: ['**/.env'],
    },
  },
})
```

## How I would use it

I would use this plugin for local development in Cursor.

For a project that needs a database, an email provider, and a payment provider, I would create one Development Environment and mount it as `.env`.

I would let the application read that path. I would let Cursor list variable names, detect missing mounts, and add a placeholder when the code introduces a new variable.

I would keep staging and production separate. Production secrets would stay in the hosting platform's secret store, not in a local `.env` mount.

The strongest part of this plugin is not that it gives an agent more secrets.

It gives the agent a structured way to work around secrets while keeping the plaintext file off disk.
