haechi 1.2.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.ko.md +57 -11
  2. package/README.md +57 -11
  3. package/docs/current/code-review-risk-register-2026-06-16.ko.md +377 -0
  4. package/docs/current/code-review-risk-register-2026-06-16.md +377 -0
  5. package/docs/current/config-version.ko.md +2 -2
  6. package/docs/current/config-version.md +2 -2
  7. package/docs/current/configuration.ko.md +28 -11
  8. package/docs/current/configuration.md +28 -11
  9. package/docs/current/operations-runbook.ko.md +36 -2
  10. package/docs/current/operations-runbook.md +39 -2
  11. package/docs/current/release-process.ko.md +5 -1
  12. package/docs/current/release-process.md +5 -1
  13. package/docs/current/risk-register-release-gate.ko.md +34 -8
  14. package/docs/current/risk-register-release-gate.md +34 -8
  15. package/docs/current/shared-responsibility.ko.md +12 -3
  16. package/docs/current/shared-responsibility.md +12 -3
  17. package/docs/current/threat-model.ko.md +7 -3
  18. package/docs/current/threat-model.md +7 -3
  19. package/examples/local-proxy-demo/README.md +51 -0
  20. package/examples/local-proxy-demo/demo.mjs +144 -0
  21. package/examples/local-proxy-demo/demo.tape +19 -0
  22. package/examples/local-proxy-demo/live-demo.mjs +121 -0
  23. package/examples/local-proxy-demo/live-demo.tape +25 -0
  24. package/haechi.config.example.json +2 -1
  25. package/package.json +3 -1
  26. package/packages/cli/bin/haechi.mjs +95 -5
  27. package/packages/cli/runtime.mjs +61 -1
  28. package/packages/core/index.mjs +15 -0
  29. package/packages/crypto/index.mjs +42 -20
  30. package/packages/filter/index.mjs +679 -6
  31. package/packages/privacy-profiles/index.mjs +72 -3
  32. package/packages/protocol-adapters/index.mjs +99 -1
  33. package/packages/proxy/index.mjs +270 -29
  34. package/packages/ssrf/index.mjs +60 -4
  35. package/packages/stream-filter/index.mjs +194 -17
