콘텐츠로 이동

보안 & 관리

API 키 섹션

API 키는 라우터 엔드포인트에 대한 클라이언트 접근을 제어합니다. 키는 여러 소스를 통해 설정할 수 있습니다.

인증 모드

mode 설정은 API 엔드포인트에 API 인증이 필요한지 제어합니다:

모드 동작
permissive (기본) API 키 없이 요청 허용. 유효한 API 키가 있는 요청은 인증됨.
blocking API 키 인증을 통과한 요청만 처리. 인증되지 않은 요청은 401 수신.

mode가 blocking이면 OpenAI 호환 API 라우터에 마운트된 모든 경로에 유효한 API 키가 필요합니다. 그 키의 scopes 목록이 비어 있지 않으면, 같은 미들웨어가 핸들러에 도달하기 전에 라우트 등급을 검사합니다. scopes가 없거나 비어 있으면 기존 호환성을 위해 제한 없음으로 처리됩니다. permissive 모드에서는 키가 없거나 잘못되어도 계속 통과하고 유효하지만 좁은 scope의 키도 scope 때문에 거부되지 않습니다.

참고: Admin, Files, Metrics 엔드포인트는 별도의 인증 메커니즘이 있으며 이 설정의 영향을 받지 않습니다.

API 라우트 scope 등급:

필요한 scope 라우트
read GET /v1/models, GET /v1/models/extended, GET /v1/models/proxy, GET /v1/models/{model}, POST /v1/models/query, GET /v1/responses/{response_id}, POST /anthropic/v1/messages/count_tokens, POST /v1/messages/count_tokens, POST /tokenize, POST /v1/tokenize, POST /detokenize, POST /v1/detokenize, GET /anthropic/v1/models, GET /v1/vector_stores, GET /v1/vector_stores/{store_id}, GET /v1/vector_stores/{store_id}/files, GET /v1/vector_stores/{store_id}/files/{file_id}, POST /v1/vector_stores/{store_id}/search, GET /v1/batches, GET /v1/batches/{batch_id}, GET /v1/batches/{batch_id}/results
write POST /v1/models/refresh, POST /v1/completions, POST /v1/chat/completions, POST /v1/embeddings, POST /v1/rerank, POST /embed_sparse, POST /v1/images/generations, POST /v1/images/edits, POST /v1/images/variations, POST /v1/audio/transcriptions, POST /v1/audio/translations, POST /v1/audio/speech, POST /v1/responses, POST /v1/responses/compact, DELETE /v1/responses/{response_id}, POST /v1/responses/{response_id}/cancel, POST /v1/vector_stores, POST /v1/vector_stores/{store_id}, DELETE /v1/vector_stores/{store_id}, POST /v1/vector_stores/{store_id}/files, DELETE /v1/vector_stores/{store_id}/files/{file_id}, POST /anthropic/v1/messages, POST /v1/messages, POST /v1/batches, POST /v1/batches/{batch_id}/cancel, GET /v1/realtime, GET /realtime

write는 read를 포함하고 admin은 이 API 라우트에서 둘 다 포함합니다. files만 가진 키는 어느 등급도 만족하지 않습니다. Batch와 realtime 행은 해당 route feature가 컴파일된 빌드에서만 적용됩니다. Realtime 라우트는 GET으로 시작하지만 양방향 추론 세션으로 업그레이드되므로 write가 필요합니다. POST /v1/models/refresh도 라우터의 모델 카탈로그를 변경하므로 write가 필요하고 POST /anthropic/v1/messages/count_tokens는 추론을 실행하지 않고 토큰 수만 계산하므로 read만 필요합니다. POST /v1/vector_stores/{store_id}/search도 스토어를 변경하지 않으므로 read만 필요합니다.

섹션 설정 속성:

속성 타입 필수 기본값 설명
mode string 아니오 permissive 인증 모드: permissive 또는 blocking
api_keys array 아니오 [] 인라인 API 키 정의
api_keys_file string 아니오 - 외부 API 키 파일 경로
api_keys:
  # 인증 모드: "permissive" (기본) 또는 "blocking"
  mode: permissive

  # 인라인 API 키 정의
  api_keys:
    - key: "${API_KEY_1}"              # 환경 변수 치환
      id: "key-production-1"           # 고유 식별자
      user_id: "user-admin"            # 연결된 사용자
      organization_id: "org-main"      # 연결된 조직
      name: "Production Admin Key"     # 사람이 읽을 수 있는 이름
      scopes:                          # 권한
        - read
        - write
        - files
        - admin
      rate_limit: 1000                 # 분당 요청 수 (선택 사항)
      enabled: true                    # 활성 상태
      expires_at: "2025-12-31T23:59:59Z"  # 선택적 만료 (ISO 8601)

    - key: "${API_KEY_2}"
      id: "key-service-1"
      user_id: "service-bot"
      organization_id: "org-main"
      name: "Service Account"
      scopes: [read, write, files]
      rate_limit: 500
      enabled: true

  # 보안 향상을 위한 외부 키 파일
  api_keys_file: "/etc/continuum-router/api-keys.yaml"

키 속성:

속성 타입 필수 설명
key string 예 API 키 값 (${ENV_VAR} 치환 지원)
id string 예 admin 작업을 위한 고유 식별자
user_id string 예 이 키와 연결된 사용자
organization_id string 예 사용자가 속한 조직
name string 아니오 사람이 읽을 수 있는 이름
description string 아니오 키에 대한 메모
scopes array 아니오 권한: read, write, files, admin. 비어 있거나 없으면 /v1에서 제한 없음이고 비어 있지 않은 목록은 blocking 모드에서만 /v1에 적용됩니다.
rate_limit integer 아니오 분당 최대 요청 수
enabled boolean 아니오 활성 상태 (기본값: true)
expires_at string 아니오 ISO 8601 만료 타임스탬프
allowed_backends array 아니오 키별 백엔드 허용 목록. 비어 있거나 없으면 제한 없음. 아래 참조.
allowed_regions array 아니오 키별 데이터 상주 허용 목록으로 backends[].region_name과 대조합니다. 비어 있거나 없으면 제한 없음. 아래 참조.
allowed_models array 아니오 키별 모델 이름 허용 목록(주 이름 및 별칭). 비어 있거나 없으면 제한 없음. 아래 참조.
prompt_logging_consent string 아니오 allowed 또는 denied: 이 키가 프롬프트 캡처에 대해 나타내는 값입니다. 없으면 prompt_logging.default_consent를 따릅니다. 키의 동의가 오는 곳을 참고하세요.

외부 키 파일 형식:

# /etc/continuum-router/api-keys.yaml
keys:
  - key: "sk-prod-xxxxxxxxxxxxxxxxxxxxx"
    id: "key-external-1"
    user_id: "external-user"
    organization_id: "external-org"
    scopes: [read, write, files]
    enabled: true

보안 기능:

  • 키 마스킹: 전체 키는 로깅되지 않음 (sk-***last4로 표시)
  • 만료 적용: 만료된 키는 자동으로 거부
  • 핫 리로드: 서버 재시작 없이 키 업데이트
  • 감사 로깅: 모든 키 관리 작업 로깅
  • Scope 검증: blocking 모드에서 비어 있지 않은 scopes가 /v1 read/write 라우트 등급을 제한
  • 상수 시간 검증: 타이밍 공격 방지
  • 최대 키 제한: DoS 방지를 위해 최대 10,000개 키

Admin API 엔드포인트 (admin 인증 필요):

엔드포인트 메서드 설명
/admin/api-keys GET 모든 키 목록 (마스킹됨)
/admin/api-keys/:id GET 키 세부 정보 가져오기
/admin/api-keys POST 새 키 생성
/admin/api-keys/:id PUT 키 속성 업데이트
/admin/api-keys/:id DELETE 키 삭제
/admin/api-keys/:id/rotate POST 새 키 값 생성
/admin/api-keys/:id/enable POST 키 활성화
/admin/api-keys/:id/disable POST 키 비활성화

인바운드 JWT/OIDC 검증

