kx.auth and kx.rbac are peer q modules, and kx auth is the CLI that does the OAuth work q
deliberately will not. Together they let a kdb+ process act on behalf of an end user whose identity was
authenticated upstream, by a trusted gateway, proxy or application, without that user logging in to
kdb+ directly. kx.auth carries the identity and provides a default-deny authorization seam; kx.rbac
is the policy engine that fills it.
New here, or wondering why any of this is shaped the way it is? Authorization for kdb+ is the place to start. It covers the problem these modules solve, the policy contract, where authorization is enforced and where it deliberately is not, and the supported deployment topologies.
- Two identities, cleanly separated. The connecting process authenticates the connection with a
service-account login; the end user is asserted on that connection afterwards and never logs in.
.z.pwonly ever sees the service account. - A default-deny Subject / Action / Resource seam.
authorize[action;resource]andentitled[action;resources]are the decision verbs. Until a deployment installs a policy, every question answers no, including "may this caller assert an identity at all". - An RBAC engine behind that seam, so you don't write the policy function.
kx.rbacis one table of(group; action; resource)grants with segment-wise cover, wildcards, no verb subsumption and no deny rules, plus verbs to declare, inspect, verify, explain and persist it. A single decision costs ~3 µs. The policy persists as native kdb data, so there is no config format to parse and any q process can read the grant table and query it directly. - One promotion path for both transports. qIPC and HTTP converge on a single canonicaliser, so a policy reads the same principal shape regardless of how the identity arrived — and it refuses a principal it cannot canonicalise, so a policy never sees junk.
- No mandatory runtime dependencies. Pure q, no shared objects, no network calls. Metadata-declared enforcement adds a soft aimeta dependency for hosts that opt into it.
- An OAuth-aware client for the part q refuses to do. The
kx authCLI handles token acquisition, RFC 8693 exchange and the claims projection, and administers policy over either transport. See choosing a path.
/ host.q
.kx.auth:use`kx.auth; / assign to the global so a REMOTE .kx.auth.bind resolves
.kx.rbac:use`kx.rbac;
.kx.auth.activate[]; / wire .z.pw / .z.po / .z.pc, composing with any priors
.kx.auth.setLoginGroups[(enlist `svcuser)!enlist `superUsers]; / what groups that LOGIN carries
.kx.rbac.grant[`superUsers; `assert; `kx.identity]; / who may assert an identity
.kx.rbac.grant[`trader; `read; `data.trades]; / and who may read what
.kx.auth.setPolicy .kx.rbac.policy[]; / install the peer engine explicitly
getTrades:{[s]
.kx.auth.authorize[`read;`data.trades]; / 'denied unless the bound principal may read trades
select from trades where sym=s };The trusted caller then connects as the service account and asserts a user:
h:hopen `$":localhost:5011:svcuser:pw";
h(`.kx.auth.bind; `sub`groups!(`alice; enlist `trader)); / refused unless the CALLER may assert
h(`getTrades;`AAPL);Run the whole thing, with checks, in one command:
bash demos/local-assertion/run.shWith a real identity in the picture, the kx auth CLI replaces the hand-built principal above — it
performs the same bind handshake, and reports back what q promoted the principal to:
# a platform-issued workload token, exchanged and asserted — no browser, no server in between
export KX_AUTH_TOKEN="$(kubectl create token ingest-sa \
| kx auth exchange --audience kdbx --token-url https://idp/token --json | jq -r .result.access_token)"
kx auth assert --connect localhost:5011 --user svcuser \
--probe ".kx.auth.authorize[\`read;\`data.trades]" --json- docs/AUTHORIZATION.md — the overall approach: the problem, the policy contract, the enforcement boundary, topologies and vocabulary. Start here.
- modules/kx/auth/README.md — identity assertion, policy seam and transports
- modules/kx/rbac/README.md — grants, cover, administration and persistence
- packages/kx-auth-cli/README.md — the
kxCLI: token acquisition, exchange and assertion plus atomickx rbacpolicy administration - demos/local-assertion/ — the runnable walkthrough: identity assertion plus four RBAC sets enforced by one engine, and a grant taking effect live with no reload
- demos/envoy-gateway/ — the same engine reached over HTTP through a real OAuth
proxy: Keycloak + Envoy + KDB-X in Docker. The topology for interactive human access with no MCP server
in the path, and where an appended
x-kx-principalheader is shown failing closed against a deliberately misconfigured listener - tests/README.md — how to run the suite, and the conventions it depends on
- skills/kx-auth-policy/ — agent guidance for authoring grants and choosing declared versus explicit enforcement
- skills/kx-auth-cli/ — agent guidance for authentication handoffs, promoted principals, policy inspection and safe atomic changes
- docs/decisions.md / docs/q-gotchas.md — architecture decisions and q implementation constraints for maintainers
- CONTRIBUTING.md / CLAUDE.md — working on the module itself
Each module carries its own install guide, and both follow the same shape — put the module on the
KDB-X module path ($QPATH, default $HOME/.kx/mod), then bootstrap it:
The two are peers and are normally installed together: kx.rbac supplies the decision function that
kx.auth's seam is otherwise missing.
The CLI is a separate, optional Python install — nothing in the q modules depends on it:
uv tool install 'kx-auth-cli[qipc]' # or: pipx install 'kx-auth-cli[qipc]'See its README for the extras and running it with uvx.
Pre-1.0. Everything described here is built and tested. None of it is frozen yet.
A handful of surfaces are treated as public contracts: the two modules' exports, the principal shape a
policy reads, the (grp;act;res) grant schema, the store file format, and the CLI's commands, JSON
envelope and exit codes. Before 1.0 those can still change.
Built and covered by tests: identity assertion over qIPC and behind an HTTP gateway, the default-deny
seam, the kx.rbac engine with atomic transactions and persistence, declared authorization through
aimeta annotations, the opt-in perimeter gate, the kx rbac CLI over both transports, and a runnable
Keycloak plus Envoy topology.
What a 1.0 still needs:
- A distribution path. Both modules install by copying or symlinking onto the module path. There is no package manager story yet.
- KxSystems/aimeta — the metadata model whose
@authorizeannotation publishes the permission a function needs