SSD Nodes Learn Hosting plans →
가이드 Matt Connor작성자 Matt Connor · 업데이트됨 2026-10-22

AI 에이전트 API 키 유출 방지 및 보안 설정 방법

AI 에이전트가 환경 변수나 API 키를 외부로 유출하는 프롬프트 인젝션 위험을 방지합니다. 실제 키 대신 수명이 짧은 토큰과 자격 증명 게이트웨이를 사용하여 에이전트의 접근 권한을 최소화하고 보안을 강화하는 구체적인 전략을 확인하십시오.

AI 에이전트에서 비밀 정보를 분리해야 하는 이유

AI 에이전트는 명령어를 실행하는 일반적인 Linux 프로세스입니다. 해당 프로세스가 보유한 모든 환경 변수는 그 프로세스가 실행하는 코드에서 읽을 수 있으므로, 에이전트 환경에 포함된 API key는 에이전트가 접근 가능한 모든 호스트로 전송될 수 있는 키가 됩니다. 에이전트에서 비밀 정보를 분리한다는 것은 키 대신 핸들을 제공하는 것을 의미합니다. 즉, 수명이 짧고 범위가 제한된 토큰이나, 네트워크 경계에서 다른 무언가가 실제 값으로 교체해 줄 플레이스홀더를 사용하는 것입니다.

이는 모델이 적대적으로 변한다는 이야기가 아닙니다. 메커니즘은 훨씬 단순합니다. 에이전트는 지침이 포함된 웹 페이지, README 또는 이슈 댓글을 읽고 이를 따릅니다. 언어 모델 입장에서는 사용자가 작성한 텍스트와 에이전트가 가져온 텍스트 사이에 차이가 없기 때문입니다. 이것이 바로 프롬프트 인젝션입니다. 일단 이런 일이 발생하면 피해 규모는 오직 한 가지, 즉 해당 프로세스가 읽을 수 있는 범위에 의해 결정됩니다. 아직 경계를 설정하지 않았다면, 서버에서 코딩 에이전트를 안전하게 실행하는 방법에서 이 가이드의 기반이 되는 격리 단계들을 확인할 수 있습니다.

위협 모델의 평이한 설명

에이전트가 실행되는 사용자와 동일한 권한으로 이 명령을 실행하십시오.

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'

셸 권한을 가진 에이전트는 데이터를 외부로 유출하기 위해 정교한 익스플로잇이 필요하지 않습니다. 다음 네 가지 일반적인 경로를 사용하면 되며, 이 모든 작업은 로그상에서 정상적인 업무처럼 보입니다.

  • 임의의 호스트로 보내는 curl 또는 fetch 요청(값은 쿼리 문자열에 포함됨).
  • 에이전트가 쓰기 권한을 가진 저장소로 수행하는 git commit 및 git push 작업.
  • 에이전트 사용자의 권한으로 임의 코드를 실행하는 패키지 설치 스크립트.
  • HTTP 외부 통신이 차단된 경우에도 작동하는, 값이 포함된 호스트 이름에 대한 DNS 조회.

외부에서 받아 설치하는 에이전트 스킬도 그 안의 셸 명령이 에이전트 사용자의 권한으로 실행되므로 같은 경로가 될 수 있으며, 스킬을 설치하기 전에 셸 실행과 도구 권한을 검토하는 방법을 따르면 위험을 줄일 수 있지만 완전히 막지는 못합니다. 코드 리뷰만으로는 이 문제를 해결할 수 없습니다. 중요한 데이터가 에이전트의 접근 범위 내에 없도록 하는 것이 해결책입니다.

작업 트리에 있는 비밀 정보는 컨텍스트 윈도우에도 노출됩니다

에이전트는 파일을 읽습니다. 작업 중인 저장소에 .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)"]
  }
}

