A self-hosted, browser-based Linux terminal that gives every visitor a real, sandboxed Bash shell running on the server. It pairs a Python backend (Flask + Flask-SocketIO) with an xterm.js frontend and, when the host kernel allows, drops each session into an isolated Bubblewrap sandbox.
The repository also ships a Dockerfile that wraps Gitpod's openvscode-server image, rebrands it under the izzidevelopers name, pre-installs a curated set of Rust-focused VS Code extensions, and exposes the whole stack on port 3000.
⚠️ Use with care. A web-facing terminal is, by definition, remote code execution. The Bubblewrap sandboxing is intended to limit the blast radius, but you are responsible for authentication, rate-limiting, and where you deploy this.
- True PTY-backed shell – every connected browser tab gets its own real
bash --loginsession viapty.fork(), with correct TTY sizing handled throughTIOCSWINSZioctls. - xterm.js UI – full xterm.js terminal with
FitAddonso the canvas always matches the browser window size. - Bidirectional I/O over WebSockets – keystrokes go to the server via Socket.IO events (
pty_input,resize); output is streamed back viapty_output. - Per-session isolation (when supported) – sessions are launched inside Bubblewrap with read-only bind mounts for
/usr,/bin,/lib,/lib64,/sbin,/etc, in-memory tmpfs for/tmpand/root, and unshared PID/IPC/UTS namespaces. Outbound network access is preserved socurl,wget,gitand friends still work. - Graceful fallback – if
bwrapis missing or kernel namespaces are restricted, the server transparently falls back to a regularbashand logs a warning on startup. - Automatic cleanup – on disconnect or PTY EOF, the child process is
SIGKILL'd and reaped, and the file descriptor is closed. - Container-ready – ships a Dockerfile that builds a fully rebranded, extension-loaded server image on top of
gitpod/openvscode-server.
.
├── app.py # Flask + Flask-SocketIO backend, PTY management, Bubblewrap integration
├── requirements.txt # Python dependencies
├── Dockerfile # Sandboxed, rebranded openvscode-server image
└── templates/
└── index.html # xterm.js + Socket.IO client UI
python3 -m pip install -r requirements.txtDependencies:
Flask >= 3.0.0Flask-SocketIO >= 5.3.6gevent >= 23.9.1gevent-websocket >= 0.10.1gunicorn >= 21.2.0
For session sandboxing on Linux:
# Debian / Ubuntu
sudo apt-get install -y bubblewrapIf bwrap is unavailable, the server will still start — sessions will simply run without an extra sandbox layer.
python3 app.pyBy default Flask-SocketIO's dev server listens on http://localhost:5000. Open it in your browser and you should see the green Connected to terminal server. banner followed by a working shell prompt.
Because the project uses gevent.monkey.patch_all(), it's safe to run behind gunicorn:
gunicorn --worker-class gevent --workers 1 \
--bind 0.0.0.0:5000 app:appThe included Dockerfile builds a more ambitious image: it takes Gitpod's openvscode-server, renames internal binaries and directories to izzidevelopers, swaps branding strings inside the compiled assets, sets a custom PS1 prompt, installs the stable Rust toolchain, and pre-installs three VS Code extensions:
rust-lang.rust-analyzertamasfe.even-better-tomlContinue.continue
Build and run:
docker build -t izzidevelopers/terminal .
docker run --rm -p 3000:3000 izzidevelopers/terminalThen visit http://localhost:3000.
You can also enforce a connection token:
docker run --rm -p 3000:3000 \
-e CONNECTION_TOKEN=please-change-me \
izzidevelopers/terminalIf CONNECTION_TOKEN is unset, the container starts the server with --without-connection-token.
┌──────────────────┐ WebSocket ┌──────────────────────┐
│ Browser │ ◄─────────────────────► │ Flask + Socket.IO │
│ (xterm.js) │ pty_input / pty_output │ (app.py) │
└──────────────────┘ └─────────┬────────────┘
│ pty.fork()
▼
┌──────────────────────┐
│ bash --login │
│ (inside bwrap if │
│ kernel allows) │
└──────────────────────┘
- On
connect, the backend forks a child process attached to a PTY, optionally executes it inside Bubblewrap, then begins a backgroundgeventtask that reads from the PTY and emitspty_outputto the originating client only. - Every keystroke from xterm.js is forwarded via
pty_inputandwrite()'d into the PTY. - Window resize events update the PTY winsize so
vim,htop, etc. redraw correctly. - On
disconnector EOF, the child is killed, the FD is closed, and the session map entry is removed.
| Variable | Default | Description |
|---|---|---|
SECRET_KEY |
fallback-secret-key |
Flask SECRET_KEY. Set this in production. |
PORT |
3000 (Docker) |
Listening port when running through the provided Dockerfile. |
CONNECTION_TOKEN |
(unset) | If set, the container's openvscode-server requires it. |
The server has cors_allowed_origins="*". Tighten this in app.py before deploying publicly.
- Run it behind authentication. There is currently no login layer; anyone reaching the page gets a shell.
- The Bubblewrap sandbox is best-effort. It hides the host filesystem and isolates PID/IPC/UTS, but it deliberately keeps network access for normal tooling. Do not rely on it as a hard isolation boundary against a determined attacker.
- Don't run as root in production. Drop capabilities, run the Python process as an unprivileged user, and consider adding
--unshare-netif you want to disable outbound network per-session (this will breakapt,pip,git clone, etc.). - Set a strong
SECRET_KEYto protect Flask-SocketIO sessions.
gevent.monkey.patch_all()must stay at the very top ofapp.py— Socket.IO needs the monkey-patched standard library before anything else imports.- Each browser tab = one PTY = one background task. Keep an eye on the
terminalsdict growth if you ever implement a "share session" feature. - To debug the sandbox, run the binary by hand:
bwrap --ro-bind /usr /usr --ro-bind /bin /bin --ro-bind /lib /lib \ --proc /proc --dev /dev --tmpfs /tmp --tmpfs /root \ --unshare-pid --unshare-ipc --unshare-uts \ --setenv TERM xterm-256color --setenv HOME /root \ bash --login
No license file is currently included in this repository. If you intend to redistribute or build on top of it, please add an explicit LICENSE file or contact the maintainers (izzidevelopers) for terms.