Every call in this SDK went to Infisical routes -- /api/v3/secrets/raw,
/api/v1/folders, /api/v2/workspace, /api/v1/dynamic-secrets, /api/v1/kms/keys.
kms.hanzo.ai runs luxfi/kms, which has NEVER served any of them.
The break stayed invisible because older server builds embedded a console SPA
under a root catch-all that answered every unmatched path with 200 text/html:
a wrong URL came back as a JSON decode error deep in the caller, not as a 404,
so it read like a parsing bug rather than a nonexistent endpoint. Server 1.12.8
removed the catch-all. Those paths now return honest JSON 404s -- verified live,
{"message":"not found","path":"/api/v3/secrets/raw"} -- so the SDK is visibly
broken, and this is the fix.
The canonical surface is the WHOLE secrets API; there is nothing else:
POST /v1/kms/auth/login {clientId, clientSecret}
GET /v1/kms/orgs/{org}/secrets?path=&env= -> {"names": [...]}
GET /v1/kms/orgs/{org}/secrets/{path}/{name}?env= -> {"secret":{"value"}}
POST /v1/kms/orgs/{org}/secrets {path, name, env, value}
DELETE /v1/kms/orgs/{org}/secrets/{path}/{name}?env=
So the SDK is now shaped like the server instead of like the fork it came from:
- One upsert. createSecret + updateSecret collapse into put(): the server has a
single POST for both, and aiming two methods at one route would misdescribe
what the store does.
- No versions. get() throws on a `version` option. Silently serving the current
value for a versioned read hands the caller a different secret than it asked
for.
- No folders / projects / environments / dynamic-secrets / tags / PITR. Those
routes map to nothing, so the clients, types and schemas are deleted rather
than left pointing at 404s. AWS-IAM login and token-renew go with them,
taking the entire AWS SigV4 dependency stack; zod went with the dynamic-secret
schemas. axios is the only runtime dependency left.
- `org` is a new required input -- constructor `org`, else KMS_ORG, else
"hanzo". Every secret route is org-scoped and the bearer's IAM owner claim
must resolve to it.
- /v1/kms/keys/* is deliberately NOT bound. It exists, but it is MPC validator
key-set management (generate/list/get/sign/rotate) -- a different contract
from the Infisical KMS-keys surface this SDK wrapped. kms() is gone rather
than guessed at.
Escaping: the server unescapes the trailing path and splits it at its LAST
slash into (path, name), so each segment is escaped individually and joined
with literal separators; encodeURIComponent over the joined string collapses
"a/b/C" into one opaque %2F-laden segment. For the same reason a name
containing "/" and an empty path are refused at the boundary -- POST would
accept both (they travel in the JSON body) and store a secret that GET and
DELETE could never address again.
Tests: a node:test contract suite (no new deps) drives the SDK over a real
socket against a stub that mirrors the Go handlers -- last-slash split,
env-required write, JSON 404s. Two guards keep the old paths from coming back:
no recorded request URL may contain "/api/", and no file in lib/ may either.
Proven by mutation: restoring /api/v3/secrets/raw fails 12 of 14 tests, and
escaping the joined path string fails 4.
4.0.6 -> 4.0.7.
Co-authored-by: Hanzo Dev <[email protected]>
3.8 KiB
LLM.md - Hanzo KMS Node SDK
Overview
TypeScript client for Hanzo KMS (kms.hanzo.ai), which runs luxfi/kms. The SDK's whole job is the org-scoped secret surface; nothing else is exposed because nothing else exists on the server.
The server surface — all of it
POST /v1/kms/auth/login {clientId, clientSecret} -> {accessToken, expiresIn}
GET /v1/kms/orgs/{org}/secrets?path=&env= -> {"names": [...]}
GET /v1/kms/orgs/{org}/secrets/{path}/{name}?env= -> {"secret": {"value": "..."}}
POST /v1/kms/orgs/{org}/secrets {path, name, env, value}
DELETE /v1/kms/orgs/{org}/secrets/{path}/{name}?env=
Bearer on everything except login. env defaults to "default" on read and is
required on write (the server 400s without it — a silent default would split
the write from the record readers resolve).
Source of truth: cmd/kms/main.go in ~/work/lux/kms (registerSecretRoutes,
putSecretHandler).
Why the rewrite happened (4.0.7)
Every call before 4.0.7 went to Infisical paths (/api/v3/secrets/raw,
/api/v1/folders, /api/v2/workspace, …). luxfi/kms has never served
those. The break was invisible because older server builds embedded a console
SPA under a root catch-all that answered every unmatched path with
200 text/html, so a wrong URL surfaced as a JSON decode error rather than a
404. Server 1.12.8 removed the catch-all; those paths now return honest JSON
404s ({"message":"not found","path":"…"}) and the breakage became visible.
Model — three keys, no more
A secret is (path, name, env) within an org. There are no projects, folders,
tags, versions or PITR, so the SDK has no surface for them:
- One upsert.
put()is create and update; the server has a single POST. - No versions.
get()throws on aversionoption instead of silently returning the current value. orgis required input. Constructororg, elseKMS_ORG, elsehanzo.
The escaping rule
The server splits the trailing path at its last slash into (path, name),
after unescaping. So:
- escape each segment with
encodeURIComponentindividually, join with literal/(secretUrlinsrc/secrets.ts); - a name containing
/is rejected — POST would accept it in the JSON body and store a secret GET could never address; - an empty path is rejected — the store interpolates the path into its key
verbatim (
kms/secrets/{path}/{env}/{name}), and an empty one produces a key that HTTP reads cannot reach.
Structure
src/index.ts HanzoKmsSDK — config, org resolution, re-exports
src/http.ts axios instance: base URL, bearer, retry/backoff
src/auth.ts login / accessToken / getAccessToken
src/secrets.ts list / get / put / delete + URL building and boundary checks
src/errors.ts HanzoKmsSDKError, HanzoKmsSDKRequestError, newKmsError
src/types.ts public options + wire shapes
test/kms.test.js node:test contract suite against a stub luxfi/kms
Build & test
npm install
npm test # builds, then runs node --test against the build output
The suite drives the SDK over a real socket against a stub that mirrors the Go
handlers (last-slash split, env-required write, JSON 404s). Two guards keep the
Infisical paths from coming back: no recorded request URL may contain /api/,
and no file in lib/ may contain /api/.
Gotchas
- Runtime deps are
axiosonly. The AWS SigV4 stack went with the deleted/api/v1/auth/aws-auth/loginroute;zodwent with the dynamic-secret schemas. /v1/kms/keys/*exists on the server but is MPC validator key-set management (generate/list/get/sign/rotate) — a different contract from the Infisical KMS keys surface this SDK used to wrap, so it is not bound here.- The repo lives at
hanzokms/node-sdk;hanzoai/kms-node-sdkdoes not exist.