본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 23. 17:45

AI 에이전트가 자격 증명(Credentials)을 유출하는 방식과 이를 방지하는 방법

요약

AI 에이전트가 작업을 수행하는 과정에서 .env 파일이나 볼트(vault)의 자격 증명을 직접 읽어 모델 제공업체로 전송함으로써 발생하는 보안 유출 문제를 다룹니다. 프롬프트 지침만으로는 한계가 있으며, 에이전트가 비밀 값을 보지 않고도 사용할 수 있는 구조적 해결책이 필요함을 강조합니다.

핵심 포인트

  • 에이전트가 자격 증명을 읽는 순간 해당 값은 모델 제공업체의 로그에 남음
  • 프롬프트 기반의 보안 권고는 에이전트에 의해 무시될 수 있어 불완전함
  • OpenClaw 사례처럼 확장 프로그램이나 MCP 도구를 통한 유출 위험 존재
  • Infisical과 같은 도구를 활용해 비밀 값이 모델 컨텍스트에 포함되지 않도록 차단해야 함

원래 th3chris.com에 게시되었습니다.

당신은 자신의 리포지토리(repo)에서 Claude Code, Cursor 등과 같은 AI 코딩 에이전트와 협업하고 있습니다. 그리고 "풀 리퀘스트(pull request)를 열고, 스테이징 API를 호출하고, 배포를 실행해줘"라는 요청을 수행하기 위해 에이전트에게 자격 증명(credentials)이 필요합니다. 자격 증명을 건네주는 순간, 그 명백한 전달 행위 자체가 이미 유출입니다. 그렇다고 "그냥 아무것도 안 주면 되지"라고 생각하는 것도 해결책이 되지 않습니다. 에이전트는 스스로 .env 파일을 읽거나, 볼트(vault)를 조회하여 자격 증명을 직접 가져오며, 당신이 알아차리지 못하는 사이에 그 과정에서 이를 유출합니다. 두 경우 모두 해당 값은 AI 모델을 통과하게 되며, 일단 통과하면 끝입니다.

불편한 사실은 다음과 같습니다. 대부분의 에이전트 설정에서 자격 증명은 당신의 인프라를 떠나 모델 제공업체(model provider)나 에이전트가 실행하는 서비스로 전달되며, 당신의 통제를 벗어나 저장됩니다. (Self-hosted 모델이나 엔터프라이즈 모델을 사용하면 저장 방식이나 학습 방식은 달라질 수 있지만, 값이 당신의 손을 떠난다는 사실은 변하지 않습니다.) 대부분의 기업에게 이는 컴플라이언스(compliance) 문제이기도 합니다. 왜 이런 일이 발생하는지, 그리고 이를 완전히 차단하는 방법은 무엇인지 알아보겠습니다.

요약(TL;DR) — AI 에이전트는 자격 증명을 읽어서 모델 제공업체로 전송되는 출력값으로 만듦으로써 유출합니다. 해결책은 에이전트가 비밀 값을 보지 못한 채로 사용만 할 수 있도록 하는 것입니다.

명백한 설정이 유출되는 이유

그 메커니즘은 단순하면서도 교묘합니다. 에이전트가 .env, 볼트(vault), 또는 당신의 메시지로부터 값을 읽는 순간, 그 값은 에이전트가 AI 제공업체로 보내는 대화의 일부가 됩니다. 이제 토큰은 타인의 로그(logs)에 남게 됩니다. 나중에 채팅을 삭제하더라도 아무런 소용이 없습니다. 값이 나타난 순간 이미 전송되었기 때문입니다.

유출 방식 — 비밀 값이 모델로 전달되는 경로

  .env / 볼트 내의 비밀 값
...

AI에게 "조심해 주세요"라고 말하는 것으로는 이 문제를 해결할 수 없습니다. 프롬프트(prompt)에 포함된 지침은 권고 사항일 뿐이며, 권고는 이 에이전트든, 다음 에이전트든, 혹은 이를 감싸고 있는 어떤 도구든 무시될 수 있습니다. 해결책은 구조적이어야 합니다. 비밀 값은 단순히 보지 말라고 권고하는 것이 아니라, 보는 것 자체가 불가능해야 합니다.

이는 가설이 아닙니다. 2026년 초, 보안 연구원들은 AI 에이전트 플랫폼인 OpenClaw의 확장 프로그램 마켓플레이스인 ClawHub에 등록된 스킬(skills) 중 7%가 정확히 이러한 방식으로 자격 증명(credentials)을 유출한다는 사실을 발견했습니다. 저는 이 사건을 OpenClaw 유출 사고에서 상세히 분석한 바 있습니다. 이는 OpenClaw만의 특수한 사례가 아닙니다. 동일한 메커니즘이 모든 스킬, 모든 MCP 도구, 그리고 Claude Code나 Cursor가 실행하는 모든 명령에 적용됩니다. 확장 프로그램이 비밀 값(secret)을 읽는 순간, 그것은 모델의 컨텍스트(context)에 포함됩니다.

