# Is Aster safe? No root, self-hosted, zero telemetry

> Self-hosted with zero telemetry and no root: device approval gate, screen-control kill switch, banking apps blocked, and where the link is not encrypted.

Aster here is the open-source Model Context Protocol server and Android companion app for AI-driven phone control, published on npm as aster-mcp. It is unrelated to Aster DM Healthcare, Aster Data Systems or the ASTER instrument on NASA’s Terra satellite.

Source: https://aster.matterwardlabs.com/security/ · Part of Aster (https://github.com/satyajiit/aster-mcp) · Last generated 2026-09-13

## No root, no ADB exploit

Aster drives the phone through the official Android Accessibility Service API — the same system that powers TalkBack and every screen reader. There is no rooting step, no unlocked bootloader, no shell exploit and no ADB persistence. The service is a toggle in Android Settings that you turn on yourself and can turn off at any time, and Android shows its own warning screen before it will let you enable it.

## Self-hosted, no account, no telemetry

The server is an npm package you run on your own machine. There is no sign-up, no licence check, no vendor relay and no analytics call: device records, approvals and logs live in a local SQLite file on that machine. What Aster does not send, nobody can leak. There is exactly one outbound call Aster can make, and it is off until you turn it on: the optional event-forwarding webhook (`aster set-event-forwarding`). Know what it carries before you enable it — six event kinds, to the URL you supply and to nothing else: notification text, SMS sender and body, incoming-call number and resolved contact name, and device connected / disconnected / pairing-required status. Incoming calls in particular default to ON once forwarding is enabled; switch them off with `events.incomingCalls: false` in `~/.aster/event-forwarding.json`. Note the boundary carefully — this is a statement about Aster, not about your AI client. Whatever you type into Claude, OpenClaw or any other MCP client, and whatever the phone returns to it, is sent to that client’s own model provider under that provider’s terms. Aster neither sees nor governs that hop.

## An unapproved device connects, but cannot be commanded

Approval is a gate on commands, not on the socket. A new phone that reaches the server is registered with status “pending”, keeps its WebSocket open and is told the handshake succeeded — it can be listed and named in the dashboard, which is how you recognise it well enough to approve it. What it cannot do is act: every command dispatch checks the device status first and refuses a device that is not approved. Nothing runs on the phone until you press approve.

## A kill switch you can always reach

While the AI is driving the screen, Aster posts a persistent high-importance notification reading “AI is controlling your phone” with a STOP action. One tap severs the control session and aborts the agent loop within a single action. The notification is deliberately the primary control rather than the on-screen overlay, because the overlay depends on the draw-over-other-apps permission and the notification does not.

## Banking and payment apps are refused by default

A companion-side guard blocks screen-control actions — tap, type, scroll, gestures, key presses and app launches — whenever the foreground app matches its bundled banking and payments denylist (bank, upi, wallet, payment, phonepe, paytm, paypal, venmo, cashapp, revolut, coinbase, binance and similar). It is fail-closed: if the live foreground package cannot be read at all, a gated action is refused rather than allowed. The guard ships with that denylist already loaded, so it protects a phone that has never synced a policy. Screen reads, device info and Aster’s own prompts stay ungated, because blinding the agent is not the same as protecting you.

## The file tools are not path-scoped — know this before you approve

This is the sharpest edge in Aster, and the previous version of this page described it wrongly. On an approved device the MCP file tools reach any path the app itself can read. aster_list_files, aster_read_file, aster_write_file and aster_delete_file each take only a device id and a path; there is no folder parameter to narrow, and the app holds MANAGE_EXTERNAL_STORAGE, so an absolute path is used as given. Owner-approved folders are real, but they scope a different surface: the on-device IPC host-directory feature, which serves its own files.read and files.list actions to a local agent such as OpenAlly. Approving a device for MCP is therefore a grant over that device’s storage, not over one folder in it. Approve devices you own, on networks you trust.

## The device link is plain ws:// by default

The phone-to-server WebSocket is unencrypted. The server opens a plain ws:// listener and terminates no TLS of its own, and the Android app ships a network-security config that permits cleartext, because Android cannot pin a certificate to a bare LAN IP literal. On a home or office network you control, that is a considered trade rather than an oversight. Off it, it is not acceptable: put the phone and the server on a Tailscale tailnet, which carries the same traffic inside an encrypted WireGuard tunnel, needs no port forwarding, and exposes nothing to the public internet. Aster detects the Tailscale CLI and reports the tailnet address for you to use.

