The Agent Secrets Pattern: Giving AI Access Without Git Exposure

Cover Image for The Agent Secrets Pattern: Giving AI Access Without Git Exposure
Mario Giancini
Mario Giancini
@MarioGiancini
Published:
Read Time: 8 min

Update (January 2026): Since this article was first published, significant security research has revealed that AI coding agents handle credentials more aggressively than many developers realized. Claude Code reads .env files by default, and protection mechanisms like deny rules have been unreliable in some versions. I've updated this article with current best practices, including PreToolUse hooks as a more robust protection layer. The core pattern remains sound—and arguably more important than ever.

I stumbled on this pattern while building automation commands for Claude Code and Cursor.

The problem: my AI agents needed credentials for browser automation, but I obviously couldn't commit those credentials to git.

The best practice for keys and credentials is .env, or environment variables, but using that would create a new problem.

My app's .env has database URLs, API keys, and other runtime secrets. Mixing automation credentials in there felt off. And more practically, the .env lived in a subproject directory, which made the path awkward for workspace-level commands.

There's also a security concern I didn't fully appreciate at the time: Claude Code reads .env files by default, without explicit consent. Security researchers at Knostic discovered that these files get silently loaded into context, meaning your database passwords and API keys are being sent to the LLM whether you intended it or not.

I needed a better solution.

The Pattern: A Dedicated .agent-secrets File

project/
├── .agent-secrets.example   # Template (committed)
├── .agent-secrets           # Your credentials (gitignored)
├── .gitignore               # Blocks .agent-secrets from git
├── .cursorignore            # Allows .agent-secrets for Cursor
└── .claude/
    └── settings.local.json  # Grants explicit read access for Claude Code

The key insight: you need to explicitly deny access to sensitive files, not just rely on .gitignore.

Here's what I learned the hard way: Claude Code doesn't fully respect .gitignore for file reading—it may skip gitignored files in search operations, but it can still read them directly. And Cursor's ignore functionality is officially described as "best effort" rather than guaranteed. You need layered protection.

Configuring Claude Code

Claude Code can read most files by default—including your .env. The critical step is denying access to sensitive files while allowing access to your agent secrets.

Add this to .claude/settings.local.json:

{
  "permissions": {
    "allow": [
      "Read(.agent-secrets)"
    ],
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./secrets/**)",
      "Read(./**/credentials*)"
    ]
  }
}

This configuration does two things: it blocks Claude from reading your app's environment files, and it explicitly allows reading .agent-secrets without prompting.

Important caveat: There have been reported issues where deny rules in settings.json weren't properly enforced in certain Claude Code versions. For critical protection, add a PreToolUse hook as a backup (see below).

Configuring Cursor

Cursor has built-in ignore patterns that block .env files by default. However, there's a critical caveat: Cursor officially describes this as "best effort" protection, not a guarantee.

There have been reported cases where Cursor accessed .env files despite ignore rules, particularly through terminal commands like cat .env.

To allow Cursor to read .agent-secrets while maintaining protection, create a .cursorignore file:

# .cursorignore
# Explicitly block sensitive files (defense in depth)
.env
.env.*
secrets/
credentials*

# Allow agent secrets (overrides gitignore)
!.agent-secrets

The ! prefix tells Cursor to include .agent-secrets even though it's in .gitignore.

Additional protection: In your VSCode/Cursor settings, you can prevent .env files from being opened at all, which also blocks composer and chat access:

{
  "files.exclude": {
    "**/.env": true,
    "**/.env.*": true
  }
}

Defense in Depth: PreToolUse Hooks

Given the reported reliability issues with deny rules, I now recommend adding a PreToolUse hook as a backup protection layer. Hooks intercept tool calls before execution and can block them based on custom logic.

Add this to your .claude/settings.local.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Read|Edit|Write|MultiEdit",
        "command": "python3 -c \"import sys,json; d=json.load(sys.stdin); p=d.get('tool_input',{}).get('file_path',''); exit(2 if any(x in p for x in ['.env','/secrets/','credentials']) and '.agent-secrets' not in p else 0)\""
      }
    ]
  }
}

This hook blocks any read/write operations on files containing .env, /secrets/, or credentials in the path—except for .agent-secrets. Exit code 2 tells Claude to stop the operation.

For a more maintainable approach, you can use a bash script:

#!/bin/bash
# .claude/hooks/protect-secrets.sh

DENY_PATTERNS=(".env" "/secrets/" "credentials" ".pem" ".key")
ALLOW_PATTERNS=(".agent-secrets")

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

