Redmine imports can keep people's passwords
Until now, accounts imported from Redmine arrived without a usable password, and everyone had to reset theirs. Run the import with --import-passwords and the Redmine password hashes come across too. Each one is upgraded to bcrypt the first time that person signs in, so nobody notices anything. It is off by default. Administrators and LDAP accounts still need a reset.
The importer also now brings Redmine's anonymous and non-member access across. It lands on per-project public access and does not turn on the instance-wide switch, so nothing becomes visible to visitors unless you decide it should. The guide to migrating from Redmine and the user guide page cover the full flow.
Plugins for password hashes and import sources
If you are moving from a system other than Redmine, two new extension points help. A plugin or the PASSWORD_HASHERS setting can add support for another system's password hashes, and you enable each algorithm explicitly with LEGACY_PASSWORD_ALGORITHMS. Plugins can also add migration sources, and python -m specivo.cli.import_data --source NAME runs any of them. The old specivo.cli.import_redmine command is replaced by import_data --source redmine. See password hasher plugins and import source plugins.
Search fixes, and search by issue key
Searching for ACME-42 now finds that issue and every mention of it, in all search modes. That includes the old keys of issues that have been moved between projects.
Several search problems from earlier versions are fixed:
- New comments were not found by any search, and edits were not re-indexed.
- Deleted wiki pages still matched keyword search.
- Keyword search returned short pages after the first one.
- Semantic search could miss visible matches that ranked behind hidden ones. It also scanned every embedding. It now uses a vector index.
- A misconfigured embedding model broke indexing and search. Semantic search now turns itself off instead.
- The search backfill reported success when no model was present and skipped some attachments. It now reports what it actually embedded.
Content is now indexed when it is saved, so keyword search finds new and edited content at once. Embeddings for semantic search follow from the worker. That worker is a new celery-embeddings service, which runs embedding jobs in one process so the model is loaded once. Only the bundled local embedding model is supported now, and the endpoint for registering other models is gone. A new python -m specivo.cli.download_model command fetches the model at a pinned revision and checks its SHA-256.
Roles and trackers get visibility settings
Two new admin pages, Roles and Trackers, expose settings that used to be fixed. A role can see all users or only the members of its projects, and all time entries or only its own. A tracker can make new issues private by default. Existing roles and trackers keep today's behaviour until you change them.
Time tracking permissions are enforced
If you use Specivo for time tracking, check this one. View time entries, Log time and Edit/delete any time entries now apply everywhere. Before, anyone who could open a project could read its time entries, and timers could be started in any project.
With the default roles, Reporter and Non member no longer see time entries. Agent is granted View time entries by the upgrade, so service accounts can read back the time they log. A custom role that has Log time but not View time entries can still log time but will not see it afterwards.
Link to attachments by filename
In issues, comments, the preview and the wiki, you can now link to an attachment by its filename: [spec](spec.pdf) for a link and  for an image.
Security fixes
Attachments. Attachments of issues a reader could not open could be downloaded by id and shown on wiki pages. Attachments of deleted wiki pages also stayed searchable.
Time entries and versions. Time entries could be read, logged and edited without the matching permissions. Shared versions of private projects were not filtered by access.
MCP sessions. An MCP session could be resumed with a different API key, or after its key was revoked, by anyone who knew the session id. MCP tool errors could also expose internal exception text. They now report "Internal error while executing tool", request bodies are capped at 4 MiB, and serverInfo.version reports the Specivo version.
Denial-of-service cases. Crafted Markdown could keep a worker busy for minutes on every view, and deeply nested lists caused errors. Text with many unclosed <!-- took minutes to index and to show in search results. Long search queries could fail, and a very deep search offset could make the database rank millions of rows. Markdown fields are now limited to 262,144 characters, and text too complex to render quickly is shown as plain text. Render limits count CPU time per field and per request and scale with the measured speed of the host, so large wiki pages are no longer shown as plain text on a busy server.
Other. Indexing errors could write content to the logs. Change password, sign out and session revocation now need a CSRF token when called with a session cookie.
Containers no longer run as root. The app containers now run as UID 10001, which owns only its own subdirectories of the data directory. A one-shot init-data service prepares them without following symlinks or hard links.
All dependencies were also upgraded to their latest versions, including the MCP SDK 2.x, Pillow 13 and PyJWT 2.15, which clears all open security advisories. CI now runs Trivy scans, and fixable HIGH or CRITICAL vulnerabilities in the image block the push to Docker Hub.
Smaller changes
- Relative times stay relative for the first 24 hours instead of switching to "Yesterday" at midnight.
- The remaining untranslated interface text is translated, and dates follow the interface language.
- Comments saved in the same moment no longer appear out of order.
- The release bundle's
configure.pyno longer writes outside the bundle, and the model download no longer points to a missing release asset. maketargets for the model download, backfill, reindex and import work from the release bundle.
What to do when upgrading
Back up your database and data directory first. The upgrade applies eight migrations (0033 to 0040) on startup.
- Stop the app containers before
docker compose up -d. The newinit-dataservice must not run while an old worker is still up. If you keep your own Compose file, addinit-data, and addcelery-embeddingsor setEMBEDDING_QUEUE=celery. - Run a search reindex after upgrading (Administration → Settings). If you use semantic search, also run
python -m specivo.cli.backfill_embeddingsonce so older comments and edits get embeddings. - Check your custom roles for View time entries, as described above.
- Scripts that rely on session cookies must now send the
X-CSRF-Tokenheader on change-password, sign-out and session-revocation calls. - One migration builds a vector index with
CREATE INDEX CONCURRENTLY. Specivo keeps working, but the start of theapicontainer waits until it finishes. That is seconds for tens of thousands of chunks and longer for millions.
The upgrading guide has the full details, including container permissions and the vector index.
How to get it
Pull the Docker image with docker pull specivo/specivo:0.9.0. For servers without internet access, the GitHub Release includes an offline bundle. For installation and upgrade steps, see the user guide.