api_keys.jwt를 설정하면 클라이언트가 정적 키 대신 identity provider가 발급한 JWT로 인증할 수 있습니다. 이 섹션은 기본적으로 꺼져 있으며, jwt-auth Cargo 기능 없이 컴파일한 빌드에서는 비활성입니다. 이런 빌드는 설정을 읽을 때 경고를 남기고 정적 키 인증을 그대로 유지합니다.

api_keys.jwt 섹션은 시작할 때 읽으므로 변경 사항은 API 키 자료와 같은 규칙으로 재시작 후에 적용됩니다. 설정된 jwks_uri에서의 키 교체는 재시작이 필요 없으며 다음 갱신에서 캐시에 반영됩니다.

정적 키가 항상 먼저입니다. 미들웨어는 제시된 자격 증명을 먼저 키 저장소와 대조하고, 일치하는 키가 없을 때만 JWT로 해석합니다. 검증 전 iss가 설정된 issuer와 다른 토큰은 기존 동작으로 넘어가므로, 다른 provider의 토큰은 계속 알 수 없는 키로 처리됩니다. 설정된 issuer를 가리키지만 검증할 수 없는 토큰은 표준 잘못된 키 본문과 함께 401로 거부됩니다. 검증되지 않은 토큰을 허용하는 경로는 없습니다.

섹션 설정 속성:

속성 타입 필수 기본값 설명
enabled boolean 아니오 false 인바운드 JWT 검증 활성화
issuer string 활성화 시 예 - 허용할 토큰이 반드시 가져야 하는 정확한 iss 값
audience string 활성화 시 예 - 토큰의 aud 클레임에 포함되어야 하는 audience
jwks_uri string jwks_uri 또는 static_jwks 중 하나 - JWKS 엔드포인트. https이거나 루프백 호스트의 http여야 합니다
static_jwks object jwks_uri 또는 static_jwks 중 하나 - 인라인 JWK Set({ "keys": [...] }). 갱신하지 않습니다
algorithms array 아니오 ["RS256", "ES256", "EdDSA"] 허용할 비대칭 알고리즘. none과 HS*는 설정 오류입니다
clock_skew_secs integer 아니오 60 exp와 nbf에 허용할 시계 오차. 0부터 300까지
jwks_cache_ttl_secs integer 아니오 300 가져온 JWKS를 사용할 수 있는 시간(초)
jwks_refresh_interval_secs integer 아니오 30 알 수 없는 kid가 유발하는 갱신 사이의 최소 간격(초)
jwks_timeout_secs integer 아니오 5 JWKS 가져오기 한 번의 고정 제한 시간. 1부터 30까지
jwks_max_bytes integer 아니오 262144 허용할 JWKS 응답 본문 최대 크기
subject_claim string 아니오 sub subject를 담는 클레임. 이 issuer의 토큰에 없으면 거부합니다
user_claim string 아니오 - user id를 담는 클레임. 없으면 subject를 사용합니다
team_claim string 아니오 - 팀 또는 조직 id를 담는 클레임. 없으면 빈 값으로 둡니다
scopes_claim string 아니오 - scope 목록을 담는 클레임. 없으면 scope를 부여하지 않습니다
allowed_models_claim string 아니오 - 허용 모델 목록을 담는 클레임
allowed_backends_claim string 아니오 - 허용 백엔드 목록을 담는 클레임
api_keys:
  mode: blocking

  jwt:
    enabled: true
    issuer: "https://idp.example.com/"
    audience: "continuum-router"
    jwks_uri: "https://idp.example.com/.well-known/jwks.json"
    algorithms: ["RS256"]
    clock_skew_secs: 60
    scopes_claim: "scope"
    user_claim: "email"
    team_claim: "org_id"

클레임 매핑:

  • 신원: subject_claim은 키 id의 기준이 되고, user_claim은 사용자 신원, team_claim은 팀별 제한과 귀속에 쓰이는 조직이 됩니다.
  • Scope: JWT의 scope 목록은 최종 기준입니다. scopes_claim을 설정했는데 클레임이 없거나 비어 있으면, 해당 principal은 scope가 없으므로 scope가 필요 없는 경로만 통과합니다. 마운트된 모든 API 경로는 read 또는 write를 요구하므로 JWT 클라이언트에는 scope 클레임을 설정하십시오.
  • 권한: allowed_models_claim과 allowed_backends_claim은 문자열 배열을 읽습니다. 형식이 잘못된 값은 권한을 넓히지 않고 토큰을 거부합니다. 클레임이 없거나 빈 배열이면 목록이 비고, 빈 목록은 제한 없음을 뜻합니다. 제한이 필요한 토큰에는 해당 클레임이 항상 실리도록 아이덴티티 제공자를 설정하십시오.

보안 속성:

  • 비대칭 알고리즘만 허용: 설정된 알고리즘만 받습니다. alg: none, HMAC 알고리즘, 허용 목록 밖의 알고리즘은 거부하며, 대칭(kty: oct) JWK는 절대 선택하지 않습니다.
  • 토큰이 키를 고르지 못함: 헤더에 jku, jwk, x5u가 있는 토큰은 거부합니다. 키 자료는 설정된 JWKS에서만 가져옵니다.
  • 입력 크기 제한: 토큰 크기, JWKS 본문 크기, JWKS 키 개수, 클레임 문자열 길이, 목록 개수, scope 개수에 상한을 둡니다.
  • 가져오기 제한: JWKS 가져오기에는 고정 제한 시간이 있고 리디렉션을 따르지 않으며, 실패하면 fail-closed로 동작합니다. 캐시는 TTL과 속도 제한된 단일 실행 갱신(single-flight)을 사용하므로, 알 수 없는 kid가 identity provider에 요청 폭주를 일으키지 못합니다.
  • 키 교체: 알 수 없는 kid를 가진 토큰은 jwks_refresh_interval_secs마다 최대 한 번 갱신을 유발합니다. 교체된 서명 키는 다음 갱신에서 반영됩니다.

메트릭:

  • continuum_jwt_auth_total{result}는 결과를 제한된 result 레이블로 집계합니다: accepted, expired, not_yet_valid, invalid_signature, wrong_audience, unsupported_algorithm, malformed, kid_not_found, jwks_unavailable, misconfigured.
  • continuum_jwt_jwks_fetch_total{result}는 JWKS 가져오기를 success, timeout, http_error, too_large, invalid_body로 집계합니다.

키별 백엔드 접근 제어

allowed_backends는 클라이언트 키를 설정된 백엔드 중 일부로 제한합니다. 이는 클라이언트 키에서 백엔드로 향하는 접근 제어로, 라우터가 OpenAI나 Anthropic 등을 호출할 때 사용하는 업스트림 자격 증명인 backends[].api_key와는 별개입니다.

목록이 비어 있지 않으면, 해당 키로 인증된 요청은 명시된 백엔드로만 라우팅됩니다. 허용되지 않은 백엔드만 제공하는 모델을 요청하면 permission_error 본문과 함께 403 Forbidden으로 거부됩니다. 목록이 비어 있거나 없으면 키는 제한 없이 동작하며 이것이 기본값입니다.

동작 방식:

  • 비어 있음 / 없음 ⇒ 제한 없음 (키는 요청한 모델을 제공하는 모든 백엔드로 라우팅 가능).
  • 비어 있지 않음 ⇒ backends[].name 값의 허용 목록. 매칭은 다른 백엔드 이름 해석과 마찬가지로 정확히 일치하며 대소문자를 구분합니다.
  • 제공 불가 요청 ⇒ 403 Forbidden ({"error": {"type": "permission_error", ...}}). 403 메시지에 모델 이름이 포함되어 404(모델 없음)와 구분할 수 있습니다.
  • 교차 제공자 폴백도 같은 목록으로 필터링되므로, 제한된 키가 폴백을 거쳐 자신의 범위를 벗어날 수 없습니다.