이는 에이전트가 탐색 중에 파일을 여는 실수를 방지합니다. 또한 Claude Code는 파일 도구뿐만 아니라 Bash에서 인식하는 cat 및 head와 같은 파일 명령, 그리고 < .env과 같은 리다이렉션 대상에 대해서도 이 규칙을 확인합니다. 하지만 프로젝트 디렉터리 내에서 grep -r KEY .을 실행하는 주입된 명령이나, .env를 직접 여는 짧은 Python 스크립트는 막지 못합니다. 권한 문서에 따르면 거부 규칙은 파일 이름을 명시하지 않고 읽는 명령이나 스스로 파일을 여는 하위 프로세스에는 적용되지 않기 때문입니다. 이러한 경우를 위해 문서는 운영 체제 수준에서 차단을 강제하는 Claude Code의 샌드박스를 권장합니다. 이러한 명령이 실행되기 전에 사용자에게 확인을 요청할지 여부는 세션의 권한 모드에 따라 다르며, auto 모드는 이제 대화형 Claude Code 세션의 기본 시작 모드이므로, 사용자가 지켜보고 있지 않은 서버는 이러한 명령을 더 많이 자동으로 실행하게 됩니다. 동일한 제한 사항이 에이전트의 권한이 아닌 습관을 형성하는 요소에도 적용됩니다. 작동하는 가장 작은 변경 사항만 수행하도록 에이전트를 제약하는 기술은 에이전트가 불필요한 파일을 여는 것을 방지하지만, 이는 모델이 대화를 통해 우회할 수 있는 조언일 뿐입니다. 설정을 가드레일로, 파일 시스템 권한을 벽으로 간주하십시오. 동일한 구분 방식이 컨테이너 내부에도 적용됩니다. Docker Compose의 env 파일 및 비밀 정보는 이 문제의 한 단계 아래 버전을 다룹니다.

각 에이전트에 권한이 없는 별도의 사용자 계정 할당

에이전트가 사용자 본인의 계정으로 실행되면, 사용자의 SSH 키, 클라우드 자격 증명, 셸 기록을 그대로 상속받습니다. 별도의 사용자를 생성하는 것은 단 하나의 명령어로 가능하며, 이러한 보안 위험을 모두 제거합니다.

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 오류와 함께 실패해야 합니다. 만약 키 값이 출력된다면, 에이전트 사용자가 해당 파일에 접근할 수 있다는 의미입니다. 파일 이름으로 파일을 열려면 상위 모든 디렉터리에 대한 검색(x) 권한이 필요하며, 읽기 권한은 필요하지 않습니다. 따라서 다른 사용자가 목록을 볼 수 없는 711 모드의 홈 디렉터리라도, 키 파일 자체가 읽기 가능하면 키가 유출될 수 있습니다. chmod 700 ~는 경로를 차단하며, chmod 600 ~/.ssh/id_ed25519은 키 권한을 수정합니다. 에이전트 사용자를 sudo 그룹에 추가하지 마십시오. 또한, 에이전트가 실제로 필요한 단일 명령어보다 더 넓은 범위의 NOPASSWD 규칙을 부여하지 마십시오. VPS의 최소 권한 사용자 문서에서 그룹 및 sudoers 설정에 대한 자세한 내용을 확인할 수 있습니다. 서버에서 두 개 이상의 세션을 실행할 때는 이러한 분리 정책을 항상 염두에 두어야 합니다. 한 Claude Code 세션은 다른 세션으로 텍스트를 직접 전송할 수 있으며, 첫 번째 세션이 보유한 모든 정보는 단일 메시지를 통해 해당 채널을 넘어갈 수 있기 때문입니다.

클라우드 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이 아닌 종료 코드를 반환해야 합니다.

경계 지점에서 자격 증명 주입하기

이 문제를 실제로 해결하는 패턴은 자격 증명 주입입니다. 에이전트는 실제 키를 직접 보유하지 않습니다. 에이전트가 로컬 게이트웨이를 통해 요청을 보내면, 게이트웨이가 외부로 나가는 과정에서 자리 표시자(placeholder)를 실제 비밀 값으로 교체합니다. 비밀 값은 에이전트와 다른 프로세스에서 실행되며 다른 사용자가 소유한 게이트웨이의 저장소에 보관됩니다.

OneCLI는 이를 구현한 오픈 소스 도구 중 하나입니다. 이 도구는 ee/ 디렉터리를 제외하면 Apache-2.0 라이선스를 따르며, 해당 디렉터리는 프로덕션 환경에서 사용 시 구독이 필요한 별도의 기업용 라이선스가 적용됩니다. OneCLI는 에이전트와 함께 컨테이너에서 실행됩니다. 2026년 10월 기준으로 이 프로젝트는 다음과 같은 Docker Compose 설정을 문서화하고 있으며, 이는 compose 파일 옆에 위치한 .env 파일에서 생성된 세 가지 비밀 값을 읽어옵니다.

git clone https://github.com/onecli/onecli.git && cd onecli/docker
cat > .env <<EOF
SECRET_ENCRYPTION_KEY=$(head -c 32 /dev/urandom | base64)
GATEWAY_INTERNAL_SECRET=$(head -c 32 /dev/urandom | base64)
BETTER_AUTH_SECRET=$(head -c 32 /dev/urandom | base64)
COMPOSE_PROFILES=runner
EOF
chmod 600 .env
docker compose up -d --wait

