Forail 2026.07.0 — Release Notes

Release date: 2026-07-25 Based on: Forail 2026.06.0 License: Apache License 2.0


Overview

2026.07.0 is a security-hardening and migration release. It tightens several authentication and audit defaults (some of which are breaking for existing SAML deployments), makes tenant isolation fail closed, replaces the insecure defaults in the Helm chart and Compose stack (breaking — installs now require an explicit admin password), and introduces a one-shot AWX → Forail importer so teams can migrate off AWX/AAP without rebuilding their configuration by hand.

Kubernetes installs also gain the pod RBAC and receptor worktype that in-cluster job execution needs — see Fixed.

There are no data-model changes. Two idempotent migrations (0209, 0210) ship with the tenancy work; both only drop and re-create PostgreSQL row-level-security policies, so they apply to an existing database without touching table schemas or rows.

⚠️ Known issue — upgrading breaks job execution

Fixed in 2026.07.1. Upgrade to it instead of applying the workaround below — see the 2026.07.1 release notes. The rest of this section describes what happens if you stay on 2026.07.0.

If you upgrade an existing 2026.06.0 install, jobs stop running: they are accepted and then stay in pending indefinitely. The only hint is the job's job_explanation, "This job is not ready to start because there is not enough available capacity" — accurate, but it does not point at the cause. Fresh installs are unaffected. The workaround below is verified on a live cluster.

The default instance group has to satisfy two conditions at once for a job to run locally, and an upgrade breaks both:

  1. It must contain an instance. The chart's init Job calls register_queue --queuename=default, which on an upgrade finds the group already there, prints Instance Group already registered default and assigns nothing.
  2. That instance must be able to execute. The task pod re-registers itself as node_type=control on every start, and a control node only orchestrates.

Either one alone is enough to hang every launch — both were measured individually, holding the other fixed.

Project updates keep working, because they run in controlplane, which does have the instance. The install therefore looks healthy right up until someone launches a job.

Workaround, after helm upgrade completes:

kubectl -n forail exec deploy/forail-web -- forail-manage shell -c "
from forail.main.models import Instance, InstanceGroup
i = Instance.objects.get(hostname='forail-node')
i.node_type='hybrid'; i.save(update_fields=['node_type'])
InstanceGroup.objects.get(name='default').instances.add(i)"

Substitute your own instance hostname if you did not install with the chart defaults. Any job already sitting in pending starts on its own within about a minute; a fresh short job should return a normal PLAY RECAP in a few seconds.

The workaround does not survive a restart of forail-task. That pod re-runs provision_instance every time it starts — after a node reboot, an eviction, or the next helm upgrade — and that call resets both node_type and the group's execution mode. Re-apply it, and re-check job execution, after any task-pod restart until the fix ships.

Also re-apply your role assignments after upgrading. Any role assignment attempted on 2026.06.0 failed silently (the ScanFinding / TenantIsolationEvent FieldDoesNotExist bug fixed in this release, see Fixed). The upgrade fixes the cause but does not recreate the assignments that were lost, and Fixed saying "no data migration is required" refers to the schema only. Check the members of every role you rely on and re-grant what is missing.

What the upgrade does do correctly: it succeeds, migrations 0209 and 0210 apply cleanly, and no data is lost — object counts and names are identical before and after.

Security advisories

Upgrade from 2026.06.0 or earlier is strongly recommended. Several of the fixes below are exploitable on a default install of an earlier release; the detail is in Security hardening and Breaking changes further down.

Severity is this project's own assessment of impact on a default deployment. It is not CVSS, and no CVE identifiers have been requested. "Affected" means every release up to and including 2026.06.0.