api_keys:
  mode: blocking
  api_keys:
    - key: "${PARTNER_KEY}"
      id: "key-partner-1"
      user_id: "partner-acme"
      organization_id: "org-acme"
      scopes: [read, write]
      allowed_backends: [openai, anthropic]   # 이 백엔드로만 라우팅 가능

    - key: "${INTERNAL_KEY}"
      id: "key-internal-1"
      user_id: "team-ml"
      organization_id: "org-internal"
      scopes: [read, write]
      allowed_backends: [vllm-local]          # 자체 호스팅 백엔드로 제한

모드 상호작용: 제한은 blocking과 permissive 모드 모두에서 적용되지만 permissive 모드에서는 유효한 키를 제시한 호출자에게만 효력이 있습니다. permissive 모드에서 인증된 키는 거부하지 않는 best-effort optional-auth 단계를 통해 자신의 정책을 그대로 적용받고 키가 없거나 잘못된 요청은 제한 없이 통과해 permissive 모드의 '익명 허용' 동작을 유지합니다.

모델 목록: 제한된 키로 인증하면 /v1/models, /v1/models/extended, /anthropic/v1/models가 필터링되어, 키는 허용된 백엔드 중 하나 이상이 제공하는 모델만 노출합니다. GET /v1/models/{model}은 키가 접근할 수 없는 모델에 대해 404 Not Found를 반환하죠. 인증되지 않은 호출자와 제한 없는 키는 전체 목록을 봅니다.

설정 검증: 시작 시점과 핫 리로드 시점에, allowed_backends의 이름이 어떤 backends[].name과도 일치하지 않으면 경고를 출력합니다. 하드 에러가 아니므로, 백엔드 이름을 변경해도 운영자가 해당 키를 갱신하기 전까지 라우터가 멈추지 않습니다.

Admin API: allowed_backends는 생성, 업데이트, 조회, 목록 엔드포인트를 통해 왕복합니다. 생성 시 필드가 없으면 제한 없음이 기본값이고 업데이트 시 null(없음)은 목록을 그대로 두며 빈 배열은 모든 제한을 해제하고 비어 있지 않은 배열은 목록을 교체합니다. 런타임 키는 영속화 파일을 통해 이 필드를 보존합니다.

Anthropic API 헤더

/anthropic/v1/messages, /anthropic/v1/messages/count_tokens, /anthropic/v1/models는 같은 키별 백엔드 및 모델 정책을 적용합니다.

  • Bearer: Authorization: Bearer <key>는 키의 allowed_backends 및 allowed_models 정책을 제공합니다.
  • Anthropic 네이티브 헤더: Bearer 기반 AuthContext가 없으면, 라우터의 클라이언트 키와 일치하는 유효한 x-api-key가 같은 정책을 제공합니다.
  • Permissive 모드: 키가 없거나 잘못된 요청은 위 optional-auth 동작과 동일하게 키별 정책 없이 통과합니다.

Batch API: control-plane 빌드에서 POST /v1/batches는 입력 파일을 가져오거나 요청을 지연하고 배치 슬롯을 획득하거나 제공자의 생성 엔드포인트에 접속하기 전에 백엔드를 선택하고 allowed_backends를 적용합니다. 승인 후 백엔드를 다시 선택하며, 설정 리로드 도중 백엔드 이름, URL, 인증 정보 등이 바뀌었으면 요청을 거부합니다.

키별 데이터 상주 제어

allowed_regions는 클라이언트 키를 운영자가 정의한 backends[].region_name 태그가 목록에 포함된 백엔드로 제한합니다. 데이터 상주 제어이며, 라우터는 운영자가 작성한 두 문자열을 비교할 뿐 URL, 제공자 계정, AWS 리전 식별자에서 리전을 추론하지 않습니다. 태그는 [A-Za-z0-9._-]로 이루어진 1자 이상 64자 이하이며, 잘못된 값은 설정 로드 실패로 이어집니다.

backends:
  - name: openai-eu
    type: openai
    region_name: eu                 # 운영자가 정의한 상주 태그
    api_key: "${OPENAI_EU_API_KEY}"
    models: [gpt-5.6-terra]

  - name: vllm-us
    type: vllm
    url: http://vllm-us.internal:8000
    region_name: us
    models: [qwen3]

api_keys:
  api_keys:
    - key: "${EU_TENANT_KEY}"
      id: "key-eu-tenant"
      user_id: "tenant-eu"
      organization_id: "org-main"
      allowed_regions: [eu]         # eu 태그가 붙은 백엔드로만 라우팅할 수 있습니다

동작 방식:

  • 비어 있음 / 없음 ⇒ 제한 없음이며 allowed_backends는 이전과 똑같이 동작합니다.
  • 비어 있지 않음 ⇒ 유효 백엔드 집합은 region_name이 목록에 있는 모든 백엔드입니다. 정확히 일치하며 대소문자를 구분합니다. region_name이 없는 백엔드는 리전 미상이며 절대 조건을 만족하지 않습니다.
  • 두 목록 모두 설정 ⇒ 교집합입니다. 백엔드는 allowed_backends를 만족하면서 allowed_regions 태그 중 하나를 가져야 합니다.
  • 교집합이 빔 ⇒ fail closed입니다. 요청은 403 Forbidden(permission_error, model_not_permitted로 보고)으로 거부되고 /v1/models는 아무것도 나열하지 않습니다. 상주 제약을 만족할 수 없을 때 라우터가 모든 백엔드로 대체하는 일은 없습니다.
  • 모든 디스패치 경로 ⇒ 유효 목록이 재시도, 폴백 홉과 헤지, 이미지, 오디오, 임베딩, 리랭크, 스파스 엔드포인트, tokenize/detokenize, Responses와 네이티브 Responses 수명 주기 호출, Anthropic Messages와 count_tokens, 실시간, 배치, 파인튜닝, control-plane 예산 자격 판정에서 allowed_backends를 대체합니다. 홉이 리전을 벗어날 수 없습니다.
  • 목록 ⇒ /v1/models, /v1/models/extended, /v1/models/{model}, /anthropic/v1/models가 같은 유효 목록을 적용합니다.
  • 캐시 격리 ⇒ 상주 태그가 다른 두 키는 allowed_backends 목록이 같더라도 응답 캐시 항목을 공유하지 않습니다.
  • 설정 검증 ⇒ allowed_regions의 태그를 어떤 백엔드도 선언하지 않으면 시작 시 경고만 출력하므로 오타로 라우터가 멈추지 않습니다. 다만 그 키는 교집합이 비어 아무것도 라우팅하지 못합니다.

allowed_regions는 config.yaml 또는 외부 키 파일에서만 작성할 수 있습니다. Admin 키 API와 WebUI에서는 이 필드를 설정하거나 표시할 수 없으며, 런타임 영속화 파일은 작성 경로가 아닙니다. 이 파일은 활성 키가 가진 값을 그대로 담지만, Admin API로 생성한 키에서는 항상 비어 있습니다. Hub에서 동기화된 키는 이 필드를 전달하지 않으므로, 로컬 키 항목이 값을 설정하지 않으면 Hub 관리 키는 상주 제한을 받지 않습니다.

키별 모델 접근 제어

allowed_models는 클라이언트 키를 모델 이름의 일부 집합으로 제한합니다. allowed_backends가 키가 라우팅할 수 있는 백엔드를 제어한다면 allowed_models는 키가 지정할 수 있는 모델을 제어합니다. 배포가 확장·축소되면서 백엔드 이름은 바뀌어도 모델 이름은 유지되므로 멀티테넌트 라우터에서는 모델 수준 제한이 지속성 있는 제어 수단입니다. 두 목록은 독립적이며 함께 설정할 수 있습니다.

