# Your coding agent can read your .env. Here's how I reduced the risk

> Source: <https://dev.to/edwinluijten/your-coding-agent-can-read-your-env-heres-how-i-reduced-the-risk-d6e>
> Published: 2026-11-13 08:11:35+00:00

If a coding agent can read your project files, assume it can read your `.env` too.

Ignore rules and editor settings can help, but they are not a security boundary. I moved secret values out of my project files and into 1Password, then used the CLI to provide them to the processes that need them.

That reduces one easy way for an agent to discover secrets. It does **not** make secrets safe from code the agent writes or commands it runs. I’ll cover that part too.

Before, my project had a file containing actual values:

```
# .env
STRIPE_API_KEY=replace-with-your-key
SENTRY_TOKEN=replace-with-your-token
API_URL=http://localhost:8787
```

Now, the shared secrets live in 1Password. The project contains references to them instead:

```
# op.env
STRIPE_API_KEY=op://My-Project/api-env/STRIPE_API_KEY
SENTRY_TOKEN=op://My-Project/api-env/SENTRY_TOKEN
```

Then I start the app with:

```
op run --env-file=op.env -- pnpm dev
```

`op run` resolves the references and provides the values to the process it starts. It does not, by itself, write those resolved values to a file.

An agent that reads `op.env` sees references, not secret values. But if it can run `op`, access the vault, or inspect a process that received the secrets, that protection may not be enough.

Not every environment variable is a secret. Go through your existing files one value at a time:

`op.env`.`.env.local`.
For example:

| File | Contents | Git | 
|---|---|---|
| `.env.local` | Non-sensitive, machine-specific settings | Ignored | 
| `op.env` | References to shared secrets | Committed | 

Make sure `.env.local` is actually ignored. Add it to `.gitignore` if it isn’t already:

```
.env.local
```

The `op://` references reveal metadata such as vault, item, and field names, so use names you’re comfortable exposing in the repository. Don’t point development references at a vault containing production credentials.