## Permissions the app asks for

Only the rows marked **Always** are needed for Aster to run at all; decline any of the rest and you lose only the tools that depend on it.

| Permission | Needed | Why |
|---|---|---|
| BIND_ACCESSIBILITY_SERVICE | Per feature | The accessibility service that reads the screen hierarchy and performs taps, text entry and gestures. This is how screen control works at all. It is a separate toggle in Android Settings, off until you enable it, and Android shows its own warning first. |
| BIND_NOTIFICATION_LISTENER_SERVICE | Per feature | Reads incoming notifications so the AI can answer “what did I miss” and react to alerts. Also a separate Settings toggle; without it the notification tools return nothing. |
| INTERNET | Always | Opens the WebSocket to your server and, in on-device MCP mode, serves the local HTTP endpoint. Aster contacts no address you have not configured. |
| FOREGROUND_SERVICE | Always | Keeps the companion running with a visible, permanent notification instead of silently in the background. The notification is the point: you can always see that Aster is up. |
| FOREGROUND_SERVICE_SPECIAL_USE | Always | The Android 14+ subtype declaration for that service, registered as device_control. Android requires the category to be stated in the manifest. |
| RECEIVE_BOOT_COMPLETED | Per feature | Restarts the companion after a reboot so a phone left on a charger reconnects without you unlocking it. Decline it and you simply reopen the app yourself. |
| WAKE_LOCK | Always | Holds the CPU awake long enough to finish a command that arrives while the screen is off, so a request does not die halfway through. |
| ACCESS_WIFI_STATE | Always | Reads the current network so the app can show which Wi-Fi it is on and reach the server by LAN address. |
| ACCESS_NETWORK_STATE | Always | Detects when connectivity drops so the WebSocket reconnects instead of hanging. |
| REQUEST_IGNORE_BATTERY_OPTIMIZATIONS | Per feature | Lets you opt the companion out of Doze, so an idle phone stays reachable. It only opens the system dialog — you decide, and the answer is reversible in Settings. |
| POST_NOTIFICATIONS | Always | Shows the foreground-service notification and the screen-control STOP notification, and backs the aster_post_notification tool, so a remote caller can also post a notification of its own. Refusing it removes the kill switch from your notification shade, so grant this one. |
| READ_SMS | Per feature | Backs the read-messages tools, so you can ask what a code was or what someone texted. Nothing is uploaded anywhere: a message is read only when a tool call asks for it, and the reply goes to the AI client you connected. |
| SEND_SMS | Per feature | Sends a text on your instruction — the “tell her I am running late” case. Every send is an explicit tool call. |
| RECEIVE_SMS | Per feature | Surfaces an incoming message as an event, so an agent can react to a one-time code or an alert without polling. |
| CALL_PHONE | Per feature | Places a call, including the call-and-speak-a-message flow. Declining it leaves every other tool working. |
| READ_PHONE_STATE | Per feature | Tells Aster whether a call is ringing, active or ended, so a spoken message is not played into a dead line. |
| READ_CALL_LOG | Per feature | Supplies the caller number on incoming-call events for API 28 to 30 only. Android 12 and newer never deliver the number to an app this way, so on a current phone this grant buys nothing and can be declined. |
| MODIFY_AUDIO_SETTINGS | Per feature | Routes audio to the earpiece or speaker for the call-and-speak flow, and restores your setting afterwards. |
| ACCESS_FINE_LOCATION | Per feature | Answers “where is my phone” and location-aware requests with a precise fix. Location is read on a tool call and returned to your client; Aster stores no location history. |
| ACCESS_COARSE_LOCATION | Per feature | The approximate fallback when you grant only coarse location, or when a precise fix is unavailable. |
| MANAGE_EXTERNAL_STORAGE | Per feature | All-files access. This is the permission behind the file tools, and the reason those tools are not path-scoped: with it granted, an absolute path is read or written as given. It is a special-access grant you make in Settings, and it is the single grant to think hardest about. |
| READ_EXTERNAL_STORAGE | Per feature | The legacy read path, capped at Android 12L and below. On newer versions the all-files grant supersedes it. |
| WRITE_EXTERNAL_STORAGE | Per feature | The legacy write path, capped at Android 10 and below. Ignored entirely on anything newer. |
| QUERY_ALL_PACKAGES | Per feature | Lists installed apps so the AI can open the right one by name, and so the banking denylist can identify the foreground package it must refuse to drive. |
| PACKAGE_USAGE_STATS | Per feature | Adds a last-used time to the installed-apps list, for “which apps have I not opened in months”. Declaring it is not granting it: you must enable Usage access in Settings, and until you do, last_used comes back empty. |
| READ_CONTACTS | Per feature | Resolves a name to a number, so “call Priya” works without you reciting digits. |
| WRITE_CONTACTS | Per feature | Required by exactly one tool, and it is the destructive one: aster_delete_contacts, which permanently removes contacts from the address book. There is no save or edit path — ContactHandler.kt dispatches only search_contacts, list_contacts_full and delete_contacts, and checks this permission solely inside the delete. Decline it and the contact tools become read-only. |
| CAMERA | Per feature | Captures a photo when a tool call asks for one. There is no background or continuous capture: the camera runs for that single request and stops. |
| FOREGROUND_SERVICE_CAMERA | Per feature | The Android 14+ subtype that must be declared for the service to use the camera at all. Camera hardware is marked optional, so Aster installs on a phone without one. |
| SET_ALARM | Per feature | Hands an alarm or timer to your existing clock app. Aster does not replace it or run a clock of its own. |
| VIBRATE | Per feature | Haptic feedback in the app’s own interface, and the aster_vibrate tool — so a remote caller with an approved device can buzz the phone with a custom pattern (MediaHandler.kt dispatches the `vibrate` action). |
| SYSTEM_ALERT_WINDOW | Per feature | Draws the interactive approval overlay and the companion face above other apps. The face itself is decorative — a bare tap on it opens a normal, tap-jacking-protected activity and performs nothing privileged. Without the grant, Aster falls back to a full activity for the same prompts. |

