# Mobix **An Android pentest lab you can run by hand, or hand off to an AI agent.** It covers the basics you'd expect from a Frida wrapper: SSL-pinning and root-detection bypass, live traffic capture, a web dashboard. What's different is the MCP side. The whole lab is exposed as a 36-tool control plane, so a Claude Code session can run the loop itself: scan an app, watch its traffic, flag likely IDORs, write up findings with evidence attached. The unattended path is locked to a read-only allowlist, so it can't do anything destructive on its own. Install is one line. Starting a scan is one command.

MIT License macOS Apple Silicon 36 MCP tools blackfoxxx/Mobix MCP server GitHub stars

Demo: clicking a captured request auto-flags it as an IDOR candidate, then the Claude Analysis tab shows the same finding written up with severity

> Synthetic demo app/traffic/findings throughout, no real target data. See > [Screenshots](#screenshots) below for the full set, one per tab. **If you find this useful, a star helps other pentesters find it too.**
Contents (click to expand) - [Installing](#installing) - [Starting a scan](#starting-a-scan) - [Switching to Burp from the dashboard](#switching-to-burp-from-the-dashboard) - [Web dashboard](#web-dashboard) - [Screenshots](#screenshots) - [Quick start: the `mp` command](#quick-start-the-mp-command) - [Device](#device) - [Layout](#layout) - [Underlying scripts](#underlying-scripts) - [Scripts](#scripts) - [Interception: three layers](#interception-three-layers) - [Using Burp instead of mitmproxy](#using-burp-instead-of-mitmproxy) - [Feeding traffic to Claude Code](#feeding-traffic-to-claude-code) - [Where the requests are](#where-the-requests-are) - [Web pentest with Burp](#web-pentest-with-burp) - [Inspecting one app's traffic](#inspecting-one-apps-traffic) - [Claude Code engagements (`mp engage`)](#claude-code-engagements-mp-engage) - [Mobile pentest skill](#mobile-pentest-skill) - [Sessions & resuming](#sessions--resuming) - [Logging in](#logging-in) - [Site map & Repeater](#site-map--repeater) - [Wiring traffic into Claude Code (lightweight)](#wiring-traffic-into-claude-code-lightweight) - [Static analysis](#static-analysis) - [Runtime exploration](#runtime-exploration) - [Installing an app](#installing-an-app) - [Switching apps](#switching-apps) - [Apps that refuse to run on a rooted device](#apps-that-refuse-to-run-on-a-rooted-device) - [Gotchas](#gotchas) - [Scope discipline](#scope-discipline)
## Installing One line on a fresh machine. It clones this repo to `~/mobile-pentest` (override with `MP_ROOT=/some/path`), then runs the installer below: ```bash curl -fsSL https://raw.githubusercontent.com/blackfoxxx/Mobix/master/scripts/bootstrap.sh | bash ``` Already cloned it yourself? Run the installer directly instead: ```bash scripts/install.sh # installs everything this lab depends on mp doctor # verifies it actually works (not just "present") ``` - **macOS**: fully supported, the only tested target. Installs Homebrew (if missing), all formulae/casks (jadx, apktool, dex2jar, scrcpy, coreutils, fzf, jq, openssl, android-platform-tools, mitmproxy, Docker), pins `frida`/`frida-tools`/`objection` to the exact version this repo's bundled `bin/frida-server-*-arm64` needs, installs `cryptography` (needed by any target-specific request-signer you write; see [Re-signing custom request-integrity fields](#re-signing-custom-request-integrity-fields)), and symlinks `mp` onto `PATH`. It does **not** install MuMu Player Pro itself, since that isn't distributed via Homebrew. Grab it from the vendor and root it first. Safe to re-run any time; every step is install-if-missing. - **Linux**: **partial install only.** The portable tooling (adb, frida, jadx, apktool, mitmproxy, Docker) installs via apt/dnf/pacman, but MuMu Player Pro is macOS-only, and so is this lab's automation layer (`mp engage`, `creds.py front`, the LaunchAgent keepalive, all built on `osascript` and `launchctl`). On Linux you drive some other adb-reachable rooted device or emulator by hand with `adb`/`frida`/`objection`. `mp` itself runs, but anything that shells out to MuMu-specific automation will fail. - **Windows (native)**: not supported. The script exits with instructions to use WSL2 and take the Linux path from inside it. A real device or emulator is still needed, since MuMu won't run under WSL2 either. `mp doctor` (`scripts/selftest.sh`) checks host tool versions, the frida CLI-vs-device-server version match (and warns about shadow pipx installs that could silently take over `PATH`), `cryptography` importability on whatever `python3` resolves to, live device root and `/system` writability, an actual frida handshake, Docker/MobSF health, and MCP registration. Being present doesn't mean it's working. Use `mp doctor --quick` to skip the device-dependent checks when no emulator is booted. ## Starting a scan **`mp scan`** is the one command to reach for. CLI and dashboard both funnel through it now, so it's the same flow no matter where you start from: ```bash mp scan # no target chosen -> opens the app picker mp scan com.target.app # an already-installed app (fuzzy name OK) mp scan app.apk # or a .apk/.xapk/.apkm/.apks file mp scan https://host/app.apk # or a direct APK URL mp scan 'play.google.com/...id=pkg' # or a Play Store link ``` Every path converges on the same four numbered steps, always printed the same way: ``` [1/4] environment emulator up, frida-server running, CA installed [2/4] target resolved package (installed / fuzzy-matched / installed-then-set) [3/4] capture FRESH session, scope printed explicitly (or a warning if empty) [4/4] app launched hooked - root checks blocked, SSL unpinning active ``` It always starts clean: force-stops whatever was hooked before, never silently reuses a stale capture. If the scope is empty it says so loudly (`⚠ no scope set -> capturing ALL hosts`) instead of leaving that state invisible. That ambiguity caused more than one mix-up early on. If a name matches more than one installed app it lists every match and asks you to be specific rather than guessing. `mp scan` is a thin front door over lower-level building blocks (`mp go`, `mp switch`, `mp install`, `mp launch`) that still exist for scripting or finer control. See `mp help`, under **BUILDING BLOCKS**. You don't need them for normal use. **Note:** `mp scan` used to mean *static APK analysis*. That's now **`mp static [apk]`**. The old name collided with what people actually meant when they said "start a scan session," and untangling that collision is most of what this redesign fixes. ## Switching to Burp from the dashboard The **Services** tab has a **Burp Suite** card mirroring `mp burp`/`mp burp --mitm`: a live status line (which process actually owns the proxy port, mitmproxy, Burp, or something else, plus what the device is currently pointed at), a port field, and **Switch to Burp** / **Switch back to mitmproxy** buttons. Same prerequisite as the CLI: Burp's listener has to be bound to **All interfaces** first, or the emulator can't reach it. **Switching to Burp stops whatever capture is currently running on that port as its first step**, before it even checks whether Burp is up. Don't click it mid-capture unless you mean to end that session. Switching back to mitmproxy is safe to click anytime; it only updates the injected cert and doesn't touch a running capture. ## Web dashboard ```bash mp dash # http://127.0.0.1:8090 ``` Nine tabs: **Install**: upload an APK/bundle or paste a link/Play Store URL. Runs static analysis and, unless passive, hooks it with a live capture. Log streams on the right. **Traffic**: a master/detail inspector with a draggable splitter, plus a host/path tree on the left to filter by API surface. Click any request to see its headers, pretty-printed request and response bodies, decoded base64 payloads (`requestBody` / `responseBody` are unwrapped automatically), flags and IDOR candidates. Free-text filter plus a per-app dropdown. You do not need to open mitmweb for normal inspection. **Repeater**: edit method/URL/headers/body and resend directly from the Mac, no device hop. Auto-detects a `macIn`-style integrity field and offers to re-sign it. Keeps the last 25 sends this session. **Login**: stored per-app credential fields (text/password/TOTP with a live code). "Send" pushes a value into whatever field is focused in MuMu. **Bypass**: a checkbox per module, with descriptions. Six Frida scripts (proxy override, cert injection, unpinning, unpinning fallback, root detection, anti-detection) and the nine anti-detection sub-modules (Build spoofing, SystemProperties, File.exists, Debug, BufferedReader, Runtime.exec, TelephonyManager, SensorManager). Toggles persist to `bypass.json` and apply on the next launch; **Relaunch target** is right there. Turning a script off drops its `-l` flag. Turning an anti-detection module off writes it into `ANTI_DETECT_SKIP` in `config.js`. **Apps**: emulator packages with running markers, click to set the target, plus scope entry and Pull APK. **Services**: environment and service state, MobSF/Burp controls, and the remaining actions. **Claude Analysis**: the live findings list from an `mp engage` run (interactive or `--auto`), with a hero card for the latest finding and a live-activity feed. See [Claude Code engagements](#claude-code-engagements-mp-engage). **Sessions**: every past capture session and Claude Code engagement, browsable read-only, forever. A new scan never deletes anything. See [Sessions & resuming](#sessions--resuming) below. The **Emulator apps** panel lists packages installed on the *Android device* (`pm list packages -3`), marks which are running, and sets the target on click. It never shows host/macOS applications; the lab only ever targets the emulator. **Bound to `127.0.0.1` only, deliberately.** The dashboard can run `mp` commands, so exposing it on a routable interface would hand the whole toolkit to anyone who can reach it. Actions are a fixed whitelist and package/scope arguments are regex-validated, but do not put it behind a tunnel or bind it to `0.0.0.0`. ## Screenshots All of the below use a synthetic demo app (`com.example.bankapp`) with fabricated traffic and findings. None of this is real target data.
Install tab
Install: upload/link an APK, static analysis and capture start automatically
Traffic tab
Traffic: host tree, request list, decoded detail pane
Repeater tab
Repeater: edit and resend, with macIn re-signing and history
Login tab
Login: stored test creds incl. live TOTP, push into a focused field
Bypass tab
Bypass: Frida scripts and anti-detection modules, toggle per launch
Apps tab
Apps: installed packages, click to set target
Services tab
Services: environment status, capture/MobSF controls, Burp switch
Claude Analysis tab
Claude Analysis: live findings by severity, latest-finding hero card
New scan modal
New scan: one target, one fresh session, mirrors mp scan
## Quick start: the `mp` command Everything runs through `mp` (on `PATH` via `/opt/homebrew/bin/mp`). Pick a target app once; every other command then applies to it. ```bash mp apps # fzf picker over installed apps -> sets the target mp scope api.target.com # optional: limit capture to these hosts mp go # everything: boot emulator, hook target, start capture # ... drive the app by hand ... mp analyze # build the Claude-readable feed from the newest session mp stop # end hooks + capture ``` `mp go` needs **nothing** running beforehand. From a completely stopped emulator it boots MuMu, waits for Android, starts frida-server, sets the proxy, repairs the system CA if missing, launches the target with root + SSL bypasses, and starts a scoped capture. About 40 seconds end to end. `mp up` does the same without launching the app. Optional extras with `mp auto mobsf-on` (starts MobSF as part of `mp up`). `mp status` shows device, root, frida-server, proxy, CA, target, hooks, capture and MobSF on one screen. `mp help` lists everything. The target and scope persist in `~/mobile-pentest/config`, so they survive reboots and you only choose once per engagement. ### Persistence A LaunchAgent (`com.aj.mobilepentest.keepalive`) runs `bin/mp-keepalive` every 60 seconds. The CA lives on `/system` and survives on its own, but frida-server and the device proxy setting do not survive an emulator restart. The agent notices and restores both, logging to `loot/keepalive.log`. It deliberately does **not** relaunch your target app; hooking an app is an explicit act. To disable: `launchctl unload ~/Library/LaunchAgents/com.aj.mobilepentest.keepalive.plist` ## Device | | | |---|---| | Serial | `emulator-5554` (ADB also on `localhost:5555`) | | Model / OS | SM-S9180, Android 12 (SDK 32) | | ABI | `arm64-v8a`, native ARM, so stock ARM tooling and ARM-only APKs run without translation | | Root | `adbd` runs as **uid 0**; `/system/bin/su` present (no Magisk) | | SELinux | **Permissive** | | `/system` | mounted **read-write**, so system CA certs install directly | | Device IP | `10.0.2.15/24`, gateway `10.0.2.2` | **Host address from the device: `10.0.2.2`.** The Mac's LAN IP (`192.168.0.184`) also works but changes with the network; `10.0.2.2` does not. Everything is configured against `10.0.2.2`. ## Layout ``` ~/mobile-pentest/ ├── scripts/ operator scripts (below) ├── frida-scripts/ httptoolkit unpinning suite + config.js (CA + proxy baked in) ├── bin/ mp / mp-dashboard / mp-mcp + frida-server (fetched by install.sh, not committed) ├── apks/ pulled APKs, one dir per package (gitignored - not source) └── loot/ captured flows, static-analysis output, MobSF data (gitignored - not source) ``` ## Underlying scripts `mp` is a thin front end over `scripts/`. Use these directly when you want finer control; each is independently runnable. Each capture creates `loot/session-/` containing both the raw flows and an analysis feed built for Claude Code to read. See [Feeding traffic to Claude Code](#feeding-traffic-to-claude-code). ## Scripts | Script | Purpose | |---|---| | `start-env.sh` | Starts frida-server, sets the global proxy, verifies root + CA. Run after every emulator reboot. | | `capture.sh [web\|dump\|proxy\|wg]` | Starts mitmproxy on `0.0.0.0:8080` **and the Claude analysis feed**. `web` = browser UI (default), `dump` = headless, `proxy` = TUI, `wg` = WireGuard VPN mode. | | `analyze.sh [scope]` | Converts a saved flow file into the analysis feed offline. | | `proxy.sh on\|off\|status` | Toggles the device-wide HTTP proxy. Turn **off** before using WireGuard mode. | | `launch.sh ` | **Detached** launch with the full bypass chain, no terminal to keep open. Use this for apps with root detection. `--stop` / `--log` to manage. | | `hook.sh [--attach]` | Same chain but an interactive Frida REPL, for when you want to poke at the runtime live. | | `install-ca.sh ` | Installs any PEM/DER CA into the **system** trust store. | | `install-burp-ca.sh [port]` | Pulls Burp's CA from a running Burp and installs it system-wide. | | `pull-apk.sh ` | Pulls base + split APKs into `apks//`. | | `static-scan.sh ` | apktool + jadx + apkleaks; reports manifest flags, exported components, network-security-config, pinning call sites, native libs. | | `mobsf.sh start\|stop\|logs` | MobSF at http://127.0.0.1:8010 (creds `mobsf/mobsf`). Port 8010 because 8000 is used by your `tengu` container. | | `install.sh` | Installs every host dependency: full on macOS, tooling-only on Linux, refuses on native Windows. Safe to re-run. | | `selftest.sh` | `mp doctor`. Verifies every dependency actually *works* (versions match, device handshake succeeds, CA store writable), not just that it's installed. | ## Interception: three layers The layers are cumulative. Each catches what the previous one misses. **1. System CA (installed).** mitmproxy's CA is in `/system/etc/security/cacerts`, so apps trust it even when they ignore user-installed certs, which every app targeting API 24+ does by default. Verified working against Google's own `play-fe.googleapis.com` and every target app tested so far: traffic decrypts cleanly without any app-side pinning changes. **1b. Detection bypasses.** Loaded with every launch: `android-disable-root-detection.js` (su binaries, root manager packages, `Runtime.exec("su")`) and `android-anti-detection.js`, which covers emulator/VM tells (15 `android.os.Build` fields spoofed to a Samsung SM-S928B, `getprop` filtered for qemu/goldfish/nox/mumu strings, emulator device nodes hidden from `File.exists`), debugger and tracer checks (`Debug.isDebuggerConnected`, `TracerPid` masked in `/proc` reads), Frida artefacts (agent lines dropped from `/proc/self/maps` reads, `frida*` paths hidden), plus spoofed telephony identifiers so an emulator's blank IMEI/operator does not give it away. Each module reports `[anti-detect] ok: `. If a defensive app dies on launch, bisect by setting `ANTI_DETECT_ONLY` or `ANTI_DETECT_SKIP` in `frida-scripts/config.js`: ```js var ANTI_DETECT_ONLY = ['Build','SystemProperties']; // enable only these var ANTI_DETECT_SKIP = ['BufferedReader']; // or drop just one ``` **Honest limit:** these defeat *client-side* checks only. Play Integrity or SafetyNet verified by a backend cannot be bypassed here. The verdict is signed by Google and checked off-device. **2. Frida unpinning (`launch.sh` / `hook.sh`).** Defeats apps that pin. The chain hooks `HttpsURLConnection`, `SSLContext.init`, Conscrypt `CertPinManager`, `NetworkSecurityConfig`, OkHttp `CertificatePinner` + `OkHostnameVerifier`, TrustKit, and Cordova/Worklight/Netty/appmattus CT, plus a fallback auto-patcher that catches custom `TrustManager`s at runtime, a proxy override for apps that ignore system proxy settings, and root-detection bypass. **3. WireGuard mode (`capture.sh wg`).** The fallback for apps that bypass the HTTP proxy entirely (raw sockets, custom DNS, gRPC). mitmproxy prints a WireGuard config; import it into a WireGuard client on the device. Turn the global proxy **off** first. Don't stack layers 1 and 3. ## Using Burp instead of mitmproxy 1. In Burp: **Proxy → Proxy settings → Proxy listeners → Edit → Binding → All interfaces.** Burp binds to `127.0.0.1` by default and the emulator cannot reach that. 2. Stop mitmproxy so port 8080 is free (or run Burp on another port and set `MP_PROXY_PORT` to match). 3. `./scripts/install-burp-ca.sh 8080` 4. Re-run the `config.js` cert step if you want Frida's runtime cert injection to use Burp's CA too. Replace `CERT_PEM` in `frida-scripts/config.js`. ## Feeding traffic to Claude Code Raw flows are far too large and noisy to hand to an LLM: a single session is megabytes of images, bundles and protobuf. `scripts/mpfeed.py` is a mitmproxy addon that rides along with every capture and distils the traffic into something readable. It runs automatically; you don't invoke it directly. Every capture writes `loot/session-/`: | File | What it's for | |---|---| | `index.md` | One line per request. **Skim this first.** | | `surface.md` | Endpoints collapsed into templates (`/users/{id}`), with hit counts, status distribution, and authed-vs-unauthenticated split. This is the attack surface. | | `flags.jsonl` | Auto-detected JWTs (with claims decoded), API keys, credentials, PII, card numbers. | | `flows.jsonl` | Full detail, one JSON object per flow. `grep`/`jq` this. | | `flows.mitm` | Raw flows, replay with `mitmweb -r`. | Then just point me at it: > "Analyse `~/mobile-pentest/loot/session-20260805-164934/`" **Scope it to your target** so out-of-scope apps aren't captured or analysed: ```bash MP_SCOPE=api.target.com,cdn.target.com ./scripts/capture.sh dump ``` **Already have a flow file?** Convert it offline: ```bash ./scripts/analyze.sh loot/session-*/flows.mitm api.target.com ``` What it does for you automatically: - Drops images, fonts, CSS/JS, media and binary blobs, so only meaningful traffic survives. - Truncates bodies to 4 KB (`MP_MAX_BODY` to change). - Decodes JWT claims inline, so `role`, `sub` and `exp` are visible without you pasting tokens into a decoder. - Collapses `/users/4821` and `/users/9930` into one `/users/{id}` endpoint, and lists the concrete IDs as **IDOR candidates**. - Flags every endpoint that returned 2xx with **no** `Authorization` or `Cookie` header. That's the fastest route to a broken-access-control finding. Detector accuracy was tuned against real traffic: millisecond epoch timestamps are no longer mistaken for Luhn-valid card numbers, version strings are no longer mistaken for emails, and noisy detectors (UUID, ObjectId, email, phone) only fire when the match is properly delimited rather than sliced out of a base64 blob. Each detector is capped at 3 hits per flow so one chatty request can't flood the file. ## Where the requests are `mp log` shows **Frida hook diagnostics** (`=> com.android.okhttp.Address $init …`). That's the unpinning chain reporting each hooked call, not your traffic. The HTTP requests live in the capture session: ```bash mp req # last 25 requests, readable mp req -f # follow live as you use the app mp req 100 # last 100 mp web # open the flows in the mitmweb UI (browse / edit / replay) ``` `mp req` asks the running proxy where it is actually writing, rather than assuming the newest directory. Those differ whenever a capture was reused. Full detail per request is in `loot/session-*/flows.jsonl` (one JSON object per flow: headers, bodies, IDOR candidates, flags). `index.md` is the skim view, `surface.md` the endpoint rollup. ## Web pentest with Burp For Repeater / Intruder / Scanner, hand the traffic to Burp: 1. Start Burp. **Proxy → Proxy settings → Proxy listeners → Edit → Binding → All interfaces.** Burp binds to `127.0.0.1` by default and the emulator cannot reach that. 2. `mp burp`: stops mitmproxy, pulls Burp's CA through the running listener, installs it into the system store, updates `frida-scripts/config.js` so the runtime injection uses Burp's CA too, and repoints the device. 3. `mp launch`: relaunch the app so the new CA is injected. `mp burp --mitm` switches back. ### Re-signing custom request-integrity fields Some APIs attach a custom integrity/MAC field to every request body (hashed and signed with a key extracted from the app), so editing a body in Repeater invalidates it. The pattern is generally: canonicalise the fields the app signs, hash them, then encrypt/sign with the key pulled from the decompiled APK or its native libs. This lab doesn't ship a target-specific signer, since the exact fields and algorithm differ per app. Write a small `scripts/-resign.py` following that shape and both `brief.py` and the dashboard's Repeater will pick it up automatically if you wire it in next to `zc-macin.py`'s loading pattern. On one real target this turned out to be trivially bypassable: the MAC was RSA-encrypted with a *public* key embedded in the app itself, so anyone could mint a valid one. Worth checking for on any app that does this. Worth trying first, before writing a signer at all: send a modified body **without** touching the integrity field. Some apps compute the MAC over a freshly generated request ID or timestamp that differs from what they transmit, and the server still accepts the request anyway. That's strong evidence the field isn't verified strictly, or at all, and which answer you get is itself the finding. ## Inspecting one app's traffic Every capture records **which app was hooked** when each flow was seen, so the UI can be filtered down to a single app instead of the whole device. ```bash mp seen # which apps produced traffic in this session mp web # picker over those apps -> opens filtered mitmweb mp web tv.app1001.android # skip the picker mp web '*' # everything, unfiltered ``` `mp web` derives the app's hosts from the session and builds a mitmweb view filter (`~d api\-gateway\.1001\.tv`). Note that mitmproxy treats `(`, `)` and `|` as **filter-language operators**, not regex, so `~d (a|b)` is a syntax error. The filter is emitted as `~d a | ~d b` with dots escaped instead. **How attribution works, and its limit.** The emulator NATs every app behind one address, so per-connection attribution is not available. Instead `launch.sh` records the hooked package in `.active-app` and the capture feed tags each flow with it. The honest unit is *"captured while app X was hooked"*, not "sent by app X". Traffic from background OS services during that window is tagged the same way. Combine with `mp scope ` when you need certainty about which traffic is really the target's. ## Claude Code engagements (`mp engage`) Beyond one-shot analysis, `mp engage` starts a **full Claude Code session** for a target: its own folder, live traffic wired in via MCP, and the pentest skills/plugins active exactly as in a hand-started session. ```bash mp engage # interactive session in a new Terminal, in the target folder mp engage --auto # headless agent: reviews the live capture, writes findings mp engage --stop # stop running agents ``` Each run creates `engagements/_/` seeded with: - `CLAUDE.md`: target, scope, authorisation, and how to reach the live traffic tools - `traffic/`: symlink to the live capture session, plus a `BRIEF.md` snapshot - `findings/`: where the agent writes one report per issue The dashboard's **Claude Analysis** tab tracks BOTH modes live, including an **interactive session you're driving by hand in Terminal**. It polls `engagements//findings/` regardless of who's writing to it, sorts newest first (not alphabetically, so a fresh Critical isn't buried under older Lows), and shows a **Latest finding** card at the top with severity and age. A small dot appears on the **Claude Scan** nav tab whenever a new finding lands while you're looking elsewhere, and opening the tab jumps straight to it. **Interactive mode** opens a real `claude` session in Terminal.app in that folder, with full skills, plugins, permissions and MCP. **`--auto` mode** runs a headless agent restricted to the 6 read-only `traffic_*` tools plus Read/Write/Grep/Skill, **no Bash, no request sending**. That restriction is enforced with an explicit `--disallowedTools` deny list, not just by leaving everything else off an allow list: in headless mode a tool call outside `--allowedTools` isn't automatically blocked, it can go through with no one there to prompt for it. This was a real gap (Bash executed successfully in a test run before the deny list was added), not a hypothetical one, which is why the deny list names every non-traffic tool explicitly, Bash included, rather than trusting the allow list alone. It streams events to `scan.log` and writes findings as it goes. Both modes are pointed at **`.claude/skills/mobile-pentest/`**, shipped with this repo: the methodology (what's confirmable from traffic alone versus what needs an interactive session), the finding write-up format, and a severity rubric, all grounded in Mobix's actual tools rather than generic advice. `mp engage` explicitly tells the agent to load it first. See [Mobile pentest skill](#mobile-pentest-skill) below. The **Claude Analysis** tab in the dashboard drives both: Start analysis, Open interactive, Stop, a live findings list with a reader, and the agent's current activity. The Traffic tab's **Scan with Claude** button starts an auto-scan on the current session. In a real engagement the auto agent produced 8 findings unattended: BOLA/IDOR on ObjectId-addressed endpoints, an HS256 JWT carrying PII, mass assignment, wildcard CORS, and an unauthenticated CodePush OTA channel, each with evidence flow numbers and a read-only confirmation test. Note: the agent's first message can trip the broad model safeguard on cyber-security phrasing; it auto-falls-back to another model and continues. The prompt is framed as a security review of already-captured data to minimise this. ## Mobile pentest skill `.claude/skills/mobile-pentest/` ships with this repo, so a fresh clone gets a genuinely knowledgeable pentest agent out of the box, not one that depends on whatever security-review skills happen to already be installed on whoever's machine is running it. Both `mp engage` modes load it explicitly as their first action. It's Android-only, matching what this lab actually does. No iOS guidance, no jailbreak tooling. The main `SKILL.md` is a mode-aware workflow: it tells the agent to check up front whether it's interactive (full tool access) or the `--auto` agent (traffic-only, read-only), then walks through attack surface mapping, authorization and IDOR, session/token handling, data exposure, business logic, and, interactive-only, local storage, IPC/deep links, and verifying the app's own client-side protections actually engaged. Six reference files under `references/` go deeper on each category, and are explicit throughout about what's **confirmed** straight from a captured flow versus what's a **candidate** that needs an interactive Repeater test to prove, the same confirmed/candidate discipline the finding write-up format enforces. A few examples of what "grounded in this lab's actual tools" means in practice rather than generic advice: `idor_candidates` and `authenticated` are already computed per-flow by the capture feed, so the skill treats *inconsistent* enforcement (same endpoint shape, auth present on some calls and not others) as a stronger signal than uniform exposure; a `Set-Cookie` missing `Secure`/`HttpOnly` is confirmable directly from response headers, no active test needed; and a custom request-integrity field (the `macIn` pattern) points back to this README's own section on it rather than duplicating that guidance. Verified against a real, previously-authorised production target in this lab: the agent loaded the skill first, read every reference file before writing anything, pulled full detail on every relevant flow individually rather than skimming the brief, and correctly declined to write up an IDOR candidate until it could point to the specific flow that would confirm it. ## Sessions & resuming Every `mp scan`/`mp switch` starts a **fresh** capture session, but never deletes the old one. `loot/session-/` directories accumulate forever (clean them up by hand if disk space matters); nothing in this lab ever removes a past session automatically. **Dashboard.** The **Sessions** tab lists every capture session (target app, flow count, age, with the live one marked) and every Claude Code engagement (mode, finding count, running or not, a **resume** link when it has one). Click a session to browse its requests read-only, the same detail view as Traffic minus the live-only actions. There's no "Analyse with Claude", since that targets the live session, but "Repeat this request" still works, fetching from the archived one. **CLI.** `mp sessions` lists the 15 most recent capture sessions with their tagged target, flow count, and disk size, plus a count of Claude Code engagements. **Resuming an engagement.** Every `mp engage` run (interactive or `--auto`) now pins a `claude --session-id` and saves it to `.session_id` in the engagement folder. Pick it back up any time: ```bash mp engage --resume # e.g. com.example.app_20260101-120000 ``` opens a new Terminal window running `claude --resume ` in that folder, with the full prior context restored. The dashboard's Sessions tab does the same via its **resume** link. Engagements from before this feature existed have no saved session id and show "not resumable". **Every tab resets cleanly on a target change.** Switching target, whether via `mp scan`/`mp switch`, the dashboard's New Scan, or the MCP `target_scan`/ `target_switch` tools, all go through the same `config` file the dashboard polls. That clears the Traffic tab's tree filter and selected request, reloads the Login tab for the new package, and refreshes the findings view. Previously a stale tree filter or an already-open Login tab could keep showing the *old* target's view even though capture had correctly moved to the new one, which looked exactly like "still stuck on the old app". It's the same class of bug as the automatic-scope-clearing gotcha (see [Gotchas](#gotchas)), just on the dashboard's client side instead of the capture backend. The Repeater's loaded request/history is deliberately **not** reset, since it's for replaying any captured request regardless of what's currently live. ## Logging in MuMu Player Pro is already a normal window on your Mac. Click into it and type, same as any app. That's "manual login" and it already works with no extra tooling. What's actually useful to add is a place to keep track of what to type between sessions, and a way to push a stored value into whatever field is focused without risking a typo on a long password or token. ```bash mp login [pkg] bring MuMu to the front, show stored creds (masked) mp login [pkg] --reveal same, unmasked mp creds set