Issue Severity Who is exposed What to do
Deployment artifacts shipped working default credentials Critical Any install that did not override the shipped secrets Upgrade; rotate forailAdminPassword, postgresPassword, forailSecretKey and the websocket secret
SSO account takeover — accounts associated by email address, not provider UID Critical Installs using SAML/OIDC/social auth where an IdP can assert an arbitrary email Upgrade; audit existing SSO-linked accounts for unexpected associations
Tenant isolation gate could essentially never fire; RLS failures degraded to global row visibility High Multi-tenant installs Upgrade; review TenantIsolationEvent records and cross-tenant access in the audit trail
forail-task ran privileged with a host cgroup mount by default — a container escape to node-root High Kubernetes and Compose installs using shipped defaults Upgrade; job execution now needs an explicit opt-in, ideally on dedicated tainted nodes
ALLOWED_HOSTS defaulted to * and session cookies were not Secure Medium Internet-reachable installs Upgrade; set your real ingress host(s) and terminate TLS
Audit records stored the raw session key; X-Forwarded-For was trusted unconditionally for the audit source IP Medium Any install; higher where audit logs are broadly readable Upgrade; configure PROXY_IP_ALLOWED_LIST; treat historical audit rows as sensitive
IaC scanner could be pointed outside the project checkout via a job template's playbook field Medium Installs where non-admins can edit job templates Upgrade
Operator held cluster-wide get/list/watch on every Secret Medium Kubernetes installs running the operator Upgrade the operator to 2026.07.1; its Secret access is now a namespaced Role
OAuth refresh_token was recorded in activity-stream entries; superuser grant/revoke was not separately audited Low Any install Upgrade; consider rotating OAuth tokens that appear in historical activity entries

Upgrading does not retroactively clean data written by an earlier release — rotate the credentials above, and treat pre-upgrade audit and activity rows as potentially containing secrets.

Added

AWX → Forail migration importer

A new backend management command migrates configuration from an existing AWX (or AAP) installation via its REST API:

forail-manage import_from_awx \
    --url https://awx.example.com \
    --token "$AWX_TOKEN" \
    --dry-run            # preview; remove to apply

Secrets are not migrated. The AWX API never returns secret credential inputs (it sends $encrypted$), and user passwords are not exported. The importer brings over credential structure and non-secret inputs, creates users with an unusable password, and reports exactly how many secret fields need manual re-entry afterwards. Notification-template secrets are stripped the same way.

RBAC role assignments are migrated where the target framework allows it: user grants and team→object-role grants are applied; organization-member grants to teams (rejected by the access framework) are skipped with a warning rather than aborting the run.

Security hardening

Tenant isolation now fails closed

Trust boundaries around import and SSO

Deployment defaults — Helm chart and Compose

Both deployment artifacts shipped working credentials and a privileged worker by default. That is over:

Use operator 2026.07.1. In 2026.07.0 that narrowing left Credential CRs working only in the operator's own namespace, because a Credential resolves spec.inputsFrom in its namespace and the Secret cache covered only one. 2026.07.1 adds secretNamespaces to the operator chart, which renders both the per-namespace Secret Role and the matching cache scope:

sh helm install forail-operator ... --set 'secretNamespaces={team-a,team-b}'

Still no cluster-wide secrets grant. Everything else in this release is unchanged at 2026.07.0. - Assistant: a wildcard CORS origin no longer combines with credentials, and /api/v1/chat accepts an optional shared bearer token (FORAIL_ASSISTANT_CHAT_TOKEN) with a concurrency cap (FORAIL_ASSISTANT_CHAT_MAX_CONCURRENCY, 429 on overload).

See Breaking changes — deployment defaults below for the upgrade actions.

⚠️ Breaking changes — SAML

Two SAML defaults changed. They affect installs that rely on the previous, weaker behavior.

1. Signed assertions + SHA-256 now required by default

SOCIAL_AUTH_SAML_SECURITY_CONFIG now defaults to:

{
  "requestedAuthnContext": false,
  "wantMessagesSigned": true,
  "wantAssertionsSigned": true,
  "rejectUnsolicitedResponsesWithInResponseTo": true,
  "signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
  "digestAlgorithm": "http://www.w3.org/2001/04/xmlenc#sha256"
}