## Hardening

Everything above is what Aster does on its own. These 6 changes to a default install are yours to make, in the order they matter.

### 1. Approve only devices you own

Approval is the whole trust boundary. An approved device can be commanded, and on the file tools that means any path the app can read. A pending device is harmless — it can connect and be named, but every command dispatch refuses it. Check the name and model in the dashboard before you press approve, and revoke anything you do not recognise.

### 2. Encrypt the link with Tailscale before leaving your LAN

The phone-to-server WebSocket is plain and unencrypted, and the server terminates no TLS of its own. On a network you control that is a considered trade. Anywhere else it is not: join both machines to a tailnet and point the phone at the tailnet address, so the same traffic rides an encrypted WireGuard tunnel with no port forwarding and no public exposure. Aster detects the Tailscale CLI and reports the address to use.

On the server:

```bash
tailscale status
# then point the phone at the tailnet host instead of ws://<server-ip>:5987
```

### 3. Withhold all-files access unless you want the file and storage tools

MANAGE_EXTERNAL_STORAGE is a separate Settings grant, and it is the one that makes the file tools unbounded. Withholding it is the real scoping control — but be clear about the price: it disables eight tools, not four. The four file tools stop reading, and so do aster_analyze_storage, aster_find_large_files, aster_index_media_metadata and aster_search_media, which walk the same external storage. Messages, calls, contacts, notifications, alarms, audio, camera capture and screen control are unaffected.

### 4. Keep notifications enabled so the kill switch stays reachable

The STOP control for screen control lives in a persistent notification, deliberately, because it does not depend on the draw-over-other-apps permission the way the on-screen overlay does. Silencing Aster’s notifications removes your fastest way to stop a session mid-action.

### 5. Leave the banking denylist alone, and audit any allow-override

The companion refuses to drive a foreground app matching its bundled banking and payments denylist, and refuses outright when it cannot read the foreground package at all. Owner overrides replace that policy wholesale rather than merging into it, so an override list is the exact thing to re-read after any change.

### 6. Leave event forwarding off unless you want it, and check where it points

Forwarding is the only way Aster makes an outbound call, and it is off until you enable it. Once on, it POSTs six kinds of event to whatever URL is recorded in `~/.aster/event-forwarding.json`: notification text, SMS sender AND body, the number and resolved contact name of an incoming call, and device connected / disconnected / pairing-required status. Note the asymmetry in that file — every other event kind is opt-in, but incoming calls forward unless `events.incomingCalls` is explicitly `false`. Read the file before trusting a phone that has forwarding enabled, and prefer a destination on your own network.


## Common questions

### Does Aster need root access?

No. Aster drives the phone through the official Android Accessibility Service API — the same system that powers TalkBack and every other screen reader — so there is no rooting step, no unlocked bootloader, no ADB exploit and no shell persistence. It runs on Android 8.0+ (API 26). Enabling the service is a toggle in Android Settings that you turn on yourself, behind Android's own warning screen, and can turn off at any time.