.env 파일은 반드시 백업하십시오. SECRET_ENCRYPTION_KEY을 분실하면 해당 파일로 저장된 모든 비밀 값을 복구할 수 없게 됩니다. 대시보드는 10254 포트에서, 게이트웨이는 10255 포트에서 대기하며 기본적으로 둘 다 127.0.0.1에 바인딩됩니다. 실제 자격 증명을 한 번 저장한 뒤, 각 에이전트에는 키 대신 자리 표시자 값과 고유한 범위의 액세스 토큰을 부여합니다. 에이전트는 이 토큰을 Proxy-Authorization 헤더에 담아 전송합니다. 게이트웨이는 호스트와 경로를 기준으로 아웃바운드 요청을 매칭하고, 일치하는 자격 증명을 복호화하여 값을 대체합니다. 결과적으로 에이전트의 환경에는 탈취할 가치가 있는 정보가 남지 않습니다.

이 방식의 핵심 가치는 암호화 그 자체가 아닙니다. "이 에이전트가 언제 무엇을 사용했는가"라는 질문을 로그 쿼리로 해결할 수 있다는 점이 중요합니다. 6개의 환경 중 어디에 키 사본이 있는지 추측할 필요 없이, 단일 감사 추적(audit trail)을 통해 확인할 수 있습니다.

비밀 값을 환경 변수가 아닌 프로세스에 직접 전달하기

systemd에서 에이전트를 실행할 때는 환경 변수를 전혀 사용할 필요가 없습니다. LoadCredential=는 서비스 사용자만 읽을 수 있는 전용 디렉터리에 비밀 값을 배치하며, 유닛 파일에서는 %d으로, 프로세스 내부에서는 $CREDENTIALS_DIRECTORY로 노출됩니다. 이 제한은 서비스 단위가 아닌 사용자 단위로 적용되므로, 각 에이전트 서비스마다 별도의 사용자를 할당하십시오. 동일한 사용자로 실행되는 다른 프로세스는 해당 디렉터리를 열 수 있습니다. 단, 유닛 파일에서 PrivateMounts=yes과 같은 파일 시스템 네임스페이스 기능을 사용하여 다른 서비스로부터 해당 경로를 숨기는 경우는 예외입니다. 이 값은 /proc/<pid>/environ에 절대 나타나지 않으므로 ps eww으로도 확인할 수 없으며, 서비스가 중지되면 디렉터리도 사라집니다.

먼저 자격 증명을 해당 머신용으로 암호화하십시오. 다음 명령어들은 systemd 문서에 기반하며, Ubuntu 24.04와 Debian 13 이상에서 지원되는 systemd 250 버전부터 사용할 수 있습니다.

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

마지막 명령어를 실행하면 sk-example-value이 출력됩니다. 이는 암호화된 파일이 현재 호스트에서 정상적으로 복호화됨을 의미합니다. 이후 유닛 파일에서 해당 파일을 참조하십시오.