동작 방식:

  • 비어 있음 / 없음 ⇒ 제한 없음(키는 게시된 모든 모델을 사용할 수 있음).
  • 비어 있지 않음 ⇒ 모델 이름 허용 목록. 주 이름과 별칭은 별개의 이름이므로 각각 따로 검사합니다. 매칭은 정확히 일치하며 대소문자를 구분합니다.
  • 허용되지 않은 모델 ⇒ 403 Forbidden(permission_error). 검사는 폴백 체인 진입 전에 실행되므로 제한된 키가 허용되지 않은 모델을 요청해 폴백 체인에 도달할 수 없습니다.
  • Anthropic Messages ⇒ /anthropic/v1/messages와 /anthropic/v1/messages/count_tokens는 Authorization: Bearer 및 네이티브 x-api-key 인증에 같은 모델 검사를 적용합니다.
  • 모델 목록 ⇒ /v1/models, /v1/models/extended, /anthropic/v1/models는 현재 게시된 모델 이름과 allowed_models의 교집합만 반환합니다. GET /v1/models/{model}은 키가 사용할 수 없는 모델에 대해 404 Not Found를 반환합니다.
api_keys:
  mode: blocking
  api_keys:
    - key: "${PARTNER_KEY}"
      id: "key-partner-1"
      user_id: "partner-acme"
      organization_id: "org-acme"
      scopes: [read, write]
      allowed_models: [gpt-5.6-terra, gpt-5.6-sol]   # 이 모델만 사용 가능

설정 검증: 시작 시점과 핫 리로드 시점에 현재 게시되지 않은 이름이 allowed_models에 있으면 하드 에러가 아닌 경고를 출력합니다.

Admin API: allowed_models는 allowed_backends와 동일한 null / 빈 배열 / 비어 있지 않은 배열 업데이트 규칙으로 생성, 업데이트, 조회, 목록 엔드포인트를 통해 왕복하며 런타임 키는 영속화 파일을 통해 이 필드를 보존합니다.

Batch API: POST /v1/batches는 로컬 allowed_models와 Hub 티어의 model_allowlist를 교집합으로 적용합니다. Hub 허용 목록이 명시적인 빈 목록이면 모든 모델을 거부합니다. 라우터는 모든 인라인 Anthropic 요청의 모델을 검사하고, 제한된 OpenAI 스타일 배치에서는 제공자 소유 JSONL 입력을 스트리밍하면서 모든 요청 모델을 지연이나 승인 전에 검사합니다. 제한된 접근에서 형식이 잘못되었거나 모델이 누락된 입력은 거부하며, 제한이 없는 Anthropic 입력은 형식이 잘못되었더라도 제공자가 검증할 수 있도록 전달합니다.

ROUTER 프런트엔드 모드: AppProxy ROUTER 워커에서는 키별 모델 가시성이 주된 접근 제어 차원입니다. 제시된 베어러 토큰의 SHA-256 해시로 키를 매칭하며 expires_at, 노드당 rate_limit(클러스터 전체 상한은 rate_limit × live_nodes), 소스 IP 허용 목록을 설정할 수 있습니다. 라우터 측 게이트는 appproxy::router::keys에 있습니다.

키별 토큰 예산

max_tokens_budget는 키가 롤링 budget_window(1d, 7d, 30d 중 하나, 기본값 30d) 동안 소비할 수 있는 토큰(입력과 출력의 합)을 제한합니다. 창의 사용량이 예산에 도달하면 그 키의 요청은 429 Too Many Requests(rate_limit_error)로 거부되며, /anthropic 마운트에서는 Anthropic 오류 형식으로 응답합니다. 기본값인 미설정은 예산이 없다는 뜻이며, 예산이 없는 키는 추적하지 않습니다.

api_keys:
  mode: blocking
  api_keys:
    - key: "${TEAM_KEY}"
      id: "key-team-1"
      max_tokens_budget: 5000000   # 창당 토큰 수
      budget_window: 7d

동작 방식:

  • 사용량: 요청 완료 시 라우터가 키에 대해 계측하는 토큰 사용량이며, 키별 사용량 통계와 사용량 내보내기가 기록하는 값과 같습니다. 창보다 오래된 사용량은 창 길이의 1/120 단위로 빠져나갑니다.
  • 동시성: 승인 시점에 요청 프롬프트의 추정치(본문 길이의 4분의 1을 토큰 수로 사용)를 응답 본문이 끝날 때까지 예약하고, 기록된 사용량과 남은 예약의 합이 예산에 도달하면 거부합니다. 거의 소진된 예산을 동시 요청들이 한꺼번에 통과할 수 없으며, 초과분은 이미 처리 중인 요청의 사용량으로 제한됩니다.
  • 리로드: 예산과 창은 매 요청마다 현재 키에서 읽으므로, 핫 리로드는 다음 요청부터 적용됩니다. 창을 바꾸면 기록된 사용량은 유지되고 새 창에 걸쳐 빠져나갑니다.
  • 재시작: api_keys.persistence_file을 설정하면 창을 30초마다 <persistence_file>.budget.json(모드 0600)에 기록하고 시작 시 복원하므로, 재시작으로 잃는 사용량은 최대 30초분입니다. 설정하지 않으면 창은 메모리에만 있고 재시작하면 새로 시작합니다.
  • 복제본: 창은 프로세스별입니다. 로드 밸런서 뒤에서는 각 복제본이 자신이 처리하는 트래픽에 대해 예산을 적용합니다.
  • 검증: 1d, 7d, 30d가 아닌 budget_window나 0인 max_tokens_budget은 로드 오류입니다.
  • 범위: 예산은 api_keys, api_keys_file, 영속화 파일의 키에 적용됩니다. Admin API는 아직 예산을 설정하지 않으며 업데이트 시 기존 예산을 유지하고, Continuum Hub에서 동기화된 키에는 예산이 없으며, 허브의 티어 한도가 적용됩니다.

데이터 플레인 예산

budgets: 섹션은 허브 없이도 동작하는 토큰 및 USD 비용의 롤링 윈도 쿼터를 추가합니다. max_tokens_budget은 이 섹션의 per_api_key 스코프에 대한 명시적 키별 재정의입니다. 이 값을 설정한 키는 해당 예산의 적용을 받으며, 플릿 기본값으로 다시 과금되지 않습니다. 값을 설정하지 않은 키는 budgets.scopes.per_api_key를 따르고, 공유 스코프인 per_model과 global은 모든 키에 계속 적용됩니다.

예산 엔진은 rate_limiting의 tokens_per_minute, tokens_per_day와 별개의 검사입니다. 두 설정은 쿼터가 아니라 리키 버킷 속도입니다. 요청은 속도 검사와 해당되는 모든 예산 윈도를 통과해야 합니다.

호출자 자신의 키별 수준만 x-budget-api-key-* 헤더로 보고합니다. 모델별 및 전역 수준은 다른 테넌트도 사용하는 버킷이므로 공개하지 않습니다. 전체 설정, 거부 봉투, 저장소 모드, 관리 엔드포인트, 메트릭은 예산 (Budgets)에서 설명합니다.

클라이언트 자격 증명 헤더

아래에 나열한 경로에서, 라우터는 아웃바운드 요청을 만들기 전에 인바운드 요청에 실린 자격 증명 헤더를 제거합니다. 따라서 호출자의 자격 증명이 백엔드로 중계되지 않습니다. 대상은 chat-completions 경로(스트리밍, 비스트리밍, Unix 소켓, 그리고 프로바이더가 바뀌는 홉을 포함한 모든 폴백 홉), HTTP와 Unix 소켓 양쪽의 Anthropic Messages 경로 /anthropic/v1/messages, /anthropic/v1/messages/count_tokens, 이미지 생성 및 이미지 변형 경로, 그리고 /v1/embeddings 뒤의 Gemini 멀티모달 임베딩 네이티브 디스패치입니다.

제거되는 헤더는 다음과 같습니다.

헤더 제거하는 이유
authorization 호출자의 라우터 키 또는 베어러 토큰. 백엔드는 자신의 자격 증명으로 인증합니다
x-api-key 인바운드 쪽에서는 라우터 키와 동등하고 아웃바운드 쪽에서는 Anthropic 네이티브 자격 증명입니다
api-key Azure OpenAI 자격 증명 형식
x-goog-api-key Gemini 자격 증명. 네이티브 경로가 설정된 키를 직접 주입합니다
cookie, set-cookie 세션 자격 증명
proxy-authorization 프록시 자격 증명
x-auth-token, x-access-token 범용 베어러 토큰 전달 헤더

