holographic-memory 0.6.3

Local-first search engine and agent memory: fast approximate nearest-neighbor and BM25 full-text search, encrypted stores, provable deletion, built on vector-symbolic architectures.
Documentation
---
search:
  boost: 1.6
---

# hms-server authentication and authorization

## Modes

| Mode | How | Who it is for |
|---|---|---|
| Per-tenant keys | `--keys-file keys.json` (`HMS_KEYS_FILE`) | Production. Keys are bound to tenants and scopes, stored only as SHA-256 digests, expire and rotate without a restart. |
| One shared key | `HMS_API_KEY` (environment only) | Small deployments and the original SDK setup. The key is valid for every tenant with every scope. May be combined with a key file. |
| No authentication | `--no-auth` (`HMS_NO_AUTH`) | Local development. Every caller has every scope. Refused unless the listener is loopback or a unix socket, refused together with credentials, and logged as a warning at startup. Requests from web pages are refused with 403 unless their `Origin` is in `--cors-origins`, and on TCP the `Host` must be a loopback name (`localhost`, `*.localhost`, a loopback address), which stops DNS rebinding. |

With neither a key nor `--no-auth` the server does not start. There is no implicit open mode.

## Tokens and storage

A token looks like `hms_<id>.<64 hex digits>`: the key id, then 256 bits from the operating system's random generator. It is shown once, by `hms-server keygen`. The server keeps `sha256(token)` only. A presented token is hashed and the digests are compared in constant time, so neither the comparison time nor the token length reveals anything about a stored value. SHA-256 is the right primitive here because tokens carry 256 bits of entropy; a slow password hash would only cost throughput.

The key id appears in access logs (`key_id`), never the secret.

## Creating keys

```sh
hms-server keygen --id acme-reader --tenants acme --scopes read --keys-file keys.json
# prints the token on stdout; the entry is appended to keys.json (mode 0600, written atomically)

hms-server keygen --id ops --tenants '*' --scopes admin --expires-in-days 90 --keys-file keys.json
```

Without `--keys-file` the entry is printed on stderr for you to place yourself.

## Key file format

```json
{
  "keys": [
    {
      "id": "acme-reader",
      "hash": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
      "tenants": ["acme"],
      "scopes": ["read"],
      "not_before": 1767225600,
      "expires_at": 1798761600,
      "disabled": false
    }
  ]
}
```

| Field | Meaning |
|---|---|
| `id` | 1 to 32 characters of `[A-Za-z0-9_-]`, unique. |
| `hash` | `sha256:` plus 64 hex digits of the full token. |
| `tenants` | Tenant ids the key may use, or `["*"]` for every tenant (not combinable with ids). |
| `scopes` | Any of `read`, `write`, `admin`. |
| `not_before`, `expires_at` | Optional Unix seconds. Outside the window the key is rejected. |
| `disabled` | Optional. A disabled key is rejected. |

An invalid file is refused at startup. On reload, an invalid file is rejected and the previous keys stay in force.

## Scopes

| Scope | Allows |
|---|---|
| `read` | Query, list, stats, compaction status. |
| `write` | Add, replace and delete documents (single, batch, stream), index maintenance. |
| `admin` | Everything `read` and `write` allow, plus snapshots, restore, compaction runs, and `/metrics` (unless `--metrics-public`; a key limited to some tenants sees only their per-tenant series). Key reload and tenant listing additionally need a key valid for every tenant. |

`write` does not include `read`; give a service that ingests and queries both. The required scope of every operation is in the OpenAPI document as its `security` requirement, and a test checks the server against it.

## Tenants

A key bound to one tenant needs no `X-Tenant-ID`; its tenant is the default. A key bound to several tenants, or to `*`, selects with `X-Tenant-ID` (default `default`). Naming a tenant outside the key's list is 403 `tenant_forbidden`. Because a key can only reach its own tenants, a leaked key cannot create stores for tenants it was not issued for.

## Rotation

1. `hms-server keygen` a new key for the same tenants and scopes; add it to the key file.
2. Reload: `kill -HUP <pid>` or `POST /api/v1/admin/keys/reload` with an all-tenant admin key. Both keys are now valid.
3. Roll the new token out to clients.
4. Set `"disabled": true` (or remove the entry) on the old key and reload. Or issue keys with `--expires-in-days` so the old one lapses by itself.

Rotating `HMS_API_KEY` needs a restart; prefer a key file where rotation matters.

## What this does not do

- No per-request signing and no mutual TLS; transport security is TLS (`--tls-cert`) or a terminating proxy.
- No lockout of repeated failures. `hms_auth_failures_total` counts them; apply network-level controls for guessing attacks (the tokens are 256-bit random, so online guessing is not practical).
- No per-key rate limits; limits are per tenant (`--rate-limit-rps`).