HIP-134: One Process, One Socket, One Identity. Status Active. Hanzo's own standard — read this before implementing against it.
Every Hanzo service is a plugin of the cloud binary (HIP-0106, HIP-0116). Plugins reach each other over ZAP on unix domain sockets. IAM is the only thing that reads a JWT, and the principal it establishes is what every other plugin uses.
That is the architecture. This HIP states it, and states its three consequences — each of which is a thing that does not exist:
TCP port to reach it on, so there is no wire to authenticate or encrypt.
cloud. There is no cloud-api.identity from headers, no edge process mints an identity-header set, and no gate in front of a plugin decides who may read what.
A plugin listens on exactly one address — its unix socket. The host hands it that address; the plugin does not choose a port.
{CLOUD_DATA_DIR}/run/<plugin>.sock # default /run/hanzo/<plugin>.sock
The wire is ZAP (HIP-0114, HIP-0120). A plugin's routes ARE its ZAP surface (HIP-0122), so there is no second registry of methods and no second serialization. zip selects the ZAP transport for a bare address, which is what a socket path is.
A caller resolves a peer by name, never by address:
cloud.Dial("treasury").As(c).Get(ctx, org, "/v1/treasury/reserve", &out)
Resolution happens per call, so a plugin that starts later is picked up without a restart. A missing socket is the answer "that plugin is not running here", returned as an error — never a zero value.
A plugin MUST NOT open a TCP listener. The only TCP listeners in a deployment are the edge HTTP listener, terminated by ingress, and the ops listener for health and metrics (HIP-0113).
Two facts do all the work.
The kernel proves the peer. A 0600 socket in the run directory is reachable only by processes on the same host running as the same user. There is no network path to it. This is the entire authentication of the internal plane: no credential to mint, distribute, rotate, or revoke, and nothing that can fail open.
IAM proves the human. The IAM plugin validates the JWT and establishes the principal — org, user, admin — once, in one place.
A call carries the principal IAM established. Because the socket bounds who may speak, carrying it is delegation rather than assertion: a caller passes on authority it already holds, and the callee still applies its own rules. There is no way to obtain a principal except from IAM.
Nothing sanitizes, re-derives, or re-validates a principal IAM established. A component that re-checks identity is a second implementation of IAM, and two implementations of "who is the caller" do not double the assurance — they create a disagreement, and the weaker one is the one an attacker uses.
The plugin that owns the data decides who may see it, reading the principal and filtering server-side on every query.
Org scope by default. A request for org X returns org X's rows. Not a UI filter, and not a query the client can widen.
SuperAdmin is the only cross-org scope, and it is ONE predicate everywhere: owner == "admin", membership of the reserved admin org. An org admin (isAdmin inside their own org) has no platform authority. Conflating the two is privilege escalation.
An edge gate may answer "is this a valid session" — that is authentication, relayed from IAM. A gate that decides which rows you may see has taken the plugin's job, and the plugin will never grow the ability to do it correctly.
The binary is cloud. A second Service name for the same pods is a second door with different properties.
One IAM application per guarded surface, named for the surface: hanzo-admin for admin.hanzo.ai, hanzo-platform for platform.hanzo.ai, hanzo-ci for ci.hanzo.ai. An application named for a component — a guard, a proxy — that fronts several surfaces makes them indistinguishable in the audit log and gives them a shared blast radius. Redirect URIs are the surface's own callback exactly; no wildcards.
Changing a surface's application is create-new → cut the surface over → verify login → retire old. An in-place rename invalidates every live token whose aud names the client.
Socket permissions are the perimeter. The run directory is writable only by the service user, sockets are 0600, and the run directory is never a shared volume another tenant's workload can mount. A world-readable socket is equivalent to an open unauthenticated port.
Delegation is not escalation. As() forwards the principal a caller already holds; the callee applies its own rules. Nothing in the internal plane grants authority a caller did not arrive with.
Shared build fabric is a separate boundary. Running another tenant's code — CI — on a runner that mounts a registry credential, shares a docker daemon, or reuses state across jobs is a tenancy violation regardless of the identity plane. A job running foreign code gets an ephemeral runner, no long-lived credential, and no shared daemon.
What an attacker must achieve. With the internal plane on sockets, a cross-tenant read requires code execution as the service user on the host — not a forged header, not a reachable port, not a stolen bearer token.
hanzoai/cloud dial.go — peer resolution by name, ZAP over the callee's socket, delegation via As, errors that name the transport they tried.
hanzoai/cloud serve.go listenOn — a plugin honours the host-providedsocket address and serves that alone.
hanzoai/zip transport.go — the scheme table: a bare address selects ZAP,which is why a plugin needs no transport configuration.
hanzoai/iam — the principal's single owner.