CHANGELOG

Captivo Access changelog

What changed in vendor access, release by release, newest first.

Captivo Access is open source; its release notes are published in English.

v2.9.1

Security

  • Patched the web framework Captivo Access runs on. It had two critical holes that could let an attacker run code on the server, and one that could tie it up with crafted requests. Access is now on the patched release, and a smaller URL-parsing weakness in a supporting library is closed too. Nothing changes for you.

v2.9.0

What's new in 2.9.0

When a person changes the e-mail address of their Captivo account, their user in the workspace follows it at once (and their sessions end, so the next sign-in carries the new address). If the new address already belongs to another user of the workspace, nothing is changed and the centre is told. Only users who belong to the Captivo organisation are affected; passkey and directory-managed accounts and self-hosted installations are untouched. No schema change, no new configuration.

v2.8.0

What's new in 2.8.0

Single sign-out from the Captivo account: signing out there (or a password or two-step reset there) now ends the person's sessions in the workspace at the same moment. Only users who belong to the Captivo organisation are affected; passkey and directory-managed accounts and self-hosted installations are untouched. No schema change, no new configuration.

v2.7.0

What's new in 2.7.0

Changes in the Captivo organisation now reach the workspace at once.

  • When a member is removed or re-roled on the Captivo account page, the workspace applies it immediately: a removed member is disabled and signed out everywhere, a member whose role changed continues with the new role from their next request. Until now the workspace learned at that person's next sign-in, which remains the fallback when the push cannot be delivered.

Only users who belong to the Captivo organisation are affected; passkey and directory-managed accounts are untouched. Self-hosted installations are unaffected. No schema change, no new configuration.

v2.6.0

What's new in 2.6.0

The organisation role on your Captivo account now runs the workspace at every sign-in.

  • One rule for roles: an Owner or Admin of the organisation arrives in the workspace as an Admin, a Member as Staff. Change someone's role on the Captivo account page and it shows in the workspace at their next sign-in. The workspace can only narrow a role (an organisation admin can be made an Operator or Auditor); a Member of the organisation cannot be made a workspace Admin, and an organisation member is never a Vendor.
  • Leaving the organisation: someone you remove on the Captivo account page can no longer sign in to the workspace and is disabled.
  • Users and invitations: users who belong to the Captivo organisation are marked "Captivo ID" in the users list; their status follows the organisation and the role choice stays within what their organisation role allows. On a workspace that belongs to a Captivo organisation the Invitations page invites vendors only (passkey sign-in, no Captivo account) and points to the Captivo account page for organisation members.

Workspaces that do not belong to a Captivo organisation, passkey accounts and directory-managed accounts are unaffected. Schema: one new nullable column on the user table; the migrate step applies it. No new configuration.

v2.5.1

What's new in 2.5.1

  • Fixed: the account-summary endpoint added in 2.5.0 answered "not found" for every workspace; the Access card on the Captivo account page now gets its live line.

v2.5.0

What's new in 2.5.0

  • New: your Captivo account page (Captivo ID 1.5.0) now shows a live line on the Access card: the workspace's name, how many sites and connectors it has, how many connectors are online, the sessions of the last 24 hours and the last activity. The workspace answers the account page on its own address with the shared service secret and only for the organisation it is linked to; nothing else changes in the console.

v2.4.0

What's new in 2.4.0

  • Changed: creating a new workspace from the cloud's sign-up page now happens on your Captivo account's registration form (Captivo ID 1.4.0). One form, one verification link; once verified you sign in with Captivo and your workspace is set up on first arrival, exactly as before. Existing workspaces, invitations and sign-in paths are unchanged, and a deployment without Captivo ID keeps its own sign-up form.

v2.3.0

What's new in 2.3.0

  • New: an organisation member joins the workspace at sign-in. Someone invited into your organisation from the Captivo account page (Captivo ID 1.2.0) now lands in your workspace the first time they sign in with Captivo, with the role the organisation gave them: owner or admin of the organisation becomes an Admin, a member becomes Staff. Previously they were turned away with "no account". This happens only on your own console, only through Captivo's sign-in (a tenant identity provider is unaffected), and after the workspace's two-step requirement has been checked.

