원문 캡처 · github.com
원문 캡처 · github.com

Secret Guard는 로컬에서 실행되는 LLM 게이트웨이다. 에이전트 도구가 보내는 요청의 비밀 정보를 같은 길이의 그럴듯한 가짜 값으로 바꾸고, 응답이 돌아오면 실제 값으로 복원해 LLM 제공업체에 실제 비밀 정보가 전달되지 않게 한다.

이 프로젝트는 에이전트 하네스가 코드·설정·터미널 출력과 그 안의 비밀번호, 개인 키, 토큰, 쿠키를 프롬프트에 함께 넣어 LLM 제공업체 서버로 보내는 문제를 다룬다. 데이터가 기기를 떠난 뒤에는 제공업체 로그에 남거나 모델 학습에 쓰이거나, 침해 사고로 유출될 수 있다고 프로젝트는 설명한다. 에이전트 도구가 실제 시스템에서 작업하려면 자격 증명이 필요한 만큼, 비밀 정보를 프롬프트에 넣지 않는 방식만으로는 에이전트 작업을 하기 어렵다는 게 개발 측의 문제 제기다.

요청을 보내고 응답을 복원하는 방식

사용자는 에이전트 도구의 API 기본 URL을 로컬 게이트웨이로 바꾸면 된다. Secret Guard가 요청을 LLM 제공업체로 전달하기 전에 비밀 정보를 같은 길이와 문자 집합의 그럴듯한 가짜 값으로 바꾸는 것이 기본 전략이다. 이때 LLM은 실제 값 대신 가짜 값을 받는다. 응답이 돌아오면 도구 호출 인수를 포함해 가짜 값을 실제 값으로 복원한다. 따라서 비밀 정보가 담긴 curl 명령이 에이전트 도구로 돌아와도 인증은 평소처럼 이뤄진다고 프로젝트는 설명한다.

비밀 정보마다 다른 처리 전략을 설정할 수도 있다. strategy = "reject"는 비밀 정보가 요청에 나타나는 즉시 전달을 거부하고 503을 반환한다. 기기 밖으로 절대 보내면 안 되는 자격 증명에 사용할 수 있다. strategy = "placeholder"는 값을 [REDACTED:{id}] 토큰으로 바꾸며, 응답에서 실제 값으로 복원하지 않는다. 이 방식의 비밀 정보는 카나리아 역할도 한다. 실제 값이 LLM 응답에 나타나면 비밀 값 자체는 알림에 포함하지 않고 WARN 로그와 웹 UI 타임라인 배지로 경고한다. 응답 내용은 그대로 통과시킨다.

Secret Guard가 발급하는 sg API 토큰과 등록된 제공업체 API 키는 별도 설정 없이 자동으로 보호된다. 프롬프트 인젝션으로 에이전트가 게이트웨이 토큰을 대화에 붙여 넣더라도 LLM 제공업체에는 같은 길이의 가짜 값만 전달된다고 프로젝트는 설명한다.

다음 예시는 실제 실행에서 캡처한 요청 전후를 보여준다. 토큰 값만 가상 값이다.

Agent Harness sends:  GITHUB_TOKEN=ghp_0123456789abcdefghijklmnopqrstuvwxyz
LLM vendor receives:  GITHUB_TOKEN=2_9rr_61dr1lmv2wf15aotyyt262l3t9_2pt4uih

웹 UI에서는 세션 타임라인, LLM이 받은 요청의 가짜 값 표시, 응답에서 복원된 실제 값을 확인할 수 있다. 전달된 요청은 Records 페이지에서 세션 타임라인과 요청 상세로 살펴볼 수 있으며, 비밀 정보가 있던 위치는 LLM이 본 가짜 값으로 표시된다.

설치와 빠른 시작

바이너리 아카이브는 Linux x86_64와 ARM64용으로 제공된다. 완전 정적 musl 빌드이며 SHA256 체크섬이 포함된다. 파일 이름 형식은 secret-guard-<version>-<triple>.tar.gz다.

# Binary archives (Linux x86_64 / ARM64, fully static musl, with SHA256 checksums):
# https://github.com/luochen1990/secret-guard/releases
# archive name pattern: secret-guard-<version>-<triple>.tar.gz
# (e.g. secret-guard-0.1.0-x86_64-linux-musl.tar.gz)

# Nix (flake):
nix run github:luochen1990/secret-guard -- --help
# or wire up the overlay / nixosModule into your flake and use pkgs.secret-guard

# Cargo (builds from source, requires rustc >= 1.96):
cargo install --git https://github.com/luochen1990/secret-guard
# or after cloning the repository: cargo install --path .

