haechi 1.1.1 → 1.2.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.
Files changed (34) hide show
  1. package/README.ko.md +97 -97
  2. package/README.md +2 -2
  3. package/SECURITY.md +19 -11
  4. package/docs/README.md +2 -0
  5. package/docs/current/api-stability.ko.md +26 -26
  6. package/docs/current/compliance-mapping.ko.md +53 -0
  7. package/docs/current/compliance-mapping.md +53 -0
  8. package/docs/current/config-version.ko.md +30 -0
  9. package/docs/current/config-version.md +51 -0
  10. package/docs/current/configuration.ko.md +242 -102
  11. package/docs/current/configuration.md +149 -9
  12. package/docs/current/operations-runbook.ko.md +121 -0
  13. package/docs/current/operations-runbook.md +204 -0
  14. package/docs/current/release-process.ko.md +19 -20
  15. package/docs/current/release-process.md +1 -2
  16. package/docs/current/reliability-hardening-track.ko.md +77 -0
  17. package/docs/current/reliability-hardening-track.md +77 -0
  18. package/docs/current/risk-register-release-gate.ko.md +26 -27
  19. package/docs/current/risk-register-release-gate.md +27 -20
  20. package/docs/current/security-whitepaper.ko.md +102 -0
  21. package/docs/current/security-whitepaper.md +102 -0
  22. package/docs/current/shared-responsibility.ko.md +33 -24
  23. package/docs/current/shared-responsibility.md +12 -3
  24. package/docs/current/threat-model.ko.md +12 -12
  25. package/docs/current/threat-model.md +3 -3
  26. package/haechi.config.example.json +19 -3
  27. package/package.json +6 -2
  28. package/packages/audit/index.mjs +26 -2
  29. package/packages/cli/bin/haechi.mjs +54 -8
  30. package/packages/cli/runtime.mjs +398 -10
  31. package/packages/core/index.mjs +189 -15
  32. package/packages/filter/index.mjs +299 -9
  33. package/packages/metrics/index.mjs +181 -0
  34. package/packages/proxy/index.mjs +535 -41