No schema change. Upgrade with pull and up -d; the migrate step is a no-op.

v2.2.0

What's new in 2.2.0

A workspace can now require two-step verification for everyone who signs in to its console.

  • Added: Require two-step verification on the Policy page. When it is on, a console sign-in is refused unless the identity provider confirmed a second factor — a one-time code with Captivo ID or with your own provider (Entra, Okta, Auth0 all report it) — or the passkey verified the person with a PIN, fingerprint or face. It applies from the next sign-in; sessions already open are not ended.
  • Added: each session now records how it was opened, and the Sessions page shows it: Two-step, Passkey, Password. Sessions from before this release show a dash.
  • Changed: a Captivo ID sign-in that arrives without a second factor is first sent back once with prompt=login, so a session opened with a password before the person enrolled an authenticator is re-authenticated with the code instead of being refused for a policy they just satisfied.
  • Changed: turning the requirement on is refused unless your own current sign-in would pass it — nobody locks a workspace with a switch they cannot get past.

Schema change: two additive columns (Session.amr, SessionPolicy.requireSecondFactor), applied by the migrate step. Upgrade with pull and up -d.

Captivo ID is unchanged: it has stamped the amr claim on every sign-in since 1.1.0; this release is Access reading it.

v2.1.5

What's new in 2.1.5

A maintenance release: the weekly security audit of production dependencies is clean again.

  • Changed: Next.js 16.3.5 → 16.3.8 (same patch line). Closes seven advisories, among them cache poisoning in SSG/ISR rendering and information disclosure in metadata image routes.
  • Changed: nodemailer 9 → 10, closing five advisories. The manager's one use of it, sending through the SMTP server you configure, is unchanged.
  • Changed: sharp and source-map-js pinned to their patched versions through the dependency graph.
  • Changed: the Monday audit reports a failure to Slack as well as by e-mail, so a red audit is seen the week it happens.

No schema change. Upgrade with pull and up -d; the migrate step is a no-op.

v2.1.4

What's new in 2.1.4

  • Changed: session recordings no longer stop at 500 MiB. That cap dated from the time recordings were stored on the control plane's own disk; since 2.1.0 they live on your connector, so the disk it was protecting is yours to size, and a recording that quietly ended before the session did was worse than the full disk it was meant to prevent. A recording now runs as long as its session. What bounds the connector's disk is the retention window set in Policy and how long your sessions tend to run.
  • Added: an optional cap for operators who want one. Set RECORDING_MAX_BYTES on the data plane and a recording that reaches it is marked, not merely shorter: the list shows a "cut short" label, and the player says the replay ends before the session did. Unset, there is no cap. No action is needed to get the new behaviour; the schema change applies on the next migrate run.

v2.1.3

What's new in 2.1.3

  • Fixed: deleting a session recording could get stuck forever. The console would accept the delete, but the request never actually reached the connector that holds the recording's bytes, so the entry stayed around indefinitely. Deletes are now pushed to the connector immediately, the connector's confirmation is now acted on instead of discarded, and a periodic reconciliation pass retries any deletion whose connector was offline at the time — so a recording you deleted will actually disappear once its connector is reachable.

v2.1.2

What's new in 2.1.2

  • Fixed: the platform console had no usable navigation on a phone. Below 900px the six destinations (Overview, Tenants, Activity, Admins, Jobs, Settings) used to simply disappear with nothing in their place; there is now a menu button that opens them.
  • Fixed: signing in through your identity provider could show the sign-in form in Turkish even for an English-speaking console. Access now explicitly tells the identity provider to render the form in English.

v2.1.1

A follow-up to 2.1.0, fixing something that release introduced.

Large file uploads to an isolated browser work again

2.1.0 gave each admin action a time window that outlasts what it waits for. For isolated-browser file uploads the two numbers disagreed: the transfer was allowed five minutes, the window around it was thirty seconds. Any upload slower than thirty seconds failed.

Uploads no longer run inside that window at all. Permission is checked first, in its own brief step, and the transfer then runs with nothing holding it — which is the only arrangement that works for a transfer that can legitimately take minutes.

