haechi 0.5.0 → 0.6.0
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.
- package/README.ko.md +37 -0
- package/README.md +37 -0
- package/docs/README.md +1 -0
- package/docs/current/api-stability.ko.md +3 -1
- package/docs/current/api-stability.md +3 -1
- package/docs/current/configuration.ko.md +25 -2
- package/docs/current/configuration.md +25 -2
- package/docs/current/release-0.6-implementation-scope.ko.md +151 -0
- package/docs/current/release-0.6-implementation-scope.md +151 -0
- package/docs/current/risk-register-release-gate.ko.md +2 -2
- package/docs/current/risk-register-release-gate.md +3 -2
- package/docs/current/threat-model.ko.md +3 -1
- package/docs/current/threat-model.md +3 -1
- package/haechi.config.example.json +10 -0
- package/package.json +3 -2
- package/packages/auth/index.mjs +170 -0
- package/packages/cli/bin/haechi.mjs +91 -6
- package/packages/cli/runtime.mjs +103 -5
- package/packages/core/index.mjs +18 -7
- package/packages/policy/index.mjs +82 -0
- package/packages/proxy/index.mjs +134 -8
package/README.ko.md
CHANGED
|
@@ -137,6 +137,38 @@ stdio MCP 서버를 래핑하여 양방향 트래픽을 필터링한다 — MCP
|
|
|
137
137
|
|
|
138
138
|
이 휴리스틱은 prompt injection에 대한 완전한 방어책이 아니다. `docs/current/threat-model.md`를 참고하라.
|
|
139
139
|
|
|
140
|
+
## 인증 및 클라이언트별 통제
|
|
141
|
+
|
|
142
|
+
하나의 host 앞에 여러 클라이언트/에이전트를 두는 경우, bearer auth를 활성화하고 각 클라이언트를 policy profile에 바인딩한다. Token은 별도의 `.haechi/auth.json`(0600)에 keyed-HMAC 해시로만 저장된다:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
haechi auth add --type service --scope team:eng --label env=prod # 토큰을 한 번만 출력
|
|
146
|
+
haechi auth list # 토큰을 표시하지 않음
|
|
147
|
+
haechi auth revoke <id>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
```json
|
|
151
|
+
{
|
|
152
|
+
"auth": { "provider": "bearer" },
|
|
153
|
+
"policy": {
|
|
154
|
+
"mode": "enforce", "presets": ["llm-redact"],
|
|
155
|
+
"profiles": {
|
|
156
|
+
"strict": { "presets": ["strict-block"] },
|
|
157
|
+
"internal": { "presets": ["llm-redact"], "modelAllowlist": ["llama3"], "rate": { "requestsPerMinute": 120 } }
|
|
158
|
+
},
|
|
159
|
+
"profileBinding": { "byScope": { "team:eng": "internal" }, "default": "strict" }
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
- **Bearer auth** (`auth.provider: bearer`): 클라이언트는 `Authorization: Bearer <token>`을 전송한다. 없거나 잘못되었거나 revoke된 경우 → `401` (바디는 읽지 않으며 upstream에 도달하지 않는다). `provider: none`(기본값)은 동작을 그대로 유지하며, `external`은 주입된 `authProvider`가 필요하다.
|
|
165
|
+
- **Named profiles**: 인증된 identity는 **scope → label → 필수 `default`** 순으로 profile로 resolve된다(매칭되지 않거나 익명인 경우 `default`로 fail-closed). Profile은 기본 policy를 재정의하며 자체 `modelAllowlist`와 `rate`를 가질 수 있다.
|
|
166
|
+
- **Model allowlist**: 허용되지 않은 `model`을 가진 요청 → `403`.
|
|
167
|
+
- **Rate limit**: identity별 분당 요청 수 → `429` (인메모리, 프로세스별).
|
|
168
|
+
- Audit 이벤트는 **PII-safe** `identity`(keyed-HMAC subject/issuer, 원시 값 아님)와 resolve된 `profile`을 포함하며, `auth_denied` / `model_not_allowed` / `rate_limited` 결정에는 credentials가 포함되지 않는다. `/__haechi/health`는 인증 없이 접근 가능하다.
|
|
169
|
+
|
|
170
|
+
OIDC/JWT provider와 KMS 기반 key custody는 0.7+ 위성 패키지이다.
|
|
171
|
+
|
|
140
172
|
## 설정
|
|
141
173
|
|
|
142
174
|
`haechi init`은 `haechi.config.json`을 생성하며, 비밀 정보를 포함하지 않는 템플릿은 `haechi.config.example.json`에 있다. 모든 키는 fail-closed 방식으로 검증된다 — 알 수 없거나 잘못된 형식의 값은 시작을 거부한다.
|
|
@@ -164,6 +196,9 @@ stdio MCP 서버를 래핑하여 양방향 트래픽을 필터링한다 — MCP
|
|
|
164
196
|
| `tokenVault.deterministic` / `deterministicTypes` / `detokenizeResponses` | `false` / `null` / `false` | 토큰 왕복 (위 참고) |
|
|
165
197
|
| `privacy.profile` | `null` | `kr-pipa`, `eu-gdpr`, `us-general` 기준 action (강화 전용) |
|
|
166
198
|
| `mcp.allowedMethods` | `initialize`, `tools/call`, `resources/read`, `prompts/get` | `mcp-stdio`/`mcp-wrap`에서 클라이언트가 호출할 수 있는 method allowlist |
|
|
199
|
+
| `auth.provider` / `auth.store` | `none` / `.haechi/auth.json` | `none`/`bearer`/`external`. Bearer token은 keyed-HMAC 해시로 저장 (0600) |
|
|
200
|
+
| `policy.profiles` / `policy.profileBinding` | — | 클라이언트별 named policy profile; scope → label → 필수 `default` 순으로 바인딩 |
|
|
201
|
+
| `policy.modelAllowlist` / `policy.rate` | — | 허용된 모델 이름 (그 외 403); identity별 분당 요청 수 rate limit (429) — profile별로도 설정 가능 |
|
|
167
202
|
|
|
168
203
|
위 표는 빠른 참고용이다. 키별 전체 레퍼런스 — 타입, 검증 규칙, 프리셋, action 강도, 일반적인 설정 — 는 [`docs/current/configuration.md`](docs/current/configuration.md)에 있으며, CLI에서 축약 버전을 출력한다:
|
|
169
204
|
|
|
@@ -225,3 +260,5 @@ Haechi는 로컬 정책 부트스트래핑을 위한 기본 지역별 Privacy Pr
|
|
|
225
260
|
0.4.0은 token round-trip(deterministic tokenization + 요청 스코프 응답 detokenization), `mcp-wrap` 양방향 MCP 필터, `status` 및 `audit-verify` 커맨드, report-only injection detection 휴리스틱을 추가하고, 0.6 인증을 위한 PII-safe `identity`/`authProvider` 계약을 예약한다. `docs/current/release-0.4-implementation-scope.md` 참고.
|
|
226
261
|
|
|
227
262
|
0.5.0은 SSE/NDJSON 스트리밍 응답 검사를 추가한다: `streaming.requestMode: "inspect"`가 bounded sliding buffer로 응답을 stream-filter하여 프레임에 걸쳐 쪼개진 PII도 잡는다(`streaming.maxMatchBytes`). `docs/current/release-0.5-implementation-scope.md` 참고.
|
|
263
|
+
|
|
264
|
+
0.6.0은 인증과 클라이언트별 통제를 추가한다: 해시 기반 token 저장소와 `haechi auth` CLI를 갖춘 내장 bearer auth, identity scope/label로 바인딩되는 named policy profile, model allowlisting, 그리고 identity별 rate limiting — audit 로그에는 PII-safe identity가 기록된다. `docs/current/release-0.6-implementation-scope.md` 참고.
|
package/README.md
CHANGED
|
@@ -137,6 +137,38 @@ Response and tool-result text is screened with heuristic rules for indirect prom
|
|
|
137
137
|
|
|
138
138
|
These heuristics are not a complete defense against prompt injection; see `docs/current/threat-model.md`.
|
|
139
139
|
|
|
140
|
+
## Authentication & Per-Client Controls
|
|
141
|
+
|
|
142
|
+
With multiple clients/agents in front of one host, turn on bearer auth and bind each client to a policy profile. Tokens live in a separate `.haechi/auth.json` (0600), stored only as keyed-HMAC hashes:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
haechi auth add --type service --scope team:eng --label env=prod # prints the token ONCE
|
|
146
|
+
haechi auth list # never shows tokens
|
|
147
|
+
haechi auth revoke <id>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
```json
|
|
151
|
+
{
|
|
152
|
+
"auth": { "provider": "bearer" },
|
|
153
|
+
"policy": {
|
|
154
|
+
"mode": "enforce", "presets": ["llm-redact"],
|
|
155
|
+
"profiles": {
|
|
156
|
+
"strict": { "presets": ["strict-block"] },
|
|
157
|
+
"internal": { "presets": ["llm-redact"], "modelAllowlist": ["llama3"], "rate": { "requestsPerMinute": 120 } }
|
|
158
|
+
},
|
|
159
|
+
"profileBinding": { "byScope": { "team:eng": "internal" }, "default": "strict" }
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
- **Bearer auth** (`auth.provider: bearer`): clients send `Authorization: Bearer <token>`. Missing/invalid/revoked → `401` (the body is never read, upstream is never reached). `provider: none` (default) keeps behavior unchanged; `external` requires an injected `authProvider`.
|
|
165
|
+
- **Named profiles**: each authenticated identity resolves to a profile by **scope → label → required `default`** (fail-closed to `default` for unmatched/anonymous). A profile overrides the base policy and may carry its own `modelAllowlist` and `rate`.
|
|
166
|
+
- **Model allowlist**: a request whose `model` is not allowed → `403`.
|
|
167
|
+
- **Rate limit**: per-identity requests-per-minute → `429` (in-memory, per-process).
|
|
168
|
+
- 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.
|
|
169
|
+
|
|
170
|
+
OIDC/JWT providers and KMS-backed key custody are 0.7+ satellite packages.
|
|
171
|
+
|
|
140
172
|
## Configuration
|
|
141
173
|
|
|
142
174
|
`haechi init` writes `haechi.config.json`; a non-secret template lives at `haechi.config.example.json`. All keys validate fail-closed — unknown or malformed values refuse to start.
|
|
@@ -164,6 +196,9 @@ These heuristics are not a complete defense against prompt injection; see `docs/
|
|
|
164
196
|
| `tokenVault.deterministic` / `deterministicTypes` / `detokenizeResponses` | `false` / `null` / `false` | Token round-trip (see above) |
|
|
165
197
|
| `privacy.profile` | `null` | `kr-pipa`, `eu-gdpr`, `us-general` baseline actions (strengthen-only) |
|
|
166
198
|
| `mcp.allowedMethods` | `initialize`, `tools/call`, `resources/read`, `prompts/get` | Client-callable method allowlist for `mcp-stdio`/`mcp-wrap` |
|
|
199
|
+
| `auth.provider` / `auth.store` | `none` / `.haechi/auth.json` | `none`/`bearer`/`external`. Bearer tokens stored as keyed-HMAC hashes (0600) |
|
|
200
|
+
| `policy.profiles` / `policy.profileBinding` | — | Named per-client policy profiles bound by scope → label → required `default` |
|
|
201
|
+
| `policy.modelAllowlist` / `policy.rate` | — | Allowed model names (403 otherwise); requests-per-minute rate limit (429) — also settable per profile |
|
|
167
202
|
|
|
168
203
|
The table above is a quick reference. The full per-key reference — types, validation rules, presets, action strength, and common setups — is in [`docs/current/configuration.md`](docs/current/configuration.md), and the CLI prints a condensed version:
|
|
169
204
|
|
|
@@ -225,3 +260,5 @@ Set `privacy.profile` in `haechi.config.json` to apply the profile's default act
|
|
|
225
260
|
0.4.0 adds the token round-trip (deterministic tokenization + request-scoped response detokenization), the `mcp-wrap` bidirectional MCP filter, `status` and `audit-verify` commands, report-only injection detection heuristics, and reserves the PII-safe `identity`/`authProvider` contracts for 0.6 auth. See `docs/current/release-0.4-implementation-scope.md`.
|
|
226
261
|
|
|
227
262
|
0.5.0 adds SSE/NDJSON streaming response inspection: `streaming.requestMode: "inspect"` stream-filters responses with a bounded sliding buffer that catches PII split across frames (`streaming.maxMatchBytes`). See `docs/current/release-0.5-implementation-scope.md`.
|
|
263
|
+
|
|
264
|
+
0.6.0 adds authentication and per-client controls: built-in bearer auth with a hashed token store and `haechi auth` CLI, named policy profiles bound by identity scope/label, model allowlisting, and per-identity rate limiting — with PII-safe identity in the audit log. See `docs/current/release-0.6-implementation-scope.md`.
|
package/docs/README.md
CHANGED
|
@@ -16,6 +16,7 @@ English is the primary documentation language. Korean translations are maintaine
|
|
|
16
16
|
- `docs/current/release-0.3.2-hardening-scope.md`: 0.3.2 security hardening release; first npm developer preview target
|
|
17
17
|
- `docs/current/release-0.4-implementation-scope.md`: 0.4 token round-trip, mcp-wrap, audit-verify/status, identity/authProvider contract reservation
|
|
18
18
|
- `docs/current/release-0.5-implementation-scope.md`: 0.5 SSE/NDJSON streaming response inspection with bounded cross-frame buffer
|
|
19
|
+
- `docs/current/release-0.6-implementation-scope.md`: 0.6 bearer auth, named policy profiles, model allowlist, rate limiting
|
|
19
20
|
- `docs/current/configuration.md`: full configuration reference (every key, defaults, validation, presets, common setups)
|
|
20
21
|
- `docs/current/risk-register-release-gate.md`: release-blocking risks, security/operational risk status, npm release gates (0.3.2 baseline)
|
|
21
22
|
- `docs/current/threat-model.md`: Haechi 0.3.2 trust boundaries, protected assets, key threats and controls
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
- 문서 상태: Draft 0.1
|
|
4
4
|
- 작성일: 2026-06-10
|
|
5
|
-
- 기준 버전: 0.
|
|
5
|
+
- 기준 버전: 0.6.0
|
|
6
6
|
|
|
7
7
|
## 1. 버전 해석
|
|
8
8
|
|
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
- `identity` audit 필드와 `authProvider` 계약 (0.4 예약, 0.6 구현 — 그 전까지 형태 변경 가능)
|
|
43
43
|
- `status` / `audit-verify` CLI 출력 형태
|
|
44
44
|
- `haechi/stream-filter` (`inspectResponseStream`, path helpers) 및 `createStreamProtector` (스트리밍 검사 내부 구현)
|
|
45
|
+
- `haechi/auth` (`createBearerAuthProvider`, token store, `buildIdentity`) 및 `authProvider` 계약
|
|
46
|
+
- `policy.profiles`/`policy.profileBinding`/`modelAllowlist`/`rate` 및 `identity`/`profile` audit 필드
|
|
45
47
|
|
|
46
48
|
## 4. Migration note 기준
|
|
47
49
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
- Status: Draft 0.1
|
|
4
4
|
- Date: 2026-06-10
|
|
5
|
-
- Target version: 0.
|
|
5
|
+
- Target version: 0.6.0
|
|
6
6
|
|
|
7
7
|
## 1. Version Interpretation
|
|
8
8
|
|
|
@@ -42,6 +42,8 @@ The following exports are treated as preview in 0.4.0.
|
|
|
42
42
|
- `identity` audit field and the `authProvider` contract (reserved in 0.4, implemented in 0.6 — shape may change until then)
|
|
43
43
|
- `status` / `audit-verify` CLI output shapes
|
|
44
44
|
- `haechi/stream-filter` (`inspectResponseStream`, path helpers) and `createStreamProtector` (streaming inspection internals)
|
|
45
|
+
- `haechi/auth` (`createBearerAuthProvider`, token store, `buildIdentity`) and the `authProvider` contract
|
|
46
|
+
- `policy.profiles`/`policy.profileBinding`/`modelAllowlist`/`rate` and the `identity`/`profile` audit fields
|
|
45
47
|
|
|
46
48
|
## 4. Migration note criteria
|
|
47
49
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Haechi 설정 레퍼런스
|
|
2
2
|
|
|
3
3
|
- 문서 상태: Living document
|
|
4
|
-
- 기준 버전: 0.
|
|
4
|
+
- 기준 버전: 0.6.0
|
|
5
5
|
|
|
6
6
|
`haechi init`은 `haechi.config.json`을 생성하며, 비밀 정보를 포함하지 않는 템플릿은 `haechi.config.example.json`에 있다. 모든 커맨드는 `--config <path>`로 설정 파일을 읽는다(기본값: `haechi.config.json`). 설정은 **fail-closed 방식으로 검증**된다: 알 수 없는 provider, 범위를 벗어난 숫자, 잘못된 형식의 값은 자동으로 무시되지 않고 로드 시점에 오류를 발생시킨다. `haechi config`는 이 레퍼런스를 출력하며, `haechi status`는 특정 설정 파일의 *실제 적용* 상태를 출력한다.
|
|
7
7
|
|
|
@@ -136,6 +136,29 @@ upstream JSON 응답을 검사한다(기본적으로 꺼져 있음 — 모델로
|
|
|
136
136
|
| `mcp.protectResults` | boolean | `true` | 응답 `result`를 보호한다(injection 휴리스틱도 실행). |
|
|
137
137
|
| `mcp.requireJsonRpc` | boolean | `true` | `jsonrpc: "2.0"`을 요구하며, 규격에 맞지 않는 메시지는 거부된다. |
|
|
138
138
|
|
|
139
|
+
## `auth`
|
|
140
|
+
|
|
141
|
+
| 키 | 타입 / 값 | 기본값 | 설명 |
|
|
142
|
+
|---|---|---|---|
|
|
143
|
+
| `auth.provider` | `none` \| `bearer` \| `external` | `none` | `none` = 인증 없음(identity null). `bearer` = 내장 token auth. `external`은 `createRuntime(config, { authProvider })`를 통해 `authProvider`를 주입해야 한다. |
|
|
144
|
+
| `auth.store` | 경로 | `.haechi/auth.json` | Bearer token 저장소(모드 `0600`). Token은 keyed-HMAC 해시로만 보관되며, 평문은 `haechi auth add` 실행 시 한 번만 표시된다. |
|
|
145
|
+
| `auth.allowedLabelKeys` | 문자열 배열 | `["team", "env", "tier", "role"]` | Token이 가질 수 있는 label 키; 값은 길이가 제한되며 PII를 포함하면 안 된다. |
|
|
146
|
+
|
|
147
|
+
## `policy` profiles & limits
|
|
148
|
+
|
|
149
|
+
기본 `policy` 위에 클라이언트별 통제를 레이어로 추가한다. [Named profiles](#named-profiles) 참고.
|
|
150
|
+
|
|
151
|
+
| 키 | 타입 / 값 | 기본값 | 설명 |
|
|
152
|
+
|---|---|---|---|
|
|
153
|
+
| `policy.profiles` | `{ <name>: { presets?, actions?, modelAllowlist?, rate? } }` | `{}` | Named profile; 각각 기본 policy를 재정의한다. |
|
|
154
|
+
| `policy.profileBinding` | `{ byScope?, byLabel?, default }` | 미설정 | identity scope/label(`"k=v"` 형태)을 profile 이름으로 매핑한다. `profiles`가 설정된 경우 `default`는 **필수**이며 가장 엄격한 profile이어야 한다(fail-closed). |
|
|
155
|
+
| `policy.modelAllowlist` | 문자열 배열 | 미설정 | 허용된 `model` 값(기본 레벨; profile별로도 설정 가능). 허용되지 않은 모델 → `403`. 비어 있거나 없으면 모두 허용. |
|
|
156
|
+
| `policy.rate` | `{ requestsPerMinute }` | 미설정 | identity별 요청 rate limit(기본 레벨 또는 profile별). 초과 시 → `429`. 인메모리, 프로세스별. |
|
|
157
|
+
|
|
158
|
+
### Named profiles
|
|
159
|
+
|
|
160
|
+
identity가 인증되면 **scope → label → `default`** 순으로 profile이 resolve된다; scope가 label보다 우선하며 첫 번째 매칭이 적용된다. `profiles`가 없거나 `auth.provider: none`인 경우 기본 policy가 적용된다. Resolve된 profile의 policy 엔진, `modelAllowlist`, `rate`가 해당 요청을 처리한다.
|
|
161
|
+
|
|
139
162
|
## Detection type과 action
|
|
140
163
|
|
|
141
164
|
내장 탐지 `type` 값: `email`, `phone`, `kr_rrn`, `card`, `api_key`, `secret`, `injection`(응답 방향 휴리스틱, 기본 report-only). 커스텀 규칙으로 새로운 type을 추가할 수 있다.
|
|
@@ -207,4 +230,4 @@ haechi proxy --config haechi.config.json --host 0.0.0.0 --allow-remote-bind
|
|
|
207
230
|
|
|
208
231
|
## 검증 요약
|
|
209
232
|
|
|
210
|
-
다음은 로드 시 오류(fail-closed)를 발생시킨다: 알 수 없는 `keys.provider`; 빈 `proxy.host`; 범위를 벗어난 `proxy.port`; `jsonl`이 아닌 `audit.sink`; `local`이 아닌 `tokenVault.provider`; 잘못된 `revealPolicy`; 양수가 아닌 `retentionDays`; boolean이 아닌 `deterministic`/`detokenizeResponses`; 비어 있거나 문자열이 아닌 `deterministicTypes`; 비어 있거나 문자열이 아닌 `mcp.allowedMethods`; boolean이 아닌 `mcp.*` 플래그; 알 수 없는 `privacy.profile`; 잘못된 `responseProtection.failureMode`; 양수가 아닌 `responseProtection.maxBytes`; 잘못된 `streaming.requestMode`; 잘못된 `streaming.responseMode`; 양수가 아닌 `streaming.maxMatchBytes`; 양수가 아닌 `limits.*`; 알 수 없는 `target.type`/`adapter`; 안전하지 않은 커스텀 정규식; `allowUnsafeOverrides` 없이 action을 약화하려는 시도.
|
|
233
|
+
다음은 로드 시 오류(fail-closed)를 발생시킨다: 알 수 없는 `keys.provider`; 빈 `proxy.host`; 범위를 벗어난 `proxy.port`; `jsonl`이 아닌 `audit.sink`; `local`이 아닌 `tokenVault.provider`; 잘못된 `revealPolicy`; 양수가 아닌 `retentionDays`; boolean이 아닌 `deterministic`/`detokenizeResponses`; 비어 있거나 문자열이 아닌 `deterministicTypes`; 비어 있거나 문자열이 아닌 `mcp.allowedMethods`; boolean이 아닌 `mcp.*` 플래그; 알 수 없는 `privacy.profile`; 잘못된 `responseProtection.failureMode`; 양수가 아닌 `responseProtection.maxBytes`; 잘못된 `streaming.requestMode`; 잘못된 `streaming.responseMode`; 양수가 아닌 `streaming.maxMatchBytes`; 잘못된 `auth.provider`; 빈 `auth.store`; 문자열이 아닌 `auth.allowedLabelKeys`; 객체가 아닌 `policy.profiles`; 유효한 `default` 없는 `policy.profileBinding`; 문자열이 아닌 `policy.modelAllowlist`; 양수가 아닌 `policy.rate.requestsPerMinute`; 양수가 아닌 `limits.*`; 알 수 없는 `target.type`/`adapter`; 안전하지 않은 커스텀 정규식; `allowUnsafeOverrides` 없이 action을 약화하려는 시도.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Haechi Configuration Reference
|
|
2
2
|
|
|
3
3
|
- Status: Living document
|
|
4
|
-
- Target version: 0.
|
|
4
|
+
- Target version: 0.6.0
|
|
5
5
|
|
|
6
6
|
`haechi init` writes `haechi.config.json`; a non-secret template is at `haechi.config.example.json`. Every command reads it with `--config <path>` (default `haechi.config.json`). Configuration is **validated fail-closed**: unknown providers, out-of-range numbers, and malformed values throw at load time rather than degrading silently. `haechi config` prints this reference; `haechi status` prints the *effective* state of a given config.
|
|
7
7
|
|
|
@@ -136,6 +136,29 @@ Applies to `mcp-stdio` and `mcp-wrap`.
|
|
|
136
136
|
| `mcp.protectResults` | boolean | `true` | Protect response `result` (and run injection heuristics on it). |
|
|
137
137
|
| `mcp.requireJsonRpc` | boolean | `true` | Require `jsonrpc: "2.0"`; non-conforming messages are rejected. |
|
|
138
138
|
|
|
139
|
+
## `auth`
|
|
140
|
+
|
|
141
|
+
| Key | Type / values | Default | Notes |
|
|
142
|
+
|---|---|---|---|
|
|
143
|
+
| `auth.provider` | `none` \| `bearer` \| `external` | `none` | `none` = no auth (identity null). `bearer` = built-in token auth. `external` requires injecting an `authProvider` via `createRuntime(config, { authProvider })`. |
|
|
144
|
+
| `auth.store` | path | `.haechi/auth.json` | Bearer token store (mode `0600`). Tokens are kept only as keyed-HMAC hashes; the plaintext is shown once by `haechi auth add`. |
|
|
145
|
+
| `auth.allowedLabelKeys` | string array | `["team", "env", "tier", "role"]` | Label keys a token may carry; values are length-limited and must not contain PII. |
|
|
146
|
+
|
|
147
|
+
## `policy` profiles & limits
|
|
148
|
+
|
|
149
|
+
Per-client controls layered on top of the base `policy`. See [Named profiles](#named-profiles).
|
|
150
|
+
|
|
151
|
+
| Key | Type / values | Default | Notes |
|
|
152
|
+
|---|---|---|---|
|
|
153
|
+
| `policy.profiles` | `{ <name>: { presets?, actions?, modelAllowlist?, rate? } }` | `{}` | Named profiles; each overrides the base policy. |
|
|
154
|
+
| `policy.profileBinding` | `{ byScope?, byLabel?, default }` | unset | Maps identity scopes/labels (`"k=v"` for labels) to profile names. `default` is **required** when `profiles` is set and should be the strictest profile (fail-closed). |
|
|
155
|
+
| `policy.modelAllowlist` | string array | unset | Allowed `model` values (base level; also settable per profile). A disallowed model → `403`. Empty/absent = allow all. |
|
|
156
|
+
| `policy.rate` | `{ requestsPerMinute }` | unset | Per-identity request rate limit (base level or per profile). Over the limit → `429`. In-memory, per-process. |
|
|
157
|
+
|
|
158
|
+
### Named profiles
|
|
159
|
+
|
|
160
|
+
When an identity authenticates, its profile resolves in order **scope → label → `default`**; scope precedes label and the first match wins. Without `profiles`, or under `auth.provider: none`, the base policy applies. The resolved profile's policy engine, `modelAllowlist`, and `rate` govern that request.
|
|
161
|
+
|
|
139
162
|
## Detection types & actions
|
|
140
163
|
|
|
141
164
|
Built-in detection `type` values: `email`, `phone`, `kr_rrn`, `card`, `api_key`, `secret`, and `injection` (response-direction heuristic, report-only by default). Custom rules may introduce new types.
|
|
@@ -207,4 +230,4 @@ haechi proxy --config haechi.config.json --host 0.0.0.0 --allow-remote-bind
|
|
|
207
230
|
|
|
208
231
|
## Validation cheatsheet
|
|
209
232
|
|
|
210
|
-
These throw at load (fail-closed): unknown `keys.provider`; empty `proxy.host`; out-of-range `proxy.port`; non-`jsonl` `audit.sink`; non-`local` `tokenVault.provider`; bad `revealPolicy`; non-positive `retentionDays`; non-boolean `deterministic`/`detokenizeResponses`; empty/non-string `deterministicTypes`; empty/non-string `mcp.allowedMethods`; non-boolean `mcp.*` flags; unknown `privacy.profile`; bad `responseProtection.failureMode`; non-positive `responseProtection.maxBytes`; bad `streaming.requestMode`/`streaming.responseMode`; non-positive `streaming.maxMatchBytes`; non-positive `limits.*`; unknown `target.type`/`adapter`; unsafe custom regex; weakening action without `allowUnsafeOverrides`.
|
|
233
|
+
These throw at load (fail-closed): unknown `keys.provider`; empty `proxy.host`; out-of-range `proxy.port`; non-`jsonl` `audit.sink`; non-`local` `tokenVault.provider`; bad `revealPolicy`; non-positive `retentionDays`; non-boolean `deterministic`/`detokenizeResponses`; empty/non-string `deterministicTypes`; empty/non-string `mcp.allowedMethods`; non-boolean `mcp.*` flags; unknown `privacy.profile`; bad `responseProtection.failureMode`; non-positive `responseProtection.maxBytes`; bad `streaming.requestMode`/`streaming.responseMode`; non-positive `streaming.maxMatchBytes`; bad `auth.provider`; empty `auth.store`; non-string `auth.allowedLabelKeys`; non-object `policy.profiles`; `policy.profileBinding` without a valid `default`; non-string `policy.modelAllowlist`; non-positive `policy.rate.requestsPerMinute`; non-positive `limits.*`; unknown `target.type`/`adapter`; unsafe custom regex; weakening action without `allowUnsafeOverrides`.
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Haechi 0.6 Implementation Scope
|
|
2
|
+
|
|
3
|
+
- 문서 상태: Final
|
|
4
|
+
- 작성일: 2026-06-10
|
|
5
|
+
- 기준 버전: 0.6.0 (0.5.0 이후)
|
|
6
|
+
- 성격: auth and per-client controls
|
|
7
|
+
- 구현 완료: 2026-06-10 — PR #17 (계약 + bearer store + CLI), #18 (named profiles), #19 (proxy enforcement)
|
|
8
|
+
|
|
9
|
+
## 1. 릴리스 목표
|
|
10
|
+
|
|
11
|
+
0.4에서 예약해 둔 `authProvider`/`identity` 계약을 구현하고, identity를 실질적인 per-client 제어로 전환한다: 내장 bearer 인증, 명명된 per-client policy profile, model allowlist, request rate limiting. 이로써 Haechi를 단일 호스트에서 복수의 클라이언트/에이전트 앞에 안전하게 배치할 수 있게 된다.
|
|
12
|
+
|
|
13
|
+
**범위 결정 (2026-06-10):** 0.6은 auth 핵심에 집중한다. 원래 0.6으로 묶였던 무거운 운영 항목들 — Vault/AWS KMS 레퍼런스 어댑터, 외부 append-only audit 싱크, 서명된 릴리스 아티팩트, npm org(`@haechi/*`) 취득 — 은 각각 별도의 보안 설계를 받을 수 있도록 **0.7**로 이월한다.
|
|
14
|
+
|
|
15
|
+
## 2. 범위
|
|
16
|
+
|
|
17
|
+
### 2.1 `authProvider` 계약 (core 소유)
|
|
18
|
+
|
|
19
|
+
- `authenticate(request) → identity | null` (null = 거부). `request`는 Node `IncomingMessage`이며 헤더만 사용 가능 — body는 **아직 읽지 않는다**.
|
|
20
|
+
- Fail-closed: 예외를 던지는 provider는 거부로 처리한다. `createRuntime(config, { authProvider })`를 통해 주입한다.
|
|
21
|
+
- `auth.provider`로 선택한다:
|
|
22
|
+
- `none` (기본값) — 인증 없음; `identity`는 `null`로 유지 (0.5와 byte 단위로 동일한 audit 형태). Per-client policy는 default profile / base policy로 결정된다.
|
|
23
|
+
- `bearer` — 내장 token auth (§2.2).
|
|
24
|
+
- `external` — 주입된 `authProvider` 필요; 없으면 fail-closed (`keys.provider: external`과 동일한 방식). OIDC/JWT provider는 **0.7+ 위성 패키지** (`@haechi/auth-oidc`)로 남긴다; 0.6은 네트워크 IdP 코드를 포함하지 않는다.
|
|
25
|
+
|
|
26
|
+
### 2.2 내장 bearer auth + token store
|
|
27
|
+
|
|
28
|
+
- 자격 증명은 **별도 파일** `.haechi/auth.json` (mode `0600`)에 저장한다. `haechi.config.json`에는 절대 두지 않는다:
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"version": 1,
|
|
32
|
+
"tokens": [
|
|
33
|
+
{ "id": "tok_auth_ab12cd", "tokenHash": "...", "type": "service",
|
|
34
|
+
"scopes": ["team:eng"], "labels": { "env": "prod" },
|
|
35
|
+
"createdAt": "...", "disabled": false }
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
- `tokenHash` = `HMAC(derive("haechi:auth:token:v1"), token)` — 키 기반, domain-separated ([[key-management]] 규범 적용), bare hash 금지. 조회 시 timing-safe 비교를 사용한다.
|
|
40
|
+
- 토큰은 고엔트로피 `hae_<base64url(32 bytes)>` 형식이다. **평문은 생성 시 한 번만 출력하고 절대 저장하지 않는다.**
|
|
41
|
+
- CLI:
|
|
42
|
+
- `haechi auth add --type user|service|agent [--scope k:v ...] [--label k=v ...]` → 토큰을 발급하고, 해시와 메타데이터를 저장하며, 평문을 한 번만 출력한다.
|
|
43
|
+
- `haechi auth list` → id, type, scopes, labels, createdAt, disabled만 출력 — 토큰이나 해시는 절대 노출하지 않는다.
|
|
44
|
+
- `haechi auth revoke <id>` → `disabled: true`로 설정한다.
|
|
45
|
+
- 라벨 키는 `auth.allowedLabelKeys` (기본값 `["team", "env", "tier", "role"]`)에 대해 검증하며; 값은 길이를 제한한다. `add` 시점에 라벨의 PII는 거부된다.
|
|
46
|
+
|
|
47
|
+
### 2.3 Identity 구성 (PII-safe)
|
|
48
|
+
|
|
49
|
+
bearer 매칭 성공 시 예약된 `identity` 객체를 구성한다:
|
|
50
|
+
- `id`: token 레코드 id (불투명).
|
|
51
|
+
- `type`: 레코드에서 가져온다.
|
|
52
|
+
- `subjectHash`: `HMAC(derive("haechi:identity:hash:v1"), record.id)`; `issuerHash`: `"bearer-local"`의 HMAC. 식별자의 bare SHA-256은 금지한다.
|
|
53
|
+
- `provider`: `"bearer"`.
|
|
54
|
+
- `scopes`, `labels`: 레코드에서 가져온다 (이미 allowlist 검증 완료).
|
|
55
|
+
|
|
56
|
+
동일한 identity가 해당 요청의 모든 audit 이벤트(protect 이벤트, decision)에 첨부된다. `identity`는 `auth.provider: none` 하에서 `null`로 유지된다.
|
|
57
|
+
|
|
58
|
+
### 2.4 명명된 policy profile (per-client policy)
|
|
59
|
+
|
|
60
|
+
`policy`에 profile과 binding 맵이 추가된다:
|
|
61
|
+
```json
|
|
62
|
+
"policy": {
|
|
63
|
+
"mode": "enforce", "presets": ["..."], "actions": { }, // base / fallback policy
|
|
64
|
+
"profiles": {
|
|
65
|
+
"strict": { "presets": ["strict-block"] },
|
|
66
|
+
"internal": { "presets": ["llm-redact"], "actions": { "email": "allow" },
|
|
67
|
+
"modelAllowlist": ["llama3"], "rate": { "requestsPerMinute": 120 } }
|
|
68
|
+
},
|
|
69
|
+
"profileBinding": {
|
|
70
|
+
"byScope": { "team:eng": "internal" },
|
|
71
|
+
"byLabel": { "tier=trusted": "internal" },
|
|
72
|
+
"default": "strict"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
- profile은 기존 `buildPolicy`를 통해 컴파일된다 (presets + actions, strengthen-only 병합 유지). `modelAllowlist`와 `rate`는 profile별 선택 사항이며, 없으면 base 레벨 값으로 fallback한다.
|
|
77
|
+
- 요청별 결정 순서: **scope 일치 → label 일치 → `profileBinding.default`**. 첫 번째 일치가 우선하며, scope가 label보다 앞선다. `profiles`가 설정된 경우 `profileBinding.default`는 **필수**이며, 가장 제한적인 profile이어야 한다 (미일치/익명 identity에 대한 fail-closed).
|
|
78
|
+
- `auth.provider: none`이거나 `profiles`가 없으면 base `policy`가 변경 없이 적용된다 (완전한 하위 호환성).
|
|
79
|
+
- 구현: `createRuntime`은 시작 시 `{ name → policyEngine }` 맵을 컴파일한다 (모든 profile을 미리 검증). `protectJson(payload, context)`는 `context.policyEngine`을 받아 기본 엔진 대신 사용한다. proxy는 `authenticate` 후 profile을 결정하고, 선택된 엔진을 전체 경로에 전달한다.
|
|
80
|
+
|
|
81
|
+
### 2.5 Model allowlist
|
|
82
|
+
|
|
83
|
+
- Profile별 `modelAllowlist` (및 base 레벨 `policy.modelAllowlist`): 설정된 경우 요청 body의 `model`이 목록에 없으면 → `403 haechi_model_not_allowed` (audit 기록, 모델 이름 포함 — 모델 이름은 비밀이 아님). 비어 있거나 없으면 모두 허용.
|
|
84
|
+
- body 읽기 **이후** 실행된다 (`model` 필드는 JSON body 안에 있음).
|
|
85
|
+
|
|
86
|
+
### 2.6 Rate limiting
|
|
87
|
+
|
|
88
|
+
- 인메모리, 프로세스 단위 fixed-window 카운터. 키는 `identity.id` (또는 `"anonymous"`). **문서화된 제한사항:** 재시작 시 초기화되며, 레플리카 간 공유 없음 — 단일 프로세스 self-hosted preview 환경에서 허용 가능한 수준.
|
|
89
|
+
- Profile별 / base `rate.requestsPerMinute`. 한도 초과 시 → `429 haechi_rate_limited` (identity + 한도 포함 audit 기록).
|
|
90
|
+
- body 읽기 **이전** 실행된다 (identity 기반으로 저렴함). throttle된 인증 클라이언트가 대용량 body로 DoS를 가할 수 없도록 한다.
|
|
91
|
+
- LLM **token budget** (tokens-per-window)은 이월 — 모델 토큰 계산이 필요하며; 0.7+ 백로그로 기재.
|
|
92
|
+
|
|
93
|
+
### 2.7 Proxy 실행 순서 (예약된 계약, 확정)
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
GET /__haechi/health (항상 무인증; mode만 노출)
|
|
97
|
+
assertRelativeProxyTarget(request.url)
|
|
98
|
+
route classify
|
|
99
|
+
authProvider.authenticate(request) → 거부 시 401 haechi_auth_denied; request stream 미소비
|
|
100
|
+
resolve policy profile from identity
|
|
101
|
+
rate limit by identity → 429 haechi_rate_limited
|
|
102
|
+
body read (bounded by limits.maxRequestBytes)
|
|
103
|
+
model allowlist check → 403 haechi_model_not_allowed
|
|
104
|
+
protect / enforce (selected profile's policyEngine)
|
|
105
|
+
forward
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### 2.8 Audit 추가 사항
|
|
109
|
+
|
|
110
|
+
- 인증 성공 시 PII-safe `identity`를 모든 이벤트에 첨부; 이벤트에는 결정된 `profile` 이름도 포함 (profile 없으면 `null`). 둘 다 비민감 정보.
|
|
111
|
+
- 새로운 decision: `auth_denied` (reason: `no_token` | `invalid_token` | `provider_error`; raw token 없음), `model_not_allowed`, `rate_limited`. 가능한 경우 시도/결정된 identity를 포함하며, 평문 자격 증명은 절대 포함하지 않는다.
|
|
112
|
+
|
|
113
|
+
## 3. Config 스키마 요약
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
"auth": {
|
|
117
|
+
"provider": "none", // none | bearer | external
|
|
118
|
+
"store": ".haechi/auth.json",
|
|
119
|
+
"allowedLabelKeys": ["team", "env", "tier", "role"]
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
`policy.profiles`, `policy.profileBinding`, `policy.modelAllowlist`, `policy.rate` (§2.4–2.6) 추가. 모두 fail-closed로 검증한다 (알 수 없는 provider, profiles 설정 시 `profileBinding.default` 누락, binding의 알 수 없는 profile 이름, 비양수 `rate` 등).
|
|
123
|
+
|
|
124
|
+
## 4. 명시적 비범위 (0.7+로 이월)
|
|
125
|
+
|
|
126
|
+
- OIDC/JWT provider (`@haechi/auth-oidc`, `@haechi/auth-jwt`) — 0.6은 bearer + external 주입만 포함.
|
|
127
|
+
- Vault/AWS KMS 레퍼런스 어댑터; 외부 append-only audit 싱크; 서명된 릴리스 아티팩트; npm org `@haechi/*`.
|
|
128
|
+
- LLM token-budget limiting; 분산/공유 rate 상태.
|
|
129
|
+
- auth provider의 동적 npm 로딩 (1.0 plugin sandbox).
|
|
130
|
+
|
|
131
|
+
## 5. 하위 호환성
|
|
132
|
+
|
|
133
|
+
`auth.provider`는 `none`이 기본값이며, `profiles`가 없으면 0.6은 0.5와 완전히 동일하게 동작한다 (identity `null`, 단일 base policy). 유일한 audit 형태 변경은 항상 존재하는 `profile` 필드(identity 도입 방식과 동일) — 문서화되어 있으며, 미공개 소비자 preview에 대한 마이그레이션은 불필요하다.
|
|
134
|
+
|
|
135
|
+
## 6. 테스트 기준 (구현 시)
|
|
136
|
+
|
|
137
|
+
- bearer: 유효 token → identity; 누락/유효하지 않은 token → 401 `auth_denied`, body 미소비, timing-safe 조회; 폐기된 token 거부.
|
|
138
|
+
- 주입된 external provider → 사용됨; 없으면 fail-closed.
|
|
139
|
+
- profile 결정: scope 일치, label 일치, default fallback; default 누락 시 검증 실패; 서로 다른 profile이 서로 다른 action/allowlist 적용.
|
|
140
|
+
- model allowlist: 허용된 model 통과, 허용되지 않은 model → 403.
|
|
141
|
+
- rate limit: 윈도 내 N개 통과 / N+1 → 429; 윈도 초기화; per-identity 격리; body 이전 적용.
|
|
142
|
+
- auth CLI: `add`는 token을 한 번만 출력; `list`는 token/hash 비노출; `revoke`는 비활성화.
|
|
143
|
+
- `/__haechi/health` 무인증.
|
|
144
|
+
- audit: identity PII-safe (raw token/subject 없음), `profile` 기록됨, chain 유효; `auth_denied`/`model_not_allowed`/`rate_limited` decision 존재.
|
|
145
|
+
|
|
146
|
+
## 7. 권장 PR 분할 (스택)
|
|
147
|
+
|
|
148
|
+
1. `authProvider` 계약 + bearer store + `haechi auth` CLI + identity 구성.
|
|
149
|
+
2. 명명된 policy profile + 요청별 policy engine 선택.
|
|
150
|
+
3. Model allowlist + rate limiting + proxy 실행 순서 배선.
|
|
151
|
+
4. 0.6.0 릴리스 컷 (버전, 문서 EN/KO, threat-model/risk-register/api-stability, wiki).
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Haechi 0.6 Implementation Scope
|
|
2
|
+
|
|
3
|
+
- Status: Final
|
|
4
|
+
- Date: 2026-06-10
|
|
5
|
+
- Target version: 0.6.0 (after 0.5.0)
|
|
6
|
+
- Type: auth and per-client controls
|
|
7
|
+
- Shipped: 2026-06-10 — PRs #17 (contract + bearer store + CLI), #18 (named profiles), #19 (proxy enforcement)
|
|
8
|
+
|
|
9
|
+
## 1. Release Goal
|
|
10
|
+
|
|
11
|
+
Implement the `authProvider`/`identity` contracts reserved in 0.4 and turn identity into real per-client controls: built-in bearer authentication, named per-client policy profiles, model allowlisting, and request rate limiting. This makes Haechi safe(r) to put in front of multiple clients/agents on one host.
|
|
12
|
+
|
|
13
|
+
**Scope decision (2026-06-10):** 0.6 is focused on the auth core. The heavier operational items originally grouped under 0.6 — Vault/AWS KMS reference adapter, external append-only audit sink, signed release artifacts, npm org (`@haechi/*`) acquisition — move to **0.7** so each gets its own security design instead of bloating one release.
|
|
14
|
+
|
|
15
|
+
## 2. Scope
|
|
16
|
+
|
|
17
|
+
### 2.1 `authProvider` contract (core-owned)
|
|
18
|
+
|
|
19
|
+
- `authenticate(request) → identity | null` (null = deny). `request` is the Node `IncomingMessage`; only headers are available — the body is **not** read yet.
|
|
20
|
+
- Fail-closed: a throwing provider is treated as deny. Injected via `createRuntime(config, { authProvider })`.
|
|
21
|
+
- Selected by `auth.provider`:
|
|
22
|
+
- `none` (default) — no authentication; `identity` stays `null` (byte-identical audit shape to 0.5). Per-client policy resolves to the default profile / base policy.
|
|
23
|
+
- `bearer` — built-in token auth (§2.2).
|
|
24
|
+
- `external` — requires an injected `authProvider`; fail-closed if absent (mirrors `keys.provider: external`). The OIDC/JWT providers remain **0.7+ satellite packages** (`@haechi/auth-oidc`); 0.6 ships no network IdP code.
|
|
25
|
+
|
|
26
|
+
### 2.2 Built-in bearer auth + token store
|
|
27
|
+
|
|
28
|
+
- Credentials live in a **separate file** `.haechi/auth.json` (mode `0600`), never in `haechi.config.json`:
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"version": 1,
|
|
32
|
+
"tokens": [
|
|
33
|
+
{ "id": "tok_auth_ab12cd", "tokenHash": "...", "type": "service",
|
|
34
|
+
"scopes": ["team:eng"], "labels": { "env": "prod" },
|
|
35
|
+
"createdAt": "...", "disabled": false }
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
- `tokenHash` = `HMAC(derive("haechi:auth:token:v1"), token)` — keyed, domain-separated ([[key-management]] discipline), never a bare hash. Lookup uses a timing-safe comparison.
|
|
40
|
+
- Tokens are high-entropy `hae_<base64url(32 bytes)>`. The **plaintext is shown once at creation and never stored.**
|
|
41
|
+
- CLI:
|
|
42
|
+
- `haechi auth add --type user|service|agent [--scope k:v ...] [--label k=v ...]` → mints a token, stores its hash + metadata, prints the plaintext once.
|
|
43
|
+
- `haechi auth list` → ids, type, scopes, labels, createdAt, disabled — never the token or its hash.
|
|
44
|
+
- `haechi auth revoke <id>` → sets `disabled: true`.
|
|
45
|
+
- Label keys are validated against `auth.allowedLabelKeys` (default `["team", "env", "tier", "role"]`); values are length-limited. PII in labels is rejected at `add` time.
|
|
46
|
+
|
|
47
|
+
### 2.3 Identity construction (PII-safe)
|
|
48
|
+
|
|
49
|
+
On a bearer match, build the reserved `identity` object:
|
|
50
|
+
- `id`: the token record id (opaque).
|
|
51
|
+
- `type`: from the record.
|
|
52
|
+
- `subjectHash`: `HMAC(derive("haechi:identity:hash:v1"), record.id)`; `issuerHash`: HMAC of `"bearer-local"`. Bare SHA-256 of any identifier is prohibited.
|
|
53
|
+
- `provider`: `"bearer"`.
|
|
54
|
+
- `scopes`, `labels`: from the record (already allowlist-validated).
|
|
55
|
+
|
|
56
|
+
The same identity is attached to every audit event for the request (protect events, decisions). `identity` remains `null` under `auth.provider: none`.
|
|
57
|
+
|
|
58
|
+
### 2.4 Named policy profiles (per-client policy)
|
|
59
|
+
|
|
60
|
+
`policy` gains profiles and a binding map:
|
|
61
|
+
```json
|
|
62
|
+
"policy": {
|
|
63
|
+
"mode": "enforce", "presets": ["..."], "actions": { }, // base / fallback policy
|
|
64
|
+
"profiles": {
|
|
65
|
+
"strict": { "presets": ["strict-block"] },
|
|
66
|
+
"internal": { "presets": ["llm-redact"], "actions": { "email": "allow" },
|
|
67
|
+
"modelAllowlist": ["llama3"], "rate": { "requestsPerMinute": 120 } }
|
|
68
|
+
},
|
|
69
|
+
"profileBinding": {
|
|
70
|
+
"byScope": { "team:eng": "internal" },
|
|
71
|
+
"byLabel": { "tier=trusted": "internal" },
|
|
72
|
+
"default": "strict"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
- A profile compiles through the existing `buildPolicy` (presets + actions, strengthen-only merges preserved). `modelAllowlist` and `rate` are optional per-profile, falling back to base-level values.
|
|
77
|
+
- Resolution order per request: **scope match → label match → `profileBinding.default`**. First match wins; scope precedes label. `profileBinding.default` is **required** when `profiles` is set, and should be the most restrictive profile (fail-closed for unmatched/anonymous identities).
|
|
78
|
+
- With `auth.provider: none` or no `profiles`, the base `policy` applies unchanged (full backward compatibility).
|
|
79
|
+
- Implementation: `createRuntime` compiles a `{ name → policyEngine }` map at startup (all profiles validated up front). `protectJson(payload, context)` accepts `context.policyEngine` and uses it over the default. The proxy resolves the profile after `authenticate` and threads the selected engine through.
|
|
80
|
+
|
|
81
|
+
### 2.5 Model allowlist
|
|
82
|
+
|
|
83
|
+
- Per-profile `modelAllowlist` (and base-level `policy.modelAllowlist`): if set and the request body's `model` is not listed → `403 haechi_model_not_allowed` (audited, model name included — model names are not secret). Empty/absent = allow all.
|
|
84
|
+
- Runs **after** body read (the `model` field lives in the JSON body).
|
|
85
|
+
|
|
86
|
+
### 2.6 Rate limiting
|
|
87
|
+
|
|
88
|
+
- In-memory, per-process fixed-window counter keyed by `identity.id` (or `"anonymous"`). **Documented limits:** resets on restart, not shared across replicas — acceptable for a single-process self-hosted preview.
|
|
89
|
+
- Per-profile / base `rate.requestsPerMinute`. Over the limit → `429 haechi_rate_limited` (audited with identity + limit).
|
|
90
|
+
- Runs **before body read** (cheap, by identity) so a throttled-but-authenticated client cannot DoS with large bodies.
|
|
91
|
+
- LLM **token budget** (tokens-per-window) is deferred — it requires counting model tokens; noted as 0.7+ backlog.
|
|
92
|
+
|
|
93
|
+
### 2.7 Proxy execution order (the reserved contract, finalized)
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
GET /__haechi/health (always unauthenticated; exposes mode only)
|
|
97
|
+
assertRelativeProxyTarget(request.url)
|
|
98
|
+
route classify
|
|
99
|
+
authProvider.authenticate(request) → 401 haechi_auth_denied on deny; request stream NOT consumed
|
|
100
|
+
resolve policy profile from identity
|
|
101
|
+
rate limit by identity → 429 haechi_rate_limited
|
|
102
|
+
body read (bounded by limits.maxRequestBytes)
|
|
103
|
+
model allowlist check → 403 haechi_model_not_allowed
|
|
104
|
+
protect / enforce (selected profile's policyEngine)
|
|
105
|
+
forward
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### 2.8 Audit additions
|
|
109
|
+
|
|
110
|
+
- Successful auth attaches the PII-safe `identity` to every event; events also carry the resolved `profile` name (`null` when no profiles). Both are non-sensitive.
|
|
111
|
+
- New decisions: `auth_denied` (reason: `no_token` | `invalid_token` | `provider_error`; no raw token), `model_not_allowed`, `rate_limited`. All carry the attempted/resolved identity where known, never plaintext credentials.
|
|
112
|
+
|
|
113
|
+
## 3. Config schema summary
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
"auth": {
|
|
117
|
+
"provider": "none", // none | bearer | external
|
|
118
|
+
"store": ".haechi/auth.json",
|
|
119
|
+
"allowedLabelKeys": ["team", "env", "tier", "role"]
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
Plus `policy.profiles`, `policy.profileBinding`, `policy.modelAllowlist`, `policy.rate` (§2.4–2.6). All validated fail-closed (unknown provider, missing `profileBinding.default` when profiles set, unknown profile names in bindings, non-positive `rate`, etc.).
|
|
123
|
+
|
|
124
|
+
## 4. Explicit non-scope (deferred to 0.7+)
|
|
125
|
+
|
|
126
|
+
- OIDC/JWT providers (`@haechi/auth-oidc`, `@haechi/auth-jwt`) — 0.6 ships bearer + external-injection only.
|
|
127
|
+
- Vault/AWS KMS reference adapter; external append-only audit sink; signed release artifacts; npm org `@haechi/*`.
|
|
128
|
+
- LLM token-budget limiting; distributed/shared rate state.
|
|
129
|
+
- Dynamic npm loading of auth providers (1.0 plugin sandbox).
|
|
130
|
+
|
|
131
|
+
## 5. Backward compatibility
|
|
132
|
+
|
|
133
|
+
`auth.provider` defaults to `none`; with no `profiles`, 0.6 behaves exactly like 0.5 (identity `null`, single base policy). The only audit-shape change is the always-present `profile` field (mirrors how `identity` was introduced) — documented, no migration needed for an unpublished-consumer preview.
|
|
134
|
+
|
|
135
|
+
## 6. Test criteria (for implementation)
|
|
136
|
+
|
|
137
|
+
- bearer: valid token → identity; missing/invalid → 401 `auth_denied`, body not consumed, timing-safe lookup; revoked token denied.
|
|
138
|
+
- external provider injected → used; absent → fail-closed.
|
|
139
|
+
- profile resolution: scope match, label match, default fallback; missing default fails validation; different profiles apply different actions/allowlists.
|
|
140
|
+
- model allowlist: allowed passes, disallowed → 403.
|
|
141
|
+
- rate limit: N pass / N+1 → 429 within a window; window reset; per-identity isolation; pre-body enforcement.
|
|
142
|
+
- auth CLI: `add` prints token once; `list` never reveals token/hash; `revoke` disables.
|
|
143
|
+
- `/__haechi/health` unauthenticated.
|
|
144
|
+
- audit: identity PII-safe (no raw token/subject), `profile` recorded, chain valid; `auth_denied`/`model_not_allowed`/`rate_limited` decisions present.
|
|
145
|
+
|
|
146
|
+
## 7. Suggested PR breakdown (stacked)
|
|
147
|
+
|
|
148
|
+
1. `authProvider` contract + bearer store + `haechi auth` CLI + identity construction.
|
|
149
|
+
2. Named policy profiles + per-request policy engine selection.
|
|
150
|
+
3. Model allowlist + rate limiting + proxy execution-order wiring.
|
|
151
|
+
4. 0.6.0 release cut (version, docs EN/KO, threat-model/risk-register/api-stability, wiki).
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
- 문서 상태: Draft 0.3
|
|
4
4
|
- 작성일: 2026-06-10
|
|
5
|
-
- 기준 버전: 0.
|
|
5
|
+
- 기준 버전: 0.6.0
|
|
6
6
|
- 기준 브랜치: `main`
|
|
7
7
|
|
|
8
8
|
## 1. 현재 판단
|
|
@@ -128,7 +128,7 @@ base64/인코딩 값 디코딩 검사, query string 검사, audit tail truncatio
|
|
|
128
128
|
|---|---|---|
|
|
129
129
|
| 0.4.0 ✅ | token round-trip and adoption | 2026-06-10 구현 완료: 요청 스코프 response detokenization, deterministic tokenization(파생 키), `haechi mcp-wrap`, `haechi audit-verify`/`haechi status`, injection detection type(기본 allow), `identity`/`authProvider` 계약 예약. `docs/current/release-0.4-implementation-scope.md` 참조 |
|
|
130
130
|
| 0.5.0 ✅ | streaming hardening | 2026-06-10 출시: bounded cross-frame 버퍼를 사용한 SSE/NDJSON 스트리밍 응답 검사(`streaming.requestMode: inspect`). stream sequence AAD, replay cache, 강화된 원격 배포 가이드는 0.6+으로 이월. `docs/current/release-0.5-implementation-scope.md` 참조 |
|
|
131
|
-
| 0.6.0 |
|
|
131
|
+
| 0.6.0 ✅ | Shipped 2026-06-10 (PRs #17–#19): built-in bearer auth, named policy profiles, model allowlist, request rate limit, PII-safe identity in audit. `docs/current/release-0.6-implementation-scope.md` 참조 |
|
|
132
132
|
| 0.7.0 | observability | npm workspaces 전환, `@haechi/dashboard` read-only audit viewer (hash chain 무결성 표시, 요약/검색/타임라인) |
|
|
133
133
|
| 1.0.0 | stable API contract | migration policy, long-term audit schema, plugin sandbox/runtime conformance 및 allowlist/manifest 통과 외부 auth/classifier package 동적 로딩 |
|
|
134
134
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
- Status: Draft 0.3
|
|
4
4
|
- Date: 2026-06-10
|
|
5
|
-
- Target version: 0.
|
|
5
|
+
- Target version: 0.6.0
|
|
6
6
|
- Branch: `main`
|
|
7
7
|
|
|
8
8
|
## 1. Current Assessment
|
|
@@ -128,7 +128,8 @@ All checklist items below were completed for 0.3.2 on 2026-06-10 except the prov
|
|
|
128
128
|
|---|---|---|
|
|
129
129
|
| 0.4.0 ✅ | Token round-trip and adoption | Shipped 2026-06-10: request-scoped response detokenization, deterministic tokenization (derived key), `haechi mcp-wrap`, `haechi audit-verify`/`haechi status`, injection detection type (default allow), `identity`/`authProvider` contracts reserved. See `docs/current/release-0.4-implementation-scope.md` |
|
|
130
130
|
| 0.5.0 ✅ | Streaming hardening | Shipped 2026-06-10: SSE/NDJSON streaming response inspection with bounded cross-frame buffer (`streaming.requestMode: inspect`). Stream sequence AAD, replay cache, stronger remote deployment guide deferred to 0.6+. See `docs/current/release-0.5-implementation-scope.md` |
|
|
131
|
-
| 0.6.0 | Auth and
|
|
131
|
+
| 0.6.0 ✅ | Auth and per-client controls | Shipped 2026-06-10 (PRs #17–#19): built-in bearer auth, named policy profiles, model allowlist, request rate limit, PII-safe identity in audit. See `docs/current/release-0.6-implementation-scope.md` |
|
|
132
|
+
| 0.7.0 | Ops hardening and ecosystem | Vault/AWS KMS reference adapter, external append-only audit sink, signed release artifacts, npm org (`@haechi/*`), OIDC satellite, `@haechi/dashboard` |
|
|
132
133
|
| 0.7.0 | Observability | npm workspaces migration, `@haechi/dashboard` read-only audit viewer (hash chain integrity display, summary/search/timeline) |
|
|
133
134
|
| 1.0.0 | Stable API contract | Migration policy, long-term audit schema, plugin sandbox/runtime conformance, and dynamic loading of external auth/classifier packages that pass allowlist/manifest |
|
|
134
135
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
- 문서 상태: Draft 0.1
|
|
4
4
|
- 작성일: 2026-06-10
|
|
5
|
-
- 기준 버전: 0.
|
|
5
|
+
- 기준 버전: 0.6.0
|
|
6
6
|
|
|
7
7
|
## 1. 보호 대상
|
|
8
8
|
|
|
@@ -47,6 +47,8 @@ Haechi가 보호하려는 주요 자산은 다음이다.
|
|
|
47
47
|
| 행 걸린 upstream | proxy 연결 고갈 | `limits.upstreamTimeoutMs` 기본 120s, 초과 시 504 fail |
|
|
48
48
|
| signing/encryption 키 혼용 | key separation 위반 | policy bundle 서명 키를 domain-separated 파생 키로 분리 |
|
|
49
49
|
| JSON number/object key 은닉 | 카드번호 등 비문자열 leaf 미탐지 | number leaf와 object key도 detection/transform 대상 |
|
|
50
|
+
| 인증 없는 멀티 클라이언트 접근 | 로컬 프로세스가 upstream / token round-trip 경로를 무단 사용 | 선택적 bearer auth (`auth.provider: bearer`); 없거나 잘못된 경우 → 바디 읽기 전 401; identity별 rate limit 및 model allowlist |
|
|
51
|
+
| audit에 원시 credentials/identity 노출 | audit 로그를 통한 token 또는 subject 유출 | Token은 keyed-HMAC 해시로만 저장; identity subject/issuer는 keyed HMAC 처리; `auth_denied` 레코드에 token 미포함 |
|
|
50
52
|
| token round-trip의 타 토큰 복원 | 클라이언트/요청 간 평문 복구 | detokenization은 opt-in(`detokenizeResponses`)이며 요청 스코프: 같은 요청을 보호하며 발급된 토큰만 복원 |
|
|
51
53
|
| tool result/응답 내 간접 prompt injection | 심어진 지시문에 의한 agent 조작 | 응답 방향 휴리스틱, 기본 report-only(`injection` action `allow`), 격상은 명시적 정책 선택. 완전 방어 아님 |
|
|
52
54
|
|