for pattern in "${ALLOW_PATTERNS[@]}"; do
  [[ "$FILE_PATH" == *"$pattern"* ]] && exit 0
done

for pattern in "${DENY_PATTERNS[@]}"; do
  [[ "$FILE_PATH" == *"$pattern"* ]] && exit 2
done

exit 0

Then reference it in your settings:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Read|Edit|Write|MultiEdit",
        "command": "bash .claude/hooks/protect-secrets.sh"
      }
    ]
  }
}

This gives you programmable, auditable protection that doesn't rely on settings.json deny rules working correctly.

The File Format

Keep it simple. No JSON, no YAML. Just key-value pairs:

# .agent-secrets.example
# Agent Automation Secrets
# Copy to .agent-secrets and fill in values

# LinkedIn Browser Automation
LINKEDIN_EMAIL=
LINKEDIN_PASSWORD=

# Other browser automation credentials can go here

The .example file gets committed. The actual .agent-secrets file with your credentials stays local.

Why Separation Matters

This isn't just about organization.

It's about the principle of least privilege applied to AI agents—and recent security research has made this more urgent than ever.

In December 2025, researchers discovered over 30 vulnerabilities across major AI coding tools including Claude Code, Cursor, GitHub Copilot, and Windsurf—resulting in 24 CVEs. One critical vulnerability (CVE-2025-55284) allowed attackers to exfiltrate secrets via DNS requests through prompt injection.

Your app's .env might contain:

  • Database connection strings
  • Payment processor keys
  • Admin API tokens

Your agent doesn't need any of that to log into LinkedIn and scrape a profile. And if a vulnerability allows context leakage, you want to minimize what's exposed.

By separating agent credentials, you:

  1. Reduce attack surface: If an agent leaks context, it only has automation credentials—not your Stripe keys
  2. Simplify auditing: One file, one purpose, easy to review what agents can access
  3. Enable workspace-level access: Credentials live at project root, not buried in subprojects
  4. Limit blast radius: A compromised LinkedIn password is bad; a compromised database connection string is catastrophic

Command Integration

In your custom commands, reference the file explicitly:

### Step 3: Login Check
If LinkedIn shows a login wall, read credentials:
\`\`\`bash
cat .agent-secrets 2>/dev/null | grep -E "LINKEDIN_EMAIL|LINKEDIN_PASSWORD"
\`\`\`
Or use ReadFile on `.agent-secrets` directly.

The agent knows exactly where to look. No searching, no guessing.

The Broader Pattern

This approach aligns with established security practices:

PrincipleApplication
Centralized ManagementOne file for all agent credentials
Environment SegmentationApp secrets vs agent secrets
Least PrivilegeAgents only access what they need
Defense in DepthGitignored AND explicitly scoped

For larger teams or production environments, you'd layer this with proper secret management (HashiCorp Vault, AWS Secrets Manager).

But for solo developers and small teams, the pattern provides meaningful security without operational overhead.

The Decision Framework

Before adding credentials to .agent-secrets, ask:

  1. Does the agent actually need this? Only include what's required.
  2. Could this be a read-only token? Prefer minimal permissions.
  3. Is this documented? Keep .example updated.
  4. Is there a fallback? "Ask user to log in manually" if credentials are missing.

Implementation Checklist

# 1. Create the template
touch .agent-secrets.example
# Add your variable names (no values)

# 2. Create your actual file
cp .agent-secrets.example .agent-secrets
# Fill in your credentials

# 3. Gitignore it
echo ".agent-secrets" >> .gitignore

# 4. Configure Claude Code (if using)
# Create .claude/settings.local.json with:
# - Allow: Read(.agent-secrets)
# - Deny: Read(.env), Read(.env.*), Read(secrets/**)
# - PreToolUse hook for defense in depth

# 5. Configure Cursor (if using)
# Create .cursorignore with:
# - Explicit deny patterns for .env files
# - !.agent-secrets to allow agent secrets

# 6. Optional: Add VSCode file exclusions
# Prevents .env from being opened in editor

# 7. Document it
# Add section to CLAUDE.md or README explaining the pattern

Further Reading

The security landscape for AI coding agents is evolving rapidly. Here are some resources I found valuable:


The intersection of AI tooling and security is moving fast. If you've found other patterns or encountered issues with these protections, I'd love to hear about it.


Did you find this article useful?

Subscribe to my newsletter for more content like this.


Related Posts

View All →