zeekayandHanzo Dev ebe77a1fe3 authz: platform authority is a membership, and this gate was reading a position
`hanzo status` could not see the cloud because the token it carries could not
pass the gate. The reason was not the token.

SanitizeIdentity granted platform sudo on `claims.homeOrg() == adminOrg`, which
is `Claims.Orgs[0].Org` — a POSITIONAL read. IAM's MemberOrgRefs always writes
the user's own org at index 0 and appends every granted membership after it, so
that test could only ever be true for someone whose USER ROW lives in the admin
org. An operator anchored in a brand org and GRANTED admin-org membership — the
deliberate, signed, revocable way operators are actually made — was structurally
unreachable by it. z@hanzo.ai carries orgs:[{hanzo,admin},{admin,admin},
{lux,admin},{pars,admin},{zoo,admin}] and was refused every superAdminOf surface
because `admin` sits at index 1.

This widens nothing. The authority was already signed by IAM and already guarded
on the write side: memberships.mayGrant refuses to create a membership into a
reserved org unless the caller is already a SuperAdmin, on the stated grounds
that it "seeds admin-org (SuperAdmin) tenancy". IAM protects the grant as
platform authority; this gate now honors it as platform authority. The two
agreeing is the fix.

Both admin scopes now ask the predicates hanzoai/authz publishes — the issuer's
own statement of what its claims mean — narrowed by the one denial only cloud can
make (the per-org KMS-sync machine, named by its owner-bound audience; authz
decides machine-ness from an empty membership set, which a machine carrying
memberships would defeat). cloud's private re-derivations are deleted rather than
kept beside them, because two readings of one claim is the condition that package
exists to end.

The org-admin bit was the same defect one scope down: `claims.IsAdmin ||
isOrgAdmin(...)` is an UNSCOPED disjunct, so a token carrying the bit would have
been org-admin in whatever org it switched INTO. authz.Claims.OrgAdmin scopes it
to the home org. The term is inert against IAM today, which is exactly why it
could sit there reading wrong — a dead term cannot fail a test. Closes the
standing "scope the legacy isAdmin bit to home org" item by adopting the
published predicate rather than patching the local one.

There is no isAdmin CLAIM in any of this, in either direction. IAM mints one into
NEITHER token: internal/oidc/jwt.go's Claims struct has no such field, and
(*Signer).claims is the single place an Identity becomes a claim set, so the
access token and the id_token differ only in aud/tokenType/nonce. The bit exists
only as a user-row column that userinfo and whoami report in a response body.
Copying it into the access token would have changed nothing, because no gate
reads it.

The adminOrg parameter is gone. The reserved org is the ISSUER's constant — IAM
hardcodes `owner == "admin"` — so a consumer-side knob could only ever let cloud
disagree with the contract it is reading. This file's own test doc asserted
"Hanzo pins it to hanzo", which, had anyone set IAM_ADMIN_ORG that way, would
have handed platform sudo to every member of the hanzo org while IAM considered
none of them a SuperAdmin. Production never set it, so the default carried the
truth by luck.

TestPlatformSudoIsMembershipNotPosition pins the fact end to end through real
JWKS-validated tokens, including z's live membership set verbatim. It fails
against the positional predicate and passes against the set one; the negative
cases (admin of every brand org but no reserved membership, a look-alike "Admin",
an empty set) pass under both, which is how the change is shown to widen nothing.

Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-08-01 12:46:31 -07:00
2026-08-01 11:59:58 -07:00

Hanzo Cloud

Hanzo Cloud

The Open AI Cloud as one Go binary. Identity, secrets, data, AI, gateway, observability, and the console — every Hanzo-native subsystem mounted into a single multi-org process. Per HIP-0106.

Status License

The same artifact serves api.hanzo.ai, api.lux.cloud, api.zoo.cloud, api.osage.cloud, and every white-label reseller. Brand, enabled subsystems, and org scope are deployment configuration — one binary, one origin, no sidecars.

Quick start

# Run the unified binary (pin a released version)
docker run -p 8080:8080 ghcr.io/hanzoai/cloud:v1.801.206

# The `hanzo` fabric CLI is a separate Rust binary (hanzoai/cli); this module
# builds only the control half of that name, cmd/hanzo (see below)
curl hanzo.sh | sh
brew install hanzoai/tap/hanzo