If you tried a large upload on 2.1.0 and it failed part-way, this is why.

Long operations are now visible in the log

Some admin actions hold a database connection while they wait on one of your systems. That is fine at the scale this is designed for, and it is the kind of thing that stops being fine quietly. The server now records when an operation holds one for an unusually long time, so the question can be answered with measurements instead of assumptions.

Nothing to configure; it appears in the server log only when it happens.

Upgrading

Manager only. Connectors need no change.

v2.1.0

Admin actions that reach out to your own systems no longer fail for the wrong reason.

"Test" buttons stop failing on slow-but-working systems

Testing a directory server, probing a site, sending a test email, verifying a domain, signing in through your identity provider — all of these reach a machine on your side, and all of them were being cut off after five seconds. Not because the test had failed, but because of an internal limit that had nothing to do with the test.

The clearest case: the directory test gives your domain controller twelve seconds to answer a bind and search, which is a sensible allowance. A controller measured answering in 6.6 seconds — comfortably inside that allowance — failed every single time. What you saw was a directory that did not work. What was actually happening was a probe being stopped six seconds early.

Twenty-two admin actions were affected: directory tests, site probes, domain verification, SMTP tests, the OIDC sign-in callback, isolated-browser file transfers, session replay, connector repair and log-level changes, invitations, grant decisions and access requests. Each now gets a window that outlasts what it is waiting for.

Requests can no longer hang forever

The flip side. Twenty outbound calls had no limit of their own at all — a connector that accepted the connection and then went quiet could hold a request open indefinitely. Mail sending had the same gap, in three different places, as did every DNS lookup.

Each now has its own limit, chosen for what it is waiting on. Downloads are treated differently on purpose: only the wait for the first response is limited, so a large recording or file is never cut off part-way — a truncated download looks like a corrupt file, not like a timeout.

Sending mail was the most consequential: a mail relay that stalled used to hold an invitation or an access decision open with no end, and the person waiting had no end either.

Upgrading

Manager only. Connectors need no change.

If you have an admin test that has been failing against a system you believe is healthy, it is worth trying again after this upgrade.

v2.0.5

One fix, to a warning that was telling you the opposite of the truth.

"Background jobs haven't run yet" was wrong

The system page reported that scheduled jobs had never run — on installations where they were running every few minutes. Each job records a heartbeat when it runs, and those heartbeats were being written from outside the tenant's own data scope, so the database refused them and the failure was silently ignored.

Two consequences, and the second is the worse one: the page showed a warning that did not apply, and it could never show the warning that does. The genuine "some scheduled jobs look stopped" message only appears once the scheduler's own heartbeat looks healthy — which it never did — so a job that really had stopped would have gone unreported.

Heartbeats now land, the false warning is gone, and a real stalled job will be named.

This was found while verifying the site-health fix in 2.0.4: confirming that the job had run at all meant reading container logs, because the console could not answer the question.

Upgrading

Manager only. Connectors need no change.

v2.0.4

Two reliability fixes. Nothing to change on your side; no visible difference to the console.

Site health results are no longer lost when a site is slow

The hourly health check probes each of your sites and records whether it answered. Those probes were running inside a single database transaction with a five-second budget — so if one site was slow to respond, the budget ran out and the whole round's results were never recorded. On the console, that looks like health information that quietly stops updating, which is the opposite of what a monitoring feature should do when something is wrong.

The check now reads its list, probes the sites, and records the results as three separate steps. A site that takes a minute to time out now simply gets marked unreachable, which is the answer you wanted.

The same applies to the notifications a health change triggers: a slow mail server can no longer cost you the health result it was reporting on.

Releases no longer depend on Google Fonts being reachable

The four typefaces the console uses were downloaded from Google every time this product was built, which meant a build could fail for reasons that had nothing to do with the code — and the error it produced pointed at the code, not the network. It cost two of six builds on 27 September.

The typefaces now ship with the product. The console looks exactly as it did: the same files, the same weights, verified against the previous output. For self-hosters this also means building from source needs no outbound connection for fonts.

