haechi 0.5.0 → 0.7.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 +42 -0
- package/README.md +42 -0
- package/docs/README.md +2 -0
- package/docs/current/api-stability.ko.md +6 -1
- package/docs/current/api-stability.md +6 -1
- package/docs/current/configuration.ko.md +25 -2
- package/docs/current/configuration.md +25 -2
- package/docs/current/release-0.6-implementation-scope.ko.md +151 -0
- package/docs/current/release-0.6-implementation-scope.md +151 -0
- package/docs/current/release-0.7-implementation-scope.ko.md +91 -0
- package/docs/current/release-0.7-implementation-scope.md +92 -0
- package/docs/current/release-process.ko.md +26 -3
- package/docs/current/release-process.md +26 -3
- package/docs/current/risk-register-release-gate.ko.md +3 -3
- package/docs/current/risk-register-release-gate.md +4 -3
- package/docs/current/threat-model.ko.md +7 -2
- package/docs/current/threat-model.md +7 -2
- package/examples/crypto-kms-reference/README.md +47 -0
- package/examples/crypto-kms-reference/index.mjs +133 -0
- package/examples/crypto-kms-reference/package.json +19 -0
- package/haechi.config.example.json +16 -1
- package/package.json +5 -2
- package/packages/audit/index.mjs +99 -6
- package/packages/auth/index.mjs +170 -0
- package/packages/cli/bin/haechi.mjs +131 -14
- package/packages/cli/runtime.mjs +136 -8
- package/packages/core/index.mjs +18 -7
- package/packages/crypto/index.mjs +107 -0
- package/packages/policy/index.mjs +82 -0
- package/packages/proxy/index.mjs +134 -8
- package/scripts/release-checksums.mjs +100 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Haechi 0.7 Implementation Scope
|
|
2
|
+
|
|
3
|
+
- 문서 상태: Final
|
|
4
|
+
- 작성일: 2026-06-10
|
|
5
|
+
- 기준 버전: 0.7.0 (0.6.0 이후)
|
|
6
|
+
- 성격: ops hardening
|
|
7
|
+
- 구현 완료: 2026-06-10 — PR #22 (audit anchoring), #23 (cryptoProvider 계약 + reference KMS), #24 (signed release artifacts)
|
|
8
|
+
|
|
9
|
+
## 1. 릴리스 목표
|
|
10
|
+
|
|
11
|
+
1.0("stable", developer-preview 레이블 제거)이 차단하는 운영 스토리를 강화한다: 단일 로컬 파일을 넘어선 audit 무결성, 외부 key custody, 검증 가능한 릴리스 아티팩트. 두 번의 1.0 차단 릴리스 중 첫 번째다.
|
|
12
|
+
|
|
13
|
+
**범위 결정 (2026-06-10):** 0.7은 **ops hardening** — audit 무결성, key custody 계약, 서명된 아티팩트에 집중한다. 이전에 여기에 묶였던 **생태계** 항목들(npm org `@haechi/*`, `@haechi/crypto-kms` / `@haechi/auth-oidc` 게시, `@haechi/dashboard`, npm workspaces)은 **0.8**로 이월하며, 0.8에서 중복된 0.7 로드맵 행도 제거한다.
|
|
14
|
+
|
|
15
|
+
Core의 **zero runtime dependency** 기조는 협상 불가: 0.7의 모든 것은 `node:` 빌트인만으로 제공된다. 무거운 어댑터(AWS KMS, Vault)는 satellite/example이며, 절대 core에 포함되지 않는다.
|
|
16
|
+
|
|
17
|
+
## 2. 범위
|
|
18
|
+
|
|
19
|
+
### 2.1 Audit tail-truncation 방어: head-hash anchoring (내장, zero-dep)
|
|
20
|
+
|
|
21
|
+
audit hash chain은 변조와 재정렬은 탐지하지만, 마지막 N개 레코드의 **삭제**는 탐지하지 못한다 — 단축된 chain도 여전히 검증을 통과하기 때문이다. 0.7은 주기적 anchoring으로 일반적인 경우를 해결한다.
|
|
22
|
+
|
|
23
|
+
- 추가 이후 JSONL sink는 현재 chain head를 별도의 **append-only anchor 스트림**에 기록한다: JSON 한 줄 `{ sequence, eventHash, timestamp }`.
|
|
24
|
+
- Config `audit.anchor`:
|
|
25
|
+
- `mode`: `none` (기본값 — 현재 동작) | `file` | `stdout`.
|
|
26
|
+
- `path`: `mode: file`일 때의 anchor 파일 (다른 매체 / append-only 플래그가 설정된 경로 권장).
|
|
27
|
+
- `everyRecords`: anchor 주기 (기본값 `1` — 레코드마다 anchor; 배치 처리 시 값 증가). Anchor 라인은 매우 작다.
|
|
28
|
+
- `verifyAuditChain(path, { anchorPath })`은 교차 검증한다: 최신 anchor의 `sequence`가 chain 길이를 초과해서는 안 되며, 앵커된 `sequence`의 chain 레코드는 앵커된 `eventHash`로 해시되어야 한다. 최신 anchor보다 짧은 chain → **truncation 탐지** (마지막 anchor 이후의 레코드가 제거된 것).
|
|
29
|
+
- `haechi audit-verify --anchor <path>`로 확인하며; `haechi status`는 anchor 모드 + 마지막으로 앵커된 sequence를 보고한다.
|
|
30
|
+
- **보장 범위의 한계:** truncation은 **마지막 anchor** 이전까지만 탐지된다; 마지막 anchor 이후, truncation 이전에 기록된 레코드는 여전히 조용히 손실될 수 있다. `everyRecords: 1`이면 그 범위는 레코드 하나다. 문서화되어 있다.
|
|
31
|
+
|
|
32
|
+
### 2.2 외부 append-only audit sink 계약
|
|
33
|
+
|
|
34
|
+
- 주입된 `auditSink` 계약을 공식화한다 (이미 `createRuntime(config, { auditSink })`를 통해 지원됨): `record(event)`는 append-only이며 순서를 보존한다; 외부 sink는 hash chain을 직접 구현하거나 내장 sink를 래핑한다. 기능 플래그(`writesAudit`, `integrity`, `appendOnly`)가 문서화된다.
|
|
35
|
+
- HTTP/syslog/object-lock 전달 레퍼런스 sink는 **0.8 satellite/example**이다; 0.7은 계약 + 내장 anchoring을 zero-dep 해답으로 제공한다.
|
|
36
|
+
|
|
37
|
+
### 2.3 cryptoProvider 계약 강화 + 레퍼런스 KMS 어댑터
|
|
38
|
+
|
|
39
|
+
- `keys.provider: external`에 대한 `cryptoProvider` 계약을 강화하고 문서화한다: 외부 provider는 `encrypt`, `decrypt`, **그리고 `hmac`** (토큰/identity를 위해 0.4에서 추가됨)을 구현해야 하며, envelope 형태(`{ v, alg, kid, iv, ct, tag, aadHash }`)를 보존하고, canonical AAD를 바인딩하며, `kid`로 키를 선택해야 한다.
|
|
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 취득 후) 게시되는 **`@haechi/crypto-kms`** satellite의 소스가 된다.
|
|
42
|
+
|
|
43
|
+
### 2.4 서명된 릴리스 아티팩트
|
|
44
|
+
|
|
45
|
+
- npm provenance (SLSA attestation)는 신뢰할 수 있는 퍼블리싱을 통해 이미 제공된다 (0.4부터). 0.7은 **GitHub 릴리스 에셋 무결성**을 추가한다: 릴리스 워크플로가 `npm pack`을 실행하고, `SHA256SUMS`를 생성하며, 타볼 + 체크섬 (그리고 가능한 경우 sigstore/cosign 서명)을 각 GitHub 릴리스에 첨부한다.
|
|
46
|
+
- 사용자가 설치 전 다운로드한 타볼을 검증할 수 있게 하고, 릴리스 에셋에 레지스트리 외의 변조 방지 매니페스트를 제공한다.
|
|
47
|
+
|
|
48
|
+
## 3. 명시적 비범위 (0.8로 이월)
|
|
49
|
+
|
|
50
|
+
- npm org `@haechi/*` 생성; `@haechi/crypto-kms`, `@haechi/auth-oidc`, `@haechi/auth-jwt` 게시.
|
|
51
|
+
- `@haechi/dashboard` (읽기 전용 audit 뷰어) 및 npm workspaces 전환.
|
|
52
|
+
- 게시된 패키지로서의 실제 AWS KMS / HashiCorp Vault SDK 연동 (0.7은 계약 + 레퍼런스 example만 제공).
|
|
53
|
+
- 분산/공유 audit 또는 rate 상태.
|
|
54
|
+
|
|
55
|
+
## 4. Config 스키마 요약
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
"audit": {
|
|
59
|
+
"sink": "jsonl",
|
|
60
|
+
"path": ".haechi/audit.jsonl",
|
|
61
|
+
"anchor": { "mode": "none", "path": ".haechi/audit.anchor.jsonl", "everyRecords": 1 }
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
Fail-closed 검증: 알 수 없는 `anchor.mode`; `path` 없이 `mode: file`; 비양수 `everyRecords`.
|
|
65
|
+
|
|
66
|
+
## 5. 1.0 졸업 기준 진행
|
|
67
|
+
|
|
68
|
+
0.7은 다섯 개의 1.0("developer-preview 레이블 제거") 차단 조건 중 세 개를 진전시킨다:
|
|
69
|
+
|
|
70
|
+
| 1.0 차단 조건 | 0.7 기여 |
|
|
71
|
+
|---|---|
|
|
72
|
+
| 운영 key custody | cryptoProvider 계약 강화 + conformance 테스트 + 레퍼런스 어댑터 (게시 패키지는 0.8) |
|
|
73
|
+
| 외부 / tamper-evident audit | 내장 anchoring으로 tail-truncation 해결; 외부 sink 계약 문서화 |
|
|
74
|
+
| 검증 가능한 릴리스 아티팩트 | 서명/체크섬된 GitHub 릴리스 에셋 |
|
|
75
|
+
| API stability freeze | (1.0) |
|
|
76
|
+
| Plugin sandbox + 실환경 검증 | (1.0) |
|
|
77
|
+
|
|
78
|
+
## 6. 테스트 기준 (구현 시)
|
|
79
|
+
|
|
80
|
+
- Anchoring: `everyRecords`에 따라 anchor 라인이 기록됨; anchor가 포함된 `verifyAuditChain`이 truncation(최신 anchor보다 짧은 chain)을 탐지하고 온전한 chain은 통과시킴; `mode: none`이면 0.6 동작과 byte 단위로 동일.
|
|
81
|
+
- `audit-verify --anchor` 종료 코드 + 출력; `status`가 anchor 모드/마지막 sequence를 보고.
|
|
82
|
+
- cryptoProvider conformance 헬퍼가 로컬 provider를 통과시키고, `hmac` 누락 / AAD 불일치 provider를 실패시킴.
|
|
83
|
+
- `audit.anchor` 블록에 대한 Config 검증.
|
|
84
|
+
- 릴리스 워크플로가 팩된 타볼과 일치하는 `SHA256SUMS`를 생성함 (CI 검증 가능).
|
|
85
|
+
|
|
86
|
+
## 7. 권장 PR 분할 (스택)
|
|
87
|
+
|
|
88
|
+
1. Audit anchoring (sink가 anchor 기록) + `verifyAuditChain` anchor 교차 검증 + config + `audit-verify --anchor` / `status`.
|
|
89
|
+
2. cryptoProvider 계약 문서 + `assertCryptoProviderConformance` + `examples/crypto-kms-reference/`.
|
|
90
|
+
3. 서명된 릴리스 아티팩트 (릴리스 워크플로 + 검증 문서).
|
|
91
|
+
4. 0.7.0 릴리스 컷 (버전, 문서 EN/KO, threat-model/risk-register/api-stability, wiki).
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Haechi 0.7 Implementation Scope
|
|
2
|
+
|
|
3
|
+
- Status: Final
|
|
4
|
+
- Date: 2026-06-10
|
|
5
|
+
- Target version: 0.7.0 (after 0.6.0)
|
|
6
|
+
- Type: ops hardening
|
|
7
|
+
- Shipped: 2026-06-10 — PRs #22 (audit anchoring), #23 (cryptoProvider contract + reference KMS), #24 (signed release artifacts)
|
|
8
|
+
|
|
9
|
+
## 1. Release Goal
|
|
10
|
+
|
|
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
|
+
|
|
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 (npm org `@haechi/*`, 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
|
+
|
|
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
|
+
|
|
17
|
+
## 2. Scope
|
|
18
|
+
|
|
19
|
+
### 2.1 Audit tail-truncation defense: head-hash anchoring (built-in, zero-dep)
|
|
20
|
+
|
|
21
|
+
The audit hash chain detects tampering and reordering but **not** deletion of the last N records — the shortened chain still verifies. 0.7 closes this for the common case with periodic anchoring.
|
|
22
|
+
|
|
23
|
+
- After appending, the JSONL sink writes the current chain head to a separate **append-only anchor stream**: one JSON line `{ sequence, eventHash, timestamp }`.
|
|
24
|
+
- Config `audit.anchor`:
|
|
25
|
+
- `mode`: `none` (default — current behavior) | `file` | `stdout`.
|
|
26
|
+
- `path`: anchor file when `mode: file` (created `0600`). `stdout` writes anchor lines to stdout for capture by a long-running command's supervisor — not for JSON-emitting commands, whose output it would interleave.
|
|
27
|
+
- `everyRecords`: anchor cadence (default `1` — anchor every record; raise to batch). Anchor lines are tiny.
|
|
28
|
+
- `verifyAuditChain(path, { anchorPath })` cross-checks: the latest anchor's `sequence` must not exceed the chain length, and the chain record at the anchored `sequence` must hash to the anchored `eventHash`. A chain shorter than the latest anchor → **truncation detected** (records after the last anchor were removed). A partial trailing anchor line (from a crash) is tolerated.
|
|
29
|
+
- `haechi audit-verify --anchor <path>` surfaces this; `haechi status` reports anchor mode + last anchored sequence.
|
|
30
|
+
- **Threat-model boundary (required, not optional):** the anchor adds tamper-evidence **only when it lives on append-only or physically separate media** (append-only-flagged FS, S3/GCS object-lock, syslog, a different host). On the **same writable filesystem** an attacker who truncates `audit.jsonl` can truncate `audit.anchor.jsonl` to match and verification passes — so file-mode on the same disk is a convenience, not a guarantee. The CLI (`status`, `audit-verify`, `config`) states this explicitly.
|
|
31
|
+
- **Bounded guarantee:** even on proper media, truncation is detected only back to the **last anchor**; records written after the last anchor and before truncation can still be lost silently. With `everyRecords: 1` that window is one record. Documented.
|
|
32
|
+
|
|
33
|
+
### 2.2 External append-only audit sink contract
|
|
34
|
+
|
|
35
|
+
- Formalize the injected `auditSink` contract (already supported via `createRuntime(config, { auditSink })`): `record(event)` is append-only and order-preserving; an external sink either implements the hash chain itself or wraps the built-in sink. Capability flags (`writesAudit`, `integrity`, `appendOnly`) are documented.
|
|
36
|
+
- A reference HTTP/syslog/object-lock forwarding sink is a **0.8 satellite/example**; 0.7 ships the contract + the built-in anchoring as the zero-dep answer.
|
|
37
|
+
|
|
38
|
+
### 2.3 cryptoProvider contract hardening + reference KMS adapter
|
|
39
|
+
|
|
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
|
+
- 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 **`@haechi/crypto-kms`** satellite in 0.8 (gated on the npm org).
|
|
43
|
+
|
|
44
|
+
### 2.4 Signed release artifacts
|
|
45
|
+
|
|
46
|
+
- npm provenance (SLSA attestation) already ships via trusted publishing (since 0.4). 0.7 adds **GitHub release asset integrity**: the release workflow runs `npm pack`, emits `SHA256SUMS`, and attaches the tarball + checksums (and, where available, a sigstore/cosign signature) to each GitHub release.
|
|
47
|
+
- Lets users verify a downloaded tarball before install and gives the release assets a tamper-evident manifest beyond the registry.
|
|
48
|
+
|
|
49
|
+
## 3. Explicit non-scope (deferred to 0.8)
|
|
50
|
+
|
|
51
|
+
- Create npm org `@haechi/*`; publish `@haechi/crypto-kms`, `@haechi/auth-oidc`, `@haechi/auth-jwt`.
|
|
52
|
+
- `@haechi/dashboard` (read-only audit viewer) and npm workspaces conversion.
|
|
53
|
+
- Real AWS KMS / HashiCorp Vault SDK integration as a published package (0.7 ships the contract + reference example only).
|
|
54
|
+
- Distributed/shared audit or rate state.
|
|
55
|
+
|
|
56
|
+
## 4. Config schema summary
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
"audit": {
|
|
60
|
+
"sink": "jsonl",
|
|
61
|
+
"path": ".haechi/audit.jsonl",
|
|
62
|
+
"anchor": { "mode": "none", "path": ".haechi/audit.anchor.jsonl", "everyRecords": 1 }
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
Fail-closed validation: unknown `anchor.mode`; `mode: file` without a `path`; non-positive `everyRecords`.
|
|
66
|
+
|
|
67
|
+
## 5. 1.0 exit-criteria progress
|
|
68
|
+
|
|
69
|
+
0.7 advances three of the five 1.0 ("remove developer-preview label") blockers:
|
|
70
|
+
|
|
71
|
+
| 1.0 blocker | 0.7 contribution |
|
|
72
|
+
|---|---|
|
|
73
|
+
| Operational key custody | cryptoProvider contract hardened + conformance test + reference adapter (published package in 0.8) |
|
|
74
|
+
| External / tamper-evident audit | Built-in anchoring closes tail-truncation; external sink contract documented |
|
|
75
|
+
| Verifiable release artifacts | Signed/checksummed GitHub release assets |
|
|
76
|
+
| API stability freeze | (1.0) |
|
|
77
|
+
| Plugin sandbox + real-environment validation | (1.0) |
|
|
78
|
+
|
|
79
|
+
## 6. Test criteria (for implementation)
|
|
80
|
+
|
|
81
|
+
- Anchoring: anchor lines written per `everyRecords`; `verifyAuditChain` with an anchor detects truncation (chain shorter than last anchor) and passes an intact chain; `mode: none` keeps 0.6 behavior byte-for-byte.
|
|
82
|
+
- `audit-verify --anchor` exit code + output; `status` reports anchor mode/last sequence.
|
|
83
|
+
- cryptoProvider conformance helper passes the local provider and fails a provider missing `hmac` / mismatching AAD.
|
|
84
|
+
- Config validation for the `audit.anchor` block.
|
|
85
|
+
- Release workflow produces `SHA256SUMS` matching the packed tarball (CI-verifiable).
|
|
86
|
+
|
|
87
|
+
## 7. Suggested PR breakdown (stacked)
|
|
88
|
+
|
|
89
|
+
1. Audit anchoring (sink writes anchors) + `verifyAuditChain` anchor cross-check + config + `audit-verify --anchor` / `status`.
|
|
90
|
+
2. cryptoProvider contract doc + `assertCryptoProviderConformance` + `examples/crypto-kms-reference/`.
|
|
91
|
+
3. Signed release artifacts (release workflow + verification doc).
|
|
92
|
+
4. 0.7.0 release cut (version, docs EN/KO, threat-model/risk-register/api-stability, wiki).
|
|
@@ -38,14 +38,37 @@ provenance 없이 수행한 publish는 release note에 갭을 명시적으로
|
|
|
38
38
|
- https://docs.npmjs.com/trusted-publishers/
|
|
39
39
|
- https://docs.github.com/actions/publishing-packages/publishing-nodejs-packages
|
|
40
40
|
|
|
41
|
-
## 3.
|
|
41
|
+
## 3. 서명된 릴리스 아티팩트
|
|
42
|
+
|
|
43
|
+
**암호학적** 신뢰 앵커는 **npm provenance 증명**(레지스트리 아티팩트)과 **sigstore 증명**(release tarball)이며, 둘 다 GitHub OIDC로 아티팩트를 이 repo의 release workflow 신원에 묶는다. `SHA256SUMS`는 오프라인 체크섬(`sha256sum -c`)을 위한 **도구 호환 편의 수단**이고, 같은 workflow가 생성·업로드하므로 그 자체로는 신뢰 앵커가 아니다. provenance에 더해, publish workflow는 다운로드한 tarball을 설치 전에 검증할 수 있도록 다음 자산을 첨부한다.
|
|
44
|
+
|
|
45
|
+
- `npm pack` 후 `node scripts/release-checksums.mjs <tarball>`로 `SHA256SUMS` 매니페스트(표준 `<sha256-hex> <name>` 형식)를 생성한다.
|
|
46
|
+
- `actions/attest-build-provenance`로 tarball의 **keyless sigstore 증명**(GitHub OIDC, 서명 키 없음)을 만든다.
|
|
47
|
+
- tarball + `SHA256SUMS`를 GitHub release에 업로드한다.
|
|
48
|
+
|
|
49
|
+
다운로드한 릴리스 검증:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# 체크섬 (크로스플랫폼: sha256sum -c, 또는 내장 스크립트)
|
|
53
|
+
node scripts/release-checksums.mjs --check SHA256SUMS
|
|
54
|
+
sha256sum -c SHA256SUMS # GNU
|
|
55
|
+
shasum -a 256 -c SHA256SUMS # macOS
|
|
56
|
+
|
|
57
|
+
# sigstore 증명 (이 repo의 release workflow가 빌드한 tarball)
|
|
58
|
+
gh attestation verify haechi-<version>.tgz --repo raeseoklee/haechi
|
|
59
|
+
|
|
60
|
+
# npm provenance (레지스트리 아티팩트)
|
|
61
|
+
npm audit signatures
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## 4. GitHub Actions
|
|
42
65
|
|
|
43
66
|
| Workflow | 목적 |
|
|
44
67
|
|---|---|
|
|
45
68
|
| `.github/workflows/ci.yml` | test, release preflight, SBOM artifact |
|
|
46
|
-
| `.github/workflows/npm-publish.yml` | GitHub release published 이벤트에서 npm publish
|
|
69
|
+
| `.github/workflows/npm-publish.yml` | GitHub release published 이벤트에서 npm provenance publish + 체크섬/증명 release 자산 |
|
|
47
70
|
|
|
48
|
-
##
|
|
71
|
+
## 5. 배포 차단 조건
|
|
49
72
|
|
|
50
73
|
다음 중 하나라도 실패하면 npm publish를 하지 않는다.
|
|
51
74
|
|
|
@@ -38,14 +38,37 @@ References:
|
|
|
38
38
|
- https://docs.npmjs.com/trusted-publishers/
|
|
39
39
|
- https://docs.github.com/actions/publishing-packages/publishing-nodejs-packages
|
|
40
40
|
|
|
41
|
-
## 3.
|
|
41
|
+
## 3. Signed release artifacts
|
|
42
|
+
|
|
43
|
+
The **cryptographic** trust anchors are the **npm provenance attestation** (registry artifact) and the **sigstore attestation** (release tarball) — both bind the artifact to this repository's release workflow identity via GitHub OIDC. `SHA256SUMS` is a **tooling-compatible convenience** for offline checksumming (`sha256sum -c`); on its own it is not a trust anchor, since the same workflow produces and uploads it. Beyond provenance, the publish workflow attaches these assets so a downloaded tarball can be verified before install:
|
|
44
|
+
|
|
45
|
+
- It runs `npm pack`, then `node scripts/release-checksums.mjs <tarball>` to emit a `SHA256SUMS` manifest (standard `<sha256-hex> <name>` format).
|
|
46
|
+
- It produces a **keyless sigstore attestation** of the tarball via `actions/attest-build-provenance` (GitHub OIDC, no signing keys).
|
|
47
|
+
- It uploads the tarball + `SHA256SUMS` to the GitHub release.
|
|
48
|
+
|
|
49
|
+
Verify a downloaded release:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# checksum (cross-platform: sha256sum -c, or the bundled script)
|
|
53
|
+
node scripts/release-checksums.mjs --check SHA256SUMS
|
|
54
|
+
sha256sum -c SHA256SUMS # GNU
|
|
55
|
+
shasum -a 256 -c SHA256SUMS # macOS
|
|
56
|
+
|
|
57
|
+
# sigstore attestation (tarball was built by this repo's release workflow)
|
|
58
|
+
gh attestation verify haechi-<version>.tgz --repo raeseoklee/haechi
|
|
59
|
+
|
|
60
|
+
# npm provenance (registry artifact)
|
|
61
|
+
npm audit signatures
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## 4. GitHub Actions
|
|
42
65
|
|
|
43
66
|
| Workflow | Purpose |
|
|
44
67
|
|---|---|
|
|
45
68
|
| `.github/workflows/ci.yml` | Tests, release preflight, SBOM artifact |
|
|
46
|
-
| `.github/workflows/npm-publish.yml` | npm publish
|
|
69
|
+
| `.github/workflows/npm-publish.yml` | npm provenance publish + checksummed/attested release assets on GitHub release published |
|
|
47
70
|
|
|
48
|
-
##
|
|
71
|
+
## 5. Deployment block conditions
|
|
49
72
|
|
|
50
73
|
npm publish is not performed if any of the following fail.
|
|
51
74
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
- 문서 상태: Draft 0.3
|
|
4
4
|
- 작성일: 2026-06-10
|
|
5
|
-
- 기준 버전: 0.
|
|
5
|
+
- 기준 버전: 0.7.0
|
|
6
6
|
- 기준 브랜치: `main`
|
|
7
7
|
|
|
8
8
|
## 1. 현재 판단
|
|
@@ -128,8 +128,8 @@ base64/인코딩 값 디코딩 검사, query string 검사, audit tail truncatio
|
|
|
128
128
|
|---|---|---|
|
|
129
129
|
| 0.4.0 ✅ | token round-trip and adoption | 2026-06-10 구현 완료: 요청 스코프 response detokenization, deterministic tokenization(파생 키), `haechi mcp-wrap`, `haechi audit-verify`/`haechi status`, injection detection type(기본 allow), `identity`/`authProvider` 계약 예약. `docs/current/release-0.4-implementation-scope.md` 참조 |
|
|
130
130
|
| 0.5.0 ✅ | streaming hardening | 2026-06-10 출시: bounded cross-frame 버퍼를 사용한 SSE/NDJSON 스트리밍 응답 검사(`streaming.requestMode: inspect`). stream sequence AAD, replay cache, 강화된 원격 배포 가이드는 0.6+으로 이월. `docs/current/release-0.5-implementation-scope.md` 참조 |
|
|
131
|
-
| 0.6.0 |
|
|
132
|
-
| 0.7.0 |
|
|
131
|
+
| 0.6.0 ✅ | Shipped 2026-06-10 (PRs #17–#19): built-in bearer auth, named policy profiles, model allowlist, request rate limit, PII-safe identity in audit. `docs/current/release-0.6-implementation-scope.md` 참조 |
|
|
132
|
+
| 0.7.0 ✅ | Shipped 2026-06-10 (PRs #22–#24): audit head-hash anchoring + external sink contract, cryptoProvider contract hardening + `assertCryptoProviderConformance` + reference KMS adapter, 서명/체크섬된 release artifact. `docs/current/release-0.7-implementation-scope.md` 참조 |
|
|
133
133
|
| 1.0.0 | stable API contract | migration policy, long-term audit schema, plugin sandbox/runtime conformance 및 allowlist/manifest 통과 외부 auth/classifier package 동적 로딩 |
|
|
134
134
|
|
|
135
135
|
동적 npm package 로딩은 1.0 plugin sandbox 이전까지 금지한다. 0.4~0.7의 외부 provider는 `createRuntime(config, providers)` 프로그래매틱 주입만 지원한다.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
- Status: Draft 0.3
|
|
4
4
|
- Date: 2026-06-10
|
|
5
|
-
- Target version: 0.
|
|
5
|
+
- Target version: 0.7.0
|
|
6
6
|
- Branch: `main`
|
|
7
7
|
|
|
8
8
|
## 1. Current Assessment
|
|
@@ -128,8 +128,9 @@ All checklist items below were completed for 0.3.2 on 2026-06-10 except the prov
|
|
|
128
128
|
|---|---|---|
|
|
129
129
|
| 0.4.0 ✅ | Token round-trip and adoption | Shipped 2026-06-10: request-scoped response detokenization, deterministic tokenization (derived key), `haechi mcp-wrap`, `haechi audit-verify`/`haechi status`, injection detection type (default allow), `identity`/`authProvider` contracts reserved. See `docs/current/release-0.4-implementation-scope.md` |
|
|
130
130
|
| 0.5.0 ✅ | Streaming hardening | Shipped 2026-06-10: SSE/NDJSON streaming response inspection with bounded cross-frame buffer (`streaming.requestMode: inspect`). Stream sequence AAD, replay cache, stronger remote deployment guide deferred to 0.6+. See `docs/current/release-0.5-implementation-scope.md` |
|
|
131
|
-
| 0.6.0 | Auth and
|
|
132
|
-
| 0.7.0 |
|
|
131
|
+
| 0.6.0 ✅ | Auth and per-client controls | Shipped 2026-06-10 (PRs #17–#19): built-in bearer auth, named policy profiles, model allowlist, request rate limit, PII-safe identity in audit. See `docs/current/release-0.6-implementation-scope.md` |
|
|
132
|
+
| 0.7.0 ✅ | Ops hardening | Shipped 2026-06-10 (PRs #22–#24): audit head-hash anchoring + external sink contract, cryptoProvider contract hardening + `assertCryptoProviderConformance` + reference KMS adapter, signed/checksummed release artifacts. See `docs/current/release-0.7-implementation-scope.md` |
|
|
133
|
+
| 0.8.0 | Ecosystem and observability | npm org (`@haechi/*`), publish `@haechi/crypto-kms` and `@haechi/auth-oidc`, npm workspaces, `@haechi/dashboard` read-only audit viewer (hash-chain integrity display, summary/search/timeline) |
|
|
133
134
|
| 1.0.0 | Stable API contract | Migration policy, long-term audit schema, plugin sandbox/runtime conformance, and dynamic loading of external auth/classifier packages that pass allowlist/manifest |
|
|
134
135
|
|
|
135
136
|
Dynamic npm package loading is prohibited until the 1.0 plugin sandbox. External providers in 0.4–0.7 are supported only via `createRuntime(config, providers)` programmatic injection.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
- 문서 상태: Draft 0.1
|
|
4
4
|
- 작성일: 2026-06-10
|
|
5
|
-
- 기준 버전: 0.
|
|
5
|
+
- 기준 버전: 0.7.0
|
|
6
6
|
|
|
7
7
|
## 1. 보호 대상
|
|
8
8
|
|
|
@@ -47,6 +47,11 @@ Haechi가 보호하려는 주요 자산은 다음이다.
|
|
|
47
47
|
| 행 걸린 upstream | proxy 연결 고갈 | `limits.upstreamTimeoutMs` 기본 120s, 초과 시 504 fail |
|
|
48
48
|
| signing/encryption 키 혼용 | key separation 위반 | policy bundle 서명 키를 domain-separated 파생 키로 분리 |
|
|
49
49
|
| JSON number/object key 은닉 | 카드번호 등 비문자열 leaf 미탐지 | number leaf와 object key도 detection/transform 대상 |
|
|
50
|
+
| 인증 없는 멀티 클라이언트 접근 | 로컬 프로세스가 upstream / token round-trip 경로를 무단 사용 | 선택적 bearer auth (`auth.provider: bearer`); 없거나 잘못된 경우 → 바디 읽기 전 401; identity별 rate limit 및 model allowlist |
|
|
51
|
+
| Audit tail truncation | 꼬리 audit 레코드의 무음 삭제 | 추가 전용/별도 미디어의 `audit.anchor` head-hash anchoring으로 마지막 anchor까지의 절단 탐지 (0.7) |
|
|
52
|
+
| Local dev key in production | 소프트웨어 키의 운영 custody 오용 | `assertCryptoProviderConformance`를 통한 외부 `cryptoProvider` 주입; reference KMS adapter (envelope 암호화) |
|
|
53
|
+
| Tampered release artifact | 변조된 tarball 설치 | npm provenance + GitHub release tarball의 sigstore attestation + `SHA256SUMS` (0.7) |
|
|
54
|
+
| audit에 원시 credentials/identity 노출 | audit 로그를 통한 token 또는 subject 유출 | Token은 keyed-HMAC 해시로만 저장; identity subject/issuer는 keyed HMAC 처리; `auth_denied` 레코드에 token 미포함 |
|
|
50
55
|
| token round-trip의 타 토큰 복원 | 클라이언트/요청 간 평문 복구 | detokenization은 opt-in(`detokenizeResponses`)이며 요청 스코프: 같은 요청을 보호하며 발급된 토큰만 복원 |
|
|
51
56
|
| tool result/응답 내 간접 prompt injection | 심어진 지시문에 의한 agent 조작 | 응답 방향 휴리스틱, 기본 report-only(`injection` action `allow`), 격상은 명시적 정책 선택. 완전 방어 아님 |
|
|
52
57
|
|
|
@@ -64,7 +69,7 @@ Haechi가 보호하려는 주요 자산은 다음이다.
|
|
|
64
69
|
- 외부 MCP server의 OAuth/resource binding 검증
|
|
65
70
|
- base64/URL-encoded 값, 유니코드 난독화 값의 디코딩 후 검사
|
|
66
71
|
- URL query string 내 민감값 검사 (JSON body만 검사)
|
|
67
|
-
-
|
|
72
|
+
- 마지막 anchor 이후의 audit tail truncation — `audit.anchor`(0.7)는 anchor가 추가 전용/별도 미디어에 있을 때 마지막 anchor까지의 레코드 삭제를 탐지한다; 마지막 anchor 이후 기록된 레코드와 동일 파일시스템 anchor는 대상에서 제외된다
|
|
68
73
|
- JSON-RPC batch 메시지 처리 (MCP stdio filter는 batch를 fail-closed로 거부)
|
|
69
74
|
|
|
70
75
|
## 5. 남은 운영 전제
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
- Status: Draft 0.1
|
|
4
4
|
- Date: 2026-06-10
|
|
5
|
-
- Target version: 0.
|
|
5
|
+
- Target version: 0.7.0
|
|
6
6
|
|
|
7
7
|
## 1. Assets Under Protection
|
|
8
8
|
|
|
@@ -47,6 +47,11 @@ The primary assets Haechi protects are:
|
|
|
47
47
|
| Hung upstream | Proxy connection exhaustion | `limits.upstreamTimeoutMs` default 120 s; 504 fail on timeout |
|
|
48
48
|
| Signing/encryption key conflation | Key separation violation | Policy bundle signing key isolated as a domain-separated derived key |
|
|
49
49
|
| JSON number / object key concealment | Undetected non-string leaves such as card numbers | Number leaves and object keys included in detection/transform scope |
|
|
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
|
+
| 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
|
+
| Local dev key in production | Software key misused as production custody | External `cryptoProvider` injection with `assertCryptoProviderConformance`; reference KMS adapter (envelope encryption) |
|
|
53
|
+
| Tampered release artifact | Modified tarball installed | npm provenance + sigstore attestation of the GitHub release tarball + `SHA256SUMS` (0.7) |
|
|
54
|
+
| Raw credentials/identity in audit | Token or subject leak through the audit log | Tokens stored only as keyed-HMAC hashes; identity subject/issuer are keyed HMAC; `auth_denied` records no token |
|
|
50
55
|
| Token round-trip restoring foreign tokens | Cross-client/request plaintext recovery | Detokenization is opt-in (`detokenizeResponses`) and request-scoped: only tokens issued while protecting the same request are restored |
|
|
51
56
|
| Indirect prompt injection in tool results/responses | Agent manipulation via planted instructions | Response-direction heuristics, report-only by default (`injection` action `allow`); escalation is an explicit policy choice. Not a complete defense |
|
|
52
57
|
|
|
@@ -64,7 +69,7 @@ The primary assets Haechi protects are:
|
|
|
64
69
|
- OAuth/resource binding validation for external MCP servers
|
|
65
70
|
- Inspection of base64/URL-encoded values or unicode-obfuscated values after decoding
|
|
66
71
|
- Detection of sensitive values in URL query strings (JSON body only)
|
|
67
|
-
- Audit
|
|
72
|
+
- 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
|
|
68
73
|
- JSON-RPC batch message processing (the MCP stdio filter rejects batches fail-closed)
|
|
69
74
|
|
|
70
75
|
## 5. Remaining Operational Assumptions
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# `@haechi/crypto-kms` (reference)
|
|
2
|
+
|
|
3
|
+
A reference KMS-backed `cryptoProvider` for Haechi's `keys.provider: external` path. This is the **shape** of the satellite published as `@haechi/crypto-kms` in 0.8 — it lives here as a dependency-free reference so core stays zero-runtime-dependency.
|
|
4
|
+
|
|
5
|
+
## How it works
|
|
6
|
+
|
|
7
|
+
Envelope encryption: each `encrypt` generates a fresh AES-256-GCM **data key**, encrypts the plaintext locally, and **wraps the data key with the KMS**. The KMS master key never leaves the KMS. `decrypt` unwraps the data key via the KMS and decrypts locally. `hmac` derives a per-domain key from the KMS, preserving Haechi's domain-separation discipline (tokens, identity, policy bundles).
|
|
8
|
+
|
|
9
|
+
The envelope matches Haechi's contract (`v, alg, kid, iv, ct, tag, aadHash`) plus a `wrappedKey`. AAD is canonicalized and bound exactly as the local provider does, so it passes `assertCryptoProviderConformance`.
|
|
10
|
+
|
|
11
|
+
## The KMS client interface
|
|
12
|
+
|
|
13
|
+
Inject any client implementing:
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
{
|
|
17
|
+
keyId: string,
|
|
18
|
+
async wrap(dataKey: Buffer): string, // KMS-encrypt a data key
|
|
19
|
+
async unwrap(wrapped: string): Buffer, // KMS-decrypt it
|
|
20
|
+
async deriveHmacKey(domain: string): Buffer // KMS-derived per-domain key
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`createInMemoryKms()` is a process-local stand-in for examples/tests. A real deployment swaps in an AWS KMS / HashiCorp Vault client (e.g. `@aws-sdk/client-kms` `GenerateDataKey`/`Decrypt`, plus an HKDF for `deriveHmacKey`).
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
In 0.7 this is a repo reference example — import it by relative path
|
|
29
|
+
(`./examples/crypto-kms-reference/index.mjs`). From 0.8 it is published as
|
|
30
|
+
`@haechi/crypto-kms` and imported by name, as shown below.
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
import { createRuntime } from "haechi/runtime";
|
|
34
|
+
import { createKmsCryptoProvider, createInMemoryKms } from "@haechi/crypto-kms";
|
|
35
|
+
|
|
36
|
+
const cryptoProvider = createKmsCryptoProvider({ kms: createInMemoryKms() });
|
|
37
|
+
const runtime = createRuntime({ keys: { provider: "external" }, /* ... */ }, { cryptoProvider });
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Self-test
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
import { assertCryptoProviderConformance } from "haechi/crypto";
|
|
44
|
+
await assertCryptoProviderConformance(cryptoProvider); // throws on any contract violation
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
This reference is **not a production key provider**; `createInMemoryKms` holds a process-local master key. Use a real KMS client for custody.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// Reference KMS-backed cryptoProvider for Haechi (keys.provider: external).
|
|
2
|
+
//
|
|
3
|
+
// This is the *shape* a published @haechi/crypto-kms satellite (0.8) takes. It
|
|
4
|
+
// uses envelope encryption: a fresh data key per record encrypts the plaintext
|
|
5
|
+
// locally with AES-256-GCM, and the data key is wrapped by the KMS. The master
|
|
6
|
+
// key never leaves the KMS. The `kms` client is injected, so this file has zero
|
|
7
|
+
// real dependencies — a real adapter swaps createInMemoryKms() for an AWS KMS /
|
|
8
|
+
// HashiCorp Vault client implementing the same small interface.
|
|
9
|
+
//
|
|
10
|
+
// Inject it: createRuntime(config, { cryptoProvider: createKmsCryptoProvider({ kms }) })
|
|
11
|
+
// and set keys.provider: "external".
|
|
12
|
+
|
|
13
|
+
import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes } from "node:crypto";
|
|
14
|
+
|
|
15
|
+
// The published @haechi/crypto-kms satellite imports this from `haechi/crypto`;
|
|
16
|
+
// it is inlined here so the reference example is fully self-contained (no
|
|
17
|
+
// cross-package import) and matches Haechi's canonical AAD exactly.
|
|
18
|
+
function canonicalize(value) {
|
|
19
|
+
if (Array.isArray(value)) {
|
|
20
|
+
return `[${value.map((item) => canonicalize(item)).join(",")}]`;
|
|
21
|
+
}
|
|
22
|
+
if (value && typeof value === "object") {
|
|
23
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalize(value[key])}`).join(",")}}`;
|
|
24
|
+
}
|
|
25
|
+
return JSON.stringify(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const ALG = "AES-256-GCM";
|
|
29
|
+
const HMAC_KEY_DOMAIN = "haechi:crypto-kms:hmac-root:v1";
|
|
30
|
+
|
|
31
|
+
// The injected KMS client must implement:
|
|
32
|
+
// keyId: string
|
|
33
|
+
// async wrap(dataKey: Buffer) -> string (KMS-encrypt a data key)
|
|
34
|
+
// async unwrap(wrapped: string) -> Buffer (KMS-decrypt it back)
|
|
35
|
+
// async deriveHmacKey(domain: string) -> Buffer (KMS-derived per-domain key)
|
|
36
|
+
export function createKmsCryptoProvider({ kms }) {
|
|
37
|
+
if (!kms || typeof kms.wrap !== "function" || typeof kms.unwrap !== "function" || typeof kms.deriveHmacKey !== "function") {
|
|
38
|
+
throw new Error("createKmsCryptoProvider requires a kms client with wrap/unwrap/deriveHmacKey");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function sha256(value) {
|
|
42
|
+
// Plain SHA-256, matching Haechi's core aadHash (defence-in-depth; GCM
|
|
43
|
+
// already authenticates the AAD via the tag).
|
|
44
|
+
return createHash("sha256").update(value).digest("base64url");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
id: "haechi.crypto.kms-reference",
|
|
49
|
+
version: "0.1.0",
|
|
50
|
+
capabilities: {
|
|
51
|
+
readsPlaintext: true,
|
|
52
|
+
networkEgress: true, // a real KMS adapter calls out to the KMS
|
|
53
|
+
keyCustody: "external-kms"
|
|
54
|
+
},
|
|
55
|
+
async encrypt({ plaintext, aad }) {
|
|
56
|
+
const dataKey = randomBytes(32);
|
|
57
|
+
const iv = randomBytes(12);
|
|
58
|
+
const cipher = createCipheriv("aes-256-gcm", dataKey, iv);
|
|
59
|
+
const aadBytes = Buffer.from(canonicalize(aad), "utf8");
|
|
60
|
+
cipher.setAAD(aadBytes);
|
|
61
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
62
|
+
const tag = cipher.getAuthTag();
|
|
63
|
+
return {
|
|
64
|
+
v: 1,
|
|
65
|
+
alg: ALG,
|
|
66
|
+
kid: kms.keyId,
|
|
67
|
+
iv: iv.toString("base64url"),
|
|
68
|
+
ct: ciphertext.toString("base64url"),
|
|
69
|
+
tag: tag.toString("base64url"),
|
|
70
|
+
wrappedKey: await kms.wrap(dataKey),
|
|
71
|
+
aadHash: sha256(aadBytes)
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
async decrypt({ envelope, aad }) {
|
|
75
|
+
if (envelope.alg && envelope.alg !== ALG) {
|
|
76
|
+
throw new Error(`Unsupported algorithm: ${envelope.alg}`);
|
|
77
|
+
}
|
|
78
|
+
const aadBytes = Buffer.from(canonicalize(aad), "utf8");
|
|
79
|
+
if (envelope.aadHash && envelope.aadHash !== sha256(aadBytes)) {
|
|
80
|
+
throw new Error("AAD hash mismatch");
|
|
81
|
+
}
|
|
82
|
+
const dataKey = await kms.unwrap(envelope.wrappedKey);
|
|
83
|
+
const decipher = createDecipheriv("aes-256-gcm", dataKey, Buffer.from(envelope.iv, "base64url"));
|
|
84
|
+
decipher.setAAD(aadBytes);
|
|
85
|
+
decipher.setAuthTag(Buffer.from(envelope.tag, "base64url"));
|
|
86
|
+
return Buffer.concat([
|
|
87
|
+
decipher.update(Buffer.from(envelope.ct, "base64url")),
|
|
88
|
+
decipher.final()
|
|
89
|
+
]).toString("utf8");
|
|
90
|
+
},
|
|
91
|
+
async hmac({ data, domain }) {
|
|
92
|
+
if (!domain || typeof domain !== "string") {
|
|
93
|
+
throw new Error("hmac requires a non-empty domain string");
|
|
94
|
+
}
|
|
95
|
+
// Domain-separated: derive a per-domain key from the KMS, then HMAC.
|
|
96
|
+
const derived = await kms.deriveHmacKey(domain);
|
|
97
|
+
return createHmac("sha256", derived).update(data).digest("hex");
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// In-memory stand-in for AWS KMS / Vault — for examples and tests only. A real
|
|
103
|
+
// deployment injects a client backed by the cloud KMS.
|
|
104
|
+
//
|
|
105
|
+
// WARNING: the default masterKey is a fresh random key PER PROCESS. Anything
|
|
106
|
+
// encrypted in one run cannot be decrypted in the next — exactly the silent
|
|
107
|
+
// data-loss footgun that key rotation must avoid. For any persistence across
|
|
108
|
+
// restarts, supply a stable `masterKey` (or, in production, use a real KMS that
|
|
109
|
+
// holds the master key). This fake is NOT a production key provider.
|
|
110
|
+
export function createInMemoryKms({ keyId = "kms-ref-local", masterKey = randomBytes(32) } = {}) {
|
|
111
|
+
return {
|
|
112
|
+
keyId,
|
|
113
|
+
async wrap(dataKey) {
|
|
114
|
+
const iv = randomBytes(12);
|
|
115
|
+
const cipher = createCipheriv("aes-256-gcm", masterKey, iv);
|
|
116
|
+
const ct = Buffer.concat([cipher.update(dataKey), cipher.final()]);
|
|
117
|
+
const tag = cipher.getAuthTag();
|
|
118
|
+
return Buffer.concat([iv, tag, ct]).toString("base64url");
|
|
119
|
+
},
|
|
120
|
+
async unwrap(wrapped) {
|
|
121
|
+
const buffer = Buffer.from(wrapped, "base64url");
|
|
122
|
+
const iv = buffer.subarray(0, 12);
|
|
123
|
+
const tag = buffer.subarray(12, 28);
|
|
124
|
+
const ct = buffer.subarray(28);
|
|
125
|
+
const decipher = createDecipheriv("aes-256-gcm", masterKey, iv);
|
|
126
|
+
decipher.setAuthTag(tag);
|
|
127
|
+
return Buffer.concat([decipher.update(ct), decipher.final()]);
|
|
128
|
+
},
|
|
129
|
+
async deriveHmacKey(domain) {
|
|
130
|
+
return createHmac("sha256", masterKey).update(`${HMAC_KEY_DOMAIN}:${domain}`).digest();
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@haechi/crypto-kms",
|
|
3
|
+
"version": "0.0.0-reference",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Reference KMS-backed cryptoProvider for Haechi (keys.provider: external). Promoted to a published @haechi/* satellite in 0.8.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.mjs"
|
|
9
|
+
},
|
|
10
|
+
"peerDependencies": {
|
|
11
|
+
"haechi": ">=0.7.0"
|
|
12
|
+
},
|
|
13
|
+
"optionalDependencies": {
|
|
14
|
+
"@aws-sdk/client-kms": "^3"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=22"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -47,7 +47,12 @@
|
|
|
47
47
|
},
|
|
48
48
|
"audit": {
|
|
49
49
|
"sink": "jsonl",
|
|
50
|
-
"path": ".haechi/audit.jsonl"
|
|
50
|
+
"path": ".haechi/audit.jsonl",
|
|
51
|
+
"anchor": {
|
|
52
|
+
"mode": "none",
|
|
53
|
+
"path": ".haechi/audit.anchor.jsonl",
|
|
54
|
+
"everyRecords": 1
|
|
55
|
+
}
|
|
51
56
|
},
|
|
52
57
|
"tokenVault": {
|
|
53
58
|
"provider": "local",
|
|
@@ -61,6 +66,16 @@
|
|
|
61
66
|
"privacy": {
|
|
62
67
|
"profile": null
|
|
63
68
|
},
|
|
69
|
+
"auth": {
|
|
70
|
+
"provider": "none",
|
|
71
|
+
"store": ".haechi/auth.json",
|
|
72
|
+
"allowedLabelKeys": [
|
|
73
|
+
"team",
|
|
74
|
+
"env",
|
|
75
|
+
"tier",
|
|
76
|
+
"role"
|
|
77
|
+
]
|
|
78
|
+
},
|
|
64
79
|
"mcp": {
|
|
65
80
|
"allowedMethods": [
|
|
66
81
|
"initialize",
|