Skip to main content
Back to all

Vikunja 2.7.1: 41 security fixes, plus a pile of bugfixes

2026-10-20

If Vikunja is useful to you, please consider signing up for Vikunja Pro, buying me a coffee, sponsoring me on GitHub or buying a sticker pack. I'm also offering a hosted version of Vikunja if you want a hassle-free solution for yourself or your team.

Not even three weeks after 2.7.0, here’s 2.7.1. It’s a patch release on paper, but a big one: 264 commits, 150 of them fixes. Most of that is the result of reported security issues and an automated security audit, which together fixed 41 security issues. Please update as soon as you can.

Speaking of security, we now have a security-announcement-only newsletter! Sign up at the bottom of the page to get notified a few days in advance when we push a new release with fixed security issues.

Security#

Sixteen reports from third-party contributors, 25 from an automated security scan. We checked and verified each one. That makes 41 advisories in total: 2 high, 24 medium, 15 low. Most are small on their own. The earlier announcement said 13. That number grew to 41 once the scanner results were triaged.

Nine were introduced in features added in the last year (v2 API, bot users), the rest have been there since the code was first written.

The two high ones:

  • Deleting an account could hand a project to someone else. Vikunja promoted a read-only share to admin or owner on every project the deleted account could see, not just its own. Anyone could trigger this by deleting a bot. (advisory, thanks to @lissy93 and @dagadjno!) This issue has existed since account deletion was added (v0.18.0, September 2021).
  • Any task on the instance could be read through a webhook. A crafted Kanban move sent the task from the request body to the project’s webhooks, and payloads included related tasks the webhook owner can’t see. (advisory, advisory, thanks to @M7Saad and @lissy93!) This has existed since webhooks were added (v0.22.0, December 2023).

Access control and sharing#

  • Read-only members could see webhook target URLs, which for Slack or Discord are the credential. (advisory)
  • A token scoped to project webhooks could manage account-wide webhooks too. (advisory)
  • API token permissions meant different things on v1 and v2, so a read-only token could delete through v2. The keys are renamed now and existing tokens migrated. (advisory, thanks to @dylanzonix!)
  • Bot tokens kept working after their owner was disabled. (advisory)
  • Tasks could be moved into another project’s bucket. (advisory, thanks to @manus-pi!)
  • Deleting a bucket via another view’s URL skipped the last-bucket check. (advisory)
  • Anyone could flag a team as SSO-managed, after which its members can’t be removed. (advisory, thanks to @EclipsSec and @l1ght-man!)
  • Trying to leave a team you’re not in revealed whether it exists. (advisory)

Sessions, login and 2FA#

  • Login session tokens kept working for a while after logout, password change or being disabled, and could mint new credentials in that window. (advisory, thanks to @r1alfarraj!)
  • Enabling 2FA didn’t ask for the password, so a stolen session could lock you out. (advisory, thanks to @dagadjno!)
  • Password re-checks on sensitive actions had no rate limit, and 2FA could be disabled with the password alone. (advisory, thanks to @M7Saad!)
  • A cross-site form could log your browser into the attacker’s account on the v2 API. (advisory)
  • Changing the API URL sent your stored tokens to the new server. (advisory)
  • Migration callbacks weren’t bound to the user who started them. (advisory)

Secrets in logs and files on disk#

  • Password reset, email confirmation and account deletion tokens ended up in access logs. They’re in the URL fragment now, old links still work. (advisory)
  • Link share hashes landed in access logs too. The log now writes the route template instead of the raw path. (advisory)
  • Stored files and the SQLite database were world-readable. (advisory)
  • So was the vikunja dump archive. (advisory)
  • Export archives were never cleaned up, not even after account deletion. (advisory)
  • With a custom root path, plugins and logs used the working directory instead. (advisory)
  • The release build toolchain was pulled by a mutable tag. (advisory)

Making the server fall over#

  • Image decoding had no memory or concurrency bound. A few tiny images could cost gigabytes. (advisory, advisory, thanks to @manus-use!)
  • Avatars were cached once per requested size, forever. (advisory, thanks to @NotAFlightRisk!)
  • Imports had no item limit and migrations downloaded attachments without a size limit. New migration.maxitems setting. (advisory, advisory, advisory, thanks to @manus-use!)
  • One CalDAV task with thousands of relations created a placeholder task for each. (advisory)
  • Unlimited Kanban buckets, two queries each on every read. Now capped at 100 manual and 50 filter buckets per view. (advisory)
  • A bucket configuration without a filter crashed the view. (advisory)
  • page=0 on the v1 API returned everything. (advisory, thanks to @Ravin06!)