백엔드의 자격 증명은 오직 그 백엔드 자신의 설정에서만 옵니다. api_key, auth 블록(auth.type: basic과 OAuth 전략 포함), 또는 Continuum Hub 볼트가 해당 프로바이더용으로 동기화한 자격 증명입니다. 헤더 이름 비교는 대소문자를 구분하지 않으며 제거는 무조건 적용됩니다. 선택된 백엔드가 자격 증명을 갖고 있는지 여부를 보지 않으므로, 폴백 홉이 한 프로바이더의 자격 증명을 다른 프로바이더로 실어 나를 수 없습니다.

따라서 자격 증명을 설정하지 않은 백엔드에는 Authorization 헤더가 아예 전송되지 않습니다. 인증이 필요 없는 로컬 엔진은 이를 그대로 받아들이고 클라우드 프로바이더는 401을 반환합니다. 이 401은 호출자의 키를 대신 중계할 이유가 아니라, 해당 백엔드 항목에 api_key가 빠져 있다는 정확한 신호입니다.

자격 증명이 아닌 헤더는 평소대로 전달됩니다. user-agent, x-request-id, 추적 헤더, 그리고 클라이언트가 붙인 커스텀 헤더는 그대로 백엔드에 도달합니다. 화이트리스트가 아니라 거부 목록 방식입니다.

Anthropic, Gemini, Bedrock 타입 백엔드 디스패치 경로, Responses API 경로, Realtime 경로, chat-completions 경로, Anthropic 네이티브 인바운드 엔드포인트, 이미지 엔드포인트, 멀티모달 임베딩 디스패치 모두 이 거부 목록을 적용합니다. 이미지 변형 경로는 거부 목록 위에 openai-* / x-* 허용 목록을 유지하고 이미지 편집 경로는 openai-*와 x-request-id만 전달합니다.

업스트림 자격 증명 설정

클라이언트의 Authorization 헤더는 업스트림으로 전달되지 않으므로 업스트림 인증이 필요한 백엔드에는 라우터 설정에서 자격 증명을 직접 지정해야 합니다.

backends:
  - name: partner-openai
    type: openai
    url: "https://api.partner.example/v1"
    api_key: "${PARTNER_API_KEY}"

업스트림 백엔드가 모든 요청에 401을 반환한다면 해당 백엔드 항목에 api_key 또는 auth가 설정되어 있는지 확인하세요.

Anthropic 최종 고객별 귀속

Anthropic 사용자 프로필(user profile)을 사용하면, 여러 최종 고객을 서비스하는 조직이 각 요청을 특정 고객에게 귀속시킬 수 있습니다. 그러면 안전 제재나 차단이 조직 전체가 아니라 해당 프로필에만 적용됩니다. 라우터 뒤에서는 조직이 운영자의 것이고, 호출자는 운영자의 테넌트입니다. 테넌트가 프로필을 직접 고를 수 있다면, 자신의 트래픽과 위반 기록을 다른 테넌트의 프로필에 귀속시킬 수 있습니다. Claude in Amazon Bedrock과 Google Cloud는 ID 형식만 검사하므로, 형식만 맞으면 어떤 ID든 받아들입니다.

따라서 귀속 값은 라우터가 결정합니다. 운영자가 키를 신뢰된 게이트웨이로 지정하지 않는 한, 클라이언트는 이 값을 선택할 수 없습니다.

라우터가 제거하는 값

모든 아웃바운드 경로와 모든 백엔드 타입에서 라우터는 다음을 제거합니다.

  • 클라이언트의 anthropic-user-profile-id 헤더.
  • 모든 백엔드 타입으로 보내는 본문의 최상위 user_profile_id 필드. 클라이언트 값을 제거한 뒤, 운영자가 request_extensions.body.defaults 또는 request_extensions.body.overrides에 설정한 값은 다시 적용합니다. 네이티브 /anthropic/v1/messages와 count_tokens 인그레스, chat-completions와 Responses 변환 경로, 그리고 Anthropic Message Batch가 모두 대상입니다. 배치에서는 본문의 최상위와 각 요청의 params 안에 있는 값을 모두 제거합니다.
  • 클라이언트가 보낸 x-continuum-resolved-user-profile-id. 이 헤더는 인그레스에서 결정된 값을 디스패치 단계로 전달하는 라우터 내부 헤더입니다.
  • 두 헤더의 밑줄 표기(anthropic_user_profile_id, x_continuum_resolved_user_profile_id, 대소문자 무관). 일부 프록시와 애플리케이션 서버는 _를 -로 읽기 때문에, 그대로 두면 업스트림이 밑줄 표기를 귀속 헤더로 받아들일 수 있습니다.

프로필이 전송되는 위치

백엔드 전달 방식
anthropic (Claude API, Claude Platform on AWS, Microsoft Foundry) anthropic-user-profile-id 헤더
기본 mantle 엔드포인트의 bedrock (Claude in Amazon Bedrock) 본문 최상위 user_profile_id 필드
endpoint_type: runtime 또는 converse인 bedrock 전송하지 않음 (기존 InvokeModel, Converse 연동은 프로필을 지원하지 않음)
continuumrouter anthropic-user-profile-id 헤더. 다운스트림 라우터가 이 라우터의 키를 신뢰된 게이트웨이 키로 지정했다면 그 값을 적용함. 모든 키에서 결정된 프로필 ID가 모든 continuumrouter 백엔드로 전송되므로, 같은 Anthropic 조직에서 운영하는 라우터만 연결해야 함.
그 밖의 타입 전송하지 않음. 단, 백엔드가 request_extensions.headers에 정적 프로필을 선언한 경우는 예외

count_tokens와 /v1/batches로 생성하는 Message Batch에도 같은 규칙이 적용됩니다. Message Batch에서는 Anthropic이 헤더를 배치 안의 모든 요청에 적용합니다.

설정

api_keys:
  mode: blocking
  api_keys:
    - key: "${TENANT_A_KEY}"
      id: "tenant-a"
      user_id: "tenant-a"
      organization_id: "acme"
      anthropic_user_profile_id: "uprof_011CZkZCu8hGbp5mYRQgUmz9"
    - key: "${GATEWAY_KEY}"
      id: "partner-gateway"
      user_id: "partner"
      organization_id: "partner"
      anthropic_user_profile_passthrough: true   # trusted: keep the caller's header
  • anthropic_user_profile_id: 이 키의 트래픽을 귀속시킬 프로필입니다. 값은 uprof_ 뒤에 ASCII 영문자, 숫자, _, -가 1자 이상 128자 이하로 와야 합니다. 설정 파일에 직접 적은 값의 형식이 잘못되면 설정 로드 오류가 됩니다. 외부 키 파일의 잘못된 값은 로그로 남기고 전송하지 않습니다.
  • anthropic_user_profile_passthrough (기본값 false): 이 키로 들어온 요청은 클라이언트가 보낸 anthropic-user-profile-id 헤더를 그대로 사용합니다. 키를 사용하는 호출자가 하나뿐이고, 그 호출자가 자체 최종 고객을 직접 귀속시키는 게이트웨이인 경우에만 켜세요. 클라이언트 값의 형식이 잘못되었거나 헤더가 여러 번 오면 무시하고 키 자체의 프로필을 적용합니다.

우선순위

라우터는 요청마다 최대 하나의 값을 다음 순서로 선택해 전송합니다.

  1. 키가 신뢰된 게이트웨이 키이고 형식이 올바른 경우, 클라이언트의 헤더 값.
  2. 키의 anthropic_user_profile_id.
  3. 백엔드의 정적 프로필. 단일 테넌트 백엔드에서 request_extensions.headers.anthropic-user-profile-id로 지정합니다. 값은 올바른 프로필 ID여야 하며, 본문에서 프로필을 읽는 bedrock 백엔드에서는 거부됩니다. bedrock에서는 키별로 프로필을 설정하세요.
  4. 없음. Anthropic은 이 요청을 조직 자체의 사용량으로 집계합니다.

