Skip to main content

<<<<<<< HEAD

VoidRemote

A Python interface to Android Debug Bridge (ADB).

VoidRemote wraps adb so you write Python instead of shell commands. It discovers devices, manages wireless pairing and connections, and gives you a typed Device object for input, file transfer, shell access, package management, screen capture, and monitoring.

from voidremote import VoidRemote

client = VoidRemote()
client.start()

device = client.devices().first()
device.tap(500, 800)
device.text("hello world")
device.screenshot("screen.png")

No adb shell input tap ... strings. No manually parsing adb devices -l output. No subprocess plumbing in your code.


Who this is for

  • Python developers automating Android devices
  • Test engineers writing device-driven test suites
  • CI/CD pipelines that need to install, launch, and verify apps
  • Scripts and tools that need device telemetry (battery, storage, CPU)
  • Applications built on top of ADB — VoidRemote's own CLI and desktop GUI are themselves built on this SDK (see Also included)

Installation

pip install voidremote

Requires Python 3.12+ and the adb binary on your PATH (install via Android Platform Tools). VoidRemote shells out to your existing adb — it does not bundle or replace it.

The base install is SDK-only, with no CLI or GUI dependencies pulled in:

pip install voidremote          # SDK only
pip install voidremote[cli]     # + command-line tool
pip install voidremote[gui]     # + desktop app
pip install voidremote[full]    # SDK + CLI + GUI

Quick start

from voidremote import VoidRemote

client = VoidRemote()
client.start()                       # verify adb, start the adb server

devices = client.devices()           # discover devices already visible to adb
device = devices.first()

device.tap(500, 800)
device.swipe(500, 1600, 500, 400)
device.text("hello world")
device.key_event(4)                  # KEYCODE_BACK

device.push("app-release.apk", "/sdcard/app.apk")
device.install("app-release.apk")

output = device.shell("pm list packages -3")
device.screenshot("screen.png")

Connecting a device that isn't already known to adb:

from voidremote import VoidRemote

client = VoidRemote()
client.start()

# Wireless debugging, paired by the 6-digit code shown on-device
device = client.pair_and_connect(host="192.168.1.42", port=37831, code="482913")

# Already paired, just needs a TCP connection
device = client.connect("192.168.1.42")

Every call above runs one real adb command under the hood — VoidRemote just gives it a name, a type, and a place to raise a real exception.


Philosophy

VoidRemote exists to remove one layer of indirection: the one between what you mean and the adb command line syntax for it.

Instead of shelling out to... You write
adb shell input tap 500 800 device.tap(500, 800)
adb shell input text "hello" device.text("hello")
adb push app.apk /sdcard/app.apk device.push("app.apk", "/sdcard/app.apk")
adb install -r app.apk device.install("app.apk")
adb shell pm list packages -3 device.list_packages()
adb pair 192.168.1.42:37831 482913 client.pair(host, port, code)
parsing adb devices -l output by hand client.devices()
parsing dumpsys battery output by hand device.battery_level

Nothing here is magic — every method is a thin, validated wrapper around one adb invocation. VoidRemote doesn't try to be smarter than adb; it tries to be a smaller, typed surface on top of it, with input validated against command injection before it ever reaches a shell.


=======

VOIDLAB — OWASP Top 10:2025 Web Penetration Testing Laboratory

A self-hosted, containerized web penetration testing training range.

Live vulnerable targets · a sandboxed in-browser terminal · flag-based scoring · a real OWASP Top 10:2025 curriculum.

Quick Start · Features · OWASP Coverage · Architecture · Extending · Credits


What is VOIDLAB?

VOIDLAB is a modular, Dockerized penetration-testing lab you run yourself: a Django 5 / DRF backend, a React 18 + TypeScript frontend, and a set of genuinely vulnerable, isolated containers to attack — wired together with an authenticated flag-submission workflow, a points/leaderboard system, hint economy, and an in-browser sandboxed terminal.

It's built around the current OWASP Top 10:2025 list (OWASP refreshed the Top 10 in late 2025 — this project tracks that update rather than the older 2021 edition), with 21 hand-written labs spanning all ten categories. Five labs run against live, disposable, non-privileged containers (SQL injection, reflected XSS, OS command injection, IDOR/BFLA, and SSRF); the remaining labs are realistic analysis challenges — a genuine artifact (vulnerable code snippet, log excerpt, encoded config) embedded in the briefing, in the same spirit as jeopardy-style CTF "misc/crypto/forensics" categories. See Extending the lab catalog for how to turn any of those into a live target, and how to grow the catalog past 30 labs using the exact same pattern.

