github nilsteampassnet/TeamPass 3.2.1.4

4 hours ago

What's Changed

This is a maintenance and feature release on the 3.2.1 line. It closes a folder-scope authorization bypass in the search backend, replaces the Search page with a faceted search, turns the "Last items seen" sidebar into a three-tab Quick access panel, makes the Health page and the backup subsystem report accurately, and repairs the login page when it has been left open longer than the server-side session. Upgrading is recommended for all installations, and required for anyone whose users rely on the Search page - the ACL bypass is reachable by any authenticated account.

This release changes the database schema. UPGRADE_MIN_DATE is raised, so every installation goes through the upgrade wizard once.

🔒 Security fixes

  • Folder scope bypass in the search backend let any authenticated user enumerate any folder - the limited parameter replaced the accessible-folder scope instead of narrowing it, and NestedTree::getDescendants() walks the raw tree without applying any ACL. Passing an arbitrary folder id returned the label, login, description preview, tags, URL and folder path of items the user had no right to see. Two guards that should have contained this were inoperative: the foreign personal-folder exclusion read $row['id'] from a DB::query() result (a list of rows, so the id was always undefined and the NOT IN clause was never added), and the per-item personal check compared a driver string against an int ($record['perso'] === 1, never true). The scope is now resolved by the new pure searchResolveFolderScope(): the requested subtree is intersected with user-accessible_folders, then user-forbiden_personal_folders is subtracted. The same commit rebuilds ORDER BY from the server-side column map with a re-derived direction - the form mandated after GHSA-fqg6-xvv8-w228 - and drops set_time_limit(0) from this endpoint.

✨ New features

  • Faceted search - the Search page offered a single text box over ten columns and no filter at all, so none of the metadata added since 3.2 (classification, security posture, rotation flags, attachments) was reachable from search. It now has a filter panel with removable chips backed by a new search.queries.php handler: classification level, live security flags, attachments, dates and rotation, tags, scope, favourites, recently viewed and unencrypted custom fields. A section is hidden when its feature is off and the matching filter is stripped server-side, so a crafted payload cannot reach it either. Text search now ANDs several words and lets the user pick the fields, with description opt-in since it is the expensive column to scan. The query was rebuilt rather than extended: item restrictions moved into SQL so filtering happens before the LIMIT (recordsTotal stops lying and pages stop coming back short), the LEFT JOIN on categories_items is gone (no more duplicate row per custom field), and mass-operation rights resolve in one query per page instead of one per role per row. Cells are escaped and sent as plain JSON instead of hand-concatenated base64-wrapped HTML, retiring the pattern behind the datatable XSS advisories.

  • Quick access panel - the "Last items seen" sidebar becomes a three-tab panel (recent, most used, favourites) with a refresh control and a per-user Most used ranking. The ranking is served by a new access_count column rather than by aggregating log_items, whose index leads with id_item and could not serve a per-user GROUP BY.

