Skip to content

v9.13.0 — authoritative principals, scoped pre-auth lookups, deployment-mode validation

Choose a tag to compare

@saurabhjain1592 saurabhjain1592 released this 30 Jul 02:52
df027c7

Read the Migration section before upgrading. This release carries two migrations (core/155, core/156) and several deliberate behaviour changes, including one that can stop a stack at boot if DEPLOYMENT_MODE is not a recognised value, and one that makes rows with a blank tenancy key permanently unreachable. Full detail is in CHANGELOG.md — this page is a summary of a long entry.

Who was affected: the pre-authentication row-level-security fixes apply to deployments running the hardened least-privilege DB posture (AXONFLOW_DB_USE_APP_ROLE=true). The authoritative-principal, tenancy-binding, egress and authentication fixes benefit every deployment. DEPLOYMENT_MODE changes affect every self-hosted stack.

This release is the output of a cross-tenant remediation programme. The recurring shape it closes: the platform trusted values the caller supplied, in places that decided authorization, tenancy or policy outcomes.


Security

The request body decided things it should never have decided

  • user.role and user.email were taken from the request body on the governed request plane — a policy-evasion primitive, not merely an attribution one. user.role is resolvable as a policy-condition field, is offered in the portal's policy builder, and appears in the shipped HIPAA PHI-access template, so a tenant policy of the form {query contains …} AND {user.role not_equals "admin"} → block was defeated by putting "user":{"role":"admin"} in the body. Precise severity: no enabled policy keyed on user.role is seeded by default, so this was latent-by-default rather than live on a stock install. Principals are now bound from the validated credential.
  • The governed request plane fell back to a body-supplied tenancy. POST /api/v1/process, /api/v1/workflows/execute, /api/v1/plan and /api/v1/plan/execute now return 401 without both X-Org-ID and X-Tenant-ID. A body naming a different tenancy is now 403 — refused, not silently corrected. Callers going through the AxonFlow Agent or the customer portal are unaffected; both stamp these from the validated credential.
  • MAP HITL approve/reject took the approver's name from the request body, and RBI compliance actors were read the same way. Both now come from the authenticated identity.
  • Workflow-control and RBI compliance routes took tenant scope from client-supplied headers and query parameters.
  • MCP dynamic-policy evaluation took its tenant scope from the request body. POST /api/v1/mcp/evaluate-policies now derives tenancy from the authenticated caller; naming another tenant is 403, and omitting the field — previously 400 — evaluates your own policies.

Authentication and exposure

  • The orchestrator API required no authentication at all, and was internet-reachable on affected topologies. It now requires an internal-service credential.
  • The customer portal's orchestrator catch-all forwarded any path to any authenticated session.
  • The portal admin API served anonymously outside DEPLOYMENT_MODE=saas, and in-VPC deployments could not enable admin authentication at all.
  • OPTIONS requests ran auth-gated handlers anonymously.
  • The orchestrator's embedded Execution Viewer UI is no longer reachable by addressing the orchestrator directly, and the orchestrator is no longer published host-wide by any Compose file in this repository.
  • AXONFLOW_CORS_ALLOWED_ORIGINS — the portal's allowed origins are now operator-configured rather than carrying a hardcoded third-party origin with credentials.

Tenant isolation

  • Org binding failed open on eight-plus governance call sites. A fail-closed choke point now makes an unscoped call unrepresentable rather than merely discouraged.
  • The dynamic-policy verdict cache was tenant-blind.
  • The dynamic-policy list endpoint disclosed every tenant's policies, and policy simulation disclosed the deployment-wide policy count. List and enforcement scoping now share one decision point.
  • The MAS FEAT compliance module never scoped a single statement to its organization.
  • MCP tool-governance plane: deployment-wide dynamic policies were silently not evaluatedcontent policies (the default type) were dropped before evaluation, and the evaluator understood neither the query field nor the regex operator that the policy-creation tool writes.

