Two pieces:
site/— the static GitHub Pages front end (index.html,app.js,styles.css).backend/—app.py(the NudeNet detector) plusconnect_launcher.py, the desktop app people download and run. It's a normal windowed GUI (Tkinter — ships with Python, nothing extra to install), styled to match the site. It boots the detector, optionally opens a tunnel withpyngrok, and shows a pairing code — this is also the exact thing packaged intoCensorSandbox-Setup.exethat the site's home page links to.
connect_launcher.py has two screens:
- Start screen — choose Tunnel (pair with another device) or Local only (same machine), then click Start sandbox. A progress bar shows it booting the detector and, if you picked tunnel mode, opening the tunnel.
- Ready screen — a "Sandbox ready" card, your pairing code in a monospace teal box, Copy code / Open pairing page buttons, and a note that closing the window disconnects the sandbox. This is deliberately the same layout as the "What you'll see when you open it" mockup on the site's home page, so what you download matches what you were shown before downloading it. The raw tunnel URL is hidden behind a "Show raw address" toggle — the app leads with the short code, not a URL.
The pairing code is the tunnel address (e.g. https://8f2a91cd.ngrok-free.app)
base64url-encoded. The site's 404.html catches deep links like
/censor/u/<code> (GitHub Pages has no server-side router), bounces to
index.html, and app.js decodes the code back into the real tunnel URL
entirely in the visitor's own browser, then uses it for every fetch() call.
That means:
- It's a cosmetic/obfuscation layer, not a hidden relay — there's no server under anyone's control proxying uploads. Nothing is registered anywhere when the code is generated.
- The code is only as private as who you give it to. Treat it like a screen-share link: anyone with it can reach your sandbox for as long as the launcher is running.
- Closing the app kills the tunnel and the code stops resolving to anything, immediately.
Pairing through a free ngrok tunnel adds real latency: every request makes a round trip through ngrok's edge network before it reaches your machine, on top of free-tier bandwidth throttling. If the browser and the sandbox are on the same machine, skip the tunnel entirely:
- In the app, choose Local only.
site/config.jsonneeds"local": trueso the site's pairing box asks for an address (127.0.0.1:5000) instead of a masked code:{ "local": true, "local_default_host": "127.0.0.1:5000" }- For a genuinely faster tunneled setup instead, set
NGROK_AUTHTOKEN(free ngrok account) before launching — authenticated tunnels get a less congested edge than anonymous ones.
cd backend
pip install -r requirements.txt
python connect_launcher.pyRequires Python 3.9+, ffmpeg on PATH, and (for fast video) an NVIDIA GPU +
onnxruntime-gpu. See site's "For developers" page for the same steps.
app.py's detection logic is just the open-source nudenet Python package
— there's no proprietary model here:
# pip install nudenet opencv-python
from nudenet import NudeDetector
import cv2
detector = NudeDetector()
image = cv2.imread("photo.jpg")
results = detector.detect(image)
for r in results:
# r == {"class": "FEMALE_BREAST_EXPOSED", "score": 0.87, "box": [x, y, w, h]}
print(r["class"], r["score"], r["box"])- Classes:
FEMALE_GENITALIA_EXPOSED,MALE_GENITALIA_EXPOSED,ANUS_EXPOSED,FEMALE_BREAST_EXPOSED,MALE_BREAST_EXPOSED,BUTTOCKS_EXPOSED,FEET_EXPOSED,BELLY_EXPOSED,ARMPITS_EXPOSED,FACE_FEMALE,FACE_MALE. Some versions also return matching_COVEREDclasses for clothed regions. - Thresholding: drop anything under ~
0.25score to cut false positives. - Batching:
detector.detect_batch(list_of_frames)is far faster than callingdetect()per-frame for video. - GPU: install
onnxruntime-gpuinstead of the CPU package; it picks up CUDA automatically if a compatible GPU/driver is present. app.pyin this repo is a complete Flask wrapper around the same calls, including video sampling/interpolation — copy from it freely.
This is what produces the file the site's Download for Windows button
links to (site/downloads/CensorSandbox-Setup.exe).
pip install pillow
python make_icon.pyWrites assets/icon.ico (block-with-an-eye mark, matches the site) and
assets/icon.png.
pip install -r requirements.txt
pip install pyinstaller
pyinstaller connect_launcher.py --name CensorSandbox-Setup --onefile --windowed --icon assets/icon.ico --add-data "app.py;."(On macOS/Linux use --add-data "app.py:." — colon instead of semicolon.)
--windowedstops a console window from flashing behind the Tkinter GUI.--onefileproduces a single exe underdist/. This is a self-contained launcher, not a wizard-style installer — good enough for "download and run." If you want an actual install wizard (Start Menu entry, uninstaller, Program Files placement), wrap the onefile build with Inno Setup: point its[Files]section atdist/CensorSandbox-Setup.exeand it'll produce a proper installer with the same name.- GPU builds:
onnxruntime-gpuand CUDA are large; PyInstaller will bundle whatever's in the environment you build in. Build on a machine with the CPU-onlyonnxruntimeif you want a smaller download and are fine with CPU-speed detection, or ship the GPU build as a separate, larger download.
Run dist/CensorSandbox-Setup.exe on a clean machine (or VM) before
publishing — PyInstaller onefile builds occasionally miss a dynamic import.
If NudeNet's ONNX model file isn't found, add it explicitly:
--add-data "path\to\nudenet\model.onnx;nudenet"(check where pip show nudenet installed it to get the right source path.)
Upload the built exe as a GitHub Release asset, then update
DOWNLOAD_URL in site/app.js and the href on the download button in
site/index.html to point at the release asset URL (or keep it at
site/downloads/CensorSandbox-Setup.exe if you're committing the binary
into the Pages repo directly — fine for a small single exe, but a GitHub
Release is cleaner for anything that'll be updated often).
Windows will show a SmartScreen "unknown publisher" warning for an unsigned exe — this is expected and unrelated to how it was built. The site already tells users to click "More info → Run anyway." Real fix is a code-signing certificate, which is a paid, separate step (EV certs from a CA like DigiCert/Sectigo) — not something PyInstaller or Inno Setup can add for you.