Security & Authentication

How Hoziron authenticates requests, enforces role-based access, protects against brute force attacks, and manages secrets.

Authentication Modes

Hoziron supports three authentication modes, configured via [auth].mode:

Disabled (default)

No authentication. All requests receive anonymous admin access. Suitable for local development and single-user deployments.

Local (API Key)

Hoziron-managed API keys with role-based access control.

[auth]
mode = "local"

[auth.rate_limit]
base_backoff_secs = 1
max_backoff_secs = 300
max_failed_attempts = 10

OIDC (Enterprise SSO)

JWT validation against an external Identity Provider (Azure AD, Keycloak, Okta, Auth0).

[auth]
mode = "oidc"
allow_local_service_keys = true   # Allow API keys alongside OIDC for CI/CD

[auth.oidc]
issuer = "https://login.microsoftonline.com/{tenant}/v2.0"
audience = "api://hoziron-platform"
jwks_uri = ""                      # Auto-discovered if blank
role_claim = "roles"               # Supports dot-path: "realm_access.roles"
allowed_algorithms = ["RS256", "ES256"]
jwks_cache_ttl_secs = 3600

[auth.oidc.role_mapping]
"HozironAdmins" = "admin"
"PlatformOps" = "operator"
"Developers" = "developer"
"ReadOnly" = "viewer"

API Key System

Key Generation

  • 32 bytes of cryptographic randomness (via OS RNG)
  • Base64url-encoded with hzk_ prefix: hzk_Ab3xYz...
  • Only shown once at creation — never stored or retrievable after
  • First 12 characters stored as prefix for identification without exposure

Key Storage

  • SQLite database at $HOZIRON_HOME/data/auth.db
  • File permissions set to 0600 (owner read/write only) on Unix
  • Only argon2id hashes are persisted (recommended params: 19 MiB memory, 2 iterations, 1 parallelism)
  • Indexed by prefix for fast lookup, but authentication always scans ALL keys

Timing Attack Prevention

Authentication performs a constant-time scan of all active keys:

for each key in active_keys:
    verify(token, key.hash)   // argon2id verify (constant-time)
    if match: record (but don't return early)
return recorded match or None

An attacker cannot determine how many keys exist or which position a valid key occupies based on response time.

Lockout Protection

The last admin key cannot be revoked. Attempting to revoke it returns:

{
  "error": {
    "category": "ValidationError",
    "message": "Cannot revoke the last admin key — this would lock out all admin access"
  }
}

Bootstrap Flow

When no keys exist yet, the first POST /auth/keys request is allowed without authentication (bootstrap bypass). This prevents the chicken-and-egg problem of needing a key to create the first key.

Key Expiration

Keys can optionally expire:

  • expires_at stored as ISO 8601
  • Checked at validation time — expired keys are treated as invalid
  • Fail-closed on malformed expiry values (treated as expired)

Role-Based Access Control (RBAC)

Roles

RolePurpose
adminFull access — all operations including key management
operatorAgent lifecycle, workflow management, schedules
developerInstall skills/competencies, create workflows, send messages
viewerRead-only access to all resources
serviceInvoke agents, start workflow runs (for automated integrations)

Permission Matrix (excerpt)

ActionAdminOperatorDeveloperViewerService
agent:create
agent:send_message
agent:list
workflow:run
workflow:create
config:write
auth:key_management
audit:read
competency:install

Authorization Check

Every endpoint checks auth_context.role.can_perform(action) before executing. Insufficient role returns:

{
  "error": "forbidden",
  "message": "Role 'viewer' is not authorized for action 'agent:create'"
}

Brute Force Protection

Per-IP exponential backoff on failed authentication attempts:

ParameterDefaultConfig Key
Base backoff1 secondauth.rate_limit.base_backoff_secs
Max backoff300 seconds (5 min)auth.rate_limit.max_backoff_secs
Max failures tracked10auth.rate_limit.max_failed_attempts

Rate limit state is:

  • In-memory (per-process, reset on restart)
  • Per-IP address
  • Cleared on successful auth from that IP
  • Stale entries evicted after 10 minutes of inactivity

OIDC / JWT Validation

For enterprise SSO integration:

Role Mapping

The role_claim field supports dot-path traversal for nested claims (Keycloak pattern):

role_claim = "realm_access.roles"   # Navigates into nested JWT claims

When multiple IdP roles match the mapping, highest-privilege wins (admin > operator > developer > viewer > service).

Hybrid Mode

With allow_local_service_keys = true, OIDC mode falls back to local API key validation when JWT validation fails. This enables:

  • CI/CD pipelines using API keys alongside human SSO
  • Break-glass access for emergencies

Network Security

TLS

[server.tls]
enabled = true
cert_path = "/etc/hoziron/tls/cert.pem"
key_path = "/etc/hoziron/tls/key.pem"
  • Native TLS termination for bare-metal/VM deployments
  • In Kubernetes: disable (set enabled = false) — ingress handles TLS
  • Certificate and key paths are validated at startup

IP Allowlist

[server]
allowed_ips = ["10.0.0.0/8", "192.168.1.100"]
  • Outermost middleware layer — checked before auth
  • Supports individual IPs and CIDR notation (IPv4 and IPv6)
  • Health endpoint (/health) always bypasses the allowlist
  • Metrics endpoint (/metrics) always bypasses (Prometheus scraping)
  • Unix socket connections bypass (no IP to check)

CORS

[server.cors]
allowed_origins = ["https://dashboard.company.com"]
allow_credentials = true
max_age_secs = 3600

Validation rules:

  • Cannot use wildcard * with allow_credentials = true
  • Each origin must start with http:// or https://
  • Empty origins list is rejected when CORS is configured

Request Limits

LimitDefaultPurpose
max_request_body_bytes10 MBPrevents memory exhaustion from large payloads
request_timeout_secs600 (10 min)Prevents hung connections
idle_timeout_secs300 (5 min)Reserved for future connection-level enforcement

Credential Security

API Key Secret Flow

Key principles:

  • config.toml stores the name of the env var (api_key_env = "ANTHROPIC_API_KEY"), never the value
  • Keys are resolved lazily at request time from the environment
  • Error messages reference the env var name, never the key value
  • The key store database has 0600 permissions (owner-only)

Vault vs Environment

MethodStored WhereBest For
Vault$HOZIRON_HOME/vault/ (encrypted at rest)Bare metal, persistent
Environment variablesProcess envContainers, orchestrator-managed
.env file$HOZIRON_HOME/.envLocal development

Endpoints Always Accessible

Regardless of auth mode, IP allowlist, or any security configuration:

EndpointReason
GET /healthOrchestrator liveness/readiness probes must always work
GET /metricsPrometheus scraping without API key
POST /auth/keys (first key only)Bootstrap — create first key without existing auth