Features

  • 🔐 JWT authentication & profiles — register, log in, track points and labs completed.
  • 🧪 21 structured labs across all ten OWASP Top 10:2025 categories, easy → insane.
  • 🎯 5 live, isolated vulnerable-app containers (SQLi, XSS, Command Injection, IDOR/BFLA, SSRF), each its own non-root, capability-dropped Docker service.
  • 🖥️ Sandboxed in-browser terminal over WebSocket, proxying an allowlisted recon toolkit into an isolated, non-privileged attacker-box container — enforced server-side, twice over, never a raw shell.
  • 🚩 Flag submission with idempotent scoring, per-hint point penalties, and a full attempt/audit trail.
  • 💡 Tiered hints that cost points, plus admin/instructor-only full solutions — gated server-side by role, not just hidden in the UI.
  • 🏆 Live leaderboard, cached briefly for snappy polling.
  • 🎨 Dark, cyberpunk-inspired UI — Tailwind v4 + a hand-built shadcn-style component set, Chakra Petch/Inter/JetBrains Mono type system, a 4-color semantic signal palette (violet/cyan/amber/crimson) instead of one flat neon accent.
  • 🐳 One-command Docker Compose stack: frontend, backend, Postgres, Redis, attacker-box, and every vulnerable-app target.

OWASP Top 10:2025 coverage

Code Category Labs
A01 Broken Access Control (now includes IDOR & SSRF) Profile Peeper (IDOR) · Admin By Accident (BFLA) · Metadata Reach (SSRF)
A02 Security Misconfiguration Debug Left On · Bucket Left Open
A03 Software Supply Chain Failures Typosquat Trap · Unsigned Update
A04 Cryptographic Failures Weak Hash Cracker · None Alg JWT
A05 Injection SQL Injection: Login Bypass · Reflected XSS: Cookie Heist · OS Command Injection
A06 Insecure Design Negative Quantity · No Rate Limit, No Problem
A07 Authentication Failures Session Fixation · Brute-Forceable Login
A08 Software or Data Integrity Failures Insecure Deserialization · Tampered Update, Take Two
A09 Security Logging and Alerting Failures Silent Intruder
A10 Mishandling of Exceptional Conditions (new for 2025) Fail Open · Race Condition Redeem

Run python manage.py seed_labs (done automatically on container start) to load the full catalog with hints and solutions.

Quick start

Prerequisites: Docker + Docker Compose. Nothing else needs to be installed locally.

git clone https://github.com/V0IDNETWORK/voidlab.git
cd voidlab
cp .env.example .env    
docker-compose up --build

Then open:

Service URL
Frontend http://localhost:5173
Backend API http://localhost:8000/api/v1/
API docs (Swagger) http://localhost:8000/api/v1/docs/
Django admin http://localhost:8000/admin/
sqli-lab target http://localhost:8081
xss-lab target http://localhost:8082
cmdi-lab target http://localhost:8083
idor-lab target http://localhost:8084
ssrf-lab target http://localhost:8085

On first boot the backend container automatically runs migrations, seeds the OWASP category + lab catalog, and (if DJANGO_SUPERUSER_USERNAME/DJANGO_SUPERUSER_PASSWORD are set in .env) creates an admin account you can use to view hint/solution content and the Django admin.

Note on migrations: this repo generates Django migrations at container startup (python manage.py makemigrations runs before migrate in backend/entrypoint.sh) rather than shipping pre-committed migration files, since they weren't generated against a live Django install when this project was authored. For a real production deployment, run makemigrations once yourself, commit the resulting files, and remove that line from the entrypoint so schema changes are explicit and reviewable like any other code change.

0f6ab1c0b207cd02bb93b87449cd9313e7e6e3f0

Architecture

<<<<<<< HEAD
Your application
       │
       ▼
voidremote (SDK)  ──  Device, VoidRemote, PairingSession
       │
       ▼
adb executable    ──  the real Android Debug Bridge binary
       │
       ▼
Android device    ──  over USB or Wireless Debugging

Internally, the SDK is layered:

voidremote.api            stable public surface (this is what you import)
    │
voidremote.controllers    composition root — wires services together
    │