Open http://localhost:8080 for the embedded console; the API is served under /v1 on the same origin.

What this is

hanzoai/cloud is one Go binary that mounts every Hanzo subsystem (iam, kms, base, gateway, ai, commerce, vfs, mq, dns, amqp, mcp, o11y, tasks, …) into a single multi-org process. The same artifact serves api.hanzo.ai, api.osage.cloud, api.lux.cloud, api.zoo.cloud, and every white-label reseller. Brand, enabled subsystems, and org scope are deployment configuration.

hanzo — cloud control CLI

cmd/hanzo is the client-only control binary: a thin client over Hanzo IAM (hanzo.id), the platform control plane (platform.hanzo.ai/v1) and the cloud /v1 API, inventing no parallel API. It cannot serve a subsystem — that is cmd/cloud's job.

The first token selects who runs it. The verbs below run here; every other verb — code, node, dev, wallet, … — is handed to the Rust fabric CLI installed alongside as hanzo-node, so the single hanzo name is a superset of both. cli.IsControlVerb draws that line off the cobra command tree itself, so the two halves cannot drift apart.

hanzo login                       # IAM password grant against hanzo.id → token in ~/.hanzo (0600)
hanzo whoami                      # identity from the stored token (--verify hits IAM userinfo)
hanzo apps list                   # platform apps board: declared/running/latest tag + drift + health
hanzo apps get <org>/<app>/<env>  # one app row
hanzo deploy <container> --project <p> --env <e>   # rolling, zero-downtime redeploy
hanzo clusters list|get|create|select|target       # dedicated DOKS cluster lifecycle
hanzo build <repo> --sha <sha> --image <img>       # platform-native (arcd/Kaniko) build, no GitHub builders
hanzo config set <k> <v>          # ~/.hanzo/config preferences
hanzo completion bash|zsh|fish    # shell completion for every verb above

Global flags: --org, -o/--output table|json, --platform-url, --iam-issuer, --platform-token. Tokens resolve from flag → env → ~/.hanzo (never hardcoded): the IAM user token is the identity; the platform control plane is service-token authed (it cannot validate user tokens), so apps/deploy/clusters use --platform-token / HANZO_PLATFORM_TOKEN / PLATFORM_SERVICE_TOKEN, and build uses HANZO_BUILD_TOKEN / PLATFORM_BUILD_CALLBACK_TOKEN.

Install the fabric CLI: curl hanzo.sh | sh, or brew install hanzoai/tap/hanzo. It is the Rust binary in hanzoai/cli; this module serves /v1, ships plugins, and builds the control half above (go build ./cmd/hanzo).

Subsystems mounted