@@ -0,0 +1,102 @@
1
+ # Haechi 보안 백서
2
+
3
+ - 문서 상태: Living document (WS6 — reliability-hardening-track §WS6)
4
+ - 이 문서의 성격: **통제 매핑 + 구조화된 자체 평가**이며, 인증이나 독립 감사가 아닙니다.
5
+ - 출처: `packages/*` 코드, `docs/current/threat-model.md`, `docs/current/risk-register-release-gate.md`. 이 백서는 그 내용을 *매핑*할 뿐 그대로 재서술하지 않으며, 내용을 복제하는 대신 저장소 경로와 위험 ID를 인용합니다.
6
+
7
+ ## 0. 이 문서가 무엇이고 — 무엇이 아닌가
8
+
9
+ Haechi는 **AI 컨텍스트 집행 계층**입니다. OpenAI 호환 / MCP / vLLM / Ollama / llama.cpp JSON payload를 검사하여 PII와 비밀 정보를 탐지하고, 모델·도구·로그에 도달하기 전에 redact / mask / tokenize / encrypt / block 합니다.
10
+
11
+ 이 문서는 Haechi가 **실제로 출시한** 통제를, 저장소 전반에서 이미 인용하는 두 프레임워크 — **OWASP Top 10 for LLM Applications (2025)**와 **NIST AI Risk Management Framework (AI RMF 1.0)** — 에 매핑하고, reliability-hardening track이 잡아내고 수정한 적대적 발견을 **구조화된 자체 모의해킹(self-pentest)**으로 기록합니다.
12
+
13
+ 이 문서는 명시적으로 다음이 **아닙니다**:
14
+ - 컴플라이언스 인증·확약·보증 보고서(reliability-hardening-track §5 비목표 및 `SECURITY.md` Scope 참고)
15
+ - 독립 제3자 침투 시험
16
+ - 모든 OWASP-LLM / NIST-AI-RMF 항목이 *완전히* 완화되었다는 주장. 여러 항목은 **책임 공유**(`docs/current/shared-responsibility.md`)이거나 범위 밖(`docs/current/threat-model.md` §4)입니다. 아래에는 출시 코드에 실제로 존재하는 통제만 매핑하며, 희망 사항은 담지 않습니다.
17
+
18
+ ## 1. 통제 목록 (출시된 표면)
19
+
20
+ 아래 매핑된 모든 통제는 load-bearing하며 테스트로 뒷받침되는 동작입니다. 각 통제의 근거는 이 표가 아니라 코드 경로와 threat-model / risk-register 행입니다.
21
+
22
+ | # | 통제 | 위치 | 근거 |
23
+ |---|---|---|---|
24
+ | C1 | 탐지 + redact/mask/tokenize/encrypt/block 파이프라인 | `packages/core` (`protectJson`), `packages/filter`, `packages/policy` | threat-model §3 |
25
+ | C2 | fail-closed 집행(알 수 없는 policy/config/`target.type`은 throw; non-JSON/무효/압축/초과 응답은 fail-closed) | `packages/cli/runtime.mjs` (`normalizeConfig`), `packages/proxy` (`maybeProtectResponse`) | CLAUDE.md 불변식; P1-SEC-010 |
26
+ | C3 | audit SHA-256 hash chain + head anchoring(변조 + tail-truncation 증거) | `packages/audit` (`verifyAuditChain`, `audit.anchor`) | P1-SEC-003 |
27
+ | C4 | audit에 평문/PII 없음(`FORBIDDEN_KEYS`); keyed-HMAC identity 해시; 구조화 경로 키 해싱 | `packages/audit`, `packages/auth` (`buildIdentity`) | P1-SEC-012 |
28
+ | C5 | streaming 기본 차단; bounded·opt-in 검사(cross-frame sliding buffer) | `packages/proxy`, `packages/stream-filter`, `core` (`createStreamProtector`) | P1-SEC-007 |
29
+ | C6 | body 읽기 전 클라이언트 auth gate; named policy-profile 해석; identity별 rate limit | `packages/proxy` (`authorizeRequest`), `packages/auth` | SECURITY.md; threat-model §3 |
30
+ | C7 | 서명·sandbox된 `authProvider` 플러그인(Ed25519 + trust anchor + pin/floor/revocation + worker/process 격리) | `packages/plugin`, `packages/cli/runtime.mjs` | P1-SEC-024 / P1-SEC-027 |
31
+ | C8 | host-mediated fetch의 SSRF 가드; absolute-form proxy target 거부 | `packages/ssrf`, `packages/proxy` (`assertRelativeProxyTarget`) | P1-SEC-009 / P1-SEC-028 |
32
+ | C9 | token-vault reveal 거버넌스(`revealPolicy`), retention, token id 기준 audit된 reveal/purge | `packages/token-vault` | P1-SEC-002 |
33
+ | C10 | 정책은 강해지기만 함(`ACTION_STRENGTH`); privacy profile은 강화만 가능, 약화 불가 | `packages/policy`, `packages/privacy-profiles` | P1-SEC-005 |
34
+ | C11 | 탐지 정밀도 통제(`filters.minConfidence`, `filters.allowlist`); hard-block 타입은 **억제 불가** | `packages/filter`, `packages/core` | WS2c |
35
+ | C12 | 매칭 전 string leaf의 NFKC 정규화(full-width/혼동 문자 회피) | `packages/core` / `packages/filter` | WS2d |
36
+ | C13 | **proxy TLS / remote-bind 강화(이 WS):** remote bind는 Haechi가 직접 TLS 종단(`proxy.tls`)하거나, 신뢰 hop을 명시적으로 확인(`proxy.trustForwardedProto`, `X-Forwarded-Proto: https` 강제)해야 하며, 그렇지 않으면 기동 시 throw | `packages/proxy` (`assertSafeProxyTransport`, 서버 선택, forwarded-proto gate), `packages/cli/runtime.mjs` (`proxy.tls` 해석) | 본 문서 §3 |
37
+ | C14 | 기본 loopback 바인드; `--allow-remote-bind` 없이는 non-loopback 거부 | `packages/proxy` (`assertSafeProxyBind`) | SECURITY.md |
38
+ | C15 | bounded recursion-depth + byte/encoding 가드(fail-closed 4xx; non-UTF-8 거부) | `packages/core`, `packages/proxy` (`readBody`) | WS5 |
39
+ | C16 | 운영 fail-closed 신호(audit 쓰기 불가 시 `/__haechi/ready` 503; metrics에 PII 없음) | `packages/proxy` (`handleReady`), `packages/metrics` | WS4 |
40
+
41
+ ## 2. OWASP Top 10 for LLM Applications (2025) 매핑
42
+
43
+ 각 행은 OWASP-LLM 위험을 그 위험을 다루는 Haechi 통제에 매핑하고 정직한 잔여 위험을 기록합니다. 주로 운영자/모델의 책임인 위험은 완화로 주장하지 않고 **책임 공유 / 범위 밖**으로 표기합니다.
44
+
45
+ | OWASP-LLM (2025) | Haechi 통제 | 커버리지 & 정직한 잔여 위험 |
46
+ |---|---|---|
47
+ | **LLM01 Prompt Injection** | C5(응답/도구 결과 injection 탐지는 기본 report-only), C1 | **부분 / 설계상.** injection 탐지는 응답/도구 결과 방향에서 동작하며 명시적 격상 전에는 report-only입니다(CLAUDE.md 불변식). Haechi는 prompt injection을 "해결"하지 않습니다 — `threat-model.md` §4는 prompt-injection *예방*을 비목표로 둡니다. 도구 결과가 컨텍스트로 재유입되기 전 비밀/PII를 탐지해 폭발 반경을 줄입니다. |
48
+ | **LLM02 Sensitive Information Disclosure** | C1, C2, C9, C4 | **주 사용 사례.** detect→redact/mask/tokenize/encrypt/block 파이프라인과 응답 보호가 바로 이 통제입니다. 잔여: 탐지는 regex+validator이며 ML이 아닙니다(비목표); 문서화된 제외(base64/URL-query)는 유효합니다(threat-model §4). |
49
+ | **LLM03 Supply Chain** | C7, SBOM + SHA-pinned CI(P1-OPS-002/006) | **플러그인 경로에 대해 대응.** 동적 코드는 서명·trust-anchor·pin/floor/revocable한 `authProvider` 플러그인으로만 허용됩니다. 코어는 zero-runtime-dependency를 유지해 의존성 공격 표면을 줄입니다. |
50
+ | **LLM04 Data & Model Poisoning** | — | **범위 밖.** Haechi는 인라인 컨텍스트 필터이며 학습/데이터 파이프라인 통제가 아닙니다. 완화가 아니라 범위 밖으로 표기합니다. |
51
+ | **LLM05 Improper Output Handling** | C5, C2, C1 | **대응.** 응답/stream 보호가 모델 출력을 호출자/도구 도달 전에 검사하며, non-JSON/무효/압축/초과 응답은 fail-closed입니다. 잔여: block 전에 이미 방출된 streaming 바이트는 회수 불가(문서화). |
52
+ | **LLM06 Excessive Agency** | C6, C7, C9 | **부분.** auth gate + named policy profile + model allowlist가 누가 게이트웨이를 구동하고 어떤 모델/연산이 도달 가능한지 제약하며, token reveal은 거버넌스됩니다. 게이트웨이 너머의 에이전트 수준 인가는 운영자의 몫입니다. |
53
+ | **LLM07 System Prompt Leakage** | C1, C5, C4 | **부분.** prompt/응답에 박힌 비밀/PII는 탐지·보호되며, audit는 원문 prompt를 저장하지 않습니다(C4). 모델이 스스로 system prompt 텍스트를 방출하는 것은 막지 않습니다. |
54
+ | **LLM08 Vector & Embedding Weaknesses** | — | **범위 밖.** RAG/vector-store 구성요소 없음. |
55
+ | **LLM09 Misinformation** | — | **범위 밖.** 사실성 통제가 아닙니다. |
56
+ | **LLM10 Unbounded Consumption** | C6(rate limit), C15(byte/depth 한계), C16(backpressure/timeout) | **게이트웨이에서 대응.** identity별 rate limit, request byte + nesting-depth 상한, max-in-flight backpressure(503 + `Retry-After`), 튜닝된 timeout. 잔여: 내장 rate limiter는 단일 프로세스(다중 replica는 주입된 공유 limiter 필요 — shared-responsibility.md). |
57
+
58
+ ## 3. NIST AI RMF (AI RMF 1.0) 매핑
59
+
60
+ AI RMF 네 기능에 매핑합니다. Haechi는 운영자가 자신의 AI-RMF 프로그램 안에서 사용하는 *기술 통제 표면*이며, 운영자를 위한 거버넌스를 대신 구현하지 않습니다.
61
+
62
+ | AI RMF 기능 | Haechi 통제 | Haechi의 기여(그리고 경계) |
63
+ |---|---|---|
64
+ | **GOVERN** | C2, C10, C14, 문서화된 threat-model + 책임 공유 구분 | fail-closed 기본값, 강화-전용 정책 격자, 기본 loopback, Haechi가 하는 일과 운영자가 소유하는 일의 명시적 문서화. 거버넌스 *정책*은 운영자의 몫입니다. |
65
+ | **MAP** | C1, 탐지 타입 행렬(`configuration.md`), threat-model §1–2 | 민감 데이터가 prompt/응답/도구 호출을 통해 *어디로* 흐르고 *어떤* 범주가 탐지되는지 드러내, 운영자가 게이트웨이 경계에서 AI-시스템 데이터 위험을 매핑하도록 돕습니다. |
66
+ | **MEASURE** | C3, C4, C11, `bench:detection` precision/recall gate, `/__haechi/metrics` | 변조 증거 audit, CI gate로 묶인 precision/recall 측정 하니스, PII 없는 운영 metrics가 측정 가능한 신호를 제공합니다. 잔여: 탐지 metrics는 regex/validator 규칙만 측정합니다. |
67
+ | **MANAGE** | C2, C5, C6, C9, C13, C16, operations-runbook | 인라인 집행, streaming 봉쇄, auth gate, token-vault 거버넌스, TLS 강화 remote bind, readiness/backpressure, Day-2 runbook(rotation/retention)이 운영자가 프로덕션에서 위험을 관리하게 합니다. |
68
+
69
+ ## 4. 구조화된 자체 모의해킹(self-pentest)
70
+
71
+ 이것은 **자체 평가**입니다 — Haechi가 자신의 통제를 스스로 적대적으로 시험한 것이며, 독립 침투 시험이 아닙니다. 아래 각 발견은 테스트로 재현·수정되었고 지금은 회귀로 보호됩니다.
72
+
73
+ ### 4.1 방법론
74
+ - **실환경 검증.** proxy + 보호 파이프라인은 env-gated 통합 스위트(`tests/local-inference.integration.test.mjs`; P1-OPS-003)를 통해 실제 OpenAI 호환(vLLM) 및 Ollama 네이티브 엔드포인트에 대해 동작합니다. 모델 서버가 없으면 CI가 이를 건너뛰므로 mock-only가 아니라 opt-in입니다.
75
+ - **적대적 회귀 코퍼스.** 라벨링된 positive/hard-negative 탐지 코퍼스가 `npm run bench:detection`을 구동하며 CI gate(`scan:detection`)로 묶여, precision/recall 회귀가 빌드를 실패시킵니다(WS2a).
76
+ - **테스트 규율로서의 fail-closed 단언.** 모든 신규 경로(depth guard, readiness, backpressure, env overlay, §3의 TLS remote-bind guard)는 happy path뿐 아니라 *throw/deny* 방향을 단언하는 테스트와 함께 출시됩니다.
77
+
78
+ ### 4.2 잡아내고 수정한 발견 (선별)
79
+
80
+ | 발견 | 분류 | 무엇이 깨졌나 | 수정 + 가드 |
81
+ |---|---|---|---|
82
+ | **WS2d — Unicode 회피** | 탐지 우회 | full-width / 혼동 코드포인트가 비밀/PII 값을 모든 regex 규칙 앞에서 통과시켰습니다(string leaf를 정규화 전에 매칭). | 매칭 전 string leaf를 NFKC 정규화(C12); 탐지 코퍼스의 회피 fixture로 커버. |
83
+ | **WS2c — bearer-recall 회귀** | 탐지 정밀도 | "Bearer …" 산문 false positive를 줄이려 추가한 context anchor가 *실제* bearer-token 비밀까지 억제했습니다(코퍼스가 잡은 recall 회귀). | anchor를 재범위화해 hard-block `secret` 타입이 allowlist/anchor로 절대 억제되지 않도록 했습니다(C11 불변식); 정밀도 통제 테스트가 FP 절감과 recall 하한을 모두 고정. |
84
+ | **WS5 — deep-nesting 스택 오버플로** | 가용성(DoS) | `maxRequestBytes` 안의 깊게 중첩된 JSON이 재귀 스택을 넘쳐 uncaught crash가 났습니다. | 설정 가능한 `limits.maxNestingDepth` fail-closed 4xx(C15); deep-nesting 테스트. |
85
+ | **P1-SEC-009 — proxy SSRF / absolute target** | SSRF | absolute/protocol-relative request target이 upstream을 우회시킬 수 있었습니다. | origin-form 전용 target; upstream URL은 고정 upstream에 path+search만 결합(C8). |
86
+ | **WS6 — 평문 remote bind(이 WS)** | 기밀성 | non-loopback 바인드가 plain HTTP를 제공해 bearer token + payload를 평문 노출했습니다. | 이제 remote bind는 Haechi 종단 TLS 또는 `X-Forwarded-Proto: https`를 강제하는 명시적 `trustForwardedProto` hop을 요구하며, 그렇지 않으면 기동 시 throw(C13). `tests/proxy-tls.test.mjs`로 가드(TLS 없을 때 throw, https-over-TLS smoke, forwarded-proto 거부, fail-closed config). |
87
+
88
+ ### 4.3 수용된 잔여 위험 (정직하게)
89
+ - 서명된 플러그인 자신의 `fs`/`fetch`는 1.0 `worker_threads` 모드에서 차단되지 않습니다(메모리/크래시 격리만); opt-in 1.1 `process-isolated` 런타임에서만 집행됩니다(threat-model §3, P1-SEC-027).
90
+ - 단일 프로세스 rate limiter / audit chain / token vault: 다중 replica 안전성은 주입된 공유 저장소가 필요합니다(shared-responsibility.md; reliability-hardening-track §3).
91
+ - 탐지는 regex + validator로 유지됩니다(ML 없음); 문서화된 base64 / URL-query 제외는 유효합니다(threat-model §4).
92
+
93
+ ## 5. 공개(Disclosure)
94
+
95
+ 의심되는 취약점은 `SECURITY.md`와 `/.well-known/security.txt`에 따라 GitHub **private vulnerability reporting**(Security Advisories) <https://github.com/raeseoklee/haechi/security/advisories> 으로 신고하십시오. 신고에 실제 비밀, 프로덕션 prompt, 고객 데이터, 개인정보를 포함하지 마십시오.
96
+
97
+ ## 6. 상호 참조
98
+ - `docs/current/threat-model.md` — 권위 있는 위협 모델과 제외 항목.
99
+ - `docs/current/risk-register-release-gate.md` — 위험별 해결 상태와 release gate.
100
+ - `docs/current/compliance-mapping.md` — 통제-의무 매핑 + DSAR/retention 워크플로.
101
+ - `docs/current/shared-responsibility.md` — Haechi가 소유하는 것 vs. 운영자.
102
+ - `docs/current/operations-runbook.md` — Day-2 운영, rotation, retention.
@@ -0,0 +1,102 @@
1
+ # Haechi Security Whitepaper
2
+
3
+ - Status: Living document (WS6 — reliability-hardening-track §WS6)
4
+ - Scope of this document: a **control mapping + structured self-assessment**, not a certification or an independent audit.
5
+ - Source of truth: the code under `packages/*`, `docs/current/threat-model.md`, and `docs/current/risk-register-release-gate.md`. This whitepaper *maps* those; it does not restate them wholesale, and it cites repo paths and risk IDs rather than duplicating their content.
6
+
7
+ ## 0. What this is — and is not
8
+
9
+ Haechi is an **AI context enforcement layer**: it inspects and protects OpenAI-compatible / MCP / vLLM / Ollama / llama.cpp JSON payloads (detecting PII and secrets, then redacting / masking / tokenizing / encrypting / blocking them) before they reach models, tools, or logs.
10
+
11
+ This document maps the controls Haechi **actually ships** to two frameworks already cited across the repo — the **OWASP Top 10 for LLM Applications (2025)** and the **NIST AI Risk Management Framework (AI RMF 1.0)** — and records a **structured self-pentest** of the adversarial findings the reliability-hardening track caught and fixed.
12
+
13
+ It is explicitly **NOT**:
14
+ - a compliance certification, attestation, or assurance report (see the reliability-hardening-track §5 non-goal and `SECURITY.md` Scope);
15
+ - an independent third-party penetration test;
16
+ - a claim that every OWASP-LLM / NIST-AI-RMF item is *fully* mitigated. Several are **shared responsibility** (`docs/current/shared-responsibility.md`) or out of scope (`docs/current/threat-model.md` §4). Only controls that exist in the shipped code are mapped below; nothing here is aspirational.
17
+
18
+ ## 1. Control inventory (the shipped surface)
19
+
20
+ Every control mapped below is load-bearing, test-backed behavior. The authority for each is the code path and the threat-model / risk-register row, not this table.
21
+
22
+ | # | Control | Where it lives | Authority |
23
+ |---|---|---|---|
24
+ | C1 | Detection + redaction/mask/tokenize/encrypt/block pipeline | `packages/core` (`protectJson`), `packages/filter`, `packages/policy` | threat-model §3 |
25
+ | C2 | Fail-closed enforcement (unknown policy/config/`target.type` throws; non-JSON/invalid/compressed/oversized responses fail closed) | `packages/cli/runtime.mjs` (`normalizeConfig`), `packages/proxy` (`maybeProtectResponse`) | CLAUDE.md invariants; P1-SEC-010 |
26
+ | C3 | Audit SHA-256 hash chain + head anchoring (tamper + tail-truncation evidence) | `packages/audit` (`verifyAuditChain`, `audit.anchor`) | P1-SEC-003 |
27
+ | C4 | No plaintext/PII in audit (`FORBIDDEN_KEYS`); keyed-HMAC identity hashes; structured-path key hashing | `packages/audit`, `packages/auth` (`buildIdentity`) | P1-SEC-012 |
28
+ | C5 | Streaming blocked by default; bounded, opt-in inspection with a cross-frame sliding buffer | `packages/proxy`, `packages/stream-filter`, `core` (`createStreamProtector`) | P1-SEC-007 |
29
+ | C6 | Client auth gate before body-read; named policy-profile resolution; per-identity rate limit | `packages/proxy` (`authorizeRequest`), `packages/auth` | SECURITY.md; threat-model §3 |
30
+ | C7 | Signed, sandboxed `authProvider` plugin (Ed25519 + trust anchors + pin/floor/revocation + worker/process isolation) | `packages/plugin`, `packages/cli/runtime.mjs` | P1-SEC-024 / P1-SEC-027 |
31
+ | C8 | SSRF guard on host-mediated fetches; absolute-form proxy target rejection | `packages/ssrf`, `packages/proxy` (`assertRelativeProxyTarget`) | P1-SEC-009 / P1-SEC-028 |
32
+ | C9 | Token-vault reveal governance (`revealPolicy`), retention, audited reveal/purge by token id | `packages/token-vault` | P1-SEC-002 |
33
+ | C10 | Policies only get stronger (`ACTION_STRENGTH`); privacy profiles may strengthen, never weaken | `packages/policy`, `packages/privacy-profiles` | P1-SEC-005 |
34
+ | C11 | Detection precision controls (`filters.minConfidence`, `filters.allowlist`) that **cannot** suppress hard-block types | `packages/filter`, `packages/core` | WS2c |
35
+ | C12 | NFKC normalization of string leaves before matching (full-width/confusable evasion) | `packages/core` / `packages/filter` | WS2d |
36
+ | C13 | **Proxy TLS / remote-bind hardening (this WS):** a remote bind requires Haechi to terminate TLS (`proxy.tls`) or an explicit trusted-hop acknowledgement (`proxy.trustForwardedProto`, enforcing `X-Forwarded-Proto: https`); otherwise it throws at startup | `packages/proxy` (`assertSafeProxyTransport`, server selection, forwarded-proto gate), `packages/cli/runtime.mjs` (`proxy.tls` resolution) | this document §3 |
37
+ | C14 | Loopback bind by default; non-loopback refused without `--allow-remote-bind` | `packages/proxy` (`assertSafeProxyBind`) | SECURITY.md |
38
+ | C15 | Bounded recursion-depth + byte/encoding guards (fail-closed 4xx; non-UTF-8 rejected) | `packages/core`, `packages/proxy` (`readBody`) | WS5 |
39
+ | C16 | Operability fail-closed signals (`/__haechi/ready` 503 when audit not writable; metrics carry no PII) | `packages/proxy` (`handleReady`), `packages/metrics` | WS4 |
40
+
41
+ ## 2. OWASP Top 10 for LLM Applications (2025) mapping
42
+
43
+ Each row maps an OWASP-LLM risk to the Haechi control(s) that address it, with the honest residual. Where a risk is primarily the operator's / model's responsibility, it is marked **Shared / Out of scope** rather than claimed as mitigated.
44
+
45
+ | OWASP-LLM (2025) | Haechi control(s) | Coverage & honest residual |
46
+ |---|---|---|
47
+ | **LLM01 Prompt Injection** | C5 (response/tool-result injection detection is report-only by default), C1 | **Partial / by design.** Injection detection runs on the response/tool-result direction and is report-only unless explicitly escalated (CLAUDE.md invariant). Haechi does not "solve" prompt injection — `threat-model.md` §4 lists prompt-injection *prevention* as a non-goal. It reduces blast radius by detecting secrets/PII in tool results before they re-enter context. |
48
+ | **LLM02 Sensitive Information Disclosure** | C1, C2, C9, C4 | **Primary use case.** The detect→redact/mask/tokenize/encrypt/block pipeline plus response protection is exactly this control. Residual: detection is regex+validator, not ML (non-goal); documented exclusions (base64/URL-query) stand (threat-model §4). |
49
+ | **LLM03 Supply Chain** | C7, plus SBOM + SHA-pinned CI (P1-OPS-002/006) | **Addressed for the plugin path.** Dynamic code is admitted only as a signed, trust-anchored, pinned/floored/revocable `authProvider` plugin. Core stays zero-runtime-dependency, shrinking the dependency attack surface. |
50
+ | **LLM04 Data & Model Poisoning** | — | **Out of scope.** Haechi is an inline context filter, not a training/data-pipeline control. Marked out of scope, not mitigated. |
51
+ | **LLM05 Improper Output Handling** | C5, C2, C1 | **Addressed.** Response/stream protection inspects model output before it reaches the caller/tools; fail-closed for non-JSON/invalid/compressed/oversized responses. Residual: streaming bytes already emitted before a block cannot be retracted (documented). |
52
+ | **LLM06 Excessive Agency** | C6, C7, C9 | **Partial.** The auth gate + named policy profiles + model allowlist constrain who can drive the gateway and which models/operations are reachable; token reveal is governed. Agent-level authorization beyond the gateway is the operator's. |
53
+ | **LLM07 System Prompt Leakage** | C1, C5, C4 | **Partial.** Secrets/PII embedded in prompts/responses are detected and protected; audit never stores raw prompt text (C4). Haechi does not prevent a model from emitting its own system prompt text. |
54
+ | **LLM08 Vector & Embedding Weaknesses** | — | **Out of scope.** No RAG/vector-store component. |
55
+ | **LLM09 Misinformation** | — | **Out of scope.** Not a factuality control. |
56
+ | **LLM10 Unbounded Consumption** | C6 (rate limit), C15 (byte/depth limits), C16 (backpressure/timeouts) | **Addressed at the gateway.** Per-identity rate limit, request byte + nesting-depth caps, max-in-flight backpressure (503 + `Retry-After`), tuned timeouts. Residual: the built-in rate limiter is single-process (multi-replica needs an injected shared limiter — shared-responsibility.md). |
57
+
58
+ ## 3. NIST AI RMF (AI RMF 1.0) mapping
59
+
60
+ Mapped to the four AI RMF functions. Haechi is a *technical control surface* an operator uses inside their own AI-RMF program; it does not implement governance for them.
61
+
62
+ | AI RMF function | Haechi control(s) | What Haechi contributes (and the boundary) |
63
+ |---|---|---|
64
+ | **GOVERN** | C2, C10, C14, the documented threat-model + shared-responsibility split | Fail-closed defaults, a stronger-only policy lattice, loopback-by-default, and explicit documentation of what Haechi does vs. what the operator owns. Governance *policy* remains the operator's. |
65
+ | **MAP** | C1, the detection-type matrix (`configuration.md`), threat-model §1–2 | Surfaces *where* sensitive data flows through prompts/responses/tool calls and *which* categories are detected, helping an operator map AI-system data risks at the gateway boundary. |
66
+ | **MEASURE** | C3, C4, C11, the `bench:detection` precision/recall gate, `/__haechi/metrics` | Tamper-evident audit, a precision/recall measurement harness wired as a CI gate, and PII-free operational metrics give an operator measurable signals. Residual: detection metrics measure regex/validator rules only. |
67
+ | **MANAGE** | C2, C5, C6, C9, C13, C16, the operations-runbook | Inline enforcement, streaming containment, the auth gate, token-vault governance, TLS-hardened remote binding, readiness/backpressure, and a Day-2 runbook (rotation/retention) let an operator manage risk in production. |
68
+
69
+ ## 4. Structured self-pentest
70
+
71
+ This is a **self-assessment** — Haechi's own adversarial testing of its own controls — not an independent pentest. Each finding below was reproduced as a test, fixed, and is now regression-guarded.
72
+
73
+ ### 4.1 Methodology
74
+ - **Real-environment validation.** The proxy + protection pipeline is exercised against real OpenAI-compatible (vLLM) and Ollama native endpoints via the env-gated integration suite (`tests/local-inference.integration.test.mjs`; P1-OPS-003). CI skips these when no model server is present, so they are opt-in rather than mocked-only.
75
+ - **Adversarial regression corpus.** A labeled positive/hard-negative detection corpus drives `npm run bench:detection`, wired as a CI gate (`scan:detection`) so a precision/recall regression fails the build (WS2a).
76
+ - **Fail-closed assertion as a test discipline.** Every new path (depth guard, readiness, backpressure, env overlay, and the TLS remote-bind guard in §3) ships a test that asserts the *throw/deny* direction, not only the happy path.
77
+
78
+ ### 4.2 Findings caught and fixed (selected)
79
+
80
+ | Finding | Class | What broke | Fix + guard |
81
+ |---|---|---|---|
82
+ | **WS2d — Unicode evasion** | Detection bypass | Full-width / confusable code points slipped a secret/PII value past every regex rule (string leaves were matched pre-normalization). | NFKC-normalize string leaves before matching (C12); covered by the evasion fixtures in the detection corpus. |
83
+ | **WS2c — bearer-recall regression** | Detection precision | A context anchor added to cut "Bearer …"-in-prose false positives also suppressed *real* bearer-token secrets (a recall regression the corpus caught). | The anchor was re-scoped so the hard-block `secret` type is never suppressed by an allowlist/anchor (C11 invariant); the precision-control tests pin both the FP cut **and** the recall floor. |
84
+ | **WS5 — deep-nesting stack overflow** | Availability (DoS) | A deeply nested JSON within `maxRequestBytes` overflowed the recursion stack → uncaught crash. | Configurable `limits.maxNestingDepth` fail-closed 4xx (C15); a deep-nesting test. |
85
+ | **P1-SEC-009 — proxy SSRF / absolute target** | SSRF | An absolute/protocol-relative request target could redirect the upstream. | Origin-form-only targets; the upstream URL combines only path+search with the fixed upstream (C8). |
86
+ | **WS6 — remote bind in plaintext (this WS)** | Confidentiality | A non-loopback bind served plain HTTP, exposing bearer tokens + payloads in cleartext. | A remote bind now requires Haechi-terminated TLS or an explicit `trustForwardedProto` hop enforcing `X-Forwarded-Proto: https`; otherwise it throws at startup (C13). Guarded by `tests/proxy-tls.test.mjs` (throw-without-TLS, https-over-TLS smoke, forwarded-proto rejection, fail-closed config). |
87
+
88
+ ### 4.3 Accepted residuals (honest)
89
+ - A signed plugin's own `fs`/`fetch` is not blocked in the 1.0 `worker_threads` mode (memory/crash isolation only); enforced only in the opt-in 1.1 `process-isolated` runtime (threat-model §3, P1-SEC-027).
90
+ - Single-process rate limiter / audit chain / token vault: multi-replica safety needs an injected shared store (shared-responsibility.md; reliability-hardening-track §3).
91
+ - Detection stays regex + validators (no ML); documented base64 / URL-query exclusions stand (threat-model §4).
92
+
93
+ ## 5. Disclosure
94
+
95
+ Report suspected vulnerabilities via GitHub **private vulnerability reporting** (Security Advisories) at <https://github.com/raeseoklee/haechi/security/advisories>, per `SECURITY.md` and `/.well-known/security.txt`. Do not include real secrets, production prompts, customer data, or personal information in a report.
96
+
97
+ ## 6. Cross-references
98
+ - `docs/current/threat-model.md` — the authoritative threat model and exclusions.
99
+ - `docs/current/risk-register-release-gate.md` — the per-risk resolution status and release gates.
100
+ - `docs/current/compliance-mapping.md` — the control-to-obligation mapping + DSAR/retention workflow.
101
+ - `docs/current/shared-responsibility.md` — what Haechi owns vs. the operator.
102
+ - `docs/current/operations-runbook.md` — Day-2 operations, rotation, and retention.
@@ -1,38 +1,47 @@
1
1
  # Haechi Shared Responsibility
