9 Commits
Author SHA1 Message Date
2707692bc8 fix(kms): require explicit env on secret writes (no silent default) (#13)
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>
2026-07-08 07:21:43 -07:00
b54c590a1e fix(deps): luxfi/keys@v1.1.0 go.sum = current proxy/sumdb hash (re-tag) (#12)
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>
2026-07-04 11:51:03 -07:00
9b0d48d836 fix(auth): kid-first JWT verification, drop per-attempt debug logging (#11)
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>
2026-07-04 11:50:16 -07:00
ec13c353fb fix(auth): kid-first JWT verification, drop per-attempt debug logging (#10)
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>
2026-07-04 11:28:28 -07:00
343872f5da feat: native /v1/sdk enveloped secrets + threshold-sign plane (red-approved) (#9)
* 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>
2026-07-03 10:37:07 -07:00
f9d4832cdf docs(LLM): finished web UI reality + canonical image (#7)
* 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>
2026-07-01 10:35:21 -07:00
z 888d5f5a78 docs(brand): add hero banner 2026-06-28 20:36:07 -07:00
z 91a16411a9 chore(brand): dynamic hero banner 2026-06-28 20:36:06 -07:00
1cffefaa57 build: fix keys@v1.1.0 go.sum to authoritative direct hash (unblocks KMS build) (#8)
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>
2026-06-28 13:48:55 -07:00