voidremote.services       device discovery, input, monitoring
    │
voidremote.adb            subprocess execution, output parsing
    │
adb binary

Only voidremote.api (re-exported at the top level, from voidremote import ...) is a stable, versioned surface. Everything below it — adb, services, controllers, models — is an internal implementation detail that can change between minor versions without notice.


API design

VoidRemote — the client

from voidremote import VoidRemote

client = VoidRemote()
client.start()

client.devices()                          # -> DeviceList
client.device(serial)                     # -> Device
client.connect(host, port=5555)           # -> Device
client.pair(host, port, code)             # -> PairingSession
client.pair_and_connect(host, port, code) # -> Device
client.auto_reconnect()                   # -> DeviceList

Also usable as a context manager, which starts on entry and stops background monitoring on exit:

with VoidRemote() as client:
    for device in client.devices():
        print(device.name, device.battery_level)

Device — everything scoped to one device

device.tap(x, y)
device.swipe(x1, y1, x2, y2)
device.text("hello")
device.key_event(keycode)

device.shell("getprop ro.build.version.release")

device.push(local, remote)
device.pull(remote, local)
device.list_dir("/sdcard")

device.install(apk_path)
device.uninstall(package)
device.list_packages()
device.is_installed(package)

device.screenshot(output_path)
device.screenrecord()

device.reboot()
device.monitor(interval=2.0, callback=on_snapshot)

Input methods return self, so they chain:

device.tap(500, 800).text("hello").key_event(66)  # tap, type, press Enter

DeviceList — what client.devices() returns

devices = client.devices()
devices.first()          # -> Device, raises NoDevicesError if empty
devices.get(serial)       # -> Device, raises DeviceNotFoundError
devices.online()          # -> DeviceList filtered to online devices
len(devices)
for device in devices: ...

PairingSession — wireless debugging setup

session = client.pair(host="192.168.1.42", port=37831, code="482913")
session.pair()             # perform the handshake
device = session.connect() # then connect on the regular ADB port

Exceptions

Everything VoidRemote raises is a VoidRemoteError:

from voidremote import VoidRemoteError, DeviceNotFoundError, NoDevicesError, PairingError

try:
    device = client.devices().first()
except NoDevicesError:
    print("No devices connected.")
except VoidRemoteError as exc:
    print(f"Something went wrong: {exc}")

AdbNotAvailableError, AdbTimeoutError, AdbCommandError, DeviceNotFoundError, NoDevicesError, PairingError, ConnectionError, and InvalidArgumentError all subclass it.

Async

AsyncVoidRemote mirrors the sync API as coroutines. adb is a subprocess-based CLI, not a socket protocol VoidRemote speaks directly, so this runs the same synchronous code via asyncio.to_thread rather than native async I/O — which is enough to keep your event loop unblocked, and enough to run many devices concurrently with asyncio.gather:

import asyncio
from voidremote import AsyncVoidRemote

async def main():
    async with AsyncVoidRemote() as client:
        devices = await client.devices()
        await asyncio.gather(*(d.tap(500, 800) for d in devices))

asyncio.run(main())

Examples

Discover and print info for every connected device:

from voidremote import VoidRemote

with VoidRemote() as client:
    for device in client.devices():
        print(f"{device.name}  {device.android_version}  {device.battery_level}%")

Install an APK on every connected device:

from voidremote import VoidRemote

with VoidRemote() as client:
    for device in client.devices().online():
        device.install("app-release.apk")

Take a screenshot from a specific device:

from voidremote import VoidRemote

client = VoidRemote()
client.start()
client.device("192.168.1.42:5555").screenshot("shot.png")

Run a shell command and use the output:

device = client.devices().first()
version = device.shell("getprop ro.build.version.release")
print(f"Android {version}")

Poll battery and memory in the background:

def on_snapshot(snapshot):
    print(f"CPU {snapshot.cpu_usage:.0f}%  RAM {snapshot.ram_usage_percent:.0f}%  "
          f"Battery {snapshot.battery_level}%")

device = client.devices().first()
device.monitor(interval=2.0, callback=on_snapshot)

Handle errors explicitly:

from voidremote import VoidRemote, NoDevicesError, AdbNotAvailableError

try:
    client = VoidRemote()
    client.start()
    device = client.devices().first()
except AdbNotAvailableError:
    print("adb isn't installed or isn't on PATH.")
except NoDevicesError:
    print("adb is running, but no device is connected.")

