AI에게 열쇠를 쥐여주기 전에. 초보자가 먼저 지키는 '사고 안 내는' 안전 대책 5가지
Claude Code에 작업을 맡기기 전의 초입문. API 키 유출·위험 명령 폭주·운영 삭제를 막는 최소 설정을, 제 실패담과 함께 쉽게 풀어드립니다.
“이 프로젝트, 대충 좀 정리해둬.”
가벼운 마음으로 그렇게 치고, 커피를 내리러 갔습니다. 돌아와 보니, 터미널이 rm -rf 를 실행하기 한 걸음 직전에 멈춰 있었어요. 승인 버튼에, 제 손가락이 무의식중에 뻗어 가려 하고 있었습니다.
그때 “예”를 눌렀더라면, .env 도 설정 파일도 통째로 사라졌을 거예요.
Claude Code는 똑똑합니다. 하지만 똑똑함과 안전은 완전히 다른 문제예요. 오히려 똑똑하고 손이 빠른 만큼, 잘못된 방향으로도 전력으로 달립니다. 칼이 잘 들수록, 다루는 법을 익히기 전에 다치는 것과 같죠.
이 글은 초보자가 가장 먼저 짚어야 할 안전 대책만으로 좁힙니다. 어려운 이야기는 뒤로. 우선 지킬 5가지를, 제 삽질과 함께 쉽게 적을게요.
애초에, 뭐가 그렇게 위험한데?
보통의 텍스트 에디터는 글자를 표시할 뿐입니다. 그런데 Claude Code는 달라요. 당신의 컴퓨터 안에서, 이런 걸 ‘할 수 있어 버리는’ 도구입니다.
- 파일을 읽고, 쓰고, 지운다
- 터미널 명령어를 실행한다(
rm도 된다) - 인터넷에 접속해, 바깥 서비스에 글을 올린다
게다가 이거 전부, 당신이 “예”라고 승인하면 움직입니다. 문제는, 승인 버튼을 수십 번 누르다 보면 내용을 안 보게 되는 것. “예예, OK”의 리듬에 올라탄 순간, 위험한 조작이 쓱 통과해 버려요.
그래서 안전 대책은 ‘조심하기’가 아닙니다. 조심하지 않아도 사고 안 나는 구조를, 먼저 만들어 두는 것. 순서대로 보겠습니다.
이런 장면에서 가슴 철렁한다 (3가지)
초보자가 하기 쉬운 ‘위험한 장면’을 3가지만. 어느 것도 특별한 짓은 안 합니다.
장면 1: 에러를 고치고 싶어서, 로그를 전부 붙인다
“이 에러 고쳐줘”라고 부탁할 때, 터미널에 나온 글자를 전부 복붙하죠. 그런데 그 로그 안에 DATABASE_URL=postgresql://user:진짜비밀번호@... 같은 줄이 섞여 있을 때가 있습니다. AI에게 넘긴 셈인데, 대화 이력에도 로그에도, 날것의 비밀번호가 남아요.
장면 2: ‘전부 맡김’ 모드로 방치한다
승인이 귀찮아서, 확인 없이 뭐든 실행할 수 있는 모드로 해두고 자리를 뜬다. AI가 좋자고 git push --force 를 두드려, 팀 누군가의 작업이 날아간다. 악의는 제로. 그런데 결과는 최악입니다.
장면 3: 비슷한 이름의 DB를 헷갈린다
myapp_dev 와 myapp_prod. 한 글자 차이입니다. “DB의 오래된 데이터 지워둬”라고만 부탁하고, AI가 어느 쪽에 붙어 있는지 확인 안 했다면 — 사라지는 건 운영 손님 데이터일지도 모릅니다.
3가지에 공통된 건, 사람이 ‘깜빡’한 순간에 AI가 전력으로 실행하는 것. 그렇다면, 깜빡해도 괜찮게 해두면 됩니다. 그게 대책이에요.
내가 저지른 실패 3가지
잘난 척 적고 있지만, 저도 처음엔 사고투성이였습니다. 솔직히 3가지 자백할게요.
첫째. Qiita 자동 게시를 만들 때, 토큰을 프롬프트에 직접 붙였습니다. “QIITA_TOKEN=xxxx 를 써서 올려줘”라고. 돌아가서 만족했는데, 나중에 깨달았어요. 그 문자열, 대화 로그에도, 뒤에서 도는 작은 AI(서브에이전트)의 이력에도 남는다는 걸. 황급히 토큰을 다시 만들었습니다. 지금 생각하면 식은땀이 나요.
둘째. 조사를 위해 “.env 의 내용을 확인해줘”라고 부탁했습니다. AI는 순순히 전부 읽어 줬어요. API 키도 DB 비밀번호도, 죄다 화면과 로그에. 읽힌 순간, 그건 이미 “샌” 것과 같습니다. .env 는 사람인 저조차 평소엔 안 엽니다.
셋째. 취미 프로젝트의 느슨한 설정을, 일 저장소에 복사했습니다. 그 결과, 일 쪽에 있어야 할 “운영 쓰기 금지”가 취미용의 느슨함으로 덮어써져 있었어요. 사고 나기 전에 알아챘지만, 설정 돌려쓰기는 정말 위험합니다.
어느 것도 지식이 없었다기보다, 구조로 막아 두지 않은 게 원인이었습니다. 여기서부터가 본론이에요.
지킬 건 이 5가지. 순서대로 하면 된다
대책 1: API 키는 ‘코드 바깥’에 둔다
가장 중요합니다. API 키나 토큰은, 절대 코드나 프롬프트에 직접 쓰지 않는다. .env 라는 전용 파일에 격리하고, 그걸 Git 관리에서 빼낸다. 이것만으로 유출의 대부분을 막습니다.
먼저, 해서는 안 되는 예부터.
// NG: 소스 코드에 직접 쓰기(커밋하면 즉시 아웃)
const client = new Anthropic({ apiKey: "진짜 API 키를 직접 붙임" });
// NG: 프롬프트에 섞기
// "QIITA_TOKEN=진짜 토큰 을 써서 올려줘" ← 내가 저지른 그것
올바르게는, .env 라는 파일에 열쇠를 모읍니다.
# .env (이 파일은 Git에 올리지 않는다. 자기 컴퓨터에만 둔다)
ANTHROPIC_API_KEY=여기에 진짜 값
QIITA_TOKEN=여기에 진짜 값
DATABASE_URL=postgresql://...
그리고, 그 .env 를 “Git이 절대 줍지 않게” 선언합니다. 이게 생명줄.
# .gitignore 에 반드시 쓴다(열쇠를 깜빡 커밋하지 않는 보험)
.env
.env.*
!.env.example # 샘플만은 공유해도 OK
*.pem
*.key
*-service-account.json # 클라우드 서비스 계정 키도 잊지 말 것
팀에 “어떤 열쇠가 필요한지”만 전하고 싶을 땐, 값을 비운 샘플을 둡니다.
# .env.example (이건 Git에 올려도 OK. 내용은 텅 비움)
ANTHROPIC_API_KEY=
QIITA_TOKEN=
DATABASE_URL=
코드에서는, 파일의 값을 직접 쓰지 말고 “환경 변수”로 읽어 들입니다. 열쇠의 실물은 코드 어디에도 나타나지 않아요.
// OK: 환경 변수에서 읽는다. 값은 코드에 일절 쓰지 않는다
import { config } from "dotenv";
config();
const token = process.env.QIITA_TOKEN;
if (!token) {
// 열쇠가 없으면, 값이 아니라 "설정을 깜빡했어"라고만 전한다
throw new Error("QIITA_TOKEN 이 미설정입니다. .env 를 확인하세요.");
}
포인트는 하나. 열쇠의 실물이 닿는 건 .env 안뿐. 코드도 프롬프트도 로그도, 열쇠의 ‘이름’만 아는 상태로 한다. 이게 기본 중의 기본이에요.
대책 2: 위험한 명령어를 ‘자동 승인’시키지 않는다
다음으로, rm -rf(일괄 삭제)나 git push --force(팀 작업 덮어쓰기) 같은 되돌릴 수 없는 명령어. 이것들은 “실행 전에 반드시 사람에게 묻기”나 “애초에 실행 불가”로 설정해 둡니다.
Claude Code에는, 명령어마다 “OK/확인 필요/금지”를 정할 수 있는 장치가 있습니다. .claude/settings.json 에 이렇게 적어요.
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"defaultMode": "default",
"allow": [
"Read(**)",
"Glob(**)",
"Grep(**)"
],
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)",
"Bash(rm -rf*)",
"Bash(git push --force*)",
"Bash(git reset --hard*)",
"Bash(curl * | bash)"
],
"ask": [
"Write(**)",
"Edit(**)",
"Bash(git commit*)",
"Bash(git push*)"
]
}
}
읽는 법은 단순합니다. 3개의 상자에 나눠 담을 뿐이에요.
| 상자 | 뜻 | 담는 것 |
|---|---|---|
allow | 확인 없이 실행 OK | 읽기만 하는 안전한 조작 |
ask | 매번 제대로 묻는다 | 쓰기·커밋·푸시 |
deny | 일절 못 하게 한다 | 삭제·force push·운영 DB |
헷갈리면, 이렇게 외우세요. 읽기만은 allow, 쓰면 ask, 지우면 deny. 처음엔 빡빡하게 좁혀 두고, “아, 이건 안전하네” 싶은 조작만 나중에 ask 나 allow 로 승격한다. 반대 방향(처음엔 느슨, 나중에 조이기)은 사고 난 뒤가 되니, 반드시 조이는 방향에서 시작합니다.
대책 3: 읽고 쓰는 ‘범위’를 좁힌다
대책 2의 deny 를 잘 보세요. 맨 앞에 이런 줄이 있습니다.
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)"
]
이건 “.env 와 secrets 폴더는 읽는 것조차 금지”라는 설정입니다. 제가 저지른 “.env 를 읽힌” 사고는, 이 한 줄이 있었으면 안 일어났어요. “확인해줘”라고 부탁해도 문전박대당하니까요.
범위를 좁힌다는 건, 보여줘도 되는 곳과 보여주면 안 되는 곳을, 처음에 선 그어 두는 것. 열쇠가 든 폴더, 운영 설정, 손님 데이터. 이것들을 “애초에 못 만진다”로 해두면, 깜빡 부탁해도 안전합니다.
더해서, 절대 편집 안 했으면 하는 파일은, 프로젝트 규칙(CLAUDE.md)에도 한국어로 적어 두면 안심이에요.
## 편집 금지 파일 (CLAUDE.md 에 쓴다)
아래는 절대 편집하지 않는다. 필요하면 반드시 나(사람)에게 확인할 것.
- .env (열쇠·비밀번호를 포함)
- wrangler.toml (운영 공개 설정)
- .github/workflows/*.yml (자동 배포 설정)
설정 파일로 기계적으로 막고, 규칙 문장으로 AI에게도 의도를 전한다. 이중으로 해두면 빠짐이 줄어요.
대책 4: 비밀 정보를 로그에 내지 않는다
이건 설정이라기보다, 사소한 쓰기 버릇입니다. 에러 메시지를 쓸 때, 열쇠의 ‘값’을 함께 출력하지 않는 것.
// NG: 에러 로그에 API 키가 그대로 나와 버린다
throw new Error(`인증 실패: token=${process.env.TOKEN}`);
// OK: 값은 안 내고, 어디를 보면 되는지만 전한다
throw new Error("인증 실패: TOKEN 환경 변수를 확인하세요");
고작 한 줄 차이지만, 위는 로그를 누군가에게 보인 순간 열쇠가 샙니다.
그리고 하나 더. AI에게 로그를 넘길 때, 날것 그대로 붙이지 않는다. 열쇠나 비밀번호 줄은, 값을 가림 문자로 바꾼 뒤 넘깁니다.
# 이렇게 바꾼 뒤 넘긴다. AI는 "DB 접속 정보가 있다"는 구조만 알면 된다
DATABASE_URL=***마스크 완료***
QIITA_TOKEN=***마스크 완료***
“넘겨도 되는 것”과 “안 되는 것”을, 대충 표로. 이걸 CLAUDE.md 에 붙여 두면, 부탁할 때마다 판단 기준을 떠올릴 수 있어요.
| 넘겨도 OK | 넘기면 안 됨 |
|---|---|
| 에러 종류, 재현 절차, 파일명 | API 키, 비밀번호, 세션 cookie |
.env.example, 설정 항목의 ‘이름’ | 운영 DB의 URL, 손님 데이터 |
| 가림 문자로 바꾼 로그 | 실토큰, 서비스 계정 .json 파일 |
대책 5: 운영의 것은 ‘별도 취급’한다
마지막. 연습용(개발)과 운영을, 또렷이 나눕니다. myapp_dev 와 myapp_prod 의 헷갈림을 막으려면, 운영 쓰기에 한 수고를 끼우게 하는 게 효과적이에요.
// scripts/db-query.mjs
const env = process.env.NODE_ENV ?? "development";
// 운영에 쓰려 하면, 전용 플래그가 없는 한 멈춘다
if (env === "production" && process.argv.includes("--write")) {
console.error("운영 쓰기에는 --force-production 플래그가 필요합니다.");
process.exit(1);
}
‘깜빡 운영’을, 플래그라는 한 수고로 물리적으로 막는다. 이 귀찮음이 생명줄입니다. 운영 데이터는, 지우면 안 돌아와요.
시작한다면, 여기서부터 (절차)
5가지 전부를 오늘 할 필요는 없습니다. 순서대로 하면 30분이면 끝나요.
- 프로젝트에
.env를 만들고, 열쇠를 전부 거기로 옮긴다 .gitignore에.env를 추가한다 (대책 1).claude/settings.json을 만들고,deny에rm -rf와.env읽기를 넣는다 (대책 2·3)- 쓰기·커밋 계열을
ask에 넣는다 (대책 2) CLAUDE.md에 “넘겨도 됨/안 됨” 표와 편집 금지 파일을 붙인다 (대책 4)
처음 3단계만으로도, 첫머리의 rm -rf 사고와 .env 유출은 멈춥니다. 완벽을 노리지 말고, 우선 하나. 그걸로 충분히 전진이에요.
권한 설정을 더 세세히 다지고 싶어지면 Claude Code 권한 설정 가이드를, 실제로 있었던 사고의 생생한 이야기가 궁금하면 Claude Code 보안 실패 사례를 읽어 보세요. 설정의 정확한 사양은, 늘 공식 문서가 제일입니다.
실제로 해본 결과
첫머리의 rm -rf 철렁 이후로, 저는 “AI를 믿느냐 마느냐”로 고민하기를 그만뒀습니다. 대신 보는 건 어느 문지기에서 멈췄나예요.
deny 에 .env 읽기를 더했더니, “환경 변수를 확인해줘”라고 부탁해도 AI가 순순히 물러나게 됐습니다. 그 어색한 “전부 읽어 올리기”가, 두 번 다시 안 일어나요.
솔직히, 설정을 적은 날은 “너무 과한가” 싶었습니다. 그런데 반대였어요. 가드가 있어야 안심하고 힘을 뺄 수 있습니다. 승인 버튼을 가벼운 마음으로 누를 수 있는 건, 위험한 조작이 먼저 deny 로 멈춘다는 걸 알기 때문이에요. 똑똑한 AI를 다루려고 애쓰기보다, 넘어져도 안 다치는 바닥을 먼저 깐다. 이게 가장 편하고 빠르다는 게, 지금 제 결론입니다.
정리
초보자가 먼저 지킬 건, 이 5가지만으로 충분합니다.
| 지킬 것 | 방법 |
|---|---|
| API 키를 안 흘린다 | .env 에 격리 + .gitignore |
| 위험 명령을 폭주시키지 않는다 | deny 에 rm -rf / force push |
| 읽고 쓰는 범위를 좁힌다 | .env·secrets를 deny, 운영 파일은 편집 금지 |
| 비밀 정보를 로그에 안 낸다 | 값을 안 낸다·가림 문자로 넘긴다 |
| 운영을 별도 취급한다 | 플래그로 운영 쓰기를 막는다 |
보안이라고 하면 움찔하지만, 할 일은 “사고가 안 나는 구조를 먼저 만든다”뿐. 한번 넣어 두면, 이후엔 내버려 둬도 지켜 줍니다. 오늘 30분. 그걸로 미래의 큰 사고를 하나 지울 수 있어요.
만약 “우리 팀에서 어디까지 묶으면 될까?”로 망설여진다면, 교재나 지원도 준비해 뒀습니다. 우선 교재 목록을 들여다봐 주세요.
무료 PDF: Claude Code 치트시트
이메일을 입력하면 명령, 리뷰 습관, 안전한 워크플로를 정리한 PDF를 받을 수 있습니다.
개인정보를 안전하게 관리하며 스팸을 보내지 않습니다.
작성자 소개
Masa
Claude Code 실무 워크플로와 팀 도입을 검증하는 엔지니어입니다.
관련 글
Claude Code에게 파일 하나만 고치게 하는 지시문 작성법
'더 좋게 만들어줘'로 40줄이 바뀐 실패에서 배운, 수정 범위·검증·되돌리기를 묶은 Claude Code용 요청문 템플릿을 소개합니다.
Claude Code 권한 거절에서 복구하기: guardrail 을 약하게 만들지 않는 법
거절된 Claude Code 명령을 이유, 안전한 대안, 증거 명령, 재시도 조건으로 나누는 복구 workflow.
Claude Code Harness Smoke Test: 에이전트를 믿기 전 15분 검증 루프
Claude Code 작업 전에 범위, 금지 영역, 증거 명령, 공개 URL, 수익 CTA를 확인하는 실무 체크입니다.