인식된 API 키가 없는 요청(permissive 모드)에는 백엔드의 정적 프로필만 적용됩니다. Continuum Hub에서 동기화된 키는 두 모드 모두에서 인식된 키로 취급합니다(Continuum Hub 키 참고).

Admin API와 캐시

  • Admin API: 두 필드 모두 POST /admin/api-keys, PUT /admin/api-keys/{id}, 조회 및 목록 엔드포인트에서 그대로 주고받으며, 런타임 키는 이 값을 영속화합니다. 업데이트 시 필드를 생략하면 기존 값이 유지되고, ""를 보내면 프로필이 지워집니다. 형식이 잘못된 ID는 400 Bad Request로 거부됩니다.
  • 요청 중복 제거: 결정된 프로필은 중복 제거 키에 포함됩니다. 따라서 서로 다른 최종 고객에게 귀속된 동일한 요청 두 개가 하나의 업스트림 생성을 공유하지 않습니다.
  • 응답 캐시: 변경되지 않습니다. 캐시는 이미 API 키 단위로 분리되어 있고, 캐시 히트는 업스트림 요청을 만들지 않으므로 귀속할 대상이 없습니다.
  • 라우터가 직접 보내는 호출: 헬스 체크, Hub 프로브, 스마트 라우팅 분류기, 가드레일 호출에는 결정된 프로필이 실리지 않습니다. 다만 다른 확장 헤더와 마찬가지로 백엔드의 정적 request_extensions 프로필은 실립니다.

Continuum Hub 키

Continuum Hub에서 동기화된 키도 Hub에서 설정한 anthropic_user_profile_id와 anthropic_user_profile_passthrough를 가질 수 있습니다. 라우터는 로컬에 설정한 키와 똑같이 위의 우선순위에 따라 이 값을 적용합니다.

  • Capability: 라우터는 anthropic_user_profile_v1을 광고합니다. Hub는 이 값을 광고한 라우터에만 두 필드를 보내고, 이전 라우터에는 두 필드를 제거해 보냅니다. 이전 라우터의 Hub 키에는 백엔드의 정적 프로필이 있을 때 그 값만 전송됩니다.
  • 인증 모드: blocking 모드에서는 Hub 키가 요청을 인증하면서 프로필을 함께 전달합니다. permissive 모드에서는 정책 적용 단계에서 키가 일치하면 그 키의 프로필이 적용됩니다. 같은 토큰에 로컬 키도 일치하면 로컬 키가 우선합니다.
  • 검증: 형식이 잘못된 Hub 프로필 ID는 전송하지 않으며, 이 경우 백엔드의 정적 프로필이 적용됩니다.
  • 영속화: control_plane.policy.persist_key_table: true이면 두 필드가 키 테이블 파일에 저장되므로, Hub 장애 중에 재시작한 라우터도 값을 복원합니다. 프로필을 설정하거나 지우면 파일을 즉시 다시 씁니다. 이 기능 이전의 라우터 릴리스는 프로필이 담긴 파일을 복원하지 못하므로, 복원 없이 시작해 Hub 응답을 기다립니다.

가드레일: PII 탐지 및 마스킹

PII 탐지는 여러 가드레일 프로바이더 중 하나입니다. 전체 가드레일 시스템(개념, 여덟 가지 프로바이더, 스트리밍 게이팅, 관리자 제어, 메트릭, 임계값 튜닝 워크플로)은 가드레일 가이드를 참고하세요.

pii 가드레일 프로바이더는 요청 프롬프트와 모델 응답에서 개인 식별 정보(PII)와 고가치 비밀 값을 탐지한 뒤 해당 부분을 그 자리에서 마스킹하거나 요청을 차단합니다. OWASP LLM02(민감 정보 노출)에 대응합니다. 분류 전용 프로바이더와 달리 이 프로바이더의 주된 동작은 콘텐츠 변환입니다. 일치한 구간을 자리 표시자로 치환하고 정제된 텍스트를 그대로 흘려보냅니다.

내장 스캐너는 외부 의존성 없이 로컬에서 동작하며 다음 항목들을 탐지합니다.

  • 기본 식별자 및 시크릿: 이메일, 미국 사회보장번호(ssn), 한국 주민등록번호(kr_rrn), 외국인등록번호(kr_frn), 사업자등록번호(kr_brn), AWS 액세스 키 ID, PEM 개인 키 블록, Bearer / sk- 형식 API 키.
  • 신용카드 / PAN 번호 (credit_card): Luhn 체크섬을 통과하는 표준 카드 숫자 배열(13~19자리 연속 숫자, 마지막 그룹이 1~4자리인 4자리 단위 그룹, 4-6-5 및 4-6-4 그룹, 또는 4111 111111111111처럼 4자리 이상인 2~3개 그룹)을 탐지합니다. 인접한 짧은 숫자나 날짜·전화번호가 카드 번호 하나로 잘못 묶이지 않도록 2-4-4-4, 3-4-4-4, 1-4-4-4-3, 2자리씩 8묶음 같은 비표준 짧은 그룹 형태는 카드 번호로 보지 않습니다.
  • 전화번호 (phone): 서식이 있는 국내외 전화번호, + 또는 00 국가 코드 뒤에 1~2자리 그룹으로 적은 번호(예: +33 1 74 53 03 18, +7 495 301-77-50), 러시아·벨기에·스위스·튀르키예 국내 번호, 영국·인도의 비정규 그룹 번호(01267-1-5398, 1800-87-6141), 슬래시 구분자를 쓰는 독일 지역 번호(030/12345678), 전화 표시 단어 뒤에 오는 프랑스 국내 번호 및 구분자 없는 중국 휴대전화 번호(Tél. : 01 42 68 53 00, Mobile: 13800138000)를 탐지합니다. 자세한 패턴 규칙은 가드레일 문서의 PII 탐지 및 마스킹 절을 참고하십시오.
  • 단어 경계 처리: 단어 경계는 ASCII((?-u:\b)) 기준이므로 한글이나 한자에 바로 붙여 쓴 값(주민번호900101-1234567입니다, 메일kim@x.io입니다)도 정상 탐지되며, 영문자·숫자·_에 바로 붙은 값만 제외됩니다.
  • 구간 중첩 시 우선순위:
    • 한국 식별자(kr_rrn, kr_frn, kr_brn)와 운영자 정의 custom_entities는 credit_card 및 phone보다 우선합니다. 겹치는 credit_card나 phone 구간은 Luhn 체크섬을 통과하더라도 제거됩니다.
    • 유효한 카드 번호(첫 자리가 0이 아닌 ISO/IEC 7812 발급자 번호) 범위 안에 완전히 포함되는 phone 구간은 제거됩니다. 따라서 phone: block과 credit_card: mask를 함께 쓸 때 카드 번호가 전화번호로 오인되어 차단되지 않고 정상 마스킹됩니다.
    • 0으로 시작하는 숫자열은 결제 카드 번호가 아니므로, 0에서 시작하는 전화번호 구간은 뒤쪽 그룹에서 실제 카드 번호가 시작하지 않는 한 전화번호로 유지됩니다. 실제 카드 번호 앞의 짧은 토큰에서 시작해 카드 번호 내부로 이어지는 전화번호 구간은 카드 번호 시작점 앞에서 잘리며(남은 숫자가 7자리 미만이면 제거), (03) 4111 1111 1111 1111처럼 앞 토큰과 카드 번호가 합쳐져 온전한 전화번호 형태를 이루는 경우에만 전화번호 구간을 유지합니다.
  • 외부 NER 분석기 연동 (선택 사항): 정교한 개체명(NER) 기반 PII 탐지가 필요하면 Microsoft Presidio 호환 분석기를 연결해 내장 스캐너 결과와 병합할 수 있습니다. on_error: fail_open 설정에서 외부 분석기에 연결할 수 없는 경우, 라우터는 내장 스캐너 탐지를 계속 수행하면서 guardrail_degraded_total{kind="external_unavailable"} 메트릭을 증가시킵니다.

