Repository navigation
NexusAndroid 0.7.0
Security
- Payment pages behind plain
httpslinks needconfirm=trueeven when they open in the browser: revolut.me,
paypal.me, paypal.com/paypalme (and /checkoutnow, /donate), cash.app, lydia-app.com, lydia.me, buy/checkout/donate
.stripe.com, venmo.com, wise.com/pay, SumUp links, bunq.me, monzo.me (host and subdomains, any case). - Shutdown, reboot and reset intents (
ACTION_REQUEST_SHUTDOWN,REBOOT,MASTER_CLEAR,FACTORY_RESETand vendor
variants with SHUTDOWN, REBOOT or WIPE in the action) needconfirm=true. - A link that Android would send to one app is still checked against every app that could take it: a banking or
identity app among them is refused as before. Any component whose class name contains "Chooser" or "Resolver" is
treated as a chooser, not a final target (One UI answersandroid/com.android.internal.app.ResolverActivity). - Clipboard: fewer false alarms and fewer misses.
.,-and_no longer count as password symbols, so file
names, versions, model numbers, plates and host names are shown; known key prefixes (sk-,ghp_,github_pat_,
AKIA,xox…-,AIza,glpat-…), JWTs, PEM blocks, long random hex or base64 tokens and short words mixing
letters with password punctuation (!@#$%^&*+=?~…) or three kinds of characters are hidden. - Forged messages: an SMS whose text contained
\nRow: 1 _id=999, type=2, body=…could add a fake (even outgoing)
message to a conversation. The phone helper no longer returns the text output ofcontent: it queries the
providers itself (sameActivityManagerpath ascontent) and serializes the cursor rows with length-prefixed
values (protocolNXQ2), decoded as such on the PC. On theadb shellfallback, a read is refused, and reported,
when itsRow: Nnumbers do not run strictly from 0, exceed the requestedLIMIT, or differ from the row count of
the same read reduced to_id(no free text). Columns are split along the requested projection, so a contact named
"Dupont, Jean" is no longer cut to "Dupont". - Predictable helper socket: each start draws a random 128-bit token, passed to
app_process, part of the abstract
socket name and sent back in every answer. The PC refuses a server that does not return its token; a name already
taken is no longer considered normal (the PC relaunches with a new token, then falls back toadb shell). A
forward left by another process is adopted with its token instead of piling up. - Shared safeguards policy updated from
NexusHarness/shared: post, reply, tweet, subscribe, free trial, book,
reserve, donate, answer and accept a call, dial, transfer and price buttons (4,99 €,$12) needconfirm=true;
plain_labelspells out €, $, £ and ¥ as eur, usd, gbp and jpy like the sharedplain. - Security codes are now also masked in English, Spanish, German and Italian messages ("Use 482913 to sign in",
"Tu código: 482-913", "Ihr Bestätigungscode lautet 482913", "Il tuo codice è 482913", "Token: 482913", "log in",
"clave"), without masking dates (12/10/2026, 2026-10-12) or amounts followed by a currency (1 250 €). - A short swipe whose start and end fall inside the same clickable, non-scrollable element is judged as a tap, with
the strict safeguard (confirmation word, unnamed icon in a messaging app, nothing readable). fileson the PC side: keys, secrets and papers (.ssh,.gnupg,.aws,.azure,.nexus,NexusSecure,
Administratif,.envfiles) and the Windows Startup folders are never read or written, even with
confirm=true; outside Downloads, Documents, Desktop, Pictures and Videos (OneDrive included),confirm=trueis
required. The real path (symlinks,..) is resolved before the check.- User back on the phone during a batch: before every gesture after the first, a light activity read
(dumpsys powerfiltered on the phone) stops the batch at once if the user touched the screen since the agent's last
gesture, and the report lists the steps already done. A one-gesture batch costs nothing more. - Screen kept on: the raised
screen_off_timeoutis now 5 minutes instead of 30 (Android counts it again from every
injected gesture; always longer than the idle delay). A server killed outright leaves the phone unlocked 5 minutes
at most. The 30-minute value set by older versions is still recognised and restored. During a longlook-only
phase, more than 3 minutes after the last gesture or poke, a screen that is on getsKEYCODE_WAKEUP(no visible
effect, counted by Android as activity), recorded as an agent gesture, never as the user's touch.
Added
-
look(marks=...): withshow=both, the image carries small[N]badges (the element numbers of the list) at the
top-left of each element, with a thin outline of its bounds.auto(default) numbers every element when there are
21 or fewer, otherwise only the unnamed ones (icons without text or description);all;none. Drawn after the
security codes are blacked out (a badge only covers, never reveals), never on an image that is refused, and not
withshow=image(no list for the numbers to refer to). About 4 ms for 80 elements on a Fold screenshot. -
clipboard(action="read"|"write", text): reads the phone's clipboard (security codes masked like messages; a lone
code, or a single word that looks like a password or a token, is never shown; says when it is empty or not text)
or sets it without pasting, through the same scrcpy server used to type accents (started for the call, stopped
after). Typing accents still gives the user their previous clipboard back. -
launch(action, data, extras, confirm): opens a precise screen (android.settings.WIFI_SETTINGS, a deep link,
extras as string, integer or boolean;appthen limits the target to that app). The target is resolved first
(cmd package resolve-activity), started by its exact component, and reported with the foreground read back.
Calling, sending or paying directly (CALL,RESPOND_VIA_MESSAGE, uninstall and reset actions, payment actions
such asorg.chromium.intent.action.PAY,upi:/bitcoin:/payto:… links, auto-send extras) needs
confirm=true; a banking, payment or identity app, as the resolved target or as one of the candidates of
Android's chooser, is refused even withconfirm=true.act open_urlrefuses payment links withoutconfirm=true
too. -
Tests that tool descriptions, titles and schema descriptions contain no French, that offline errors and refusals
(phone absent, unknown target, step needingconfirm=true, missing or invalid arguments, locked phone) are in
English, and that the instructions and descriptions never compare the server with other tools. -
Typing accents and emoji without
install.ps1: when the pinned scrcpy server (v5.0.1) is missing or altered, the
server downloads it once from the official Genymobile/scrcpy GitHub release into~/.nexus/nexus-android/tools,
checks it against the SHA-256 written in the code and writes it atomically. If the download fails or the digest
differs, the step fails with a message giving the URL, the expected digest and the folder; nothing is typed, and
the text is never transliterated. -
Windows installer for the public repository (
install.ps1): uv environment under~\.nexus\nexus-android\venv
(package copied, not editable), pinned scrcpy server, stdio registration with Codex (~\.codex\config.toml,
previousnexus-androidtable replaced, file backed up first) and Antigravity (~\.gemini\config\mcp_config.json,
backed up first) when they are installed, theclaude mcp addcommand printed for Claude Code (no Claude CLI call),
thentest.ps1. No OneDrive, daemon supervisor or author-specific path;-NoRegister,-SkipTests,-Live. -
Python 3.12 and 3.13 support:
requires-python = ">=3.12"(3.11 lacks thetypestatement); the CI runs
test.ps1 -Python 3.12|3.13|3.14on Windows.test.ps1 -Python X.Ykeeps a separate test environment per version. -
server.jsonlistsNEXUS_ANDROID_AWAKE_IDLE. -
messages(before=...): reads older messages (or older conversations) from the ISO date given at the end of the
previous answer. Answers ofmessages,notificationsandfiles listare capped at about 60,000 characters, cut
between two messages or lines, and say how to read the rest. -
A call cancelled by the client (MCP
notifications/cancelled, or the client disconnecting) stops the batch before
its next step, stops awaitstep, and stops waiting for the phone.
Changed
-
Private installer: Antigravity now gets nexus-android as a plugin (
~\.gemini\config\plugins\nexus-android, with
the skill and its ownmcp_config.jsonpointing at the shared daemon with the token, written on the PC only), like
the other Nexus tools. The old entry in the globalmcp_config.jsonis removed after a backup; the plugin is
enabled inconfig.jsononly while Antigravity is closed. -
lookin about 0.17 s instead of 3 s over Wi-Fi: the phone helper keeps one accessibility connection open and
returns the UI tree, the screenshot (JPEG made on the phone, already at the size sent to the agent) and the screen
state with the fold posture in one exchange, instead ofuiautomator dump,screencapand severaldumpsys. The
tree is read likeuiautomator dumpdoes (active window, visible nodes, bounds clipped to the display, same
attributes), so the element list, numbering, masking and refusals do not change; the target check before each
gesture uses it too. As withuiautomator dump, the tree waits for one second without accessibility events, but
only after a change: a still screen is read at once, and a screen that never settles (video) is read after 3 s
instead of failing. Read-only (no gesture, no global action); the user's accessibility services are not suspended.
The connection is released before anyuiautomator dumpfallback, after 45 s without a screen read, and right after a read of a protected (banking or identity) app, whose security SDKs watch for an active accessibility connection. -
phone: model, Android version, boot id, fold posture and screen state are read in one shell command, and an
already known link is no longer searched again: 0.19 s instead of 0.34 to 0.42 s per call over Wi-Fi (1 adb round
trip instead of 4 or 5), 0.43 s instead of 0.51 to 0.57 s for the first call of a process. -
info(kind="location"): phone clock and known positions in one shell command,dumpsys locationfiltered on the
phone: 0.11 to 0.15 s instead of 0.20 to 0.23 s. -
The SIM country (used to compare numbers) is read once per phone for all the sessions of the shared daemon,
instead of once per session. -
Tool descriptions, parameter descriptions and every message returned to the model (results, per-step reports,
refusals, errors, hints, warnings, element labels, phone, settings, info, files, messages and notifications
outputs) are now in English, with the same meaning and the same guardrails. Data patterns stay multilingual: the
shared safeguards file, keyword lists and regular expressions over screen text, SMS and notifications, Android UI
labels and bank app names; the safeguards file's French screen descriptions are translated by the server. -
Quick start,
server.jsonand client configs:uvx nexus-android(no more--python 3.14). -
Classifiers: Python 3.12 to 3.14, Telephony and AI topics; the unrelated "Testing" topic and the license
classifier (redundant with the SPDXlicensefield, PEP 639) removed. -
README: absolute image and link URLs (the demo image was broken on PyPI); exact language of the server (everything
sent to the client in English); first-use download of the scrcpy server, and the one internet
request in Privacy; files left on the phone (helpers kept; UI dump andnexus-q-*/nexus-p-*read folders deleted
after each read) and a cleanup command that removes them all (rm -rf /data/local/tmp/nexus-*); public installer;
shared daemon (--http, token, limits); warning thatadb tcpip 5555is not encrypted; Performance section. -
SECURITY.md: what the daemon token does and does not protect against (local malware can use the ADB server on
port 5037, which has no authentication), the phone helper's shell/root-only peer check and per-launch token, and
the unencryptedadb tcpipmode. -
Shared policy file (
garde-fous.json): comments made neutral (no names); no pattern or value changed. The public
copy is embedded by the export with its SHA-256 (vendored.sha256), checked by a public CI workflow. -
Finding the phone over Wi-Fi: the last known address is tried first, then mDNS; the port scan runs only when the
PC is on the same /24 network, checks each open port with an ADB handshake (CNXNanswered byCNXN,AUTHor
STLS) and tries 4 ports at most. A failed search is remembered for 120 s (a USB cable is still seen at once), and
the screen-awake timer never scans. -
launchresolves the launcher activity (cmd package resolve-activity) and starts it witham start -n,monkey
only as a fallback; the package list is cached for 60 s; the foreground app is polled every 0.25 s (5 s at most)
instead of a fixed 2 s wait.draftpolls the same way instead of waiting 2.5 s. -
The phone helper answers an error when a batch of reads takes more than 8 s (under the PC's 10 s, so a hung
provider is reported as such, not as a lost link) and cancels the hung read, instead of blocking one of its 8
threads for good. -
Element.scrollableis a typed field; the tap-like swipe check no longer relies on a French state label. -
MCP annotations:
filesis no longer idempotent (a repeated pull createsname (1),name (2)) nor destructive
(it never deletes or overwrites);checkupis destructive and not idempotent (clean=trueclears caches).
Fixed
- Wi-Fi lost during a read: about 3 minutes of waiting (60 s, three 30 s retries, then the
adb shellfallback).
Now a helper read waits 10 s at most and state reads 5 s; a link error goes up at once instead of falling back;
after a timeout a Wi-Fi transport is dropped from adb and the next call looks for the phone again. - The in-process lock of the shared daemon waited forever; it now gives up after 30 s with "phone busy", like the
lock shared between servers. - A refused step no longer marks the time of the agent's last gesture (a touch by the user just before could pass for
the agent's at the next batch); only a gesture really sent does. x,ytargets accept the@540,1100form printed bylook.checkup: a short, empty or wrappeddfoutput no longer crashes it; apps installed by hand (installer=null)
are now listed as outside the stores.settings brightness: an unset (null) brightness no longer crashes the read; a non-numeric value for
brightness or volume is refused instead of being read as 0.- WhatsApp drafts no longer assume +33: the country code comes from the SIM card (
gsm.sim.operator.iso-country,
else the network's), with the national prefix of that country (libphonenumber table). Unknown country and a number
without country code: refused with a clear message. Phone numbers are compared in the SIM country's national
format. phone/build.ps1works when the helper compiles to a single class file.