### Is the connection between the phone and the server encrypted?

Not by default. The phone-to-server WebSocket is plain ws:// and the server terminates no TLS of its own; the Android app ships a network-security config that permits cleartext because Android cannot pin a certificate to a bare LAN IP address. On a home or office network you control, that is a considered trade. Off it, it is not acceptable — join the phone and the server to a Tailscale tailnet so the same traffic rides an encrypted WireGuard tunnel, with no port forwarding and nothing exposed to the public internet.

### Can Aster read or control my banking apps?

Driving them is refused by default. A companion-side guard blocks screen-control actions — tap, type, scroll, gestures, key presses and app launches — whenever the foreground app matches its bundled banking and payments denylist, and it is fail-closed: if the live foreground package cannot be read at all, a gated action is refused rather than allowed. The guard ships with that denylist loaded, so it protects a phone that has never synced a policy. Screen reads are deliberately not gated by it, because blinding the agent would also blind the check that decides whether acting is safe; the guard stops the agent acting, not looking.

### Which folders can the file tools reach?

Any path the app itself can read, on a device you have approved. aster_list_files, aster_read_file, aster_write_file and aster_delete_file each take only a device id and a path — there is no folder parameter to narrow — and the app holds the all-files MANAGE_EXTERNAL_STORAGE grant, so an absolute path is used as given. Owner-approved folders are real but scope a different surface: the on-device IPC host-directory feature used by a local agent such as OpenAlly. Withholding all-files access is what bounds this, and it costs more than the four file tools — see the next answer.

### What exactly stops working if I decline all-files access?

Eight tools, not four. MANAGE_EXTERNAL_STORAGE is a separate Settings grant, and declining it disables the four file tools — aster_list_files, aster_read_file, aster_write_file, aster_delete_file — and also the four storage and media tools that walk the same external storage: aster_analyze_storage, aster_find_large_files, aster_index_media_metadata and aster_search_media. So "find what is eating my storage" and "find my beach photos from December" stop working too. Messages, calls, contacts, notifications, alarms, audio, camera capture and screen control are unaffected.

### Does a new phone get access as soon as it connects?

No. Approval gates commands, not the socket. An unknown phone that reaches the server is registered with status "pending", keeps its WebSocket open and is told the handshake succeeded, so you can see and name it in the dashboard well enough to recognise it. It cannot act: every command dispatch checks the device status first and refuses anything that is not approved. Nothing runs on the phone until you press approve.

### What happens if I lose the phone?

Reject the device in the dashboard. That flips its stored status, pushes a rejection to the phone and closes its WebSocket, and any later reconnection is refused at command dispatch — so the AI can no longer act on it even if the handset is powered on and online. Because the link only works across your own LAN or tailnet, a phone off that network cannot be commanded in the first place. Aster holds no cloud account to compromise; the device records live in a local SQLite file on your own machine.

### Does Aster phone home?

No, with one opt-in exception you turn on yourself. There is no sign-up, no licence check, no analytics call and no vendor relay: the server is a MIT-licensed npm package you run yourself, and device records, approvals and logs stay in a local SQLite file on that machine. The one outbound call Aster can make is the optional event-forwarding webhook — off until you run `aster set-event-forwarding`, and then it POSTs only to the URL you supplied. It carries six event kinds, and the list is worth reading before you switch it on: notification text, SMS sender and body, the number and resolved contact name of an incoming call, and device connected / disconnected / pairing-required status. Incoming-call forwarding is on by default once forwarding is enabled — set `events.incomingCalls: false` in `~/.aster/event-forwarding.json` to stop it. Scope the rest of the claim correctly, too: it is about Aster, not about your AI client. Whatever you type into Claude, OpenClaw or any other MCP client, and whatever the phone returns to it, still goes to that client's own model provider under that provider's terms. Aster neither sees nor governs that hop.

### Where is my Aster data stored, and how do I delete it?

Two places, both on the machine running the server, and both plain files you can delete. The database — devices, approvals, tool-call logs — is SQLite at `./aster.db`, resolved relative to the directory you ran `aster start` from, or wherever `DB_PATH` points. Runtime state lives in `~/.aster`: `~/.aster/status.json`, `~/.aster/aster.pid` and, if you enabled forwarding, `~/.aster/event-forwarding.json`. Stop the server, delete both, and nothing of Aster's is left. There is no cloud copy to request.