The desktop app and CLI are separate installations. Follow [1Password’s CLI installation guide](https://developer.1password.com/docs/cli/get-started/) for your platform.

```
# macOS
brew install --cask 1password-cli
# Windows
winget install AgileBits.1Password.CLI
```

In the 1Password desktop app, enable **Settings → Developer → Integrate with 1Password CLI**. Then open a new terminal and try:

```
op vault list
```

Authenticate when prompted, and check that you can see the vault you intend to use. Keep the development vault scoped to development credentials.

**Do this yourself, in a terminal or the 1Password app—not through the coding agent.** If the agent reads the old file while you migrate it, the secret has already entered the environment you’re trying to protect.

Create an item in 1Password and add each secret as a concealed field. Avoid passing secret values as command-line arguments: depending on the system and tooling, arguments can be exposed through process listings or monitoring.

Then create `op.env` with one reference per secret. For example:

```
STRIPE_API_KEY=op://My-Project/api-env/STRIPE_API_KEY
SENTRY_TOKEN=op://My-Project/api-env/SENTRY_TOKEN
```

Keep the file format simple: one `NAME=op://...` reference per line. This check flags any non-comment, non-blank line that doesn’t match that format:

```
grep -nEv '^(#|[A-Za-z_][A-Za-z0-9_]*=op://|$)' op.env
```

No output means every line matches the expected shape. It does **not** prove the references point to the right fields, or that a secret wasn’t committed elsewhere.

Check the diff before committing, and rotate any credentials an agent may already have seen. Moving a credential into a vault does not undo an earlier exposure.

`op run`
For most apps, start the process that needs the secrets through 1Password:

```
op run --env-file=op.env -- pnpm dev
```

The secret values are now available to the app and its child processes. They are no longer just references—but they are still accessible to code running in that process.

Give each service only the secrets it needs. If you run multiple services, use separate references or vault items rather than handing every service the same broad set of credentials.

Wrangler’s local variable-file behavior matters here. If `.dev.vars` exists, Wrangler may not use the process environment the way you expect. Wrangler 4.27 and later support `.env.local`; `CLOUDFLARE_INCLUDE_PROCESS_ENV=true` enables merging the process environment as well. Check the [Wrangler environment variable documentation](https://developers.cloudflare.com/workers/wrangler/system-environment-variables/) for the behavior in the version you use.

If you currently use `.dev.vars`:

`.dev.vars`.
Bash or zsh:

```
if [ -f op.env ]; then
  CLOUDFLARE_INCLUDE_PROCESS_ENV=true \
    op run --env-file=op.env -- wrangler dev
else
  wrangler dev
fi
```

PowerShell:

``` php
if (Test-Path op.env) {
  $previous = $env:CLOUDFLARE_INCLUDE_PROCESS_ENV
  try {
    $env:CLOUDFLARE_INCLUDE_PROCESS_ENV = 'true'
    op run --env-file=op.env -- wrangler dev
  } finally {
    $env:CLOUDFLARE_INCLUDE_PROCESS_ENV = $previous
  }
} else {
  wrangler dev
}
```

The process-environment setting can expose more of the environment than you intended to the Worker. Set it only for the command that needs it, and test the behavior with your Wrangler version.

Avoid using `op inject` to write resolved secrets into `.dev.vars` or another project file. That puts plaintext back on disk.

For CI, use a 1Password service account with read-only access to the specific vault it needs. Store its token as a GitHub Actions secret on a protected environment, not as a broadly available repository secret.

This example loads secrets as step outputs rather than exporting them into the whole job environment:

```
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4

      - name: Load secrets from 1Password
        id: op
        uses: 1password/load-secrets-action@v5
        with:
          export-env: false
        env:
          OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
          CLOUDFLARE_API_TOKEN: op://My-Project/ci/CLOUDFLARE_API_TOKEN
          STRIPE_API_KEY: op://My-Project/api-env/STRIPE_API_KEY

      - name: Deploy Worker
        uses: cloudflare/wrangler-action@v4
        env:
          STRIPE_API_KEY: ${{ steps.op.outputs.STRIPE_API_KEY }}
        with:
          apiToken: ${{ steps.op.outputs.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
          workingDirectory: workers/api
          secrets: |
            STRIPE_API_KEY
```

The `secrets` input names the variables Wrangler should upload; the values come from the step’s environment. Keep the service-account token and loaded values away from steps that do not need them.

Before using this, check the actions’ current documentation. For stronger supply-chain protection, pin actions to reviewed commit SHAs rather than relying only on version tags. Never make secrets available to workflows running untrusted code, such as pull requests from forks.

A vault keeps secret values out of files. It does not keep them out of processes.

When `op run` starts your app, the app receives ordinary environment variables. Code in that process can read them. A malicious dependency or a change the agent made could send them over the network.

Other processes running as your user may also be able to inspect process environments. For example, macOS has `ps eww <pid>` and Linux exposes process environment information under `/proc/<pid>/environ`, subject to system permissions and configuration.

And if you start the agent from a shell that already has secrets exported, commands it runs may inherit those values.

What helps:

A command allowlist is not a complete defense. An allowed command such as `pnpm test` or `node` can execute code in the project.

Agent settings can reduce accidental access, but they are not a replacement for keeping plaintext secrets out of the workspace.

You can add deny rules for secret files and the 1Password CLI in Claude Code’s settings. Check the current [Claude Code permissions documentation](https://docs.anthropic.com/en/docs/claude-code/settings) for the supported syntax and behavior.

Organizations on Copilot Business or Enterprise can configure [content exclusions](https://docs.github.com/en/copilot/how-tos/configure-content-exclusion/exclude-content-from-copilot). These can limit what Copilot accesses, but GitHub documents limitations for indirect access, such as a script reading an excluded file.

VS Code also provides settings for edit approval, terminal approval, and agent sandboxing. These settings vary by version and agent mode, so verify them against the current [VS Code documentation](https://code.visualstudio.com/docs/copilot).

A sandbox can restrict an agent’s commands, filesystem access, and network access. It does not necessarily protect a secret file that remains readable inside the workspace. And disabling inline suggestions for `.env` files is not the same as preventing an agent from reading them.

Keep secret values out of files the agent can read. Use runtime injection to provide credentials only to the process that needs them, and assume that process can access those credentials.

The vault prompt is useful for protecting vault access. If you get an unexpected prompt, deny it. But a prompt cannot protect secrets already handed to a running process.

This setup reduces exposure; it does not eliminate it. Use narrow, disposable credentials, and review what the agent changed before you run it.

*Commands and settings can change. Check the current 1Password CLI, Wrangler, GitHub Actions, and editor documentation before adopting them.*
