연결이 거의 완료되었습니다. 에이전트가 설정 파일을 만들고 서비스 이름도 적었지만, API key라는 필드 하나에서 멈췄습니다. 서비스가 긴 문자열을 발급했는데, 그 문자열만 봐서는 어디에 넣어야 할지 알 수 없습니다. 채팅에? 코드 옆의 파일에? 확실히 작동하게 설정 파일에 바로 넣을까요?
하나의 모델을 통해 살펴봅시다. 값을 어디에 설정하는지, 어떤 프로세스가 값을 받는지, 비밀을 표시하지 않고 모든 설정이 올바르게 되었는지 어떻게 확인하는지 알아봅시다. 로컬 실행, MCP 연결, 게시된 애플리케이션 모두에 같은 모델이 적용됩니다. 달라지는 것은 누구의 환경을 설정하느냐뿐입니다.
API 키가 제공하는 것
키는 요청을 계정이나 프로젝트 및 부여받은 권한과 연결합니다. 이 키를 통해 서비스는 누가 요청했는지, 그 요청자에게 무엇이 허용되는지, 이미 얼마나 요청했는지 파악합니다. 키가 정확히 무엇을 뜻하는지는 서비스마다 다릅니다. 전체 계정에 대한 액세스일 수도 있고, 한 프로젝트에만 읽기 전용 액세스일 수도 있습니다.