탐지된 각 엔티티 유형은 다음 동작에 매핑됩니다:

  • mask(기본값): 일치한 구간을 자리 표시자(예: [REDACTED:EMAIL])로 치환하고 정제된 텍스트로 진행합니다(Transform 판정).
  • block: 해당 엔티티 유형이 존재하면 요청/응답을 차단합니다.
  • allow: 해당 엔티티 유형을 무시합니다(마스킹도 차단도 하지 않음).

탐지된 원본 값은 로그에 기록되지 않습니다. 감사를 위해 엔티티 유형과 개수만 기록됩니다.

guardrails:
  enabled: true
  mode: enforce
  providers:
    - name: pii-redaction
      type: pii
      enabled: true
      # 두 단계 모두에서 실행(기본값): 백엔드로 가기 전 프롬프트와 클라이언트로
      # 가기 전 응답을 모두 정제합니다.
      stages: [input, output]
      options:
        # `actions`에 없는 엔티티 유형에 적용할 동작. 기본값: mask.
        default_action: mask
        # 엔티티 유형별 동작 재정의.
        actions:
          email: mask
          phone: mask
          ssn: block
          kr_rrn: block
          kr_frn: block
          kr_brn: mask
          credit_card: block
          aws_access_key: block
          private_key: block
          api_key: block
          employee_id: mask
        # 마크다운/HTML 렌더러가 `<REDACTED:...>`를 지우지 않도록 꺾쇠괄호 없는
        # 템플릿을 권장합니다. 기본값: "<REDACTED:{TYPE}>".
        placeholder_format: "[REDACTED:{TYPE}]"
        # 선택적 운영자 정의 정규식 엔티티(최대 64개, 패턴당 최대 1 KiB).
        # `config validate`와 핫 리로드 시 검증됩니다.
        custom_entities:
          - name: employee_id
            pattern: '(?-u:\b)EMP-\d{6}(?-u:\b)'
            context_words: ["employee", "사번"]
            context_window: 64
        # 선택적 외부 Presidio 호환 분석기(내장 스캐너는 항상 실행되며, 외부
        # 구간이 병합됩니다).
        # external:
        #   endpoint: "http://presidio-analyzer:3000/analyze"
        #   language: en
        #   entities: ["PERSON", "LOCATION", "IBAN_CODE"]
      on_error: fail_open

옵션:

속성 타입 기본값 설명
default_action string mask actions에 없는 엔티티 유형에 적용할 동작: mask, block, allow.
actions map {} 엔티티 유형별 동작 재정의. 키는 엔티티 유형(email, phone, ssn, kr_rrn, kr_frn, kr_brn, credit_card, aws_access_key, private_key, api_key, 또는 custom_entities[].name).
placeholder_format string <REDACTED:{TYPE}> 마스킹 구간 템플릿. {TYPE}는 대문자 엔티티 유형으로 치환됩니다. 마크다운 렌더러가 <...>를 제거할 수 있으므로 "[REDACTED:{TYPE}]" 사용을 권장합니다.
context_words map 또는 array 내장 한국어/영어 단어 하이픈 없는 13자리 kr_rrn 또는 kr_frn 후보 주변(context_window 바이트 이내)에 필요한 문맥 단어({"kr_rrn": [...], "kr_frn": [...]} 또는 공통 배열).
kr_rrn_context_words array - 하이픈 없는 13자리 kr_rrn 전용 문맥 단어 목록 재정의.
kr_frn_context_words array - 하이픈 없는 13자리 kr_frn 전용 문맥 단어 목록 재정의.
context_window integer 64 하이픈 없는 kr_rrn/kr_frn(또는 자체 context_window가 없는 사용자 정의 엔티티) 양옆에서 문맥 단어를 탐색할 바이트 범위.
custom_entities array [] 운영자 정의 정규식 엔티티({name, pattern, context_words?, context_window?}). 최대 64개, 각 pattern은 최대 1,024바이트. config validate 및 핫 리로드 시 검증됩니다.
external.endpoint string - Presidio 호환 분석기 URL. 프로바이더 상위 endpoint로 폴백합니다. 구간의 start/end 오프셋은 Presidio가 보고하는 대로 유니코드 코드 포인트로 읽으므로, UTF-8 바이트나 UTF-16 오프셋을 보고하는 분석기는 호환되지 않습니다. 뒤집혔거나 텍스트 끝을 넘는 구간은 on_error를 따릅니다. fail_closed는 검사를 실패로 처리하고 fail_open은 유효한 구간을 마스킹하고 guardrail_degraded_total{kind="external_invalid_span"}를 증가시킵니다.
external.entities array [] 외부 분석기를 이 엔티티 유형으로 제한(비어 있으면 분석기 기본값).
external.language string en 분석기에 전달하는 언어 힌트.

한국 식별자 형식 및 충돌 안내 (공개 사양 구조만 검사하며 체크섬 검증은 수행하지 않음):

엔티티 / 식별자 형식 동작
kr_rrn (주민등록번호) YYMMDD-NNNNNNN 또는 YYMMDD NNNNNNN (월 01-12, 일 01-31, 7번째 자리 1-4); 문맥 단어가 동반된 13자리 YYMMDDNNNNNNN kr_rrn으로 탐지
kr_frn (외국인등록번호) YYMMDD-NNNNNNN 또는 YYMMDD NNNNNNN (월 01-12, 일 01-31, 7번째 자리 5-8); 문맥 단어가 동반된 13자리 YYMMDDNNNNNNN kr_frn으로 탐지
kr_brn (사업자등록번호) NNN-NN-NNNNN (3-2-5자리) kr_brn으로 탐지
법인등록번호 NNNNNN-NNNNNNN (6-7자리) 앞 6자리가 유효한 YYMMDD(월 01-12, 일 01-31)이고 7번째 자리가 1-4이면 kr_rrn과 충돌하여 kr_rrn으로 탐지됨

이 프로바이더는 기본적으로 두 단계 모두에 참여합니다. 여러 엔티티 유형이 동시에 존재하면 차단 엔티티가 마스킹보다 우선합니다(가장 심각한 판정 우선). 차단 엔티티가 있으면 판정은 차단이고 그렇지 않고 마스킹된 항목이 있으면 Transform이 정제된 텍스트를 담아 반환되며 둘 다 없으면 콘텐츠는 허용됩니다.

WebUI 섹션

선택적 webui 섹션은 내장 브라우저 기반 관리 인터페이스를 제어합니다. WebUI는 바이너리에 컴파일되어 관리자 인증으로 보호되는 정적 에셋으로 제공됩니다.

webui:
  enabled: true        # WebUI 활성화 또는 비활성화 (기본값: true)
  path_prefix: /webui  # URL 경로 접두사 (기본값: /webui)

구성 속성:

속성 타입 기본값 설명
enabled boolean true WebUI 활성화 또는 비활성화
path_prefix string /webui URL 경로 접두사. /로 시작해야 하며 ..을 포함해서는 안 됩니다.

webui가 생략되면 기본값이 적용됩니다: WebUI는 /webui에서 활성화됩니다. WebUI를 비활성화하려면:

webui:
  enabled: false

브라우저 인터페이스 사용에 대한 전체 가이드는 내장 WebUI를 참조하세요.

Admin 섹션

admin 섹션은 인증 및 통계 수집을 포함한 Admin REST API를 설정합니다.

인증

Admin 인증은 실패 시 차단합니다. admin.auth 블록이 없으면 모든 Admin API 요청이 401 Unauthorized를 반환하고, method가 없는 블록은 기본적으로 api_key를 사용합니다. method: none은 격리된 개발 환경에서만 명시적으로 선택하세요.

admin:
  auth:
    method: bearer             # none, bearer, basic, ip_whitelist, api_key
    bearer_token: "${ADMIN_TOKEN}"

method: api_key는 일반 /v1 인증과 Admin 키 관리가 공유하는 API 키 저장소를 사용하며 Authorization: Bearer와 X-API-Key를 모두 허용하고 required_scope(기본값 admin)를 강제합니다. 키 비활성화·회전·만료·삭제는 즉시 반영됩니다. admin.auth는 시작 시 소유되며 런타임 설정 엔드포인트는 변경을 거부합니다. 대신 원본 설정을 편집하고 라우터를 재시작하세요.

