Dev.to Security πŸ” Cybersecurity πŸ‘ 0 πŸ“– 7 min read

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

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 1P

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.

The idea

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.

First, sort your environment variables

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

  • Shared credentials or sensitive values: Put them in a vault and reference them from op.env.
  • Non-sensitive local settings: Values such as a localhost URL or port can stay in .env.local.
  • Machine-specific credentials: Treat these as secrets too. β€œIt only works on my laptop” does not make a credential harmless. Don’t leave it in a project file the agent can read.

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.

Set up the 1Password CLI

The desktop app and CLI are separate installations. Follow 1Password’s CLI installation guide for your platform.

For example:

# 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.

Move existing secrets carefully

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.

Start local development with 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.

A Wrangler wrinkle

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 for the behavior in the version you use.

If you currently use .dev.vars:

  1. Rename it to .env.local and update scripts that create or read .dev.vars.
  2. Make sure .env.local contains only non-sensitive local settings.
  3. Start Wrangler through op run when the project has an op.env.

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:

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.

Use a scoped identity in GitHub Actions

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.

What this does not protect against

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:

  • Review the agent’s changes before running them with secretsβ€”especially new network requests, scripts, and dependencies.
  • Use low-privilege, short-lived development credentials.
  • Give each process only the secrets it needs.
  • Don’t start the agent from a shell with secrets already exported.
  • Where practical, sandbox agent-run commands and restrict their network access.

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

Add agent-specific guardrails

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

Claude Code

You can add deny rules for secret files and the 1Password CLI in Claude Code’s settings. Check the current Claude Code permissions documentation for the supported syntax and behavior.

VS Code with GitHub Copilot

Organizations on Copilot Business or Enterprise can configure content exclusions. 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.

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.

The practical takeaway

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.

πŸ“° Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.