안내 문서에서는 "키"와 함께 토큰이라는 말도 자주 보입니다. 일상적인 글에서는 흔히 같은 뜻으로 쓰지만, 세부 메커니즘에는 차이가 있습니다. 발급 방식, 유효 기간, 권한 범위가 그렇습니다. 둘을 완전한 동의어로 보지 않는 것이 좋습니다. 서비스가 정확히 무엇을 제공하는지는 해당 문서에 기재되어 있습니다.
서비스가 굳이 여러분을 구분해야 하는 이유는 일상적인 예로 보면 더 쉽게 이해됩니다. 여러분이 계정을 만들고, 월간 요청 한도가 있는 요금제를 고른 뒤 키를 받았습니다. 그러면 서비스는 이 키로 여러분의 요청 수를 세고, 한도와 비교하고, 유료 요금제라면 요금을 청구합니다. 타인에게 유출된 키는 한도와 청구서가 다른 사람에게 돌아간다는 의미입니다.
키가 스스로 결정을 내리지는 않습니다. 키는 서비스가 소유자의 설정을 찾을 수 있게 해 줄 뿐이고, 특정 요청을 허용할지, 한도에서 차감할지는 서비스가 결정합니다.
서비스가 일부러 공개용으로 표시한 값도 있습니다. 이런 값은 원래 페이지 코드에 넣도록 만들어진 것입니다. 변수 이름만으로는 공개 값과 비밀 값을 구분할 수 없습니다. 용도는 문서에 적혀 있으니 문서를 확인해야 합니다. 아래 예제에는 모두 fc-EXAMPLE-0000000000처럼 누가 봐도 알 수 있는 자리 표시 값이 들어 있습니다. 실제 키는 이렇게 생기지 않았습니다.
애플리케이션이 비밀을 어디에 저장할 수 있을까요
이런 상황을 상상해 봅시다. 여러분이 키를 페이지 코드에 바로 넣고, 프로젝트를 실행해 API의 응답을 받았습니다. 모두 잘 작동합니다. 애플리케이션을 게시한 뒤에도 페이지가 열리고 요청도 통과합니다. 문제가 해결된 것처럼 보입니다.
하지만 코드와 함께 키도 모든 방문자의 브라우저로 전달됩니다. 개발자 도구를 열어 JavaScript나 요청에서 키를 찾아 복사하기만 하면 됩니다. 그러면 그 사람은 여러분의 이름으로 자기 요청을 보낼 수 있습니다. 그 요청들이 여러분의 한도를 소진하고, 결국 서비스는 여러분 자신의 요청까지 거부하기 시작합니다.
문제는 특정 빌드 도구에 있지 않습니다. 페이지가 브라우저에 넘긴 것은 HTML, 스타일, JavaScript, 요청 주소와 헤더까지 모두 그 브라우저의 주인이 볼 수 있습니다. 빌드할 때 로컬 .env에서 키를 가져왔더라도, JavaScript에 값이 들어간 순간 나머지 코드와 함께 방문자에게 넘어갑니다.
백엔드는 서버에서 실행되는 코드입니다. 방문자는 백엔드의 응답은 받지만, 소스 코드나 서버 프로세스의 환경 변수는 받지 않습니다. 그래서 백엔드는 키를 자기 쪽에 보관하고 애플리케이션을 대신해 외부 서비스를 호출할 수 있습니다.
요청 흐름이 바뀝니다. 브라우저는 비밀 키 없이 백엔드에 일반 요청을 보냅니다. 백엔드는 자신의 환경에서 키를 읽고, 이를 별도의 API 요청에 추가한 후, 브라우저에 결과만 반환합니다.
이제 방문자는 백엔드 주소와 응답 데이터는 보지만, 외부 서비스의 키는 보지 못합니다. 비밀은 그 비밀을 사용하는 프로세스에만 남습니다.
비밀의 주인이 될 수 있는 곳은 두 가지입니다.
- 로컬 프로세스 - 통합이 연결된 Claude Code, 스크립트, 개발 서버입니다. 여러분의 컴퓨터에서 실행되고 키도 그곳에서 받습니다.
- 게시된 애플리케이션의 백엔드 - 외부 API를 직접 호출하는 서버 코드입니다. 이 환경은 호스팅 패널에서 설정합니다.
둘은 서로 별개의 설정입니다. 서비스와 변수 이름이 같더라도, 여러분 컴퓨터의 환경에 있는 키가 호스팅에 저절로 생기지는 않습니다.
키를 백엔드로 옮기면 브라우저에서는 보이지 않지만, 공개된 백엔드 자체는 여전히 다른 사람의 요청으로부터 보호해야 합니다.
Claude Code가 값을 설정해 달라고 하기 전에, 먼저 두 가지를 말해 달라고 요청하세요. 어떤 프로세스가 키를 사용할지, 그리고 값이 어떤 방식으로 그 프로세스까지 전달될지입니다. 답이 없다면 아직 설정할 단계가 아닙니다.
환경 변수는 어떻게 작동하나요
환경 변수는 프로그램이 시작할 때 받는 명명된 값입니다. 변수에는 FIRECRAWL_API_KEY 같은 이름과, 앞에서 본 그 긴 문자열인 값이 있습니다. 설정 파일과 코드에는 이름을 쓰고, 값은 따로 두며 코드 옆에 보관하지 않습니다.
프로그램은 이 값을 어디서 받을까요? 터미널에서 실행한 프로그램은 그 터미널의 환경을 물려받습니다. 한 창에서 변수를 설정하고 그 창에서 Claude Code를 실행했다면 Claude Code는 변수를 봅니다. 두 번째 창을 열어 거기서 변수를 설정했다면, 이미 실행 중인 프로세스에는 아무 영향이 없습니다. 그 프로세스는 시작할 때 이미 환경을 받았기 때문입니다.
"키는 설정했는데 작동하지 않아요"라는 가장 흔한 불만도 여기서 나옵니다. 값을 다른 창에서 설정했거나, 프로세스를 시작한 뒤에 설정한 경우입니다. 변수가 있는 터미널에서 프로세스를 다시 시작하면 해결됩니다.
누가 봐도 가짜인 값을 쓴 학습용 예제입니다.
# bash: 현재 터미널에만 적용되는 변수
export FIRECRAWL_API_KEY=fc-EXAMPLE-0000000000# PowerShell: 동일
$env:FIRECRAWL_API_KEY = "fc-EXAMPLE-0000000000"학습용 예제에서 자리 표시 값이라면 이렇게 써도 됩니다. 하지만 실제 키를 이런 식으로 입력하지는 마세요. 문자열 전체가 명령 기록에 남고, 터미널을 닫은 뒤에도 사라지지 않습니다. 실제 값에는 에코 없는 입력을 씁니다. 문자열을 붙여 넣어도 화면에는 아무것도 나타나지 않고, 기록에는 명령만 남습니다.
# bash: 값을 화면에 표시하지 않고 입력
read -s -r FIRECRAWL_API_KEY
export FIRECRAWL_API_KEY# PowerShell: 숨김 입력 후 값을 꺼내 환경 변수에 저장
$secure = Read-Host "FIRECRAWL_API_KEY" -AsSecureString
$env:FIRECRAWL_API_KEY = [System.Net.NetworkCredential]::new('', $secure).Password설정은 값이 화면, 로그, 스크린샷에 드러나지 않는 방식으로 확인해야 합니다. "설정됐는가, 아닌가"라는 질문에 답하는 데는 두 줄이면 충분합니다.
# bash
[ -n "$FIRECRAWL_API_KEY" ] && echo "설정됨" || echo "설정 안 됨"# PowerShell
if ($env:FIRECRAWL_API_KEY) { "설정됨" } else { "설정 안 됨" }길이나 앞 몇 글자도 출력하지 마세요. 확인에는 아무 도움이 되지 않고, 키의 일부도 이미 비밀의 일부입니다.
변수를 영구적으로 설정하는 방법은 많고, 시스템마다 다릅니다. 지금 그것까지 알아볼 필요는 없습니다. 로컬 작업에는 터미널이면 충분하고, 호스팅에서는 호스팅 자체의 패널에서 변수를 설정합니다.
환경 변수는 코드에 비밀을 쓰지 않기 위한 방법이지, 보안 수단이 아닙니다. 값은 암호화되지 않으며, 같은 환경에서 실행되는 프로그램은 얼마든지 읽을 수 있습니다. 그래서 누구의 프로세스를 설정하는지가 중요합니다.
.env와 .env.example은 왜 필요한가요
새 터미널을 열 때마다 변수를 직접 설정하는 일은 금방 귀찮아집니다. 그래서 값을 텍스트 파일 .env에 모아 둡니다. 한 줄에 이름과 값 한 쌍이 들어갑니다.
여기서 중요한 점을 가장 자주 놓칩니다. 파일 자체는 아무것도 설정하지 않습니다. 누군가 파일을 읽어서 값을 프로세스 환경에 넣어 줘야 합니다. 애플리케이션이나 라이브러리, 또는 프로그램을 실행하는 런처가 그 일을 합니다. .env를 만들어 두기만 하면 어떤 프로그램이든 알아서 읽을 거라고 생각하면 안 됩니다. 여러분의 경우에 무엇이 이 파일을 불러오는지 추측하지 말고 직접 확인하세요.
보통 그 옆에는 파일이 두 개 더 있습니다.
# .env - 여러분의 컴퓨터에만 남음
FIRECRAWL_API_KEY=fc-EXAMPLE-0000000000# .env.example - 프로젝트를 열 사람을 위한 템플릿
# 값은 비워 두고 이름만 적음
FIRECRAWL_API_KEY=# .gitignore
.env| 파일 | 용도 | 내용 |
|---|---|---|
.env | 실행을 위한 로컬 값 | 실제 이름과 값. 여러분의 컴퓨터에만 있음 |
.env.example | 프로젝트를 여는 사람을 위한 힌트 | 같은 이름이지만 빈 값. 때로는 짧은 설명 |
.gitignore | 로컬 파일이 저장소에 들어가지 않도록 하는 규칙 | 파일 이름이 있는 줄 |
이렇게 나누는 이유는 간단합니다. .env.example은 "이 프로젝트에 어떤 변수가 필요한가"라는 질문에 답하고, 값이 없으니 저장소에 두어도 괜찮습니다. 실제 키는 잠깐이라도 이 파일에 옮겨 적지 않습니다.
아직 Git이 낯설다면 여기서는 .gitignore의 역할만 기억하면 됩니다. 저장소에 추가하지 않을 파일을 나열하는 파일입니다. Git과 .gitignore 사용법은 레벨 13에서 자세히 다룹니다.
이제 .gitignore에서 자주 걸려 넘어지는 함정을 볼 차례입니다. 규칙은 Git이 아직 추적하지 않는 파일에만 적용됩니다. .env를 한 번이라도 커밋했다면 .gitignore에 줄을 추가해도 추적이 풀리지 않습니다. 파일은 계속 버전 관리 대상으로 남고, 변경 사항도 계속 커밋에 들어갑니다. Git 문서에도 분명히 적혀 있습니다. Git이 이미 추적하는 파일은 무시 규칙의 영향을 받지 않습니다. gitignore 문서.
다이어그램에서 핵심 조건은 가운데에 있습니다. .gitignore의 같은 줄도 .env가 추적되지 않을 때만 도움이 됩니다. 이미 추적 중인 파일이라면 별도의 단계가 필요합니다.
그래서 파일에 실제 비밀을 적기 전에 Git이 그 파일을 모르는 상태인지 확인해야 합니다. git status를 실행하면 파일이 변경 사항에 잡히는지 알 수 있습니다. 파일이 이미 추적 중이라면 git rm --cached .env 명령으로 추적을 해제하고 그 변경을 커밋합니다. 파일은 디스크에 그대로 남지만 버전 관리에서는 빠집니다.
다만 이것은 앞으로의 커밋에만 영향을 줍니다. 이미 기록에 들어간 값은 그대로 남아 있고, 이 문제는 다른 방법으로 처리해야 합니다. 아래에서 다룹니다.
MCP 연결에 키를 전달하는 방법
MCP는 외부 도구를 Claude Code에 연결하는 방법입니다. 연결 설정은 .mcp.json 파일에 적는데, 이 파일은 보통 프로젝트와 함께 저장소에 들어가므로 팀 전체가 봅니다. 그러니 이 파일에는 비밀이 있으면 안 됩니다.
HTTP로 Firecrawl 서비스를 연결하는 최소 설정은 다음과 같습니다.
{
"mcpServers": {
"firecrawl": {
"type": "http",
"url": "https://mcp.firecrawl.dev/v2/mcp",
"headers": {
"Authorization": "Bearer ${FIRECRAWL_API_KEY}"
}
}
}
}파일에는 키가 없습니다. 헤더에 있는 ${FIRECRAWL_API_KEY}는 환경 변수를 이름으로 가리키는 참조입니다. Claude Code는 이 자리에 자신이 실행된 프로세스의 환경에서 값을 가져와 넣습니다. 저장소에는 이름만 올라가고, 값은 사람마다 각자의 것을 씁니다. Claude Code는 url, headers, env, command, args 필드에서 이런 치환을 할 수 있습니다. Claude Code의 MCP 문서.
다이어그램을 보면 요청 헤더에서 서로 다른 곳에서 온 두 가지가 만납니다. 헤더의 틀은 공유 설정 파일에서 오고, 값은 프로세스 환경에서 채워집니다. 어느 단계에서도 키가 저장소로 올라가는 파일에 기록되지 않습니다.
주소와 키 전달 방식은 서비스 자체의 문서를 보고 정합니다. Firecrawl의 경우 Authorization 헤더를 쓰며, 문서에서 MCP 주소에 키를 넣지 말라고 따로 당부합니다. 이유는 간단합니다. 주소는 평범한 텍스트라서 설정 파일, 로그, 명령 기록에 남고, 키도 함께 남게 됩니다.
브라우저 로그인을 지원하는 서비스도 있습니다. 클라이언트가 직접 로그인 페이지를 열어 주므로 API key를 손으로 넘길 필요가 아예 없습니다. Firecrawl에는 이를 위한 별도 주소가 있습니다. Firecrawl MCP 문서.
도구가 응답하지 않으면 다음 순서로 확인하세요.
- 설정 파일의 변수 이름과 여러분이 설정한 이름이 마지막 글자까지 같은가
- Claude Code를 실행한 바로 그 프로세스에 값이 설정되어 있는가
- 변수를 설정한 뒤 프로세스를 다시 시작했는가
변수가 없어도 Claude Code는 조용히 멈추지 않습니다. 설정 파일을 불러오되 ${FIRECRAWL_API_KEY} 텍스트는 치환하지 않은 채로 두고, 빠진 변수의 이름을 담은 경고를 출력합니다. 값 자체는 어디에도 출력되지 않으므로 이 진단은 안전합니다. 문제를 찾겠다고 환경 전체를 출력할 필요는 전혀 없습니다.
이 예제는 이미 만들어진 연결을 이해하기 위한 것입니다. 서버를 어떻게 고르고 추가하는지, 서버가 어떤 권한을 얻는지는 별도의 주제입니다. 여기서 중요한 것은 이 구조에서 비밀이 어디에 있는가 하나뿐입니다.
키가 이미 유출된 경우
흔히 있는 일입니다. 키가 메시지나 커밋, 스크린샷에 들어가 버렸습니다. 당황할 필요는 없지만 미뤄서도 안 됩니다. 순서는 다음과 같습니다.
- 서비스 대시보드에서 키를 폐기하거나 교체합니다. 이전 문자열로 더 이상 아무것도 열 수 없게 만드는 유일한 단계입니다.
- 새 값을 필요한 곳에 설정합니다. 로컬, 호스팅, 연결 설정입니다.
- 노출된 곳을 정리합니다. 메시지를 지우고, 파일을 고치고, 접근을 막습니다.
- 대시보드에서 키가 어떻게 쓰였는지, 서비스가 비용을 어떻게 보여 주는지 확인합니다.
순서는 꼭 이대로이며, 첫 단계의 위치는 바꿀 수 없습니다. 메시지를 지우거나 이전 커밋 위에 새 커밋을 올려도 기존 비밀이 안전해지지는 않습니다. 메시지를 읽었을 수 있고, 저장소를 복제했을 수 있고, 기록을 저장했을 수 있습니다. 키가 작동하는 한, 키를 가진 모든 사람에게 작동합니다.
저장소는 따로 짚고 넘어가겠습니다. 비밀이 커밋에 들어갔다면 GitHub에 이런 데이터를 제거하는 가이드가 있습니다. 저장소에서 민감한 데이터 제거. 다만 기록을 완전히 정리하면 기존 커밋이 다시 작성되고, 그 저장소로 작업하는 모든 사람이 영향을 받는다는 점을 기억하세요. 이런 작업은 저장소 소유자와 합의한 뒤에 하는 것이지, 학습용 글 하나만 보고 혼자 실행하는 것이 아닙니다. 반면 키 교체는 바로 하고, 누구와도 합의할 필요가 없습니다.
Claude Code에 맡길 일
이 글에 나온 절차는 한 가지만 빼고 거의 모두 에이전트가 처리할 수 있습니다. 비밀 자체는 여러분이 직접 입력합니다. 에이전트에게는 다음 일을 맡기면 됩니다.
- 값이 아니라 변수 이름을 넣어 설정 파일을 준비하기
- 무엇이 변수를 프로세스에 불러오는지, 어느 프로세스에 변수가 있어야 하는지 알려 주기
- 클라이언트 빌드와 Git이 추적하는 파일에 비밀이 없는지 확인하기
- 값을 출력하지 않고 액세스를 확인한 뒤 결과를 보고하기
여러분에게 남는 일은 실제 키를 그 용도로 마련된 방법으로 입력하는 것입니다. 터미널의 숨김 입력, 로컬 파일, 호스팅 패널이 그 방법입니다. 비밀은 채팅, 스크린샷, 에이전트 보고서에 절대 들어가지 않습니다. "확인하려고 딱 1초만"이라도 마찬가지입니다.
요청은 대략 이렇게 쓸 수 있습니다.
API로 서비스를 연결해 줘. 키는 내가 직접 설정할게.
문서에서 주소, 메서드, 키 전달 방식을 확인해.
설정 파일에는 값이 아니라 환경 변수 이름을 넣어.
어떤 프로세스가 변수를 받아야 하고, 무엇이 그 변수를 불러오는지 알려 줘.
클라이언트 빌드와 Git이 추적하는 파일에 키가 없는지 확인해.
액세스 확인은 값이나 값의 일부를 출력하지 말고 해 줘.
결과를 알려 줘: 설정됨, 설정 안 됨, 액세스 거부됨 중 하나로.마지막으로 보고서를 읽는 법입니다. "변수가 설정됨"과 "액세스가 작동함"은 서로 다른 말이고, 하나를 다른 하나로 대신하면 안 됩니다. 값이 없는 것과 서비스가 거부한 것도 다릅니다. 앞의 경우에는 요청에 서명할 값이 없고, 뒤의 경우에는 서비스가 응답은 했지만 액세스를 허용하지 않았습니다. 에이전트가 그냥 "완료"라고만 하면 정확히 무엇을 확인했는지 물어보세요.
비밀은 채팅으로 보내지 않고, 스크린샷에 보이게 하지 않고, 저장소로 올라가는 파일에 넣지 않습니다. 이 글의 나머지 내용은 모두 이 규칙을 지키면서도 작업에 방해가 되지 않게 하는 방법입니다.
GO TO FULL VERSION