NixOS 사용자는 구조화된 옵션을 제공하는 services.secret-guard.* 배포 방식을 권장한다. 이 방식은 평가 시점에 설정을 검증하고 파일을 통해 자격 증명을 주입한다. 자세한 내용은 NixOS 배포 문서를 참고하면 된다.

OIDC 로그인은 선택 기능이며 Cargo 기능으로 제공된다. 기본 빌드에는 의존성 그래프를 작게 유지하기 위해 OIDC 로그인 체인이 포함되지 않는다. 따라서 바이너리 아카이브와 cargo install은 단일 사용자 모드로 시작한다. 이 상태에서 [auth] enabled = true를 설정하면 --features oidc로 다시 빌드하라는 안내와 함께 시작에 실패한다. OIDC를 사용하려면 cargo install --path . --features oidc로 소스에서 빌드하거나, 기능을 기본 활성화하는 NixOS 모듈을 사용해야 한다. 로컬 API 키 경로는 항상 빌드에 포함된다. 자세한 설정은 설정 문서의 [auth] 항목에 있다.

최소 설정에는 업스트림 제공업체 하나와 보호할 비밀 정보 하나가 필요하다. secret-guard.toml을 만들고 다음 내용을 넣는다.

[[providers]]
id = "openai-main"
kind = "direct" # direct upstream (use "router" for routing endpoints, "pool" for plan pools)
api_key = "sk-your-upstream-key" # entry-level credential shared by all endpoints — must come BEFORE [[providers.endpoints]] (TOML sub-table switch)
[[providers.endpoints]]
protocol = "openai" # at most one [[providers.endpoints]] per protocol;
base_url = "https://api.openai.com" # unmatched ingress protocols fall back to the first endpoint

[[secrets.entries]]
id = "my-github-token"
value = "ghp_0123456789abcdefghijklmnopqrstuvwxyz" # the secret to protect (never sent upstream)

설정 구조에 유의해야 한다. 비밀 정보는 [[secrets.entries]] 아래에 두지만 제공업체는 평면 구조의 [[providers]]를 사용한다. 전체 필드 설명은 설정 문서에 있다.

다음과 같이 서버를 시작하고 첫 요청을 보낼 수 있다.

secret-guard run --port 18787
curl http://127.0.0.1:18787/o/openai-main/v1/chat/completions \
 -H 'Content-Type: application/json' \
 -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"My token is ghp_0123456789abcdefghijklmnopqrstuvwxyz, please summarize."}]}'

LLM 제공업체에는 그럴듯한 가짜 값이 전달된다. 전달 완료 로그의 redactions=1은 한 번 교체했다는 뜻이다. 브라우저에서 http://127.0.0.1:18787/을 열면 Records 페이지에서 요청을 확인할 수 있다.

예시에서는 포트 18787을 사용하지만 기본 수신 주소는 127.0.0.1:8787이다. [server] port 또는 SG_PORT 환경 변수로 변경할 수 있다. 일상적으로 사용할 때는 curl 대신 에이전트 도구의 base_url을 게이트웨이로 지정하면 된다.

from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:18787/o/openai-main/v1", api_key="ignored")

라우팅과 에이전트 연결

