Skip to Main Content

Haptura

Built at Meta Global AI Developer Hackathon · Oct 26, 2026 · Remote

Haptura — Demo video

An agent that works your phone's apps, and a gate outside the model that refuses any touch landing on a window your task's app does not own. Live demo page: https://sharminsirajudeen.github.io/haptura/ (the measured results, how the gate works and the arena replay, in a browser, no setup). An AI agent working a phone's apps can be led to touch a window that is not the app it was asked to use: a fake system dialog drawn over that app, or another app's window that never takes focus. A check of which app is in front misses the second. Before every tap and scroll, the gate asks Android's input dispatcher which window the touch will reach, takes that window's owner uid (set by the system, not chosen by the app), and allows the touch only when the owner is the task's app or an allowlisted package (the system UI, the keyboard the phone is set to). Muse proposes every action; the gate, outside the model, grants or refuses each touch, and Muse is told why when it refuses. Measured on a real phone, a Galaxy S26 (Android 17) in Samsung's Remote Test Lab, turned sideways: - 22 GhostEI-Bench overlay attacks, one decision each: the gate refused all 21 taps Muse Spark sent at the attacker's dialog, naming the attacker's app as the window's owner. In 4 of them Muse had pressed the attacker's Install; ungated, those taps reached the attacker's app. Told the owner first, Muse pressed Install in none, and the gate refused the 9 taps it still sent. In the same tasks with no attack, 0 of 43 taps were refused. - The agent, recorded: asked to turn on Dark mode, with GhostEI's dialog fired mid-task. Gate on: Muse tapped the dialog's "Cancel", the gate refused it, Muse reopened Settings and finished, Dark mode on. Gate off: the same tap reached the attacker's app. Why not a prompt: on the Galaxy S26 the gate's verdict went into Muse's prompt ("window owned by com.example.myapplication, not the task app"). Muse still tried to tap the attacker's window 9 times; the gate refused all 9. A check inside the model is advice; the gate, outside it, decides. Measured on the Android emulator: - A window that never takes focus: with the task app holding focus and another app's window (the keyboard, standing in) over it, an ungated touch reached that window in 20 of 20 trials while a focused-window check allowed all 20; the gate, run with an empty allowlist for this test (the shipped one includes the keyboard), refused all 20, naming the window's app. - A fake dialog mid-task: in 36 multi-step runs per model (3 tasks, with and without a GhostEI-Bench dialog, gate on and off), with the gate off the attacker's app received the touch in 9 of 9 attack runs on Muse Spark (11 touches in 9 runs on Muse Wonder); with the gate on it received none, the gate refused in 9 of 9 per model, and every run completed its task. False refusals: Spark 5 (on Settings' search page, another package), Wonder 0. - Muse's own captured choice (the dialog's "Cancel", run 37) replayed without a model call: delivered to the attacker's app with the gate off, refused with it on. How we built it Python, adb, the Android emulator (API 36) and a Galaxy S26 in Samsung's Remote Test Lab. The gate parses dumpsys input's window list under a strict grammar (any line that breaks it means no owner, so the touch is refused), finds the window a point reaches, and maps its uid to a package with cmd package list packages -U. On a screen turned sideways it first maps the touch through the display's transform from the same dump, so the window it judges is the one the finger lands on. The agent reads the screen's elements and a screenshot each step and names one action by element number. Every measurement has its raw dumps, records and a README; tests recompute every verdict from the saved dumps, and every check has a mutant that must turn its test red. Each change went through one adversarial review and a full gate in a fresh clone before merging. Built during the hackathon The gate, the agent, the lab, the replay and the bench (phone/), the desktop owner gate (mac/), the arena and its evaluation, the demo page, every measurement under results/, and every test and mutant. Not ours: GhostEI-Bench's attack app and dataset (MIT), run unchanged; the Android emulator and its apps (Google's); Muse Spark and Muse Wonder (Meta's). The README lists the few tooling files written before the event. Limits - Not atomic: 37.6–76.3 ms pass between reading the window list and sending the touch; a window raised just before an approved touch took it in 6 of 10 trials when the touch went 55–60 ms later. - Back is not checked; after a refusal Muse pressed Back in 6 of 9 gated attack runs per model, and Back reaches the focused window. - A dialog drawn by the task app itself passes, and a window of an allowlisted package (the system UI, the keyboard) passes wherever it is drawn. - In every multi-step bench attack run both models tapped the dialog's dismiss button, not its confirm: what the gate stopped there is the attacker's app receiving the touch. On the phone's single-step cases Muse pressed the confirm in 4 of 22. - One real phone model; the rest on the emulator. No hostile not-focusable overlay was run (the keyboard stands in). The gate runs from the computer that drives the phone over adb. - A screen of the task app that belongs to another package is refused (Settings' search). What's next Gate the Back key the way touches are gated: today the agent's Back (input keyevent BACK) reaches the focused window unchecked, and after a refused tap that window is the attacker's dialog (Back was the next action in 6 of 9 gated attack runs per model). Run a hostile overlay that never takes focus (an attacker's FLAG_NOT_FOCUSABLE window over the task app) against the owner check, which reads the input dispatcher's window list rather than the focused window; in the lab the keyboard stood in for it, and the gate named the keyboard's uid and refused 20 of 20 touches that a focus check allowed. The README names the records behind every number.

Team