TL;DR — Four strategies for using Claude Code on real projects with secrets: demo project (max security, painful), duplicate + git patch (strong security, manageable),
.claudeignore(fast, trust required), and git worktree (physical security + git-native sync). Add aCLAUDE_CONTEXT.mdto any of them for a 3–5x output quality jump. Jump to the strategies →
I asked Claude Code to refactor our authentication module. Ten minutes later: clean code, proper error handling, tests included. The hype is real.
Then reality hit. I wanted to use Claude Code on actual production projects. Every real project has secrets:
.envfiles with database URLs and API keys- AWS/GCP/Azure credentials
- Stripe/payment provider keys
- OAuth client secrets
- Private SSL certificates
- Proprietary business logic (trading algorithms, pricing engines)
docker-compose.ymlwith production passwords- Firebase/Supabase config files
The naive approach (create a blank demo project, copy files over, ask Claude Code to edit them, copy back) is perfectly safe. It’s also perfectly miserable. I tried it for a week and wanted to throw my laptop out the window. So I tested four strategies over the last three months. Here’s what actually works.
What Does Claude Code Actually See in Your Project?
First, let’s clear up a common misconception. Claude Code doesn’t automatically scan your entire project. It requests file access. You approve or deny. This is good.
The problem is that the boundaries aren’t clean. When Claude Code needs architectural context, it naturally wants to read files near your sensitive code. The interface might be harmless, but the implementation contains database URLs, auth headers, and API keys.
Example:
# These files are SAFE for Claude Code to read:src/services/user.service.ts # TypeScript interfaceapp/api/users/route.ts # Next.js API routelib/repositories/user_repo.dart # Flutter repositorySources/Services/UserService.swift # iOS service protocol
# These files contain SECRETS — danger zone:.env # DATABASE_URL, API keysdocker-compose.yml # DB passwords, service tokenssrc/config/database.ts # Connection stringsfirebase.json # Firebase configgoogle-services.json # Android FirebaseGoogleService-Info.plist # iOS FirebaseHere’s what it looks like in code across multiple languages:
// src/services/payment.service.ts — harmless interface ✅export interface PaymentService { charge(amount: number, currency: string): Promise<PaymentResult>;}
// src/config/stripe.ts — contains secrets ❌export const stripeConfig = { secretKey: process.env.STRIPE_SECRET_KEY, // real key at runtime webhookSecret: "whsec_live_abc123...", // hardcoded! danger};# services/payment_service.py - harmless interface ✅class PaymentService: def charge(self, amount: float, currency: str) -> PaymentResult: pass
# config/stripe.py - contains secrets ❌STRIPE_SECRET_KEY = "sk_live_51J..." # dangerSTRIPE_WEBHOOK_SECRET = os.getenv("STRIPE_WEBHOOK_SECRET", "whsec_live_abc123")// Services/PaymentService.swift - harmless protocol ✅protocol PaymentService { func charge(amount: Decimal, currency: String) async -> PaymentResult}
// Config/StripeConfig.swift - contains secrets ❌struct StripeConfig { static let secretKey = "sk_live_51J..." // danger static let webhookSecret = "whsec_live_abc123..."}Claude Code asks to read payment.service.ts for context. Seems fine. Then it asks for stripe.ts because that’s where configuration lives. Now it has your secrets.
The question isn’t whether Claude Code will see sensitive code. It’s when and how much you’re comfortable exposing. Here are four strategies I’ve used, ranked by security and daily sustainability.
Key insight: Claude Code does not automatically scan your entire project on startup — it requests file access tool-by-tool as it needs context. The security risk emerges when architectural context-gathering leads Claude to request files that happen to contain secrets alongside the interfaces it actually needs. The danger zone is not bulk scanning but incremental context expansion during complex tasks.
Strategy 1: Demo Project + Copy-Paste
How it works:
Real Project (secret files) ↓ manual copyBlank Demo Project (no secrets) → Claude Code works here ↓ manual copy backReal Project (updated)Create a blank project with no secrets. Copy the files you want to edit into it. Use Claude Code there. Copy the edited files back to your real project.
The experience:
This is maximum security. Claude Code never touches your real project. The problem is that it’s miserable to use.
I tried this for a week. I needed Claude Code to refactor an auth module that depends on five services, two middleware files, and a database config. I copy-pasted seven files into the demo project. Claude Code asked for context about the base service class. I copy-pasted that. Then it needed the error handler. I copy-pasted that. Then it needed the middleware chain setup.
Thirty minutes later, I’d copy-pasted 15 files, Claude Code gave me a refactor that didn’t compile because it was missing context from three other files I didn’t copy, and I spent another 45 minutes manually merging changes back into the real project.
A task that should have taken five minutes took 90. The output quality was mediocre because Claude Code was working with context poverty. It had snippets without the full architectural picture.
Verdict:
| Security | Speed | Output Quality | Daily Sustainability |
|---|---|---|---|
| ⭐⭐⭐⭐⭐ | ⭐ | ⭐⭐ | ⭐ |
Best for: One-off edits on a single isolated file with zero dependencies. Not realistic for most real work.
Key insight: The demo project copy-paste strategy breaks down because Claude Code’s output quality depends on architectural context. Working from isolated file snippets without the full dependency graph causes Claude to generate code that compiles in isolation but fails when integrated — an auth module refactor that needed 15 files for full context took 90 minutes via copy-paste versus an estimated 5 minutes with full project access.
Strategy 2: Duplicate Project + Git Patch
How it works:
git clone real-project duplicated-projectcd duplicated-project./remove-secrets.sh # automated script./add-dummy-secrets.sh # automated script# work with Claude Code heregit diff > changes.patchcd ../real-projectgit apply changes.patchClone your real project. Run a script that removes all secret files and replaces constants with dummy values. Work with Claude Code in this clean duplicate. When done, generate a git patch of the changes and apply it to the real project.
The experience:
This was a massive improvement over copy-paste. I wrote a universal cleanup script:
#!/bin/bash# remove-secrets.sh (works for any project)
# Remove environment filesrm -f .env .env.local .env.production .env.*.local
# Remove infrastructure secretsrm -f docker-compose.prod.yml docker-compose.override.yml
# Remove cloud credentialsrm -f **/credentials.json **/service-account.jsonrm -rf ~/.aws/ .aws/
# Remove certificates and keysfind . -name "*.pem" -deletefind . -name "*.key" -deletefind . -name "*.p12" -deletefind . -name "*.keystore" -deletefind . -name "*.jks" -delete
# Remove platform-specific secretsfind . -name "google-services.json" -deletefind . -name "GoogleService-Info.plist" -deleterm -f local.properties local.settings.json
# Replace hardcoded secrets with dummies (adapt patterns to your stack)# TypeScript/JavaScriptfind . -name "*.ts" -o -name "*.js" | xargs sed -i '' 's/sk_live_[a-zA-Z0-9]*/sk_test_DUMMY_KEY/g'find . -name "*.ts" -o -name "*.js" | xargs sed -i '' 's/https:\/\/api\.internal\./https:\/\/api.example.com\//g'
# Pythonfind . -name "*.py" | xargs sed -i '' 's/sk_live_[a-zA-Z0-9]*/sk_test_DUMMY_KEY/g'
# Gofind . -name "*.go" | xargs sed -i '' 's/sk_live_[a-zA-Z0-9]*/sk_test_DUMMY_KEY/g'
echo "✅ Secrets removed"#!/bin/bash# Create dummy .envcat > .env << EOFDATABASE_URL=postgres://user:pass@localhost:5432/mydbREDIS_URL=redis://localhost:6379STRIPE_SECRET_KEY=sk_test_DUMMYSTRIPE_WEBHOOK_SECRET=whsec_test_DUMMYJWT_SECRET=dummy-secret-for-devAWS_ACCESS_KEY_ID=AKIADUMMYAWS_SECRET_ACCESS_KEY=dummyGOOGLE_OAUTH_CLIENT_SECRET=GOCSPX-dummyEOF
# Create dummy docker-compose.ymlcat > docker-compose.yml << EOFversion: '3.8'services: db: image: postgres:15 environment: POSTGRES_PASSWORD: dummy POSTGRES_DB: mydbEOF
# Platform-specific dummies# Androidif [ -d "android" ]; then echo "STRIPE_KEY=pk_test_DUMMY" > local.properties echo '{"project_info":{"project_id":"dummy"}}' > google-services.jsonfi
# iOSif [ -d "ios" ]; then echo '<?xml version="1.0" encoding="UTF-8"?><plist><dict><key>API_KEY</key><string>DUMMY</string></dict></plist>' > ios/Runner/GoogleService-Info.plistfi
echo "✅ Dummy secrets added"Now Claude Code has near-full project context without seeing real secrets. The output quality jumped dramatically. It understood the architecture, followed existing patterns, and generated code that actually compiled.
The sync workflow was fast (about 30 seconds per iteration):
git diff > /tmp/changes.patchcd ../real-projectgit apply /tmp/changes.patchnpm test # or: pytest, go test, ./gradlew test, swift test# if tests fail, iterateThe downside is maintenance. Every time a new secret is added to the real project, I need to update remove-secrets.sh. Every time a new required configuration file is added, I need to update add-dummy-secrets.sh. It’s friction, but manageable.
Verdict:
| Security | Speed | Output Quality | Daily Sustainability |
|---|---|---|---|
| ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
Best for: Medium-to-high security environments where you can invest 15-20 minutes in initial setup and accept occasional script updates.
Key insight: The duplicate project + git patch strategy achieves strong security by removing real secrets and replacing them with dummy values in a cloned repo, then syncing changes back via
git diff > changes.patchandgit apply. The sync cycle runs in about 30 seconds per iteration. The maintenance cost is real: every new secret added to the main project requires updating theremove-secrets.shscript to keep the clean clone current.
Strategy 3: .claudeignore on Real Project
How it works:
Real Project/├── .claudeignore # blocks sensitive files├── secrets/ # ❌ blocked│ └── *.pem├── .env # ❌ blocked├── docker-compose.yml # ❌ blocked└── src/ # ✅ accessibleWork directly on your real project. Use a .claudeignore file to block Claude Code from accessing sensitive files and directories.
The experience:
This is the fastest strategy by far. Zero overhead. No duplicate projects. No sync scripts. No context loss. Claude Code sees your full architecture and produces excellent output.
Here’s a comprehensive .claudeignore that works for any stack:
# ============================================# Universal Secrets# ============================================.env**.pem*.key*.p12*.keystore*.jkscredentials.jsonservice-account.json**/secrets/**
# ============================================# Infrastructure# ============================================docker-compose.prod.ymldocker-compose.override.yml**/vault/**secrets.yamlvault-config.hcl
# ============================================# Platform-Specific Secrets# ============================================
# Androidgoogle-services.jsonlocal.propertieskeystore.properties
# iOSGoogleService-Info.plist*.mobileprovision*.certSigningRequest
# Firebasefirebase.json.firebaserc
# Cloud Providers.aws/.gcp/.azure/
# Node.js.npmrc
# Python.pypirc
# ============================================# Proprietary Business Logic# ============================================src/**/trading/src/**/pricing-engine/src/**/encryption/impl/lib/**/proprietary/**/internal/algorithms/
# ============================================# Build Artifacts (not secret but noisy)# ============================================node_modules/dist/build/.next/out/__pycache__/*.pyctarget/bin/obj/.gradle/.dart_tool/Pods/Podfile.lockvendor/.terraform/The output quality is the best of all strategies because Claude Code has complete architectural context. It follows your patterns perfectly, understands your dependency injection setup, and generates code that compiles on the first try.
The trust question:
Here’s the honest part: .claudeignore is a software boundary, not a physical one. The files are still on your disk. You’re trusting that:
- Claude Code respects
.claudeignoreand doesn’t read blocked files - There are no bugs that accidentally bypass the filter
- No files that reference secrets (like a README with example API calls) slip through
I’ve used this strategy for months on personal projects and client work where I control the risk policy. I’ve never seen Claude Code access a blocked file. But I can’t verify it in real-time. There’s no audit log showing “Claude Code requested X, was denied.”
Some companies can’t accept this. If your security policy requires provable guarantees that secrets were never accessible, this isn’t enough.
Edge cases:
Files that reference secrets but don’t contain them directly:
# docs/API.md - not a secret file, but contains secret references## Authentication
curl -H "Authorization: Bearer $STRIPE_KEY" \ https://api.stripe.com/v1/customersYou need to either add docs/*.md to .claudeignore (loses valuable documentation context) or manually review every file that might reference secrets.
Which secrets matter for which platform:
| Secret File | Platforms |
|---|---|
.env, .env.* | All (universal) |
docker-compose.prod.yml | Backend, fullstack |
google-services.json | Android |
GoogleService-Info.plist | iOS |
credentials.json | GCP (any) |
*.keystore, *.jks | Android, Java |
*.p12, *.pem | iOS, SSL certificates |
local.properties | Android |
secrets.yaml | Kubernetes |
.npmrc | Node.js private registries |
.pypirc | Python private packages |
firebase.json | Firebase (all platforms) |
Verdict:
| Security | Speed | Output Quality | Daily Sustainability |
|---|---|---|---|
| ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
Best for: Solo developers, startups, and teams with moderate security requirements who trust software boundaries and can accept that blocked files are still on disk.
Key insight: The
.claudeignorestrategy provides maximum speed and output quality because Claude Code has complete architectural context — only the secret-containing files are blocked. The security tradeoff is that blocked files remain on disk and the protection is a software boundary, not a physical one. There is no audit log confirming which file access requests were denied, making it unsuitable for compliance environments that require provable access controls.
Try it now: Create a
.claudeignorefile in one of your projects and add.env*,*.pem,*.key, andgoogle-services.json. Then start a Claude Code session and ask it to describe your project architecture. Verify it gives you useful architectural context without needing to read any of the blocked files.
Strategy 4: How Do You Use Git Worktree for a Clean Branch?
How it works:
real-project/ # main worktree on 'master' (has secrets)real-project-clean/ # second worktree on 'clean' branch (no secrets) # Claude Code works here ↑# Sync via git merge or cherry-pickUse git worktree to create a second working directory of the same repository on a separate branch. The clean branch has all secrets removed and added to .gitignore. Claude Code works in the clean worktree. You sync changes between branches using git merge or git cherry-pick.
The experience:
This is the most elegant solution I’ve found. It combines the physical security of Strategy 2 (secrets literally don’t exist in the Claude Code workspace) with git-native sync (no manual patches).
Setup (one-time, ~10 minutes):
cd ~/projects/my-app
# Create clean branchgit checkout -b clean
# Remove all secretsrm -f .env .env.local .env.productionrm -f docker-compose.prod.ymlrm -f **/credentials.json **/service-account.jsonfind . -name "*.pem" -deletefind . -name "*.key" -deletefind . -name "*.p12" -deletefind . -name "google-services.json" -deletefind . -name "GoogleService-Info.plist" -delete
# Add dummy secretsecho "DATABASE_URL=postgres://user:pass@localhost:5432/mydb" > .envecho "STRIPE_KEY=sk_test_DUMMY" >> .envecho "JWT_SECRET=dummy-secret-for-dev" >> .env
git add -Agit commit -m "Remove secrets for AI workspace"
# Create second worktreegit worktree add ../my-app-clean clean
# Add real secrets to .gitignore on clean branch (prevents accidents)cat >> .gitignore << EOF# Real secrets (never commit on clean branch).env.productiondocker-compose.prod.yml**/credentials.json**/service-account.json*.pem*.key*.p12google-services.jsonGoogleService-Info.plistEOF
git add .gitignoregit commit -m "Ignore secrets on clean branch"Daily workflow:
cd ~/projects/my-app-clean # Claude Code workspace# work with Claude Codegit add -Agit commit -m "Add retry logic to payment service"
cd ~/projects/my-app # real projectgit cherry-pick clean # pull changes from clean branchnpm test # or: pytest, go test, cargo test, swift test# verify with real secretsThe beauty: Claude Code’s changes are committed to the clean branch. You review them, test them in the real workspace, and cherry-pick them over. If something breaks, you haven’t polluted your main branch.
Why this works:
- Physical security: The clean worktree doesn’t have secret files on disk. Even if Claude Code tries to read them, they don’t exist.
- Full context: The clean branch has your full architecture, dependencies, and patterns, just with dummy secrets.
- Git-native sync: No manual patches.
git cherry-pickorgit mergehandles the sync in seconds. - Verifiable: You can
lsthe clean workspace and confirm secret files aren’t there.
The learning curve:
Most developers don’t know about git worktree. It’s intimidating the first time. But once you’ve set it up, it’s incredibly smooth. I now use this for every production project.
Verdict:
| Security | Speed | Output Quality | Daily Sustainability |
|---|---|---|---|
| ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
Best for: Teams with strong security policies who need provable guarantees, developers comfortable with git, and anyone working on long-term projects where setup time amortizes to zero.
Key insight: Git worktree creates two physical working directories from one repository — the main worktree retains real secrets on disk while the clean worktree literally does not have those files. This makes the security guarantee verifiable with a simple
lscommand. Changes committed to the clean branch sync to the main project viagit cherry-pickin seconds, combining physical secret isolation with a git-native workflow that requires no manual patching or file copying.
Which Security Strategy Should You Use?
Choose based on your risk profile:
| If you need… | Use Strategy |
|---|---|
| Absolute maximum security, don’t care about speed | #1 Demo Project |
| Strong security + good context, can handle setup | #2 Duplicate + Patch |
| Maximum speed + great output, trust software boundaries | #3 .claudeignore |
| Physical security + git workflow, comfortable with git | #4 Git Worktree |
My personal journey:
Started with #1. Lasted a week. Switched to #2 for two months. Now I use #4 for production work and #3 for personal projects.
What Is the Single Biggest Quality Improvement You Can Make?
Regardless of which strategy you use, add a CLAUDE_CONTEXT.md file to your project root. This is the single biggest improvement to output quality I’ve found.
Examples for different stacks:
Next.js/React
## Project: E-commerce Dashboard
**Tech Stack:**- Next.js 14 + TypeScript + App Router- React Server Components + Server Actions- Prisma + PostgreSQL- TailwindCSS + shadcn/ui
**Architecture:**app/ ├── (auth)/ # Auth routes ├── api/ # API routes └── dashboard/ # Protected routes lib/ ├── actions/ # Server Actions ├── db/ # Prisma client └── utils/ # Shared utilities
**Critical Conventions:**- Server Components by default, use "use client" only when needed- All database access via Prisma, never raw SQL- Server Actions for mutations, not API routes- All secrets via process.env, never hardcoded
**Secret Files (for reference, never access):**- `.env.local` - DATABASE_URL, STRIPE_SECRET_KEY- `docker-compose.yml` - Database passwords
**When working on payment features:**Reference `app/api/stripe/webhook/route.ts` as the canonical pattern.Python/FastAPI
## Project: Analytics API
**Tech Stack:**- FastAPI + Python 3.11- SQLAlchemy + Alembic- PostgreSQL + Redis- Pydantic for validation
**Architecture:**app/ ├── api/ # Routers ├── core/ # Config, dependencies ├── models/ # SQLAlchemy models ├── schemas/ # Pydantic schemas ├── services/ # Business logic └── repositories/ # Data access
**Critical Conventions:**- Router → Service → Repository pattern (never skip layers)- All config via Settings (Pydantic BaseSettings), never direct env access- Async everywhere (async def, await)- Type hints mandatory (mypy strict mode)
**Secret Files:**- `.env` - DATABASE_URL, REDIS_URL, SECRET_KEY- `docker-compose.yml` - Service passwords
**When working on authentication:**Reference `app/services/auth_service.py` for token patterns.Mobile (Kotlin Multiplatform)
## Project: Mobile Banking App (KMP)
**Tech Stack:**- Kotlin Multiplatform (Android + iOS)- Compose Multiplatform for UI- Ktor for networking- SQLDelight for database- Koin for DI
**Architecture:**shared/ ├── commonMain/ # Shared logic ├── androidMain/ # Android-specific └── iosMain/ # iOS-specific androidApp/ # Android UI iosApp/ # iOS UI
**Critical Conventions:**- Business logic in commonMain, platform-specific only when necessary- Sealed classes for all state (Loading, Success, Error)- expect/actual for platform APIs- All secrets injected via platform modules
**Secret Files:**- Android: `local.properties`, `google-services.json`- iOS: `GoogleService-Info.plist`, `Config.xcconfig`
**When working on network layer:**Reference `shared/src/commonMain/.../ApiClient.kt` for patterns.With this file in place, Claude Code’s output quality jumps 3-5x. It knows your patterns, follows your architecture, and doesn’t make assumptions about where to put code.
I wrote a deep-dive on this: Why CLAUDE.md Is the Most Important File in Your Project.
Key insight: Adding a
CLAUDE_CONTEXT.md(orCLAUDE.md) file to a project produces a 3–5x improvement in Claude Code’s output quality because it gives every session the same architectural context a senior developer would bring. Without it, Claude infers patterns from file structure alone. With it, Claude knows your layer boundaries, naming conventions, dependency injection approach, and which files to treat as authoritative patterns — before reading a single line of source code.
Get weekly Claude Code tips — One practical tip every week. No fluff, no spam. Subscribe to AI Developer Weekly →
What Is the Bottom Line on Using Claude Code Securely?
You don’t have to choose between Claude Code’s productivity and your project’s security. The strategies here — physical isolation, permissions boundaries, git-native workflows — are what harness engineering for Claude Code calls the permissions and constraints layer: the system controls that make it safe to give an AI agent broad access without losing sleep over what it might touch. Pick the strategy that matches your risk tolerance:
- Strategy #1: Maximum security, terrible speed (demo project)
- Strategy #2: Strong security, good balance (duplicate + patch)
- Strategy #3: Maximum speed, trust required (.claudeignore)
- Strategy #4: Physical security + git workflow (worktree)
Whatever you choose, add CLAUDE_CONTEXT.md. That alone will 3x your output quality.
I started with copy-paste hell, moved to duplicate projects, and now run git worktrees for production and .claudeignore for personal work. The right strategy depends on your project, your team, and your security requirements.
Want to go deeper? The Claude Code Mastery course covers all of this and more, including the complete security module (Phase 2) with hands-on exercises. Phases 1-3 are free.
Get the free Claude Code Cheat Sheet, 50+ commands in a single PDF, when you join the newsletter.
What to Read Next
- I Set Up 3 Layers of Defense in Claude Code. It Deleted My File Anyway. — The specific deny list configuration that blocks all
rmcommands, not justrm -rf - Claude Code Hooks: The Power Feature Nobody Talks About — Add a hook-based secret scanner that catches credentials before they touch any file
- Claude Code Git Worktrees for Parallel Development — Go deeper on the git worktree workflow for both security and parallel development