Skip to main content

AxonFlow v9.12.2 Release Notes

v9.12.2 is a security patch release in two parts. First, it completes the v9.12.1 read-path repair for deployments running the hardened least-privilege database posture: the static-policy readers were still unscoped on the restricted role, so the request and response content gates — SQL-injection detection, dangerous-command blocking, PII block/redact — evaluated zero policies and allowed everything. Second, it ships tenant-isolation and fail-safety hardening that benefits every deployment: the policy engine now fails closed when a policy load returns an empty system policy set, the shared global baseline policies are no longer writable through the tenant policy API, and the HITL approval queue is now organization-isolated.

Am I affected?​

The enforcement and visibility fixes apply to the same population as v9.12.1: you are affected only if your database pools actually run as axonflow_app_role — that is, AXONFLOW_DB_USE_APP_ROLE=true with the app-role DSNs provisioned:

  • CloudFormation deployments that completed the two-step app-role provisioning and flipped AppRoleProvisioned=true.
  • Self-hosted deployments whose operator followed the least-privilege provisioning guide (provision-app-role.sh + the app-role environment variables).

You are NOT affected by the silent-enforcement issue on the defaults: the docker-compose stack, the self-hosted install bundle, and CloudFormation stacks that never flipped AppRoleProvisioned all connect as the table-owning database role, which bypasses row-level security. Their content gates enforced correctly throughout.

Every deployment benefits from the hardening in this release regardless of posture: the fail-closed empty-policy-set guard, the global-baseline write lockdown, and HITL organization isolation.

If you are affected, upgrade promptly: until you do, the request and response content gates are not enforcing static policies — prompts that should be blocked for SQL injection or dangerous commands pass through, and PII that should be blocked or redacted is not.

What was broken on the app-role posture, now fixed​

Content gates evaluated zero static policies​

The agent's static_policies readers and the shared policy loader ran unscoped on the restricted database role, so row-level security matched zero rows: policy loads succeeded but loaded nothing, and the request and response content gates allowed everything with policies_evaluated: 0. All static-policy readers now run organization-scoped in two disjoint passes — the caller's own organization plus the shared global baseline — restoring full enforcement of the SQL-injection, dangerous-command, and PII block/redact policies.

Enforcement gates pass the caller's organization​

All policy-evaluation gates — MCP check-input / check-output, the Gateway pre-check, the OpenAI-compatible endpoint, Cowork telemetry redaction, and response processing — now scope their policy loads by the validated caller organization. This closes a silent under-enforcement gap for organizations whose organization and tenant identifiers differ, where a gate could previously load policies against the wrong key and evaluate a smaller set than the organization had configured.

Portal, HITL, integration, and export reads returned nothing​

The same unscoped-read defect class affected a family of read paths on least-privilege deployments, all fixed the same way:

  • The customer portal's "Static (Read-only)" policy count and the unified policies view showed 0 static policies, and the dashboard "Total Policies" tile counted only dynamic policies.
  • HITL approve/reject flows returned "approval request not found".
  • Integration activation enabled zero int_* policies.
  • Policy explain, override lookups, decision-chain reads, LLM provider listing, connector runtime configuration, and policy version history returned empty results. Version history now also includes organization-tier parent policies for all tenants of the owning organization.
  • On Enterprise: the enterprise HITL approval repository, SCIM role readers, the EU-AI-Act and evidence export repositories, and the portal API-key handlers returned empty results.

Hardening for every deployment​

The policy engine fails closed on an empty system policy set​

A successful policy load that contains zero system-tier policies is an impossible state on a healthy deployment — the system policies are migration-seeded — so it is now treated exactly like a load failure on both the request and response planes, with a distinct policy_load_empty_system_set log line and metric so the condition is diagnosable rather than silent. This is defense in depth: any future defect class that produces an empty policy load blocks at the gate instead of silently allowing, which is exactly the guard that would have caught the zero-row reads above the moment they happened.

Global baseline policies are write-locked​

The shared global baseline policy rows — the SQL-injection guards, PII detection, EU-AI-Act templates, and integration policies every organization inherits — are no longer mutable by tenant callers. Update, delete, and disable of a global baseline row through the tenant policy API now return 403 for every caller. Previously only system-tier rows were protected, so a tenant could weaken a shared baseline policy for every organization on the deployment.

The HITL approval queue is organization-isolated​

Queue listing, request lookup, history, approve, reject, and override now bind to the authenticated caller's organization; a request belonging to another organization returns not-found. Previously these flows were unavailable on least-privilege deployments (the "not found" fix above) and deployment-wide on owner-connection multi-tenant deployments — one organization's approver could see and act on another organization's pending approvals.

Behavior changes​

  • Tenant-API writes to global baseline policy rows now return 403 (previously only system-tier rows were rejected). If an integration was editing shared baseline rows through the tenant API, it must stop; per-organization customization belongs in organization-tier policies and overrides.
  • A deployment booted with no migrations applied fails closed at the enforcement gates instead of running ungoverned, because it has no seeded system policies and the empty-set guard treats that as a load failure. Run your migrations (a normal boot applies them automatically) before sending traffic.
  • Effective-policy responses collapse duplicate overrides. When multiple live overrides exist for one policy, the effective-policy response now returns only the latest-created entry; previously duplicate rows could appear. Adjust any client that depended on seeing every override row.

Migration​

One migration ships in this release: core/154 re-runs the global backfill for policy rows seeded under the legacy global sentinel (covering industry template seeds applied after core/153's backfill ran), and installs BEFORE INSERT triggers on the static and dynamic policy tables that default the organization key on global-sentinel rows so future seeds cannot reintroduce the gap. It is additive and idempotent, and no maintenance window is needed.

Known limitations carried from v9.12.0​

The v9.12.0 known limitation regarding owner backfills on managed-PostgreSQL deployments where the migration role is subject to row-level security remains open in this release; the in-product break-glass owner endpoint is the documented recovery path.

Upgrading​

Standard patch upgrade: pull the v9.12.2 images and restart; migration core/154 applies automatically on boot. After upgrading, verify enforcement with a content-gate round-trip: send a request that a static policy should block (for example a SQL-injection probe) and confirm it is blocked with a non-zero policies_evaluated, and confirm the portal's static policy count is non-zero. Review the behavior changes if you operate a multi-tenant deployment, edit policies through the tenant API programmatically, or consume the effective-policy endpoint.