All four are licensed under the SIL Open Font Licence; the licence and per-family attribution ship alongside them.

Upgrading

Manager only. Connectors need no change; 2.0.1 and newer stay compatible.

v2.0.3

Disk housekeeping that was not happening. No change to access, recording or replay.

Upgrading a connector now reclaims its own disk

The connector install/update command the console generates has always included a cleanup step, but it ran first — before the new images were pulled, while the images it was meant to remove were still in use. So every upgrade left its replaced images on disk and tidied up the previous upgrade's instead, one release behind. On a host upgraded twice, 1.5 GB was sitting there until someone cleaned it by hand.

The cleanup now runs last, after the new containers are up, and reclaims what that upgrade just replaced.

It is also limited to Captivo Access images. Before, it removed every unused image on the machine, including ones belonging to other software you run on the same host. If your connector shares a host with anything else, that command no longer touches it.

Nothing to do differently — copy the command from the connector's page as usual.

The documented server upgrade now reclaims disk too

docker compose pull + up -d leaves the replaced version on disk, and unlike the connector's images those keep their version tag, so an ordinary cleanup command skips them entirely. Thirteen unused images had accumulated on one server, the database-migration image alone weighing 1.05 GB per version.

The update instructions now include the cleanup step that actually reaches them, with a note on what it will and will not remove: the version you are running is always kept; the previous one is removed, so going back means downloading it again.

Upgrading

Manager and connector can move independently; neither is required by the other for this release.

v2.0.2

A correction to what the recordings list and the erasure audit trail reported.

The recordings list no longer reports a count that was not one

The recordings list had an Events column, and the erasure audit entry read "… 24 events, 216367 bytes …". Neither number was an event count: it counts the pieces a connector writes a recording in, so a session containing six typed commands reported 24.

The audit entry now says chunks, which is what it always counted. Audit entries cannot be corrected after the fact, so a reviewer reading one should not have to know that the noun was wrong.

The column is gone from the list. Once labelled truthfully it said nothing that Duration and Size do not — how many pieces a recording was written in is an internal detail, not something to read a session by.

Nothing about recording, replay, search or retention changes.

Upgrading

Manager only. Connectors need no change for this release; 2.0.1 connectors stay compatible.

v2.0.1

Two fixes to session recording and monitoring. Upgrade the manager and every connector together — see below.

Session replay no longer hangs on "Loading…"

Opening a recording showed "Loading…" and 0:00 / 0:00 forever, while the typed-input timeline beside it listed only some of the keystrokes it had captured.

A session records more than one stream at the same time — the screen and the keystrokes — and both were being written to the same filenames on the connector, each overwriting the other as the session went on. The player was handed a stream with the wrong data spliced into it and could never start. Each stream is now stored separately, so a recording plays back whole and the timeline lists every keystroke that was captured.

Erasing a recording still erases everything it holds, keystrokes included.

Monitoring a live session no longer drops after a few seconds

Watching an active session connected, showed the screen, then failed within seconds with "The session ended or is no longer available." — while the session was still running. Reconnecting gave the same result.

A viewer that only watches still has to answer the remote desktop service's periodic check, and those answers were being discarded along with the viewer's keyboard and mouse input. The service treated the silent viewer as gone and closed it. Watching now keeps working for as long as the session lasts; a viewer still cannot type or click until they take control.

The server log now records why a view ended, so a dropped viewer is no longer indistinguishable from a session that simply finished.

Upgrading

Recordings made by 2.0.0 cannot be played back — they were stored in the broken layout and were already unreadable. Recordings made from 2.0.1 onward are unaffected.

Upgrade the manager and every connector to 2.0.1. A 2.0.0 connector paired with a 2.0.1 manager will report an error when a recording is opened instead of playing it.

v2.0.0

Session recordings now stay on your own infrastructure.

Recordings, keystroke logs and isolated-browser video are written to the connector inside your network, encrypted with a key the connector generates and never sends anywhere. The control plane keeps only an index — which recording, which resource, which user, how large — and cannot decrypt the content. Command search runs on the connector too, so the text never has to leave to be searched.