package/README.ko.md CHANGED
@@ -8,7 +8,7 @@
8
8
  [![CI](https://github.com/raeseoklee/haechi/actions/workflows/ci.yml/badge.svg)](https://github.com/raeseoklee/haechi/actions/workflows/ci.yml)
9
9
  [![license](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
10
10
  [![node](https://img.shields.io/node/v/haechi)](https://nodejs.org)
11
- [![status](https://img.shields.io/badge/status-stable%201.1-brightgreen)](docs/current/api-stability.md)
11
+ [![status](https://img.shields.io/badge/status-stable%201.2-brightgreen)](docs/current/api-stability.md)
12
12
 
13
13
  [English](README.md) | **한국어**
14
14
 
@@ -18,7 +18,7 @@ Haechi는 LLM·MCP·vLLM·Ollama 및 에이전트 payload가 모델, 도구, 로
18
18
 
19
19
  이 저장소는 로컬 개발, 보안 설계 검토, 자체 호스팅 통합 실험을 위한 것입니다. 컴플라이언스를 보장하지는 않습니다.
20
20
 
21
- **1.0.0이 첫 stable 릴리스입니다.** 1.0부터 public API는 strict semver 하의 frozen 계약입니다. `package.json`의 `exports` 표면, CLI의 기계 판독 동작, audit event schema, config key shape이 모두 major 버전 계약의 일부이며, 문서화된 deprecation 정책과 in-minor 보안 예외 하나가 함께 적용됩니다. [`docs/current/api-stability.md`](docs/current/api-stability.md)를 참고하세요. 네 개의 `haechi-*` 위성은 pre-1.0으로 유지되며 core와 독립적으로 버저닝됩니다.
21
+ **1.0.0이 첫 stable 릴리스입니다.** 1.0부터 public API는 strict semver 하의 frozen 계약입니다. `package.json`의 `exports` 표면, CLI의 기계 판독 동작, audit event schema, config key shape이 모두 major 버전 계약의 일부이며, 문서화된 deprecation 정책과 in-minor 보안 예외 하나가 함께 적용됩니다. [`docs/current/api-stability.md`](docs/current/api-stability.md)를 참고하세요. `haechi-*` 위성은 pre-1.0으로 유지되며 core와 독립적으로 버저닝됩니다([위성 패키지](#위성-패키지) 참고).
22
22
 
23
23
  현재 범위는 로컬 도입에 초점을 맞춥니다.
24
24
 
@@ -30,6 +30,28 @@ Haechi는 LLM·MCP·vLLM·Ollama 및 에이전트 payload가 모델, 도구, 로
30
30
  - `haechi audit-verify`: audit hash chain을 검증하고 head hash를 출력합니다
31
31
  - `haechi mcp-wrap -- <command>`: MCP 서버를 양방향 stdio 보호로 감쌉니다
32
32
 
33
+ ## 데모
34
+
35
+ <p align="center">
36
+ <img src="https://raw.githubusercontent.com/raeseoklee/haechi/main/docs/assets/haechi-demo.gif" alt="Haechi 라이브 end-to-end 데모(실제 모델): 탐지 후 tokenize/mask/redact, 모델은 마스킹된 전화만 반복, 무평문 감사, 라이브 readiness + Prometheus metrics, 카드 차단" width="900">
37
+ </p>
38
+
39
+ 위 녹화는 실제 self-hosted 모델(vLLM의 Qwen3.6-35B)에 붙인 **라이브** end-to-end 실행입니다(`enforce` 모드). 모델에게 받은 전화번호를 그대로 반복하라고 시키면 — 진짜 번호는 모델에 도달조차 하지 않았으므로 **마스킹된** 형태만 돌려줄 수 있습니다. 무평문 감사, 라이브 `/__haechi/ready` + `/__haechi/metrics`, upstream 호출 전에 fail-closed로 차단되는 카드도 함께 보여줍니다.
40
+
41
+ 직접 실행해 보십시오 — 백엔드 없이 재현 가능한 스텁 버전:
42
+
43
+ ```bash
44
+ npm run demo
45
+ ```
46
+
47
+ …또는 본인의 OpenAI-호환 서버 상대로:
48
+
49
+ ```bash
50
+ HAECHI_LIVE_UPSTREAM=http://127.0.0.1:8000 node examples/local-proxy-demo/live-demo.mjs
51
+ ```
52
+
53
+ [`examples/local-proxy-demo/`](examples/local-proxy-demo/)를 참고하십시오.
54
+
33
55
  ## 설치
34
56
 
35
57
  ```bash
@@ -76,7 +98,7 @@ upstream 요청은 `limits.upstreamTimeoutMs`(기본값 120000) 이후 타임아
76
98
 
77
99
  ## Local Inference Servers
78
100
 
79
- Haechi는 OpenAI 호환 서버, vLLM, Ollama, llama.cpp용 프로토콜 adapter 프리셋을 제공합니다.
101
+ Haechi는 OpenAI 호환 서버, vLLM, Ollama, llama.cpp, Anthropic Messages API, 그리고 Google Gemini API용 프로토콜 adapter 프리셋을 제공합니다.
80
102
 
81
103
  ```json
82
104
  {
@@ -96,7 +118,7 @@ Haechi는 OpenAI 호환 서버, vLLM, Ollama, llama.cpp용 프로토콜 adapter
96
118
  }
97
119
  ```
98
120
 
99
- 그런 다음 OpenAI 호환 클라이언트를 `http://127.0.0.1:11016/v1`으로 향하게 합니다. Ollama 네이티브 API는 `target.adapter: "ollama"`를 사용하고 proxy를 통해 `/api/chat` 또는 `/api/generate`를 호출하세요.
121
+ 그런 다음 OpenAI 호환 클라이언트를 `http://127.0.0.1:11016/v1`으로 향하게 합니다. Ollama 네이티브 API는 `target.adapter: "ollama"`를 사용하고 proxy를 통해 `/api/chat` 또는 `/api/generate`를 호출하세요. Claude는 `target.type: "anthropic"`을 설정하고 `/v1/messages`(또는 `/v1/messages/count_tokens`, `/v1/complete`)를 호출하세요. 클라이언트의 `x-api-key`/`anthropic-version` 헤더는 upstream으로 전달됩니다(upstream 헤더 허용목록에 포함되어 있습니다). Gemini는 `target.type: "gemini"`를 설정하고 모델이 경로에 포함된 엔드포인트 `/v1beta/models/{model}:generateContent`(또는 `:streamGenerateContent`, `:countTokens`, `:embedContent`, `:batchEmbedContents`)를 호출하세요. 클라이언트의 `x-goog-api-key`(또는 `?key=`)는 upstream으로 전달됩니다. proxy는 명시적 허용목록의 헤더만 전달하고 클라이언트의 주변 credential은 절대 전달하지 않습니다 — [Gateway 인증과 upstream 인증의 분리](#gateway-인증과-upstream-인증의-분리-헤더-전달)를 참고하세요.
100
122
 
101
123
  ## 토큰 왕복
102
124
 
@@ -173,14 +195,37 @@ haechi auth revoke <id>
173
195
  - **Rate limit**: identity별 분당 요청 수 → `429`(인메모리, 프로세스별).
174
196
  - Audit 이벤트에는 **PII-safe** `identity`(keyed-HMAC subject/issuer이며 원시 값이 아닙니다)와 매핑된 `profile`이 들어가고, `auth_denied`/`model_not_allowed`/`rate_limited` 결정에는 credential이 포함되지 않습니다. `/__haechi/health`는 인증 없이 접근할 수 있습니다.
175
197
 
176
- JWT/JWKS 인증과 KMS 기반 key custody는 `haechi-*` 위성 패키지로 제공되며, 각각 core와 독립적으로 버저닝·발행됩니다. 위성은 pre-1.0으로 유지되며 `haechi` peer 범위를 `>=0.8.0 <2.0.0`으로 선언합니다(상한이 core major를 따라가므로 core 1.0.0이 위성 설치를 깨뜨리지 않습니다).
198
+ ### Gateway 인증과 upstream 인증의 분리 (헤더 전달)
199
+
200
+ Haechi는 **gateway-클라이언트 인증**과 **upstream-제공자 인증**을 분리하며, 요청 헤더를 모델 upstream으로 무조건 전달하지 않습니다. proxy는 **기본 차단(default-drop) 허용목록**을 적용합니다. 알려진 안전한 헤더 집합만 모델 제공자 경계로 넘어가고, 클라이언트의 주변(ambient) credential은 폐기됩니다.
201
+
202
+ - **`auth.provider: bearer` / `external` / `plugin` (gateway가 클라이언트를 인증).** 클라이언트의 `Authorization` 헤더는 Haechi가 클라이언트 인증에 사용한 **gateway credential**이므로 **폐기됩니다** — upstream으로 절대 전달되지 않습니다. 이로써 gateway 토큰이 신뢰 경계를 넘어 모델 제공자로 유출되는 것을 막습니다.
203
+ - **`auth.provider: none` (gateway 인증 없음).** 클라이언트의 `Authorization` 헤더는 **upstream 제공자 키**로 간주되어 **전달됩니다**(클라이언트가 `Authorization`에 모델 API 키를 넣는 OpenAI 호환 pass-through 패턴).
204
+ - **모드와 무관하게 항상 폐기:** `Cookie`, `Set-Cookie`, `Proxy-Authorization`, hop-by-hop 헤더(`Connection`, `Keep-Alive`, `TE`, `Trailer`, `Transfer-Encoding`, `Upgrade`), 그리고 허용목록에 없는 모든 헤더.
205
+ - **항상 전달(제공자/어댑터 헤더):** `x-api-key`, `anthropic-version`, `anthropic-beta`, `x-goog-api-key`, `openai-organization`, `openai-beta`, `accept`, `accept-language`, `user-agent`, 그리고 `content-type`(`application/json`으로 재작성).
206
+ - **예외 통로:** 특이한 upstream이 추가 헤더를 요구하면 그 소문자 이름을 `target.forwardHeaders`에 나열하십시오(예: `"forwardHeaders": ["x-tenant-id"]`). 이는 허용목록을 **추가로 넓히기만** 할 수 있으며, 항상 폐기되는 credential/hop-by-hop 헤더를 다시 켤 수는 없습니다(해당 이름은 설정 시점에 fail-closed로 거부됩니다).
207
+
208
+ JWT/JWKS 인증과 KMS 기반 key custody(및 기타 선택 기능)는 **`haechi-*` 위성 패키지**로 제공됩니다 — 아래 [위성 패키지](#위성-패키지)를 참고하세요.
209
+
210
+ ## 위성 패키지
211
+
212
+ 선택 기능은 **npm에 독립 발행되는 `haechi-*` 패키지**로 제공됩니다 — 각각 core와 별도로 버저닝되고, 기본적으로 `node:` 전용이며(KMS나 Redis 클라이언트 같은 무거운 SDK는 optional peer), `haechi` peer 범위를 `>=0.8.0 <2.0.0`으로 선언합니다(상한이 core major를 따라가므로 core minor가 위성 설치를 깨뜨리지 않습니다).
213
+
214
+ **위성과 함께 core를 반드시 설치하세요** — `haechi`는 **번들되지 않은 peer dependency**이므로, 위성만으로는 동작하지 않습니다:
215
+
216
+ ```bash
217
+ npm install haechi haechi-<satellite>
218
+ ```
177
219
 
178
- - [`haechi-auth-jwt`](satellites/auth-jwt/)(0.2.1) 헤드리스 JWKS bearer 검증. 재사용 가능한 JWS 검증기(`createJwtVerifier`)를 추가로 export합니다.
179
- - [`haechi-crypto-kms`](satellites/crypto-kms/)(0.2.1) — 실제 KMS 클라이언트 기반 envelope 암호화. AWS에 더해 GCP(`./gcp`), Azure(`./azure`), HashiCorp Vault Transit(`./vault`, `node:` 전용) 백엔드를 지원합니다.
180
- - [`haechi-dashboard`](satellites/dashboard/)(0.1.2) audit 로그와 hash chain 상태를 보는 zero-dependency 읽기 전용 audit 뷰어(`node:http`)입니다.
181
- - [`haechi-auth-oidc`](satellites/auth-oidc/)(0.1.2) 대시보드의 사람 로그인을 담당하는 대화형 OIDC 세션 브로커(authorization-code + PKCE)입니다.
220
+ | 패키지 | 추가하는 기능 |
221
+ |---|---|
222
+ | [`haechi-auth-jwt`](satellites/auth-jwt/) | 헤드리스 JWKS bearer(JWT) `authProvider`. 재사용 가능한 JWS 검증기(`createJwtVerifier`)를 추가로 export합니다. |
223
+ | [`haechi-auth-oidc`](satellites/auth-oidc/) | 대화형 OIDC 세션 브로커(authorization-code + PKCE) — 대시보드의 사람 로그인. `haechi-auth-jwt`를 재사용합니다. |
224
+ | [`haechi-crypto-kms`](satellites/crypto-kms/) | `keys.provider: external`용 envelope 암호화 `cryptoProvider` — AWS, GCP(`./gcp`), Azure(`./azure`), HashiCorp Vault Transit(`./vault`, `node:` 전용) 백엔드. |
225
+ | [`haechi-dashboard`](satellites/dashboard/) | audit 로그와 hash chain 상태를 보는 zero-dependency 읽기 전용 audit 뷰어(`node:http`). |
226
+ | [`haechi-ratelimit-redis`](satellites/ratelimit-redis/) | `providers.rateLimiter` 주입 시임을 통한 다중 복제용 공유 저장소(Redis 기반) `rateLimiter`. |
182
227
 
183
- 위성은 기본적으로 `node:` 전용이며(무거운 SDK는 optional peer), core zero-dependency로 유지합니다.
228
+ 패키지의 README가 사용법과 정확한 peer 요구사항을 다룹니다. 위성의 무거운 SDK는 해당 백엔드를 쓸 때만 설치되는 optional peer core zero-dependency로 유지됩니다.
184
229
 
185
230
  ## 설정
186
231
 
@@ -189,8 +234,9 @@ JWT/JWKS 인증과 KMS 기반 key custody는 `haechi-*` 위성 패키지로 제
189
234
  | 키 | 기본값 | 설명 |
190
235
  |---|---|---|
191
236
  | `mode` / `policy.mode` | `dry-run` | `dry-run`과 `report-only`는 탐지와 audit만 하고, `enforce`는 변환/차단을 적용합니다. `policy.mode`가 `mode`보다 우선합니다 |
192
- | `target.type` / `target.adapter` | `llm-http` / `openai-compatible` | upstream 프로토콜: `openai-compatible`, `vllm-openai`, `ollama`, `llama-cpp`. 알 수 없는 type은 fail-closed로 처리됩니다 |
237
+ | `target.type` / `target.adapter` | `llm-http` / `openai-compatible` | upstream 프로토콜: `openai-compatible`, `vllm-openai`, `ollama`, `llama-cpp`, `anthropic`, `gemini`. 알 수 없는 type은 fail-closed로 처리됩니다 |
193
238
  | `target.upstream` | `http://127.0.0.1:9999` | proxy가 요청을 전달하는 유일한 upstream(절대 URL 요청 대상은 거부됩니다) |
239
+ | `target.forwardHeaders` | `[]`(미설정) | 내장 허용목록 외에 upstream으로 전달할 추가 소문자 헤더 이름. 추가만 가능하며, 항상 폐기되는 credential/hop-by-hop 헤더를 다시 켤 수는 없습니다 |
194
240
  | `proxy.host` / `proxy.port` | `127.0.0.1` / `11016` | proxy 바인드 주소. 아래 remote 바인딩 참고 |
195
241
  | `responseProtection.enabled` | `false` | upstream JSON 응답을 검사합니다. `failureMode: fail-closed`는 비JSON/압축/대용량 응답을 거부합니다 |
196
242
  | `responseProtection.maxBytes` | `1048576` | 응답 크기 상한 — `failureMode: allow`에서도 적용됩니다 |
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
  [![CI](https://github.com/raeseoklee/haechi/actions/workflows/ci.yml/badge.svg)](https://github.com/raeseoklee/haechi/actions/workflows/ci.yml)
9
9
  [![license](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
10
10
  [![node](https://img.shields.io/node/v/haechi)](https://nodejs.org)
11
- [![status](https://img.shields.io/badge/status-stable%201.1-brightgreen)](docs/current/api-stability.md)
11
+ [![status](https://img.shields.io/badge/status-stable%201.2-brightgreen)](docs/current/api-stability.md)
12
12
 
13
13
  **English** | [한국어](README.ko.md)
14
14
 
@@ -18,7 +18,7 @@ The name comes from Haechi, a Korean guardian figure associated with discernment
18
18
 
19
19
  This repository is intended for local development, security design review, and self-hosted integration experiments. It is not a compliance guarantee.
20
20
 
21
- **1.0.0 is the first stable release.** From 1.0 the public API is a frozen contract under strict semver: the `package.json` `exports` surface, the CLI's machine-readable behavior, the audit event schema, and the config key shape are all part of the major-versioned contract, with a documented deprecation policy and a one in-minor security exception. See [`docs/current/api-stability.md`](docs/current/api-stability.md). The four `haechi-*` satellites stay pre-1.0 and version independently of core.
21
+ **1.0.0 is the first stable release.** From 1.0 the public API is a frozen contract under strict semver: the `package.json` `exports` surface, the CLI's machine-readable behavior, the audit event schema, and the config key shape are all part of the major-versioned contract, with a documented deprecation policy and a one in-minor security exception. See [`docs/current/api-stability.md`](docs/current/api-stability.md). The `haechi-*` satellites stay pre-1.0 and version independently of core (see [Satellite packages](#satellite-packages)).
22
22
 
23
23
  The current scope focuses on local adoption:
24
24
 
@@ -30,6 +30,28 @@ The current scope focuses on local adoption:
30
30
  - `haechi audit-verify`: verify the audit hash chain and print its head hash
31
31
  - `haechi mcp-wrap -- <command>`: wrap an MCP server with bidirectional stdio protection
32
32
 
33
+ ## Demo
34
+
35
+ <p align="center">
36
+ <img src="https://raw.githubusercontent.com/raeseoklee/haechi/main/docs/assets/haechi-demo.gif" alt="Haechi live end-to-end demo against a real model: detection then tokenize/mask/redact, the masked phone the model can only repeat, a no-plaintext audit, live readiness + Prometheus metrics, and a blocked card" width="900">
37
+ </p>
38
+
39
+ The recording above is a **live** end-to-end run against a real self-hosted model (Qwen3.6-35B on vLLM) in `enforce` mode. The model is asked to repeat the phone number it was given — and it can only return the **masked** form, because the real number never reached it. It also shows the no-plaintext audit, the live `/__haechi/ready` + `/__haechi/metrics` surface, and a card blocked fail-closed before any upstream call.
40
+
41
+ Run it yourself — a no-backend, reproducible version with a stub upstream:
42
+
43
+ ```bash
44
+ npm run demo
45
+ ```
46
+
47
+ …or against your own OpenAI-compatible server:
48
+
49
+ ```bash
50
+ HAECHI_LIVE_UPSTREAM=http://127.0.0.1:8000 node examples/local-proxy-demo/live-demo.mjs
51
+ ```
52
+
53
+ See [`examples/local-proxy-demo/`](examples/local-proxy-demo/).
54
+
33
55
  ## Install
34
56
 
35
57
  ```bash
@@ -76,7 +98,7 @@ Upstream requests time out after `limits.upstreamTimeoutMs` (default 120000) and
76
98
 
77
99
  ## Local Inference Servers
78
100
 
79
- Haechi includes protocol adapter presets for OpenAI-compatible servers, vLLM, Ollama, and llama.cpp.
101
+ Haechi includes protocol adapter presets for OpenAI-compatible servers, vLLM, Ollama, llama.cpp, the Anthropic Messages API, and the Google Gemini API.
80
102
 
81
103
  ```json
82
104
  {
@@ -96,7 +118,7 @@ Haechi includes protocol adapter presets for OpenAI-compatible servers, vLLM, Ol
96
118
  }
97
119
  ```
98
120
 
99
- Then point an OpenAI-compatible client at `http://127.0.0.1:11016/v1`. For Ollama native APIs, use `target.adapter: "ollama"` and call `/api/chat` or `/api/generate` through the proxy.
121
+ Then point an OpenAI-compatible client at `http://127.0.0.1:11016/v1`. For Ollama native APIs, use `target.adapter: "ollama"` and call `/api/chat` or `/api/generate` through the proxy. For Claude, set `target.type: "anthropic"` and call `/v1/messages` (or `/v1/messages/count_tokens`, `/v1/complete`); the client's `x-api-key`/`anthropic-version` headers are forwarded to the upstream (they are on the upstream header allowlist). For Gemini, set `target.type: "gemini"` and call the model-in-path endpoints `/v1beta/models/{model}:generateContent` (or `:streamGenerateContent`, `:countTokens`, `:embedContent`, `:batchEmbedContents`); the client's `x-goog-api-key` (or `?key=`) is forwarded to the upstream. The proxy forwards only an explicit allowlist of headers and never forwards ambient client credentials — see [Gateway auth vs upstream auth](#gateway-auth-vs-upstream-auth-header-forwarding).
100
122
 
101
123
  ## Token Round-Trip
102
124
 
@@ -173,14 +195,37 @@ haechi auth revoke <id>
173
195
  - **Rate limit**: per-identity requests-per-minute → `429` (in-memory, per-process).
174
196
  - Audit events carry the **PII-safe** `identity` (keyed-HMAC subject/issuer, never raw values) and the resolved `profile`; `auth_denied` / `model_not_allowed` / `rate_limited` decisions never include credentials. `/__haechi/health` stays unauthenticated.
175
197
 
176
- JWT/JWKS auth and KMS-backed key custody ship as `haechi-*` satellite packages, each versioned and published independently of core. They remain pre-1.0 and declare a `haechi` peer range of `>=0.8.0 <2.0.0` (the upper bound tracks the core major, so core 1.0.0 does not break satellite installs):
198
+ ### Gateway auth vs upstream auth (header forwarding)
199
+
200
+ Haechi separates **gateway-client authentication** from **upstream-provider authentication**, and does **not** blindly forward your request headers to the model upstream. The proxy applies a **default-drop allowlist**: only a known-safe set of headers crosses the trust boundary into the model provider, and ambient client credentials are dropped.
201
+
202
+ - **`auth.provider: bearer` / `external` / `plugin` (the gateway authenticates the client).** The client's `Authorization` header is the **gateway credential** Haechi consumed to authenticate the client, so it is **dropped** — it is never forwarded to the upstream. This prevents leaking a gateway token across the trust boundary into the model provider.
203
+ - **`auth.provider: none` (no gateway auth).** The client's `Authorization` header is treated as the **upstream provider key** and **is forwarded** (the OpenAI-compatible pass-through pattern, where the client puts the model API key in `Authorization`).
204
+ - **Always dropped, regardless of mode:** `Cookie`, `Set-Cookie`, `Proxy-Authorization`, and hop-by-hop headers (`Connection`, `Keep-Alive`, `TE`, `Trailer`, `Transfer-Encoding`, `Upgrade`), plus any header not on the allowlist.
205
+ - **Always forwarded (the provider/adapter headers):** `x-api-key`, `anthropic-version`, `anthropic-beta`, `x-goog-api-key`, `openai-organization`, `openai-beta`, `accept`, `accept-language`, `user-agent`, and `content-type` (rewritten to `application/json`).
206
+ - **Escape hatch:** if an unusual upstream needs an extra header, list its lowercase name in `target.forwardHeaders` (e.g. `"forwardHeaders": ["x-tenant-id"]`). This can only **widen** the allowlist additively — it can never re-enable an always-dropped credential or hop-by-hop header (those names are rejected fail-closed at config time).
207
+
208
+ JWT/JWKS auth and KMS-backed key custody (and other optional capabilities) ship as the **`haechi-*` satellite packages** — see [Satellite packages](#satellite-packages) below.
209
+
210
+ ## Satellite packages
211
+
212
+ Optional capabilities ship as independently-published **`haechi-*` packages on npm** — each versioned separately from core, `node:`-only by default (heavy SDKs like a KMS or Redis client are optional peers), and each declaring a `haechi` peer range of `>=0.8.0 <2.0.0` (the upper bound tracks the core major, so a core minor never breaks a satellite install).
213
+
214
+ **Install the core alongside any satellite** — `haechi` is a **peer dependency, not bundled**, so a satellite does nothing on its own:
215
+
216
+ ```bash
217
+ npm install haechi haechi-<satellite>
218
+ ```
177
219
 
178
- - [`haechi-auth-jwt`](satellites/auth-jwt/) (0.2.1) headless JWKS bearer verification; additively exports a reusable JWS verifier (`createJwtVerifier`).
179
- - [`haechi-crypto-kms`](satellites/crypto-kms/) (0.2.1) — envelope encryption with a real KMS client; AWS plus GCP (`./gcp`), Azure (`./azure`), and HashiCorp Vault Transit (`./vault`, `node:`-only) backends.
180
- - [`haechi-dashboard`](satellites/dashboard/) (0.1.2) a zero-dependency, read-only audit viewer (`node:http`) over the audit log and its hash-chain status.
181
- - [`haechi-auth-oidc`](satellites/auth-oidc/) (0.1.2) an interactive OIDC session broker (authorization-code + PKCE) that provides the dashboard's human login.
220
+ | Package | What it adds |
221
+ |---|---|
222
+ | [`haechi-auth-jwt`](satellites/auth-jwt/) | Headless JWKS bearer (JWT) `authProvider`; additively exports a reusable JWS verifier (`createJwtVerifier`). |
223
+ | [`haechi-auth-oidc`](satellites/auth-oidc/) | Interactive OIDC session broker (authorization-code + PKCE) the dashboard's human login. Reuses `haechi-auth-jwt`. |
224
+ | [`haechi-crypto-kms`](satellites/crypto-kms/) | Envelope-encryption `cryptoProvider` for `keys.provider: external` — AWS, GCP (`./gcp`), Azure (`./azure`), and HashiCorp Vault Transit (`./vault`, `node:`-only) backends. |
225
+ | [`haechi-dashboard`](satellites/dashboard/) | Zero-dependency, read-only audit viewer (`node:http`) over the audit log and its hash-chain status. |
226
+ | [`haechi-ratelimit-redis`](satellites/ratelimit-redis/) | Shared-store (Redis-backed) `rateLimiter` for multi-replica deployments, via the `providers.rateLimiter` injection seam. |
182
227
 
183
- The satellites are `node:`-only by default (heavy SDKs are optional peers) and keep core zero-dependency.
228
+ Each package's README covers its usage and exact peer requirements. The satellites keep core zero-dependency: their heavy SDKs are optional peers installed only when you use that backend.
184
229
 
185
230
  ## Configuration
186
231
 
@@ -189,8 +234,9 @@ The satellites are `node:`-only by default (heavy SDKs are optional peers) and k
189
234
  | Key | Default | Meaning |
190
235
  |---|---|---|
191
236
  | `mode` / `policy.mode` | `dry-run` | `dry-run` and `report-only` detect + audit only; `enforce` transforms/blocks. `policy.mode` wins over `mode` |
192
- | `target.type` / `target.adapter` | `llm-http` / `openai-compatible` | Upstream protocol: `openai-compatible`, `vllm-openai`, `ollama`, `llama-cpp`. Unknown types fail closed |
237
+ | `target.type` / `target.adapter` | `llm-http` / `openai-compatible` | Upstream protocol: `openai-compatible`, `vllm-openai`, `ollama`, `llama-cpp`, `anthropic`, `gemini`. Unknown types fail closed |
193
238
  | `target.upstream` | `http://127.0.0.1:9999` | The only upstream the proxy will forward to (absolute-URL request targets are rejected) |
239
+ | `target.forwardHeaders` | `[]` (unset) | Extra lowercase header names to forward upstream, beyond the built-in allowlist. Additive only; cannot re-enable always-dropped credential/hop-by-hop headers |
194
240
  | `proxy.host` / `proxy.port` | `127.0.0.1` / `11016` | Proxy bind address. See remote binding below |
195
241
  | `responseProtection.enabled` | `false` | Inspect upstream JSON responses. `failureMode: fail-closed` rejects non-JSON/compressed/oversized responses |
196
242
  | `responseProtection.maxBytes` | `1048576` | Hard response size cap — enforced even in `failureMode: allow` |