그리고 비밀 값을 가져오는 것이 에이전트뿐만이 아닙니다. 수백만 명의 개발자, 그리고

저는 **Infisical**를 사용합니다. 이는 오픈 소스이며 셀프 호스팅(self-hostable)이 가능하여 (비밀 정보가 제 자체 인프라에 머뭅니다), 이러한 에이전트 및 CI(지속적 통합) 용도에 딱 맞게 설계된 헤드리스(headless), 단기 토큰 인증(short-lived-token auth)과 같은 일급 **머신 아이덴티티 (machine identities)**를 제공합니다. 이 문제에 대한 Infisical의 진정한 이점은 세 가지를 하나로 처리한다는 점입니다. 즉, 비밀 값을 가져오고(fetch), 이를 프로그램의 환경 변수(environment)에 넣고, **명령어를 실행(run)**하는 것인데, 이 과정에서 값은 절대 표준 출력(stdout)에 나타나지 않습니다. 이 체인(fetch → env → run)은 HashiCorp Vault, pass 또는 AWS Secrets Manager와 같은 볼트(vault)가 여러분을 위해 해주지 않는 바로 그 작업입니다. 그러한 도구들은 오직 **값(value)**만을 반환하므로, 여러분이 직접 짧은 래퍼 스크립트(wrapper script)를 작성하여 체인을 연결해야 합니다. 두 방식 모두 동일한 결과에 도달하지만, Infisical은 단지 저항이 가장 적은(path of least resistance) 경로일 뿐입니다.

실제 사례: 대부분 단 하나의 명령어로 해결됩니다

비밀 값이 Infisical에 저장되면, 실제로는 다음과 같이 작동합니다. 에이전트는 여러분의 열려 있는 풀 리퀘스트(pull requests) 목록을 나열해야 합니다. 이를 위해 GitHub의 CLI인 gh pr list를 실행하며, gh는 여러분의 GitHub 토큰을 필요로 합니다. 에이전트에게 토큰을 직접 건네주는 대신, Infisical이 명령어를 실행하도록 합니다:

infisical run --env=prod --path=/git -- gh pr list

단계별 과정은 다음과 같습니다: infisical run/git 폴더(환경 prod)에서 비밀 값을 가져온 뒤, -- 뒤에 오는 명령어(여기서는 gh pr list)를 위한 새로운 프로세스를 시작합니다. 이때 비밀 값은 해당 프로세스의 환경 변수에 포함됩니다. 각 비밀 값은 Infisical의 키와 동일한 이름의 환경 변수가 됩니다. 예를 들어 /git 폴더에 GITHUB_TOKEN이라는 비밀 값이 있다면, 프로세스는 $GITHUB_TOKEN을 볼 수 있게 됩니다. GitHub CLI인 gh는 많은 도구가 알려진 환경 변수로부터 토큰을 가져오는 방식과 동일하게 해당 변수를 스스로 읽어 들여 작업을 수행합니다. 핵심 포인트는 이것입니다: 비밀 값은 에이전트가 아닌 해당 자식 프로세스(child process) 내부에 존재하며, 그 값은 절대 표준 출력(stdout)에 나타나지 않습니다. 따라서 에이전트가 AI에게 보내는 트랜스크립트(transcript)에도 절대 포함되지 않습니다. 이것이 바로 위에서 언급한 fetch → env 삽입 → run 체인을 하나의 명령어로 압축한 것입니다. 대부분의 팀에게 이것이 바로 해결책입니다.

Infisical project overview: the /git folder with secrets GITHUB_TOKEN, GITLAB_HOST and GITLAB_TOKEN, values masked

Infisical에서는: /git 폴더가 GITHUB_TOKEN이라는 이름의 비밀값(secret)을 보유하며, 해당 키는 프로세스 내에서 정확히 $GITHUB_TOKEN이 됩니다 (값은 마스킹된 상태로 유지됩니다).

저는 호출을 짧게 만들고 인증(auth)을 자동화하기 위해 이를 get-secret 헬퍼(helper)로 감쌌습니다:

get-secret exec git -- gh pr list

내부적으로 이것은 OS 키체인(keychain)에서 가져온 머신 아이덴티티 토큰(machine-identity token)을 사용한 infisical run일 뿐입니다. 따라서 모든 호출에서 --token, --projectId, --domain을 수동으로 전달하는 과정을 생략할 수 있습니다. 비밀값을 사용하기 위해서라면 그냥 infisical run을 입력해도 무방하지만, 여기서는 래퍼(wrapper)를 통해 편의성을 극대화한 것입니다.

이로써 유출 경로가 차단됩니다: 비밀값은 사용되지만, 모델이나 트랜스크립트(transcript)에 결코 노출되지 않습니다. 저는 이를 적대적(adversarially)으로 테스트했습니다: 실제 값을 가져오고, 명령어가 출력하는 모든 것을 캡처한 뒤, 그 출력물에서 비밀값을 검색해 보았습니다. get-secret exec를 사용할 경우, 에이전트가 읽을 수 있는 곳 어디에도 나타나지 않았습니다.

