AxonFlow v9.10.0 Release Notes
v9.10.0 is a minor release that closes a broken-access-control gap on the fleet / MCP-server plane and builds the per-user identity foundation underneath it. In a fleet where every developer's plugin authenticates with one shared org:license credential, a shared credential previously granted attribution, not authorization: any holder could read the entire tenant's audit trail, decisions, policy overrides, per-user cost, and execution history through the MCP read tools (or a direct API call) — other people's emails, queries, file paths, session IDs, and spend. This release makes those cross-user reads role-scoped and fail-closed, and delivers the validated per-user {identity, role} needed to enforce them.
This release carries database migrations (core/143 + enterprise/135, existence-guarded with down migrations) and a deliberate behavior change: a shared-credential fleet caller that presents no per-user token now reads zero rows from the governed read tools instead of the whole tenant trail — see Upgrading. The compatibility matrix advertised on /health moves the recommended plugin versions forward (below).
Security
Role-scoped authorization for fleet audit / decision / override reads (Enterprise)
The fleet plane resolves a validated, non-forgeable per-user {identity, role} (see Per-user identity) and scopes every cross-user read on it:
- A developer (non-admin) role reads only their own rows — the audit events, decisions, and policy overrides attributed to their own identity.
- An admin / owner role reads the full tenant trail, as before.
- An unresolved or shared identity fails closed — zero rows, never the tenant pool.
Scoping is enforced server-side in the query, so a direct API call with the shared credential is covered, not just the MCP tools. The same rule is applied uniformly across the read surfaces: the audit search / decision / override listings, the whole-tenant compliance and evidence exports (gated on an admin role rather than scoped, since a per-user export is meaningless), and the per-user cost / usage breakdown and replay / execution listings — a different data domain reachable with the same credential. Reads key on the same canonical identity the write path stamps, so a caller sees exactly the rows they authored and no others.
Shared synthesized identities fail closed (Enterprise)
The platform mints several shared synthesized identities for token-less and internal traffic (a client-scoped fallback, an audit-writer fallback, internal-service identities). None of them is a real person, and scoping a read to one would return that whole shared pool. v9.10.0 fails every shared synthesized identity closed at the read-scope boundary — canonicalized and matched against a single shared census so a newly added internal identity is covered without a code change — while a validated per-user identity reads its own rows normally.
Added
Per-user identity for the fleet plane, two validator backends (Enterprise)
A pluggable per-user identity layer yields a validated {identity, role} on the fleet plane, on either of two provisioning paths — both converging on one validator-agnostic authorization layer:
- Path A — AxonFlow-managed (no IdP required). An admin-plane API mints, rotates, and revokes per-user HS256 tokens with an admin-assigned role. Because the role travels as a signed claim, mint / rotate / revoke require a valid admin key even where admin auth is otherwise optional — a developer can never mint themselves an admin role. Tokens carry a required expiry (default 30 days, capped at 1 year) and a unique id; revocation is a server-side deny-list consulted on every validation, fail-closed.
- Path B — IdP-issued OIDC. A per-tenant OIDC configuration drives an asymmetric verifier that validates JumpCloud / Okta / Azure AD tokens against the IdP JWKS — RS256 only (
alg:noneand HS256 algorithm-confusion rejected before any key is consulted), with issuer / audience / expiry enforced and the role resolved from the SCIM-synced directory rather than any token claim, so IdP misconfiguration cannot escalate privilege. The SSO-configuration endpoints that rewrite an org's authentication trust anchors are now enforced behind thesso:configurepermission.
The identity model, the role→scope rule, and per-client token setup are documented in Per-Developer Identity → Authorization. The full fleet-scale provisioning runbooks — the per-user token provisioning guide (mint/rotate/revoke, end-to-end JumpCloud OIDC) and the read-scoping model — live in the enterprise repository (access-gated: the enterprise repo is private; ask your AxonFlow contact if the links 404 for you).
Client support for per-user tokens (Enterprise)
The Claude Code plugin (1.10.0), Cursor plugin (1.6.0), Codex plugin (1.6.0), and OpenClaw plugin (2.7.0) now send the per-user token as X-User-Token on every governed surface; when unconfigured they behave exactly as before. The LiteLLM integration (1.0.4) now fails closed on a platform policy rejection even under fail_open — a deterministic deny is a governance verdict, not an availability degradation — while still honoring fail_open on a genuine engine outage. Per-client setup for every integration is collected in Per-Developer Identity → Configuring your client.
Fixed
- Token-machinery robustness (Enterprise). Per-user token revocation on the request hot path is now short-TTL cached (invalidated by the per-user mass-revoke watermark, and still fail-closed on any checker error); fleet-validator registration moved to a deterministic startup step with a warning when a token is presented but no validator is registered.
Upgrading
v9.10.0 carries migrations core/143 + enterprise/135 (existence-guarded, with down migrations). Apply them as part of a normal upgrade.
# Community
docker compose pull && docker compose up -d
# Enterprise
docker compose -f docker-compose.yml -f docker-compose.enterprise.yml pull
docker compose -f docker-compose.yml -f docker-compose.enterprise.yml up -d
Behavior change — fleet read tools now require a per-user (or admin) identity. The MCP read tools (search_audit_events, list_recent_decisions, list_overrides) and the equivalent API reads are now role-scoped. A caller presenting only the shared org:license credential with no per-user token resolves to a shared identity and now reads zero rows, where previously it read the whole tenant trail. To restore reads:
- Provision per-user tokens for your fleet — Path A (admin-minted) or Path B (IdP/OIDC) above; see Per-Developer Identity → Authorization. A developer then reads their own rows; an admin role reads the full tenant trail.
- Or call with an admin-role token for tenant-wide visibility.
This is the intended fix: cross-user visibility through a shared credential was the broken-access-control gap this release closes. (A fleet that has enabled the identity-header trust gate and sets a per-developer AXONFLOW_USER_EMAIL is capped at that developer's own rows rather than zero — but never the tenant pool.) Single-admin and Community-mode deployments are unaffected (no fleet of distinct users to scope between).
Recommended client versions. /health now recommends Claude Code plugin 1.10.0, Cursor plugin 1.6.0, and OpenClaw plugin 2.7.0; minimum floors are unchanged, so existing plugins keep working. Upgrade the plugins to send per-user tokens.
