Bugs I found in UniFi Talk
Reproduction and forensic validation were performed on a UniFi Dream Machine Special Edition (UDM-SE) acting as the console hosting UniFi OS, UniFi Talk, and Identity-related services.
| Item | Version / detail |
|---|---|
| Console | UDM-SE |
| UniFi OS | 5.0.12 |
| UniFi Talk application | 4.2.11 |
| UniFi Talk Softphone component (per investigation notes) | 5.0.3 to 5.1.2 (latest) |
| Site Manager | Early Access (EA) enabled |
Note: The underlying issue class is lifecycle/state integrity across Talk’s local persistence and the identity/service layer, not a single UI button. Other UniFi OS consoles that run the same Talk + Identity stack may exhibit similar behavior, but that was not exhaustively tested on other SKUs in this research.
Requirements
| Product / platform | Role | Version / detail |
|---|---|---|
| UniFi Site Manager | Remote user/softphone enablement path tested (Site Manager > Users) | EA enabled; exact web client build as served by Ubiquiti at time of testing |
| UniFi Identity / UniFi Endpoint | Mobile/desktop client used to exercise softphone provisioning / login flows | Version as installed on test device(s) at time of testing — document your exact app store build in the private report |
| UniFi Talk Pro (subscription) | Documented prerequisite for Identity softphone per Ubiquiti; entitlement boundary under test | Active subscription in reporter’s tenant |
| Modern web browser | Local console UI and Site Manager UI | e.g. Chrome/Safari + DevTools for HAR export |
| HTTPS proxy (optional but recommended) | Capture/replay API traffic for evidence | e.g. Caido (or similar) with scoped targets (console host, unifi.ui.com, account.ui.com as needed) |
| SSH access to console | Read-only psql validation and evidence (Ubiquiti warns SSH can be risky; use as documented) | SSH enabled per UniFi OS Settings → Control Plane → Console; user root per Ubiquiti documentation |
PostgreSQL client (psql) | Query local databases on-console for state proof | Invoked on-console as psql -U postgres -d <database> |
Databases directly inspected (on-console PostgreSQL):
unifi-talk— Talk device/user/device lifecycle tables (e.g.public.device,public.device_account,public.device_configuration,public.device_discovery).ulp-go— Service-layer Talk/Identity state (e.g.public.talk,public.identity_userincludingassignmentsJSONB).
Issues
Incomplete lifecycle and cross-plane state desynchronization for UniFi Talk Identity App softphones (device.model = 'msp' in unifi-talk.public.device). The system can leave phantom or partially cleaned softphone objects such that:
- Talk UI shows persistent Identity App softphone entries (e.g. under Talk → Phones) that cannot be removed through normal UI workflows.
- Local database state can exist in at least two confirmed orphan patterns:
- Class A (structural orphan):
msprow withuser_id IS NULLwhile stale metadata may remain inadditional_config. - Class B (logical orphan):
msprow still has a non-NULLuser_id, but no matching rows indevice_account/device_configuration, whiledevice_discoverymay still reference the samemac. This is not fixed** by cleanup logic that only deletesuser_id IS NULLsoftphones.
- Class A (structural orphan):
- Independent state planes were observed:
- Local Talk anchor:
unifi-talk.public.device(and related tables). - Identity/service layer:
ulp-go.public.identity_user.assignmentsandulp-go.public.talk.enabled— these can diverge from local device rows.
- Local Talk anchor:
- UI seat / “used softphones” style counters were observed not to equal a naive
COUNT(*)ofmspdevices alone, suggesting seat/entitlement presentation may depend on service-layer assignment state and/or cloud-synced signals, not purely on local device table cardinality.
Incorrect “used softphones” count displayed in the Talk subscription UI while local device tables told a different story
-
Operational impact: Vendor support reportedly directed manual SQL deletion of phantom rows as remediation — indicating the product lacks a safe, supported admin repair path for inconsistent state, and that state repair requires privileged datastore access inappropriate for typical administrators.
Talk owner vs. console owner confirmation during ticket handling — evidence that privilege boundaries in Talk are separate from the UniFi OS console owner
Class B phantom softphone entries observed in Talk > Phones
Security-relevant angles
- Authorization / entitlement enforcement: If cloud or UI billing/seat counts trust stale assignment or device graphs, customers may experience incorrect consumption of Talk Pro / softphone seats, or inability to downgrade/manage plans despite UI actions — a business logic / integrity failure with financial and availability impact.
- Stale authorization (hypothesis for further proof): If softphone capability or registration remains usable when admin believes it is disabled, that is access retention — must be proven with controlled deprovisioning tests and internal-only call tests (no abuse of PSTN).
Confirmed (with psql + screenshots): orphan classes A/B, UI phantoms, manual cleanup effect.
Steps required to reproduce
Preconditions
- UniFi OS console (e.g. UDM-SE, UniFi OS 5.0.12) with UniFi Talk (4.2.11) and Identity/Endpoint integration.
- Talk Pro and Identity prerequisites per Ubiquiti documentation for softphone.
- Site Manager using Fabrics .
- Test Identity user with Talk Phone Access / softphone toggles exercised via Site Manager and/or local console UI.
Reproduction
SSH into console; psql as postgres user.
-
List msp softphones:
psql -U postgres -d unifi-talk -c "SELECT mac, model, user_id, display_name, additional_config FROM public.device WHERE model = 'msp' ORDER BY mac;" -
For suspect
macvalues, verify dependencies:psql -U postgres -d unifi-talk -c "SELECT device_mac FROM public.device_account WHERE device_mac IN ('<mac>');" psql -U postgres -d unifi-talk -c "SELECT device_mac FROM public.device_configuration WHERE device_mac IN ('<mac>');" psql -U postgres -d unifi-talk -c "SELECT mac FROM public.device_discovery WHERE mac IN ('<mac>');" -
Cross-plane correlation (run separate queries per database; combine in analysis — single cross-DB
SELECTmay require running twopsqlsessions or materializing joins manually):psql -U postgres -d ulp-go -c "SELECT user_id, assignments FROM public.identity_user ORDER BY user_id;" psql -U postgres -d ulp-go -c "SELECT user_id, enabled FROM public.talk ORDER BY user_id;" -
Orphan checks:
- Class A pattern:
model='msp' AND user_id IS NULL. - Class B pattern:
msppresent,device_account/device_configurationabsent (and compare todevice_discovery).
- Class A pattern:
Remediation
-
Cleanup A —
model = 'msp'anduser_id IS NULL(structural orphans). Does not remove Class B (user_idstill set).psql -U postgres -d unifi-talk -c " WITH orphaned_softphones AS ( DELETE FROM public.device WHERE model = 'msp' AND user_id IS NULL RETURNING mac ) DELETE FROM public.device_discovery WHERE mac IN (SELECT mac FROM orphaned_softphones); " -
Cleanup B — explicit
maclist after validating rows are stale (e.g. no requireddevice_account/device_configurationfor active service). Replace<macN>with validated MACs.psql -U postgres -d unifi-talk -c " WITH deleted AS ( DELETE FROM public.device WHERE mac IN ('<mac1>','<mac2>','<mac3>') RETURNING mac ) DELETE FROM public.device_discovery WHERE mac IN (SELECT mac FROM deleted); " -
Post-cleanup verification — expect
(0 rows)for the deleted MACs; phantom entries should disappear from Talk → Phones.psql -U postgres -d unifi-talk -c " SELECT 'device' AS table_name, mac FROM public.device WHERE mac IN ('<mac1>','<mac2>','<mac3>') UNION ALL SELECT 'device_discovery' AS table_name, mac FROM public.device_discovery WHERE mac IN ('<mac1>','<mac2>','<mac3>'); "
Business impact
- Operational: Phantom softphones clutter Talk → Phones, block or confuse line reassignment, plan changes, or downgrades, and force support-driven manual database edits — unacceptable for production PBX operations.
- Financial / subscription: If seat counts or entitlement reflect stale assignment/device state, customers may see incorrect “used” softphone consumption, billing confusion, or inability to complete subscription changes despite paying for the correct plan.
- Trust: Inconsistent UI vs backend state undermines admin confidence in Talk Pro and Identity integration.
Security impact
- Integrity / authorization (primary): Multi-plane state drift means administrative actions in UI do not reliably enforce the intended telephony entitlement and user capability model documented by Ubiquiti.
- Access retention (requires proof): If deprovisioning does not revoke softphone use, that is continued access to telephony resources after admin removal — validate with internal extension-only tests and document results.
- Privilege / billing boundary (requires proof): Only report Talk owner vs console owner separation issues if demonstrated with role-scoped API replay evidence.
Suggested (for vendor)
- Enforce atomic lifecycle across
unifi-talkdevice rows,device_discovery, andulp-gotalk/identity_user state on every enable/disable/unassign/delete path. - Provide a supported “repair inconsistent softphone state” admin tool with audit logs, instead of ad-hoc SQL.
- Ensure seat/billing counters derive from a single authoritative entitlement pipeline with reconciliation checks against local device reality.
Talk application updated during remediation testing
This caused Controller > Talk > Settings > Remove and Downgrade to be selectable on each line (only after manually removing all orphans of Class A and B); however the non-specific error shown below was thrown. This is an ongoing issue (04/21/2026) I am working with the Ubiquiti team to resolve.
Current non-specific error thrown when selecting Remove or Downgrade on a UniFi Talk line in Talk > Settings > Plans