Changelog
This page tracks Zilean’s tagged releases. It’s regenerated by hand whenever a new
vX.Y.Z tag ships.
v3.0.0 — 2026-10-13
Section titled “v3.0.0 — 2026-10-13”Security hardening across login, webhooks, the MCP endpoint and recorded run history, plus a
demo mode for public trial deployments. One upgrade step is required for zilean-mcp —
read the upgrade notes before pulling.
Upgrade notes
-
zilean-mcprefuses to start ifSETTINGS_ENCRYPTION_KEYorSCRIPT_SECRETS_KEYis set in its environment. If your Compose file giveszilean-mcpenv_file: .env, replace it with a narrowedenvironment:block before upgrading:environment:DATABASE_URL: ${DATABASE_URL}TZ: ${TZ}MCP_ADDR: ${MCP_ADDR}zilean-webhookcarries the same guard. The reference Compose file already narrows its environment, so no change is needed there unless you customized it. -
Behind a reverse proxy, set
UI_TRUST_PROXY_HEADERS=trueonzilean-uiif your proxy sets and sanitizesX-Forwarded-For. Otherwise every client shares the proxy’s address and one login rate-limit bucket. See Environment variables.
Added
- Login and accept-invite rate limiting.
zilean-uirate-limits and body-caps unauthenticated login and invite-acceptance submissions per client (IPv4 address or IPv6 /64). Tune withUI_LOGIN_RATE_PER_MINUTE(default 10) andUI_LOGIN_RATE_BURST(default 5), and choose the client key withUI_TRUST_PROXY_HEADERS. Only CSRF-valid login attempts count against the limit.zilean-uialso applies read, header and idle timeouts. - Webhook hop limit. Outbound HTTP job requests carry an
X-Zilean-Webhook-Hopsheader, and the webhook endpoint answers508without triggering once a chain reaches 8 hops. Script jobs see the inbound count asZILEAN_WEBHOOK_HOPS. Saving a job whose URL points at its own webhook URL is rejected. - Demo mode (
DEMO_MODE=1,DEMO_DISPLAY_PASSWORD,DEMO_ECHO_BASE_URL) for public trial deployments: it seeds sample data and refuses script, SMTP, notification-target, MCP-settings, password, user, role and invite writes. Leave it unset on real deployments. - The notification target picker shows each target’s kind, in the picker and on the review step.
Fixes
- HTTP jobs mask resolved secret values (secret global variables and
auth_configsecrets, including percent-encoded forms) in recorded call history, errors, River UI, MCP recent calls and job-finished notifications. - Workflow run history masks secrets in escaped forms (JSON- and URL-escaped), including echoed JSON response bodies, before step output is truncated. A step’s recorded output also masks secrets that appear as object keys; the run context, loop items and loop state are masked by value only, so variable and block names are never rewritten.
for_eachloops free item snapshots when an iteration ends, and stored loop items are size-capped, so long-running loops no longer grow a run’s stored state without bound.- Admin writes complete even if the browser disconnects mid-request, and a one-off job’s or workflow’s scheduled marker is committed together with its queued run, so a failed insert leaves it unfired instead of marked scheduled with no run.
group_nameis capped at 255 bytes and 16 levels for jobs, workflows, CSV import and group enable/disable. Existing longer names keep working until edited.
Changed
- Dependency updates: River, River UI 0.21.0, gjson 1.20.0, driver.js and frontend tooling.
developmentimages report anX.Y.Z-SNAPSHOTversion.- Zilean builds with Go 1.27.
v2.1.1 — 2026-10-09
Section titled “v2.1.1 — 2026-10-09”Documentation moves to its own domain. No configuration or upgrade steps are required.
Changes
- Docs are served at zilean.dev. The admin UI’s sidebar “Docs”
link and the script-worker sandbox warnings (missing or failing
bwrap) point at the new location. The previous/docspath is retired. - The deployment runbook no longer describes a public admin hostname. The reference Caddy configuration serves the admin UI on a LAN-only hostname; expose it publicly only if you deliberately choose to, behind your own access controls.
- The docs site checks its links at build time. Broken internal links or anchors fail the docs build.
v2.1.0 — 2026-10-08
Section titled “v2.1.0 — 2026-10-08”Script volumes, expression mode for workflow variables, hardened script-worker containers and configurable HTTP worker concurrency. Read the upgrade notes before pulling.
Upgrade notes
- Add the hardening settings to your Compose file.
zilean-script-worker-bash,-nodeand-groovynow run with a read-only root filesystem, a size-cappednoexectmpfs at/tmp, all capabilities dropped, andno-new-privileges— see Installation. An existing Compose file keeps working unchanged, but the workers only get the hardening once you update it. Nothing needs to be enabled and there is no host prerequisite: the settings are always on, and they compose with the opt-inbwrapsandbox rather than replacing it. - A script that executes a file it wrote to scratch will fail.
/tmpis mountednoexec, sochmod +x helper && ./helperexits withpermission denied. Run the file through its interpreter instead —bash helper.sh,node helper.js. Bash, Node/TypeScript viatsxand Groovy themselves are unaffected, because the interpreters live on the read-only root filesystem. - A script can no longer persist writes outside
/tmp, except to a declared script volume. Writes to the container’s own filesystem fail; with bwrap sandboxing enabled, a write outside the per-run directory instead lands in the sandbox’s own temporary root and is discarded when the run ends. The per-run working directory under/tmpkeeps relative-path writes working. Script volumes are opt-in: nothing changes unless you add the volumes mount to the script workers. - A new migration adds the script volume catalog and a
capabilitiescolumn on jobs. It applies automatically whenzilean-corestarts. Existing jobs get{}(no capabilities) and behave as before. - Permissions. New keys
scriptvolumes:readandscriptvolumes:write. Admin receives both, Read-Only receivesscriptvolumes:read, and Super Admin has everything. Custom roles receive neither. - New optional variable for the three script workers:
SCRIPT_VOLUMES_ROOT. Volumes are opt-in and need a host directory mounted into the script workers; see Volumes. Without it everything runs as before. - New optional variable for
zilean-worker:WORKER_MAX_CONCURRENCY(default 20, range 1–200). Raising it also needs a larger database pool via?pool_max_conns=N; see Environment variables.
Changed
- The three script workers now run with a read-only root filesystem, a
noexec,nosuid,nodevtmpfs at/tmpsized 128m (Bash) or 256m (Node, Groovy), an empty capability set, andno-new-privileges. A compromised script cannot modify the image, gain privileges through setuid binaries or file capabilities, or persist writes outside its scratch space (other than to an opt-in script volume). Concurrent jobs still share one UID and one/tmp, so per-job separation remains what the opt-inbwrapsandbox provides. - River UI updated to 0.20.0, plus frontend dependency updates.
Added
- Script volumes. Script jobs and workflow script blocks declare named, persistent
directories (
roorrw) they may reach, defined under Settings → Script volumes with no restart. Each script receivesZILEAN_VOLUME_<NAME>with the path. Declarations are validated on save and again on every attempt, enforced by thebwrapsandbox, and carried in CSV import (capabilities_json), the API (/api/script-volumes,/api/script-workers/status) and the MCP job tools. Outbound-host restrictions are not yet supported. See Volumes. - Expression mode for Set variable blocks. A workflow assignment can compute its value with a
sandboxed expression (arithmetic, comparisons,
len,upper,split,join, …) over{{references}}instead of a template, with overflow-checked integer math, strict size limits, and inline compile errors in the editor. See Workflows. WORKER_MAX_CONCURRENCYsets how many HTTP jobs eachzilean-workerreplica runs at once. An invalid value stops the worker at startup instead of being clamped.- A “What’s new” tour for this release, replayable from Settings → Tours.
Fixed
- The job row action menu closes on an outside click and when another row’s menu opens.
- The sidebar version label is no longer truncated next to the wordmark.
v2.0.0 — 2026-10-06
Section titled “v2.0.0 — 2026-10-06”Workflows, notifications and webhook-triggered jobs. This is a major release because the script workers need a new environment variable, so read the upgrade notes before pulling.
Upgrade notes
- New required variable for the script workers:
SCRIPT_SECRETS_KEY.zilean-script-worker-bash,-nodeand-groovyexit at startup without it. It is not a fresh random value. It is derived from your existingSETTINGS_ENCRYPTION_KEY, so follow GeneratingSCRIPT_SECRETS_KEYand give the result to the three script workers only.zilean-coreandzilean-workerderive it themselves and need nothing new. If you rotateSETTINGS_ENCRYPTION_KEY, regenerate this key too. - New optional service:
zilean-webhook. It serves inbound webhook triggers atPOST /api/webhooks/{job_id}/{token}. Add it to your Compose file, route/api/webhooks/*to it, and rate-limit that path at your reverse proxy (see Deploying). It needsDATABASE_URLand no encryption key. Jobs on the other schedule types keep working without it. - New optional variables:
WEBHOOK_ADDR(default:8093) andWEBHOOK_TRUST_PROXY_HEADERS(defaultfalse), both described in Environment variables. - Permissions. New permission keys:
notify:read,notify:write,notify_settings:read,notify_settings:write,workflows:readandworkflows:write. The built-in Admin role receives all of them, Read-Only receivesworkflows:read, and Super Admin has everything as before. Custom roles receive none. Add the keys to any custom role that should see Notifications or Workflows (see Permissions). - API change. List endpoints return
[]instead ofnullwhen there is nothing to list. Update any script that checks fornull. - A manual run (
POST /api/jobs/{id}/run, and the MCPzilean_run_jobtool) is rejected with409forwebhookandone_offjobs. - Database migrations add notification targets, metric thresholds, script-secret opt-ins,
webhook tokens, workflows and invocation alarms. They apply automatically when
zilean-corestarts.
Added
- Workflows. A second kind of schedulable entity: a graph of blocks built in a visual editor.
Available blocks are
http,script,parse,if,switch,waitandset_variable, plusfor_each/whilecontainers withbreak/continue, andsuccess/failterminals. Workflows run on cron, at a fixed rate, once, or manually. Every run records each step’s input, output and errors, with secret values redacted. Awaitblock does not hold a worker while it waits, and script blocks run on the same isolated script workers as script jobs. See Workflows. - Notifications. Deliver job results to Apprise channels, email, or webhook targets, configured
under Settings → Notifications and Settings → Webhooks. Each job picks which outcomes
notify (
notify_on) and which targets receive them. Webhook targets have their own event filter and an optional signing secret that is sent as anX-Zilean-SignatureHMAC header. Targets can be edited as well as added and removed. See Configuration and Receiving a webhook. - Metric alarms. Per-service CPU and memory thresholds that notify when a service goes over and again when it recovers. See Alerting on worker metrics.
- Invocation alarms. Per-job alarms on attempt or failure count over a rolling window of 1–1440 minutes, managed from the job’s Activity modal. See Invocation alarms.
- Webhook-triggered jobs. A new
webhookschedule type gives a job its own URL that an external service can POST JSON to (256 KiB max). The token is shown once, stored only as a hash, and rotatable. HTTP jobs read the payload as{{webhook_*}}variables, and script jobs receive it on stdin plusZILEAN_WEBHOOK_*environment variables. See Scheduling. - Script secrets. A secret global variable can be opted into script jobs. It reaches a script only when the variable allows it and the job names it, and its value is redacted from recorded output and notifications. See Script jobs.
- System Health (Settings → System Health): shows whether every Zilean service is reporting (live, stale, or never seen). See System Health.
- Script editor. Script bodies are edited in a code editor with syntax highlighting.
- A collapsible sidebar, per-section tours in Settings → Tours, a “What’s new” tour for this release, and show/hide toggles on password fields.
Fixed
- The admin UI recovers instead of showing a blank page when a page fails to load after a session is revoked.
- Secret values are redacted longest-first, so one secret can no longer leave part of another visible in recorded output.
- Notification error snippets no longer include untrusted response or script content.
- Fractional alarm thresholds are accepted, and the Apprise URL is validated before Apprise can be enabled.
- Mobile sidebar overflow, toast placement, and permission gating of user and role actions in the admin UI.
- River upgraded to 0.48.
v1.4.1 — 2026-09-21
Section titled “v1.4.1 — 2026-09-21”Security hardening release. Update as soon as convenient.
Upgrade notes
- Secret global variables are now encrypted at rest with the existing
SETTINGS_ENCRYPTION_KEY(see Environment variables); no new configuration is needed. An existing secret stays plaintext until you save it again, so re-save each one after upgrading. - Script jobs no longer receive secret global variables as
GLOBALVAR_<NAME>, and{{name}}placeholders are no longer substituted into script bodies or arguments. Secret variables still resolve in HTTP jobs. - No database migrations.
Security
- Secret global variable values are masked in the variables API, the audit log (REST and MCP) and recorded call URLs.
- The login form carries a CSRF token, and logout accepts POST only.
- Passwords are checked for length on the server (8 characters minimum, 72 bytes maximum), including when accepting an invite.
- Invite links no longer leave the token in the address bar or in the
Refererheader. - Resending an invite and editing roles now apply the role-grant rule, and removing a user’s role revokes Super Admin status.
- SMTP connections refuse link-local, unspecified and multicast destinations, and the test-send password is only reused for the saved host and port.
- HTTP response bodies over 4 MiB fail the attempt instead of being read in full.
- MCP output labels untrusted remote text in job errors and strips control characters from recent-call output.
- The bearer token and API key inputs in the job form are masked.
v1.4.0 — 2026-09-20
Section titled “v1.4.0 — 2026-09-20”Adds an MCP endpoint, CSV job import, and batch actions on the Jobs page.
Upgrade notes
- A new service,
zilean-mcp, serves the MCP endpoint. It is optional: add it to your Compose file and route/mcpto it (see Deploying).zilean-coreand the other services keep working without it. - Four database migrations (0027–0030) apply automatically when
zilean-corestarts. - New optional variables:
MCP_ADDR(default:8092) andMCP_HEALTH_URL, both described in Environment variables.
Added
- MCP endpoint. MCP clients can list and inspect jobs, read activity, recent calls,
metrics and the audit log, and (with the right permissions) enable, disable or run a
job. Access uses tokens scoped to a role, managed under Settings → MCP, behind a
new
settings:mcppermission and an on/off toggle. The process never receives the encryption key, so it cannot read job credentials. See the MCP guide. - CSV job import. Import jobs from a CSV file with overwrite, skip or merge handling for name collisions, with live progress and a downloadable template. See CSV import.
- Batch job actions. Select several jobs on the Jobs page to enable, disable or delete them together.
- River job retention is configurable under Settings → River Jobs.
- Onboarding tours. A first-run product tour, a What’s new tour and a Settings tour, replayable from Settings → Tours.
- Change roles for a user from the Users table.
Changed
- The Jobs page lists groups in a sidebar tree instead of nested cards.
- The admin UI is usable on mobile: the Jobs page, Settings, Metrics, modals and the job tables reflow for narrow screens.
- Image builds are faster.
Fixed
- Only the account bootstrapped from
ADMIN_USERNAMEis protected from demotion, deletion and admin password resets. Other Super Admins are ordinary accounts. - Search on the Jobs page covers every job when a group is selected.
v1.3.3 — 2026-09-16
Section titled “v1.3.3 — 2026-09-16”Security fix — follow-up to v1.3.2, update as soon as convenient if you use invites or admin-created accounts.
- Fixed: v1.3.2 only fixed the invite path. Creating a user directly with a temporary
password (
POST /api/users) or reassigning an existing user’s roles (PATCH /api/users/{id}) into the Super Admin role had the same two bugs: the account activated with zero effective permissions, and a caller holding onlyusers:writecould grant a role — including Super Admin — with more permissions than their own.
v1.3.2 — 2026-09-16
Section titled “v1.3.2 — 2026-09-16”Security fix — update as soon as convenient if you use invites.
- Fixed: accepting an invite into the Super Admin role activated the account with zero effective permissions instead of granting full access. If you have a user stuck in this state, this release’s migration backfills it automatically on upgrade.
- Fixed: a user holding only the
invites:createpermission could previously invite a new account into a more privileged role than their own, including Super Admin. Creating an invite now checks the requested role’s permissions against the inviter’s own.
v1.3.1 — 2026-09-16
Section titled “v1.3.1 — 2026-09-16”Internal fix release — no config, API, or user-facing behavior changes.
- Fixed a Go build issue affecting the call-stats duration handling on certain toolchain versions.
v1.3.0 — 2026-09-15
Section titled “v1.3.0 — 2026-09-15”Admin UI polish release — no config, API, or deploy changes.
- Toast notifications now surface transient action feedback (test emails sent, passwords changed, invites resent, and similar one-off confirmations) instead of an inline status message that could vanish on navigation.
- Audit log detail modal shows a real line-by-line diff instead of a flat before/after dump.
- Row action menu no longer gets clipped when opened near the bottom of a long job table.
- Job review step no longer forces horizontal scroll when a job’s request URL is long.
v1.2.0 — 2026-09-15
Section titled “v1.2.0 — 2026-09-15”- SMTP is now a Settings-page feature. Configure it from Settings → SMTP
in the admin UI, with immediate effect and a test-email button — no more
SMTP_HOST/PORT/USERNAME/PASSWORD/FROMenvironment variables. - Users can hold multiple roles. Invite creation, user creation, and user
editing now accept a set of
role_idsinstead of a singlerole_id; a user’s effective permissions are the union of every assigned role’s permissions, and a user always keeps at least one role. - Job auth secrets and the SMTP password are now encrypted at rest, using
the new required
SETTINGS_ENCRYPTION_KEYenvironment variable. Bothcoreandworkerneed this key; the three script workers deliberately do not get it.
v1.1.0 — 2026-09-14
Section titled “v1.1.0 — 2026-09-14”- Admin and Read-Only role permissions are now editable. Previously fixed at their
seeded defaults, they can now be customized by anyone holding
roles:write. The permissions editor also groups permissions by section, with a select/deselect-all control per group. - Settings is now split into sub-nav pages — Users, Global Variables, Roles, and Account (moved under Settings from its own top-level location).
- Adding a user or a global variable now happens through a dedicated modal instead of an inline form.
- Fixed a checkbox layout bug where a global CSS reset was collapsing the gap in HeroUI checkboxes.
v1.0.1 — 2026-09-14
Section titled “v1.0.1 — 2026-09-14”- Prometheus endpoint moved to
/prometheus/metrics. It was colliding with the admin SPA’s client-side/metricsroute. Update any scrape configs pointing at the old path. - Login/logout routes dropped the stale
/adminprefix. They’re now/loginand/logout. Update any bookmarks or reverse-proxy rules referencing/admin/loginor/admin/logout. - “Create Role” is now a modal instead of an always-open form, and the admin UI sidebar gained a Docs link.
v1.0.0 — 2026-09-13
Section titled “v1.0.0 — 2026-09-13”First tagged release. Highlights:
- Script job sandboxing. Bash, Node, and Groovy script jobs now run confined under
bwrap, gated behind the opt-inSCRIPT_SANDBOX_ENABLEDvariable, with a startup capability check and its own test coverage. - Automatic River migrations. Core applies River’s queue migrations at startup instead of requiring a manual step.
- Build version embedding. The build version is now embedded via
-ldflagsand surfaced throughGET /api/metaand the admin UI. - Multi-arch image publishing. Tag pushes (
v*) build and push multi-arch (amd64 + arm64) images for all fourzilean-*targets to GHCR, taggedvX.Y.Z/vX.Y/vX/latest. - Pinned, verified image dependencies. The two dependencies fetched from outside
the base image — Apache Groovy and
tsx— are downloaded during the image build and checked against pinned SHA-256 digests, with no package-manager resolution at build time. The bytes in a given image tag are therefore fixed, rather than depending on what a version range resolves to on the day it is built. - Assorted fixes: per-run temp directories for script jobs, correct working directory
for non-sandboxed runs, fresh procfs and bound tsx runtime paths under
bwrap, concurrent sandboxed-run isolation, and admin UI metrics-graph gap handling.