haechi 0.6.0 → 0.8.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 +8 -1
- package/README.md +8 -1
- package/docs/README.md +1 -0
- package/docs/current/api-stability.ko.md +14 -2
- package/docs/current/api-stability.md +14 -2
- package/docs/current/release-0.6-implementation-scope.ko.md +4 -4
- package/docs/current/release-0.6-implementation-scope.md +4 -4
- package/docs/current/release-0.7-implementation-scope.ko.md +91 -0
- package/docs/current/release-0.7-implementation-scope.md +92 -0
- package/docs/current/release-0.8-implementation-scope.ko.md +145 -0
- package/docs/current/release-0.8-implementation-scope.md +145 -0
- package/docs/current/release-process.ko.md +60 -6
- package/docs/current/release-process.md +60 -6
- package/docs/current/risk-register-release-gate.ko.md +4 -2
- package/docs/current/risk-register-release-gate.md +4 -3
- package/docs/current/threat-model.ko.md +5 -2
- package/docs/current/threat-model.md +5 -2
- package/examples/crypto-kms-reference/README.md +13 -0
- package/haechi.config.example.json +6 -1
- package/package.json +9 -1
- package/packages/audit/index.mjs +99 -6
- package/packages/auth/index.mjs +45 -0
- package/packages/cli/bin/haechi.mjs +40 -8
- package/packages/cli/runtime.mjs +33 -3
- package/packages/crypto/index.mjs +107 -0
- package/scripts/release-checksums.mjs +100 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Haechi 0.8 Implementation Scope
|
|
2
|
+
|
|
3
|
+
- Status: Draft 0.3 (design — not yet implemented)
|
|
4
|
+
- Date: 2026-06-10
|
|
5
|
+
- Target version: 0.8.0 (after 0.7.0)
|
|
6
|
+
- Type: ecosystem foundation
|
|
7
|
+
|
|
8
|
+
## 1. Release Goal
|
|
9
|
+
|
|
10
|
+
Stand up the `haechi-*` package ecosystem: convert the repo to an npm workspaces monorepo and publish the first two satellites — `haechi-crypto-kms` (promoting the 0.7 reference) and `haechi-auth-jwt` (JWKS bearer verification), both unscoped (the `@haechi` scope is taken; no org needed). This realizes operational key custody as an installable package and grows the auth ecosystem without touching core's zero-dependency posture.
|
|
11
|
+
|
|
12
|
+
**Scope decision (2026-06-10):** 0.8 is the **packaging foundation + satellites**. The `haechi-dashboard` read-only audit viewer (a UI build) and full interactive `haechi-auth-oidc` move to **0.9** so 0.8 stays code-light and focused on the monorepo + two headless-friendly adapters.
|
|
13
|
+
|
|
14
|
+
Core (`haechi`, unscoped) stays **zero runtime dependency**. Satellite dependencies (e.g. an AWS SDK) live in the satellite's own `package.json` only and never enter core's tarball or SBOM.
|
|
15
|
+
|
|
16
|
+
## 2. Scope
|
|
17
|
+
|
|
18
|
+
### 2.1 npm workspaces monorepo (verified resolution mechanic)
|
|
19
|
+
|
|
20
|
+
The naive layout (`"workspaces": ["satellites/*"]` + a satellite peer range `"haechi": ">=0.8.0"`) **does not work** and was rejected after empirical testing: npm treats the unmet peer range as a registry lookup (`ETARGET: no matching version for haechi@>=0.8.0`), never symlinks the root project into `node_modules/haechi`, and the satellite's `import "haechi/crypto"` throws `ERR_MODULE_NOT_FOUND`. The root project is **not** a workspace member by default, so npm never links it.
|
|
21
|
+
|
|
22
|
+
The verified working layout:
|
|
23
|
+
|
|
24
|
+
- **Root lists itself as a workspace member:** `"workspaces": [".", "satellites/*"]`. The `"."` entry is what makes npm create the `node_modules/haechi → ..` symlink so satellites resolve core. Without it, satellites fall back to the registry.
|
|
25
|
+
- **The repo root remains the published `haechi` package** — its `exports`, `bin`, and `files` allowlist are unchanged. The only `package.json` delta is the added `workspaces` field and the version bump. Satellites are **not** in core's `files`, so they never ship inside the `haechi` tarball (verified: a root `npm pack --dry-run` lists only the `files` allowlist; `satellites/` is excluded).
|
|
26
|
+
- **Satellites declare a dual dependency on core:**
|
|
27
|
+
- `"peerDependencies": { "haechi": ">=0.8.0 <1.0.0" }` — the contract a *consumer* installs against, so the satellite reuses the consumer's single `haechi` instance (one crypto/identity surface, no duplicate copies).
|
|
28
|
+
- `"devDependencies": { "haechi": "*" }` — the mechanism that makes npm link the **local workspace** during monorepo development/CI instead of resolving the peer range from the registry. `npm pack` strips devDependencies from the published tarball, so the consumer-facing manifest carries only the peer range — the `*` devDep is invisible downstream. (A repo/source scanner reading `satellites/*/package.json` will still see it; that source-vs-artifact difference is expected and harmless.)
|
|
29
|
+
- **Consumer peer-mismatch behavior:** installing a published satellite against an *incompatible* `haechi` (e.g. `haechi@0.7.0` already present) yields an npm `ERESOLVE` **warning**, not a hard failure; the consumer must upgrade `haechi` (or `--legacy-peer-deps` at their own risk). The satellite does not function correctly against an out-of-range core.
|
|
30
|
+
- Satellites import core by subpath (`haechi/crypto`, `haechi/auth`, `haechi/runtime`); these resolve through the workspace symlink in dev and through the consumer's installed `haechi` in production.
|
|
31
|
+
- `examples/crypto-kms-reference/` is **promoted** to `satellites/crypto-kms/`. The reference example inlined `canonicalize` because its nested `package.json` could not self-resolve `haechi/crypto` pre-workspaces; **under workspaces that import resolves**, so the satellite imports `canonicalize` from `haechi/crypto` rather than carrying a copy (avoiding AAD-canonicalization drift between core and satellite). The old `examples/` directory keeps a short README pointing at the published package. A conformance test asserts the satellite's AAD canonicalization is **byte-for-byte identical** to `haechi/crypto` (not merely semantically equivalent).
|
|
32
|
+
- **Lock file:** converting to workspaces regenerates `package-lock.json` with workspace-resolved entries (including the root self-member). The regenerated lock file is committed; CI uses `npm ci` (which fails on a stale/missing lock), so the conversion PR must commit the fresh lock.
|
|
33
|
+
|
|
34
|
+
**CI strategy (avoid double-runs):** root CI runs `node --test` directly from the root, which discovers `satellites/**/*.test.mjs` automatically (workspace symlinks make their `haechi/*` imports resolve). CI does **not** use `npm test --workspaces` (which would recurse into the root self-member and re-run the suite). Each satellite keeps its own `test` script for isolated local runs (`npm test -w haechi-crypto-kms`) only. Verified: root `node --test` runs core + satellite tests once, and the `node_modules/haechi → ..` symlink cycle does not hang the runner (node skips `node_modules`).
|
|
35
|
+
|
|
36
|
+
**Honest packaging note (was a "byte-stable" claim):** the root tarball is **not** byte-identical to 0.7.0 — `package.json` gains the `workspaces` field and the version bumps, which is expected for any release. The defensible, tested claim is narrower and is enforced by a CI gate (see §6.1): **(a) no satellite files appear in the `haechi` tarball, and (b) the `haechi` tarball's own `package.json` declares zero runtime `dependencies`.** The gate inspects the **packed manifest** (extract `package.json` from `npm pack` output and assert `dependencies` is empty/undefined) — not the installed `node_modules` SBOM, which would pass vacuously today and miss a future runtime-dep leak.
|
|
37
|
+
|
|
38
|
+
### 2.2 Unscoped `haechi-*` names + per-package trusted publishing
|
|
39
|
+
|
|
40
|
+
- **Naming (decision 2026-06-10):** the `@haechi` npm org/scope is already taken by a third party, so satellites are published as **unscoped `haechi-*`** names — `haechi-crypto-kms`, `haechi-auth-jwt` (both verified free on npm). This needs **no npm org**, matches the unscoped core `haechi`, and each name is reserved + Trusted-Publisher-bound individually. (Trade-off vs a scope: no namespace grouping/defence; the `haechi-` prefix is the convention.)
|
|
41
|
+
- Each satellite is published with the **same OIDC trusted-publishing + sigstore + SHA256SUMS** path proven in 0.7 — its own npmjs.com Trusted Publisher link and a tag-triggered publish workflow.
|
|
42
|
+
- **Satellite `package.json` requirements (do not inherit from root):** each satellite still sets its own `"publishConfig": { "access": "public", "provenance": true }`. Unscoped packages are public by default, so `access: public` is belt-and-suspenders; `provenance: true` and not inheriting the root's `publishConfig` are the load-bearing parts. Post-publish, the runbook verifies `npm view haechi-<pkg> access` reports `public`.
|
|
43
|
+
- **Tag namespacing + workflow guards (avoid mis-triggers and collisions):**
|
|
44
|
+
- Core release tags: `v<semver>` (e.g. `v0.8.0`). The root publish workflow triggers on `push: tags: ['v[0-9]*.[0-9]*.[0-9]*']`. Because GitHub's tag glob treats `.` literally and `[0-9]*` loosely (it would also match `v1.2.3.4` or `v1a.2.3`), the workflow **re-validates** the tag against a strict `^v[0-9]+\.[0-9]+\.[0-9]+$` regex in a pre-publish step and fails closed on a non-match.
|
|
45
|
+
- Satellite tags are **prefixed**: `crypto-kms-v<semver>`, `auth-jwt-v<semver>`. Each satellite workflow triggers only on its own prefix glob and likewise re-validates against `^<prefix>-v[0-9]+\.[0-9]+\.[0-9]+$`.
|
|
46
|
+
- Each workflow re-asserts the package directory it publishes (`npm publish -w <dir>`) so a mistagged push can't publish the wrong package. The Trusted Publisher on npmjs.com is bound to a **specific workflow filename** — renaming the workflow without updating the npm config breaks OIDC auth (documented as a failure mode in the runbook, with a package→workflow-filename→tag-glob mapping table).
|
|
47
|
+
- **Independent semver** per satellite (a satellite patch never bumps core). Satellites start at `0.1.0`. **Pre-1.0 contract:** satellites follow standard npm semver where a `0.x` **minor** bump may carry breaking changes; consumers should pin `major.minor` (e.g. `haechi-crypto-kms@~0.1`). Satellites are pre-stable until their own `1.0.0`.
|
|
48
|
+
- **Bootstrapping the first publish (no org needed):** order per satellite — (1) on npmjs.com, **configure the Trusted Publisher** for the (not-yet-published) unscoped name, linking the repo + exact workflow filename; (2) push the satellite's first tag → the workflow's OIDC publish creates `0.1.0` with provenance and **claims the name** on first publish. No manual `npm publish` from a laptop is required (matches the 0.7 trusted-publishing posture). Because the names are unscoped and currently free, there is no org-membership prerequisite.
|
|
49
|
+
|
|
50
|
+
### 2.3 `haechi-crypto-kms` (publish + real KMS client)
|
|
51
|
+
|
|
52
|
+
- Promote the 0.7 reference (`createKmsCryptoProvider` envelope encryption + `createInMemoryKms`) into the published package, switching its inlined `canonicalize` to `import { canonicalize } from "haechi/crypto"` (§2.1). The existing `kms` client interface (`keyId` / `wrap` / `unwrap` / `deriveHmacKey`) is **unchanged**, so the promoted provider and in-memory client stay byte-for-byte and their 0.7 tests carry over.
|
|
53
|
+
- Add a **real AWS KMS client** at `haechi-crypto-kms/aws`: `createAwsKmsClient({ keyId, region, client, hmacRootCiphertext })`. It implements the same `kms` interface: `wrap` = KMS `Encrypt` of a CSPRNG-generated 32-byte data key, `unwrap` = KMS `Decrypt` (envelope encryption — the master key never leaves KMS); `deriveHmacKey(domain)` = **HKDF-SHA256** over a single KMS-`Decrypt`ed 32-byte root (`hmacRootCiphertext`, cached), domain-separated — deterministic with no per-token network call. With no `hmacRootCiphertext`, `deriveHmacKey` throws and the provider is encrypt-only (valid via `requireHmac:false`).
|
|
54
|
+
- **`@aws-sdk/client-kms` is an OPTIONAL peer dependency, not a hard dependency** (decision 2026-06-10, revising the earlier "satellite's own dependency" wording). It is imported **lazily** only when no `client` is injected, so: the monorepo `npm ci`/CI never pulls the (large) AWS SDK; consumers on the in-memory or an injected client never install it; and `core` is trivially unaffected. The published satellite declares it under `peerDependencies` + `peerDependenciesMeta.optional`. This keeps the satellite dependency-light while still offering a real backend.
|
|
55
|
+
- The satellite's CI runs `assertCryptoProviderConformance` (imported from `haechi/crypto` via the workspace symlink) against the in-memory client **and** the AWS client driven by an **injected mock** of the two KMS ops (`encrypt`/`decrypt`) — **no SDK, no network**. The mock is a faithful envelope (AES-256-GCM under a per-mock master key): `Decrypt` returns the plaintext only for a blob this key wrapped and **rejects** a blob wrapped by a different key (cross-key isolation) or a corrupted blob. A trivial always-succeeds stub is insufficient; the suite exercises these rejection paths plus HMAC determinism/domain-separation. Live `createAwsKmsClient` validation against a sandbox KMS key is an **out-of-CI integration test** (documented, not gating).
|
|
56
|
+
|
|
57
|
+
### 2.4 `haechi-auth-jwt` (JWKS bearer verification, dependency-light)
|
|
58
|
+
|
|
59
|
+
`createJwtAuthProvider({ issuer, audience, jwksUri, cryptoProvider, algorithms, clockSkewSeconds, claimMappings })` implements the `authProvider` contract for a **headless** gateway. It is implementable with `node:` builtins only (no `jose`): JWKS fetched via global `fetch`, JWK→key via `crypto.createPublicKey({ key: jwk, format: "jwk" })`, signatures verified via `crypto.verify`.
|
|
60
|
+
|
|
61
|
+
**Implementation note — ES256 signature encoding (verified):** a JWS ES256 signature is raw `R‖S` (IEEE-P1363, 64 bytes for P-256), but `node:crypto.verify` defaults to **DER** for EC keys and returns `false` for a raw signature — silently rejecting every valid ES256 token. The verifier MUST pass `dsaEncoding: "ieee-p1363"` for the EC algorithms. (Empirically confirmed: default DER ⇒ `false`; `ieee-p1363` ⇒ `true`.) This is an acceptance criterion, not an option.
|
|
62
|
+
|
|
63
|
+
**Security spec (mandatory — these are acceptance criteria, not options).** Concrete constants below are *decisions*, not implementation discretion.
|
|
64
|
+
|
|
65
|
+
- **Algorithm selection is server-side, never from the token.** The verifier picks the algorithm from the configured `algorithms` allowlist (default `["RS256","ES256"]`) and the JWK's `kty`/`crv`. The token's `alg` header is checked for *membership* in the allowlist **before** key selection; it never selects the verification routine.
|
|
66
|
+
- **Reject `alg: "none"`** unconditionally.
|
|
67
|
+
- **Block alg-confusion:** never feed an RSA public key into an HMAC verify. HMAC family (`HS*`) is **not allowed** by default; a JWKS-sourced public key is only ever used with its matching asymmetric algorithm.
|
|
68
|
+
- **`kid` is required**; the signing key is selected by `kid` from the JWKS, not by trying every key.
|
|
69
|
+
- **RSA key-strength floor:** an RSA JWK with modulus `< 2048` bits is rejected as invalid.
|
|
70
|
+
- **JWK usage intent:** if a JWK carries `use`, it must be `sig`; if it carries `key_ops`, it must include `verify`/`sign` and must not include `encrypt`/`decrypt`. Otherwise reject.
|
|
71
|
+
- **Header `typ` / no JWE:** if `typ` is present it must be `JWT`; encrypted (JWE) tokens are rejected unconditionally — only JWS is accepted.
|
|
72
|
+
- **Claims are mandatory and fully validated:**
|
|
73
|
+
- `iss` must equal the configured `issuer`.
|
|
74
|
+
- `aud` (token) may be a string or an array of strings (RFC 7519); the configured `audience` must equal the string or be a member of the array — exact, case-sensitive match.
|
|
75
|
+
- `sub` is required and must be a non-empty string (it is the input to `subjectHash`).
|
|
76
|
+
- `exp` and `nbf` are **required**. `exp`: reject when `now > exp + clockSkewSeconds`. `nbf`: reject when `now < nbf - clockSkewSeconds`. A token missing `exp` is rejected. `iat` is sanity-checked if present.
|
|
77
|
+
- **`clockSkewSeconds`** default `60`, **maximum `300`** — construction rejects a value `> 300` (a larger skew would gut expiry validation).
|
|
78
|
+
- **JWKS fetching is SSRF-hardened:**
|
|
79
|
+
- `issuer` must be a valid **HTTPS URL**; `jwksUri` must be HTTPS and its **hostname must exactly equal the `issuer` hostname** (port excluded). 0.8 supports **single-origin issuers only** — IdPs that serve JWKS from a different host than the issuer identifier (some CDN-fronted setups) are explicitly out of scope for 0.8 and rejected at construction. A non-URL issuer (URN-style) is rejected at construction with a clear error.
|
|
80
|
+
- Requests to private/loopback/link-local ranges and cloud-metadata endpoints are refused: `127.0.0.0/8`, `::1`, `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `169.254.0.0/16` (incl. `169.254.169.254`), `fe80::/10`. Refusal is at fetch time after DNS resolution (guard against rebinding), with a fetch timeout.
|
|
81
|
+
- **JWKS response bounds:** reject a response body `> 1 MiB`; `JSON.parse` is guarded against pathological nesting (depth bound). JWT segments are decoded as **strict base64url** (`[A-Za-z0-9_-]`, no padding) before `JSON.parse`.
|
|
82
|
+
- **JWKS cache is bounded and DoS-resistant:** keys are cached with a TTL; an **unknown `kid` does not trigger an unbounded refetch** — at most one full JWKS refresh per **60 s cooldown**, so a flood of forged `kid`s can't become a fetch storm against the IdP.
|
|
83
|
+
- **Identity is PII-safe (fail-closed):** `cryptoProvider` is **required** and must expose `hmac`; if absent, the provider constructor throws (it cannot produce a PII-safe identity). `subjectHash` / `issuerHash` are keyed **HMAC-SHA-256** (`haechi:identity:hash:v1`), hex-encoded (64 chars) — raw `sub` / `iss` are never stored or logged. `scopes` come from a configured scope claim (`scp` / `scope`); `labels` from an allowlisted claim mapping only.
|
|
84
|
+
- **Fail-closed everywhere:** any verification error → `authenticate` returns `null` (deny), never throws into the request path; no token detail is echoed to the client.
|
|
85
|
+
|
|
86
|
+
Wired via **injection** (`createRuntime(config, { authProvider: createJwtAuthProvider(...) })`); `auth.provider: external`. Dynamic loading stays banned until the 1.0 plugin sandbox.
|
|
87
|
+
|
|
88
|
+
### 2.5 Release process for the monorepo
|
|
89
|
+
|
|
90
|
+
- The existing root workflow keeps publishing `haechi` (guarded to the `v*` tag glob + strict regex re-validation).
|
|
91
|
+
- Each satellite has its own prefixed-tag publish workflow reusing the 0.7 signed-artifacts steps (pack → checksum → attest → publish → upload), scoped to its own directory, each bound to its own Trusted Publisher (specific workflow filename).
|
|
92
|
+
- The per-package release runbook (`release-process.md`) documents: tag conventions + the package→workflow-filename→tag-glob mapping table, the Trusted Publisher bootstrap order (reserve → configure → tag), the workflow-rename failure mode, and post-publish verification (provenance, `npm view ... access`).
|
|
93
|
+
|
|
94
|
+
## 3. Explicit non-scope (deferred to 0.9+)
|
|
95
|
+
|
|
96
|
+
- `haechi-dashboard` read-only audit viewer (UI build, its own tech-stack decision).
|
|
97
|
+
- `haechi-auth-oidc` full interactive OIDC (authorization-code flow) — `haechi-auth-jwt` covers the headless case first.
|
|
98
|
+
- `haechi-auth-jwt` multi-origin/CDN-fronted JWKS (issuer host ≠ JWKS host).
|
|
99
|
+
- `haechi-classifier-*` ML/heuristic classifier plugins.
|
|
100
|
+
- `haechi-crypto-kms` Vault/GCP/Azure backends (AWS only in 0.8).
|
|
101
|
+
- Dynamic loading of satellites (1.0 plugin sandbox).
|
|
102
|
+
|
|
103
|
+
The risk-register and roadmap are updated to move `haechi-auth-oidc` and `haechi-dashboard` out of the 0.8 row and into a new **0.9** row, so the public docs match this scope.
|
|
104
|
+
|
|
105
|
+
## 4. Backward compatibility
|
|
106
|
+
|
|
107
|
+
Core behavior is unchanged: the root package's `exports`, `bin`, `files`, and zero-dep runtime posture are identical. Adding `workspaces` (including the `"."` self-entry) to the root `package.json` is inert for anyone installing `haechi` as a single dependency. Existing config and APIs are untouched; satellites are purely additive and opt-in.
|
|
108
|
+
|
|
109
|
+
## 5. 1.0 relationship
|
|
110
|
+
|
|
111
|
+
0.8 does not itself close a 1.0 blocker, but it **realizes operational key custody as an installable, attested package** (`haechi-crypto-kms`) and proves the satellite model end-to-end. The remaining 1.0 gates stay: API-stability freeze and plugin sandbox + real-environment validation.
|
|
112
|
+
|
|
113
|
+
## 6. Test criteria (mapped to the PR breakdown)
|
|
114
|
+
|
|
115
|
+
### 6.1 PR1 — workspaces conversion (no new published package)
|
|
116
|
+
|
|
117
|
+
- Root `npm install` exits 0 with **no ERESOLVE/ETARGET**; `node_modules/haechi` is the workspace symlink; the committed `package-lock.json` makes `npm ci` succeed on a fresh checkout.
|
|
118
|
+
- A satellite test that does `import { ... } from "haechi/crypto"` runs green under root `node --test`.
|
|
119
|
+
- **No-leak + zero-dep gate:** core `npm pack --dry-run` contains **no `satellites/` paths**; the **packed** `haechi` `package.json` (extracted from the tarball) has empty/undefined `dependencies`. The gate is **negatively tested**: temporarily adding `satellites/` to core's `files`, or a runtime dep to core's `package.json`, makes the gate fail with a clear error (so the gate isn't a vacuous pass).
|
|
120
|
+
- In-memory crypto provider (promoted 0.7 code) passes `assertCryptoProviderConformance` through the workspace symlink, including the byte-for-byte `canonicalize` parity check vs `haechi/crypto`.
|
|
121
|
+
|
|
122
|
+
### 6.2 PR2 — `haechi-crypto-kms` (real AWS client)
|
|
123
|
+
|
|
124
|
+
- In-memory **and AWS** clients (the AWS one driven by an **injected mock** of the KMS `encrypt`/`decrypt` ops — no SDK, no network) pass `assertCryptoProviderConformance`, including the cross-key/corrupted-blob **rejection** paths and HMAC determinism/domain-separation; end-to-end through `createRuntime` (encrypt + tokenization round-trip).
|
|
125
|
+
- `createAwsKmsClient` without a `keyId` throws; with no `hmacRootCiphertext`, `deriveHmacKey` throws and the provider passes conformance as encrypt-only (`requireHmac:false`).
|
|
126
|
+
- The published manifest sets `publishConfig.access: public` and declares `@aws-sdk/client-kms` under `peerDependencies` + `peerDependenciesMeta.optional` (NOT a runtime `dependency`); the published satellite tarball has `dependencies: {}`, and core's tarball stays zero-dep (the §6.1 gate still passes).
|
|
127
|
+
- A satellite publish workflow (`crypto-kms-v<semver>`) exists with the 0.7 signed-artifacts path; the core workflow is guarded so a satellite release tag never publishes `haechi`.
|
|
128
|
+
|
|
129
|
+
### 6.3 PR3 — `haechi-auth-jwt` (security gates)
|
|
130
|
+
|
|
131
|
+
- A valid RS256/ES256 JWT (test-key-signed, stub JWKS) authenticates into a PII-safe identity with **no raw `sub` in the audit**; `subjectHash`/`issuerHash` are 64-hex-char HMAC-SHA-256.
|
|
132
|
+
- Each of the following is **denied**: `alg:"none"`; an `HS256` token forged with the RSA public key (alg-confusion); a JWE/`typ` mismatch; expired (`exp`); not-yet-valid (`nbf`); missing `exp`; missing/empty `sub`; wrong-`aud` (string and array forms); wrong-`iss`; unknown-`kid`; bad-signature; an RSA JWK `< 2048` bits; a JWK with `use:"enc"`/`key_ops:["encrypt"]`.
|
|
133
|
+
- Construction rejects: a non-HTTPS or cross-origin `jwksUri`; a non-URL `issuer`; `clockSkewSeconds > 300`; a missing `cryptoProvider.hmac`. A `jwksUri` resolving to `127.0.0.1`, `169.254.169.254`, `::1`, or any RFC1918 CIDR is rejected.
|
|
134
|
+
- An unknown-`kid` flood triggers **exactly one** JWKS refetch within the 60 s cooldown; a JWKS response `> 1 MiB` is rejected.
|
|
135
|
+
|
|
136
|
+
### 6.4 All satellites
|
|
137
|
+
|
|
138
|
+
- Each publishes with provenance + sigstore attestation, verified post-release like 0.7.
|
|
139
|
+
|
|
140
|
+
## 7. Suggested PR breakdown (stacked)
|
|
141
|
+
|
|
142
|
+
1. **Workspaces conversion** (no new published package): root `workspaces: [".", "satellites/*"]`, bump root to **0.8.0**, move `crypto-kms` to `satellites/crypto-kms/` with `peer + dev` core deps, switch the inlined `canonicalize` to `haechi/crypto` (+ parity test), repoint tests, commit the regenerated `package-lock.json`, add the **no-leak + zero-dep CI gate** (with negative tests), root CI runs all workspace tests via root `node --test`. → §6.1
|
|
143
|
+
2. **`haechi-crypto-kms`:** real AWS KMS client (satellite-only `@aws-sdk/client-kms` dep) + faithful mocked-AWS conformance CI + `publishConfig` + prefixed-tag publish workflow (strict regex guard) + Trusted Publisher bootstrap. → §6.2
|
|
144
|
+
3. **`haechi-auth-jwt`:** JWKS verification provider implementing the full §2.4 security spec + identity mapping + the §6.3 security-gate tests + `publishConfig` + prefixed-tag publish workflow. → §6.3
|
|
145
|
+
4. **0.8.0 release cut:** docs EN/KO, packaging/roadmap/risk-register (move OIDC+dashboard to 0.9)/api-stability, wiki, npm org / Trusted Publisher runbook (mapping table + bootstrap order + failure modes).
|
|
@@ -38,14 +38,68 @@ 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
42
|
|
|
43
|
-
|
|
44
|
-
|---|---|
|
|
45
|
-
| `.github/workflows/ci.yml` | test, release preflight, SBOM artifact |
|
|
46
|
-
| `.github/workflows/npm-publish.yml` | GitHub release published 이벤트에서 npm publish (trusted publishing 구성 후 provenance 경로) |
|
|
43
|
+
**암호학적** 신뢰 앵커는 **npm provenance 증명**(레지스트리 아티팩트)과 **sigstore 증명**(release tarball)이며, 둘 다 GitHub OIDC로 아티팩트를 이 repo의 release workflow 신원에 묶는다. `SHA256SUMS`는 오프라인 체크섬(`sha256sum -c`)을 위한 **도구 호환 편의 수단**이고, 같은 workflow가 생성·업로드하므로 그 자체로는 신뢰 앵커가 아니다. provenance에 더해, publish workflow는 다운로드한 tarball을 설치 전에 검증할 수 있도록 다음 자산을 첨부한다.
|
|
47
44
|
|
|
48
|
-
|
|
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
|
|
65
|
+
|
|
66
|
+
| Workflow | 배포 대상 | 트리거 태그 | 목적 |
|
|
67
|
+
|---|---|---|---|
|
|
68
|
+
| `.github/workflows/ci.yml` | — | 모든 push/PR | test, release preflight, SBOM artifact |
|
|
69
|
+
| `.github/workflows/npm-publish.yml` | `haechi` | `v<semver>` | npm provenance publish + 체크섬/증명 release 자산 |
|
|
70
|
+
| `.github/workflows/crypto-kms-publish.yml` | `haechi-crypto-kms` | `crypto-kms-v<semver>` | satellite publish, 동일한 서명 아티팩트 경로 |
|
|
71
|
+
| `.github/workflows/auth-jwt-publish.yml` | `haechi-auth-jwt` | `auth-jwt-v<semver>` | satellite publish, 동일한 서명 아티팩트 경로 |
|
|
72
|
+
|
|
73
|
+
각 publish 워크플로는 `release: published`에서 트리거되지만 **가드**되어 둘이 교차 발화하지 않는다: core job은 `v`로 시작하는 태그에서만 실행되고(그리고 `^v[0-9]+\.[0-9]+\.[0-9]+$` 재검증), satellite job은 `crypto-kms-v…`에서만 실행된다(그리고 `^crypto-kms-v[0-9]+\.[0-9]+\.[0-9]+$` 재검증 **및** 태그 버전이 satellite `package.json` 버전과 일치하는지 검증). npmjs.com Trusted Publisher는 각 패키지의 **특정 워크플로 파일명**에 바인딩된다 — 워크플로 파일 rename은 npm 설정을 갱신할 때까지 OIDC publish를 깨뜨린다.
|
|
74
|
+
|
|
75
|
+
## 5. Satellite 패키지 (unscoped `haechi-*`)
|
|
76
|
+
|
|
77
|
+
Satellite는 npm workspaces 모노레포의 `satellites/*`에 살며 core와 **독립적으로** 발행된다(자체 semver; satellite patch가 `haechi`를 bump하지 않음). core와 동일한 서명 아티팩트 경로를 재사용한다(pack → checksum → sigstore attest → OIDC publish → upload). **unscoped** `haechi-*` 이름으로 발행하므로(`@haechi` org/scope는 제3자가 점유) **npm org가 필요 없다**.
|
|
78
|
+
|
|
79
|
+
**satellite별 부트스트랩 순서(첫 발행, org 불필요):**
|
|
80
|
+
|
|
81
|
+
1. npmjs.com에서 (아직 미발행) unscoped 이름(예: `haechi-crypto-kms`)에 **Trusted Publisher 설정**: `raeseoklee/haechi` 저장소와 satellite의 **정확한 워크플로 파일명**(예: `crypto-kms-publish.yml`) 연결. npm은 아직 발행 전인 이름에도 Trusted Publisher 설정을 허용한다.
|
|
82
|
+
2. 접두사 태그를 push하고 GitHub Release 발행(예: `crypto-kms-v0.1.0`) → 워크플로의 OIDC publish가 provenance와 함께 `0.1.0`을 생성하고 첫 발행 시 이름을 확보.
|
|
83
|
+
|
|
84
|
+
노트북에서의 수동 `npm publish`는 필요 없다. 이름이 unscoped이고 비어있으므로 org-membership 선행 요건이 없다.
|
|
85
|
+
|
|
86
|
+
**태그 → 워크플로 → 패키지 매핑:**
|
|
87
|
+
|
|
88
|
+
| 패키지 | 태그 패턴 | 워크플로 파일 | npm 버전 소스 |
|
|
89
|
+
|---|---|---|---|
|
|
90
|
+
| `haechi-crypto-kms` | `crypto-kms-v<semver>` | `crypto-kms-publish.yml` | `satellites/crypto-kms/package.json` |
|
|
91
|
+
| `haechi-auth-jwt` | `auth-jwt-v<semver>` | `auth-jwt-publish.yml` | `satellites/auth-jwt/package.json` |
|
|
92
|
+
|
|
93
|
+
**satellite 릴리스 검증** (core와 동일한 신뢰 앵커):
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
gh attestation verify haechi-crypto-kms-<version>.tgz --repo raeseoklee/haechi
|
|
97
|
+
npm view haechi-crypto-kms --json # dist.attestations 존재 확인; access "public"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**의존성 노트:** `haechi-crypto-kms`는 core를 zero-dependency로 유지한다 — `@aws-sdk/client-kms`는 **optional peer dependency**이며, 실제 AWS 클라이언트를 쓰고 주입하지 않을 때만 lazy import된다. in-memory 또는 주입형 클라이언트를 쓰는 소비자는 SDK를 설치하지 않는다.
|
|
101
|
+
|
|
102
|
+
## 6. 배포 차단 조건
|
|
49
103
|
|
|
50
104
|
다음 중 하나라도 실패하면 npm publish를 하지 않는다.
|
|
51
105
|
|
|
@@ -38,14 +38,68 @@ 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
42
|
|
|
43
|
-
|
|
44
|
-
|---|---|
|
|
45
|
-
| `.github/workflows/ci.yml` | Tests, release preflight, SBOM artifact |
|
|
46
|
-
| `.github/workflows/npm-publish.yml` | npm publish on GitHub release published event (provenance path once trusted publishing is configured) |
|
|
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:
|
|
47
44
|
|
|
48
|
-
|
|
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
|
|
65
|
+
|
|
66
|
+
| Workflow | Publishes | Fires on tag | Purpose |
|
|
67
|
+
|---|---|---|---|
|
|
68
|
+
| `.github/workflows/ci.yml` | — | any push/PR | Tests, release preflight, SBOM artifact |
|
|
69
|
+
| `.github/workflows/npm-publish.yml` | `haechi` | `v<semver>` | npm provenance publish + checksummed/attested release assets |
|
|
70
|
+
| `.github/workflows/crypto-kms-publish.yml` | `haechi-crypto-kms` | `crypto-kms-v<semver>` | satellite publish, same signed-artifacts path |
|
|
71
|
+
| `.github/workflows/auth-jwt-publish.yml` | `haechi-auth-jwt` | `auth-jwt-v<semver>` | satellite publish, same signed-artifacts path |
|
|
72
|
+
|
|
73
|
+
Each publish workflow triggers on `release: published` but is **guarded** so the two never cross-fire: the core job runs only for tags starting `v` (and re-validates `^v[0-9]+\.[0-9]+\.[0-9]+$`); the satellite job runs only for `crypto-kms-v…` (and re-validates `^crypto-kms-v[0-9]+\.[0-9]+\.[0-9]+$` **and** that the tag version equals the satellite's `package.json` version). The npmjs.com Trusted Publisher for each package is bound to its **specific workflow filename** — renaming a workflow file breaks its OIDC publish until the npm config is updated.
|
|
74
|
+
|
|
75
|
+
## 5. Satellite packages (unscoped `haechi-*`)
|
|
76
|
+
|
|
77
|
+
Satellites live under `satellites/*` in the npm workspaces monorepo and publish **independently** of core (their own semver; a satellite patch never bumps `haechi`). They reuse the exact signed-artifacts path as core (pack → checksum → sigstore attest → OIDC publish → upload). They are published as **unscoped** `haechi-*` names (the `@haechi` org/scope is taken by a third party), so **no npm org is required**.
|
|
78
|
+
|
|
79
|
+
**Per-satellite bootstrap order (first publish, no org needed):**
|
|
80
|
+
|
|
81
|
+
1. On npmjs.com, **configure a Trusted Publisher** for the (not-yet-published) unscoped name (e.g. `haechi-crypto-kms`): link the `raeseoklee/haechi` repository and the satellite's **exact workflow filename** (e.g. `crypto-kms-publish.yml`). npm allows configuring a Trusted Publisher for a name you have not published yet.
|
|
82
|
+
2. Push the prefixed tag and publish a GitHub Release (e.g. `crypto-kms-v0.1.0`) → the workflow's OIDC publish creates `0.1.0` with provenance and claims the name on first publish.
|
|
83
|
+
|
|
84
|
+
No manual `npm publish` from a laptop is needed. Because the names are unscoped and free, there is no org-membership prerequisite.
|
|
85
|
+
|
|
86
|
+
**Tag → workflow → package mapping:**
|
|
87
|
+
|
|
88
|
+
| Package | Tag pattern | Workflow file | npm version source |
|
|
89
|
+
|---|---|---|---|
|
|
90
|
+
| `haechi-crypto-kms` | `crypto-kms-v<semver>` | `crypto-kms-publish.yml` | `satellites/crypto-kms/package.json` |
|
|
91
|
+
| `haechi-auth-jwt` | `auth-jwt-v<semver>` | `auth-jwt-publish.yml` | `satellites/auth-jwt/package.json` |
|
|
92
|
+
|
|
93
|
+
**Verify a satellite release** (same anchors as core):
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
gh attestation verify haechi-crypto-kms-<version>.tgz --repo raeseoklee/haechi
|
|
97
|
+
npm view haechi-crypto-kms --json # dist.attestations present; access "public"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Dependency note:** `haechi-crypto-kms` keeps core zero-dependency — `@aws-sdk/client-kms` is an **optional peer dependency**, imported lazily only when a real AWS client is used and not injected. Consumers who use the in-memory or an injected client never install the SDK.
|
|
101
|
+
|
|
102
|
+
## 6. Deployment block conditions
|
|
49
103
|
|
|
50
104
|
npm publish is not performed if any of the following fail.
|
|
51
105
|
|
|
@@ -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. 현재 판단
|
|
@@ -129,7 +129,9 @@ base64/인코딩 값 디코딩 검사, query string 검사, audit tail truncatio
|
|
|
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
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 |
|
|
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
|
+
| 0.8.0 (구현 완료; 발행 대기) | ecosystem foundation + satellites | 2026-06-10 구현(PR #27–#29): npm workspaces 모노레포(루트 자기참조 `["."]` + `satellites/*`), `haechi-crypto-kms`(실제 AWS KMS 클라이언트, AWS SDK는 optional peer)와 `haechi-auth-jwt`(헤드리스 JWKS bearer 검증), 각각 provenance-attest 발행 워크플로 보유. core는 zero runtime dependency 유지(CI no-leak + zero-dep + satellite-packaging 게이트). **발행은 운영자가 `@haechi` npm org + 위성별 Trusted Publisher를 생성**해야 진행된다(`docs/current/release-process.md` §5); core `haechi@0.8.0`은 기존 TP로 발행 가능. `docs/current/release-0.8-implementation-scope.md` 참조 |
|
|
134
|
+
| 0.9.0 | observability + interactive auth | `haechi-auth-oidc` 전체 authorization-code flow, `haechi-dashboard` 읽기 전용 audit 뷰어(hash-chain 무결성 표시, 요약/검색/타임라인), `haechi-crypto-kms` 추가 백엔드(Vault/GCP/Azure) |
|
|
133
135
|
| 1.0.0 | stable API contract | migration policy, long-term audit schema, plugin sandbox/runtime conformance 및 allowlist/manifest 통과 외부 auth/classifier package 동적 로딩 |
|
|
134
136
|
|
|
135
137
|
동적 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
|
|
@@ -129,8 +129,9 @@ All checklist items below were completed for 0.3.2 on 2026-06-10 except the prov
|
|
|
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
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
|
|
133
|
-
| 0.
|
|
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 (implemented; publish pending) | Ecosystem foundation + satellites | Implemented 2026-06-10 (PRs #27–#29): npm workspaces monorepo (root self-member `["."]` + `satellites/*`), `haechi-crypto-kms` (real AWS KMS client, AWS SDK as optional peer) and `haechi-auth-jwt` (headless JWKS bearer verification), each with its own provenance-attested publish workflow. Core stays zero runtime dependency (CI no-leak + zero-dep + satellite-packaging gates). **Publish is gated on the operator creating the `@haechi` npm org + per-satellite Trusted Publishers** (`docs/current/release-process.md` §5); core `haechi@0.8.0` can publish on the existing TP. See `docs/current/release-0.8-implementation-scope.md` |
|
|
134
|
+
| 0.9.0 | Observability + interactive auth | `haechi-auth-oidc` full authorization-code flow, `haechi-dashboard` read-only audit viewer (hash-chain integrity display, summary/search/timeline), additional `haechi-crypto-kms` backends (Vault/GCP/Azure) |
|
|
134
135
|
| 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 |
|
|
135
136
|
|
|
136
137
|
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
|
|
|
@@ -48,6 +48,9 @@ Haechi가 보호하려는 주요 자산은 다음이다.
|
|
|
48
48
|
| signing/encryption 키 혼용 | key separation 위반 | policy bundle 서명 키를 domain-separated 파생 키로 분리 |
|
|
49
49
|
| JSON number/object key 은닉 | 카드번호 등 비문자열 leaf 미탐지 | number leaf와 object key도 detection/transform 대상 |
|
|
50
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) |
|
|
51
54
|
| audit에 원시 credentials/identity 노출 | audit 로그를 통한 token 또는 subject 유출 | Token은 keyed-HMAC 해시로만 저장; identity subject/issuer는 keyed HMAC 처리; `auth_denied` 레코드에 token 미포함 |
|
|
52
55
|
| token round-trip의 타 토큰 복원 | 클라이언트/요청 간 평문 복구 | detokenization은 opt-in(`detokenizeResponses`)이며 요청 스코프: 같은 요청을 보호하며 발급된 토큰만 복원 |
|
|
53
56
|
| tool result/응답 내 간접 prompt injection | 심어진 지시문에 의한 agent 조작 | 응답 방향 휴리스틱, 기본 report-only(`injection` action `allow`), 격상은 명시적 정책 선택. 완전 방어 아님 |
|
|
@@ -66,7 +69,7 @@ Haechi가 보호하려는 주요 자산은 다음이다.
|
|
|
66
69
|
- 외부 MCP server의 OAuth/resource binding 검증
|
|
67
70
|
- base64/URL-encoded 값, 유니코드 난독화 값의 디코딩 후 검사
|
|
68
71
|
- URL query string 내 민감값 검사 (JSON body만 검사)
|
|
69
|
-
-
|
|
72
|
+
- 마지막 anchor 이후의 audit tail truncation — `audit.anchor`(0.7)는 anchor가 추가 전용/별도 미디어에 있을 때 마지막 anchor까지의 레코드 삭제를 탐지한다; 마지막 anchor 이후 기록된 레코드와 동일 파일시스템 anchor는 대상에서 제외된다
|
|
70
73
|
- JSON-RPC batch 메시지 처리 (MCP stdio filter는 batch를 fail-closed로 거부)
|
|
71
74
|
|
|
72
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
|
|
|
@@ -48,6 +48,9 @@ The primary assets Haechi protects are:
|
|
|
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
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) |
|
|
51
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 |
|
|
52
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 |
|
|
53
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 |
|
|
@@ -66,7 +69,7 @@ The primary assets Haechi protects are:
|
|
|
66
69
|
- OAuth/resource binding validation for external MCP servers
|
|
67
70
|
- Inspection of base64/URL-encoded values or unicode-obfuscated values after decoding
|
|
68
71
|
- Detection of sensitive values in URL query strings (JSON body only)
|
|
69
|
-
- 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
|
|
70
73
|
- JSON-RPC batch message processing (the MCP stdio filter rejects batches fail-closed)
|
|
71
74
|
|
|
72
75
|
## 5. Remaining Operational Assumptions
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# `haechi-crypto-kms` moved
|
|
2
|
+
|
|
3
|
+
The KMS-backed `cryptoProvider` reference that used to live here has been **promoted to a published satellite**. It now lives in the monorepo at [`satellites/crypto-kms/`](../../satellites/crypto-kms/) and is published as **`haechi-crypto-kms`**.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install haechi-crypto-kms
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
import { createKmsCryptoProvider, createInMemoryKms } from "haechi-crypto-kms";
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
See [`satellites/crypto-kms/README.md`](../../satellites/crypto-kms/README.md) for usage. Core (`haechi`) stays zero-runtime-dependency; the satellite carries any KMS-client dependency itself.
|
|
@@ -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",
|
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "haechi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Experimental developer preview for self-hosted AI context enforcement across LLM, MCP, vLLM, Ollama, and agent traffic.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
|
+
"workspaces": [
|
|
8
|
+
".",
|
|
9
|
+
"satellites/*"
|
|
10
|
+
],
|
|
7
11
|
"repository": {
|
|
8
12
|
"type": "git",
|
|
9
13
|
"url": "git+https://github.com/raeseoklee/haechi.git"
|
|
@@ -55,6 +59,7 @@
|
|
|
55
59
|
"haechi.config.example.json",
|
|
56
60
|
"packages/",
|
|
57
61
|
"examples/",
|
|
62
|
+
"scripts/release-checksums.mjs",
|
|
58
63
|
"docs/current/"
|
|
59
64
|
],
|
|
60
65
|
"scripts": {
|
|
@@ -62,7 +67,10 @@
|
|
|
62
67
|
"check:types": "tsc -p jsconfig.json --noEmit",
|
|
63
68
|
"pack:dry": "npm pack --dry-run",
|
|
64
69
|
"scan:stale-names": "node scripts/stale-name-scan.mjs",
|
|
70
|
+
"check:packaging": "node scripts/check-core-packaging.mjs",
|
|
71
|
+
"check:satellite-packaging": "node scripts/check-satellite-packaging.mjs",
|
|
65
72
|
"sbom": "node scripts/generate-sbom.mjs",
|
|
73
|
+
"checksums": "node scripts/release-checksums.mjs",
|
|
66
74
|
"bench:payload": "node scripts/bench-payload.mjs",
|
|
67
75
|
"release:preflight": "node scripts/release-preflight.mjs",
|
|
68
76
|
"release:preflight:npm": "node scripts/release-preflight.mjs --require-npm-auth",
|