안전하게 유지되는 방법 — 비밀값이 모델에 도달하지 않음

  AI 에이전트
...

🔒 토큰은 해당 자식 프로세스(child process) 내부에 머뭅니다. AI는 자신이 입력한 명령어와 결과만을 볼 뿐, 값 자체는 결코 볼 수 없습니다.

단일 값 읽기 (가드레일 포함)

이것은 사용에 관한 내용입니다. 하지만 때로는 값을 단순히 확인만 하고 싶을 때(디버깅, 빠른 점검 등)가 있으며, 혹은 에이전트에게 직접

[ -t 1 ]은 stdout이 실제 터미널인지 확인합니다. 에이전트의 stdout은 일반적으로 파이프(pipe)이기 때문에, 값 대신 [redacted len=40]을 받고, 터미널에서 사람이 전체 값을 공개하는 방법(--show)에 대한 힌트를 받습니다.

따라서 오해가 없어야 합니다: 이것은 방화벽(wall)이 아니라 가드레일(guardrail)입니다. 그리고 실제 예방책도 아닙니다. 그것은 exec에 있습니다. 에이전트는 비밀값을 사용하기 위해 원시 읽기 경로(raw read path)를 전혀 필요로 하지 않습니다. 이 가드레일은 가장 흔한 반사 행동, 즉

#!/bin/bash
# run-with-secret <cmd...> — 어떤 vault에서든 비밀값을 환경 변수로 로드한 후 명령어를 실행합니다.
export GITHUB_TOKEN="$(vault kv get -field=token secret/git)"   # 또는: pass show git/token, aws secretsmanager get-secret-value …
...

run-with-secret gh pr list는 명령어 치환 (command substitution)을 통해 환경 변수를 거쳐 gh에 토큰을 전달합니다. 이 과정은 결코 표준 출력 (stdout)을 거치지 않으므로, 에이전트는 명령어만 볼 뿐 값은 절대 볼 수 없습니다. 더 많은 비밀값이 필요하다면? export 줄을 더 추가하면 됩니다. 이것이 바로 get-secret exec가 내부적으로 작동하는 방식이며, Infisical은 단지 여러분이 이를 직접 작성하는 수고를 덜어줄 뿐입니다.

따라서 분리는 간단합니다. **읽기 게이트 (read gate)**는 여러분이 사용하는 vault가 무엇이든 한 번 구축해 두면 됩니다. **주입 (injection)**은 Infisical(infisical run)을 사용하면 무료이며, 다른 도구를 사용하더라도 몇 줄의 코드만 있으면 됩니다. 안전함은 vault에 존재하는 것이 아니라, 값을 에이전트에게 전달하는 방식, 즉 프롬프트 (prompt)가 아닌 프로세스 (process) 내부로 전달하는 방식에 존재합니다.

새로운 비밀값 쓰기

새로운 비밀값을 쓰는 것은 비대칭적인 방향성을 가집니다. 값은 어딘가로부터 유입되어야 하며, 만약 사람이 에이전트가 저장하도록 채팅창에 값을 붙여넣는다면 그 붙여넣기 자체가 이미 유출입니다. 따라서 쓰기 경로 (write path)는 값을 오직 표준 입력 (stdin) 또는 파일을 통해서만 받으며, 명령에 직접 타이핑하지는 않습니다:

echo -n "$VALUE" | set-secret git/TOKEN -
# `infisical secrets set`을 래핑(wrap)하여 stdin을 읽습니다.

스크립트 가져오기

결국 이 모든 것은 짧은 셸 스크립트입니다. 비밀값을 사용하기 위한 infisical run, 비밀값을 읽기 위한 TTY 게이트 방식의 infisical secrets get, 그리고 인증을 위해 OS 키체인(keychain)에서 가져온 헤드리스 머신 아이덴티티 (machine identity) (대화형 로그인 없음)로 구성됩니다. 다운로드할 두 가지 래퍼 (wrapper)는 다음과 같습니다: get-secretset-secret.

설치 (macOS; infisical CLI 및 jq 필요):

mkdir -p ~/.local/bin
curl -fsSL https://th3chris.com/scripts/get-secret -o ~/.local/bin/get-secret
curl -fsSL https://th3chris.com/scripts/set-secret -o ~/.local/bin/set-secret
...

이 스크립트들은 macOS를 가정합니다 — 자격 증명(Credentials)이 Keychain에 저장되기 때문입니다. 이 패턴 자체는 플랫폼에 독립적입니다. Linux/Windows의 경우 security 호출(3단계)을 해당 OS의 비밀 저장소(libsecret/pass, Windows Credential Manager)로 교체하거나, INFISICAL_TOKEN 환경 변수(env var)를 통해 머신 아이덴티티(machine identity)를 전달하면 됩니다.

설정 (Setup, 1회성):

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0