[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_KEY_FILE에 위치한 파일을 엽니다. 파일 읽기는 일시적인 작업입니다. 반면 환경 변수는 프로세스가 실행되는 내내 유지되며, 프로세스가 생성하는 모든 자식 프로세스에도 상속됩니다.

장기 키보다 단기 토큰을 우선 사용하십시오

만료되지 않는 키는 몇 달 뒤 로그나 기록에서 발견되더라도 여전히 유효합니다. 서비스에서 세션 토큰을 제공한다면 해당 토큰을 사용하고, 작업에 필요한 가장 짧은 유효 기간을 설정하십시오.

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분이며, 이는 일반적으로 에이전트 작업 한 번을 수행하기에 충분합니다. GitHub의 경우, 에이전트 사용자에게 별도의 gh 로그인을 부여하고 작업 대상인 단일 저장소로 범위를 제한한 세분화된 토큰을 발급하십시오. 이렇게 하면 해당 세션 내의 gh auth token은 다른 어떤 것에도 접근할 수 없게 됩니다. 먼저 리소스 단위로 범위를 제한하고, 그다음 시간 단위로 제한하십시오.

검증하고, 또 검증하십시오

에이전트 설정 변경 후에는 세 가지 점검을 수행해야 합니다. 본인 계정이 아닌 에이전트 사용자로 실행하십시오.

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을 출력해야 하는데, 이는 해당 디렉터리의 목록을 볼 수 없음을 의미할 뿐입니다. 앞서 수행한 cat 테스트가 그 내부 파일을 읽을 수 없음을 증명합니다. 세 번째 명령은 에이전트의 네트워크 경로가 어떤 자격 증명을 제시하는지 알려줍니다. 이는 게이트웨이 패턴이 해결하고자 하는 핵심 질문입니다. 401는 요청이 GitHub에 도달했으나 GitHub이 수락하는 자격 증명이 없거나, 유효하지 않거나, 만료되었음을 의미합니다. 200은 유효한 자격 증명이 전달되고 있음을 의미하므로, 어떤 토큰이 사용 중인지 파악해야 합니다. 에이전트를 무인으로 실행하는 경우, VPS에서 AI 에이전트 비용 제어하기 문서에서 이러한 접근 제한과 함께 고려해야 할 예산 한도 설정을 다룹니다.

FAQ

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

아니요, 이 위협 모델에서 모델은 공격자가 아니기 때문입니다. 에이전트는 웹 페이지, 저장소, 이슈 트래커에서 텍스트를 읽어 들이며, 해당 텍스트에는 명령이 포함될 수 있습니다. 모델은 사용자의 명령과 자신이 가져온 텍스트를 구분할 신뢰할 만한 방법을 가지고 있지 않습니다. 모델의 올바른 선택에 의존하는 모든 제어 방식은 주입된 명령이 설득력을 갖추는 순간 실패하므로, 제어 장치는 운영 체제나 네트워크 계층에 위치해야 합니다.

에이전트 비밀 정보를 환경 변수에 저장하는 것이 정말 나쁜가요?

환경 변수는 상속된다는 점에서 치명적입니다. 에이전트가 생성하는 모든 자식 프로세스(빌드 스크립트, 테스트 러너, 패키지 설치 훅 등)는 환경 변수의 복사본을 전달받습니다. 또한 해당 변수는 동일한 사용자의 /proc/<pid>/environ을 통해 읽을 수 있으므로, 에이전트가 직접 전달하지 않아도 에이전트가 실행하는 모든 프로세스가 이를 읽을 수 있습니다. 사용 시점에 LoadCredential=이나 게이트웨이를 통해 파일을 읽는 방식은 노출 범위를 해당 시점으로 제한합니다.

비밀 정보를 볼트(vault)에 넣는 것만으로 해결됩니까?

부분적으로만 해결됩니다. 볼트는 저장소 문제를 해결할 뿐입니다. 볼트를 직접 호스팅한다면 별도의 강화 작업이 필요합니다. Vaultwarden 서버는 보통 암호화된 항목 자체가 아니라 관리자 토큰이나 백업 파일을 통해 침해되기 때문입니다. 또한 볼트에서 비밀 정보를 꺼내 에이전트에게 환경 변수로 전달하는 마지막 단계의 문제는 해결하지 못하며, 이는 다시 원점으로 돌아가는 결과를 낳습니다. 중요한 것은 누가 치환을 수행하느냐입니다. 에이전트가 비밀 정보를 가져오면 에이전트가 그 정보를 소유하게 됩니다. 게이트웨이나 init 시스템이 에이전트 프로세스 외부에서 치환을 수행하면 에이전트는 비밀 정보를 결코 소유하지 않습니다.

에이전트가 이미 정보를 유출했는지 어떻게 알 수 있습니까?

보통 사후에는 알 수 없으며, 이것이 바로 게이트웨이가 필요한 이유입니다. 게이트웨이가 없다면 증거는 셸 히스토리, 에이전트 기록, 그리고 아마도 기록하고 있지 않을 아웃바운드 연결 로그에 흩어져 있을 것입니다. 자격 증명 게이트웨이를 사용하면 자격 증명을 사용할 때마다 에이전트 식별자와 타임스탬프가 포함된 한 줄의 로그가 남습니다. 유출이 의심된다면 조사를 수행하기 전에 먼저 키를 교체하십시오. 키 교체는 비용이 적게 들지만, 확신은 그렇지 않습니다.

오늘 당장 해야 할 최소한의 조치는 무엇입니까?

모든 .env 파일을 에이전트가 작업하는 디렉터리 밖으로 옮기고, 에이전트마다 권한이 없는 별도의 사용자를 생성하십시오. 이 두 가지 변경 사항은 약 10분 정도 소요되며, 코드 옆에 있을 이유가 없는 자격 증명 파일을 에이전트가 읽는 가장 흔한 경로를 차단합니다. 게이트웨이와 단기 토큰 도입은 첫 번째 단계가 아니라 그다음 단계입니다. 동일한 시작점은 VPS에서 자율 에이전트를 안전하게 실행하는 것을 포함한 모든 에이전트 런타임에 적용됩니다.

이 글이 도움이 되었나요?