요청 URL은 /{proto_short}/{provider_id}/*rest 형식이다. 경로에 들어가는 짧은 프로토콜 이름과 제공업체 ID가 수신 프로토콜 및 대상 제공업체를 지정한다.

프로토콜 코드프로토콜SDK의 base_url 예시
oOpenAIhttp://127.0.0.1:18787/o/<provider-id>/v1
aAnthropichttp://127.0.0.1:18787/a/<provider-id>
gGeminihttp://127.0.0.1:18787/g/<provider-id>
lOllamahttp://127.0.0.1:18787/l/<provider-id>
rResponses (OpenAI Responses API)http://127.0.0.1:18787/r/<provider-id>/v1

OpenAI SDK에서는 /v1 접두어를 직접 넣어야 한다. Anthropic SDK는 /v1/messages를 자동으로 덧붙이므로 http://.../a/<provider-id>만 지정하면 된다.

실제 API 키는 로컬 secret-guard.toml에만 두고 에이전트 하네스에는 더미 값을 설정한다. Secret Guard가 업스트림으로 전달할 때 실제 키를 넣는다. 프로젝트는 이 구성으로 하네스 자체가 완전히 침해되거나 클라우드 저장소가 유출되더라도 실제 키가 노출되지 않는다고 설명한다.

OpenAI 호환 경로를 사용하는 opencode 설정 예시는 다음과 같다.

{
 "$schema": "https://opencode.ai/config.json",
 "provider": {
 "secret-guard": {
 "npm": "@ai-sdk/openai-compatible",
 "name": "secret-guard (local)",
 "options": {
 "baseURL": "http://127.0.0.1:18787/o/<provider-id>/v1",
 "apiKey": "dummy-key-harness-never-sees-the-real-one"
 },
 "models": {
 "<model-id>": { "name": "<any display name>" }
 }
 }
 }
}

모델 ID는 업스트림 목록에서 가져온다. 게이트웨이는 GET /v1/models 요청을 그대로 전달하므로 다음 명령으로 사용 가능한 모델을 확인할 수 있다.

curl http://127.0.0.1:18787/o/<provider-id>/v1/models

claude-code는 환경 변수 두 개로 설정할 수 있다.

export ANTHROPIC_BASE_URL=http://127.0.0.1:18787/a/<provider-id>
export ANTHROPIC_AUTH_TOKEN=dummy-key-harness-never-sees-the-real-one

프로토콜 간 변환도 내장돼 있다. 예를 들어 에이전트 하네스가 OpenAI 형식을 사용하고 업스트림이 Anthropic이어도 스트리밍을 포함해 변환할 수 있다. 따라서 업스트림이 아니라 하네스에 맞는 경로를 고르면 된다.

설정 파일과 로그

secret-guard.toml은 직접 작성하고 프로세스에서는 읽기 전용으로 사용하는 선언형 설정이다. secret-guard.state.toml은 웹 UI에서 편집한 내용이 저장되는 동적 상태 파일이다. 삭제하면 초기화된다. 같은 항목에 정적 설정과 동적 설정이 모두 있으면 동적 설정이 정적 설정을 덮어쓸 수 있다.

state.toml에는 웹 UI에서 만든 비밀 정보 값과 명시적으로 작성한 제공업체 API 키가 평문으로 들어간다. 민감도는 secret-guard.toml과 같으므로 .gitignore에 넣어야 한다. 실수로 커밋하면 비밀 정보가 버전 기록에 남는다.

전달이 완료될 때마다 forward ... status=... elapsed_ms=... redactions=N provider=... 형식의 한 줄 INFO 요약 로그가 남는다. 이 로그로 명령줄에서 트래픽이 Secret Guard를 거치는지 확인할 수 있다. 업스트림 오류인 502와 504는 응답 본문에 읽을 수 있는 원인을 포함한다. 기본 로그 수준은 info이며, 로그가 많으면 RUST_LOG=secret_guard=warn으로 낮출 수 있다.

프로젝트가 제시한 동작 보장과 지원 범위

프로젝트는 속성 기반 테스트와 엔드투엔드 통합 테스트로 다음 동작을 검증하며, 모든 릴리스에서 완전히 확인한다고 설명한다. 이는 프로젝트가 제시한 테스트 및 보장 내용이다.

  • 투명한 중계: 비밀 정보 교체가 없으면 바이트 단위로 그대로 전달한다. 교체가 있으면 필드 순서 같은 직렬화 차이를 제외하고 실제 값과 가짜 값의 교환만 일어나 의미는 달라지지 않는다고 한다.
  • 복원 가능성: 각 가짜 값은 하나의 실제 값에 대응하며, 복원은 교체의 정확한 역연산이다. 비밀 정보를 담은 도구 호출이 손상되지 않는다는 설명이다.
  • 결정적인 가짜 값: 같은 비밀 정보에는 여러 차례 요청에서도 같은 가짜 값을 사용한다. 따라서 LLM 제공업체의 접두사 캐시 적중을 유지하며 일반적인 세션에서는 토큰 비용에 영향을 주지 않는다고 한다. 가짜 값이 이스케이프된 뒤 재전달되는 예외는 문서화돼 있다.
  • 요청 내용과 충돌하지 않는 값: 가짜 값이 요청의 다른 내용과 일치하지 않아 응답의 엉뚱한 부분이 교체되지 않는다는 설명이다.
  • 여러 제공업체 라우팅: OpenAI, Anthropic, Gemini, Ollama, Responses를 지원하며 요청마다 모델 와일드카드 규칙으로 업스트림을 선택할 수 있다.
  • 플랜 풀 전환: 각자 자격 증명을 가진 여러 코딩 플랜 구독을 하나의 엔드포인트 뒤에 연결할 수 있다. 한 플랜이 사용 기간 한도에 도달하면 다음 플랜으로 자동 전환하고, 한도가 초기화되면 되돌아온다고 한다.
  • 필요할 때 상세 로그 기록: 기본 상태에서는 메모리 사용을 적게 유지하면서 세션 타임라인을 제공한다. 웹 UI에서 설정을 한 번 바꾸면 이후 요청의 전체 요청·응답 본문을 문제 해결용으로 기록한다.

프로젝트는 LiteLLM, LLM Guard, PasteGuard와 비밀 정보 보호의 일곱 가지 측면을 소스 수준에서 비교한 문서도 제공한다. 비교 대상이 더 강한 부분도 포함한다고 설명한다. 비교 문서

프로토콜전달비밀 정보 교체프로토콜 간 변환
OpenAI (Chat Completions)✅✅✅ ⇄ Anthropic (스트리밍 포함) / ⇄ Responses (스트리밍 포함)
Anthropic (claude)✅✅✅ ⇄ OpenAI (스트리밍 포함) / ⇄ Responses (스트리밍 포함)
OpenAI Responses✅✅ (스트리밍 포함)✅ ⇄ Chat Completions / Anthropic (스트리밍 포함)
Gemini / Ollama✅ 바이트 그대로 전달🚧 로드맵🚧 로드맵

Gemini와 Ollama는 바이트를 그대로 전달하므로 전달 자체는 작동하지만, 현재 코덱이 이 프로토콜을 다루지 않아 비밀 정보 교체는 적용되지 않는다. 비밀 정보가 설정된 상태에서는 기본적으로 해당 프로토콜 요청을 거부하며 503을 반환한다. 설정은 [redact] on_unsupported_protocol = "fail_closed"이고 오류 메시지에 안내가 포함된다. 직접 작성한 설정이나 REST API로 이 프로토콜을 만들 수는 있지만, 비밀 정보가 없는 단순 전달 용도에 한한다. 웹 UI의 생성 양식에는 나오지 않지만 기존 항목 편집이나 Detect 탐침 권고에서는 (experimental) 옵션으로 지원한다.

교체 과정의 비정상 경로에서는 기본적으로 가짜 값을 그대로 전달한다. 여기에는 파싱할 수 없는 업스트림 응답이나 2xx가 아닌 응답이 포함된다. 실패 응답 본문에 실제 값이 들어가지 않도록 하는 기본 동작이며, on_fallback_restore = "restore"를 설정하면 복원을 선택할 수 있다. 세 가지 성능 저하 스위치의 전체 의미는 설정 문서에 있다.

프로젝트는 프로토콜 동작을 속성 기반 테스트와 모의 업스트림 통합 테스트로 고정했다고 설명한다. 다만 Anthropic, Gemini, Ollama의 실제 제공업체 필드 검증은 아직 이뤄지지 않았다. 이 프로토콜을 처음 도입할 때는 본격적으로 사용하기 전에 웹 UI의 Records 페이지에서 실제 요청 하나를 확인하라고 안내한다. 자세한 내용은 로드맵을 참고하면 된다.

개발 중인 기능과 문서

프로젝트는 다음 항목을 진행 중이며, 주 저장소에서 이슈와 PR을 받는다. 전체 개발 안내는 AGENTS.md에 있다.

  • Gemini와 Ollama 코덱: 두 프로토콜군에 비밀 정보 교체와 프로토콜 간 지원을 추가하는 작업이다. 새 프로토콜은 분배 로직을 바꾸지 않고 Reader와 Writer 트레이트를 구현하는 방식으로 추가할 수 있으며, 프로젝트는 약 200줄이면 된다고 설명한다. 구조는 src/codec/AGENTS.md에서 확인할 수 있다.
  • Responses 스트리밍 변환 정확도: 스트리밍과 비밀 정보 복원 경로는 코덱이 지원하는 모든 조합에서 작동하지만, 알려진 손실이 남아 있다. 호스팅 도구와 거부 응답 부분이 WARN 로그와 함께 누락될 수 있고, 추론 델타 이벤트 유형이 정규화되며, 여러 콘텐츠 부분이 하나로 합쳐질 수 있다. 자세한 내용은 알려진 제한 사항에 있다.
  • 선언되지 않은 비밀 정보 탐지: NER, 정규 표현식, 엔트로피 기반 탐지를 알림 계층으로 추가하는 방안이다. 자동으로 값을 교체하지 않고 웹 UI에서 의심 항목을 표시하며, 오탐으로 인한 교체가 없다는 보장을 유지하는 것이 목표다. 이슈 #257
  • 실제 제공업체 필드 검증: 프로토콜 조합을 실제 업스트림 대상으로 통합 테스트하는 작업이다.

빠른 시작은 웹사이트의 3분 안내를, 설정 전체 항목은 설정 문서를 참고하면 된다. 웹 UI 사용법은 WebUI 안내, NixOS 배포와 자격 증명 주입은 배포 문서에 정리돼 있다. 아키텍처와 데이터 흐름 계약은 설계 문서, 대안 비교는 비교 문서, 개발 절차와 테스트 전략은 AGENTS.md에서 볼 수 있다.

Secret Guard의 라이선스는 MIT다. 라이선스 파일