Each subsystem exposes func Mount(app *zip.App, deps cloud.Deps) error and wires its own /v1/<name>/* routes onto the shared app.

  • iam — identity & access (users, orgs, roles, OIDC/JWKS per HIP-0026)
  • base — per-org SQLite + in-process extension runtimes (HIP-0105)
  • kms — secret custody (sealed secrets, HIP-0027)
  • commerce — checkout, billing, pricing, invoicing (light router; NOT in PCI-DSS scope)
  • ai — AI control plane: inference, RAG, model hub, agents, MCP management
  • gateway — HTTP routing + policy
  • o11y — metrics / traces / logs
  • vfs — virtual filesystem / object-store abstraction
  • mq — message queue
  • dns, amqp, mcp, auto, tasks, … — full list per HIP-0106

Deployment modes

Same binary; different startup configuration:

cloud --enable=iam,base,kms,commerce,ai,gateway,o11y --brand=hanzo  --domain=hanzo.ai
cloud --enable=iam,base,kms,commerce,ai,gateway,o11y --brand=osage  --domain=osage.cloud
cloud --enable=iam,base,kms,commerce,ai,gateway,o11y --brand=lux    --domain=lux.cloud
cloud --enable=iam,base,kms,commerce,ai,gateway,o11y --brand=zoo    --domain=zoo.cloud

Architecture

                 api.{org}.{brand}
                          |
                   hanzoai/cloud (one Go binary)
                          |
   +----------+----------+----------+----------+----------+
   |    iam   |   base   |   kms    |    ai    | gateway  | ...
   |  Mount() |  Mount() |  Mount() |  Mount() |  Mount() |
   +----------+----------+----------+----------+----------+
   per-org SQLite (HIP-0302)   |   Hanzo IAM JWKS (HIP-0026)
   replicate -> S3 (HIP-0107)  |   ZAP inter-subsystem RPC

Every subsystem mounts through the same Mount(app, deps) seam. Cross-subsystem calls ride a narrow in-process interface; no subsystem reaches into another's store.

White-label fork pattern

Customers fork hanzoai/cloud to launch their own ecosystem in one binary. Brand detection, enabled subsystems, and ZAP endpoints (payments / vault backends) are all deployment configuration.

Web framework

hanzoai/zip — Sinatra-style Go web framework built on Fiber v3. The ONE Go web framework. No .Fast escape hatch.

Console UI — embedded in the ONE binary

The same hanzoai/cloud binary serves the console (@hanzo/gui) UI at the web root AND the /v1 API from one process — one artifact, one origin, no separate console Service. The UI is compiled in via //go:embed (see webui.go).

Pipeline (in the Dockerfile, before go build):

console stage  →  build console static bundle  →  /out
      COPY --from=console /out/ → src/webui/dist/     (overlays the fallback shell)
build stage    →  go build   →  //go:embed all:webui/dist bakes it into /cloud

Serving (webui.go, registered LAST in Serve so it never shadows the API):

  • GET / and any client-side route (/orgs, /models, …) → the SPA shell (index.html) with Cache-Control: no-cache; fingerprinted assets under assets//_next/ are served immutable for a year, with brotli/gzip precompressed negotiation when the build emits .br/.gz siblings.
  • GET /v1/* (and /zap, /healthz, …) → the API. Real subsystem routes are registered before the console catch-all, so they always win; an unmatched path under an API prefix returns a real 404 (JSON namespace), never HTML.
  • Same-origin: the embedded console calls /v1 on its own host, so the session cookie is first-party — no second origin, no CORS.

webui/dist/index.html is a committed fallback shell (a real same-origin /v1 bootstrap) so go build always compiles and the binary always serves a UI even without the Node toolchain. The image build overwrites webui/dist with the real console bundle. See webui_test.go for the boot-and-assert tests (/ → shell, deep link → shell 200, /v1/* → API, unmatched /v1 → 404).

The hanzoai/console build:embed script (scripts/build-embed.mjs) stashes its Next server route handlers (BFF proxies that collapse to the cloud /v1/* the SPA calls same-origin), wraps the client catch-all pages for output: 'export', neutralizes the root layout's request-time headers() read, and emits a real static export at out/ (a ~360 KB index.html + _next/ chunks). The image build (and make webui) run it and overlay webui/dist, so //go:embed bakes the FULL @hanzo/gui console into the ONE binary. The Dockerfile console stage FAILS HARD if that bundle is missing or degenerate — the placeholder shell can never silently ship to prod (escape hatch: --build-arg ALLOW_PLACEHOLDER=1 for a pure-Go dev image).

Specs

Implements:

  • HIP-0014 Application Deployment
  • HIP-0026 IAM
  • HIP-0027 KMS
  • HIP-0037 AI Cloud Platform
  • HIP-0105 In-Process Extension Runtime
  • HIP-0106 Unified Cloud Binary
  • HIP-0129 Open Cloud Planes
  • HIP-0302 Encrypted SQLite + ZapDB Durability

Status

In production. The unified binary serves api.hanzo.ai and the white-label cloud surfaces today, with per-org SQLite (HIP-0302) and the embedded console. Subsystems continue to land per HIP-0106's migration phases; apps/apps.go:Wire() is the one ordered list of everything mounted. For repo-level engineering doctrine (module graph, route-table projections, cross-subsystem seams), see LLM.md.

Hanzo — the Open AI Cloud

Open source · every language · on-chain settlement. hanzo.ai · docs.hanzo.ai

SDKs in every languagePython (flagship) · TypeScript · Go · Rust · C++ · Swift · Kotlin · umbrella

S
Description
No description provided
Readme Apache-2.0
400 MiB
2026-07-16 06:27:48 +00:00
Languages
Go 98.7%
Python 0.4%
Makefile 0.4%
JavaScript 0.2%
Shell 0.1%
Other 0.1%