자격 증명 프록시 — 기술 문서
프록시 작동 방식.
보호하는 대상과 보호하지 않는 대상.
이 페이지는 프록시의 위협 모델을 평가하는 보안 검토자, 모의 해킹 테스트 담당자 및 엔지니어를 위한 자료예요. 프로토콜 수준에서 프록시가 수행하는 작업, 메모리 내 자격 증명의 존재 위치, 남아 있는 공격 표면에 대해 설명해요.
아키텍처
이 프록시는 CONNECT 기반 HTTPS MITM 프록시예요. AI 에이전트는 HTTPS_PROXY를 이 프록시를 가리키도록 설정해요. 에이전트가 HTTPS 요청을 보내면 프록시가 TLS 연결을 가로채고, 요청 헤더에서 자격 증명 참조를 검사한 후, Clavitor 볼트와 대조하여 이를 확인하고, 자격 증명이 주입된 요청을 업스트림 API로 전달해요.
프록시는 기본적으로 127.0.0.1:1983에서 수신 대기하며, 이는 프록시와 에이전트가 호스트를 공유하는 사이드카 패턴이에요. 공유 배포(프라이빗 네트워크에서 여러 에이전트를 서비스하는 단일 프록시, 여러 워크로드에 대한 컨테이너 사이드카, 전용 프록시 호스트)의 경우, CLAVITOR_PROXY_LISTEN을 통해 수신 인터페이스를 구성할 수 있어요.
프록시는 독립 실행형 Go 바이너리예요. CGO를 사용하지 않아요. 모든 Clavitor 프로토콜 암호화는 WebAssembly로 컴파일되어 시작 시 wazero를 통해 로드되는 표준 Rust 구현을 통해 라우팅돼요.
TLS 처리
프록시는 첫 실행 시 자체 서명된 ECDSA P-256 루트 CA를 생성하며, 이는 바이너리 디렉터리에 0600 모드로 저장돼요. 각 업스트림 호스트에 대해 온디맨드로 리프 인증서가 발급되고 이 CA로 서명된 후, 제한된 축출(1,000개 호스트) 방식의 메모리에 캐시돼요. 리프 인증서는 24시간 동안 유효하며, 세션 중 만료를 방지하기 위해 23시간 차에 투명하게 재생성돼요.
에이전트는 프록시의 CA 인증서를 신뢰해야 해요. clavitor-proxy ca로 내보낼 수 있어요.
업스트림 연결은 HTTP/2 및 HTTP/1.1에 대한 ALPN 협상을 포함하여 최소 TLS 1.3을 사용해요. 업스트림 검증에는 시스템 인증서 풀이 사용돼요. 인증서 고정은 없으며, 프록시는 OS가 신뢰하는 모든 것을 신뢰해요.
자격 증명은 절대 캐시되지 않고, 디스크에 기록되지 않으며, 하나의 HTTP 요청보다 오래 유지되지 않아요.
| 단계 | 자격 증명 존재 위치 | 기간 |
|---|---|---|
| 볼트 내 저장 상태 | 볼트 데이터베이스의 AES-GCM 암호문 | 삭제될 때까지 |
| 프록시로 전송 중 | 볼트 API의 TLS 암호화 JSON 응답 | 1회 HTTP 왕복 |
| 프록시 내 복호화 상태 | 프로세스 메모리(힙의 Go 문자열) | 1회 HTTP 요청 |
| 업스트림 요청에 주입됨 | 업스트림으로 전송되는 TLS 암호화 바이트 | 1회 HTTP 요청 |
프록시는 실행 기간 내내 에이전트의 자격 증명 복호화 키(16바이트)를 메모리에 보관해요. 시작 시 암호화된 사이드카 구성(CLV1 형식)에서 로드되며, 정상 종료 시 삭제돼요. 이 키는 프로세스 외부로 절대 유출되지 않아요.
사이드카 구성은 정적 시드에서 파생된 결정론적 키를 사용하여 AES-128-GCM 및 HMAC-SHA256으로 암호화돼요. 이는 기밀성이 아닌 난독화이며, 보안 경계는 파일 권한(0600)과 파일 소유에 기반해요. CLV1 형식은 프록시, CLI 및 브라우저 확장 프로그램 간에 공유돼요.
조회 모드
모드 1 — 명시적 플레이스홀더
에이전트가 요청 헤더에 clavitor://Entry/field 참조를 포함해요. 프록시는 이름으로 볼트에서 해당 항목을 검색하고, 가져온 후, 명명된 필드를 복호화하여 플레이스홀더를 실제 값으로 대체해요.
검색 결과가 0개이거나 둘 이상인 경우, 프록시는 안정적인 오류 코드와 함께 502를 반환해요. 플레이스홀더는 절대 제거되지 않고 그대로 전달되는 일도 없어요.
모드 2 — URL 일치
플레이스홀더가 없는 경우, 프록시는 URL 필드가 업스트림 호스트와 일치하는 항목을 볼트에 요청해요. 인식된 필드 형식과 정확히 일치하는 항목이 하나만 존재하면 프록시가 자격 증명을 자동으로 주입해요.
일치 항목 0개 → 통과(자격 증명 예상 없음). 여러 항목 일치 → 모호성 해소 안내와 함께 502 반환. 알 수 없는 필드 형식 → 502.
의사결정 트리는 결정론적이에요: 플레이스홀더 존재 → 조회 또는 실패. 플레이스홀더 없음 → URL 일치 또는 통과. 조회 실패 시 자격 증명 없이 요청이 업스트림으로 전송되는 자동 폴백 경로는 존재하지 않아요.
기본적으로 볼트는 모든 요청에서 프록시 고유의 에이전트 ID를 확인해요. 속도 제한, 범위 검사 및 감사 로그 항목은 프록시에 귀속돼요.
여러 에이전트가 하나의 프록시 인스턴스를 공유할 때, 플레이스홀더에 에이전트 ID를 포함할 수 있어요: clavitor://agentid@Entry/field. 프록시는 이 에이전트 ID를 볼트로 전송하고, 볼트는 해당 에이전트의 범위와 속도 제한을 적용하며 이에 대한 접근을 감사 로그에 기록해요. 에이전트 ID는 볼트 UI의 에이전트 세부 정보 페이지에 표시되는 32자의 16진수 값이에요.
# Without agent ID — attributed to the proxy Authorization: Bearer clavitor://OpenAI/key # With agent ID — attributed to agent 0102030405060708090a0b0c0d0e0f10 Authorization: Bearer clavitor://0102030405060708090a0b0c0d0e0f10@OpenAI/key
| 배포 | 신원 모델 | 격리 |
|---|---|---|
| 에이전트당 하나의 프록시 | 프록시 ID = 에이전트 ID(기본값) | 완전 격리 — 별도 바이너리, 구성, 범위, 속도 제한 |
| 공유 프록시, URL에 에이전트 ID 없음 | 모든 에이전트가 프록시의 ID를 공유 | 공유 범위 및 속도 제한 |
공유 프록시 + URL에 agentid@ 포함 | 에이전트별 신원 | 에이전트별 범위, 속도 제한 및 감사 로그 |
URL의 에이전트 ID는 인증 메커니즘이 아니에요. 프록시의 CVT 토큰이 연결을 인증해요. 에이전트 ID는 귀속 대상을 결정해요: 누구의 범위가 적용되고, 누구의 속도 제한이 계산되며, 누구의 감사 추적에 접근이 기록되는지 결정하죠. 볼트는 알 수 없는 에이전트 ID에 대해 명확한 실패를 반환하며 거부해요.
네트워크 보안
SSRF 보호
기본적으로 프록시는 프라이빗 네트워크(RFC 1918), 클라우드 인스턴스 메타데이터(169.254.169.254), 루프백, 링크 로컬 및 통신사 등급 NAT 범위에 대한 업스트림 연결을 차단해요. DNS가 먼저 확인되며, TCP 연결이 이루어지기 전에 반환된 모든 IP가 검증되어 DNS 리바인딩 TOCTOU 취약점 창을 닫아요.
합법적으로 프라이빗 API에 접근해야 하는 에이전트의 경우 CLAVITOR_PROXY_ALLOW_PRIVATE=true로 재정의할 수 있어요.
대상 고정
CONNECT 대상 호스트는 터널 설정 시 캡처되어 터널 수명 주기 내내 사용돼요. 터널 내 후속 요청은 Host 헤더를 조작하여 다른 호스트로 리디렉션할 수 없어요. 불일치 시 502가 발생해요.
이는 에이전트가 api.openai.com으로 터널을 설정한 다음 internal-service.corp로 요청을 보내는 것을 방지해요.
헤더 처리
RFC 7230 §6.1에 따라 홉바이홉 헤더는 요청과 응답 모두에서 제거돼요: Connection, Keep-Alive, Proxy-Authenticate, Proxy-Authorization, Proxy-Connection, TE, Trailers, Transfer-Encoding, Upgrade.
업스트림이 에이전트의 HTTP 클라이언트에 쿠키를 심는 것을 방지하기 위해 업스트림 응답에서 Set-Cookie가 제거돼요.
요청 및 응답 본문은 버퍼링 없이 스트리밍돼요. 요청 본문은 기본적으로 64MB로 제한돼요(CLAVITOR_PROXY_MAX_BODY_MB). 응답 본문은 엄격한 제한 없이 스트리밍되며, Content-Length가 100MB를 초과하면 로그 경고가 발생해요.
URL 일치 모드에서 프록시는 볼트 필드 레이블을 HTTP 헤더에 매핑해요:
| 필드 레이블 | 주입된 헤더 |
|---|---|
key, apikey, api_key, token, secret, bearer, access_token | Authorization: Bearer <value> |
x-api-key, api-key | X-API-Key: <value> |
username + password (쌍) | Authorization: Basic base64(user:pass) |
| 기타 모든 항목 | 거부됨 — ERR-PROXY-052 |
플레이스홀더 모드에서는 에이전트가 어떤 필드를 조회할지와 어디로 보낼지를 제어해요. 위의 매핑은 URL 일치 모드에만 적용돼요.
모든 실패는 안정적인 ERR-PROXY-NNN 코드를 생성해요. 이 코드는 프록시의 공개 인터페이스의 일부이며, 에이전트와 운영자는 알림 및 디버깅을 위해 이 코드를 매칭할 수 있어요.
| 범위 | 카테고리 |
|---|---|
001–019 | 설정(구성, 초기화, CA 생성, WASM) |
020–029 | 데몬 수명 주기 |
030–049 | 플레이스홀더 조회(clavitor:// URI) |
050–069 | URL 일치 주입 |
070–089 | 업스트림 / TLS |
신원 필드는 접근 불가
볼트 항목은 세 가지 암호화 수준을 지원해요. 볼트 암호화 필드는 평문 메타데이터예요. 자격 증명 암호화 필드는 에이전트의 키로 복호화돼요. 신원 암호화 필드는 서버와 프록시가 절대 알 수 없는 키로 암호화돼요. 볼트 소유자만이 하드웨어 키를 통해 이를 복호화할 수 있어요.
플레이스홀더가 신원 암호화 필드를 참조하는 경우, 프록시는 ERR-PROXY-035를 반환해요. 폴백도 없고 부분 결과도 없어요. 해당 필드는 아키텍처상 프록시에서 접근할 수 없어요.
프록시가 보호하지 않는 대상
프록시의 위협 모델은 인증된 에이전트가 자격 증명을 수집하도록 유도하는 손상된 스킬 또는 프롬프트 인젝션이에요. 볼트의 에이전트별 속도 제한, 고유 항목 할당량 및 투 스트라이크 잠금이 주요 방어 수단이에요. 프록시는 자격 증명이 요청별로 조회되고 에이전트가 절대 보유하지 않는 네트워크 계층 강제 지점을 추가해요.
프록시는 에이전트와 동일한 머신에서 실행돼요. 루트 접근 권한이 있는 공격자는 프로세스 메모리를 읽고, 디버거를 연결하거나, 루프백 트래픽을 가로챌 수 있어요. 프록시는 자격 증명 주입 계층이지 하드웨어 보안 경계가 아니에요.
업스트림 API가 응답에서 자격 증명을 다시 에코하는 경우(예: "whoami" 엔드포인트), 에이전트가 이를 보게 돼요. 프록시는 응답이 아닌 요청에 자격 증명을 주입해요. 돌아오는 응답은 필터링하지 않아요.
로깅
프록시는 수락된 CONNECT당 한 줄의 로그를 기록하고 실패 시 오류 줄을 내보내요. 다음 항목은 절대 로그에 기록하지 않아요:
- 복호화된 자격 증명 값
- 전체 요청 URL(쿼리 문자열에 시크릿이 포함될 수 있음 — 스키마 + 호스트 + 경로만 기록됨)
- 요청 또는 응답 본문
- 자격 증명 복호화 키 또는 사이드카 구성 내용
프록시는 업스트림의 400 응답에 인증 관련 키워드(unauthorized, invalid token 등)가 포함되어 있음을 감지하면, 주입된 자격 증명이 만료되었을 수 있음을 시사하는 진단 힌트를 로그에 기록해요. 응답은 변경 없이 전달돼요.
암호화
모든 Clavitor 프로토콜 암호화(AES-GCM 필드 복호화, HKDF 키 파생, base62 인코딩, CVT 토큰 발행, CLV1 구성 패킹/언패킹)는 순수 Go WASM 런타임인 wazero를 통해 로드되는 단일 WebAssembly 모듈(clavis_crypto.wasm) 내부에서 실행돼요. CGO 없음. Clavitor 프리미티브의 Go 재구현 없음.
WASM 모듈은 브라우저, CLI 및 브라우저 확장 프로그램에서 사용되는 것과 동일한 Rust 크레이트(clavis-crypto)에서 컴파일돼요. 단일 진실 공급원, 단일 바이너리, 단일 감사 표면.
프록시는 TLS 와이어에 Go의 crypto/tls를 사용하고, MITM 인증서 생성에 crypto/ecdsa를 사용해요. 이는 전송 관련 문제이며 Clavitor 프로토콜 작업이 아니에요.
시크릿(자격 증명 복호화 키, 에이전트 ID, 디바이스 ID, 볼트 URL)은 clavitor-proxy init 동안 한 번 기록되는 암호화된 CLV1 사이드카 구성에 존재해요. 운영 관련 설정은 환경 변수에 존재해요:
| 변수 | 기본값 | 목적 |
|---|---|---|
CLAVITOR_PROXY_LISTEN | 127.0.0.1 | 수신 인터페이스. 공유 배포의 경우 0.0.0.0으로 설정하거나, 특정 인터페이스 IP로 설정해요. |
CLAVITOR_PROXY_PORT | 1983 | 수신 포트 |
CLAVITOR_PROXY_ALLOW_PRIVATE | false | RFC-1918 / 프라이빗 네트워크 연결 허용 |
CLAVITOR_PROXY_MAX_BODY_MB | 64 | 요청 본문 크기 제한 |
CLAVITOR_PROXY_WRITE_TIMEOUT | 300 | 응답 쓰기 시간 초과(초) |
CLAVITOR_CONFIG | (실행 파일 디렉터리) | 사이드카 구성 경로 재정의 |
운영 관련 설정은 시크릿이 아니에요. 암호화된 구성에 속하지 않아요. 배포 도구가 이미 관리하는 곳, 즉 환경에 속해요.
직접 검토해 보세요.
암호화는 단일 감사 가능 WASM 아티팩트예요. 위협 모델은 문서화되어 있어요. 저희가 놓친 부분을 발견하시면 알려주세요.