<img alt="nenya" src="https://github.com/user-attachments/assets/bd518ded-2b65-42f9-866e-5a670cf9dbb1" />
Nenya AI Gateway
![go-version] ![License][license] ![zero-deps] ![CI][ci] ![CodeQL][codeql] ![Release][release] ![Sponsor][sponsor]
AI 코딩 클라이언트는 모든 요청마다 소스 코드, 프롬프트, 자격 증명을 클라우드 LLM 제공업체로 전송합니다. Nenya는 그 사이에 위치한 게이트키퍼입니다: 기기(machine)를 벗어나기 전에 비밀 정보(secrets)를 마스킹하고, 컨텍스트 페이로드(context payloads)를 작게 유지하며, 폴백(fallback), 캐싱(caching), 투명한 SSE 스트리밍을 통해 제공업체 간에 라우팅하고, 프롬프트 주입(prompt injection)을 감지하고 이그레스(egress)를 제어하는 선택적 안티-포이즈닝 스택(anti-poisoning stack)까지 갖춘 가벼운, 제로 의존성 API 게이트웨이입니다. 보안 강화: non-root 실행, 비밀 정보용 mlock, seccomp + no-new-privileges.
OpenAI 또는 Anthropic의 Chat Completions API를 구현하는 모든 제공업체와 호환됩니다. 23개 제공업체에 대해 특화된 처리 기능을 갖춘 내장 어댑터를 제공합니다.
<img alt="Nenya demo: secrets are redacted before reaching the upstream provider" src="https://raw.githubusercontent.com/gumieri/nenya/HEAD/docs/demo.gif" width="100%">
Quick Start
Podman으로 실행하기
최소한의 설정(config)과 비밀 정보(secrets)를 생성합니다:
mkdir -p config secrets
cat > config/config.json << 'EOF'
{
...
참고: 마지막 heredoc은 의도적으로 따옴표로 묶지 않았기 때문에, $(openssl rand -hex 32)는 파일이 작성되는 동안 한 번 확장되어 토큰이 고유합니다.
컨테이너를 실행합니다 (동일한 플래그가 docker run과 작동합니다):
podman run -d \
--name nenya \
-p 8080:8080 \
...
테스트해봅니다 — 인증된 스모크 테스트(smoke test)는 구성한 모델을 나열해야 합니다:
export NK=$(jq -r '.client_token' secrets/client.json)
curl -s -H "Authorization: Bearer $NK" http://localhost:8080/v1/models | jq -r '.data[].id'
그런 다음 첫 번째 완료(completion)를 전송합니다 (SSE 응답을 스트리밍합니다):
curl -N -H "Authorization: Bearer $NK" \
-d '{"model":"gemini-2.5-flash","messages":[{"role":"user","content":"Say hi in five words"}]}' \
http://localhost:8080/v1/chat/completions
API 키가 없으신가요? 오프라인 마스킹 데모를 실행하여 Nenya가 로컬 모의 상위 시스템(mock upstream)을 대상으로 작동하는 것을 확인해 보세요. 외부 호출이나 API 키가 필요 없습니다. 이 데모는 더미 비밀값(dummy secrets)을 생성하고, 가짜 AWS/GitHub 자격 증명(credentials)을 게이트웨이를 통해 전송하며, 상위 시스템이 실제로 무엇을 수신하는지 보여줍니다. 위 GIF를 mise run demo로 재생성하세요.
패키지 관리자를 통한 설치
Nenya는 주요 Linux 배포판과 커뮤니티 패키지 관리자를 위한 네이티브 패키지를 제공합니다:
| 배포판 | 명령어 |
|---|
| Debian/Ubuntu (.deb) | 릴리스 페이지에서 nenya_<version>_linux_amd64.deb를 다운로드하고 sudo dpkg -i를 실행하세요. |
| ... | |
모든 패키지는 바이너리를 /usr/bin/nenya에 설치하며, systemd 서비스 및 소켓 유닛을 포함합니다. 설치 후 다음 명령어로 활성화하고 시작하세요:
sudo systemctl enable --now nenya.socket
sudo systemctl enable --now nenya.service
배포 가이드
Nenya를 사용하는 이유
- 단일 정적 바이너리, 런타임 종속성 없음 — Go 표준 라이브러리만 사용합니다. 플러그인, 인터프리터, 설치하거나 업그레이드할 사이드카가 없습니다.
- 기본적으로 개인 정보 보호 중심(Privacy-first by default) — Tier-0 정규 표현식 필터가 AWS 키, GitHub 토큰, 비밀번호 및 유사한 비밀 정보를 페이로드가 장치를 떠나기 전에 마스킹합니다. 선택적 엔트로피 필터링, TF-IDF 가지치기, 엔진 요약 기능을 통해 전송되는 데이터의 양을 줄입니다.
- 안티-포이즈닝 스택(Anti-poisoning stack) — 치명적인 삼중 조합에 대한 옵트인(opt-in), 심층 방어 제어를 제공합니다: 결정론적 프롬프트 주입 감지, 신뢰할 수 없는 콘텐츠 조명화(spotlighting), 출력 이탈 제어(ExfilGuard + canary tripwire), 그리고 게이트웨이 관리 디스패치 전에 MCP 도구 호출 인자 가드입니다.
- 투명한 호환성(Transparent compatibility) — OpenAI 및 Anthropic과 호환되는 드롭인 엔드포인트를 제공합니다. 클라이언트는 변경 없이 계속 작동하며, 공급자는 코드가 아닌 교체 가능한 설정으로 관리됩니다.
- 복원력 있는 라우팅(Resilient routing) — 회로 차단기(circuit breakers)가 있는 폴백 체인, 업스트림 속도 제한 인지 기능, 스트림 헤드 장애 조치(stream-head failover), 그리고 공급자 측 접두사 캐시를 따뜻하게 유지하는 스티키 세션이 있습니다.
- 강화된 서비스(Hardened service) — mlock으로 봉인된 비밀 정보, seccomp 및 no-new-privileges 적용, 비루트 컨테이너, 읽기 전용 파일 시스템, 제로 다운타임 재시작을 위한 systemd 소켓 활성화 기능을 갖추고 있습니다.
Nenya의 요청 처리 방식
flowchart TD
CLIENT[
Flow notes:
- `/v1/*` 엔드포인트는 클라이언트 bearer 인증을 필요로 하며, `server.telemetry_unauthenticated`가 설정되지 않은 경우 `/statsz`와 `/metrics`는 모든 키(읽기 전용으로 충분)를 필요로 합니다. `/healthz`는 그렇지 않습니다.
- 파이프라인 실패 시 500 에러를 반환하는 대신 우아하게 저하되고 요청을 전달합니다.
- MCP가 활성화된 에이전트는 클라이언트에게 MCP의 복잡성을 노출하지 않고 로컬/원격 도구를 실행할 수 있습니다.
- Sticky 전략은 세션(에이전트 + 시스템 프롬프트 + 첫 사용자 메시지)을 하나의 제공업체/모델에 고정하여, 턴 간에 제공업체 측 접두사 캐시를 따뜻하게 유지합니다.
- 업스트림 스트림은 헤더 커밋 전에 프로브됩니다: 빈 스트림이나 초기 SSE 오류는 다음 대상으로 장애 조치되며, 중단된 스트림은 스트림 지속을 통해 자동 재개됩니다.
## 기능 (Features)
### 라우팅 및 에이전트 (Routing & Agents)
- **설정 기반 제공업체 레지스트리 (Config-driven provider registry)** — JSON을 통해 제공업체를 추가하고 코드 변경 없이 사용합니다.
- 와이어 형식 차이를 위한 전문 어댑터가 포함된 23개의 내장 제공업체
- **동적 모델 검색 (Dynamic model discovery)** — 시작 시 및 리로드 시 제공업체로부터 라이브 모델 카탈로그를 가져옵니다.
- **모델 레지스트리 (Model registry)** — 자동 제공업체/컨텍스트 해상도를 통해 문자열 약어로 모델을 참조합니다.
- **다중 제공업체 모델 해상도 (Multi-provider model resolution)** — 모델이 여러 제공업체에 존재하는 경우, 모두 에이전트의 폴백 체인에 추가됩니다.
- **3단계 모델 해상도 (Three-tier model resolution)** — 설정 오버라이드 > 검색된 모델 > 정적 레지스트리 순서로 적용됩니다.
- **모델별 와이어 형식 (Per-model wire format)** — 다중 형식 게이트웨이(OpenCode Zen)의 모델은 모델의 `format` 속성을 기반으로 OpenAI, Anthropic, Gemini 와이어 형식 간에 자동으로 변환됩니다.
- **에이전트 폴백 체인 (Agent fallback chains)** — 회로 차단기 및 자동 장애 조치와 함께 라운 로빈 또는 순차 방식으로 작동합니다.
- **지연 시간 인식 라우팅 (Latency-aware routing)** — 역사적 중앙값 응답 시간에 ±5% 지터(jitter)를 적용하여 대규모 요청 폭주를 방지하기 위해 타겟을 자동으로 재정렬합니다.
- **에이전트별 시스템 프롬프트 (Per-agent system prompts)** — 인라인 또는 파일 기반으로 설정할 수 있습니다.
### 보안 및 개인정보 보호 (Security & Privacy)
안티-포이즈닝 스택은 심층 방어(defense-in-depth) 구조입니다. 그 아래의 결정론적(deterministic) 플로어는 항상 활성화되어 있으며, 주입(injection) 및 추출(egress) 레이어는 선택적으로 사용하며 활성화 시 실패 폐쇄(fail closed)됩니다. 위협 모델, 배포 순서 및 문서화된 사각지대는 [docs/INJECTION_DEFENSE.md](docs/INJECTION_DEFENSE.md)를 참조하십시오.
- **Tier-0 정규식 비밀 필터** — AWS 키, GitHub 토큰, 비밀번호 등의 항상 활성화되는 마스킹(redaction)
- **3단계 콘텐츠 파이프라인** — 플러그인 인터셉터 체인: 정규식 마스킹, 엔트로피 필터링, TF-IDF 관련성 점수 산정, 엔진 요약
- **컨텍스트 창 압축** — 구성 가능한 엔진을 사용한 슬라이딩 윈도우 요약
- **오래된 도구 호출 가지치기(pruning)** — 토큰 절약을 위해 오래된 어시스턴트+도구 응답 쌍을 압축
- **사고(Thought) 가지치기** — 어시스턴트 메시지 기록에서 추론 블록 제거
- **프롬프트 주입 방어** (선택적, `governance.injection`) — 지침 재정의 구문, 역할/형식 위조, 숨겨진 텍스트 캐리어 및 인코딩된 블롭에 대한 결정론적 탐지 및 정제; 에이전트별 `strict` 모드는 `403 error_kind=injection_detected`로 거부
- **신뢰할 수 없는 콘텐츠 스포트라이팅** (선택적, `governance.spotlight`) — MCP/메모리 도구 결과 및 들어오는 도구 역할 기록을 래핑하여 모델이 이를 지침이 아닌 데이터로 처리하도록 함
- **2단계 분류기** (선택적, `governance.injection.escalation`) — 모호한 탐지에 대한 자문 LLM 판정; 이는 오탐지(false positives)를 해제할 수 있지만 결정론적 판정을 약화시키지는 않음
- **출력 추출 제어** (선택적, `governance.exfil_guard`) — 모델이 생성하는 마크다운 링크/이미지 및 순수 URL에 대한 URL 정책으로, `log`, `strip` 또는 `block` 액션을 가짐
- **캐나리아 트립와이어** (선택적, `governance.canary`) — 요청당 응답 스트림, 버퍼링된 본문 및 도구 호출 인수에 대해 감시하는 마커; 탐지(hit)는 주입 기반 추출의 서명임
- **MCP argument guard** (MCP 서버 구성 시 기본 활성화됨, `governance.mcp_guard`) — 툴 호출 인수를 툴 스키마와 비교하여 검증하고, 최대 용량을 1 MiB로 제한하며, 게이트웨이가 관리하는 디스패치 전에 사설/루프백 목적지를 거부합니다.
- **System One 의사결정 모델** — `POST /v1/systemone`은 TypeSafe Jev 타입의 의사결정 요청(noul/choice/score)을 Nenya를 통해 프록시하며, 채팅 라우팅에서 제외하는 비채팅 모델 가드(see [docs/SYSTEM_ONE.md](docs/SYSTEM_ONE.md))가 적용됩니다.
- **입력 유효성 검사** — 엄격한 본문 제한, JSON 정제(sanitization), 헤더 필터링
- **점진적 성능 저하 (Graceful degradation)** — `bouncer.fail_open=true` 설정 시 (기본값), 엔진 및 토큰 저장 파이프라인 실패가 요청을 차단하지 않으며; 보안 인터셉터는 설계상 닫힘(fail closed)으로 작동하여(503) 손상된 방어벽이 콘텐츠를 조용히 통과시키는 것을 막습니다.
- **역할 기반 접근 제어 (RBAC)** — API 키별 역할(관리자, 사용자, 읽기 전용) 및 에이전트와 엔드포인트 제한
### 강화 (배포 보안)
- **보안 메모리 (기본값)**: 모든 토큰은 mlock으로 보호되는 RAM에 저장되며, 초기화 후 봉인된 읽기 전용 상태가 되고 코어 덤프는 비활성화됩니다.
- **비루트 실행** — 권한을 낮춘(dropped) 기능(capabilities)과 함께 UID 65532로 실행됩니다.
- **메모리 보호** — systemd에서 `LimitMEMLOCK=infinity` 및 `LimitCORE=0` 설정
- **읽기 전용 파일 시스템** — 불변의 루트와 사설 `/tmp` 디렉터리
- **Seccomp + no-new-privileges** — 제한된 시스템 호출(syscalls)을 사용하고 권한 상승을 방지합니다.
- **제로 트러스트 비밀 정보 (Zero-trust secrets)** — systemd 자격 증명 또는 컨테이너 마운트를 통해 로드되며, 디스크에 저장되지 않습니다.
- **소켓 활성화 (Socket activation)** — 연결 끊김 없이(zero dropped connections) 원활하게 재시작됩니다.
### 신뢰성
- **외부 의존성 없음(Zero external dependencies)** — Go 표준 라이브러리만 사용
- **핫 리로드(Hot reload)** — `systemctl reload nenya`를 통해 다운타임 없는 설정 변경 가능
- **회로 차단기(Circuit breaker)** — 에이전트+프로바이더+모델별 자동 장애 조치, 지수 백오프(exponential backoff), 의미론적 오류 분류 기능 제공
- **속도 제한(Rate limiting)** — 업스트림 호스트별(RPM/TPM) 속도 제한 및 프로바이더별 재정의 가능
- **응답 캐시(Response cache)** — SHA-256 지문 인식 기반 메모리 내 LRU 캐시와 선택적 의미론적 유사성 검색 기능 제공
- **우아한 종료(Graceful shutdown)** — 진행 중인 요청에 대해 30초의 유예 기간을 부여하고, MCP 클라이언트 정리 수행
- **컨텍스트 제한 자동 재시도(Context-limit auto-retry)** — 업스트림 컨텍스트 길이 오류 발생 시 요약 및 재시도를 트리거
- **로컬 엔진 생명 주기 관리(Local engine lifecycle)** — 로컬 Ollama 모델을 사전 로드하고 LRU 제거 방식으로 관리
- **구조화된 오류(Structured errors)** — 모든 오류 응답에 프로그램 진단을 위한 `error_kind` 필드를 포함
### MCP 도구 통합 (MCP Tool Integration)
- **도구 검색(Tool discovery)** — 자동 도구 주입을 위해 MCP 서버에 연결
- **다중 턴 실행(Multi-turn execution)** — 도구 호출을 가로채고, MCP 서버에서 실행한 후 결과를 전달
- **인수 보호 장치(Argument guard)** — 게이트웨이가 관리하는 모든 도구 호출은 전송 전에 스키마 검증, 크기 제한, URL 확인(`governance.mcp_guard`)을 거침
- **기본적으로 신뢰하지 않음(Untrusted by default)** — 도구 결과와 자동 검색 메모리 컨텍스트는 모델에 다시 진입하기 전에 `<untrusted-content>`로 표시됨
- **자동 검색(Auto-search)** — 결과를 전달하기 전에 MCP 서버에서 관련 컨텍스트를 사전 가져옴
- **자동 저장(Auto-save)** — 어시스턴트 응답을 MCP 메모리 서버에 영구 저장함
## API 엔드포인트 (API Endpoints)
모든 `/v1/*` 엔드포인트는 `Authorization: Bearer <client_token>` 또는 `Bearer <api_key_token>`를 요구합니다.
API 키는 **RBAC 적용(RBAC enforcement)**을 지원하며, 에이전트 범위 지정, 엔드포인트 화이트리스트, 역할 기반 권한 부여(관리자 계정은 모든 검사를 우회함) 기능을 제공합니다.
| Endpoint | Auth | Description |
|----------|------|-------------|
| `POST /v1/chat/completions` | Bearer + RBAC | OpenAI와 호환되는 채팅 기능으로, SSE 스트리밍, 에이전트 폴백(agent fallback), MCP 멀티턴을 지원합니다. |
... |
자세한 passthrough proxy 사용법은 [`docs/PASSTHROUGH_PROXY.md`](docs/PASSTHROUGH_PROXY.md)를 참조하십시오.
## 런타임 설정 (Runtime Configuration)
Nenya는 배포 이식성(deployment portability)을 위해 표준 환경 변수를 지원합니다:
| Variable | Default | Description |
|----------|---------|-------------|
| `PORT` | `8080` | 리스닝 포트입니다 (`server.listen_addr`를 덮어씁니다). |
... |
사용 예시:
```bash
PORT=9090 HOST=127.0.0.1 ./nenya --config /path/to/config.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
또는 Docker에서:
docker run -e PORT=9090 -p 9090:9090 ghcr.io/gumieri/nenya:latest
문서화 (Documentation)