v2.19.7
semaphoreui/semaphorev2.19.7Aug 6, 2026by github-actions[bot]
AI Summary
Semaphore UI v2.19.7 introduces a graphical workflow editor, Docker and Kubernetes executors, and JWT/OIDC ID tokens for keyless authentication. The release removes BoltDB in favor of SQLite/MySQL/Postgres and implements real server-side pagination to handle large-scale task histories.
Key Highlights
- Graphical workflow editor with node-based task orchestration (Drawflow-based)
- Docker and Kubernetes executors for containerized task execution (Pro/Enterprise)
- JWT/OIDC ID tokens enabling keyless authentication to Vault, AWS, GCP, and Azure
- Encryption key rotation with a labelled keyring
- Real server-side pagination for task history to support millions of tasks
- BoltDB removed; migration to SQLite/MySQL/Postgres required
Breaking Changes
- BoltDB removed; users must migrate to SQLite, MySQL, or Postgres before upgrading.
- Runner encryption keys removed; runner and server must both be on v2.19 with TLS protection.
- Task list APIs now use cursor-based pagination; clients relying on fetching the newest 200 tasks must adapt.
- Runner `active` flag removed from registration.
- Project backups no longer contain runner tokens; runners must be re-registered after restore.
New Features
- Workflows: Graphical editor (Drawflow-based) with task, approval, and note nodes, plus REST API endpoints.
- Executors: Docker and Kubernetes runner support with configurable images, namespaces, and resource limits.
- Authentication: OIDC provider support allowing tasks to authenticate using short-lived JWTs.
- Security: Password change now requires current password, CSRF validation, and session cookies marked Secure.
- Observability: Prometheus metrics endpoint and configurable SSH host key checking.
- Performance: Server-side pagination, Git operation serialization per repository, and UI request optimization.
- Secrets: OpenBao secret storage support, survey secrets working on remote runners, and encryption key rotation.
Full Release Notes
<img width="1537" height="730" alt="image" src="https://github.com/user-attachments/assets/bb0ef600-70cf-4943-8247-debb59e438a7" />
# Semaphore UI v2.19
## Highlights
- **Workflows** — build multi-template pipelines with a graphical editor
- **Docker and Kubernetes executors** — run tasks in containers/pods instead of on the server (Pro/Enterprise)
- **JWT / OIDC ID tokens for tasks** — keyless auth to Vault, AWS, GCP, Azure
- **Encryption key rotation** with a labelled keyring
- **Real server-side pagination** for task history — projects with millions of tasks no longer choke
- **BoltDB removed** — SQLite/MySQL/Postgres only
- A large batch of **security hardening** across the API
## Workflows
A workflow is a graph of task templates that run as one unit.
- Graphical editor at `/workflows/new` and `/workflows/:id/edit` (Drawflow-based), with palette
drag-and-drop, edge condition selectors, live cycle/self-edge guards, a validation problems panel,
and auto-layout for position-less workflows. Node positions are persisted.
- Node kinds: **task** (runs a template), **approval** (gates the run, with timeout and message), and
**note** (free-form annotation, never executed).
- Per-node task parameters (`task_params_id` on workflow nodes).
- API: `/project/{id}/workflows` (CRUD), `/{workflow_id}/run`, `/runs`,
`/runs/{run_id}/{stop,artifacts,approvals}`, `POST /runs/{run_id}/approvals/{node_id}`.
## Executors: Docker and Kubernetes (Pro/Enterprise)
Runners can now execute tasks in a container or a pod instead of directly on the runner host.
- **Kubernetes** (`runner.executor.k8s`): `kubeconfig`, `namespace` (default `semaphore`), `image`,
`helper_image`, `service_account`, `pull_secrets`, `poll_interval_seconds`, `cleanup_grace_seconds`.
- **Docker** (`runner.executor.docker`): `host`, `tls_verify`, `cert_path`, `image`, `helper_image`,
`network`, `pull_policy`, `cpu_limit`, `memory_limit`, `privileged` (off by default), poll/cleanup
timings.
- Each option also has a `SEMAPHORE_RUNNER_K8S_*` / `SEMAPHORE_RUNNER_DOCKER_*` environment variable.
- New `semaphoreui/job` and `semaphoreui/helper` images are built and published by CI; the job image
ships Terraform/OpenTofu/Terragrunt and `paramiko`.
- **Per-template executor image** — a template can override the container image used for its tasks
(`project__template.executor_image`).
## Task JWT / OIDC ID tokens
Semaphore can now act as an OIDC provider for running tasks, so jobs authenticate to external systems
without long-lived credentials.
- Short-lived ECDSA-signed JWTs issued per task, published via `GET /.well-known/jwks.json`.
- Per-template JWT options (multiple audiences, per-token TTL) configured in the template form; claims
carry IDs only.
- Config block `jwt`: `enabled`, `issuer`, `default_ttl` (1h), `max_ttl` (24h), with `SEMAPHORE_JWT_*`
env vars.
- Template JWT params persisted in `project__template.jwt_params`.
## Secrets & encryption
- **Encryption key rotation.** New `encryption` config block with a labelled keyring: inline `keys`
(value or file), or a `keys_folder` where each file is a key named by its filename, plus
`active.secret_key` / `active.option_key` pointers. Ciphertext now carries a key ID, so keys can be
rotated without a big-bang re-encrypt. `keys_file` + `keys_poll_interval` (default `15s`) allow hot
reload. The legacy flat `access_key_encryption` still works and is used when `encryption` is unset.
- **`option_encryption`** — separate key for encrypting DB-stored options.
- **Survey secret variables now work on remote runners and in HA.** Previously a survey `secret` value
lived only in the memory of the node that accepted the task, so it arrived empty on remote runners
and was lost across restarts. Secrets are now persisted as task-bound access keys
(`access_key.task_id`, `access_key.expire_at`, cascade-deleted with the task); TTL is derived from
`MaxTaskDurationSec` (+1h queue allowance, 24h when unlimited).
- **OpenBao** secret storage type (routed through the Vault provider), with its own icon in the UI.
- TLS-skip-verify checkbox for Vault/OpenBao storages.
- Synchronized and read-only secret fields are no longer wiped on update.
## Runners
- **Online/offline status** shown on the Runners page, derived from heartbeat liveness (webhook-driven
runners are always dispatch candidates).
- **Hung-task recovery.** Runners report their process start time (`X-Runner-Started-At`, stored in
`runner.started_at`), which lets the server detect a runner that restarted and silently lost its
in-memory job pool. `starting` tasks are reassigned to a healthy runner; `running` tasks get a
recovery window and are then failed with a clear message. Tunables in the new `runners` config
block: `offline_timeout_sec` (120), `task_fail_timeout_sec` (420), `reconcile_interval_sec` (30).
- Tasks reassigned away from a runner are terminated on the old runner.
- **Per-runner RSA encryption keys removed** — secrets are protected by TLS on the wire instead. This
removes ~380 lines of key-exchange code from the runner protocol.
- Runner registration tokens are stored hashed, with an expiry; invalid registration tokens are
rejected with `400`, and token prefixes are validated.
- Fixed a TCP connection leak in the runner client.
- Runner options moved into a dedicated `runners` config struct (old flat options still read).
- The `active` flag was dropped from runner registration; only registered runners are used.
## Performance & scale
- **Keyset pagination for task history.** The history page used to fetch the 200 newest tasks and page
through them client-side. The backend now returns one page at a time via a `before` cursor +
`count` (legacy `limit` still accepted), with no `COUNT(*)` and no `OFFSET` — so page depth no
longer degrades on projects with millions of tasks. Applies to project history, template task lists
(`/templates/{id}/tasks`, `/tasks/last`, `/stats`) and the dashboard.
- Task lists reload at most once every 5 seconds; several redundant UI requests removed.
- **Git operations are serialized per repository directory** (`KeyLock`). Templates with
`AllowParallelTasks=true` shared one working copy, and concurrent `git pull` + `git checkout` could
corrupt it. `updateRepository()` + `checkoutRepository()` is now one critical section, covering both
local and runner execution. Inventory repo operations are serialized the same way.
- **BoltDB removed.** SQLite replaces it everywhere, including session storage; the
permanent-connection flag is gone.
- Stale HA pool state is released on duplicate finalize.
## Templates, tasks & UI
- **Dynamic playbook picker** — the template form lists actual playbook files from the repository
(`GET /repositories/{repository_id}/playbooks`) instead of requiring a hand-typed path. Playbooks
reset when the branch changes, and branch-load failures no longer block the playbook list.
- **Survey variable target** — a survey var can now be delivered as a process environment variable
(`target: "env"`) instead of the app-specific CLI way (`--extra-vars` / `-var` / CLI arg). The env
var name is the variable name verbatim, so `TF_VAR_foo` works. Stored in the existing `survey_vars`
JSON — no migration.
- **New survey variable types**: `int`, `text` (multiline), and restyled `enum`.
- **Typed variables in variable groups** (including `int`).
- **Skip Ansible Galaxy install** — per-template and per-task option to skip role/collection
requirement installation.
- Dropdown cards for JWT and schedule sections in the template form; new `DropdownCard` /
`HighlightedCard` components.
- **Czech translation** added.
- Copy-to-clipboard icon is visible in light mode; running-task spinners fixed; template form bottom
padding fixed.
- Schedules are validated with the server-side cron parser (client and server no longer disagree).
- Integration variable extraction preserves JSON objects and arrays instead of stringifying them.
## Observability
- **Prometheus metrics.** New `metrics` config block (`enabled`, `username`, `password`, plus
`SEMAPHORE_METRICS_*`) exposing `/api/metrics` with Go/process collectors,
`semaphore_tasks_running` (gauge) and `semaphore_tasks_total{status}` (counter). The endpoint is off
by default and can be protected with basic auth.
- **Namespaced debug logging.** A Node.js `debug`-style filter via `--debug-filter` /
`SEMAPHORE_DEBUG_FILTER` lets you turn on verbose tracing for one subsystem (runners, LDAP,
schedules, git) without the noise from everything else. Applies to syslog hooks too.
`SEMAPHORE_LOG_LEVEL` / `--log-level` behave exactly as before.
- Many new contextual debug statements across runners, tasks and auth.
- **SSH host key checking is configurable**: `ssh.known_hosts_file`, `ssh.config_path`,
`ssh.strict_host_key_checking` (`no` / `yes` / `accept-new`). With no known-hosts file configured,
Semaphore uses a persistent trust-on-first-use file under `TmpPath` — first connection pinned, later
host-key changes rejected.
## Security
- **Changing a password or managing 2FA/TOTP now requires the current password** (CWE-620 —
unverified password change).
- **Origin/Referer validation** on state-changing requests (CSRF hardening).
- Session cookies marked `Secure` over HTTPS.
- Custom role creation now checks the caller's permissions.
- Task branch override is only allowed when the template sets `AllowOverrideBranchInTask`.
- Git URL validation; `--end-of-options` passed to git so a crafted ref cannot be read as a flag;
commit hashes format-checked; branches validated before repository browsing; playbook paths
validated.
- Access key payloads validated; template `app` validated.
- Project/integration ID ownership verified on integration API requests; `template_id` verified on
vault update.
- Runner tokens are no longer written into project backups.
- API returns after a write error instead of continuing with a partially written response.
- Runner per-runner encryption keys removed in favour of transport TLS.
- Fixed a nil-pointer panic when closing an uninitialized DB.
- CodeQL: potential integer overflow in allocation size computation.
- Security SLA published in `SECURITY.md`; release artifacts signed with the
`security@semaphoreui.com` GPG key.
---
## Upgrade notes
### Breaking / behavioural changes
1. **BoltDB is gone.** If you are still running a BoltDB installation, migrate to SQLite (or
MySQL/Postgres) before upgrading. `bolt` is no longer a valid `dialect`.
2. **Runner encryption keys removed.** Runners and server must both be on 2.19; the key-exchange step
no longer exists. Make sure runner↔server traffic is TLS-protected.
3. **Task list APIs are paginated.** `GET /project/{id}/tasks` and the template task endpoints return
one page and take a `before` cursor plus `count`. `limit` is still accepted for backward
compatibility, but clients relying on getting the newest 200 tasks in one response must page.
4. **Runner `active` flag removed** from registration.
5. Project backups no longer contain runner tokens — re-register runners rather than expecting a
restore to carry credentials.
### New configuration
`encryption`, `jwt`, `metrics`, `runners`, `ssh`, `runner.executor.{docker,k8s}`,
`ldap_tls_skip_verify`, `option_encryption`, `external_auth_email_matching`.
All are optional; existing configs keep working. `use_remote_runner` and the flat runner options are
deprecated in favour of `runners`. The JSON schema (`config.schema.yaml`) and the config-option
reference docs have been regenerated.
## Documentation
Docs are now a git submodule and are embedded in the binary, so they ship offline. New/updated pages:
Docker & Kubernetes executors, encryption and key rotation, JWT auth during task execution, OpenBao
secret storage, IdP-initiated OIDC, PingFederate TLS, license activation, dev setup, logging, runner
registration, and regenerated CLI reference.
## Dependencies & build
Go 1.26.5; Debian 13 and Alpine 3.24 base images; `go-git` 5.19.2, `go-oidc` 3.20.0,
`golang.org/x/crypto` 0.54.0, `go-ldap` 3.4.14, `modernc.org/sqlite` 1.54.0; frontend `axios` 1.19,
`cron-parser` 5.7, `nanoid` 6, `shell-quote` 1.10. MariaDB 12.1 migration compatibility fixed.
`THIRD-PARTY-LICENSES.md` regenerated.
## Known gaps
Two plans in `AGENTS/plans/2_19` did **not** ship in this line and are still open:
`password-hash-argon2id` (bcrypt → Argon2id) and `runner-token-hash` (hashing the runner bearer token
itself — only *registration* tokens are hashed today). The `runner-version-platform-uptime` plan
shipped partially: uptime and online/offline status landed, but runner version/OS/arch columns did
not.