Impact: If your IdP sends unsigned responses/assertions, or signs with SHA-1, logins will be rejected after upgrade.

Action: - Preferred: reconfigure your IdP to sign responses and assertions with SHA-256. - Temporary fallback: explicitly set SOCIAL_AUTH_SAML_SECURITY_CONFIG via PATCH /api/v2/settings/saml/ for a legacy IdP (not recommended for production).

⚠️ Setting this replaces the whole dict — it does not merge with the secure defaults. When the setting is unset, Forail's hardened defaults apply; the moment you set it, your value is used verbatim and any key you omit falls back to the weak python-saml/OneLogin default (unsigned assertions, SHA-1). So to relax a single key you must re-specify the full secure dict with only that key changed, e.g. to accept unsigned assertions from one legacy IdP while keeping every other protection:

json { "requestedAuthnContext": false, "wantMessagesSigned": true, "wantAssertionsSigned": false, "rejectUnsolicitedResponsesWithInResponseTo": true, "signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", "digestAlgorithm": "http://www.w3.org/2001/04/xmlenc#sha256" }

Clearing the setting (back to null) restores the full hardened defaults.

2. SAML role-attribute grants require an explicit value

Granting is_superuser / is_system_auditor from a SAML attribute now requires a non-empty is_superuser_value / is_system_auditor_value.

Impact: A configuration that sets only is_superuser_attr (with no value) previously granted superuser to every user the IdP sent with that attribute. That now fails safe (no grant) and logs a warning.

Action: Set the required attribute value(s) for the flags you intend to grant.

⚠️ Breaking changes — deployment defaults

1. helm install requires an admin password

secrets.forailAdminPassword has no default and no generated fallback; the chart fails to render without it. The other three secrets (postgresPassword, forailSecretKey, forailBroadcastWebsocketSecret) are generated on first install and looked up on subsequent upgrades, so leave them empty unless you pin them deliberately.

Action: pass --set secrets.forailAdminPassword='<strong-password>' on install. Automation that renders the chart (CI helm lint / helm template) needs a throwaway value for the same reason.

2. forail-task is no longer privileged by default

The podman-in-pod execution path needs a privileged container and the host cgroup namespace; both now default off, because a privileged pod with a host cgroup mount is a trivial container escape.

Action, Kubernetes: --set task.privileged=true --set task.hostCgroup=true (ideally pinning those workers to dedicated, tainted nodes). Action, Compose: FORAIL_TASK_PRIVILEGED=true FORAIL_TASK_CGROUP=host.

3. Allowed hosts and secure cookies

forail.allowedHosts / FORAIL_ALLOWED_HOSTS no longer default to "*", and session cookies are Secure by default — a deployment served over plain HTTP will not keep a session.

Action: set your real ingress host(s), and keep 127.0.0.1,localhost in the list — the in-cluster health probes call the API on loopback. Terminate TLS in front of the ingress, or set forail.cookieSecure: "false" for a lab install.

Fixed

Upgrade

No schema migrations. Migrations 0209 and 0210 re-create RLS policies only and are idempotent, so the standard image re-point applies — but the chart's new required/secure defaults have to be supplied:

helm upgrade forail oci://ghcr.io/forail-platform/forail-helm \
    --version 2026.7.0 -n forail \
    --set secrets.forailAdminPassword='<strong-password>' \
    --set 'forail.allowedHosts=forail.example.com\,127.0.0.1\,localhost' \
    --set task.privileged=true --set task.hostCgroup=true   # only if you run jobs in-pod

Escape the commas. Helm's --set splits unescaped commas into a list, so --set forail.allowedHosts='a,b,c' fails to parse. Either escape them as above (a\,b\,c, inside single quotes so the shell keeps the backslashes) or put the value in a values file, where no escaping is needed:

yaml forail: allowedHosts: "forail.example.com,127.0.0.1,localhost"

Before upgrading: