SSD Nodes Learn 8GB RAM — 연 $66
가이드 Matt Connor작성자 Matt Connor · 업데이트됨 2026-08-01

AI 에이전트에서 API key를 안전하게 분리하는 법

AI 에이전트가 API key를 한 번의 tool call로 유출할 수 있는 이유와 실제 key 대신 범위가 제한된 단기 token을 credential gateway 뒤에서 사용하는 방법을 설명합니다.

AI 에이전트에서 비밀 정보를 분리한다는 의미

AI 에이전트는 명령을 실행하는 일반적인 Linux 프로세스입니다. 프로세스가 보유한 모든 환경 변수는 프로세스가 실행하는 코드에서 읽을 수 있습니다. 따라서 에이전트의 환경에 API key가 있으면 에이전트는 연결할 수 있는 모든 host로 해당 key를 보낼 수 있습니다. 에이전트에서 비밀 정보를 분리한다는 것은 key 자체 대신 에이전트에 핸들을 제공하는 것을 의미합니다. 핸들은 수명이 짧고 범위가 제한된 token이거나, 네트워크 경계에서 다른 구성 요소가 실제 값으로 교체하는 placeholder일 수 있습니다.

이는 model이 악의적으로 변한다는 이야기가 아닙니다. 실제 메커니즘은 더 단순합니다. 에이전트는 지침이 포함된 웹 페이지, README 또는 issue comment를 읽고 해당 지침을 따릅니다. language model에는 사용자가 작성한 텍스트와 에이전트가 가져온 텍스트를 구분할 방법이 없기 때문입니다. 이것이 prompt injection입니다. prompt injection이 발생하면 피해 범위는 정확히 한 가지에 의해 제한됩니다. 바로 프로세스가 읽을 수 있는 항목입니다. 아직 경계를 설정하지 않았다면 server에서 coding agent를 안전하게 실행하기에서 이 가이드의 기반이 되는 격리 단계를 설명합니다.

위협 모델을 쉽게 설명하면

에이전트가 실행되는 사용자로 다음을 실행합니다.

tr '\0' '\n' < /proc/self/environ | grep -iE 'key|token|secret|password'

에이전트가 출력하는 모든 줄은 낯선 사람의 서버로 보내는 HTTP 요청 하나만으로 전달될 수 있습니다. 이제 에이전트 주변의 디스크에 무엇이 있는지 확인합니다.

grep -rIl --exclude-dir=.git -e 'API_KEY' -e 'SECRET' ~/projects
find ~ -maxdepth 3 -name '.env' -o -name 'credentials' -o -name '*.pem'

셸을 사용할 수 있는 에이전트는 이 데이터를 외부로 반출하기 위해 정교한 익스플로잇을 사용할 필요가 없습니다. 일반적인 4가지 경로로 충분하며, 로그에는 모두 정상적인 작업처럼 표시됩니다.

  • 모든 호스트에 대한 outbound curl 또는 fetch 요청입니다. 값은 query string에 포함됩니다.
  • 에이전트가 쓸 수 있는 repository에 대한 git commitgit push 작업입니다.
  • 에이전트 사용자 권한으로 임의의 코드를 실행하는 package install script입니다.
  • 값이 포함된 hostname에 대한 DNS lookup입니다. HTTP egress가 차단되어도 이 요청은 외부로 전달됩니다.

검토만으로는 이 문제를 해결할 수 없습니다. 접근 가능한 위치에 중요한 데이터가 없도록 해야 합니다.

작업 트리에 있는 비밀은 컨텍스트 창에 있는 비밀입니다

에이전트는 파일을 읽습니다. 에이전트가 작업 중인 저장소에 있는 .env 파일은 읽히며, 읽힌 파일은 컨텍스트 창에 포함됩니다. 즉, 해당 파일은 트랜스크립트와 유지하는 모든 로그, 그리고 에이전트가 이후에 작성하는 내용에 포함됩니다.

이전에는 에이전트가 작업하는 트리에 키가 있었습니다.

cd ~/projects/billing
cat .env
# STRIPE_SECRET_KEY=sk_live_...
# DATABASE_URL=postgres://app:hunter2@db.internal:5432/billing

