v9.10.0 — role-scoped read enforcement + per-user identity foundation
[9.10.0] - 2026-07-17 (per-user identity & role-scoped authorization for fleet deployments)
Closes the fleet broken-access-control epic: in a fleet where every developer's plugin authenticates with one shared org:license credential, that credential granted attribution, not authorization — any holder could read the entire tenant's audit trail, decisions, overrides, per-user cost, and execution history. This release resolves the caller into a validated, non-forgeable {identity, role} on the fleet plane and role-scopes every cross-user read. Migrations core/143 + enterprise/135 (existence-guarded, with down migrations). Behavior change: a shared-credential caller with no per-user token now reads zero rows from the governed read tools — see Changed.
Security
- Role-scoped audit / decision / override reads (Enterprise). (Enterprise) The presenting fix: the fleet read tools (
search_audit_events,list_recent_decisions,list_overrides) and the equivalent API reads are now scoped server-side (SQLWHERE, never post-fetch) — a developer role reads only their ownuser_emailrows, an admin/owner reads the full tenant. The census is exhaustive: audit/decision/override, plus the whole-tenant compliance/evidence exports (admin-gated, since a per-user export is meaningless) and the adjacent cost/usage and replay/execution surfaces — a different data domain reachable with the same credential; both stamp a shared license identity, so a non-admin is denied (403) rather than given a vacuous filter, with cross-org isolation on every by-id route. - Shared synthesized identities fail closed (Enterprise). (Enterprise) Every shared synthesized identity the platform mints (
mcp-client:*, the reserved@axonflow.local/@axonflow.internalservice domains, the evaluator identity) is matched against a single canonicalized census predicate at the read-scope boundary and empties the scope, so a read can never resolve to a multi-developer pool. A validated per-user identity reads its own rows normally; near-miss customer domains are untouched.
Added
- Per-user identity foundation for the fleet plane (Enterprise). (Enterprise)
authenticateMCPServerRequestresolves a validated, non-forgeable{identity, role}— replacing the forgeableX-User-Email-only identity and the hardcodedrole: unknown— on a pluggable validator seam shared by both provisioning backends below. - Per-user token provisioning — the two validator backends for fleet identity (Enterprise). (Enterprise) Part of the fleet-identity epic: fleets that authenticate every developer's plugin with one shared
org:licensecredential get attribution, not authorization. This delivers per-user credentials on two paths, both producing a validated, non-forgeable{identity, role}for the fleet plane's pluggable validator. Path A (AxonFlow-managed): an admin-plane API (/api/v1/admin/organizations/{org_id}/user-tokens) mints, rotates, and revokes per-user HS256 tokens with an admin-assigned role. Because the role travels as a JWT claim, mint/rotate/revoke require a validX-Admin-API-Keyeven in deployment modes where admin auth is otherwise optional — a developer can never mint themselvesrole: admin. Tokens carry a requiredexp(default 30d, capped 1y) and ajti; revocation is a server-side deny-list (enterprise migration 135) consulted on every validation, fail-closed. Path B (IdP-issued OIDC): a per-tenant OIDC configuration onsso_configurations(core migration 143, CRUD now gated onsso:configure) drives a new 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),iss/aud/exp/nbfenforced, JWKS cached with kid-miss refetch for signing-key rotation, and the role resolved from the SCIM-synced directory rather than any token claim so IdP misconfiguration cannot escalate privilege. The SSO-config mutation endpoints, which rewrite an org's authentication trust anchors, are now enforced behindsso:configure(previously declared but unenforced). Migrationscore/143+enterprise/135are existence-guarded with down migrations. Fleet-scale provisioning for both paths, including end-to-end JumpCloud OIDC, is documented indocs/enterprise/per-user-token-provisioning.md. - Advisory-plane server / tool identity fields. (Community) The
check_policy/ MCP check-input schema andDecisionTargetgain distinctserverandtoolidentity fields (previously one caller-supplied string was duplicated into both), surfaced in the portal audit view. Additive and backward-compatible (omitempty). This is one self-contained addition from the separate audit-identity epic that landed ahead of this release; the remainder of that epic follows in a later release. - Client support for per-user tokens. The Claude Code (1.10.0), Cursor (1.6.0), Codex (1.6.0), and OpenClaw (2.7.0) plugins now send the per-user token as
X-User-Tokenon every governed surface (unconfigured behavior unchanged); the LiteLLM integration (1.0.4) fails closed on a platform policy rejection even underfail_open, while still honoringfail_openon a genuine engine outage./healthadvertises the new recommended plugin versions.
Fixed
- Token-machinery robustness (Enterprise). (Enterprise) Per-user token revocation on the request hot path is short-TTL cached (invalidated by the per-user mass-revoke watermark, 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.
Changed
- Behavior change — fleet read tools require a per-user (or admin) identity (Enterprise). (Enterprise) The MCP read tools and equivalent API reads are role-scoped: a caller presenting only the shared
org:licensecredential with no per-user token resolves to a shared identity and reads zero rows, where it previously read the whole tenant trail. Provision per-user tokens (admin-minted or IdP/OIDC) or call with an admin-role token to restore reads. Single-operator and Community-mode deployments are unaffected.