⚠️ Before you upgrade

1. Upgrade your connectors first. A 1.x connector does not understand the new recording frame and its sessions' recordings are lost.

Use the upgrade command from the console — Connectors → your connector → update command. Do not hand-write a docker run for this: a connector deployment is three containers on a shared captivo-gateway network (connector, captivo-guacd, captivo-kasm) plus several volumes, and the generated command has all of them with the right environment. It pulls :latest, which now points at 2.0.0.

Upgrade every connector first, then the manager and data plane.

2. Back up the connector's volume — and keep it backed up. Nobody else has a copy, including us. If the volume is lost the recordings are gone permanently.

3. Back up /data/recording.key with it. The recordings cannot be read without that file and it cannot be reissued. The connector warns at start-up if its recording directory is not on a mounted volume.

What changes for you

  • Search still works. It runs on the connectors that hold the text and merges the answers.
  • Playback still seeks. Byte ranges are passed through to the connector, so scrubbing a long video does not download it from the start.
  • An offline connector makes an answer incomplete, not empty. The console says which connectors could not be searched rather than reporting "no results" — the two mean opposite things during an investigation.
  • Erasure completes when the connector next connects. Asking the console to delete a recording is recorded immediately and marked pending; the content goes when the connector confirms. An offline connector delays it, and the console does not claim otherwise.
  • Retention is enforced by the connector, which owns the files, and it reports back what it removed.

Breaking changes

  • Connectors must be at 2.0.0. Older ones lose their recordings (logged once per session).
  • Four internal endpoints are retired and answer 410: recording ingest-guac, ingest-video, finalize-video and keyevents. Nothing calls them; they refuse rather than store, so a stale data plane cannot quietly keep a central copy.
  • SessionRecording gains a nullable purgePendingAt column.

A Data Processing Agreement covering this is published at https://captivo.io/en/dpa-access.

v1.14.1

A fresh self-hosted install now asks for a version that exists

Installing from scratch could fail while pulling images. The compose files pinned a fallback version written in by hand — 1.9.0 in one file and 1.10.0 in the other, while the current release was 1.14.0. That was invisible for as long as the old image registry still held every old tag: the fallback was stale, but the stale version was still there to download.

Moving the images to a new registry namespace in v1.14.0 removed that cover. A default install began asking for a version that had never been published there, and stopped.

What changed

  • The version this checkout ships is recorded in one place (a VERSION file), and both compose files read the same value.
  • setup.sh writes that version into the .env it generates, so a first install pins something that exists rather than relying on the fallback at all.
  • A test compares both compose fallbacks against VERSION, so the next time one goes stale the build says so instead of an install failing months later.

Existing installs are unaffected — they already pin a working version in their own .env. Upgrading is the usual git pull, set CAPTIVO_VERSION in .env, and re-run ./setup.sh.

Full changelog

v1.14.0

The project has moved to the Captivo organisation

This repository now lives at captivo-io/captivo-access, and the container images now live at *ghcr.io/captivo-io/captivo-access-**. Nothing about the product changed; the code has an organisation behind it instead of a personal account.

If you have a self-hosted install, pull the repository and re-run ./setup.sh when convenient — the compose files point at the new image namespace. The old images at ghcr.io/kurtserdar/* remain public, so an existing install keeps working on the version it has pinned until you choose to move.

Links keep working. The old repository address redirects here, so bookmarks, clones and any documentation pointing at the old URL continue to resolve.

> Installing from scratch on this tag hits a version pin that was already stale before the move — use v1.14.1 instead, which fixes it.

Full changelog

v1.13.16

Plan names now match across Captivo products

The plan tiers in Captivo Access are named free, pro and enterprise — the same three names Captivo Portal uses. Previously Access called its middle tier "standard", so an organisation using both products saw two different vocabularies for the same thing.

If your workspace had never been assigned a tier, its allowance line read "Your plan: 0 of 1 connectors used". It now reads "Free plan", which is what it always was.

Nothing about your allowance changed, and self-hosted installations are unaffected: the tier is a label there, and the caps it would describe are not applied.

Full changelog