---
title: Mobile device tracking (which phone is connecting, and should it)
---

# Mobile device tracking (protect your pairing token)

## What it is

### What it is (plain language)

When you turn on **Mobile Access**, Omniscio gives you a QR code / link secured by a
**pairing token** so your phone can reach your desktop. That token is a bearer
credential — until now, anything holding it could connect, with no awareness of _which_
device. Mobile device tracking adds a recognition layer: Omniscio remembers each device
that uses your token, **trusts your first device silently**, and raises an **inbox alert
the moment a new, unrecognized device connects** — so if your token ever leaks, you find
out and can act.

## Where to find it

Two places: a **"New device on your mobile access" inbox alert** when an unseen device connects, and the **device list** behind Mobile Access in Settings, where you approve, block or review devices.

## How it behaves

### What you see

- **A "New device on your mobile access" inbox alert** when a device Omniscio hasn't seen
  before connects with your token. It names what the device looks like (e.g. "A new
  iPhone just connected…") and — on the desktop — shows the device **inline in the inbox**
  with one-click **Approve**, **Trust** and **Block** (and **Unblock** if it's already blocked), so you
  act right there without opening Settings; a small **Manage devices** link jumps straight to
  that device's row in Settings. It is inbox-only (it does not buzz your phone). On mobile web
  it shows a "manage on your desktop" note instead (the device controls are desktop-only).
- **A Devices card** in Settings → Remote Access listing every device that has used the
  token: its type/name, **richer device detail** (browser + operating system, e.g. "iOS 17 ·
  Safari"), when it was first and last seen, a **network-origin** label ("Local network" /
  "Tailscale" / "This computer" / "Public internet"), and — when enabled — a **coarse location**
  (city/region/country). Each row has:
  - **Trust** — dismiss the "new" flag on a device you recognize (it won't alert again).
  - **Rename** — give a device a friendly name.
  - **Block** — refuse that one device without kicking everyone off (see below).
- **A "Show where devices connect from" toggle** (Settings → Remote Access, default ON). It
  controls the coarse-location lookup: with it on, each phone quietly asks an Omniscio cloud
  endpoint to turn its own public IP into a rough city/region (the same MaxMind geo engine that
  powers the "new sign-in location" security emails). Turn it OFF to stop the lookup entirely —
  the phone then makes NO cloud geo call and the card shows only device detail + network origin.

### Where a device is connecting from (location)

The desktop's mobile server only ever sees a **private** address for a connecting phone (a
home-Wi-Fi `192.168.x`, a Tailscale `100.x`, or `127.0.0.1` behind the Tailscale proxy, private or public), so it
can't geolocate the phone itself. Instead the **phone** reports its own location: it fetches its
coarse city/region/country from the `resolveMyGeo` cloud endpoint (which reads the phone's real
public IP) and sends the result to the desktop over the already-authenticated WebSocket. This
works in every connection mode (a phone always has its own internet), and it's the useful
security signal — a home phone reads as your city; a remote leaked-link phone reads as somewhere
else. Only the **coarse location** is stored on the device row; the raw IP is never persisted.

### How it recognizes a device

Omniscio assigns each device a hidden **server-generated id** stored in an HttpOnly
cookie (`amc_device_id`), issued the first time a device authenticates over the web
layer. It is not something the phone makes up (so a thief's fresh browser has no cookie
and shows up as new), and it survives your phone hopping between Wi-Fi and cellular (an
IP-based guess would false-alarm constantly). A device that can't carry a cookie falls
back to a coarser fingerprint of its browser + token.

The **first** device ever seen on a token is trusted silently — it's your existing
phone, not "new." Only a _second_, unfamiliar device raises the alarm. Re-pairing your
own device after you regenerate the token does **not** false-alarm, because the device
keeps its stable id.

### Blocking vs. regenerating

- **Block** adds one device to a denylist the server checks at connect time, so that
  device is refused (on both the normal and the freeze-proof off-loop connection paths)
  while your other devices keep working — no re-pairing required.
- **Regenerate token** (in Mobile Access) remains the full kill-switch: it invalidates
  the token so _every_ device must re-pair. It's the real remedy for a confirmed leak.
- **Forget paired devices** now also clears the device registry AND the in-memory approval map.

### Device approval — the second factor (mobile 2FA)

The tracking above is DETECTION. **Device approval** is PREVENTION, and it is **ON by
default**: a phone that connects with the pairing token is **inert until you APPROVE that
device from your signed-in desktop**. Every mobile request — reads, actions, AND the live
feed, over BOTH the live socket and plain web (HTTP) requests — is refused for an unapproved
device, so a **leaked pairing token alone can do nothing**.

- **You approve from the desktop only.** The "Approve your device" inbox alert shows the device
  **inline with one-click Approve / Block** (desktop), and the Devices card has the same
  **Approve** / **Revoke approval** controls; approving is a desktop action (the approve channel is
  blocked from the phone bridge), so a phone can never approve itself. That desktop step — taken
  while you are signed into your account — IS the second factor.
- **Your existing phones keep working.** Devices already recognized are _grandfathered_ in,
  so you are not locked out on upgrade; a brand-new device needs one approval.
- **Only you can grant a device — an AI agent can only ask.** An agent working for you can't
  approve, trust or unblock a device itself (every agent on the computer can reach Omniscio's
  control server, so it can't stand in for you). When one asks to approve or trust a device, the
  device's own inbox card appears — one card per device, however often it asks — and nothing
  changes until **you** click **Approve** or **Trust** in it. An agent can't unblock a device at
  all: you do that in the device list. Taking access away (blocking, revoking an approval) still
  works from an agent, since that only makes things safer.
- **The phone tells you what's happening.** An unapproved phone shows a "waiting for approval —
  approve this device from your desktop" banner instead of a silently broken screen; it clears
  the moment you approve the device.
- **And the desktop never just goes quiet.** If nobody is signed in on this computer, approving
  isn't possible yet — so instead of showing nothing, the same inbox row appears as **"Sign in to
  use mobile access"**, telling you to sign in here and then approve the device. The card swaps the
  Approve button for a "sign in on this computer to approve" note (**Block still works** — blocking
  never needed an account). Before this, a signed-out desktop meant mobile was completely dead with
  an empty inbox and no explanation anywhere.
- **Two toggles** (Settings → Remote Access, both default on, both CLI-blocked):
  **Require device approval** (the master switch) and **Keep existing devices approved**
  (turn off to make grandfathered devices re-approve — a testing lever).
- **Cookie-only + fail-closed.** Only a device with a real random `amc_device_id` cookie is
  approvable (the coarse UA+token fingerprint is guessable, so it is never authorized); an
  unresolved device or a signed-out owner is denied. The per-request check is a pure
  in-memory lookup — no crypto, no network, no per-tap slowdown.

### Honest limits

- **With Device approval ON (the default), a leaked token alone is useless** — the thief's
  device shows up unapproved and can do nothing (no actions, no live feed, and no reading the
  bootstrap snapshot or any file/media route over HTTP either) until you approve it from your
  desktop. The limits below describe the _tracking_ layer on its own (approval off).
- Tracking alone is **detection, not prevention**. A determined thief who clears their
  cookie reappears as a _new_ device — which simply re-alerts you.
- It defends against the common case (the **token alone** leaking), not theft of your
  entire browser session (token **and** the device cookie together).
- Trust-on-first-use assumes the token isn't already leaked when tracking begins; the
  auditable Devices list is how you'd catch an unexpected trusted device.
- **The shown location is phone-supplied**, so a sophisticated malicious client could FORGE it to
  look local. It is a decision-AID only — the fail-closed device-approval gate never trusts it, so
  a forged location cannot approve a device (approval is a desktop-only action). The
  "Show where devices connect from" toggle disables the lookup + hides location entirely.

## For agents

### For agents / the code

- Identity + cookie: `src/main/services/web/web-access-http-auth-cookie.ts`
  (`amc_device_id`, `resolveWsDeviceIdentity`). Issued in `refreshAuthCookie`
  (`web-access-http-routes.ts`) + POST /login.
- Detection + alert + block denylist: `src/main/services/web/web-access-device-store.ts`;
  DB layer `src/main/db/queries-web-access-devices.ts` (table `web_access_devices`,
  `approved_uid` + `grandfathered` columns).
- **Coarse geolocation (phone-reported):** the phone fetches its own city/region/country from the
  `resolveMyGeo` cloud fn (`firebase/functions/src/global-auth/user-geo/resolve-my-geo.ts`, reusing
  `resolveGeo`/`getGeoReader`; `/t/resolve-my-geo` hosting rewrite; CSP `connect-src` allows
  `https://agentmc-shares.web.app`). Renderer reports it via `src/renderer/src/lib/report-device-geo.ts`
  → the `device-geo` WS frame (`web-access-ws-schema.ts`) → `handleDeviceGeoFrame`
  (`web-access-ws-frames.ts`, in-main path only for v1; the off-loop worker ignores it → network-label
  fallback) → `recordDeviceGeo` → `setWebAccessDeviceGeo` (`geo_country/geo_region/geo_city/
geo_captured_at` columns — coarse only, never the raw IP). Gated by `mobileDeviceLocationEnabled`
  (default on, CLI-flippable; carried in WEB_BOOTSTRAP so the phone reads it at first paint).
- Enforcement at connect (block denylist): `web-access-ws.ts` (in-main) +
  `web-access-host-entry.ts` (off-loop worker, via a mirrored denylist over
  `host-bridge-protocol.ts`).
- **Device-approval gate (mobile 2FA):** `src/main/services/web/web-access-device-approval.ts`
  — fail-closed, cookie-only, owner-scoped, in-memory. Enforced on EVERY request: in-main
  invoke (`web-access-ws-frames.ts`) + push feed (`web-access-push-forwarder.ts`), off-loop
  invoke (`web-access-host.ts`) + feed (`web-access-host-entry.ts`, via a mirrored
  `authorizedDeviceIds` set), AND the **HTTP layer** (`web-access-http-routes.ts` via
  `httpRequestDeviceApprovalBlocked` — skips the authed-route dispatcher for a blocked device so
  `/bootstrap` + every file/media route is refused, while the static app shell still serves so the
  phone can boot). `requireMobileDeviceApproval` is **FORCED ON** — its value can no longer disable
  the gate (the Settings toggle is shown locked-on); `grandfatherExistingMobileDevices` (default on)
  still lets already-approved / pre-existing devices through so existing phones keep working. Both are
  in `SETTINGS_PATCH_BLOCKED`; the ONLY off-switch for device approval is the env kill switch
  `AMC_DISABLE_WEB_ACCESS_DEVICE_APPROVAL` (test harness / QA fleet). The sibling account second
  factor (`requireMobileAccountLogin`) is NOT also forced on — it is the OPPOSITE: device approval
  SUPERSEDES it, so `accountVerifyEnabled()` returns false whenever device approval is enabled, which
  is every production install. It is inert here and survives only as the fallback when device
  approval is env-kill-switched (test/QA). Device approval is the mobile second factor.
- **Pending-state UX:** the phone shows a "waiting for approval" banner
  (`src/renderer/src/components/ui/DeviceApprovalBanner.tsx`, mounted in `AppBanners.tsx`) while
  inert, and a `deviceApprovalRequired` bootstrap failure is suppressed from the "could not
  connect" toast (`useAppBootstrap.ts`) — the banner is the messaging. The owner reviews via the
  inbox card's **inline Approve / Block** (`src/renderer/src/features/alerts/AlertMobileDeviceCard.tsx`,
  wired in `AlertInboxViewer.tsx` via `use-alert-actions.ts`, desktop-only); the
  `alert-actions/mobile-web.ts` settings action is kept as the directive-guard's declared action and
  backs the card's "Manage devices" link, which deep-links to the device's own Settings row
  (`web-access-device-row-<id>`, routed to the Devices tab by `WebAccessSettings-tabs.ts`).
- Settings UI: `src/renderer/src/features/settings/sections/web-access/WebAccessDevicesCard.tsx`.
- The **five** management channels (`WEB_ACCESS_DEVICES_LIST` / `_DEVICE_TRUST` / `_DEVICE_RENAME`
  / `_DEVICE_REVOKE` / `_DEVICE_APPROVE`) are Zod-validated, have headless CLI routes under
  `/web-access/devices*`, and are **blocked from the phone bridge** — device management +
  approval are desktop-only.
- Full invariants: `.claude/memory/contracts/web-access-device-tracking-contract.md` (tracking)
  - `.claude/memory/contracts/web-access-device-approval-contract.md` (the 2FA gate).

## Related

- [mobile-remote-access.md](mobile-remote-access.md) — the phone access these devices are connecting to.
- [mobile-remote-access.md](mobile-remote-access.md) — getting a phone connected in the first place.
- [inbox-alerts.md](inbox-alerts.md) — the alert surface the new-device notice arrives on.

