Files
python-sdk/pkg/hanzo-kms
zeekayandHanzo Dev ccc14a5dd2 kms.lux.cloud is the brand host; kms.lux.network is retired
Caught on a second pass: grep -I treats these as binary and skipped them, so the
first sweep reported clean while they still named a host that no longer resolves.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-05 08:12:55 -07:00
..
2026-03-23 10:26:07 -07:00

Hanzo KMS - Python SDK

Official Python SDK for Hanzo KMS — secret management for your applications.

The server is luxfi/kms; this SDK speaks its canonical /v1/kms surface and nothing else.

Installation

pip install hanzo-kms

Or with uv:

uv add hanzo-kms

Quick Start

A secret is identified by (org, path, name, env). The org scopes both the URL and the JWT; path, name and env identify the value.

from hanzo_kms import ClientSettings, KMSClient

client = KMSClient(ClientSettings(
    org="lux",
    client_id="your-client-id",
    client_secret="your-client-secret",
))

# List the secret names at a path
names = client.list_secrets("providers/lux", env="prod")

# Read one value
mnemonic = client.get_secret("providers/lux", "deploy-mnemonic", env="prod")

# Create or replace — one upsert; KMS holds one value per (path, name, env)
client.put_secret("providers/lux", "deploy-mnemonic", mnemonic, env="prod")

# Delete
client.delete_secret("providers/lux", "deploy-mnemonic", env="prod")

# Load a whole path into os.environ, keyed by secret name
client.inject_env("providers/lux", env="prod")

env defaults to "default".

Async

AsyncKMSClient is the mirror image of KMSClient — same methods, same arguments.

from hanzo_kms import AsyncKMSClient

async with AsyncKMSClient() as client:
    names = await client.list_secrets("providers/lux", env="prod")

Environment Variables

export HANZO_KMS_URL="https://kms.hanzo.ai"   # default
export HANZO_KMS_ORG="hanzo"                  # default
export HANZO_KMS_CLIENT_ID="your-client-id"
export HANZO_KMS_CLIENT_SECRET="your-client-secret"
# ...or a pre-issued IAM bearer token instead of client credentials:
export HANZO_KMS_TOKEN="eyJ..."

Then:

from hanzo_kms import KMSClient

client = KMSClient()  # configures itself from the environment

Authentication

Two ways, because the server has one login route:

Setting Behavior
client_id + client_secret Exchanged at POST /v1/kms/auth/login for a bearer token
access_token A pre-issued IAM bearer token, used as-is

The AWS / Azure / GCP / Kubernetes / SRP methods this SDK used to advertise were Infisical's. luxfi/kms has never served them.

CLI

hanzo-kms list --path providers/lux --env prod
hanzo-kms get providers/lux deploy-mnemonic --env prod
hanzo-kms set providers/lux deploy-mnemonic "word word ..." --env prod
hanzo-kms delete providers/lux deploy-mnemonic --env prod
hanzo-kms export --path providers/lux --env prod --format json

API Reference

KMSClient

Method Description
list_secrets(path="", env="default") Secret names at a path
get_secret(path, name, env="default") The secret's value
put_secret(path, name, value, env="default") Create or replace
delete_secret(path, name, env="default") Delete
inject_env(path="", env="default", overwrite=False) Load a path into os.environ
health() {"service": "kms", "status": "ok"}

Server surface

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=
GET    /healthz | /v1/kms/healthz                    -> {"service": "kms", "status": "ok"}

Two things follow from it:

  • The server splits the trailing path at its LAST slash into (path, name). A name may not contain / — this SDK rejects one rather than let a write land under a key the matching read can never find.
  • There is no versioned read. KMS holds exactly one value per (path, name, env), so get_secret(..., version=N) raises VersionUnsupportedError instead of quietly handing back the current value.

Compatibility

License

MIT License - see LICENSE for details.