Least-privilege DB posture (AXONFLOW_DB_USE_APP_ROLE=true)

  • The portal's pre-authentication API-key lookup ran inside the row-level-security scope it exists to discover — so it matched nothing and reported a valid key as an authentication failure. Operators rotated credentials that were never wrong.
  • SCIM bearer-token authentication rejected every valid token, and the SCIM directory returned an empty directory instead of an error — identity providers acted on that as "no users" . If you run SCIM on an app-role deployment, read the SCIM section of the CHANGELOG.
  • The app-role boot guard passed when the administrative pool was configured but unusable — it checked that a string was set, never that a pool was obtained.

Egress (SSRF)

  • Nine SSRF egress classifiers with five distinct behaviours, unified onto one range table. 2001:db8::/32, NAT64 and 6to4 were treated as public by all nine. The HTTP connector had no socket-level egress guard at all. Four callback dialers validated the DNS answer and then dialled the hostname, which resolved again (a DNS-rebinding window).
  • Operator action: circuit-breaker notifications and orchestrator webhooks now refuse ranges they previously accepted. If either targets an internal address, re-check it before upgrading.

Migration

HAS MIGRATION: core/155 and core/156.

  • core/156 makes tenancy keys NOT NULL with a blank-string CHECK on plans, workflows, workflow_checkpoints, execution_summaries and webhook_subscriptions, after stamping orphaned rows with the inert sentinel __axonflow_unowned__. These are scans, not table rewrites — but all five ALTERs run in one transaction, so ACCESS EXCLUSIVE locks are held until COMMIT. Size the window for the sum of the five scans, not the largest table.
  • Behaviour change: a blank-tenancy row was previously readable by every tenant — that is the vulnerability. Such rows are now unreachable and unwritable. The migration does not attempt to determine an owner, so no such row is recovered to a real tenant. An execution that started before the upgrade and finishes after it will fail its final update rather than be marked completed. That window is the upgrade itself.
  • budgets is deliberately not constrained — its scope lookup treats an empty org as apply-to-all, so the constraint would silently disable deployment-global spend caps.

DEPLOYMENT_MODE — do this before upgrading

  • A stack whose DEPLOYMENT_MODE is not a recognised value now REFUSES TO BOOT instead of silently applying the SaaS schema. Recognised: community, community-saas, enterprise, evaluation, in-vpc-banking, in-vpc-enterprise, in-vpc-healthcare, in-vpc-travel, invpc, saas. A leading space or a capitalised spelling now stops the agent. Check with docker compose exec axonflow-agent printenv DEPLOYMENT_MODE.
  • An UNSET DEPLOYMENT_MODE is unchanged and still selects core/ only. It is still not fatal.
  • DEPLOYMENT_MODE=enterprise was not a recognised mode, so every self-hosted enterprise stack applied the SaaS schema. It is now an alias for in-vpc-enterprise. Nothing is dropped — the industry tables your stack acquired through the old fallback stay where they are and simply stop being re-selected. No corrective migration drops them.
  • On a FRESH install, DEPLOYMENT_MODE=enterprise no longer creates the industry tables, and the compliance routes that read them will answer 500. Existing stacks keep everything. If you run a compliance module, name its vertical (in-vpc-banking, in-vpc-healthcare, in-vpc-travel) or saas. Note the RBI kill switch is on the Decision Mode hot path and fails open on a database without its tables — the same state in-vpc-enterprise has always been in.
  • AxonFlow's own E2E fixtures and demo tenants were seeded into every customer deployment and are no longer applied. Rows already present are NOT removed — removing them is an operator decision, on your schedule. See the CHANGELOG for the exact predicates.
  • scripts/marketplace/deploy-with-metering.sh now refuses to run without an explicit DEPLOYMENT_MODE.

Also in this release

  • MCP tool-governance: content policies now govern MCP tool calls where the dynamic plane is enabled (MCP_DYNAMIC_POLICIES_ENABLED=true). Deployments on the default posture are unaffected until they opt in. Review tenant content policies before enabling — a known field with a negated operator can match on an empty value, e.g. {user.role not_equals "admin"} is true when the role is unset.
  • axonflow_create_tenant_policy now reports real enforcement state instead of an unconditional success promise that was false on every default install.

Full detail, including the reasoning and the precise blast radius of each item, is in CHANGELOG.md.