Security

Every device path, package name, host, port, and pairing code is validated before use, and shell arguments are checked against a small blocklist of injection characters (; & | \ $ < > `) and rejected outright rather than escaped-and-hoped. See voidremote.utils.security — it's a small module, worth reading if you're deciding whether to trust this with untrusted input.


Also included

The voidremote repository also ships two applications built entirely on top of this SDK — neither adds anything to the SDK's public API, both are optional installs.

CLI (pip install voidremote[cli]) — a voidremote command covering device discovery, pairing, input, file transfer, package management, shell access, and monitoring, with --json output for scripting. See docs/CLI.md.

Desktop GUI (pip install voidremote[gui]) — a PySide6 application (voidremote-gui) with a device dashboard, embedded shell, file manager, and live monitoring graphs. See docs/GUI.md.

Further reading: docs/API.md for the full API reference, docs/ARCHITECTURE.md for how the layers fit together, CONTRIBUTING.md to work on VoidRemote itself.


Requirements

  • Python 3.12 or later
  • adb (Android SDK Platform Tools) on PATH
  • An Android device with USB debugging or Wireless Debugging enabled

Tested on Linux, macOS, and Windows.


License

MIT — see LICENSE.

Author

V0IDNETWORK — an ongoing, open research effort to document, rigorously and accurately, how the modern Internet's circumvention and surveillance technologies actually work at the protocol level, in support of a more open and resilient Internet.

GitHub · Website · LinkedIn · Instagram · YouTube · TryHackMe · Medium · Telegram · ilianothingg@gmail.com

voidlab/ ├── docker-compose.yml # orchestrates every service below ├── .env.example ├── backend/ # Django 5 + DRF + Channels │ ├── config/ # settings, URLs, ASGI (HTTP + WebSocket) │ └── apps/ │ ├── core/ # health check, request-ID middleware, JWT-for-WS auth │ ├── accounts/ # custom user model, JWT auth, profile │ ├── labs/ # categories, labs, hints, solutions, submissions, scoring │ ├── leaderboard/ # cached global ranking │ └── terminal/ # WebSocket consumer → attacker-box proxy ├── frontend/ # React 18 + TS + Vite + Tailwind v4 + Zustand │ └── src/ │ ├── components/ui/ # hand-built shadcn-style primitives │ ├── components/layout/ # navbar, auth guard │ ├── store/ # auth + labs zustand stores │ └── pages/ # landing, auth, dashboard, labs, terminal, leaderboard ├── attacker-box/ # isolated, non-root command-runner behind the terminal ├── vulnerable-apps/ │ ├── sqli-lab/ # Flask + SQLite, string-concatenated login query │ ├── xss-lab/ # Flask, reflected/unescaped search + local cookie collector │ ├── cmdi-lab/ # Flask, os.popen() ping tool │ ├── idor-lab/ # Flask, IDOR + BFLA in one app │ └── ssrf-lab/ # Flask, server-side fetch + loopback metadata service └── docs/


### Network segmentation

Two Docker networks: `voidlab_core` (frontend, backend, Postgres, Redis) and `voidlab_targets`
(attacker-box + every vulnerable-app container). The backend joins both, but **only** to reach
`attacker-box` for the terminal proxy — it never runs `subprocess`/`os.system` against a target
itself and never mounts the Docker socket. Every lab/attacker-box container runs as a non-root
user with `cap_drop: [ALL]` (plus a narrow, explicit `cap_add` only where a tool genuinely needs
it, e.g. `NET_RAW` for `ping` in `cmdi-lab`).

### The sandboxed terminal, honestly

The in-browser terminal is a real, working feature — not a simulation — but it is scoped
deliberately: it proxies a **fixed allowlist** of read-only recon tools (`curl`, `nmap`, `dig`,
`whoami`, `id`, `ls`, `cat`, `pwd`, `echo`, `nslookup`, `sqlmap`, `nikto`) into the isolated
`attacker-box` container, enforced independently in **two** places (the Django consumer and the
exec server itself), with a hard per-command timeout and output cap. It is appropriate for a
**self-hosted** training lab you run yourself. It is **not** hardened for exposing to the public
internet as a multi-tenant SaaS — that would need a fresh, ephemeral, per-session sandbox (e.g.
gVisor or Firecracker microVMs) instead of one shared `attacker-box`, which is a meaningful next
step, not a checkbox.

## Extending the lab catalog

Every lab lives as one entry in `backend/apps/labs/management/commands/seed_labs.py`'s
`LAB_CATALOG` list. To add a new lab:

1. Add a new dict to `LAB_CATALOG` — `cat` (category code), `title`, `difficulty`, `points`,
   `summary`, `briefing`, `objective`, `flag`, `hints`, `solution`, and either a `target_app` +
   `target_path` (if it targets a live container) or leave those blank for a self-contained
   analysis challenge.
2. Re-run `python manage.py seed_labs` (idempotent — safe to re-run any time).
3. If it needs a **new live container**: copy the shape of any folder under `vulnerable-apps/`,
   add it as a service to `docker-compose.yml` on the `voidlab_targets` network, add its URL to
   `VULNERABLE_APP_URLS` in `backend/config/settings.py`, and point `target_app` at its key.

This repository ships 21 labs; the pattern above is identical for lab #22 through #30+.

## Tech stack

**Frontend:** React 18, TypeScript, Vite, Tailwind CSS v4, hand-built shadcn-style components, React Router 6, Zustand, Axios, lucide-react, react-markdown.
**Backend:** Django 5.2 LTS, Django REST Framework, SimpleJWT, Django Channels (WebSocket), drf-spectacular (OpenAPI docs), PostgreSQL 16, Redis 7.
**Infra:** Docker, Docker Compose, nginx (frontend static serving), Daphne (ASGI server).


## Honest scope notes

This is a real, working platform, not a mockup — but it's worth being precise about what
"working" means here:

- 5 of the 21 labs run against **live** containers you can actually attack end-to-end; the rest
  are analysis-style challenges (see above) rather than live-exploitable services.
- Migrations are generated at container start rather than committed (see the note under [Quick
  start](#quick-start)) — fine for spinning the stack up, not a substitute for committed
  migrations in a real deployment.
- The terminal's sandboxing is real but scoped to a **self-hosted, single-tenant** use case; see
  [Architecture](#architecture) for what a public multi-tenant version would additionally need.
## License

This project is licensed under the **MIT License** - see the [LICENSE](LICENSE) file for details.

## Credits

Built by **V0IDNETWORK** — an open research effort on Internet circumvention & surveillance technologies.

- **GitHub**: [V0IDNETWORK](https://github.com/V0IDNETWORK)
- **Website**: [voidnetwork.ir](http://voidnetwork.ir/)
- **LinkedIn**: [ilianothing](https://www.linkedin.com/in/ilianothing)
- **Instagram**: [@ilianothing](https://www.instagram.com/ilianothing)
- **TryHackMe**: [ilianothingg](https://tryhackme.com/p/ilianothingg)
- Contact: +989928102005 · [ilianothingg@gmail.com](mailto:ilianothingg@gmail.com)

<p align="center"><b>∆ Join VOID ∆</b></p>

>>>>>>> 0f6ab1c0b207cd02bb93b87449cd9313e7e6e3f0

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

voidremote-2.0.0.tar.gz (90.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

voidremote-2.0.0-py3-none-any.whl (100.4 kB view details)

Uploaded Python 3

File details

Details for the file voidremote-2.0.0.tar.gz.

File metadata

  • Download URL: voidremote-2.0.0.tar.gz
  • Upload date:
  • Size: 90.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for voidremote-2.0.0.tar.gz
Algorithm Hash digest
SHA256 8e9e429a212a95f908022f9284a03a0294fabde6f09fc3cdceb80dd3c25dd2c5
MD5 a09f0a142508e3635fd5b49924466c25
BLAKE2b-256 d013b9db776034b21d495e9c6935a3417d0a2013087fa56f958878e82c596fb5

See more details on using hashes here.

File details

Details for the file voidremote-2.0.0-py3-none-any.whl.

File metadata

  • Download URL: voidremote-2.0.0-py3-none-any.whl
  • Upload date:
  • Size: 100.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for voidremote-2.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 bd5ce1da912fc5475105f5f4771075d9589fe53baeacb634442a59414f6652d9
MD5 ab0e26ec5deee66e11aca651f6c84ab0
BLAKE2b-256 635c44da77d443d5f3ec7707a11e68c5789e2cde7e916c20a40e3aa42233e78f

See more details on using hashes here.

Release history Release notifications | RSS feed

2.0.1

2 files

This release

2.0.0 This release

2 files

1.0.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page