🛠️ Improvements

  • Health System accuracy and backup reliability (PR #5314, @guerricv) - the system health check logic is refactored into dedicated functions, the secure-file check and the AES v2 migration status now report real progress with indicators instead of a bare state, backup compatibility checks and grace-period calculation are reworked, stale database dump fragments and temporary directories are purged by a new maintenance task, and the chunk_rows backup option is deprecated in favour of better file handling. Backed by four new unit-test suites covering backup streaming, backup packaging, the backup list interaction and health-check consistency.

  • A TeamPass version difference is no longer counted as a backup anomaly - the compatibility classifier already treats the schema level as the only restore boundary, but tp_version_mismatch still fed anomalies_total, so the Health page raised a warning on every existing backup after any patch release. The counter is still reported so administrators can see the difference; it just no longer makes the backup look broken.

  • The Security posture flagged-items list becomes usable at scale - it could only be narrowed by folder and issue type, and a folder change redrew it with no feedback while the query ran. It now has a server-side free-text filter over label, login, URL and folder name (debounced client-side, so it applies to the whole result set rather than the loaded page), a sort selector, and a "Password changed" column - the overdue and no-expiry flags were not actionable without the password age. The loader is scoped to the table rather than the card, so the filter controls stay usable during a slow query, and a request sequence number discards the answer to a filter the user has already moved on from. Filters persist per tab in sessionStorage because the "fix" links leave the page and the context was lost on the way back; a restored folder filter that no longer holds flagged items resets itself and refetches instead of showing an empty list.

  • The posture filter bar stays on one line - a <select> sizes itself on its longest option, so a single deep folder path widened the grouping filter until the sort control wrapped. The select width is now capped and long paths are abbreviated by collapsing the middle segments; the leaf identifies the folder, so it is only cut into when it alone overflows. The full path stays available as a tooltip on each option and on the closed select.

  • Favorites page layout and broken bookmarks - the unstar control moves to the end of the actions column in list view, the default key icon that carried no information is dropped, and the action buttons are spaced with a flex gap in both views. Favourites whose item was deleted or became unreadable are now listed as dedicated entries - id and date added only, never the item attributes - behind a new availability filter reachable in one click from the warning banner. The filter toggles back and resets itself when the last broken bookmark is removed, so the list can never stay empty.

  • The security dashboard intro text moves into a help tooltip on the page title, reusing the wider tooltip template already used by the score help, instead of floating between the header and the first card.

  • The clipboard copy fallback is shared - the helper is renamed tpClipboardCopy and applied to the three pages that had no fallback and therefore could not copy at all in browsers restricting the async clipboard API.

🐛 Bug fixes

  • "Session expired" when logging in on a page left open too long - a login page open longer than the server-side session loses the encryption key it was rendered with. The credentials could then not be decoded, identify.php answered a raw marker, and the client turned it into a "Session expired" dialog - confusing for a user who is precisely trying to open a session, and requiring a manual reload. The page now repairs itself: a new refresh_session_key request renews the key and the submission is replayed once, transparently. When that fails, a dedicated dialog explains the refresh, counts down and reloads, restoring the login, the MFA choice and the session duration. The password is never persisted.

  • The login page no longer breaks when the tab returns to the foreground - the silent key renewal above only runs once the user has submitted credentials, so a machine waking from sleep still hit the stale key. On visibilitychange and focus, and at most every five minutes, the page now asks identify.php whether the key it holds is still current. A stale key with an empty password field triggers a plain reload; when something is already typed, the key is renewed in place so nothing is lost. The new is_session_key_valid endpoint answers a boolean only - the caller already owns the key it submits.

  • Avatar upload broken on the profile page - a missing closing bracket in the language selector made jQuery throw an "unrecognized expression" error that aborted the page script before the uploader was wired. The selector is repaired and the profile picture is registered as a second plupload browse button.

  • Item labels showing HTML entities in the Quick access panel - labels saved by older versions store entities (acc&egrave;s) and were sent through htmlspecialchars() again, so the entity itself became visible. All three affected strings now leave the server as plain text and are escaped by the client.

⬆️ Upgrade notes

  • Schema. The upgrade adds the access_count column to users_latest_items (with a (user_id, access_count) index), indexes cache.id_tree and cache.label(191) so the search scope predicate plans as a range scan instead of a full table scan, and seeds the show_last_items setting on databases upgraded from older releases where it was never present. All statements are idempotent and safe to replay.
  • show_last_items defaults to 1 on installations that lacked it, which preserves the previous behaviour of always rendering the panel. Turn it off in the options if you do not want it.
  • The "Most used" ranking starts flat. Existing users_latest_items rows are created with access_count = 1; the tab becomes meaningful as items are opened again after the upgrade.
  • Backup warnings will clear. Backups previously flagged solely because their TeamPass version differed from the running one are no longer counted as anomalies. Re-check the Health page after upgrading - a warning that disappears is expected, not a lost signal.
  • The Search page is a rewrite. Its result query, ordering and JSON payload all changed. If you automated anything against the old find.queries.php datatable payload, re-test it.
  • Back up your database before upgrading, as always.

Full Changelog: 3.2.1.3...3.2.1.4

Important

  • Requires at least PHP 8.2

Languages

Please join Teampass v3 translation project on Poeditor and translate it for your language.

Installation

Follow instructions from Documentation.

Upgrade

Follow instructions from Documentation.

Ideas and comments

Are welcome ... please use Discussions.

Download TeamPass

Don't miss a new TeamPass release

NewReleases is sending notifications on new releases.