haechi 0.7.0 → 0.9.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 +13 -2
- package/README.md +13 -2
- package/docs/current/api-stability.ko.md +14 -1
- package/docs/current/api-stability.md +14 -1
- package/docs/current/configuration.ko.md +106 -2
- package/docs/current/configuration.md +106 -2
- package/docs/current/release-0.6-implementation-scope.ko.md +4 -4
- package/docs/current/release-0.6-implementation-scope.md +4 -4
- package/docs/current/release-0.7-implementation-scope.ko.md +4 -4
- package/docs/current/release-0.7-implementation-scope.md +4 -4
- package/docs/current/release-0.8-implementation-scope.ko.md +145 -0
- package/docs/current/release-0.8-implementation-scope.md +145 -0
- package/docs/current/release-0.9-implementation-scope.ko.md +231 -0
- package/docs/current/release-0.9-implementation-scope.md +231 -0
- package/docs/current/release-process.ko.md +42 -5
- package/docs/current/release-process.md +42 -5
- package/docs/current/risk-register-release-gate.ko.md +18 -5
- package/docs/current/risk-register-release-gate.md +16 -4
- package/docs/current/threat-model.ko.md +16 -1
- package/docs/current/threat-model.md +16 -1
- package/examples/crypto-kms-reference/README.md +6 -40
- package/haechi.config.example.json +2 -1
- package/package.json +7 -1
- package/packages/audit/index.mjs +12 -1
- package/packages/auth/index.mjs +45 -0
- package/packages/cli/runtime.mjs +5 -1
- package/packages/core/index.mjs +4 -0
- package/packages/filter/index.mjs +58 -3
- package/packages/proxy/index.mjs +3 -0
- package/examples/crypto-kms-reference/index.mjs +0 -133
- package/examples/crypto-kms-reference/package.json +0 -19
package/README.ko.md
CHANGED
|
@@ -167,7 +167,14 @@ haechi auth revoke <id>
|
|
|
167
167
|
- **Rate limit**: identity별 분당 요청 수 → `429` (인메모리, 프로세스별).
|
|
168
168
|
- Audit 이벤트는 **PII-safe** `identity`(keyed-HMAC subject/issuer, 원시 값 아님)와 resolve된 `profile`을 포함하며, `auth_denied` / `model_not_allowed` / `rate_limited` 결정에는 credentials가 포함되지 않는다. `/__haechi/health`는 인증 없이 접근 가능하다.
|
|
169
169
|
|
|
170
|
-
|
|
170
|
+
JWT/JWKS 인증과 KMS 기반 key custody는 `haechi-*` 위성 패키지로 제공되며, 각각 core와 독립적으로 버저닝·발행된다:
|
|
171
|
+
|
|
172
|
+
- [`haechi-auth-jwt`](satellites/auth-jwt/) (0.8) — 헤드리스 JWKS bearer 검증; 0.2.0은 재사용 가능한 JWS 검증기(`createJwtVerifier`)를 추가 export한다.
|
|
173
|
+
- [`haechi-crypto-kms`](satellites/crypto-kms/) (0.8) — 실제 KMS 클라이언트 기반 envelope 암호화; 0.2.0은 AWS에 더해 GCP(`./gcp`), Azure(`./azure`), HashiCorp Vault Transit(`./vault`, `node:` 전용) 백엔드를 추가한다.
|
|
174
|
+
- [`haechi-dashboard`](satellites/dashboard/) (0.9, 신규) — audit 로그와 hash chain 상태에 대한 zero-dependency 읽기 전용 audit 뷰어(`node:http`).
|
|
175
|
+
- [`haechi-auth-oidc`](satellites/auth-oidc/) (0.9, 신규) — 대시보드의 사람 로그인을 제공하는 대화형 OIDC 세션 브로커(authorization-code + PKCE).
|
|
176
|
+
|
|
177
|
+
위성들은 기본 `node:` 전용이며(무거운 SDK는 optional peer) core를 zero-dependency로 유지한다.
|
|
171
178
|
|
|
172
179
|
## 설정
|
|
173
180
|
|
|
@@ -244,7 +251,7 @@ Haechi는 로컬 정책 부트스트래핑을 위한 기본 지역별 Privacy Pr
|
|
|
244
251
|
- Privacy profile은 명시적으로 더 엄격한 사용자 action을 강화할 수는 있지만 약화할 수는 없다.
|
|
245
252
|
- 탐지는 문자열 값, JSON 숫자(예: 카드 번호), 객체 키 이름을 검사한다. Base64/URL 인코딩된 값과 URL 쿼리 스트링은 검사되지 않는다.
|
|
246
253
|
- Audit tail truncation: `audit.anchor.mode: file`을 설정하면(추가 전용/별도 미디어에서) `haechi audit-verify --anchor`가 마지막 anchor 이후 꼬리 레코드 삭제를 탐지한다. 동일한 쓰기 가능 파일시스템에서는 공격자가 두 파일을 함께 잘라낼 수 있다.
|
|
247
|
-
- Key custody: `keys.provider: external`은 주입된 `cryptoProvider`를 허용한다; `assertCryptoProviderConformance`로 adapter를 검증한다. envelope 암호화 KMS adapter는 `
|
|
254
|
+
- Key custody: `keys.provider: external`은 주입된 `cryptoProvider`를 허용한다; `assertCryptoProviderConformance`로 adapter를 검증한다. envelope 암호화 KMS adapter는 `haechi-crypto-kms` satellite(`satellites/crypto-kms/`)가 제공한다.
|
|
248
255
|
- Release integrity: 배포된 tarball에는 npm provenance attestation이 포함되며, GitHub release asset에는 sigstore attestation과 `SHA256SUMS`가 추가된다(`gh attestation verify`와 `node scripts/release-checksums.mjs --check`로 검증한다).
|
|
249
256
|
- 이 패키지는 개발자 프리뷰이다. 인터넷에 노출된 운영 LLM 게이트웨이로 사용하지 않는다.
|
|
250
257
|
|
|
@@ -267,3 +274,7 @@ Haechi는 로컬 정책 부트스트래핑을 위한 기본 지역별 Privacy Pr
|
|
|
267
274
|
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` 참고.
|
|
268
275
|
|
|
269
276
|
0.7.0은 운영 강화(ops-hardening) 릴리스이다: 꼬리 절단을 탐지하는 audit head-hash anchoring(`audit.anchor`), `assertCryptoProviderConformance`와 reference KMS adapter를 포함한 강화된 외부 `cryptoProvider` 계약, 그리고 서명/체크섬된 GitHub release artifact. `docs/current/release-0.7-implementation-scope.md` 참고.
|
|
277
|
+
|
|
278
|
+
0.8.0은 `haechi-*` 에코시스템을 세운다: npm workspaces 모노레포(core는 unscoped `haechi` 유지, zero runtime dependency, 패킹 매니페스트 CI 게이트로 강제) + 첫 두 위성 — [`haechi-crypto-kms`](satellites/crypto-kms/)(실제 AWS KMS 클라이언트 기반 envelope 암호화; AWS SDK는 optional peer)와 [`haechi-auth-jwt`](satellites/auth-jwt/)(헤드리스 JWKS bearer 검증, `node:` 전용). 각각 자체 provenance + sigstore attest 워크플로로 독립 발행한다. `docs/current/release-0.8-implementation-scope.md` 참고.
|
|
279
|
+
|
|
280
|
+
0.9.0은 관측성(observability) + 대화형 인증 테마이다: 두 개의 새 위성 — [`haechi-dashboard`](satellites/dashboard/)(audit 로그와 hash chain 상태에 대한 zero-dependency 읽기 전용 `node:http` audit 뷰어; anti-DNS-rebinding Host allowlist, 엄격한 CSP/Trusted Types, fail-closed loopback/remote-bind 가드 포함)와 [`haechi-auth-oidc`](satellites/auth-oidc/)(대시보드의 사람 로그인을 제공하는 대화형 OIDC 세션 브로커 — authorization-code + PKCE + 서버측 세션). 기존 위성도 additive minor를 발행한다: `haechi-auth-jwt@0.2.0`은 재사용 가능한 JWS 검증기(`createJwtVerifier`)를 export하고, `haechi-crypto-kms@0.2.0`은 GCP/Azure/Vault 백엔드를 추가한다. core는 `0.9.0`으로 bump되며, 추가적인 `FORBIDDEN_KEYS` audit 새니타이즈 강화(현재 이벤트 출력은 바뀌지 않는 심층 방어)만 포함한다. `docs/current/release-0.9-implementation-scope.md` 참고.
|
package/README.md
CHANGED
|
@@ -167,7 +167,14 @@ haechi auth revoke <id>
|
|
|
167
167
|
- **Rate limit**: per-identity requests-per-minute → `429` (in-memory, per-process).
|
|
168
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
169
|
|
|
170
|
-
|
|
170
|
+
JWT/JWKS auth and KMS-backed key custody ship as `haechi-*` satellite packages, each versioned and published independently of core:
|
|
171
|
+
|
|
172
|
+
- [`haechi-auth-jwt`](satellites/auth-jwt/) (0.8) — headless JWKS bearer verification; 0.2.0 additively exports a reusable JWS verifier (`createJwtVerifier`).
|
|
173
|
+
- [`haechi-crypto-kms`](satellites/crypto-kms/) (0.8) — envelope encryption with a real KMS client; 0.2.0 adds GCP (`./gcp`), Azure (`./azure`), and HashiCorp Vault Transit (`./vault`, `node:`-only) backends alongside AWS.
|
|
174
|
+
- [`haechi-dashboard`](satellites/dashboard/) (0.9, new) — a zero-dependency, read-only audit viewer (`node:http`) over the audit log and its hash-chain status.
|
|
175
|
+
- [`haechi-auth-oidc`](satellites/auth-oidc/) (0.9, new) — an interactive OIDC session broker (authorization-code + PKCE) that provides the dashboard's human login.
|
|
176
|
+
|
|
177
|
+
The satellites are `node:`-only by default (heavy SDKs are optional peers) and keep core zero-dependency.
|
|
171
178
|
|
|
172
179
|
## Configuration
|
|
173
180
|
|
|
@@ -244,7 +251,7 @@ Set `privacy.profile` in `haechi.config.json` to apply the profile's default act
|
|
|
244
251
|
- Privacy profiles can strengthen but never weaken an explicitly stricter user action.
|
|
245
252
|
- Detection scans string values, JSON numbers (e.g. card numbers), and object key names. Base64/URL-encoded values and URL query strings are NOT inspected.
|
|
246
253
|
- Audit tail truncation: set `audit.anchor.mode: file` (on append-only/separate media) so `haechi audit-verify --anchor` detects deletion of trailing records back to the last anchor. On the same writable filesystem an attacker can truncate both files together.
|
|
247
|
-
- Key custody: `keys.provider: external` accepts an injected `cryptoProvider`; validate adapters with `assertCryptoProviderConformance`.
|
|
254
|
+
- Key custody: `keys.provider: external` accepts an injected `cryptoProvider`; validate adapters with `assertCryptoProviderConformance`. The `haechi-crypto-kms` satellite (`satellites/crypto-kms/`) provides an envelope-encryption KMS adapter.
|
|
248
255
|
- Release integrity: published tarballs carry an npm provenance attestation; GitHub release assets add a sigstore attestation and `SHA256SUMS` (verify with `gh attestation verify` and `node scripts/release-checksums.mjs --check`).
|
|
249
256
|
- The package is a developer preview. Do not expose it as an internet-facing production LLM gateway.
|
|
250
257
|
|
|
@@ -267,3 +274,7 @@ Set `privacy.profile` in `haechi.config.json` to apply the profile's default act
|
|
|
267
274
|
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`.
|
|
268
275
|
|
|
269
276
|
0.7.0 is operational hardening: audit head-hash anchoring (`audit.anchor`) that detects tail truncation, a hardened external `cryptoProvider` contract with `assertCryptoProviderConformance` and a reference KMS adapter, and signed/checksummed GitHub release artifacts. See `docs/current/release-0.7-implementation-scope.md`.
|
|
277
|
+
|
|
278
|
+
0.8.0 stands up the `haechi-*` ecosystem: an npm workspaces monorepo (core stays the unscoped `haechi`, zero runtime dependency, gated by a packed-manifest CI check) plus the first two satellites — [`haechi-crypto-kms`](satellites/crypto-kms/) (envelope encryption with a real AWS KMS client; the AWS SDK is an optional peer) and [`haechi-auth-jwt`](satellites/auth-jwt/) (headless JWKS bearer verification, `node:`-only). Each publishes independently with its own provenance + sigstore-attested workflow. See `docs/current/release-0.8-implementation-scope.md`.
|
|
279
|
+
|
|
280
|
+
0.9.0 is the observability + interactive-auth theme: two new satellites — [`haechi-dashboard`](satellites/dashboard/) (a zero-dependency, read-only `node:http` audit viewer over the audit log and its hash-chain status, with an anti-DNS-rebinding Host allowlist, strict CSP/Trusted Types, and fail-closed loopback/remote-bind guards) and [`haechi-auth-oidc`](satellites/auth-oidc/) (an interactive OIDC session broker — authorization-code + PKCE + server-side sessions — that provides the dashboard's human login). Existing satellites also ship additive minors: `haechi-auth-jwt@0.2.0` exports a reusable JWS verifier (`createJwtVerifier`) and `haechi-crypto-kms@0.2.0` adds GCP/Azure/Vault backends. Core bumps to `0.9.0`, carrying only an additive `FORBIDDEN_KEYS` audit-sanitization hardening — defense-in-depth that changes no current event output. See `docs/current/release-0.9-implementation-scope.md`.
|
|
@@ -42,7 +42,7 @@
|
|
|
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` 계약
|
|
45
|
+
- `haechi/auth` (`createBearerAuthProvider`, token store, `buildIdentity`, `buildExternalIdentity`) 및 `authProvider` 계약
|
|
46
46
|
- `policy.profiles`/`policy.profileBinding`/`modelAllowlist`/`rate` 및 `identity`/`profile` audit 필드
|
|
47
47
|
- `assertCryptoProviderConformance` 및 강화된 cryptoProvider 계약 (envelope base shape + provider 확장)
|
|
48
48
|
- `audit.anchor` 설정 및 `verifyAuditChain(path, { anchorPath })`
|
|
@@ -58,3 +58,16 @@
|
|
|
58
58
|
- audit event 필드 변경
|
|
59
59
|
- token format 변경
|
|
60
60
|
- plugin manifest schema 변경
|
|
61
|
+
|
|
62
|
+
## 5. Satellite 패키지 (`haechi-*`)
|
|
63
|
+
|
|
64
|
+
위성(예: `haechi-crypto-kms`, `haechi-auth-jwt`, `haechi-dashboard`, `haechi-auth-oidc`)은 core와 **독립적으로** 버저닝한다 — 위성 릴리스가 `haechi`를 bump하지 않고, 그 반대도 마찬가지다.
|
|
65
|
+
|
|
66
|
+
- **pre-1.0:** 위성은 npm semver를 따르며 `0.x` **minor** bump가 breaking change를 담을 수 있다; `major.minor`로 핀한다(예: `haechi-crypto-kms@~0.2`). 각자 자체 `1.0.0`까지 pre-stable.
|
|
67
|
+
- **core 호환성**은 `peerDependencies` 범위(`"haechi": ">=0.8.0 <1.0.0"`)로 표현한다 — 위성은 소비자가 설치한 단일 `haechi`를 재사용하므로 crypto/identity 표면이 하나다. `haechi-auth-oidc`는 추가로 `haechi-auth-jwt`(`">=0.2.0 <1.0.0"`)에 peer-depend하여 둘이 audit되는 단일 JWS/JWKS 검증 경로를 공유한다.
|
|
68
|
+
- **무거운 백엔드는 optional peer다.** `haechi-crypto-kms`는 SDK 백엔드(`@aws-sdk/client-kms`, 그리고 0.2.0의 `@google-cloud/kms`, `@azure/keyvault-keys`, `@azure/identity`)를 `peerDependencies` + `peerDependenciesMeta.optional`로 선언하고 lazy import하므로, 해당 경로를 쓰지 않는 소비자는 설치하지 않고 core는 zero-dependency를 유지한다. `./vault` 백엔드는 `node:` `fetch`만 사용한다(optional peer 없음). 위성의 배포 tarball은 항상 **runtime `dependencies` 0**을 선언한다(CI `check-satellite-packaging`로 강제).
|
|
69
|
+
- **pre-1.0 위성 export**는 preview이며 각 위성의 자체 `1.0.0` 전에 변경될 수 있다:
|
|
70
|
+
- `haechi-crypto-kms` (0.8 → 0.2.0): `createKmsCryptoProvider`, `createInMemoryKms`, `./aws`의 `createAwsKmsClient`, 그리고 0.2.0의 새 subpath `./gcp`(`createGcpKmsClient`), `./azure`(`createAzureKmsClient`), `./vault`(`createVaultKmsClient`).
|
|
71
|
+
- `haechi-auth-jwt` (0.2.0): `createJwtAuthProvider`(0.8, behavior-preserving)와 추가된 `createJwtVerifier`(재사용 가능한 JWS 검증 primitive), `isBlockedAddress`(SSRF 범위 술어, `haechi-auth-oidc`가 재사용).
|
|
72
|
+
- `haechi-dashboard` (0.1.0, 신규): `createDashboardServer`, `normalizeDashboardConfig`.
|
|
73
|
+
- `haechi-auth-oidc` (0.1.0, 신규): `createOidcSessionBroker`, `normalizeOidcConfig`.
|
|
@@ -42,7 +42,7 @@ 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
|
|
45
|
+
- `haechi/auth` (`createBearerAuthProvider`, token store, `buildIdentity`, `buildExternalIdentity`) and the `authProvider` contract
|
|
46
46
|
- `assertCryptoProviderConformance` and the hardened cryptoProvider contract (envelope base shape + provider extensions)
|
|
47
47
|
- `audit.anchor` config and `verifyAuditChain(path, { anchorPath })`
|
|
48
48
|
- `scripts/release-checksums.mjs` (SHA256SUMS generate/verify)
|
|
@@ -58,3 +58,16 @@ A migration note is added to `docs/current/release-*.md` or the README whenever
|
|
|
58
58
|
- Changing an audit event field
|
|
59
59
|
- Changing the token format
|
|
60
60
|
- Changing the plugin manifest schema
|
|
61
|
+
|
|
62
|
+
## 5. Satellite packages (`haechi-*`)
|
|
63
|
+
|
|
64
|
+
Satellites (e.g. `haechi-crypto-kms`, `haechi-auth-jwt`, `haechi-dashboard`, `haechi-auth-oidc`) version **independently** of core — a satellite release never bumps `haechi`, and vice versa.
|
|
65
|
+
|
|
66
|
+
- **Pre-1.0:** satellites follow npm semver where a `0.x` **minor** bump may carry breaking changes; pin `major.minor` (e.g. `haechi-crypto-kms@~0.2`). Each is pre-stable until its own `1.0.0`.
|
|
67
|
+
- **Core compatibility** is expressed as a `peerDependencies` range (`"haechi": ">=0.8.0 <1.0.0"`) — a satellite reuses the consumer's single installed `haechi`, so there is one crypto/identity surface. `haechi-auth-oidc` additionally peer-depends on `haechi-auth-jwt` (`">=0.2.0 <1.0.0"`) so the two share one audited JWS/JWKS verification path.
|
|
68
|
+
- **Heavy backends are optional peers.** `haechi-crypto-kms` declares its SDK backends (`@aws-sdk/client-kms`, and in 0.2.0 `@google-cloud/kms`, `@azure/keyvault-keys`, `@azure/identity`) under `peerDependencies` + `peerDependenciesMeta.optional` and imports them lazily, so consumers who do not use a given path never install it and core stays zero-dependency. The `./vault` backend uses `node:` `fetch` only (no optional peer). A satellite's published tarball always declares **zero runtime `dependencies`** (CI-gated by `check-satellite-packaging`).
|
|
69
|
+
- **Pre-1.0 satellite exports** are preview and may change before each satellite's own `1.0.0`:
|
|
70
|
+
- `haechi-crypto-kms` (0.8 → 0.2.0): `createKmsCryptoProvider`, `createInMemoryKms`, the `./aws` `createAwsKmsClient`, and the new 0.2.0 subpaths `./gcp` (`createGcpKmsClient`), `./azure` (`createAzureKmsClient`), `./vault` (`createVaultKmsClient`).
|
|
71
|
+
- `haechi-auth-jwt` (0.2.0): `createJwtAuthProvider` (0.8, behavior-preserving) plus the additive `createJwtVerifier` (reusable JWS verifier primitive) and `isBlockedAddress` (SSRF range predicate, reused by `haechi-auth-oidc`).
|
|
72
|
+
- `haechi-dashboard` (0.1.0, new): `createDashboardServer`, `normalizeDashboardConfig`.
|
|
73
|
+
- `haechi-auth-oidc` (0.1.0, new): `createOidcSessionBroker`, `normalizeOidcConfig`.
|
|
@@ -53,11 +53,12 @@ upstream JSON 응답을 검사한다(기본적으로 꺼져 있음 — 모델로
|
|
|
53
53
|
| 키 | 타입 / 값 | 기본값 | 설명 |
|
|
54
54
|
|---|---|---|---|
|
|
55
55
|
| `responseProtection.enabled` | boolean | `false` | 마스터 스위치. `detokenizeResponses`가 작동하려면 반드시 활성화되어 있어야 한다. |
|
|
56
|
-
| `responseProtection.mode` | `dry-run` \| `report-only` \| `enforce` | `enforce` | 응답 방향의 집행 모드. |
|
|
56
|
+
| `responseProtection.mode` | `dry-run` \| `report-only` \| `enforce` | `enforce` | 응답 방향의 집행 모드. **실제 LLM upstream엔 `report-only` 권장:** envelope 메타데이터(id, unix 타임스탬프 `created`, 긴 숫자 필드)가 PII/secret 모양으로 보일 수 있어 `enforce`면 정상 완성 응답을 502로 막는다. `report-only`도 탐지·감사·`detokenizeResponses`는 그대로 동작. (Haechi는 응답에서 자체 `[TOKEN:…]`/`[HAECHI_ENC:…]` 마커를 제외하고, phone 규칙도 맨 타임스탬프를 무시하며, 응답의 bare JSON number leaf는 검사하지 않으므로 실제 vLLM/Ollama 응답은 clean. 응답 *텍스트*까지 검사하려면 `enforce`가 더 엄격.) |
|
|
57
57
|
| `responseProtection.failureMode` | `fail-closed` \| `allow` | `fail-closed` | *검사 불가능한* 응답(비JSON, 잘못된 JSON, 압축)에 대한 처리 방식. `fail-closed`는 502를 반환하고, `allow`는 통과시킨다(audit 기록됨). |
|
|
58
58
|
| `responseProtection.allowNonJson` | boolean | `false` | 비JSON 응답을 검사 없이 통과시킨다. |
|
|
59
59
|
| `responseProtection.allowCompressed` | boolean | `false` | 압축 응답을 검사 없이 통과시킨다. |
|
|
60
60
|
| `responseProtection.maxBytes` | 양의 정수 | `1048576` | 응답 크기의 상한. `failureMode: allow` 상태에서도 적용되며, 크기를 초과한 응답은 항상 거부된다. |
|
|
61
|
+
| `responseProtection.scanNumbers` | boolean | `false` | 응답의 **bare JSON number leaf**에 탐지를 돌릴지 여부. 기본 off — 응답 숫자는 추론서버 메타데이터(`*_duration`, count, timestamp)라 검사하면 `card`/`kr_rrn` 오탐만 발생. 모델이 숫자 필드로 유출할 수 있다고 보는 엄격 위협모델에서만 `true`; `mode: report-only`와 함께 써서 차단 없이 감사만. 요청 방향은 항상 숫자 검사. |
|
|
61
62
|
|
|
62
63
|
## `streaming`
|
|
63
64
|
|
|
@@ -230,4 +231,107 @@ haechi proxy --config haechi.config.json --host 0.0.0.0 --allow-remote-bind
|
|
|
230
231
|
|
|
231
232
|
## 검증 요약
|
|
232
233
|
|
|
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을 약화하려는 시도.
|
|
234
|
+
다음은 로드 시 오류(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`; boolean이 아닌 `responseProtection.scanNumbers`; 잘못된 `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을 약화하려는 시도.
|
|
235
|
+
|
|
236
|
+
# Satellite 운영자 설정 (0.9)
|
|
237
|
+
|
|
238
|
+
아래 두 섹션은 0.9에서 도입된 **독립적으로 배포되는 satellite 패키지** — `haechi-dashboard`와 `haechi-auth-oidc`의 설정을 다룬다. **이들은 코어 `haechi.config.json` / `normalizeConfig` 스키마의 키가 아니다.** 각 satellite는 팩토리 함수(`createDashboardServer(options)` / `createOidcSessionBroker(options)`)에 **옵션 객체**를 전달해 설정하며, 각자의 `normalizeDashboardConfig` / `normalizeOidcConfig`가 검증한다. 검증은 코어와 동일한 **strict, fail-closed** 원칙을 따른다: 알 수 없는 옵션 키는 오류를 발생시키고, 아래의 모든 필드는 fail-closed throw 조건을 명시한다. 소스: `satellites/dashboard/index.mjs`, `satellites/auth-oidc/index.mjs`. 위협 모델 커버리지: **P1-OPS-005**(dashboard audit 노출 / DNS-rebinding / remote bind), **P1-SEC-009**(broker session/login 보안), `docs/current/release-0.9-implementation-scope.md` §6 참고.
|
|
239
|
+
|
|
240
|
+
## `haechi-dashboard` (satellite)
|
|
241
|
+
|
|
242
|
+
audit JSONL과 그 hash-chain 상태를 제공하는 zero-dependency **read-only** audit 뷰어(`node:http`)다. 런타임이 아닌 **경로**를 받는다. `createDashboardServer(options)`로 설정하며, `normalizeDashboardConfig(options)`가 검증 후 실제 적용 설정을 반환한다. 소스: `satellites/dashboard/index.mjs`.
|
|
243
|
+
|
|
244
|
+
| 옵션 | 타입 / 값 | 기본값 | 설명 / fail-closed throw |
|
|
245
|
+
|---|---|---|---|
|
|
246
|
+
| `auditPath` | 비어 있지 않은 문자열 | **필수** | audit JSONL 경로. 누락되거나 비어 있지 않은 문자열이 아니면 throw. |
|
|
247
|
+
| `anchorPath` | string \| `null` | `null` | tail 절단 탐지를 위해 `verifyAuditChain`에 전달되는 anchor 스트림 경로. 존재하지만 비어 있지 않은 문자열이 아니면 throw. |
|
|
248
|
+
| `host` | 비어 있지 않은 문자열 | `127.0.0.1` | 바인드 주소. loopback이 아니면 `allowRemoteBind`와 아래 remote-bind 전제 조건을 모두 충족해야 한다. 존재하지만 비어 있거나 문자열이 아니면 throw. |
|
|
249
|
+
| `port` | 정수 0–65535 | `1018` | 리슨 포트; `0` = OS 할당 임시 포트(의도된 affordance). `[0,65535]` 정수가 아니면 throw. |
|
|
250
|
+
| `allowRemoteBind` | boolean | `false` | loopback이 아닌 `host`를 허용한다. boolean이 아니면 throw. 설정만으로는 충분하지 않다 — remote-bind 전제 조건 참고. |
|
|
251
|
+
| `sessionGuard` | object \| `null` | `null` | `authenticate(req) -> session\|null`과 선택적 `handlers` 맵을 구현하는 guard. object가 아니거나 `authenticate`가 함수가 아니면 throw. `handlers` 키는 고정된 broker 경로 `/auth/login`, `/auth/callback`, `/auth/logout`만 허용되며, 다른 키(특히 `/api/*`, `/healthz`, `/`)는 throw — guard가 audit 경로를 게이트에서 면제시키는 auth-bypass를 차단한다. `haechi-auth-oidc` broker를 주입하면 충족된다(아래 참고). |
|
|
252
|
+
| `window` | 정수 4096–67108864 | `1048576` | `/api/events`와 `/api/summary`의 tail-read 윈도우(최대 바이트). `[4096, 67108864]`(4 KiB–64 MiB) 정수가 아니면 throw. |
|
|
253
|
+
| `tlsContext` | object \| `null` | `null` | dashboard가 직접 HTTPS를 종단하기 위한 TLS 자료. object가 아니거나, non-null인데 **사용 가능한 자료**가 없으면 throw — `(key && cert)` 또는 `pfx`를 반드시 포함해야 한다(빈 `{}`는 거부되어 loopback이 아닌 plaintext 리스너를 green-light하지 못하게 한다). |
|
|
254
|
+
| `trustProxy` | string \| `null` | `null` | 신뢰하는 fronting-proxy 주소/CIDR를 명시한다. 문자열이 아니거나, 비어 있거나, falsy 모양 문자열(`"false"`/`"0"`)이면 throw. **`trustProxy`만으로는 loopback이 아닌 바인드를 절대 인가하지 못한다** — 실제 `tlsContext`만 가능하다. |
|
|
255
|
+
|
|
256
|
+
### 라우트
|
|
257
|
+
|
|
258
|
+
모든 라우트는 **GET/HEAD 전용**(그 외 method → `405`)이며, asset 맵은 in-code로 고정되어 있다(파일시스템 traversal 없음):
|
|
259
|
+
|
|
260
|
+
- `/api/events` — audit JSONL의 bounded tail read, 최신순. `limit`은 `[1,200]` 정수(기본 50); `cursor`는 opaque `auditIntegrity.sequence`(파일시스템 오프셋이 아님). 각 이벤트는 **recursive key-by-key allowlist projection**으로 재구성된다(blind spread 없음; identity는 scope/label/raw subject 없이 `subjectHash`/`issuerHash`만 보유). 요청된 페이지가 유지된 윈도우보다 오래되면 `windowExceeded`를 반환한다.
|
|
261
|
+
- `/api/chain` — `verifyAuditChain`을 감싸며, 파생된 `truncationDetected` boolean을 노출한다(raw 실패 reason은 **절대** 반환하지 않음). mtime+size 캐시(동시 재-walk 없음); 32 MiB 상한 초과 시 `{valid:null}`과 함께 `413`; `HEAD`는 walk를 강제하지 않고 헤더만 반환한다.
|
|
262
|
+
- `/api/summary` — tail 윈도우에 대한 집계 탐지 카운트(`byType`/`byAction`/`detectionCount`).
|
|
263
|
+
- `/healthz` — liveness 전용(`{status:"ok"}`); loopback 밖에서도 session 불필요.
|
|
264
|
+
|
|
265
|
+
### 보안 기본값
|
|
266
|
+
|
|
267
|
+
- **기본 loopback 바인드.** `host` 기본값은 `127.0.0.1`이며, loopback이 아닌 host 바인드는 코어의 `assertSafeProxyBind`(재-표현)를 재사용하고 `allowRemoteBind`를 요구한다.
|
|
268
|
+
- **Remote bind는 fail-closed.** loopback이 아닌 바인드는 `allowRemoteBind: true`, `sessionGuard`, **그리고** 유효한 `tlsContext`(dashboard가 직접 TLS 종단)를 **모두** 요구한다. `trustProxy`는 이를 충족하지 못한다 — loopback이 아닌 plaintext 리스너는 audit 데이터를 평문으로 제공하면서 HSTS를 방출하므로 거부된다. HSTS는 서버가 실제로 HTTPS를 제공할 때**만** 방출된다.
|
|
269
|
+
- **anti-DNS-rebinding Host allowlist**가 모든 요청(`/api/*`, `/healthz`, 모든 method 포함)의 무조건적 첫 게이트다; 잘못되거나 중복된 `Host` 헤더 → method 검사 이전에 `403`.
|
|
270
|
+
- **strict CSP + Trusted Types**(`require-trusted-types-for 'script'`, `textContent` 렌더링) 및 `X-Frame-Options: DENY`, `Cross-Origin-Resource-Policy`/`-Opener-Policy: same-origin`, `X-Content-Type-Options: nosniff`, `Cache-Control: no-store`; CORS 헤더는 의도적으로 절대 설정하지 않는다.
|
|
271
|
+
- **sessionGuard seam.** guard가 존재하면 모든 `/api/*` 라우트는 `authenticate()` 뒤에 게이트된다; 미인증 요청은 `401`(`302` 리다이렉트가 아님). auth-면제 집합은 고정된 broker-path allowlist와 guard가 선언한 handlers의 **교집합**(exact match)이다 — guard는 audit-data 라우트를 절대 면제시킬 수 없다.
|
|
272
|
+
- **generic 오류.** 5xx는 `{error:"internal"}`만 반환한다 — stack, OS code, 파일시스템 경로는 절대 없음. satellite-local fixed-window rate limiter(소스별 120 req/60s)가 `/api/*` 앞단을 막는다.
|
|
273
|
+
|
|
274
|
+
bin `haechi-dashboard`(workspace)가 서버를 구동하며, publish 워크플로는 `.github/workflows/dashboard-publish.yml`(태그 `dashboard-v<semver>`)이다. `peerDependencies: { haechi: ">=0.8.0 <1.0.0" }`.
|
|
275
|
+
|
|
276
|
+
## `haechi-auth-oidc` (satellite)
|
|
277
|
+
|
|
278
|
+
zero-dependency **interactive OIDC session broker**(authorization-code + PKCE) — dashboard의 사람-로그인 메커니즘이다. opaque server-side session을 생성하고, **주입을 통해 dashboard `sessionGuard` 계약을 충족한다**(`{ authenticate(req), handlers: { "/auth/login", "/auth/callback", "/auth/logout" } }`). per-request bearer validator가 **아니다**(그 역할은 `haechi-auth-jwt`에 남는다). `createOidcSessionBroker(options)`로 설정하며 `normalizeOidcConfig(options)`가 검증한다. 소스: `satellites/auth-oidc/index.mjs`. `peerDependencies: { haechi: ">=0.8.0 <1.0.0", haechi-auth-jwt: ">=0.2.0 <1.0.0" }`.
|
|
279
|
+
|
|
280
|
+
| 옵션 | 타입 / 값 | 기본값 | 설명 / fail-closed throw |
|
|
281
|
+
|---|---|---|---|
|
|
282
|
+
| `cryptoProvider` | `hmac()`를 가진 object | **필수** | PII-safe identity 해시와 `sessionIdHash`를 위한 keyed-HMAC를 제공한다. `hmac`이 함수가 아니면 throw. |
|
|
283
|
+
| `issuer` | HTTPS URL 문자열 | **필수** | OIDC issuer; 정확한 string-equal discovery와 single-origin endpoint 검사를 위해 pin된다. 누락되거나 `https`가 아니면 throw. |
|
|
284
|
+
| `clientId` | 비어 있지 않은 문자열 | **필수** | OAuth client id(ID-token의 기대 `aud`이기도 함). 누락/비어 있으면 throw. |
|
|
285
|
+
| `clientSecret` | string \| 생략 | 생략 | 존재 ⇒ confidential client; 생략 ⇒ public(PKCE 전용) client. 존재하지만 비어 있으면 throw. |
|
|
286
|
+
| `redirectUri` | 절대 URL 문자열 | **필수** | `https`(또는 carve-out 하의 **loopback** `http`)여야 하고, broker와 **same-origin**이며, path가 정확히 `/auth/callback`이어야 한다. 그 외에는 throw. |
|
|
287
|
+
| `scopes` | 문자열 배열 | `["openid"]` | `openid`는 강제 포함(dedup)되고, `offline_access`는 제거된다(refresh rotation은 0.9 범위 밖). 비어 있지 않은 문자열 배열이 아니면 throw. |
|
|
288
|
+
| `returnToAllowlist` | 문자열 배열 | `["/"]` | **relative same-origin** 복귀 경로의 allowlist(단일 `/`로 시작, scheme/host/`//`/백슬래시 없음). 배열이 아니거나 비적합 항목이 있으면 throw. |
|
|
289
|
+
| `sessionTtlSeconds` | 정수 1–2592000 | `28800`(8h) | 절대 session 수명. `[1, 2592000]`(30d 상한)을 벗어나면 throw. |
|
|
290
|
+
| `idleTtlSeconds` | 정수 1–2592000 | `1800`(30m) | idle 타임아웃(sliding `lastSeen`). 범위를 벗어나면 throw. |
|
|
291
|
+
| `maxAgeSeconds` | 정수 1–2592000 \| `null` | `null` | 설정 시 OIDC `max_age`를 보내고 `auth_time`이 `maxAge + skew` 이내일 것을 요구한다. 존재하지만 범위를 벗어나면 throw. |
|
|
292
|
+
| `tokenEndpointAuthMethod` | `client_secret_basic` \| `client_secret_post` | `client_secret_basic` | token-endpoint 인증 방식. 알 수 없는 값이거나, `clientSecret` 없이 설정되면 throw(confidential client에서만 유효). |
|
|
293
|
+
| `secureCookies` | `true` \| `false` \| `"auto"` | `"auto"` | externally-visible scheme로부터 쿠키 `Secure`/`__Host-` 하드닝을 강제하거나 자동 도출한다. 그 외 값이면 throw. |
|
|
294
|
+
| `trustProxy` | string \| `null` | `null` | TLS를 종단하는 fronting proxy를 명시한다; browser-facing scheme를 HTTPS로 간주한다(쿠키 하드닝에 반영). 문자열이 아니거나 비어 있으면 throw. |
|
|
295
|
+
| `algorithms` | 비어 있지 않은 문자열 배열 | `["RS256","ES256"]` | 허용된 JWS 알고리즘(verifier로 전달). 비어 있지 않은 배열이 아니면 throw. |
|
|
296
|
+
| `clockSkewSeconds` | 수 0–300 | (verifier 기본값) | ID-token 시간 클레임의 여유. `[0,300]`을 벗어나면 throw. |
|
|
297
|
+
| `prompt` | string \| `null` | `null` | 선택적 OIDC `prompt`. 존재하지만 비어 있거나 문자열이 아니면 throw. |
|
|
298
|
+
| `pendingTtlSeconds` | 정수 1–3600 | `600`(10m) | 로그인 완료 제한 시간(pre-auth 레코드 TTL). `[1,3600]`을 벗어나면 throw. |
|
|
299
|
+
| `pendingCap` | 정수 1–1000000 | `1024` | 동시 진행 중 로그인의 hard cap; store가 가득 차면 **새** 로그인을 거부하고 진행 중 auth는 절대 evict하지 않는다(fail-closed). 범위를 벗어나면 throw. |
|
|
300
|
+
| `rateLimitMax` | 정수 1–1000000 | `60` | 소스별 60s 윈도우당 `/auth/login`+`/auth/callback`. 범위를 벗어나면 throw. |
|
|
301
|
+
| `fetchTimeoutMs` | 정수 1–120000 | `5000` | egress별 타임아웃(discovery / token / JWKS). 범위를 벗어나면 throw. |
|
|
302
|
+
| `fetchImpl` / `lookupImpl` / `now` | 함수 | 주입/전역 | `fetch` / DNS `lookup` / clock seam 주입. 존재하지만 함수가 아니면 throw. |
|
|
303
|
+
| `sessionStore` | object | in-memory | opaque-id → session store; `get`/`set`/`delete`를 구현해야 한다. 존재하지만 비적합하면 throw. |
|
|
304
|
+
| `pendingStore` | object | in-memory | pre-auth 레코드 store; `set`/`take`(원자적 단일-사용 `take`)를 구현해야 한다. 존재하지만 비적합하면 throw. |
|
|
305
|
+
| `auditSink` | 함수 \| `record()`를 가진 object | 없음 | PII-safe 이벤트 sink. 존재하지만 함수도 `record()` 가진 object도 아니면 throw. |
|
|
306
|
+
|
|
307
|
+
### 쿠키 하드닝 의미
|
|
308
|
+
|
|
309
|
+
session은 **server-side 전용**이다 — 쿠키는 클레임/토큰이 아닌 opaque id만 보유한다. 두 개의 쿠키를 사용한다(pending 레코드를 바인딩하는 pre-auth 쿠키, 그리고 session 쿠키). externally-visible scheme가 HTTPS이면(`https` `redirectUri`, `secureCookies: true`, 또는 non-null `trustProxy`) 쿠키는 **`__Host-` prefix + `Secure` + `HttpOnly` + `SameSite=Lax`**(`Path=/`, `Domain` 없음)를 사용한다; `SameSite=Lax`는 IdP의 top-level GET이 `/auth/callback`으로 쿠키를 실어 보내게 한다. 문서화된 **loopback-`http` carve-out** 하에서는 `__Host-`/`Secure` 속성이 제거되고(plaintext 리스너는 `Secure`를 설정할 수 없음) bare 쿠키 이름을 사용한다. **HTTPS가 확인되지 않은 off-loopback broker는 construction에서 fail-closed**된다 — `Secure`/`__Host-` 쿠키는 평문으로 전송되지 않으므로 로그인이 조용히 깨질 것이다. `/auth/callback`에서 **새** session id가 발급된다(fixation 없음); `/auth/logout`은 non-GET, CSRF-헤더 게이트(`x-haechi-csrf`)이며 server-side 상태를 파괴한다. access token은 폐기된다(절대 저장하지 않음). audit 이벤트(`oidc.login.start`/`success`/`failure{reasonCode}`/`logout`/`session.evict`)는 keyed-HMAC `subjectHash`/`issuerHash`/`sessionIdHash` + `provider` + 거친 `reasonCode` + timestamp만 보유한다.
|
|
310
|
+
|
|
311
|
+
### dashboard와의 연결
|
|
312
|
+
|
|
313
|
+
broker를 dashboard의 `sessionGuard`로 주입한다:
|
|
314
|
+
|
|
315
|
+
```js
|
|
316
|
+
import { createDashboardServer } from "haechi-dashboard";
|
|
317
|
+
import { createOidcSessionBroker } from "haechi-auth-oidc";
|
|
318
|
+
|
|
319
|
+
const broker = createOidcSessionBroker({
|
|
320
|
+
cryptoProvider,
|
|
321
|
+
issuer: "https://idp.example.com",
|
|
322
|
+
clientId: "haechi-dashboard",
|
|
323
|
+
clientSecret: "…",
|
|
324
|
+
redirectUri: "https://dash.example.com/auth/callback",
|
|
325
|
+
returnToAllowlist: ["/"]
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
const dashboard = createDashboardServer({
|
|
329
|
+
auditPath: ".haechi/audit.jsonl",
|
|
330
|
+
host: "0.0.0.0",
|
|
331
|
+
allowRemoteBind: true,
|
|
332
|
+
tlsContext: { key, cert }, // remote bind: dashboard가 직접 TLS 종단
|
|
333
|
+
sessionGuard: broker // /api/*를 authenticate() 뒤로 게이트; /auth/* handlers 마운트
|
|
334
|
+
});
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
broker의 `handlers` 맵은 dashboard가 auth 게이트에서 면제하는 고정 broker 경로에서만 마운트되며, 모든 `/api/*` 라우트는 `broker.authenticate(req)` 뒤에 게이트된다. publish 워크플로: `.github/workflows/auth-oidc-publish.yml`(태그 `auth-oidc-v<semver>`).
|
|
@@ -53,11 +53,12 @@ Inspects upstream JSON responses (off by default — turn on to protect what com
|
|
|
53
53
|
| Key | Type / values | Default | Notes |
|
|
54
54
|
|---|---|---|---|
|
|
55
55
|
| `responseProtection.enabled` | boolean | `false` | Master switch. Required for `detokenizeResponses` to do anything. |
|
|
56
|
-
| `responseProtection.mode` | `dry-run` \| `report-only` \| `enforce` | `enforce` | Enforcement mode for the response direction. |
|
|
56
|
+
| `responseProtection.mode` | `dry-run` \| `report-only` \| `enforce` | `enforce` | Enforcement mode for the response direction. **For real LLM upstreams, prefer `report-only`:** envelope metadata (ids, a unix-timestamp `created`, long numeric fields) can look PII/secret-shaped, and `enforce` would 502 a legitimate completion. `report-only` still detects, audits, and runs `detokenizeResponses`. (Haechi already skips its own `[TOKEN:…]`/`[HAECHI_ENC:…]` markers on the response, the phone rule ignores bare timestamps, and bare JSON number leaves aren't scanned on the response — so real vLLM/Ollama responses scan clean. `enforce` remains stricter if you also want the response *text* policed.) |
|
|
57
57
|
| `responseProtection.failureMode` | `fail-closed` \| `allow` | `fail-closed` | What to do with an *uninspectable* response (non-JSON, invalid JSON, compressed). `fail-closed` returns 502; `allow` passes it through (audited). |
|
|
58
58
|
| `responseProtection.allowNonJson` | boolean | `false` | Permit non-JSON responses through without inspection. |
|
|
59
59
|
| `responseProtection.allowCompressed` | boolean | `false` | Permit compressed responses through without inspection. |
|
|
60
60
|
| `responseProtection.maxBytes` | positive integer | `1048576` | Hard response size cap. Enforced even under `failureMode: allow` — oversized responses are always denied. |
|
|
61
|
+
| `responseProtection.scanNumbers` | boolean | `false` | Whether to run detection on **bare JSON number leaves** of the response. Off by default — response numbers are inference-server metadata (`*_duration`, counts, timestamps) and scanning them only false-positives (`card`/`kr_rrn`). Set `true` only for a strict threat model (a model assumed able to exfiltrate via a numeric field); pair with `mode: report-only` to audit without blocking on metadata. Request-direction always scans numbers regardless. |
|
|
61
62
|
|
|
62
63
|
## `streaming`
|
|
63
64
|
|
|
@@ -230,4 +231,107 @@ haechi proxy --config haechi.config.json --host 0.0.0.0 --allow-remote-bind
|
|
|
230
231
|
|
|
231
232
|
## Validation cheatsheet
|
|
232
233
|
|
|
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`.
|
|
234
|
+
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`; non-boolean `responseProtection.scanNumbers`; 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`.
|
|
235
|
+
|
|
236
|
+
# Satellite operator configuration (0.9)
|
|
237
|
+
|
|
238
|
+
The two sections below document the **independently published satellite packages** introduced in 0.9 — `haechi-dashboard` and `haechi-auth-oidc`. **These are not keys of the core `haechi.config.json` / `normalizeConfig` schema.** Each satellite is configured by passing an **options object** to its factory function (`createDashboardServer(options)` / `createOidcSessionBroker(options)`), validated by its own `normalizeDashboardConfig` / `normalizeOidcConfig`. Validation is the same **strict, fail-closed** discipline as core: an unknown option key throws, and every field below lists its fail-closed throw condition. Source: `satellites/dashboard/index.mjs`, `satellites/auth-oidc/index.mjs`. Threat-model coverage: **P1-OPS-005** (dashboard audit exposure / DNS-rebinding / remote bind) and **P1-SEC-009** (broker session/login security), per `docs/current/release-0.9-implementation-scope.md` §6.
|
|
239
|
+
|
|
240
|
+
## `haechi-dashboard` (satellite)
|
|
241
|
+
|
|
242
|
+
A zero-dependency, **read-only** audit viewer (`node:http`) that serves the audit JSONL and its hash-chain status. It takes **paths**, not a runtime. Configured via `createDashboardServer(options)`; `normalizeDashboardConfig(options)` validates and returns the effective config. Source: `satellites/dashboard/index.mjs`.
|
|
243
|
+
|
|
244
|
+
| Option | Type / values | Default | Notes / fail-closed throw |
|
|
245
|
+
|---|---|---|---|
|
|
246
|
+
| `auditPath` | non-empty string | **required** | Path to the audit JSONL. Throws if missing or not a non-empty string. |
|
|
247
|
+
| `anchorPath` | string \| `null` | `null` | Anchor stream path passed to `verifyAuditChain` for tail-truncation detection. Throws if present but not a non-empty string. |
|
|
248
|
+
| `host` | non-empty string | `127.0.0.1` | Bind address. Non-loopback requires `allowRemoteBind` **and** the remote-bind preconditions below. Throws if present but empty/non-string. |
|
|
249
|
+
| `port` | integer 0–65535 | `1018` | Listen port; `0` = OS-assigned ephemeral (intentional affordance). Throws if not an integer in `[0,65535]`. |
|
|
250
|
+
| `allowRemoteBind` | boolean | `false` | Permit a non-loopback `host`. Throws if non-boolean. Config alone is not enough — see remote-bind preconditions. |
|
|
251
|
+
| `sessionGuard` | object \| `null` | `null` | A guard implementing `authenticate(req) -> session\|null` and an optional `handlers` map. Throws if non-object or `authenticate` is not a function. `handlers` keys may **only** be the fixed broker paths `/auth/login`, `/auth/callback`, `/auth/logout` — any other key (notably `/api/*`, `/healthz`, `/`) throws, closing the auth-bypass where a guard exempts an audit route from the gate. Satisfied by injecting a `haechi-auth-oidc` broker (see below). |
|
|
252
|
+
| `window` | integer 4096–67108864 | `1048576` | Tail-read window (max bytes) for `/api/events` and `/api/summary`. Throws if not an integer in `[4096, 67108864]` (4 KiB–64 MiB). |
|
|
253
|
+
| `tlsContext` | object \| `null` | `null` | TLS material for the dashboard to terminate HTTPS itself. Throws if non-object, or if non-null but lacking **usable material** — it must carry `(key && cert)` or `pfx` (an empty `{}` is rejected so it can't green-light a non-loopback plaintext listener). |
|
|
254
|
+
| `trustProxy` | string \| `null` | `null` | Names a trusted fronting-proxy address/CIDR. Throws if non-string, empty, or a falsy-looking string (`"false"`/`"0"`). **`trustProxy` alone never authorizes a non-loopback bind** — only a real `tlsContext` does. |
|
|
255
|
+
|
|
256
|
+
### Routes
|
|
257
|
+
|
|
258
|
+
All routes are **GET/HEAD only** (any other method → `405`); the asset map is fixed in-code (no filesystem traversal):
|
|
259
|
+
|
|
260
|
+
- `/api/events` — bounded tail read of the audit JSONL, newest-first. `limit` is an integer in `[1,200]` (default 50); `cursor` is the opaque `auditIntegrity.sequence` (never a filesystem offset). Each event is rebuilt by a **recursive key-by-key allowlist projection** (no blind spread; identity carries only `subjectHash`/`issuerHash`, never scopes/labels/raw subject). Returns `windowExceeded` when a requested page predates the retained window.
|
|
261
|
+
- `/api/chain` — wraps `verifyAuditChain`; surfaces a derived `truncationDetected` boolean (the raw failure reason is **never** returned). mtime+size cached (no concurrent re-walk); over the 32 MiB cap returns `413` with `{valid:null}`; `HEAD` returns headers only without forcing a walk.
|
|
262
|
+
- `/api/summary` — aggregated detection counts (`byType`/`byAction`/`detectionCount`) over the tail window.
|
|
263
|
+
- `/healthz` — liveness only (`{status:"ok"}`); no session required even off-loopback.
|
|
264
|
+
|
|
265
|
+
### Security defaults
|
|
266
|
+
|
|
267
|
+
- **Loopback bind by default.** `host` defaults to `127.0.0.1`; binding a non-loopback host reuses core's `assertSafeProxyBind` (re-worded) and requires `allowRemoteBind`.
|
|
268
|
+
- **Remote bind is fail-closed.** A non-loopback bind requires **all** of: `allowRemoteBind: true`, a `sessionGuard`, **and** a valid `tlsContext` (the dashboard terminates TLS itself). `trustProxy` does not satisfy this — a non-loopback plaintext listener would serve audit data in cleartext while emitting HSTS, so it is refused. HSTS is emitted **only** when the server actually serves HTTPS.
|
|
269
|
+
- **Anti-DNS-rebinding Host allowlist** is the unconditional first gate on every request (including `/api/*`, `/healthz`, and any method); a bad/duplicate `Host` header → `403` before the method check.
|
|
270
|
+
- **Strict CSP + Trusted Types** (`require-trusted-types-for 'script'`, `textContent` rendering) plus `X-Frame-Options: DENY`, `Cross-Origin-Resource-Policy`/`-Opener-Policy: same-origin`, `X-Content-Type-Options: nosniff`, and `Cache-Control: no-store`; CORS headers are intentionally never set.
|
|
271
|
+
- **sessionGuard seam.** When a guard is present, every `/api/*` route is gated behind `authenticate()`; an unauthenticated request gets `401` (never a `302` redirect). The auth-exempt set is the **intersection** of the fixed broker-path allowlist and the guard's declared handlers (exact match) — a guard can never exempt an audit-data route.
|
|
272
|
+
- **Generic errors.** A 5xx returns `{error:"internal"}` only — never a stack, OS code, or filesystem path. A satellite-local fixed-window rate limiter (120 req/60s per source) fronts `/api/*`.
|
|
273
|
+
|
|
274
|
+
The bin `haechi-dashboard` (workspace) launches the server; the publish workflow is `.github/workflows/dashboard-publish.yml` (tag `dashboard-v<semver>`). `peerDependencies: { haechi: ">=0.8.0 <1.0.0" }`.
|
|
275
|
+
|
|
276
|
+
## `haechi-auth-oidc` (satellite)
|
|
277
|
+
|
|
278
|
+
A zero-dependency **interactive OIDC session broker** (authorization-code + PKCE) — the dashboard's human-login mechanism. It produces an opaque server-side session and **satisfies the dashboard `sessionGuard` contract by injection** (`{ authenticate(req), handlers: { "/auth/login", "/auth/callback", "/auth/logout" } }`). It is **not** a per-request bearer validator (that role stays with `haechi-auth-jwt`). Configured via `createOidcSessionBroker(options)`; `normalizeOidcConfig(options)` validates. Source: `satellites/auth-oidc/index.mjs`. `peerDependencies: { haechi: ">=0.8.0 <1.0.0", haechi-auth-jwt: ">=0.2.0 <1.0.0" }`.
|
|
279
|
+
|
|
280
|
+
| Option | Type / values | Default | Notes / fail-closed throw |
|
|
281
|
+
|---|---|---|---|
|
|
282
|
+
| `cryptoProvider` | object with `hmac()` | **required** | Supplies the keyed-HMAC for PII-safe identity hashes and `sessionIdHash`. Throws if `hmac` is not a function. |
|
|
283
|
+
| `issuer` | HTTPS URL string | **required** | OIDC issuer; pinned for exact string-equal discovery and single-origin endpoint checks. Throws if missing or not `https`. |
|
|
284
|
+
| `clientId` | non-empty string | **required** | OAuth client id (also the expected ID-token `aud`). Throws if missing/empty. |
|
|
285
|
+
| `clientSecret` | string \| omitted | omitted | Present ⇒ confidential client; omitted ⇒ public (PKCE-only) client. Throws if present but empty. |
|
|
286
|
+
| `redirectUri` | absolute URL string | **required** | Must be `https` (or **loopback** `http` under the carve-out), **same-origin** with the broker, and path exactly `/auth/callback`. Throws otherwise. |
|
|
287
|
+
| `scopes` | string array | `["openid"]` | `openid` is force-included (deduped); `offline_access` is stripped (refresh rotation is out of scope for 0.9). Throws if not an array of non-empty strings. |
|
|
288
|
+
| `returnToAllowlist` | string array | `["/"]` | Allowlist of **relative same-origin** return paths (must start with a single `/`, no scheme/host/`//`/backslash). Throws on a non-array or any non-conforming entry. |
|
|
289
|
+
| `sessionTtlSeconds` | integer 1–2592000 | `28800` (8h) | Absolute session lifetime. Throws if out of `[1, 2592000]` (30d ceiling). |
|
|
290
|
+
| `idleTtlSeconds` | integer 1–2592000 | `1800` (30m) | Idle timeout (sliding `lastSeen`). Throws if out of range. |
|
|
291
|
+
| `maxAgeSeconds` | integer 1–2592000 \| `null` | `null` | If set, sends OIDC `max_age` and requires `auth_time` within `maxAge + skew`. Throws if present but out of range. |
|
|
292
|
+
| `tokenEndpointAuthMethod` | `client_secret_basic` \| `client_secret_post` | `client_secret_basic` | Token-endpoint auth method. Throws on an unknown value, **or** if set without a `clientSecret` (only valid for a confidential client). |
|
|
293
|
+
| `secureCookies` | `true` \| `false` \| `"auto"` | `"auto"` | Forces or auto-derives the cookie `Secure`/`__Host-` hardening from the externally-visible scheme. Throws on any other value. |
|
|
294
|
+
| `trustProxy` | string \| `null` | `null` | Names a TLS-terminating fronting proxy; treats the browser-facing scheme as HTTPS (folds into cookie hardening). Throws if non-string or empty. |
|
|
295
|
+
| `algorithms` | non-empty string array | `["RS256","ES256"]` | Allowed JWS algorithms (passed to the verifier). Throws if not a non-empty array. |
|
|
296
|
+
| `clockSkewSeconds` | number 0–300 | (verifier default) | Leeway for ID-token time claims. Throws if out of `[0,300]`. |
|
|
297
|
+
| `prompt` | string \| `null` | `null` | Optional OIDC `prompt`. Throws if present but empty/non-string. |
|
|
298
|
+
| `pendingTtlSeconds` | integer 1–3600 | `600` (10m) | Time to complete a login (pre-auth record TTL). Throws if out of `[1,3600]`. |
|
|
299
|
+
| `pendingCap` | integer 1–1000000 | `1024` | Hard cap on concurrent in-flight logins; a full store rejects **new** logins and never evicts an in-flight auth (fail-closed). Throws if out of range. |
|
|
300
|
+
| `rateLimitMax` | integer 1–1000000 | `60` | `/auth/login`+`/auth/callback` per source per 60s window. Throws if out of range. |
|
|
301
|
+
| `fetchTimeoutMs` | integer 1–120000 | `5000` | Per-egress timeout (discovery / token / JWKS). Throws if out of range. |
|
|
302
|
+
| `fetchImpl` / `lookupImpl` / `now` | function | injected/global | Injectable `fetch` / DNS `lookup` / clock seams. Throws if present but not a function. |
|
|
303
|
+
| `sessionStore` | object | in-memory | Opaque-id → session store; must implement `get`/`set`/`delete`. Throws if present but non-conforming. |
|
|
304
|
+
| `pendingStore` | object | in-memory | Pre-auth record store; must implement `set`/`take` (atomic single-use `take`). Throws if present but non-conforming. |
|
|
305
|
+
| `auditSink` | function \| object with `record()` | none | PII-safe event sink. Throws if present but neither a function nor an object with `record()`. |
|
|
306
|
+
|
|
307
|
+
### Cookie hardening semantics
|
|
308
|
+
|
|
309
|
+
Sessions are **server-side only** — the cookie carries only an opaque id, never claims or tokens. Two cookies are used (a pre-auth cookie binding the pending record, and the session cookie). When the externally-visible scheme is HTTPS (an `https` `redirectUri`, `secureCookies: true`, or a non-null `trustProxy`), cookies use the **`__Host-` prefix + `Secure` + `HttpOnly` + `SameSite=Lax`** (`Path=/`, no `Domain`); `SameSite=Lax` lets the IdP top-level GET to `/auth/callback` carry the cookie. Under the documented **loopback-`http` carve-out** the `__Host-`/`Secure` attributes are dropped (a plaintext listener cannot set `Secure`), using the bare cookie names. An **off-loopback broker without confirmed HTTPS fails closed at construction** — a `Secure`/`__Host-` cookie is never sent over plaintext, so login would silently break. At `/auth/callback` a **fresh** session id is minted (no fixation); `/auth/logout` is non-GET, CSRF-header gated (`x-haechi-csrf`), and destroys server-side state. The access token is discarded (never stored). Audit events (`oidc.login.start`/`success`/`failure{reasonCode}`/`logout`/`session.evict`) carry only keyed-HMAC `subjectHash`/`issuerHash`/`sessionIdHash` + `provider` + a coarse `reasonCode` + timestamp.
|
|
310
|
+
|
|
311
|
+
### Wiring into the dashboard
|
|
312
|
+
|
|
313
|
+
Inject the broker as the dashboard's `sessionGuard`:
|
|
314
|
+
|
|
315
|
+
```js
|
|
316
|
+
import { createDashboardServer } from "haechi-dashboard";
|
|
317
|
+
import { createOidcSessionBroker } from "haechi-auth-oidc";
|
|
318
|
+
|
|
319
|
+
const broker = createOidcSessionBroker({
|
|
320
|
+
cryptoProvider,
|
|
321
|
+
issuer: "https://idp.example.com",
|
|
322
|
+
clientId: "haechi-dashboard",
|
|
323
|
+
clientSecret: "…",
|
|
324
|
+
redirectUri: "https://dash.example.com/auth/callback",
|
|
325
|
+
returnToAllowlist: ["/"]
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
const dashboard = createDashboardServer({
|
|
329
|
+
auditPath: ".haechi/audit.jsonl",
|
|
330
|
+
host: "0.0.0.0",
|
|
331
|
+
allowRemoteBind: true,
|
|
332
|
+
tlsContext: { key, cert }, // remote bind: dashboard terminates TLS itself
|
|
333
|
+
sessionGuard: broker // gates /api/* behind authenticate(); mounts /auth/* handlers
|
|
334
|
+
});
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
The broker's `handlers` map mounts only at the fixed broker paths the dashboard exempts from its auth gate; every `/api/*` route is gated behind `broker.authenticate(req)`. Publish workflow: `.github/workflows/auth-oidc-publish.yml` (tag `auth-oidc-v<semver>`).
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
0.4에서 예약해 둔 `authProvider`/`identity` 계약을 구현하고, identity를 실질적인 per-client 제어로 전환한다: 내장 bearer 인증, 명명된 per-client policy profile, model allowlist, request rate limiting. 이로써 Haechi를 단일 호스트에서 복수의 클라이언트/에이전트 앞에 안전하게 배치할 수 있게 된다.
|
|
12
12
|
|
|
13
|
-
**범위 결정 (2026-06-10):** 0.6은 auth 핵심에 집중한다. 원래 0.6으로 묶였던 무거운 운영 항목들 — Vault/AWS KMS 레퍼런스 어댑터, 외부 append-only audit 싱크, 서명된 릴리스 아티팩트,
|
|
13
|
+
**범위 결정 (2026-06-10):** 0.6은 auth 핵심에 집중한다. 원래 0.6으로 묶였던 무거운 운영 항목들 — Vault/AWS KMS 레퍼런스 어댑터, 외부 append-only audit 싱크, 서명된 릴리스 아티팩트, `haechi-*` 패키지 패밀리 — 은 각각 별도의 보안 설계를 받을 수 있도록 **0.7**로 이월한다.
|
|
14
14
|
|
|
15
15
|
## 2. 범위
|
|
16
16
|
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
- `auth.provider`로 선택한다:
|
|
22
22
|
- `none` (기본값) — 인증 없음; `identity`는 `null`로 유지 (0.5와 byte 단위로 동일한 audit 형태). Per-client policy는 default profile / base policy로 결정된다.
|
|
23
23
|
- `bearer` — 내장 token auth (§2.2).
|
|
24
|
-
- `external` — 주입된 `authProvider` 필요; 없으면 fail-closed (`keys.provider: external`과 동일한 방식). OIDC/JWT provider는 **0.7+ 위성 패키지** (
|
|
24
|
+
- `external` — 주입된 `authProvider` 필요; 없으면 fail-closed (`keys.provider: external`과 동일한 방식). OIDC/JWT provider는 **0.7+ 위성 패키지** (`haechi-auth-oidc`)로 남긴다; 0.6은 네트워크 IdP 코드를 포함하지 않는다.
|
|
25
25
|
|
|
26
26
|
### 2.2 내장 bearer auth + token store
|
|
27
27
|
|
|
@@ -123,8 +123,8 @@ forward
|
|
|
123
123
|
|
|
124
124
|
## 4. 명시적 비범위 (0.7+로 이월)
|
|
125
125
|
|
|
126
|
-
- OIDC/JWT provider (
|
|
127
|
-
- Vault/AWS KMS 레퍼런스 어댑터; 외부 append-only audit 싱크; 서명된 릴리스 아티팩트;
|
|
126
|
+
- OIDC/JWT provider (`haechi-auth-oidc`, `haechi-auth-jwt`) — 0.6은 bearer + external 주입만 포함.
|
|
127
|
+
- Vault/AWS KMS 레퍼런스 어댑터; 외부 append-only audit 싱크; 서명된 릴리스 아티팩트; the `haechi-*` package family.
|
|
128
128
|
- LLM token-budget limiting; 분산/공유 rate 상태.
|
|
129
129
|
- auth provider의 동적 npm 로딩 (1.0 plugin sandbox).
|
|
130
130
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
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
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,
|
|
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, the `haechi-*` package family — move to **0.7** so each gets its own security design instead of bloating one release.
|
|
14
14
|
|
|
15
15
|
## 2. Scope
|
|
16
16
|
|
|
@@ -21,7 +21,7 @@ Implement the `authProvider`/`identity` contracts reserved in 0.4 and turn ident
|
|
|
21
21
|
- Selected by `auth.provider`:
|
|
22
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
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** (
|
|
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
25
|
|
|
26
26
|
### 2.2 Built-in bearer auth + token store
|
|
27
27
|
|
|
@@ -123,8 +123,8 @@ Plus `policy.profiles`, `policy.profileBinding`, `policy.modelAllowlist`, `polic
|
|
|
123
123
|
|
|
124
124
|
## 4. Explicit non-scope (deferred to 0.7+)
|
|
125
125
|
|
|
126
|
-
- OIDC/JWT providers (
|
|
127
|
-
- Vault/AWS KMS reference adapter; external append-only audit sink; signed release artifacts;
|
|
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; the `haechi-*` package family.
|
|
128
128
|
- LLM token-budget limiting; distributed/shared rate state.
|
|
129
129
|
- Dynamic npm loading of auth providers (1.0 plugin sandbox).
|
|
130
130
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
1.0("stable", developer-preview 레이블 제거)이 차단하는 운영 스토리를 강화한다: 단일 로컬 파일을 넘어선 audit 무결성, 외부 key custody, 검증 가능한 릴리스 아티팩트. 두 번의 1.0 차단 릴리스 중 첫 번째다.
|
|
12
12
|
|
|
13
|
-
**범위 결정 (2026-06-10):** 0.7은 **ops hardening** — audit 무결성, key custody 계약, 서명된 아티팩트에 집중한다. 이전에 여기에 묶였던 **생태계** 항목들(
|
|
13
|
+
**범위 결정 (2026-06-10):** 0.7은 **ops hardening** — audit 무결성, key custody 계약, 서명된 아티팩트에 집중한다. 이전에 여기에 묶였던 **생태계** 항목들(the `haechi-*` package family, `haechi-crypto-kms` / `haechi-auth-oidc` 게시, `haechi-dashboard`, npm workspaces)은 **0.8**로 이월하며, 0.8에서 중복된 0.7 로드맵 행도 제거한다.
|
|
14
14
|
|
|
15
15
|
Core의 **zero runtime dependency** 기조는 협상 불가: 0.7의 모든 것은 `node:` 빌트인만으로 제공된다. 무거운 어댑터(AWS KMS, Vault)는 satellite/example이며, 절대 core에 포함되지 않는다.
|
|
16
16
|
|
|
@@ -38,7 +38,7 @@ audit hash chain은 변조와 재정렬은 탐지하지만, 마지막 N개 레
|
|
|
38
38
|
|
|
39
39
|
- `keys.provider: external`에 대한 `cryptoProvider` 계약을 강화하고 문서화한다: 외부 provider는 `encrypt`, `decrypt`, **그리고 `hmac`** (토큰/identity를 위해 0.4에서 추가됨)을 구현해야 하며, envelope 형태(`{ v, alg, kid, iv, ct, tag, aadHash }`)를 보존하고, canonical AAD를 바인딩하며, `kid`로 키를 선택해야 한다.
|
|
40
40
|
- `assertCryptoProviderConformance(provider)` (익스포트된 테스트 헬퍼)를 제공한다: encrypt→decrypt 왕복, AAD 불일치 거부, `hmac` 결정론 + domain separation. Satellite 어댑터는 이를 통해 자체 테스트한다.
|
|
41
|
-
- `examples/crypto-kms-reference/` 아래에 **레퍼런스 어댑터**를 제공한다 (자체 `package.json`, AWS/Vault SDK는 *optional/peer* 의존성으로, core의 `files`에 포함하지 않음): 주입 방법을 시연한다. 이것이 0.8에서(npm org 취득 후) 게시되는
|
|
41
|
+
- `examples/crypto-kms-reference/` 아래에 **레퍼런스 어댑터**를 제공한다 (자체 `package.json`, AWS/Vault SDK는 *optional/peer* 의존성으로, core의 `files`에 포함하지 않음): 주입 방법을 시연한다. 이것이 0.8에서(npm org 취득 후) 게시되는 **`haechi-crypto-kms`** satellite의 소스가 된다.
|
|
42
42
|
|
|
43
43
|
### 2.4 서명된 릴리스 아티팩트
|
|
44
44
|
|
|
@@ -47,8 +47,8 @@ audit hash chain은 변조와 재정렬은 탐지하지만, 마지막 N개 레
|
|
|
47
47
|
|
|
48
48
|
## 3. 명시적 비범위 (0.8로 이월)
|
|
49
49
|
|
|
50
|
-
-
|
|
51
|
-
-
|
|
50
|
+
- the `haechi-*` package family 생성; `haechi-crypto-kms`, `haechi-auth-oidc`, `haechi-auth-jwt` 게시.
|
|
51
|
+
- `haechi-dashboard` (읽기 전용 audit 뷰어) 및 npm workspaces 전환.
|
|
52
52
|
- 게시된 패키지로서의 실제 AWS KMS / HashiCorp Vault SDK 연동 (0.7은 계약 + 레퍼런스 example만 제공).
|
|
53
53
|
- 분산/공유 audit 또는 rate 상태.
|
|
54
54
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
Harden the operational story that 1.0 ("stable", developer-preview label removed) blocks on: audit integrity beyond a single local file, external key custody, and verifiable release artifacts. This is the first of the two 1.0-blocker releases.
|
|
12
12
|
|
|
13
|
-
**Scope decision (2026-06-10):** 0.7 is focused on **ops hardening** — audit integrity, key custody contract, signed artifacts. The **ecosystem** items previously grouped here (
|
|
13
|
+
**Scope decision (2026-06-10):** 0.7 is focused on **ops hardening** — audit integrity, key custody contract, signed artifacts. The **ecosystem** items previously grouped here (the `haechi-*` package family, publishing `haechi-crypto-kms` / `haechi-auth-oidc`, `haechi-dashboard`, npm workspaces) move to **0.8**, which also removes the duplicate 0.7 roadmap row.
|
|
14
14
|
|
|
15
15
|
Core's **zero runtime dependency** posture is non-negotiable: everything in 0.7 ships with `node:` builtins only. Heavy adapters (AWS KMS, Vault) are satellites/examples, never core.
|
|
16
16
|
|
|
@@ -39,7 +39,7 @@ The audit hash chain detects tampering and reordering but **not** deletion of th
|
|
|
39
39
|
|
|
40
40
|
- Tighten and document the `cryptoProvider` contract for `keys.provider: external`: a provider always implements `encrypt`/`decrypt`, binds canonical AAD, and selects keys by `kid`; the envelope **base shape** is `{ v, alg, kid, iv, ct, tag, aadHash }` and adapters **may add provider-specific fields** (e.g. a KMS adapter's `wrappedKey`). `hmac` is required **only by features that use it** — bearer auth and deterministic tokenization — and `createRuntime` fails closed at construction when one of those is configured without `hmac` (an encrypt-only provider is otherwise valid). Policy-bundle signing uses the local key file directly via the CLI, not the injected provider.
|
|
41
41
|
- Ship `assertCryptoProviderConformance(provider, { requireHmac = true })` (an exported test helper): encrypt→decrypt round-trip (distinct plaintexts), AAD-mismatch rejection, **tampered-ciphertext rejection (real AEAD authentication)**, and `hmac` determinism + data-dependency + domain separation + invalid-domain rejection. Satellite adapters self-test against it; pass `requireHmac: false` for an encrypt-only provider.
|
|
42
|
-
- Ship a **reference adapter** under `examples/crypto-kms-reference/` (its own `package.json`, AWS/Vault SDK as an *optional* dependency; the in-process `createInMemoryKms` is explicitly non-production) demonstrating envelope-encryption injection. It is the source that becomes the published
|
|
42
|
+
- Ship a **reference adapter** under `examples/crypto-kms-reference/` (its own `package.json`, AWS/Vault SDK as an *optional* dependency; the in-process `createInMemoryKms` is explicitly non-production) demonstrating envelope-encryption injection. It is the source that becomes the published **`haechi-crypto-kms`** satellite in 0.8 (gated on the npm org).
|
|
43
43
|
|
|
44
44
|
### 2.4 Signed release artifacts
|
|
45
45
|
|
|
@@ -48,8 +48,8 @@ The audit hash chain detects tampering and reordering but **not** deletion of th
|
|
|
48
48
|
|
|
49
49
|
## 3. Explicit non-scope (deferred to 0.8)
|
|
50
50
|
|
|
51
|
-
-
|
|
52
|
-
-
|
|
51
|
+
- Publish the `haechi-*` family: publish `haechi-crypto-kms`, `haechi-auth-oidc`, `haechi-auth-jwt`.
|
|
52
|
+
- `haechi-dashboard` (read-only audit viewer) and npm workspaces conversion.
|
|
53
53
|
- Real AWS KMS / HashiCorp Vault SDK integration as a published package (0.7 ships the contract + reference example only).
|
|
54
54
|
- Distributed/shared audit or rate state.
|
|
55
55
|
|