← Back to Writeups

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.

ItemVersion / detail
ConsoleUDM-SE
UniFi OS5.0.12
UniFi Talk application4.2.11
UniFi Talk Softphone component (per investigation notes)5.0.3 to 5.1.2 (latest)
Site ManagerEarly 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 / platformRoleVersion / detail
UniFi Site ManagerRemote 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 EndpointMobile/desktop client used to exercise softphone provisioning / login flowsVersion 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 testActive subscription in reporter’s tenant
Modern web browserLocal console UI and Site Manager UIe.g. Chrome/Safari + DevTools for HAR export
HTTPS proxy (optional but recommended)Capture/replay API traffic for evidencee.g. Caido (or similar) with scoped targets (console host, unifi.ui.com, account.ui.com as needed)
SSH access to consoleRead-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 proofInvoked 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_user including assignments JSONB).

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:

  1. Talk UI shows persistent Identity App softphone entries (e.g. under Talk → Phones) that cannot be removed through normal UI workflows.
  2. Local database state can exist in at least two confirmed orphan patterns:
    • Class A (structural orphan): msp row with user_id IS NULL while stale metadata may remain in additional_config.
    • Class B (logical orphan): msp row still has a non-NULL user_id, but no matching rows in device_account / device_configuration, while device_discovery may still reference the same mac. This is not fixed** by cleanup logic that only deletes user_id IS NULL softphones.
  3. Independent state planes were observed:
    • Local Talk anchor: unifi-talk.public.device (and related tables).
    • Identity/service layer: ulp-go.public.identity_user.assignments and ulp-go.public.talk.enabled — these can diverge from local device rows.
  4. UI seat / “used softphones” style counters were observed not to equal a naive COUNT(*) of msp devices 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 storyIncorrect “used softphones” count displayed in the Talk subscription UI while local device tables told a different story

  1. 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 ownerTalk 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 > PhonesClass 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

  1. UniFi OS console (e.g. UDM-SE, UniFi OS 5.0.12) with UniFi Talk (4.2.11) and Identity/Endpoint integration.
  2. Talk Pro and Identity prerequisites per Ubiquiti documentation for softphone.
  3. Site Manager using Fabrics .
  4. 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.

  1. 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;"
    
  2. For suspect mac values, 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>');"
    
  3. Cross-plane correlation (run separate queries per database; combine in analysis — single cross-DB SELECT may require running two psql sessions 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;"
    
  4. Orphan checks:

    • Class A pattern: model='msp' AND user_id IS NULL.
    • Class B pattern: msp present, device_account / device_configuration absent (and compare to device_discovery).

Remediation

  • Cleanup Amodel = 'msp' and user_id IS NULL (structural orphans). Does not remove Class B (user_id still 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 mac list after validating rows are stale (e.g. no required device_account / device_configuration for 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-talk device rows, device_discovery, and ulp-go talk/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 testingTalk 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 > PlansCurrent non-specific error thrown when selecting Remove or Downgrade on a UniFi Talk line in Talk > Settings > Plans