이후에는 파일을 에이전트가 접근할 수 없는 위치로 옮겼습니다.

sudo install -d -m 750 -o root -g agent-review /etc/agent-review
sudo install -m 640 -o root -g agent-review ~/projects/billing/.env /etc/agent-review/billing.env
rm ~/projects/billing/.env

이제 에이전트의 사용자는 파일을 열 수 없습니다. 작업 트리에 파일이 더 이상 없기 때문입니다. 에이전트 자체 구성의 거부 규칙은 첫 번째 방어 계층이 아니라 두 번째 계층입니다. Claude Code는 프로젝트의 .claude/settings.json에서 권한 규칙을 읽습니다.

{
  "permissions": {
    "deny": ["Read(./.env)", "Read(./secrets/**)", "Read(./**/*.pem)"]
  }
}

이 규칙은 에이전트가 탐색 중 실수로 파일을 여는 것을 방지합니다. 그러나 base64 .env를 실행하는 삽입된 명령은 차단하지 못합니다. 이는 파일 읽기가 아니라 shell command이기 때문입니다. 구성은 보호 장치로 사용하고, 파일 시스템 권한은 장벽으로 사용해야 합니다. 같은 구분은 container 내부에도 적용됩니다. Docker Compose의 env 파일과 비밀에서 이 문제를 한 단계 아래에서 다루는 방법을 설명합니다.

모든 에이전트에 고유한 비특권 사용자 할당

에이전트가 사용자 계정으로 실행되면 해당 계정의 SSH 키, 클라우드 자격 증명 및 셸 기록을 상속합니다. 별도의 사용자를 만들면 명령 1개로 이 모든 접근을 차단할 수 있습니다.

sudo adduser --disabled-password --gecos "" agent-review
sudo chmod 700 /home/agent-review
sudo -u agent-review cat ~/.ssh/id_ed25519

마지막 줄은 cat: /home/you/.ssh/id_ed25519: Permission denied와 함께 실패해야 합니다. 대신 키가 출력되면 홈 디렉터리를 그룹 또는 모든 사용자가 읽을 수 있는 상태입니다. chmod 700 ~로 이를 수정할 수 있습니다. 에이전트 사용자를 sudo에 추가하지 마십시오. 또한 실제로 필요한 명령 1개보다 광범위한 NOPASSWD 규칙을 부여하지 마십시오. VPS에서 최소 권한 사용자 사용에서 그룹 및 sudoers 설정을 자세히 설명합니다.

클라우드 VPS에서는 경계를 하나 더 설정해야 합니다. 인스턴스 메타데이터 서비스는 고정된 링크 로컬 주소에서 요청에 응답하며, 요청하는 모든 대상에 역할 자격 증명을 제공하는 경우가 많습니다.

sudo iptables -A OUTPUT -m owner --uid-owner agent-review -d 169.254.169.254 -j REJECT

에이전트 측에서 이를 확인합니다. sudo -u agent-review curl -s --max-time 3 http://169.254.169.254/은 아무것도 출력하지 않고 0이 아닌 상태 코드로 종료해야 합니다. 패킷이 시스템을 벗어나기 전에 거부되기 때문입니다.

경계에서 자격 증명 주입

이 문제를 실제로 해결하는 패턴은 자격 증명 주입입니다. 에이전트는 실제 키를 보유하지 않습니다. 에이전트는 로컬 게이트웨이를 통해 요청을 전송하고, 게이트웨이는 요청이 외부로 나가는 과정에서 자리 표시자를 실제 비밀 값으로 바꿉니다. 비밀 값은 게이트웨이의 저장소에 있으며, 별도의 프로세스에서 다른 사용자가 소유합니다.

OneCLI는 이 방식을 구현한 오픈 소스 프로젝트 중 하나입니다. Apache-2.0 라이선스가 적용되며, 에이전트 옆에서 컨테이너로 실행됩니다. 2026년 7월 기준으로 프로젝트에서는 다음과 같이 구성하는 방법을 설명합니다.

git clone https://github.com/onecli/onecli.git
cd onecli
docker compose -f docker/docker-compose.yml up -d --wait

