에이전틱스
도메인 7. 보안과 안전

7.4신원과 비밀값, 키 관리

예상 학습 시간 22

API 키가 새는 사고는 대개 정교한 공격이 아니라 평범한 실수에서 납니다. 이 수업에서는 키를 두는 자리와 회전, 접근 통제의 기본을 다룹니다.

키를 두는 자리

원칙은 하나입니다. 키는 코드에 없어야 합니다.

# 나쁨 — 저장소에 커밋되고 로그에 찍힌다
client = anthropic.Anthropic(api_key="sk-ant-...")

# 나음 — 환경에서 읽는다
client = anthropic.Anthropic()          # ANTHROPIC_API_KEY를 자동으로 읽음

챙겨야 할 자리가 몇 곳 있습니다.

  • 저장소 — 커밋 이력에 한 번 들어가면 지워도 남습니다. 커밋 전 검사를 걸어 두는 편이 안전합니다.
  • 로그 — 요청 헤더를 통째로 기록하면 키가 남습니다.
  • 오류 메시지 — 예외에 요청 정보가 포함되어 사용자에게 노출될 수 있습니다.
  • 클라이언트 코드 — 브라우저나 앱에 키를 두면 누구나 꺼낼 수 있습니다. 호출은 서버를 거쳐야 합니다.

마지막 항목이 특히 중요합니다. 프런트엔드에서 직접 모델 API를 부르는 구조는 키를 감출 방법이 없습니다. 서버에 중계 지점을 두는 것이 기본입니다.

모델에 비밀값을 주지 않기

도구가 외부 서비스를 호출할 때 자격 증명이 필요합니다. 이때 키를 프롬프트나 도구 인자로 넘기지 않습니다.

# 나쁨 — 모델이 키를 알게 되고 출력에 섞여 나갈 수 있다
tools = [{"name": "call_api",
          "input_schema": {"properties": {"api_key": {"type": "string"}, ...}}}]

# 나음 — 모델은 무엇을 할지만 정하고 키는 서버가 붙인다
tools = [{"name": "call_api",
          "input_schema": {"properties": {"endpoint": {"type": "string"}, ...}}}]

def call_api(endpoint: str):
    return requests.get(endpoint, headers={"Authorization": f"Bearer {SECRET}"})

모델이 키를 알 필요가 없습니다. 도구 이름과 인자만 정하면 되고, 실제 인증은 도구를 실행하는 우리 코드가 붙입니다. 이렇게 두면 프롬프트 인젝션으로 키를 빼내려는 시도도 통하지 않습니다. 모델의 컨텍스트에 없는 것은 유출될 수 없습니다.

회전과 범위

키는 언젠가 샙니다. 그 전제로 관리합니다.

회전 — 정기적으로 새 키로 바꿉니다. 그러려면 키가 한 곳에서만 참조되어야 합니다. 여러 곳에 흩어져 있으면 회전이 사실상 불가능해집니다.

범위 분리 — 환경마다, 용도마다 키를 나눕니다. 개발과 운영이 같은 키를 쓰면 개발 실수가 운영 한도를 소모하고, 어느 쪽에서 샜는지도 알 수 없습니다.

사용량 관측 — 키별로 사용량을 보면 이상을 빨리 발견할 수 있습니다. 평소와 다른 시간대나 규모의 호출이 그 신호입니다.

유출이 확인되면 순서가 있습니다. 먼저 무효화하고, 새 키로 교체하고, 그다음 원인을 찾습니다. 원인 조사를 먼저 하다 유출된 키가 계속 살아 있는 상황이 흔한 실수입니다.

누구의 권한으로 실행되는가

키는 서비스의 신원이고, 사용자의 신원과는 다릅니다. 이 둘을 섞으면 문제가 생깁니다.

모델을 통한 데이터 접근에서 호출자가 볼 수 있는 범위를 지켜야 합니다. 서비스 키로 전체를 조회한 뒤 결과에서 걸러 내는 방식은 위험합니다. 걸러 내기 전 데이터가 이미 모델의 컨텍스트에 들어가 버립니다.

def search(query: str, *, actor: User) -> list:
    # 조회 시점에 범위를 좁힌다 — 나중에 걸러 내지 않는다
    return db.search(query, scopes=actor.scopes)

승인이 필요한 동작에서는 누가 승인했는지 기록해야 합니다. 나중에 문제가 생겼을 때 그 동작이 어떤 근거로 실행됐는지 추적할 수 있어야 합니다.

접근 권한은 정기적으로 다시 봅니다. 처음에 필요해서 준 권한이 계속 필요한지, 그 사람이나 서비스가 아직 그 일을 하는지 확인하지 않으면 권한만 쌓입니다.

시험 함정

  • 키를 코드에 두되 저장소를 비공개로 하면 된다는 선택지 — 커밋 이력과 로그에 남습니다.
  • 프런트엔드에서 모델 API를 직접 호출하는 구조 — 키를 감출 방법이 없습니다.
  • 도구 인자로 API 키를 넘기는 스키마 — 모델 컨텍스트에 들어가 유출 경로가 됩니다.
  • 개발과 운영이 같은 키를 쓰는 구성 — 한도를 공유하고 유출 출처를 가릴 수 없습니다.
  • 유출 확인 시 원인 조사부터 하는 순서 — 무효화가 먼저입니다.
  • 서비스 키로 전체를 조회한 뒤 결과에서 걸러 내는 구현 — 걸러 내기 전에 이미 컨텍스트에 들어갑니다.

실습 시나리오

실제 시험과 같은 형식의 시나리오 문제입니다.

사내 도구에서 모델이 외부 결제 API를 호출해야 합니다. 자격 증명을 다루는 방식으로 가장 적절한 것은 무엇입니까?

빌드 연습 · 비밀값을 컨텍스트 밖으로 빼기

25
  1. 1.현황 점검

    코드와 로그에서 키가 노출되는 자리를 찾습니다.

    기대 결과 · 노출 지점 목록이 나옵니다.

  2. 2.환경으로 이동

    키를 환경 변수로 옮기고 참조를 한 곳으로 모읍니다.

    기대 결과 · 회전이 한 곳 수정으로 가능해집니다.

  3. 3.도구 스키마 점검

    도구 인자에서 자격 증명 관련 필드를 제거합니다.

    기대 결과 · 모델이 키를 다루지 않습니다.

  4. 4.로그 정리

    요청 헤더 전체 기록을 없애고 필요한 항목만 남깁니다.

    기대 결과 · 로그에 키 문자열이 없습니다.

  5. 5.범위 분리

    개발과 운영 키를 나누고 사용량을 각각 관측합니다.

    기대 결과 · 환경별 사용량이 따로 보입니다.

출처 및 더 읽기