2
2
 
3
- - 문서 상태: Draft 0.1
3
+ - 문서 상태: Living document (core 1.2.x 추적)
4
4
  - 작성일: 2026-06-10
5
- - 기준 버전: 0.3.2
6
5
 
7
6
  ## 1. 책임 매트릭스
8
7
 
9
8
  | 영역 | Haechi 제공 | 사용자/운영자 책임 |
10
9
  |---|---|---|
11
- | 로컬 개발 | CLI, default config, dev key 생성 | dev key를 운영/공유 환경에 재사용하지 않음 |
12
- | 정책 집행 | redact/mask/tokenize/encrypt/block pipeline | 규제/조직 정책에 맞는 action 선택 |
13
- | HTTP proxy | loopback 기본, remote bind guard, body/response limit | 인증, TLS termination, firewall, upstream auth |
14
- | Streaming | 기본 차단 | pass-through 사용 보호 미적용 위험 수용 |
15
- | TokenVault | 암호화 저장, reveal 기본 차단, purge | reveal 승인 절차, DSAR/retention 운영 |
16
- | Audit | 평문 제거, hash chain | append-only storage, backup, 보존 기간, 외부 서명 |
17
- | Key custody | local dev key, external crypto provider contract | KMS/HSM/Vault adapter 구현, rotation, access review |
18
- | Plugin | manifest validation, dynamic runtime 차단 | plugin code review, sandbox 제공 실행 금지 |
19
- | MCP | JSON-RPC/method allowlist | MCP server auth, resource consent, env secret allowlist |
20
- | Privacy profile | KR/EU/US baseline actions | 법률 검토, data residency, cross-border transfer evidence |
10
+ | 로컬 개발 | CLI, default config, dev key 생성 | dev key를 운영 환경이나 공유 환경에 재사용하지 않습니다 |
11
+ | 정책 집행 | redact/mask/tokenize/encrypt/block pipeline | 규제 정책과 조직 정책에 맞는 action 선택합니다 |
12
+ | HTTP proxy | loopback 기본값, remote bind guard, body/response limit | 인증, TLS termination, firewall, upstream auth 담당합니다 |
13
+ | Streaming | 기본 차단 | pass-through 사용할 보호가 적용되지 않는 위험을 감수합니다 |
14
+ | TokenVault | 암호화 저장, reveal 기본 차단, purge | reveal 승인 절차와 DSAR/retention 운영을 담당합니다 |
15
+ | Audit | 평문 제거, hash chain | append-only storage, backup, 보존 기간, 외부 서명을 담당합니다 |
16
+ | Key custody | local dev key, external crypto provider 계약 | KMS/HSM/Vault adapter 구현, rotation, access review 담당합니다 |
17
+ | Plugin | manifest validation; 서명 + 샌드박스된 `authProvider` 플러그인에 한해 동적 로딩 좁게 허용(worker-isolated 1.0 / process-isolated 1.1) | trust anchor/pin/revocation을 관리하고, `process-isolated`를 우선하며, plugin code review를 수행합니다 |
18
+ | MCP | JSON-RPC/method allowlist | MCP server auth, resource consent, env secret allowlist 담당합니다 |
19
+ | Privacy profile | KR/EU/US baseline action | 법률 검토, data residency, cross-border transfer 증빙을 담당합니다 |
21
20
 
