env-isolation-pattern
Skill0 starsChanged 6 months agoMITView on GitHub
운영 환경변수가 테스트/마이그레이션 프로세스에 누설되어 운영 DB가 손상되는 사고 회피. 사용 시점 — "테스트가 운영 DB 건드림", "env 격리", "setdefault no-op", "GATEWAY_DB_URL 누설", "destructive test", "CI prod env leak". explicit unset + unconditional override + .env export 후 격리 3 패턴.
- Reads credentials
In this repository61 other files it gives its agents.
- api-contract-testing-patternSkill
- api-key-lifecycle-patternSkill
- api-route-consistency-patternSkill
- bash-cli-best-practicesSkill
- bilingual-book-authoringSkill
- blue-green-deployment-patternSkill
- book-builderSkill
- bulk-user-onboarding-patternSkill
- cicd-github-actions-patternSkill
- claude-code-skill-authoringSkill
- cloudflare-tunnel-setupSkill
- cloudflare-tunnel-ssh-access-patternSkill
- concurrent-load-testing-patternSkill
- coverage-instrumentation-patternSkill
- dependency-vulnerability-fixSkill
- deployment-checklistSkill
- developer-onboarding-doc-patternSkill
- exam-systemSkill
- fastapi-async-patternsSkill
- fastapi-gateway-patternSkill
- gem-llm-admin-cliSkill
- gem-llm-build-docsSkill
- gem-llm-cli-clientSkill
- gem-llm-cloudflare-tunnelSkill
- gem-llm-debug-mcpSkill
- gem-llm-deploy-vllmSkill
- gem-llm-gateway-debugSkill
- gem-llm-load-testSkill
- gem-llm-overviewSkill
- gem-llm-review-promptSkill
- gem-llm-supervisorSkill
- gem-llm-test-inferenceSkill
- gem-llm-troubleshootingSkill
- gem-llm-vllm-debugSkill
- html-static-dashboard-patternSkill
- k8s-cron-alternativesSkill
- k8s-pod-autostartSkill
- korean-tech-blog-authoringSkill
- llm-eval-multi-modelSkill
- llm-serving-performance-tuningSkill
- md-raid-deadlock-recoverySkill
- mermaid-diagram-authoringSkill
- multi-agent-autonomous-loop-patternSkill
- multi-agent-git-collaboration-patternSkill
- multi-agent-orchestratorSkill
- multi-llm-routing-patternSkill
- observability-bundleSkill
- pandoc-bilingual-buildSkill
- playbook-authoring-patternSkill
- pod-process-autostart-patternSkill
- postgres-migration-from-sqliteSkill
- production-postmortem-patternSkill
- project-bootstrapSkill
- prometheus-fastapi-metricsSkill
- pytest-fastapi-patternSkill
- quota-rate-limit-patternSkill
- shell-cli-dispatch-patternSkill
- sqlite-wal-safe-opsSkill
- subagent-prompt-engineering-patternSkill
- vllm-bootstrapSkill
- The first 60 shown.
The file
Files
env-isolation-pattern
운영 환경변수가 자식 프로세스(pytest / alembic / 마이그레이션 스크립트)에 그대로 상속되어 운영 DB를 테스트 코드가 wipe 하는 사고를 막는 가이드. case 18 사고를 일반화 — 특정 변수명(GATEWAY_*) 이 아니라 "부모 → 자식 환경 누설" 자체를 다룬다.
이 skill 의 핵심 명제 — os.environ.setdefault(...) 는 방어가 아니다. 부모가 이미 export 했다면 setdefault 는 no-op 이고, 테스트 코드는 운영 DB 에 그대로 연결된다.
1. 사용 시점
- "테스트 돌렸더니 운영 DB 가 비었음"
- "alembic upgrade 가 prod 에 적용됨"
- conftest 의
os.environ.setdefault(...)가 안 먹힘 - supervisor / wrapper 스크립트가
set -a; source .env; set +a로 export - CI 파이프라인에서 secret 이 통째로 child 에 노출
- "왜
:memory:SQLite 가 아니라 prod URL 로 붙지?" - destructive test (drop_all, truncate, factory reset) 가 prod 와 한 끗 차이
2. 사고 시나리오 (case 18 사례)
supervisor.sh
set -a; source .env; set +a ← GATEWAY_DB_URL=prod.db export
└── pytest
└── conftest.py
os.environ.setdefault("GATEWAY_DB_URL", ":memory:")
# setdefault 는 이미 설정된 값을 안 덮음 → :memory: 적용 안 됨
Base.metadata.drop_all(engine) # ← 운영 DB wipe원인은 두 군데 — supervisor 가 .env 를 그대로 export, conftest 가 약한 방어(setdefault)만 사용. 둘 중 하나만 고쳐도 사고는 안 났다.
3. 약한 방어 vs 강한 방어
| 방어 | 코드 | 강도 | 비고 |
os.environ.setdefault(K, V) | conftest.py | 약함 | 이미 설정된 값 무시 (case 18 원인) |
os.environ[K] = V | conftest.py | 강함 | 무조건 덮음 |
del os.environ[K] | conftest.py | 강함 | 부모 값 제거 후 재설정 |
env -u K 로 child 호출 | shell wrapper | 강함 | 자식 환경에서 unset |
Docker -e K=V | container | 가장 강함 | 외부 격리, .env 누설 원천 차단 |
separate .env.test | 운영 스크립트 | 강함 | export 자체가 다른 파일 |
규칙 — 가능하면 두 단계 이상. shell wrapper 에서 unset + conftest 에서 override 면 한쪽이 깨져도 다른 쪽이 막는다.
4. 3가지 패턴
패턴 A: explicit unset (운영 → 테스트)
운영 wrapper 가 export 하는 prod 변수를 테스트 진입 직전에 명시적으로 제거.
env -u GATEWAY_DB_URL \
-u GATEWAY_ADMIN_KEY \
-u GATEWAY_API_KEY_SALT \
-u DATABASE_URL \
pytest tests/integration장점 — Python 코드 수정 불필요, 운영 스크립트 수정 불필요. 단점 — 변수가 늘어날 때마다 unset 목록 갱신.
패턴 B: unconditional override (conftest)
conftest 최상단 (import 전) 에서 무조건 덮기.
# tests/conftest.py — 최상단, 다른 import 보다 먼저
import os
# 모든 prod prefix 변수 제거
for k in list(os.environ):
if k.startswith(("GATEWAY_", "ADMIN_", "DATABASE_URL")):
del os.environ[k]
# 테스트용 값 명시적 설정
os.environ["GATEWAY_DB_URL"] = "sqlite+aiosqlite:///:memory:"
os.environ["GATEWAY_ADMIN_KEY"] = "test_admin_key"
os.environ["GATEWAY_API_KEY_SALT"] = "test_salt"핵심 — setdefault 가 아니라 직접 할당. fixture 가 아니라 모듈 import 시점에 실행 (앱 코드가 환경변수를 읽기 전).
패턴 C: container isolation (CI/CD)
GitHub Actions / GitLab CI / Docker — 환경변수를 명시적으로 declare 하고 secret 만 inject.
# .github/workflows/test.yml
env:
GATEWAY_DB_URL: sqlite+aiosqlite:///:memory:
GATEWAY_ADMIN_KEY: test
# 다른 prod 환경변수는 secrets 에서만 가져옴
# → declare 안 한 변수는 자동으로 누설 안 됨장점 — 자동 격리. 단점 — 로컬 개발자 머신에서 source .env && pytest 하면 여전히 위험. 패턴 B 와 같이 써야 함.
5. 검증 방법
테스트 환경에서 prod env 가 누설되었는지 확인.
# 1. 셸 레벨 — pytest 실행 직전
env | grep -E "^(GATEWAY_|ADMIN_|DATABASE_URL)"
# 비어있어야 안전
# 2. Python 레벨 — conftest 실행 직후
python3 -c "
import os
leaked = [k for k in os.environ
if k.startswith(('GATEWAY_', 'ADMIN_'))
and 'test' not in os.environ[k].lower()
and ':memory:' not in os.environ[k]]
if leaked:
print(f'leaked: {leaked}')
raise SystemExit(1)
"CI 에서는 이 검증 자체를 테스트 첫 step 으로 넣어 fail-fast.
6. .env 가 누설되는 흔한 경로
set -a; source .env; set +a— 전체 export, supervisor 패턴docker run --env-file .env— 모든 변수 컨테이너 진입- systemd
EnvironmentFile=— service 단위 전체 상속 - VSCode dev container
.envauto-load — IDE 자체가 주입 - supervisor / wrapper 스크립트가 sub-command 로 .env 주입
- direnv
.envrc— 디렉터리 진입 시 자동 export - Jupyter / IPython 커널이 부모 셸 환경 그대로 상속
7. 흔한 함정
setdefault사용 — 부모가 이미 설정한 값 무시 (case 18 원인).setdefault는 "기본값 제공"이지 "강제 격리"가 아니다.- conftest fixture 가 test 시작 후 환경변수 변경 — engine 은 import 시점에 이미 prod 연결로 생성됨. 환경변수 override 는 fixture 가 아니라 모듈 top-level.
pytest -p no:cacheprovider등으로 plugin 격리해도 환경변수는 누설.- subprocess 에
env=os.environ.copy()— 전체 복사, 격리 안 됨. 필요한 키만 골라서 dict 구성. - Jupyter / IPython 커널 — 부모 셸 환경 그대로 상속. 노트북에서 destructive 코드 실행 시 동일 사고.
- alembic / 마이그레이션 스크립트 — pytest 와 같은 함정.
alembic upgrade head가 prod 에 적용되는 사고. .env.example과.env혼용 — 누군가.env에 prod URL 남겨두고 git ignore 만으로 안전하다고 착각.
8. 책임 분리
3 레이어 — 어느 한 레이어가 깨져도 나머지가 막아야 한다.
- 운영 스크립트 (supervisor / wrapper) — 환경변수를 export 하는 책임. 테스트 명령에는 unset 추가.
- 테스트 wrapper (run-tests.sh) —
env -u로 prod 변수 명시적 제거. - conftest.py —
setdefault대신 unconditional override. 모듈 top-level 에서 실행. - 앱 코드 (settings.py) — 환경변수가 prod-like 하면 fail-fast.
if "prod" in url and pytest_running: raise.
9. 백업이 마지막 방어선
case 18 은 백업이 사고 비용을 0 으로 만들었다 — backup-db.sh (case 13) 가 자동으로 보존했기에 즉시 복구 가능했다. 백업 + env 격리 = 이중 방어.
env 격리만 믿지 말 것 — 누군가 setdefault 를 다시 PR 로 넣을 수도 있고, 새 환경변수 추가하면서 unset 목록 빠뜨릴 수도 있다. 백업이 없으면 한 번의 실수가 영구 손실이다.
관련 패턴은 sqlite-wal-safe-ops (백업 정책) 참조.
10. 일반화 매핑
case 18 의 GATEWAY_* 는 예시일 뿐. 다른 프로젝트에서는 prefix 만 갈아끼우면 된다.
| 프로젝트 유형 | prod prefix 예 | 격리 대상 |
| Django | DJANGO_*, DATABASE_URL | DJANGO_SETTINGS_MODULE, DB URL |
| Rails | RAILS_ENV, DATABASE_URL | env 자체를 test 로 강제 |
| Node.js | NODE_ENV, DB_HOST, REDIS_URL | NODE_ENV=test 강제 |
| Generic | APP_*, PROD_*, *_DB_URL, *_API_KEY | 모든 secret prefix |
규칙 — secret 으로 다루는 prefix 전부가 격리 대상. "이 변수는 안전하니 둬도 돼" 는 case 18 의 출발점이다.
11. 관련 skill
- pytest-fastapi-pattern — 격리된 통합 테스트
- sqlite-wal-safe-ops — 백업 정책 (마지막 방어선)
- bash-cli-best-practices — env 우선순위, set -a 함정
- postgres-migration-from-sqlite — alembic 마이그레이션 격리
12. 템플릿
templates/conftest-isolation.py.template— conftest 최상단 격리 코드templates/safe-test-runner.sh.template—env -uwrapper
복사 후 GATEWAY_* prefix 만 자기 프로젝트에 맞게 교체.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

