<<<<<<< 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.
=======
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-boxcontainer — 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 makemigrationsruns beforemigrateinbackend/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, runmakemigrationsonce 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) onPATH- 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8e9e429a212a95f908022f9284a03a0294fabde6f09fc3cdceb80dd3c25dd2c5
|
|
| MD5 |
a09f0a142508e3635fd5b49924466c25
|
|
| BLAKE2b-256 |
d013b9db776034b21d495e9c6935a3417d0a2013087fa56f958878e82c596fb5
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bd5ce1da912fc5475105f5f4771075d9589fe53baeacb634442a59414f6652d9
|
|
| MD5 |
ab0e26ec5deee66e11aca651f6c84ab0
|
|
| BLAKE2b-256 |
635c44da77d443d5f3ec7707a11e68c5789e2cde7e916c20a40e3aa42233e78f
|