대시보드는 10254 포트에서 수신 대기하고 게이트웨이는 10255 포트에서 수신 대기합니다. 실제 자격 증명을 한 번 저장한 다음, 각 에이전트에는 키 대신 자리 표시자 값과 해당 에이전트에만 적용되는 범위의 액세스 토큰을 제공합니다. 에이전트는 이 토큰을 Proxy-Authorization 헤더로 전송합니다. 게이트웨이는 호스트와 경로를 기준으로 외부로 나가는 요청을 식별하고, 일치하는 자격 증명을 복호화한 후 자리 표시자를 실제 값으로 바꿉니다. 에이전트의 환경에는 탈취할 가치가 있는 값이 남지 않습니다.

여기서 중요한 점은 암호화가 아닙니다. “이 에이전트가 무엇을 언제 사용했는가”라는 질문을 로그 쿼리로 확인할 수 있다는 점입니다. 키 사본이 6개 환경 중 어디에 있었는지 추측하는 대신 하나의 감사 로그를 확인하면 됩니다.

비밀을 환경이 아니라 프로세스에 전달합니다

agent를 systemd에서 실행하면 환경 변수가 전혀 필요하지 않습니다. LoadCredential=는 해당 서비스만 읽을 수 있는 private directory에 secret을 저장합니다. 이 directory는 unit file에서 %d으로 노출되고 process 내부에서는 $CREDENTIALS_DIRECTORY으로 노출됩니다. 값은 /proc/<pid>/environ에 절대 나타나지 않으므로 ps eww으로 값을 표시할 수 없습니다. 또한 service가 중지되면 directory가 사라집니다.

먼저 credential을 machine에 암호화합니다. 다음 command는 systemd documentation에 설명된 방법이며 systemd 250 이상에서 작동합니다. Ubuntu 24.04 및 Debian 13이 여기에 해당합니다.

echo -n 'sk-example-value' > /tmp/plain.txt
sudo systemd-creds encrypt --name=api_key /tmp/plain.txt /etc/credstore/api_key.cred
shred -u /tmp/plain.txt
sudo systemd-run -P --wait -p LoadCredentialEncrypted=api_key:/etc/credstore/api_key.cred \
  systemd-creds cat api_key

마지막 command는 sk-example-value을 출력합니다. 그러면 이 host에서 암호화된 file을 복호화할 수 있음을 확인할 수 있습니다. 그런 다음 unit에서 이를 참조합니다.

[Service]
User=agent-review
LoadCredentialEncrypted=api_key:/etc/credstore/api_key.cred
Environment=AGENT_KEY_FILE=%d/api_key
ExecStart=/usr/local/bin/agent-worker

값이 필요할 때 agent code는 $AGENT_KEY_FILE에서 file을 엽니다. file read는 순간적으로 수행됩니다. 환경 변수는 process의 수명 동안 유지되며, 해당 process가 생성하는 모든 child에도 전달됩니다.

만료 기간이 짧은 token을 장기 유효 key보다 우선 사용

만료되지 않는 key는 로그나 transcript에 기록된 후 몇 달이 지나도 다시 나타나면 여전히 유효합니다. 서비스에서 session token을 제공하면 session token을 사용하고, 작업에 허용되는 가장 짧은 lifetime으로 설정합니다.

aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/agent-readonly \
  --role-session-name agent-review \
  --duration-seconds 900

AWS STS (security token service)가 허용하는 최소 기간은 15분이며, 일반적으로 agent 작업 하나를 수행하기에 충분합니다. GitHub에서는 agent 사용자에게 별도의 gh login을 제공하고, 해당 사용자가 작업하는 단일 repository로 범위를 제한한 fine grained token을 사용합니다. 그러면 해당 session 안의 gh auth token은 다른 어떤 항목에도 접근할 수 없는 값을 반환합니다. 먼저 resource로 범위를 제한한 다음 time으로 제한합니다.

확인한 후 계속 확인합니다

에이전트 설정을 변경한 후에는 3가지 검사를 실행하는 것이 좋습니다. 이 검사는 직접 실행하지 말고 에이전트의 사용자로 실행합니다.

