POST /v1/kms/orgs/{org}/secrets defaulted a missing env to "default",
committing the write to a bucket that project/env/path readers (the
kms-operator, cluster syncs) never resolve. That split landed an IAM
z-password in env=default while prod kept serving the stale value. env is a
first-class component of the storage key (kms/secrets/{path}/{env}/{name})
and cannot be aliased, so a write with no env now fails loud (400). GET
keeps a backward-compatible default (a read can't plant a value another
reader trusts; legacy readers that omit env must keep working).
The POST handler is extracted to a named func (putSecretHandler) so the
contract is unit-testable. Regression tests: write without env -> 400;
write env=prod is stored under prod (sha256 round-trip, values never
printed) and is not visible in env=default.
Co-authored-by: hanzo-dev <dev@hanzo.ai>
The module was re-tagged; go.sum pinned the stale hash so every CI build
failed at go mod download (checksum mismatch / SECURITY ERROR). Refetched
clean — the new hash is sumdb-verified.
Co-authored-by: hanzo-dev <dev@hanzo.ai>
validateToken logged one line per JWKS key per request (a leftover DEBUG line
plus a per-failed-key line) — with multi-brand certs that produced a log line
per brand cert per request, ~4.4M lines/12h in production, and brute-forced
every key on every call. Now: verify against the key the token's kid header
names (single signature check in the common case), fall back across the
remaining keys only on rotation skew, and emit no per-attempt logs — the
returned error carries the diagnosis.
Co-authored-by: hanzo-dev <dev@hanzo.ai>
validateToken logged one line per JWKS key per request (a leftover DEBUG line
plus a per-failed-key line) — with multi-brand certs that produced a log line
per brand cert per request, ~4.4M lines/12h in production, and brute-forced
every key on every call. Now: verify against the key the token's kid header
names (single signature check in the common case), fall back across the
remaining keys only on rotation skew, and emit no per-attempt logs — the
returned error carries the diagnosis.
Co-authored-by: hanzo-dev <dev@hanzo.ai>
* test(zapserver): fix e2e seed/assert mismatch (secret-value vs sk_live_real)
TestConsensusE2E_ValidatorReadsSecret seeded "secret-value" but asserted
"sk_live_real" — the E2E read path was correct, the assertion constant
disagreed with the seed. Align the seed to the asserted live-secret value
so the green test actually guards the consensus read path.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
* feat(zapserver): expose enveloped secret+sign plane over HTTP /v1/sdk
Decomplect the verify->authorize->dispatch core so the ZAP wire and a new
HTTP /v1/sdk transport share ONE implementation (Server.dispatch). Add:
- POST /v1/sdk/secrets: single RPC endpoint. The op is read from the
SIGNED env.Op field, never the URL, so no framing can escalate a read
identity into a write. Maps status byte -> HTTP code; replay masked as
generic 403 'forbidden' (nonce ledger unprobeable); 4 MiB body cap.
- OpSign (0x0050, write/operator) + OpVerify (0x0051, read/validator):
deliberate, documented widening of the authorizer. Dispatch to a narrow
SignBackend that delegates to luxfi/mpc t-of-n; KMS holds no key
material. nil backend -> clear 'signing not configured'.
18 new httptest end-to-end tests: validator read / operator write splits,
replay/stale/tamper/oversize/malformed/unknown-op rejection, and sign
delegation gated BEFORE the backend (forbidden sign never reaches MPC).
All prior authz tests stay green.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
* feat(sdksign): keys.Manager -> zapserver.SignBackend adapter
Sign delegates to the MPC t-of-n cluster (KMS holds no full key). Verify
is a local public-key check: ed25519 (corona) via stdlib crypto/ed25519
(real, tested with a live keypair); secp256k1 (bls) returns a precise
ErrVerifyBLSDelegated — a documented capability boundary (secp256k1
verification is owned by the chain/precompile layer), not a fake.
Tests (CGO=0, no MPC daemon needed): sign routes to the correct wallet
per scheme, result propagates; ed25519 valid verifies + tampered
sig/message do NOT; bls verify hits the delegated boundary; unknown
validator errors.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
* fix(gomod): realign age@v1.5.0 + keys@v1.1.0 go.sum to re-tagged hashes
Both tags were force-moved (vendor/docs sync lineage, per LLM.md); go.sum
held the pre-re-tag zip hashes, so any GOWORK=off / clean-cache build
(CI) failed 'checksum mismatch' before compiling. Updated the two h1
lines to the authoritative direct-fetch hashes; the GOWORK=off build
re-verifies the downloaded bits against these, which is the proof they
are correct. Never bypassed the check.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
* feat(kms): mount /v1/sdk enveloped secrets+sign surface in kmsd
Construct the zapserver.Server once from the loaded REK and back BOTH
transports with ONE consensus authorizer + ONE nonce ledger: the HTTP
/v1/sdk surface (SDK-facing) and the in-cluster ZAP wire. The authorizer
and ledger are built whenever the REK is present (fail-closed: refuse to
boot without them), independent of ZAP_PORT — so /v1/sdk is available
even when the ZAP wire is disabled. Wire the MPC-backed sign/verify via
sdksign.New(mgr) when a vault is configured; nil otherwise.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
* docs(llm): document /v1/sdk enveloped secrets+sign surface
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
---------
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
* docs(LLM): state finished web UI reality + canonical image
* feat(oidc): derive KMS SSO client id+secret from brand (zero env, matches IAM)
loadOIDCConfig now DERIVES the confidential SSO client from KMS_IAM_OWNER:
client_id=<owner>-kms, client_secret=HMAC-SHA256("hanzo-kms-oidc/v1",<owner>-kms),
state_secret=HMAC("hanzo-kms-state/v1",<owner>-kms). Same derivation hanzoai/iam
init_apps.go uses, so IAM provisions and KMS consumes the identical secret with
ZERO coordination — no KMS_OIDC_CLIENT_SECRET/KMS_STATE_SECRET env, no stored
secret, no manual copy. Env still overrides each (escape hatch). Only the
non-secret IAM_ENDPOINT remains required. Closes the deterministic KMS-admin
loop: reboot IAM+KMS -> SSO works -> admins mint scoped identities.
Note: full repo build is blocked by the pre-existing luxfi/* go.sum re-publish
mismatch (luxfi/age, luxfi/keys); oidc.go itself is gofmt-clean and the
derivation is parity-verified against the IAM side.
---------
Co-authored-by: hanzo-dev <dev@hanzo.ai>
luxfi/keys v1.1.0 was re-published; go.sum still pinned the original D3s754…
zip hash → 'go mod download SECURITY ERROR' in CI/clean builds. Updated to the
authoritative direct-from-GitHub hash /3mQfl… (same fix applied to hanzoai/iam
this cycle). age@v1.5.0 already correct. Builds clean (CGO sqlcipher).
Co-authored-by: hanzo-dev <dev@hanzo.ai>