What's Changed
This is a major feature and security release, and the first stable build of the 3.2.1 line (superseding the 3.2.1.0 pre-release). It hardens the cryptographic core, closes nine security advisories (two of them exploitable without authentication), adds a full set of security-posture, governance and productivity features, and introduces configurable RFC 6238 TOTP profiles. Upgrading is strongly recommended for all installations.
On an upgrade, all new features arrive disabled and are opt-in from the admin settings — upgrading alone changes nothing until you enable them. New installations start with the hardened encryption format already enabled.
Already ran the 3.2.1.0 pre-release? The delta for you is: configurable TOTP profiles, the import-module authorization fix (GHSA-cgcj-f9rx-c8r4), the API tree-order / server-version improvements, and the six bug fixes listed below (#5299, #5293, #5291, #5289, LDAP TLS setting, item-list highlighting).
🔐 Encryption hardening
The cryptographic core has been reworked. The migration is automatic, transparent and reversible: data upgrades itself to the new format as it is read, with no downtime and no re-typing. See the new Encryption hardening page.
- Authenticated AES-256-GCM — stored secrets were encrypted with AES-CBC using a fixed IV, a fixed salt and no integrity check. The new v2 format uses authenticated GCM with a random IV and a per-secret random salt, so a database leak alone stays unusable and any tampering with an encrypted value is detected instead of silently returning corrupted data. Enabled by default on new installations (including the recovery account's key); after an upgrade it stays off until you turn on the
aes_v2_write_enabledtoggle (Settings → Security). The previous format stays readable indefinitely. - Modern key derivation — PBKDF2 raised from 1,000 to 600,000 iterations, HKDF-SHA256 for key derivation, aligned with current NIST recommendations.
- 256-bit object keys — the per-secret key entropy went from 64 to 256 bits (
KEY_LENGTH16 → 64). Backward compatible, existing keys still decrypt. - Truly private personal folders — a personal item used to distribute a decryption key to every account at creation time. Only the owner (plus the internal recovery account) now holds a key. A
--dry-runremediation script cleans up existing data, see Security hardening. - Resilient key distribution — a single corrupted user public key used to fail the whole batch. The failing user is now isolated and logged while everyone else still gets their key, and failed background subtasks retry automatically (capped at 3).
- Faster saves — saving a shared password triggered one RSA-4096 operation per user inside the web request. The request now performs a single operation and the distribution to other users moves to the background.
- Lazy migration — each secret is re-encrypted to the hardened format the first time it is read and on user login, with a progress indicator in the admin area.
🔒 Security fixes
- Unauthenticated MFA reset and authentication oracles (GHSA-6x6x-v79m-v5x3) — the
ga_generate_qraction trusted the client-supplieddemand_originfield. Sendingdemand_origin=users_management_listskipped the password check and the "already enrolled" guard, letting an unauthenticated caller overwrite any user's TOTP secret, enumerate logins and probe passwords with no lockout. The administrative right is now derived from the session, the password is mandatory in self-service, the answer is uniform (no enumeration or password oracle), and failures are recorded under the standard anti-bruteforce lock. - Unauthenticated forced password reset (GHSA-cm5h-m2xm-5pxr) — the reset request was authorized against a value the same caller had planted in their own session on a failed login. The request now only mints a token and emails a confirmation link, mutates nothing, and always returns a uniform answer. The reset itself moved to a new endpoint behind a 256-bit, single-use, 15-minute token.
- Folder access bypass in item details (GHSA-hjhc-6g7v-8jxr) — with "restrict items to roles" enabled, the role clause of the
show_details_itemgate granted metadata reads without checking folder access, exposing label/login/url/email/description of every unrestricted item across all folders, including other users' personal items. The folder predicate is now applied first, so the role restriction is additive and never replaces the folder boundary. - Legacy unguarded user update branches (GHSA-58ph-5gg6-h2v8) — dead legacy branches selected by the absence of a
typeparameter wrote thepwcolumn verbatim (unhashed, no complexity check) and theadminflag, both outside the manager/admin target-scope guard. Chaining them gave full admin takeover. Both branches removed. An adjacent escalation found during the audit is fixed in the same release:update_users_rights_sharingtargeteddestination_idsrather thanuser_id, so the upstream guard never fired and a manager could setadmin=1on any account, including their own. - Unscoped role assignment (GHSA-gjc5-pmxw-58p4) — the role id was never validated against the caller's own grant rights, so an in-scope manager could grant any role. A non-admin may now only grant a role they hold or created.
- Stored XSS through the API (GHSA-r298-6mxv-j9hc) —
ItemModel::validateData()called the sanitizer but discarded its return value, so the API stored every field verbatim while the web path encodes. Payloads then rendered raw in the items list and the recycle bin. Sanitization is now applied field by field (passwords and TOTP secrets untouched, so exact bytes are preserved) and both rendering sinks escape their output. - User logs IDOR (GHSA-qhff-v9qj-75wc) — the user-logs datatable was gated on the
itemspage right, which every authenticated user holds, with the client-supplieduserIdas the only scoping predicate. It is now gated on theusersright with a per-target check, and a refusal answers an empty datatable rather than an enumeration oracle. - Decoupled authorization in downloadFile (GHSA-3f3c-cw29-xxm7) — the branch served the file named by
namewhile authorizing an unrelatedfileid. It was dead code and has been removed rather than patched. - Import module authorization (GHSA-cgcj-f9rx-c8r4) — temporary uploaded files and staged import operations were not bound to their owner, so an operation id could be used to consume or destroy another user's import, and no import sink authorized the destination folder. Uploaded files are now bound to their uploader and operations to their owner, every import sink authorizes the destination folder (mirroring the standard item-creation check), the personal-folder flag is derived server-side, and uploads are re-checked per type with
allow_importre-validated in the handler.
✨ New features
Security posture
- Security Posture Dashboard — a per-user and admin view of the vault's password posture: weak, reused, breached, overdue, no-expiry, widely-shared and unreadable credentials. Indicator cards act as filters and flagged items deep-link to the item. Metadata indicators are computed server-side without decryption; reused and fresh-HIBP detection run as a chunked in-session scan. Reuse buckets are HMAC'd with a per-install and per-user salt, so identical passwords never correlate across users, and the admin aggregate exposes counts only, never a password.
- Personal Security Score — a per-user score gauge on the dashboard plus an always-on navbar badge, with a "since last scan" progress delta.
- Proactive health nudges — an in-app banner, a per-item list badge and an opt-in email digest riding on the posture scan.
- Password quality on edit — opening an item in edit mode now shows the current password's entropy and estimated crack time, no regeneration needed.
Governance and compliance (new Governance & compliance settings tab)
- Access Recertification Campaigns — snapshot folder grants, attest or revoke them, with immutable decisions and closure only at 100% decided. Managers can run campaigns restricted to their own accessible folders.
- Compliance Reports & Evidence Export — a new admin Reports page with access matrix, access changes, posture summary, rotation evidence and classification reports, exportable as CSV (formula-injection hardened).
- Leaver / Offboarding Risk — list the credentials a disabled or departing user could still know, and flag them for rotation in bulk. Optional auto-flagging when an account is disabled.
- Rotation Policy & Tracking — overdue and SLA reports built on the existing folder renewal period.
- Data Classification & Ownership — item-level classification labels (4 levels) with a badge on the item card and a dedicated report.
Productivity
- Configurable TOTP profiles (RFC 6238) — TOTP items are no longer limited to the default 6-digit / 30-second / SHA-1 profile. The
items_otptable now carries algorithm, digits and period, anotpauth://provisioning URI can be parsed and validated to set them up in one paste, and Base32 secrets are accepted with the spaces and hyphens services display. The API exposes and documents the new fields. - Secure Send — the One-Time-View engine becomes a first-class feature: share an existing item or an ad-hoc note with someone outside TeamPass through an expiring, view-count-limited link, optionally protected by a recipient passphrase. The passphrase wraps the per-link key, so a leaked link alone no longer decrypts the content. Senders can list and revoke their own sends.
- Importers for Bitwarden, LastPass, 1Password and KeePassXC — the Import page is unified behind a single source selector, with a new follow-up panel showing per-operation status, counters and recent history.
- Universal command palette —
Ctrl+Kto search items and pages, scoped to the user's accessible folders. - In-app Notification Centre — a bell with persisted notifications, working even without the WebSocket daemon.
- First-run onboarding wizard — a guided tour for non-admin users on first login (create folder, create item, send a secret, import, browser-extension setup), each step shown only when applicable, replayable from the user menu.
- In-context security micro-learning — short contextual and daily tips, mutable per user.
- Folder API endpoints —
PUT /api/v1/folder/updateandDELETE /api/v1/folder/delete, plus a fix tofolder/createwhich did not rebuild the tree nor populate permissions. Web and API now share oneFolderManagerengine.
🛠️ Improvements
- API folder tree in one call —
GET /api/v1/folder/writableFoldersnow sorts bynested_tree.nleft(MPTT pre-order) and exposes apositionfield, so a client can rebuild the exact folder hierarchy, sibling order included, in a single call. The endpoint is now documented, and thelistFoldersresponse shape was corrected in the docs. - Server version in API authentication —
/authorizeand/authorizeTokennow returnteampass_version,teampass_version_majorandteampass_version_minor(also served bymisc/refreshExtensionSettings), so the browser extension can report the server version without waiting for token expiry. - API Bash client — new
scripts/teampass-cli.sh, a Bash client for the API with single authentication per run, optional PAT mode and 429 handling (contributed by @danfossi, PR #5298). - Faster, cleaner TOTP — the OTP read path builds a single generator instead of three (OTP generation twice as fast), and the previous OTP secret is no longer written in cleartext to the item history.
- Password generator UX — a centered Random / Passphrase mode toggle driving a single options panel and one Generate button, live regeneration on any option change, and the folder's required complexity reflected in the options (forced classes locked, lengths below the minimum disabled) instead of silently reverting the user's input. The chosen mode and passphrase parameters are remembered.
- Navigation reorganized — the left sidebar is grouped into domain drawers, the authenticated top bar into intent groups, and top-bar badges render instantly from cache (stale-while-revalidate).
- Admin Options page reorganized — the settings pages are split into themed paragraphs, with security-posture settings moved to the Security tab, governance settings to a new Governance tab, and interface settings to Collaboration. Presentation only, no setting was renamed or removed.
- Accessibility, dark mode and small screens — skip link, aria-labels on icon-only controls, a focusable theme toggle,
prefers-color-schemeas the first-visit default,:focus-visiblestyling and mobile media queries. - Real-time presence polish — presence and lock badges are now rehydrated after an items-list redraw instead of being wiped.
- External libraries updated.
🐛 Bug fixes
- Docker migration crashing the container on start (issue #5299) — upgrade scripts run in CLI, but
main.functions.phpstarted an HTTP session at include time, soNativeSessionStorage::start()threw and the upgrade exited with code 255, aborting the whole container.SessionManagernow uses an in-memory session storage under the CLI SAPI, and the Docker entrypoint guards the upgrade so a failure no longer tripsset -e. - Blank login page on MFA enrollment through OAuth2 (issue #5293) — the OAuth2 auto-continue path hid the login box before submitting the primary factor, so a
2fa_not_setanswer left the user on an empty page; an unguarded read of the OAuth2 context also threw and stopped initialization. The box is now restored when MFA is requested or authentication fails, and the context is captured before the store is cleared. - OAuth2 user synchronization crashing on directory errors (issue #5291) —
getAllUsers()returned an error map on failure while the caller always iterated the result as a user list, producing a fatalCannot access offset of type string on stringinstead of a message. It now returns a proper{error, message, users}envelope, paginates the Graph collection through@odata.nextLink(only the first page was read), and no longer aborts the whole sync on a failingmemberOfcall. - Background tasks failing with exit code 255 on Windows (issue #5289) — the task payload was passed as a shell argument, and Windows
escapeshellarg()destroyed the JSON, so every task failed. The worker now reads the payload from the database by task id, which also removes the Windows command-line length limit and keeps encrypted credentials out of the process list. - LDAP TLS certificate check setting throwing a TypeError — the setting stored the constant name as a string while
ldap_set_option()requires the integer value, raising a TypeError on PHP 8;LdapExtraalso read a misspelled key that is never written, silently ignoring the admin setting. Both are fixed with a shared conversion helper. - Favorite and selected-item highlighting missing on the initial item-list load —
browserSession('init')never seeded 15 store keys, sohighlightFavorites/highlightSelectedstayed undefined until a deep-link navigation. The merge logic is rewritten, the highlight settings are re-applied before the first render, and the two divergent favorite flags (is_favourited/is_favorite) are normalized so search results are highlighted too. - OAuth2 user synchronization crash on hybrid Entra setups (issue #5279) — a null
userPrincipalNamepassed topreg_match()threw a fatal TypeError and aborted the whole synchronization. Such objects are now skipped and counted for diagnostics. - Misleading folder creation error —
add_foldermasked every failure as "you are not allowed to do that" (a regression from 3.1.2) while the real cause was usually a complexity below the parent folder's minimum. The real reason is now returned, and both creation forms preset the selector to the parent's minimum level. - Self-unlock link dead since the
public/restructure — the bruteforce unlock link emailed to locked-out users returned HTTP 500. The handler was moved and two latent defects fixed while reviving it (cross-account unlock and ignored timezone). - Item descriptions shown as literal HTML markup in search results (PR #5285, issue #5282) — the legacy search handler stripped tags and truncated before decoding entities, displaying fragments such as
<p><b>coucou</b></p><p&. Descriptions are now normalized into safe plain-text previews before truncation, and the expanded detail renders as sanitized rich HTML. - Corrupted items always highlighted in red (PR #5288) — the item list ignored the
corruption_severityreturned by the backend. The renderer now uses the persisted severity, and the corruption tooltip no longer surfaces raw scanner exception messages to non-admin users. - Password generator mode selector not applying on item edit, "No action has been identified" on item edit, item security posture now refreshed when a password is saved, Reviews page manager access / revocation scope, and classification change on read-only items.
⬆️ Upgrade notes
- A new
upgrade_run_3.2.1.phpstep creates the tables for the new features (item_health,user_nudges,rotation_flags,user_notifications,access_reviews,access_review_items,data_classification,import_tracking), extends theotvtable for Secure Send, adds theonboarding_completedcolumn on users, and adds thealgorithm,digitsandperiodcolumns onitems_otpfor the configurable TOTP profiles. NoALTER TABLEis required on the sensitive data tables. - Every new feature is seeded disabled on upgrade. Enable what you need from the admin settings; an upgrade alone changes no behaviour. Fresh installations differ on one point only: the hardened encryption format starts enabled.
- The hardened encryption format is off by default after an upgrade. Turn on
aes_v2_write_enabled(Settings → Security) when you are ready; the previous format stays readable indefinitely and the toggle is reversible. - Back up your database before running the personal-sharekeys remediation script, and run it in
--dry-run(the default) first.
Full Changelog: 3.2.0.9...3.2.1.1
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.