Content injection#

  • A crafted table cell color could inject CSS into every viewer’s browser, enough for a fake login overlay. (advisory, thanks to @r1alfarraj!)
  • Images in descriptions could make every viewer’s browser probe localhost and LAN addresses. (advisory)
  • Titles weren’t escaped in the header of comment and mention emails. (advisory, thanks to @LeoWSY-hashblue!)

veans#

Four in veans, the agent CLI:

  • Server discovery could fall back from https to http. (advisory)
  • A .veans.yml in an untrusted repo could send VEANS_TOKEN to an attacker’s server. veans now asks before talking to a new server. (advisory)
  • Task and project titles went straight into the agent’s instructions. (advisory)
  • veans init followed symlinks and could write outside the repo. (advisory)

Attachment previews#

Image previews for attachments and Kanban cover images used to be generated on demand and cached in the keyvalue store, which meant Redis filled up with image blobs that never expired. Previews are now stored next to the file itself, all sizes are generated from a single decode, and they’re streamed instead of loaded into memory first. Cover images on Kanban cards only load once the card is visible (#4147, #4146, #4148). This could lead to DoS when some preconditions were met, which is why it is also covered by advisories above.

If you use Redis, vikunja repair attachment-previews removes the old cached previews. It scans every key, so run it once things are quiet.

Signed Docker images#

Release Docker images are now signed with cosign and come with a build provenance attestation, so you can verify an image really came from our CI (#4151).

Fixes and improvements#

  • LDAP users and groups are now matched by an immutable directory id instead of their name, and group sync only queries the groups the user is in (#4096, #4095).
  • The MCP server has a new filter_syntax tool so your assistant can look up how filters work instead of guessing, and read tools can return only the fields you ask for (#4182, #4181).
  • Editor toolbar buttons that don’t fit move into a “More” menu instead of wrapping (#4167).
  • The time entry list shows the date of each entry (#4205).
  • Bulk updates no longer clear assignees, reminders or favorites you didn’t mention (#4166).
  • Tasks synced over CalDAV with more than one category now get all of them as labels. Thanks to @Tyagiquamar for contributing this in #4183!
  • Bot users got default reminder settings, which fixes a crash in the reminder parser. Thanks to @Tyagiquamar for contributing this in #4089, and for #4119 too!
  • Monthly repeating tasks on the 31st no longer skip months. Thanks to @Kshot3000 for contributing this in #4107!
  • Gantt bars are always at least one day wide. Thanks to @creightonfrance for contributing this in #4116!
  • Escape closes Quick Actions reliably. Thanks to @busslina for contributing this in #4129!
  • The PWA install banner stays hidden until you’re logged in. Thanks to @mnkprs for contributing this in #3964!
  • Postgres passwords with special characters work in the connection URL (#4092).
  • Migrations on MySQL skip indexes on text columns instead of aborting (#4133).
  • Dragging a task on a phone auto-scrolls between Kanban buckets (#4164).
  • A timer started with a custom start time actually starts there (#4157).
  • Quick Add Magic no longer reads a date range as the due date (#4134).
  • From this release on, Vikunja is built with Go 1.27.3, which protects against the eMudhra Intermediate CA Compromise.

Things to check when upgrading#

A few of the security fixes change behavior you might rely on:

  • API token permission keys were renamed. Existing tokens are migrated, but if you create tokens via the API, check the new names.
  • The access log writes the route template instead of the raw URI.
  • New limits: 100 manual buckets and 50 filter buckets per view, and migration.maxitems (default 100000) per import.
  • The v2 attachment download route now ignores preview_size. Previews come from /tasks/{task}/attachments/{attachment}/preview?size=.

How to Upgrade#

To get the upgrade, simply replace the Vikunja binary with the new release from the downloads page or pull the :latest docker image.

You can also check out the update docs for more information about the process.

Closing#

As usual, you can find the full changelog in the GitHub repo.

If you have any questions about this release, please reach out either in the community forum, Bluesky, or Mastodon.

Thank you for using Vikunja, and I look forward to bringing you more enhancements in future updates!