Team Mode
What Team Mode is
Team Mode is PentestPath's collaborative workflow for shared pentest sessions. It is built around a self-hosted relay model — there is no managed PentestPath relay service. If you want collaboration, you run the relay.
Pro feature
Team Mode requires a Pro license on all participating machines.
How it works
The relay is a lightweight WebSocket server that forwards encrypted session state between PentestPath clients. The relay never sees plaintext session content — it only forwards opaque blobs.
Participants connect to the relay endpoint you provide. One user hosts the room, others join with an invitation token.
Roles
| Role | What they can do |
|---|---|
| Host | Create room, share session, kick peers, end session |
| Member | Join room, view and edit shared session in real time |
| Spectator | Join room, view only |
Relay deployment
Minimal local setup (testing only)
For local testing on a trusted network, run the relay binary directly and connect clients with a ws:// address. This is not suitable for production use.
Production setup
For real use across machines or networks, you have two deployment options. Both expose the relay on port 4444 by default and require a TLS-terminating reverse proxy in front.
Option A — Docker image (recommended)
A prebuilt image is published on GitHub Container Registry for every release:
# Latest stable
docker pull ghcr.io/maesecurity/pentestpath-relay:latest
# Pinned version (recommended in production)
docker pull ghcr.io/maesecurity/pentestpath-relay:2.0.0Minimal run command with a persistent named volume for the message history database:
docker run -d \
--name pentestpath-relay \
--restart unless-stopped \
-p 4444:4444 \
-v pentestpath-relay-data:/data \
ghcr.io/maesecurity/pentestpath-relay:latestThe container exposes GET /health for use with Docker healthchecks, Kubernetes readiness probes, or external monitoring.
Storage and persistence
The relay stores room message history in a SQLite database. Without a volume, the database lives inside the container's writable layer and is destroyed when the container is recreated (image update, docker rm, host reboot if not restarted, etc.). Always mount a volume in production.
You have three storage patterns, depending on how much control you want.
1. Default — named volume on /data (simplest)
docker run -d -p 4444:4444 \
-v pentestpath-relay-data:/data \
ghcr.io/maesecurity/pentestpath-relay:latestThe database lives at /data/relay.db inside the container, persisted in the Docker-managed volume pentestpath-relay-data. You can list and inspect Docker volumes with docker volume ls and docker volume inspect pentestpath-relay-data.
2. Bind mount to a host directory (best for backups and inspection)
mkdir -p /srv/pentestpath/relay-data
docker run -d -p 4444:4444 \
-v /srv/pentestpath/relay-data:/data \
ghcr.io/maesecurity/pentestpath-relay:latestThe relay.db file appears directly on the host at /srv/pentestpath/relay-data/relay.db. You can back it up with cp, inspect it with sqlite3, or include it in your existing backup pipeline.
3. Custom database path (advanced)
If your filesystem conventions require the database somewhere other than /data, override DATABASE_PATH and mount the corresponding parent directory:
docker run -d -p 4444:4444 \
-e DATABASE_PATH=/var/lib/pentestpath/sessions.db \
-v pentestpath-relay-data:/var/lib/pentestpath \
ghcr.io/maesecurity/pentestpath-relay:latestWARNING
The parent directory of DATABASE_PATH must be on a mounted volume. If you set DATABASE_PATH without mounting the parent, the database is silently created in the container's ephemeral filesystem and lost on the next recreation.
Backups
The database is a regular SQLite file. While the relay is running, prefer the SQLite online backup API rather than a raw cp (which can capture an inconsistent snapshot if a write happens mid-copy):
docker exec pentestpath-relay \
sqlite3 /data/relay.db ".backup '/data/relay.db.bak'"
docker cp pentestpath-relay:/data/relay.db.bak ./relay-$(date +%F).db.bakFor a full cold backup, stop the container first:
docker stop pentestpath-relay
cp /srv/pentestpath/relay-data/relay.db ./relay-$(date +%F).db
docker start pentestpath-relayRestoring is the reverse — stop the container, replace the file, start again.
Option B — Standalone binary
If you prefer not to use Docker, prebuilt relay binaries are attached to every release:
pentestpath-relay-linux-x86_64pentestpath-relay-windows-x86_64.exe
Download from the PentestPath-Release page, mark it executable on Linux (chmod +x), and run it directly. The same environment variables apply.
By default the binary writes the database to relay.db in the current working directory. For a production install, set DATABASE_PATH explicitly so you control where the data lives:
DATABASE_PATH=/var/lib/pentestpath/relay.db ./pentestpath-relay-linux-x86_64Make sure the parent directory exists and is writable by the user running the relay. A typical systemd unit looks like:
[Unit]
Description=PentestPath Team Mode relay
After=network-online.target
[Service]
Type=simple
User=pentestpath
Group=pentestpath
WorkingDirectory=/var/lib/pentestpath
Environment=PORT=4444
Environment=DATABASE_PATH=/var/lib/pentestpath/relay.db
Environment=RUST_LOG=info
ExecStart=/usr/local/bin/pentestpath-relay
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.targetEnvironment variables reference
The relay is configured exclusively via environment variables. Defaults are tuned for a small shared session (one or two pentest teams) and almost never need to be changed.
Core
| Variable | Default | Purpose |
|---|---|---|
PORT | 4444 | TCP port the relay listens on. |
DATABASE_PATH | relay.db (binary) / /data/relay.db (Docker) | SQLite database file path. Parent directory must exist and be writable. |
RUST_LOG | info | Log level. Use p2p_relay_server=debug for verbose protocol logging. |
PUBLIC_WSS_URL | (unset) | Informational only. If set, the relay logs the public wss:// URL on startup as a reminder for operators. Has no effect on routing. |
Authentication
| Variable | Default | Purpose |
|---|---|---|
ROOM_PSK | (unset) | Pre-shared key required to join any room. If set, every connecting client must send a valid auth payload before joining a room, otherwise the connection is rejected. Strongly recommended for any relay reachable from the public Internet. |
When ROOM_PSK is enabled, the relay logs ROOM_PSK is enabled: websocket clients must authenticate with message type 0x07 at startup. The PentestPath client must be configured with the same PSK in its Team Mode connection settings.
TIP
ROOM_PSK is in addition to the per-room invitation token. The PSK gates who can talk to the relay at all, while the room token gates which room a client can join. Use both for defense in depth.
Limits and rate limiting
These control the resources a single room or a single IP can consume. The defaults are conservative enough for typical multi-pentester sessions and aggressive enough to prevent a misbehaving or malicious client from saturating the relay.
| Variable | Default | Purpose |
|---|---|---|
MAX_CONNECTIONS_PER_ROOM | 64 | Hard cap on simultaneous WebSocket connections per room. |
HISTORY_LIMIT_PER_ROOM | 10000 | Maximum number of messages retained per room in the database. |
MAX_MESSAGES_PER_SECOND | 250 | Per-connection rate limit (messages/sec). |
MAX_BYTES_PER_SECOND | 2097152 | Per-connection rate limit (bytes/sec, default 2 MiB). |
MAX_LAGGED_MESSAGES | 256 | Maximum messages a slow consumer can fall behind before being disconnected. |
MAX_IP_MESSAGES_PER_SECOND | 750 | Per-source-IP rate limit (messages/sec, summed across all that IP's connections). |
MAX_IP_BYTES_PER_SECOND | 6291456 | Per-source-IP rate limit (bytes/sec, default 6 MiB). |
IP_RATE_ENTRY_TTL_SECONDS | 300 | How long the relay remembers an IP's rate-limit window after it goes idle. |
WARNING
Raising these limits may improve throughput for very large rooms but also raises the resource exposure of the relay. Tune cautiously and monitor memory/CPU.
The relay also performs an internal cleanup pass that deletes messages older than 24 hours from the database; this is hardcoded and not configurable.
Reverse proxy and TLS
Whichever deployment option you pick, the relay itself only speaks plain WebSocket on TCP. You must put a TLS-terminating reverse proxy in front before exposing it on the public Internet.
- Set up a reverse proxy (Caddy, Nginx, Traefik, HAProxy, or equivalent) on the same host or on a fronting load balancer.
- Obtain a TLS certificate for your relay hostname (Caddy and Traefik can do this automatically via Let's Encrypt).
- Connect PentestPath to
wss://relay.yourdomain.comfrom the Team Mode panel of each participating client.
Example Caddy configuration
relay.yourdomain.com {
reverse_proxy localhost:4444
}Caddy handles HTTPS and certificate renewal automatically.
Example Nginx configuration
server {
listen 443 ssl;
server_name relay.yourdomain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://localhost:4444;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}The long proxy_read_timeout / proxy_send_timeout values prevent Nginx from cutting idle WebSocket connections during quiet periods.
Example Traefik labels (Docker Compose)
services:
relay:
image: ghcr.io/maesecurity/pentestpath-relay:2.0.0
restart: unless-stopped
volumes:
- pentestpath-relay-data:/data
environment:
ROOM_PSK: ${RELAY_ROOM_PSK}
labels:
- traefik.enable=true
- traefik.http.routers.pentestpath-relay.rule=Host(`relay.yourdomain.com`)
- traefik.http.routers.pentestpath-relay.entrypoints=websecure
- traefik.http.routers.pentestpath-relay.tls.certresolver=letsencrypt
- traefik.http.services.pentestpath-relay.loadbalancer.server.port=4444
volumes:
pentestpath-relay-data:Network requirements
- The relay port must be reachable from all participant machines.
- Firewalls must allow WebSocket upgrade traffic on the relay port.
- All clients must connect to the same endpoint.
Access control
The relay itself does not enforce authentication beyond the room token generated by PentestPath. Put the relay behind a VPN or firewall if you want to restrict who can attempt to connect.
Shared vs detached sessions
When a participant disconnects or the host ends the session, members can detach their local copy and continue working independently. The detached session is no longer synced.