Skip to content

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

RoleWhat they can do
HostCreate room, share session, kick peers, end session
MemberJoin room, view and edit shared session in real time
SpectatorJoin 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.

A prebuilt image is published on GitHub Container Registry for every release:

bash
# Latest stable
docker pull ghcr.io/maesecurity/pentestpath-relay:latest

# Pinned version (recommended in production)
docker pull ghcr.io/maesecurity/pentestpath-relay:2.0.0

Minimal run command with a persistent named volume for the message history database:

bash
docker run -d \
  --name pentestpath-relay \
  --restart unless-stopped \
  -p 4444:4444 \
  -v pentestpath-relay-data:/data \
  ghcr.io/maesecurity/pentestpath-relay:latest

The 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)

bash
docker run -d -p 4444:4444 \
  -v pentestpath-relay-data:/data \
  ghcr.io/maesecurity/pentestpath-relay:latest

The 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)

bash
mkdir -p /srv/pentestpath/relay-data
docker run -d -p 4444:4444 \
  -v /srv/pentestpath/relay-data:/data \
  ghcr.io/maesecurity/pentestpath-relay:latest

The 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:

bash
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:latest

WARNING

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):

bash
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.bak

For a full cold backup, stop the container first:

bash
docker stop pentestpath-relay
cp /srv/pentestpath/relay-data/relay.db ./relay-$(date +%F).db
docker start pentestpath-relay

Restoring 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_64
  • pentestpath-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:

bash
DATABASE_PATH=/var/lib/pentestpath/relay.db ./pentestpath-relay-linux-x86_64

Make sure the parent directory exists and is writable by the user running the relay. A typical systemd unit looks like:

ini
[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.target

Environment 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

VariableDefaultPurpose
PORT4444TCP port the relay listens on.
DATABASE_PATHrelay.db (binary) / /data/relay.db (Docker)SQLite database file path. Parent directory must exist and be writable.
RUST_LOGinfoLog 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

VariableDefaultPurpose
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.

VariableDefaultPurpose
MAX_CONNECTIONS_PER_ROOM64Hard cap on simultaneous WebSocket connections per room.
HISTORY_LIMIT_PER_ROOM10000Maximum number of messages retained per room in the database.
MAX_MESSAGES_PER_SECOND250Per-connection rate limit (messages/sec).
MAX_BYTES_PER_SECOND2097152Per-connection rate limit (bytes/sec, default 2 MiB).
MAX_LAGGED_MESSAGES256Maximum messages a slow consumer can fall behind before being disconnected.
MAX_IP_MESSAGES_PER_SECOND750Per-source-IP rate limit (messages/sec, summed across all that IP's connections).
MAX_IP_BYTES_PER_SECOND6291456Per-source-IP rate limit (bytes/sec, default 6 MiB).
IP_RATE_ENTRY_TTL_SECONDS300How 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.

  1. Set up a reverse proxy (Caddy, Nginx, Traefik, HAProxy, or equivalent) on the same host or on a fronting load balancer.
  2. Obtain a TLS certificate for your relay hostname (Caddy and Traefik can do this automatically via Let's Encrypt).
  3. Connect PentestPath to wss://relay.yourdomain.com from 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

nginx
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)

yaml
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.

PentestPath documentation