sudo -u agent-review env | grep -iE 'key|token|secret'
sudo -u agent-review ls -la /home/you/ 2>&1 | head -3
sudo -u agent-review curl -s -o /dev/null -w '%{http_code}\n' --max-time 5 https://api.github.com/user

첫 번째 검사는 아무것도 출력하지 않아야 합니다. 두 번째 검사는 ls: cannot open directory '/home/you/': Permission denied를 출력해야 합니다. 세 번째 검사는 에이전트의 네트워크 경로가 어떤 ID를 제시하는지 알려 줍니다. 이것이 gateway 패턴이 답하려는 질문입니다. 401이면 에이전트가 자체 GitHub credential을 전달하지 않는다는 뜻입니다. 200이면 credential을 전달하고 있다는 뜻이므로 어떤 token인지 확인해야 합니다. 에이전트를 unattended로 실행한다면 VPS에서 AI 에이전트 비용 제어에서 이러한 access 제한과 함께 적용할 budget 제한을 설명합니다.

FAQ

모델이 키를 유출하지 않을 것이라고 믿어도 됩니까?

아니요. 이 위협 모델에서 공격자는 모델이 아니기 때문입니다. agent는 웹 페이지, repository, issue tracker에서 텍스트를 읽으며, 해당 텍스트에는 지시가 포함될 수 있습니다. 모델은 자신에게 전달된 지시와 가져온 텍스트를 안정적으로 구분할 수 없습니다. 모델이 올바른 결정을 내리는 데 의존하는 제어는 주입된 지시가 설득력 있게 작성되는 순간 실패합니다. 따라서 제어는 운영 체제나 네트워크에 배치해야 합니다.

agent secret에 환경 변수를 사용하는 것이 정말 그렇게 나쁩니까?

특정한 한 가지 이유로 문제가 됩니다. 환경 변수는 상속되기 때문입니다. agent가 생성하는 모든 하위 process가 환경 변수의 사본을 받습니다. 여기에는 build script, test runner, package install hook도 포함됩니다. 같은 사용자는 /proc/<pid>/environ를 통해서도 해당 변수를 읽을 수 있습니다. 따라서 agent가 실행하는 모든 항목은 agent가 변수를 전달하지 않아도 변수를 읽을 수 있습니다. 사용 시점에 LoadCredential= 또는 gateway를 통해 파일을 읽으면 노출을 해당 시점으로 제한할 수 있습니다.

secret을 vault에 저장하면 이것만으로 해결됩니까?

부분적으로만 해결됩니다. vault는 저장 문제를 해결합니다. 하지만 vault에서 secret을 가져와 환경 변수로 agent에 전달하는 마지막 단계는 해결하지 못합니다. 그러면 문제가 다시 원래 상태로 돌아갑니다. 중요한 것은 치환을 수행하는 주체입니다. agent가 secret을 가져오면 agent가 secret을 보유합니다. gateway 또는 init system이 agent process 외부에서 치환을 수행하면 agent는 secret을 보유하지 않습니다.

agent가 이미 무언가를 유출했는지 어떻게 알 수 있습니까?

대부분의 경우 사후에는 알 수 없습니다. 이것이 gateway를 사용해야 하는 이유입니다. gateway가 없으면 근거가 shell history, agent transcript, 그리고 아마 보관하지 않을 outbound connection log에 흩어집니다. credential gateway를 사용하면 credential을 사용할 때마다 agent identity와 timestamp가 포함된 한 줄의 기록이 남습니다. 유출이 의심되면 먼저 key를 rotate하고 나중에 조사하십시오. Rotation은 저렴하지만 확실한 판단은 그렇지 않습니다.

오늘 최소한으로 무엇을 해야 합니까?

모든 .env file을 agent가 작업하는 directory 밖으로 옮기고, agent마다 권한이 없는 사용자 하나를 생성하십시오. 이 두 가지 변경에는 약 10분이 걸립니다. 또한 가장 일반적인 경로를 차단합니다. 가장 일반적인 경로는 agent가 code 옆에 둘 이유가 없었던 credential file을 읽는 것입니다. gateway와 short-lived token은 다음 단계이지 첫 단계가 아닙니다. 다음 출발점은 VPS에서 autonomous agent를 안전하게 실행하기를 포함한 모든 agent runtime에 적용됩니다.