22
21
  ## 2. 금지되는 기본 사용
23
22
 
24
- - `--allow-remote-bind`를 네트워크 통제 없이 사용
25
- - `.haechi/dev.keys.json`을 운영 데이터에 사용
26
- - `streaming.requestMode: "pass-through"`를 보호 적용으로 오해
27
- - `responseProtection.failureMode: "allow"`를 민감 데이터 경로에 사용
28
- - `token-reveal --allow-dev-reveal`를 운영 복원 절차로 사용
23
+ - `--allow-remote-bind`를 네트워크 통제 없이 사용하는 것
24
+ - `.haechi/dev.keys.json`을 운영 데이터에 사용하는 것
25
+ - `streaming.requestMode: "pass-through"`를 보호가 적용된 것으로 오해하는 것
26
+ - `responseProtection.failureMode: "allow"`를 민감 데이터 경로에 사용하는 것
27
+ - `token-reveal --allow-dev-reveal`를 운영 복원 절차로 사용하는 것
29
28
 
30
29
  ## 3. 운영 전환 체크리스트
31
30
 
32
- 1. 외부 crypto provider 또는 KMS/HSM/Vault adapter를 연결한다.
33
- 2. proxy 앞단에 인증, TLS, firewall, rate limit을 둔다.
34
- 3. responseProtection을 켜고 fail-closed를 유지한다.
35
- 4. streaming endpoint는 별도 stream-aware gateway가 준비되기 차단한다.
36
- 5. audit sink를 append-only 또는 외부 서명 저장소로 보낸다.
37
- 6. TokenVault reveal 승인/보존/삭제 절차를 문서화한다.
38
- 7. privacy profile은 법률 검토 결과로 보정한다.
31
+ 1. 외부 crypto provider 또는 KMS/HSM/Vault adapter를 연결하세요.
32
+ 2. proxy 앞단에 인증, TLS, firewall, rate limit을 두세요.
33
+ 3. responseProtection을 켜고 fail-closed를 유지하세요.
34
+ 4. streaming endpoint는 별도의 stream-aware gateway가 준비되기 전까지 차단하세요.
35
+ 5. audit sink를 append-only 저장소나 외부 서명 저장소로 보내세요.
36
+ 6. TokenVault reveal 승인, 보존, 삭제 절차를 문서화하세요.
37
+ 7. privacy profile은 법률 검토 결과로 보정하세요.
38
+ 8. 복제본이 2개 이상이면 §4의 공유 인프라(front-door rate limit, 복제본별 audit 경로, 공유 token vault)를 제공하세요.
39
+
40
+ ## 4. 수평 확장 / 다중 복제
41
+
42
+ Haechi의 상태 보유 통제는 설계상 단일 프로세스입니다. 로드밸런서 뒤에서 복제본을 2개 이상 실행하면, 운영자가 공유 인프라를 제공하지 않는 한 이들이 **무음으로 약화**됩니다.
43
+
44
+ - **Rate limit**은 프로세스별·인메모리이므로 전체 처리량이 복제본 수만큼 배가됩니다. identity별 한도를 공유 front door에서 강제하거나, `createRuntime(config, { rateLimiter })`를 통해 공유 저장소 기반 `rateLimiter`를 주입하세요(이 시임은 `allow(key, limit)` 계약을 만족합니다. [`configuration.md` → Rate limiter 주입](./configuration.ko.md#rate-limiter-주입) 참고). 기본 프로세스별 limiter는 window map도 bounding하므로 identity 기준 무한 메모리 증가가 없습니다.
45
+ - **Audit hash chain + anchor**는 단일 작성자입니다. 각 복제본에 **고유한** `audit.path`(및 anchor 경로)를 주세요. 하나의 audit 파일을 복제본 간에 공유하면 체인이 분기되어 검증 불가 상태가 됩니다.
46
+ - **TokenVault와 auth store**는 whole-file 로컬 저장소입니다 — 단일 호스트에서는 올바르지만 공유 다중 작성자 저장소는 아닙니다. 다중 복제 토큰화에는 공유 `tokenVault`를 주입하세요.
47
+ - 파일 락은 `O_EXCL` + atomic rename에 의존하며 NFS/공유 파일시스템에서는 보장되지 않습니다 — 이 저장소들은 로컬 디스크에 두세요.
@@ -1,8 +1,7 @@
1
1
  # Haechi Shared Responsibility
2
2
 
3
- - Status: Draft 0.1
3
+ - Status: Living document (tracks core 1.2.x)
4
4
  - Date: 2026-06-10
5
- - Target version: 0.3.2
6
5
 
7
6
  ## 1. Responsibility Matrix
8
7
 
@@ -15,7 +14,7 @@
15
14
  | TokenVault | Encrypted storage, reveal blocked by default, purge | Reveal approval workflow, DSAR/retention operations |
16
15
  | Audit | Plaintext removal, hash chain | Append-only storage, backup, retention period, external signing |
17
16
  | Key custody | Local dev key, external crypto provider contract | KMS/HSM/Vault adapter implementation, rotation, access review |
18
- | Plugin | Manifest validation, dynamic runtime blocked | Plugin code review, do not execute before sandbox is available |
17
+ | Plugin | Manifest validation; dynamic loading lifted narrowly for signed + sandboxed `authProvider` plugins (worker-isolated 1.0 / process-isolated 1.1) | Curate trust anchors/pins/revocation; prefer `process-isolated`; review plugin code |
19
18
  | MCP | JSON-RPC/method allowlist | MCP server auth, resource consent, env secret allowlist |
20
19
  | Privacy profile | KR/EU/US baseline actions | Legal review, data residency, cross-border transfer evidence |
21
20
 
@@ -36,3 +35,13 @@
36
35
  5. Send the audit sink to an append-only or externally signed storage backend.
37
36
  6. Document the TokenVault reveal approval, retention, and deletion procedures.
38
37
  7. Calibrate privacy profiles based on legal review findings.
38
+ 8. For more than one replica, supply the shared infrastructure in §4 (front-door rate limit, per-replica audit paths, shared token vault).
39
+
40
+ ## 4. Horizontal scale / multiple replicas
41
+
42
+ Haechi's stateful controls are single-process by design. Running 2+ replicas behind a load balancer **silently weakens** them unless the operator supplies shared infrastructure:
43
+
44
+ - **Rate limit** is per-process and in-memory — total throughput multiplies by the replica count. Enforce a per-identity limit at a shared front door, or inject a shared-store `rateLimiter` via `createRuntime(config, { rateLimiter })` (the seam satisfies the `allow(key, limit)` contract; see [`configuration.md` → Rate limiter injection](./configuration.md#rate-limiter-injection)). The default per-process limiter also bounds its window map (no unbounded memory growth keyed by identity).
45
+ - **Audit hash chain + anchor** are single-writer. Give each replica its **own** `audit.path` (and anchor path); never share one audit file across replicas, or the chain forks into an unverifiable state.
46
+ - **TokenVault and the auth store** are whole-file local stores — correct for one host, but not a shared multi-writer store. For multi-replica tokenization, inject a shared `tokenVault`.
47
+ - File locking relies on `O_EXCL` + atomic rename, which do not hold on NFS / shared filesystems — keep these stores on local disk.
@@ -1,12 +1,11 @@
1
1
  # Haechi Threat Model
2
2
 
3
- - 문서 상태: Draft 0.1
3
+ - 문서 상태: Living document(core 1.2.x 추적)
4
4
  - 작성일: 2026-06-10
5
- - 기준 버전: 1.0.0
6
5
 
7
6
  ## 1. 보호 대상
8
7
 
9
- Haechi가 보호하려는 주요 자산은 다음이다.
8
+ Haechi가 보호하려는 주요 자산은 다음과 같습니다.
10
9
 
11
10
  | 자산 | 예시 | 보호 목표 |
12
11
  |---|---|---|
@@ -24,7 +23,7 @@ Haechi가 보호하려는 주요 자산은 다음이다.
24
23
  | CLI local process | 개발자 로컬 신뢰 | dev key 경고, dry-run 기본값 |
25
24
  | HTTP proxy listener | 비신뢰 client 입력 | loopback bind 기본, remote bind 명시 플래그 |
26
25
  | Upstream model/tool server | 비신뢰 또는 부분 신뢰 | request/response protection, uninspectable response fail-closed |
27
- | Streaming response | 검사(bounded) 또는 차단 | `inspect` 모드는 bounded cross-frame 버퍼로 SSE/NDJSON을 stream-filter함; `block`(기본값)은 거부 |
26
+ | Streaming response | 검사(bounded) 또는 차단 | `inspect` 모드는 bounded cross-frame 버퍼로 SSE/NDJSON을 stream-filter합니다. `block`(기본값)은 거부합니다 |
28
27
  | MCP stdio peer | 부분 신뢰 | JSON-RPC 2.0 요구, method allowlist |
29
28
  | Local filesystem | 부분 신뢰 | local key/token vault 0600, audit hash chain |
30
29
  | External provider/plugin | 비신뢰 | provider method contract, plugin manifest-only gate |
@@ -34,8 +33,8 @@ Haechi가 보호하려는 주요 자산은 다음이다.
34
33
  | 위협 | 영향 | 현재 통제 |
35
34
  |---|---|---|
36
35
  | 인터넷 노출 proxy | 인증 없는 LLM gateway | non-loopback bind 기본 실패 |
37
- | streaming 우회 | SSE/NDJSON 평문 유출 | `inspect` 모드는 SSE/NDJSON을 stream-filter함; `block`(기본값)은 거부; `pass-through`는 명시적으로 감사된 opt-out |
38
- | Ollama 암묵 streaming 우회 | `stream` 생략 시 NDJSON 평문 유출 | `/api/chat`·`/api/generate`는 `stream: false` 명시 없으면 streaming으로 간주해 기본 차단 |
36
+ | streaming 우회 | SSE/NDJSON 평문 유출 | `inspect` 모드는 SSE/NDJSON을 stream-filter합니다. `block`(기본값)은 거부하고, `pass-through`는 명시적으로 감사된 opt-out입니다 |
37
+ | Ollama 암묵 streaming 우회 | `stream` 생략 시 NDJSON 평문 유출 | `/api/chat`·`/api/generate`는 `stream: false`를 명시하지 않으면 streaming으로 간주해 기본 차단합니다 |
39
38
  | 비JSON/압축/대용량 응답 | responseProtection 우회 | fail-closed response policy |
40
39
  | token reveal 남용 | tokenized PII 복원 | revealPolicy 기본 disabled, reveal/purge 결정 audit 기록 |
41
40
  | audit 변조 | 감사 증거 신뢰 저하 | SHA-256 hash chain |
@@ -47,6 +46,7 @@ Haechi가 보호하려는 주요 자산은 다음이다.
47
46
  | 행 걸린 upstream | proxy 연결 고갈 | `limits.upstreamTimeoutMs` 기본 120s, 초과 시 504 fail |
48
47
  | signing/encryption 키 혼용 | key separation 위반 | policy bundle 서명 키를 domain-separated 파생 키로 분리 |
49
48
  | JSON number/object key 은닉 | 카드번호 등 비문자열 leaf 미탐지 | number leaf와 object key도 detection/transform 대상 |
49
+ | 모든 정규식 규칙을 우회하는 유니코드 난독화 | card/RRN/phone/email/secret을 시각·의미상 동등한 비ASCII 유니코드 형태(전각 숫자 `4242…`, 전각 `@`, 수학·원문자 영숫자)로 보내 모든 탐지 규칙을 무력화 | **매칭 전 각 string leaf의 NFKC 정규화**(WS2d)입니다. 정규화가 무변환인 경우(leaf의 약 99%) 탐지는 이전과 바이트 단위로 동일합니다. 접힘이 **위치 안정적**인 경우(모든 코드포인트가 같은 UTF-16 길이로 접히고 코드포인트별 접힘이 전체 정규화를 그대로 재구성) 정규화 사본에서 탐지하고 원본의 정확한 구간을 redact/block하며, 기록되는 값은 접힌 형태가 아니라 원본 바이트입니다. 그 외 — 길이가 달라지거나(수학 숫자·합자) 총 길이는 같지만 내부 offset을 이동시키는 수축+확장 보상 — 의 경우 offset을 원본에 매핑할 수 없으므로 탐지가 **fail closed**되어 leaf 전체를 덮는 단일 탐지로 처리합니다(leaf 전체 redact/block — 우회 시도를 과도 redact하는 것이 안전한 실패입니다). `String.prototype.normalize` 빌트인을 사용하므로 새 의존성은 없습니다. **수용된 잔여:** base64/percent-encoded 페이로드는 여전히 디코딩 후 재검사하지 않습니다(§4 참조) |
50
50
  | 인증 없는 멀티 클라이언트 접근 | 로컬 프로세스가 upstream / token round-trip 경로를 무단 사용 | 선택적 bearer auth (`auth.provider: bearer`); 없거나 잘못된 경우 → 바디 읽기 전 401; identity별 rate limit 및 model allowlist |
51
51
  | Audit tail truncation | 꼬리 audit 레코드의 무음 삭제 | 추가 전용/별도 미디어의 `audit.anchor` head-hash anchoring으로 마지막 anchor까지의 절단 탐지 (0.7) |
52
52
  | Local dev key in production | 소프트웨어 키의 운영 custody 오용 | `assertCryptoProviderConformance`를 통한 외부 `cryptoProvider` 주입; reference KMS adapter (envelope 암호화) |
@@ -65,7 +65,7 @@ Haechi가 보호하려는 주요 자산은 다음이다.
65
65
  | 토큰 엔드포인트 POST(및 Vault `fetch`)를 통한 broker SSRF — cloud metadata (0.9) | discovery와 request 사이에 `169.254.169.254`로 DNS-rebind되는 `token_endpoint`(또는 운영자 제공 `VAULT_ADDR`)가 instance-metadata 자격증명을 유출 | 모든 egress(discovery GET, 공유 verifier 경유 JWKS GET, token-exchange POST, end-session redirect, `haechi-crypto-kms` Vault `fetch`)가 **request 직전**(post-DNS) `lookup` 후 `isBlockedAddress` 재검사를 `redirect: "error"`·bounded body·timeout과 함께 수행. 운영자 신뢰 엔드포인트에 한함 |
66
66
  | audit/로그로의 token/secret leak (broker) (0.9) | ID/access/refresh token, `client_secret`, `code`, `state`, `nonce`, raw `sub`가 audit 로그나 client 응답에 기록됨 | broker는 모든 audit 이벤트를 자체 allowlist로 projection해 `subjectHash`/`issuerHash`/`sessionIdHash`(keyed-HMAC) + `provider`/`reasonCode`/timestamp만 방출; core `FORBIDDEN_KEYS`를 broker token/claim key까지 확장; access token은 **폐기**(저장·사용 안 함). 실질적 잔여 없음 |
67
67
  | KMS backend egress (Vault HTTP, GCP/Azure SDK) (0.9) | `haechi-crypto-kms` Vault/GCP/Azure backend가 key material이나 provider/key-path 상세를 유출하거나 의도치 않은 엔드포인트에 도달 | optional-peer + injected-client 모델과 **faithful-mock conformance**(cross-key·corrupted-blob 거부, HMAC determinism/domain-separation); Vault `fetch`는 위 satellite-local SSRF 가드 수행; 모든 backend는 provider 오류를 generic fail-closed 오류로 매핑하고 provider/key-ARN 상세를 audit에 기록하지 않음. live-backend 검증은 CI 외부 |
68
- | 동적 로딩된 악의적/침해된 signed plugin (1.0) | signed `authProvider` plugin이 worker sandbox에 로딩된 뒤 실행 중 host를 악용 | `canonicalize({pluginId, kind, version, capabilities, coreVersionRange, entrySha256, notBefore, notAfter})`에 대한 Ed25519 서명, **trust-anchor-only** 키 해석(`signerKeyId`가 allowlist된 anchor가 아니면 verify 이전 거부; 알고리즘은 Ed25519로 고정), pin + `pluginId`별 version-floor + revocation denylist(`revokedSignerKeyIds`/`revokedEntrySha256`) + validity-window 집행, `assertAuthProviderConformance` 정합성 게이트, `node:worker_threads` memory/crash 격리 + per-call timeout-terminate, 전체 lifecycle audit(`plugin.load.*`/`authenticate.deny`/`worker.terminated`). 전체 게이트는 매 respawn마다 재실행. **수용된 잔여:** signed plugin 자신의 `fs`/`fetch`/`process.env`는 차단되지 않으며(`networkEgress: false`는 선언일 뿐 1.0에서 집행 통제 아님) 정당하게 받은 credential을 exfiltrate할 수 있음 — 오직 signing/vetting 신뢰 모델로만 통제됨. **1.1이 새 opt-in `process-isolated` 런타임에 대해 이 잔여를 닫는다**(다음 행, P1-SEC-027); `worker_threads`(1.0) 모드는 불변이며 이 수용된 잔여를 유지 |
68
+ | 동적 로딩된 악의적/침해된 signed plugin (1.0) | signed `authProvider` plugin이 worker sandbox에 로딩된 뒤 실행 중 host를 악용 | `canonicalize({pluginId, kind, version, capabilities, coreVersionRange, entrySha256, notBefore, notAfter})`에 대한 Ed25519 서명, **trust-anchor-only** 키 해석(`signerKeyId`가 allowlist된 anchor가 아니면 verify 이전 거부; 알고리즘은 Ed25519로 고정), pin + `pluginId`별 version-floor + revocation denylist(`revokedSignerKeyIds`/`revokedEntrySha256`) + validity-window 집행, `assertAuthProviderConformance` 정합성 게이트, `node:worker_threads` memory/crash 격리 + per-call timeout-terminate, 전체 lifecycle audit(`plugin.load.*`/`authenticate.deny`/`worker.terminated`). 전체 게이트는 매 respawn마다 재실행. **수용된 잔여:** signed plugin 자신의 `fs`/`fetch`/`process.env`는 차단되지 않으며(`networkEgress: false`는 선언일 뿐 1.0에서 집행 통제 아님) 정당하게 받은 credential을 exfiltrate할 수 있음 — 오직 signing/vetting 신뢰 모델로만 통제됨. **1.1이 새 opt-in `process-isolated` 런타임에 대해 이 잔여를 닫음**(다음 행, P1-SEC-027); `worker_threads`(1.0) 모드는 불변이며 이 수용된 잔여를 유지 |
69
69
  | plugin으로의 PII/secret leak (1.0) | request body·crypto 키·token vault·raw claim이 worker 경계를 넘어 유출 | host는 worker에 **credential slice만** 전달(`Authorization` 헤더 / bearer token — request body 절대 안 보냄, crypto 키 절대 안 보냄); wire는 MessagePort 위 평문 JSON 문자열; **null-prototype, own-key-allowlist claims sanitizer**가 `__proto__`/`constructor`/`prototype`을 제거하고 크기를 bound한 뒤 **host**가 `buildExternalIdentity`로 keyed-HMAC identity를 구성(HMAC 키는 worker에 들어가지 않음). **수용된 잔여:** auth plugin이 정당하게 검증하는 credential은 그 plugin에 보임(위 행 참조) |
70
70
  | 경계 간 object/proto smuggling (1.0) | 악의적 claims object가 host prototype을 오염시키거나 raw 값을 경계 너머로 밀반입 | JSON-string wire만 사용(structured-clone 없음, `SharedArrayBuffer`/transferables 없음 → shared-memory·object-graph 채널 없음) + `buildExternalIdentity` 이전 null-proto own-key-allowlist sanitizer. 실질적 잔여 없음 |
71
71
  | plugin entry의 swap / TOCTOU (1.0) | 서명 검사 후 실행 전에 검증된 entry 바이트가 swap됨(예: symlink 경로 재해석) | 서명이 `entrySha256`을 바인딩; loader는 entry를 **메모리로** 읽어 hash·verify하고 **메모리 내 검증된 소스에서** Worker를 spawn(`eval: true`)하며 검증 후 경로를 재해석하지 않고 symlink entry를 거부. 실질적 잔여 없음 |
@@ -77,9 +77,9 @@ Haechi가 보호하려는 주요 자산은 다음이다.
77
77
 
78
78
  ## 4. 명시적 제외
79
79
 
80
- Haechi는 다음을 보장하지 않는다.
80
+ Haechi는 다음을 보장하지 않습니다.
81
81
 
82
- - 코어 자체의 운영 KMS/HSM/Vault adapter 제공(`haechi-crypto-kms` satellite가 외부 `cryptoProvider` 계약을 통해 AWS/GCP/Azure/Vault adapter를 제공한다)
82
+ - 코어 자체의 운영 KMS/HSM/Vault adapter 제공(`haechi-crypto-kms` satellite가 외부 `cryptoProvider` 계약을 통해 AWS/GCP/Azure/Vault adapter를 제공합니다)
83
83
  - internet-facing gateway 인증/인가
84
84
  - `streaming.maxMatchBytes`보다 긴 cross-frame 매칭(스트림 프레임에 걸쳐 분할될 수 있음)
85
85
  - `block`이 발동되기 전에 이미 방출된 스트림 바이트의 회수
@@ -87,9 +87,9 @@ Haechi는 다음을 보장하지 않는다.
87
87
  - 법적 컴플라이언스 인증
88
88
  - 모델 hallucination, prompt injection 완전 방어
89
89
  - 외부 MCP server의 OAuth/resource binding 검증
90
- - base64/URL-encoded 값, 유니코드 난독화 값의 디코딩 후 검사
90
+ - base64/percent-encoded 값의 **디코딩 후** 검사 — Haechi는 NFKC 정규화 텍스트에서 매칭하지만(§3의 유니코드 난독화 참조) base64/URL 디코딩 후 재검사는 하지 **않습니다**. 전송 전 base64·percent로 인코딩된 값은 검사되지 않습니다. (WS2d는 디코딩-후-재검사 패스를 보류했습니다. 상시 디코딩은 오탐이 많고, recall-safe한 opt-in을 범위 내에서 precision-neutral하게 만들 수 없어 문서화된 제외로 남깁니다.)
91
91
  - URL query string 내 민감값 검사 (JSON body만 검사)
92
- - 마지막 anchor 이후의 audit tail truncation — `audit.anchor`(0.7)는 anchor가 추가 전용/별도 미디어에 있을 때 마지막 anchor까지의 레코드 삭제를 탐지한다; 마지막 anchor 이후 기록된 레코드와 동일 파일시스템 anchor는 대상에서 제외된다
92
+ - 마지막 anchor 이후의 audit tail truncation — `audit.anchor`(0.7)는 anchor가 추가 전용/별도 미디어에 있을 때 마지막 anchor까지의 레코드 삭제를 탐지합니다. 마지막 anchor 이후 기록된 레코드와 동일 파일시스템 anchor는 대상에서 제외됩니다
93
93
  - JSON-RPC batch 메시지 처리 (MCP stdio filter는 batch를 fail-closed로 거부)
94
94
  - `haechi-auth-oidc`의 multi-origin / CDN-fronted IdP(issuer host ≠ `token_endpoint`/`jwks_uri` host) — single-origin만 지원, `haechi-auth-jwt`와 동일 제약 (0.9)
95
95
  - refresh-token rotation / silent renewal / 장수명 broker 세션 — 0.9 세션은 absolute-TTL + idle-timeout만; `offline_access`는 제거되고 access token은 폐기 (0.9)
@@ -108,4 +108,4 @@ Haechi는 다음을 보장하지 않는다.
108
108
 
109
109
  ## 5. 남은 운영 전제
110
110
 
111
- 운영 사용자는 Haechi 외부에서 네트워크 접근 제어, upstream 인증, secret injection, key custody, 로그 보존, DSAR/삭제 요청 처리, 법적 transfer 근거를 책임져야 한다.
111
+ 운영 사용자는 Haechi 외부에서 네트워크 접근 제어, upstream 인증, secret injection, key custody, 로그 보존, DSAR/삭제 요청 처리, 법적 transfer 근거를 책임져야 합니다.
@@ -1,8 +1,7 @@
1
1
  # Haechi Threat Model
2
2
 
3
- - Status: Draft 0.1
3
+ - Status: Living document (tracks core 1.2.x)
4
4
  - Date: 2026-06-10
5
- - Target version: 1.0.0
6
5
 
7
6
  ## 1. Assets Under Protection
8
7
 
@@ -47,6 +46,7 @@ The primary assets Haechi protects are:
47
46
  | Hung upstream | Proxy connection exhaustion | `limits.upstreamTimeoutMs` default 120 s; 504 fail on timeout |
48
47
  | Signing/encryption key conflation | Key separation violation | Policy bundle signing key isolated as a domain-separated derived key |
49
48
  | JSON number / object key concealment | Undetected non-string leaves such as card numbers | Number leaves and object keys included in detection/transform scope |
49
+ | Unicode-obfuscation evasion of every regex rule | A card/RRN/phone/email/secret sent in a visually/semantically equivalent non-ASCII Unicode form (full-width digits `4242…`, full-width `@`, mathematical/enclosed alphanumerics) defeats every detection rule | **NFKC normalization of each string leaf before matching** (WS2d). When the normalization is a no-op (~99% of leaves) detection is byte-identical to before. When the fold is **position-stable** (every codepoint folds to the same UTF-16 length and the per-codepoint folds reconstruct the whole normalization), detection runs on the normalized copy and the exact original span is redacted/blocked (the recorded value is the original bytes, never the fold). Otherwise — a length change (mathematical digits/ligatures) **or** a compensating contraction+expansion that keeps the total length equal while shifting interior offsets — offsets cannot map back, so detection **fails closed** to a single whole-leaf detection (the entire leaf is redacted/blocked — over-redacting an evasion attempt is the safe failure). Uses the `String.prototype.normalize` builtin (no new dependency). **Accepted residual:** base64/percent-encoded payloads are still not decoded-and-rescanned (see §4) |
50
50
  | Unauthenticated multi-client access | Any local process uses the upstream / token round-trip | Optional bearer auth (`auth.provider: bearer`); missing/invalid → 401 before body read; per-identity rate limit and model allowlist |
51
51
  | Audit tail truncation | Silent deletion of trailing audit records | `audit.anchor` head-hash anchoring on append-only/separate media detects truncation back to the last anchor (0.7) |
52
52
  | Local dev key in production | Software key misused as production custody | External `cryptoProvider` injection with `assertCryptoProviderConformance`; reference KMS adapter (envelope encryption) |
@@ -87,7 +87,7 @@ Haechi does not guarantee:
87
87
  - Legal compliance certification
88
88
  - Complete defense against model hallucination or prompt injection
89
89
  - OAuth/resource binding validation for external MCP servers
90
- - Inspection of base64/URL-encoded values or unicode-obfuscated values after decoding
90
+ - Inspection of base64/percent-encoded values **after decoding** — Haechi matches on the NFKC-normalized text (see the Unicode-evasion row in §3) but does **not** base64/URL-decode-and-rescan. A value that is base64- or percent-encoded before sending is not inspected. (WS2d deferred a decode-and-rescan pass: an always-on decode is false-positive-prone, and a recall-safe opt-in could not be made precision-neutral within scope; it remains a documented exclusion.)
91
91
  - Detection of sensitive values in URL query strings (JSON body only)
92
92
  - Audit tail truncation beyond the last anchor — `audit.anchor` (0.7) detects deletion of records back to the last anchor when the anchor is on append-only/separate media; records written after the last anchor, and same-filesystem anchors, are not covered
93
93
  - JSON-RPC batch message processing (the MCP stdio filter rejects batches fail-closed)
@@ -1,4 +1,5 @@
1
1
  {
2
+ "configVersion": 1,
2
3
  "mode": "dry-run",
3
4
  "target": {
4
5
  "type": "llm-http",
@@ -7,7 +8,9 @@
7
8
  },
8
9
  "proxy": {
9
10
  "host": "127.0.0.1",
10
- "port": 11016
11
+ "port": 11016,
12
+ "tls": null,
13
+ "trustForwardedProto": false
11
14
  },
12
15
  "responseProtection": {
13
16
  "enabled": false,
@@ -25,7 +28,12 @@
25
28
  },
26
29
  "limits": {
27
30
  "maxRequestBytes": 1048576,
28
- "upstreamTimeoutMs": 120000
31
+ "maxNestingDepth": 256,
32
+ "upstreamTimeoutMs": 120000,
33
+ "maxInFlight": 0,
34
+ "shutdownGraceMs": 10000,
35
+ "requestTimeoutMs": null,
36
+ "headersTimeoutMs": null
29
37
  },
30
38
  "policy": {
31
39
  "mode": "dry-run",
@@ -40,7 +48,9 @@
40
48
  }
41
49
  },
42
50
  "filters": {
43
- "customRules": []
51
+ "customRules": [],
52
+ "minConfidence": 0,
53
+ "allowlist": []
44
54
  },
45
55
  "keys": {
46
56
  "provider": "local",
@@ -67,6 +77,12 @@
67
77
  "privacy": {
68
78
  "profile": null
69
79
  },
80
+ "logging": {
81
+ "format": "text"
82
+ },
83
+ "metrics": {
84
+ "enabled": true
85
+ },
70
86
  "auth": {
71
87
  "provider": "none",
72
88
  "store": ".haechi/auth.json",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "haechi",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "Self-hosted AI context enforcement across LLM, MCP, vLLM, Ollama, and agent traffic — a stable, zero-dependency security gateway.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -51,7 +51,8 @@
51
51
  "./token-vault": "./packages/token-vault/index.mjs",
52
52
  "./stream-filter": "./packages/stream-filter/index.mjs",
53
53
  "./auth": "./packages/auth/index.mjs",
54
- "./ssrf": "./packages/ssrf/index.mjs"
54
+ "./ssrf": "./packages/ssrf/index.mjs",
55
+ "./metrics": "./packages/metrics/index.mjs"
55
56
  },
56
57
  "files": [
57
58
  "README.md",
@@ -68,11 +69,14 @@
68
69
  "check:types": "tsc -p jsconfig.json --noEmit",
69
70
  "pack:dry": "npm pack --dry-run",
70
71
  "scan:stale-names": "node scripts/stale-name-scan.mjs",
72
+ "scan:doc-freshness": "node scripts/check-doc-freshness.mjs",
71
73
  "check:packaging": "node scripts/check-core-packaging.mjs",
72
74
  "check:satellite-packaging": "node scripts/check-satellite-packaging.mjs",
73
75
  "sbom": "node scripts/generate-sbom.mjs",
74
76
  "checksums": "node scripts/release-checksums.mjs",
75
77
  "bench:payload": "node scripts/bench-payload.mjs",
78
+ "bench:detection": "node scripts/bench-detection.mjs",
79
+ "scan:detection": "node scripts/bench-detection.mjs --gate",
76
80
  "check:peer-ranges": "node scripts/check-satellite-peer-ranges.mjs",
77
81
  "release:preflight": "node scripts/release-preflight.mjs && node scripts/check-satellite-peer-ranges.mjs",
78
82
  "release:preflight:npm": "node scripts/release-preflight.mjs --require-npm-auth && node scripts/check-satellite-peer-ranges.mjs",
@@ -1,5 +1,5 @@
1
- import { createReadStream } from "node:fs";
2
- import { appendFile, mkdir, open, stat, unlink } from "node:fs/promises";
1
+ import { createReadStream, constants as fsConstants } from "node:fs";
2
+ import { access, appendFile, mkdir, open, stat, unlink } from "node:fs/promises";
3
3
  import { createHash } from "node:crypto";
4
4
  import { dirname } from "node:path";
5
5
  import { createInterface } from "node:readline";
@@ -91,6 +91,30 @@ export function createJsonlAuditSink({ path, anchor = null }) {
91
91
  });
92
92
  writeQueue = write.catch(() => {});
93
93
  await write;
94
+ },
95
+
96
+ // WS4-A readiness probe: a CHEAP writability check used by /__haechi/ready.
97
+ // A security gateway that cannot append to its audit log is NOT ready
98
+ // (fail-closed), so this confirms the audit directory exists and is writable
99
+ // WITHOUT writing an event (no audit-chain side effect). It returns the bare
100
+ // boolean and an enum reason — never a path value or any payload/PII.
101
+ async ready() {
102
+ try {
103
+ const dir = dirname(path);
104
+ await mkdir(dir, { recursive: true });
105
+ await access(dir, fsConstants.W_OK);
106
+ // If the audit file already exists, confirm it is writable too.
107
+ try {
108
+ await access(path, fsConstants.W_OK);
109
+ } catch (error) {
110
+ if (error.code !== "ENOENT") {
111
+ return { ok: false, reason: "audit_file_not_writable" };
112
+ }
113
+ }
114
+ return { ok: true };
115
+ } catch {
116
+ return { ok: false, reason: "audit_dir_not_writable" };
117
+ }
94
118
  }
95
119
  };
96
120
  }