모든 인증 옵션에 대해서는 Admin REST API 레퍼런스를 참조하세요.

통계 수집

admin.stats 하위 섹션은 요청 메트릭 수집 및 영속화를 제어합니다. 통계 수집은 기본적으로 활성화되어 있습니다.

admin:
  stats:
    enabled: true                # 수집 활성화/비활성화 (기본값: true)
    retention_window: 24h        # 윈도우 쿼리용 링 버퍼 보존 기간 (기본값: 24h)
    token_tracking: true         # 응답 본문에서 토큰 사용량 파싱 (기본값: true)
    persistence:
      enabled: true              # 재시작 시 통계 영속 활성화 (기본값: true)
      path: ./data/stats.json    # 스냅샷 파일 경로 (기본값: ./data/stats.json)
      snapshot_interval: 5m      # 주기적 스냅샷 간격 (기본값: 5m)
      max_age: 7d                # 시작 시 이보다 오래된 스냅샷은 폐기 (기본값: 7d)

설정 속성:

속성 타입 기본값 설명
enabled boolean true 통계 수집 활성화 또는 비활성화
retention_window string 24h 윈도우 쿼리를 위한 링 버퍼 보존 기간
token_tracking boolean true 토큰 사용량 추출을 위한 응답 본문 파싱

영속화 속성:

속성 타입 기본값 설명
persistence.enabled boolean true 재시작 시 통계 영속화 활성화
persistence.path string ./data/stats.json 영속화 스냅샷 파일 경로
persistence.snapshot_interval string 5m 주기적 스냅샷 간격
persistence.max_age string 7d 시작 시 스냅샷 복원 최대 기간

영속화가 활성화되면:

  • 시작 시 라우터는 스냅샷 파일에서 카운터와 링 버퍼 레코드를 복원합니다. 업타임은 항상 0으로 리셋됩니다.
  • 백그라운드 태스크가 설정된 간격으로 원자적(임시 파일 + 이름 변경)으로 스냅샷을 기록합니다.
  • 정상 종료(SIGTERM/SIGINT) 시 최종 스냅샷이 저장됩니다.
  • 누락, 손상, 또는 오래된 스냅샷은 정상적으로 처리됩니다. 라우터는 새 카운터로 시작하고 경고를 로깅합니다.

핫 리로드: retention_window와 token_tracking은 즉시 핫 리로드를 지원합니다. 영속화 설정(path, snapshot_interval, max_age)은 재시작이 필요합니다.

지원되는 기간 형식 (retention_window, snapshot_interval, max_age):

형식 예제 의미
Xs 30s 30초
Xm 5m 5분
Xh 1h 1시간
Xd 7d 7일

전체 엔드포인트 문서는 Admin REST API 레퍼런스: 통계 API를 참조하세요.

공유 주소 공간 백엔드

Admin API는 URL이 차단된 대역으로 해석되는 런타임 백엔드를 거부합니다. 여기에는 공유 주소 공간 100.64.0.0/10(RFC 6598)이 포함됩니다. 이 대역은 통신사급 NAT(carrier-grade NAT)와 Tailscale 같은 오버레이 네트워크에서도 사용됩니다. 이 대역에 백엔드를 두는 운영자는 다음과 같이 명시적으로 허용합니다:

admin:
  allow_shared_address_space_backends: true

기본값은 false입니다. 이 플래그는 시작 시 소유되며 기준 설정 파일에서만 읽습니다. 따라서 Admin API 또는 WebUI 변경으로는 예외를 부여할 수 없습니다. 파일을 편집하고 라우터를 재시작하십시오.

활성화하면 백엔드 기본 URL과 engine_stats.metrics_url에 대해서만 100.64.0.0/10의 일반 IPv4 주소를 허용합니다. 다음은 계속 거부됩니다:

  • 대역 안의 100.100.100.200 클라우드 메타데이터 주소.
  • 대역의 IPv4 매핑(::ffff:100.64.0.1), NAT64(64:ff9b::/96), 6to4(2002::/16) 형태.
  • 사설, 루프백, 링크 로컬, IPv6 고유 로컬 주소를 포함한 다른 모든 차단 대역. Tailscale의 fd7a:115c:a1e0::/48도 여기에 해당합니다.
  • OAuth 엔드포인트, Converse 이미지 가져오기, 내보내기 싱크, 클라이언트가 제공한 파일 URL은 전체 차단 목록을 그대로 적용합니다.

ACP (Agent Communication Protocol) 섹션

acp 섹션은 Agent Communication Protocol 하위 시스템을 설정합니다. ACP는 IDE 및 도구 통합이 stdio를 통한 JSON-RPC 2.0으로 라우터와 통신할 수 있도록 합니다. 기본값은 비활성화입니다.

ACP를 사용하려면 --mode stdio로 라우터를 실행하세요.

acp:
  enabled: true

  transport:
    stdio:
      enabled: true

  agent:
    name: "Continuum Router"
    version: "1.0.0"
    description: "Local LLM inference agent"

  capabilities:
    load_session: true
    image: false
    audio: false
    embedded_context: false
    mcp: true

  default_model: "gpt-5.6-sol"
  system_prompt: "You are a helpful coding assistant."
  coding_agent_mode: true

  permissions:
    default_policy: ask_always
    auto_allow:
      - read
      - search
      - think
    always_ask:
      - edit
      - delete
      - execute

  sessions:
    max_concurrent: 10
    idle_timeout: "1h"
    storage: "memory"

  mcp:
    max_connections_per_session: 5
    allowed_servers: []
    server_spawn_timeout: "10s"

최상위 옵션

속성 타입 기본값 설명
enabled bool false ACP 하위 시스템 활성화/비활성화
default_model string 없음 session/prompt 요청이 model을 생략했을 때 사용할 모델. 설정된 백엔드가 실제로 제공하는 모델이어야 하며 둘 다 없으면 프롬프트는 -32602로 거부됩니다
system_prompt string 없음 모든 ACP 요청에 시스템 프롬프트 주입
coding_agent_mode bool false 코딩 에이전트 시스템 프롬프트 활성화

session/prompt는 HTTP 경로와 같은 선택 지점을 통해, 결정된 모델을 제공하는 백엔드를 고릅니다. 모델 기반 후보 선정, 내부/비활성 백엔드 제외, 헬스 필터링, 서킷 브레이커 승인, 그리고 설정된 selection_strategy 순으로 적용됩니다. internal: true 또는 enabled: false인 백엔드는 ACP 트래픽을 받지 않습니다. ACP 경로는 의도적으로 HTTP 재시도 루프, 모델 폴백 체인, 키별 백엔드 허용 목록을 적용하지 않습니다. 자세한 범위 결정은 ACP 아키텍처를 참고하세요.

전송 옵션

속성 타입 기본값 설명
transport.stdio.enabled bool true stdio 전송 활성화

권한 옵션

속성 타입 기본값 설명
permissions.default_policy enum ask_always 기본 정책: ask_always, allow_read, allow_all
permissions.auto_allow list [read, search, think] 확인 없이 자동 허용되는 도구 종류
permissions.always_ask list [edit, delete, execute] 항상 권한을 요청하는 도구 종류

세션 옵션

속성 타입 기본값 설명
sessions.max_concurrent int 10 최대 동시 세션 수
sessions.idle_timeout string "1h" 세션 정리 전 유휴 타임아웃
sessions.storage string "memory" 저장 백엔드: memory 또는 file
sessions.storage_path string 없음 파일 기반 저장 경로

MCP 브릿지 옵션

속성 타입 기본값 설명
mcp.max_connections_per_session int 5 세션당 최대 MCP 연결 수
mcp.allowed_servers list [] 허용된 서버 ID (빈 값 = 모두 허용)
mcp.server_spawn_timeout string "10s" MCP 서버 프로세스 생성 타임아웃

프로토콜 세부 사항은 ACP 아키텍처를, 실용 예제는 ACP 사용 가이드를 참조하세요.