AgileSecOps AgileSecOps Warden Docs

Warden Documentation

Secure Browser Terminals · AgileSecOps

Warden is a self-hosted, browser-based terminal for SSH servers and AWS ECS Fargate containers. It pairs passwordless passkey authentication with time-boxed access approvals, encrypted session recording, live session sharing, and resumable sessions — all behind a full admin UI, with no browser extensions required.

A single Go HTTP server proxies WebSocket connections to real ssh and aws ecs execute-command subprocesses over a PTY. The UI is static HTML with xterm.js from a CDN — no build step. State lives in PostgreSQL; secrets and recordings are AES-256-GCM encrypted at rest.

Features #

Passkey / WebAuthn authentication — passwordless, admin-invited via magic link
SSH & AWS ECS terminals in the browser
Stored AWS accounts — encrypted creds, picked by name
Time-boxed access approvals with full audit trail
Resumable sessions — survive logout / tab close
Live session sharing — view-only or give-control
Encrypted session recording (asciicast v2) with playback
Multi-tab, tile layout, broadcast-to-all input
Saved SSH / ECS connection profiles (encrypted)
Admin UI: users, environments, grants, AWS accounts, recordings
Roles: user, admin, admin_only (approver/auditor)
No build step — static UI, single Go binary, Docker image
New here?

Start with Quick Start (Docker) — Compose brings up Warden (with the AWS CLI + SSM plugin bundled) and PostgreSQL together. Then read How It Works to understand the session model.

Prerequisites #

Warden ships as Docker containers — everything it needs to run is inside the image and the Compose stack. On the host you only need Docker.

ToolPurpose
Docker Engine 24+Runs the Warden container.
Docker Compose v2Brings up Warden and its PostgreSQL together.

Everything else is provided for you: the PostgreSQL database runs as a Compose service, and ssh/sshpass plus the AWS CLI v2 + session-manager-plugin (for ECS connections) are bundled inside the Warden image — nothing to install on the host.

Quick Start (Docker) #

Docker Compose starts the app (with the AWS CLI + session-manager-plugin bundled) and a PostgreSQL instance together.

cp .env.example .env        # set ENCRYPTION_KEY, POSTGRES_PASSWORD, and review the rest
docker compose up -d        # starts the provided Warden image + PostgreSQL
open http://localhost:8080
Required before first boot

Compose refuses to start until POSTGRES_PASSWORD and ENCRYPTION_KEY are set in .env. Generate a key with openssl rand -base64 32changing it later makes existing encrypted secrets and recordings unreadable.

The image runs as non-root user warden (UID 1000) with a read-only root filesystem; /tmp and /home/warden are tmpfs, and /data (recordings + known_hosts) is a persistent volume.

First-Run Setup #

On first launch — when no users exist — Warden's bootstrapCheck() prints a one-time setup link to stdout (valid 24 hours):

WARDEN setup link (expires in 24 h): http://localhost:8080/...

Open that link to create the initial admin account with a passkey. Every subsequent user is invited from Admin → Users (an invite or reset link is emailed via SMTP, or printed to stdout if SMTP is not configured).

How It Works #

Browser (xterm.js)
  │  WS binary frames  — raw terminal I/O
  │  WS text frames    — JSON connect / resize / session_id / exit
  ▼
Go HTTP server  (net/http + gorilla/websocket)
  │  ── SSH path ──  pty.Start(ssh / sshpass)
  │  ── ECS path ──  pty.Start(aws ecs execute-command --interactive)
  │  one long-lived liveSession owns the PTY:
  │     output → recorder (if enabled) + hub fan-out (owner + share subscribers) + scrollback
  ▼
Remote SSH server  or  AWS ECS task (via SSM session-manager-plugin)
  1. Browser authenticates via passkey; the server issues an http_sessions cookie.
  2. User opens a WebSocket to /ws and sends {"type":"connect", "connType":"ssh"|"ecs", ...} (or resumeSessionId to reattach).
  3. Saved configs are resolved and the target is checked against the access-approval gate; inputs that become process args are validated.
  4. The PTY is owned by a liveSession that outlives the WebSocket. Detaching (logout / tab close) leaves it running; the owner reattaches later and the buffered scrollback is replayed.
  5. PTY output is fanned out to the owner and any share subscribers, and (when the environment opts in) appended to the recorder.
  6. On a clean remote exit the server sends {"type":"exit"} and the tab auto-closes; the recording is finalized to an encrypted file on disk.
  7. Sessions with no browser attached are reclaimed after SESSION_IDLE_TIMEOUT; a session is also killed if its access grant expires.

The liveSession Model #

The load-bearing invariant: a liveSession owns the PTY — not the WebSocket. Registered in a sync.Map by sessionID, it owns the ptmx, the recorder, and a sessionHub, and it outlives any WebSocket.

  • Start: newLiveSession launches run() (PTY → recorder + hub fan-out) and a cmd.Wait() reaper.
  • Attach / detach: WebSockets attach via serveOwner or handleShareWS. On WS close they detach — they never finalize or kill the session.
  • End: the session ends only when the PTY closes — process exit or kill() (ptmx.Close(), called by the End endpoint, the idle sweeper, or when a grant expires). Then finish() runs once: reads the exit code, notifies a clean exit ({"type":"exit"} → tab auto-closes), closes the hub, deletes from the registry, finalizes the recording, removes the temp SSH key.
  • Reclaim: detached sessions with no clients are reclaimed after SESSION_IDLE_TIMEOUT; the sweeper also kills a session whose access grant has expired.

The session hub appends to a bounded scrollback (256 KiB) and fans output out to all clients; on attach it replays the scrollback to restore the screen. An owner-approved view→control upgrade takes effect live, with no reconnect.

Roles #

RoleTerminal accessAdmin panelUse for
user defaultYesNoEngineers who open SSH/ECS terminals.
adminYesYesOperators who manage users, environments, AWS accounts, recordings.
admin_onlyNoYesApprovers / auditors — admin panel access without any terminal access.

admin_only passes the admin guard but is blocked from /ws; the terminal page redirects it to /admin. Disabling a user immediately drops their browser sessions and kills their live terminals.

SSH Terminals #

Connect to any reachable SSH server. Warden launches a real ssh subprocess (wrapped by sshpass for password auth) attached to a PTY.

  • In the UI, choose the SSH tab and enter host, username, port, and credentials — or pick a saved connection.
  • Host, username, port, and any value that becomes a process argument are validated and rejected if they could be parsed as options (e.g. -oProxyCommand=…).
  • SSH host keys are verified via accept-new (trust-on-first-use) against a persistent known_hosts by default; set SSH_HOST_KEY_CHECKING=yes for strict verification against a pre-seeded file.
SettingDefaultDescription
SSH_HOST_KEY_CHECKINGaccept-newaccept-new (TOFU), yes (require known_hosts), or no (legacy insecure).
SSH_KNOWN_HOSTS_FILEknown_hostsPath to the SSH known_hosts file (Docker: /data/known_hosts).

AWS ECS Terminals #

Exec into a Fargate container with aws ecs execute-command --interactive over the SSM session-manager-plugin.

  1. Enable ECS Exec on your task definition / service (enableExecuteCommand: true).
  2. Add an AWS account in Admin → AWS Accounts (access key ID + secret key, stored AES-256-GCM encrypted; optional default region/cluster/role ARN). Users then pick the account by name in the ECS connect form — no AWS credentials on the Warden host required. Selecting "host credentials" falls back to the host's ambient AWS config/instance role.
  3. The Docker image bundles the AWS CLI + session-manager-plugin — nothing to install on the host.

In the UI: switch to the AWS ECS tab, pick an AWS account, enter cluster + task ID (or ▾ List to browse running tasks), then connect.

Required IAM permissions

Credentials are shared by all Warden users, so apply least privilege — scope Resource to the specific cluster/task ARNs you expose rather than "*", and prefer a separate, narrowly scoped role per environment.

{
  "Effect": "Allow",
  "Action": [
    "ecs:ExecuteCommand", "ecs:ListTasks", "ecs:DescribeTasks",
    "ssm:StartSession", "ssm:TerminateSession", "ssm:DescribeSessions"
  ],
  "Resource": [
    "arn:aws:ecs:REGION:ACCOUNT:cluster/YOUR_CLUSTER",
    "arn:aws:ecs:REGION:ACCOUNT:task/YOUR_CLUSTER/*"
  ]
}
Fail-closed by default

ECS_ALLOW_UNGATED=false (default) means an AWS account or cluster must be placed in an environment the user can access before it may be used — the shared admin AWS credentials never reach a target no admin has assigned. Set it to true only to restore the legacy behavior where any authenticated user may use any ungated account/cluster.

AWS Accounts #

Admin-managed AWS credentials, encrypted at rest and shared across users — so engineers never handle raw AWS keys.

  • Created in Admin → AWS Accounts: a name, access key ID, secret key (AES-256-GCM encrypted), and optional default region / cluster / role ARN.
  • Users select an account by name in the ECS connect form; the secret never leaves the server and is passed only to the aws subprocess via a minimal environment.
  • The non-admin list endpoint returns only names/metadata — never secret material.
ECS gating keys on both account and cluster

So "host credentials" can't be used to dodge an account-scoped gate. See Access Approvals.

Saved Connections #

SSH and ECS connection profiles are stored server-side per user so common targets are one click away.

  • SSH configs (/api/configs): host, user, port, and credentials — secrets AES-256-GCM encrypted at rest.
  • ECS configs (/api/ecs-configs): cluster + task references, pointing at a stored AWS account (account_id) rather than embedding credentials.

When you connect with a saved config, Warden resolves it server-side, then runs the same approval gate and argument validation as a manual connection.

Access Approvals #

Gate sensitive environments behind time-boxed, approver-granted access with a full audit trail.

Admins define environments and attach connection resources to them — an AWS account or ECS cluster (for ECS) and/or an SSH host glob. An environment marked restricted (requires_approval) needs an active, time-boxed grant to connect; each environment has a list of approvers.

  • Users request access from the terminal page (My Access): environment + duration-or-until + justification. Approvers see the request (a ~5 s polling banner, or the admin Access Grants table) and approve / deny; admins can also grant directly.
  • The gate runs at connect time and is re-checked on reattach; a running session is killed when its grant expires.
  • ECS gating keys on both the AWS account and the cluster, so "host credentials" can't be used to dodge an account-scoped gate. SSH matches the host glob via path.Match.
  • Every request and decision is auditable in Admin → Access Grants.

Environment fields

FieldDescription
Resources (kind)aws_account, ecs_cluster, or ssh_host (glob) attached to the environment.
ApproversUsers who may approve/deny requests for this environment.
requires_approvalWhen true, an active grant is required to connect.
record_sessionsIndependently opt this environment into session recording (off by default).
max_duration_secsOptional cap on how long a grant can last.

Grants are a state machine: pending → approved/denied/cancelled, approved → revoked; expired is derived. An active grant is approved and not expired. Approve / deny / revoke self-authorize (approver-for-env or admin).

Resumable Sessions #

A session keeps running server-side when you log out or close the tab. Reattach later with the screen restored.

  • Because the liveSession owns the PTY, closing the browser only detaches — the remote process keeps running.
  • Reconnect by sending resumeSessionId on the /ws connect frame (the UI's Running Sessions list does this for you). Ownership and access are re-verified, then the buffered scrollback is replayed to restore the screen.
  • Detached sessions are reclaimed after SESSION_IDLE_TIMEOUT (default 2h); each user is capped at SESSION_MAX_PER_USER concurrent live sessions (default 20), with a global cap of MAX_LIVE_SESSIONS (default 500).

Live Session Sharing #

Share a running terminal with another user — view-only or with control — for pairing, debugging, or supervision.

  • The owner invites a user to a running session; the invitee accepts and attaches as a subscriber to the session hub via /ws/share/{id}, receiving the replayed scrollback.
  • A viewer can request control; the owner approves and the viewer is upgraded to control live — the share mode is re-checked per message, so no reconnect is needed.
  • Revoking a share, or disabling the user, immediately disconnects active subscribers.

Sharing is managed through the /api/share/* endpoints (create, accept/decline, request/grant/deny control, revoke) — see the API Reference.

Session Recording #

Opt-in per environment. Captured as asciicast v2, gzipped and AES-256-GCM encrypted on disk, played back and downloaded by admins with speed control and scrubbing.

  • Recording is off by default. A session is recorded only when its target's environment has record_sessions=true — independent of whether the environment requires approval.
  • On finalize, output is gzipped → AES-GCM encrypted → written to {RECORDINGS_DIR}/{id}.cast.gz (magic header WER1), with recording_path + recording_size stored in the DB. On a file-write failure it falls back to an encrypted DB column.
  • Admins browse and play recordings in Admin → Session recordings (and via /api/admin/sessions/{id}/recording). Playback handles legacy plaintext columns and legacy unencrypted gzip transparently.
SettingDefaultDescription
RECORDINGS_DIRrecordingsDirectory for encrypted recording files (Docker: /data/recordings).
RECORDING_MAX_BYTES16777216 (16 MiB)Per-recording in-memory buffer cap.
RECORDING_RETENTION_DAYS0 (keep forever)Delete recordings older than N days. Orphaned files with no DB row are always cleaned at startup.
Encryption key

Recordings are encrypted with ENCRYPTION_KEY. Changing the key makes existing recordings (and stored secrets) unreadable.

Tabs, Tile & Broadcast #

Work many connections at once.

  • Tabs — keep multiple SSH/ECS connections open and switch between them.
  • Tile layout — view several terminals side-by-side.
  • Broadcast — optionally mirror your keystrokes to every open terminal at once, for fan-out operations across a fleet.

Running and saved sessions are listed in the left rail; a running session shows a reattach (↻) indicator.

Authentication #

Warden is passkey-only (WebAuthn) — there are no passwords to phish, reuse, or leak.

  • Registration is invite-driven: an admin sends a magic link (one-time token, 24 h) which the user opens to register a passkey. The very first admin is created from the first-run setup link.
  • Login uses the registered passkey; the WebAuthn sign counter is persisted on each login.
  • Browser sessions are http_sessions cookies — 7-day rolling with an absolute 30-day cap. https:// auto-enables Secure cookies + HSTS.
  • Auth endpoints are protected by a per-IP rate limiter; magic tokens are one-time; disabling a user drops their sessions and kills live terminals.
SettingDefaultDescription
APP_URLhttp://localhost:8080Public URL — the WebAuthn origin; also scopes WS/CSRF origin checks and CSP. https:// auto-enables Secure cookies + HSTS.
RP_ID(APP_URL host)WebAuthn relying-party ID — must match the domain users browse to.

Security Model #

Built-in protections across the request, subprocess, and storage boundaries.

  • Input validation (the RCE boundary) — host/username/port and ECS fields that become process arguments are rejected if they could be parsed as options (e.g. ssh -oProxyCommand=…). Any user value reaching exec goes through validate.go.
  • SSH host keys — verified via accept-new (TOFU) against a persistent known_hosts by default; SSH_HOST_KEY_CHECKING=yes for strict.
  • WebSocket — origin checked against APP_URL; per-connection read limit; keepalive with read deadlines.
  • Secrets at rest — SSH/AWS credentials and recordings are AES-256-GCM encrypted. Subprocesses receive a minimal environment — the AES key / DB URL / SMTP password are never passed to ssh / aws.
  • Headers — CSP, X-Frame-Options: DENY, nosniff, Referrer-Policy: no-referrer; HSTS when SECURE_COOKIES.
  • Auth — passkey-only; per-IP rate limiting; one-time magic tokens; absolute session lifetime; disabling a user drops sessions and kills live terminals.
Container posture

The Docker image runs as non-root warden (UID 1000) with a read-only root filesystem, all Linux capabilities dropped, and no-new-privileges. /tmp and /home/warden are tmpfs; /data is the only persistent, writable mount.

STRICT_AAD

STRICT_AAD=true rejects legacy unauthenticated/unbound data — it disables the no-AAD decryption fallback and requires the WER1 header on recordings. Enable only after re-encrypting any pre-existing secrets/recordings.

Hardening Checklist #

Before exposing Warden to the internet:

  • Put TLS in front (the bundled Caddy service, or Nginx) — see Deployment & HTTPS.
  • Set APP_URL to your https:// domain and RP_ID to its host.
  • Set SECURE_COOKIES=true and TRUST_PROXY=true (only when actually behind a trusted proxy that sets X-Forwarded-For / X-Real-IP).
  • Keep BIND_ADDR=127.0.0.1 unless you front it with TLS; never publish the app port directly without TLS.
  • Generate a strong ENCRYPTION_KEY and back it up — losing it makes all encrypted secrets and recordings unreadable.
  • Use sslmode=require (or verify-full) for any non-loopback DATABASE_URL.
  • Prefer strict SSH host keys (SSH_HOST_KEY_CHECKING=yes with a seeded known_hosts) for sensitive fleets.
  • Scope AWS IAM to specific cluster/task ARNs; use a narrowly scoped role per environment.

Configuration #

All settings are environment variables. Copy .env.example to .env to get started; Docker Compose reads .env automatically.

Server & networking

VariableDefaultDescription
ADDR or PORT:8080Listen address / port.
BIND_ADDR127.0.0.1(Docker) host interface the port binds to; 0.0.0.0 to expose directly (only behind TLS).
APP_URLhttp://localhost:8080Public URL — WebAuthn origin; scopes WS/CSRF origin checks and CSP. https:// auto-enables Secure cookies + HSTS.
RP_ID(APP_URL host)WebAuthn relying-party ID.
TZUTCContainer timezone (affects log timestamps).

Database

VariableDefaultDescription
DATABASE_URLrequiredPostgreSQL connection string. Use sslmode=require for a non-loopback DB. The server refuses to start if unset.
POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DBwarden / — / wardenUsed by Docker Compose to configure the postgres service. Compose refuses to start until POSTGRES_PASSWORD is set.

Encryption & cookies

VariableDefaultDescription
ENCRYPTION_KEYrequiredBase64 32-byte key encrypting stored credentials and recordings. Generate: openssl rand -base64 32. The server refuses to start without it.
WARDEN_DEV(unset)1 permits booting with an ephemeral encryption key — local dev only; data is lost on restart.
SECURE_COOKIESfalseForce the cookie Secure flag + HSTS. Auto-enabled when APP_URL is https://.
TRUST_PROXYfalseTrust X-Real-IP / rightmost X-Forwarded-For (for rate limiting) only when behind a trusted proxy.
STRICT_AADfalseReject legacy unauthenticated/unbound data (requires the WER1 header). Enable only after re-encrypting legacy data.

Sessions & limits

VariableDefaultDescription
SESSION_IDLE_TIMEOUT2hHow long a detached session runs before the server kills it (Go duration: 30m, 2h, 8h).
SESSION_MAX_PER_USER20Cap on concurrent live sessions per user.
MAX_LIVE_SESSIONS500Global cap on concurrent live sessions across all users.
CONN_RATE_PER_MIN30Per-source-IP rate limit for new connection/spawn attempts on /ws, /ws/share, and /api/ecs/tasks.

SSH & ECS

VariableDefaultDescription
SSH_HOST_KEY_CHECKINGaccept-newaccept-new (TOFU), yes (require known_hosts), or no (legacy insecure).
SSH_KNOWN_HOSTS_FILEknown_hostsPath to the SSH known_hosts file (Docker: /data/known_hosts).
ECS_ALLOW_UNGATEDfalseWhen false (fail-closed), an AWS account/cluster must live in an accessible environment before use. true restores legacy ungated access.

Recordings

VariableDefaultDescription
RECORDINGS_DIRrecordingsDirectory for encrypted recording files (Docker: /data/recordings).
RECORDING_MAX_BYTES16777216Per-recording in-memory buffer cap (16 MiB).
RECORDING_RETENTION_DAYS0Delete recordings older than N days. 0/unset keeps them indefinitely.

SMTP (optional — magic links print to stdout if unset)

VariableDefaultDescription
SMTP_HOSTSMTP server for magic-link emails. Unset → links printed to stdout.
SMTP_PORT / SMTP_USER / SMTP_PASS / SMTP_FROM587 / —SMTP delivery settings.
SMTP_TLSrequireTransport security: STARTTLS required by default (implicit TLS on port 465). disable for local testing only; implicit to force SMTPS on a non-465 port.

Deployment & HTTPS #

The recommended deployment is Docker Compose with a TLS reverse proxy in front.

Compose services

The bundled docker-compose.yml runs the warden app (read-only root FS, all caps dropped, tmpfs /tmp + /home/warden, persistent /data volume) and a pinned postgres:16-alpine with healthchecks. The app port binds to ${BIND_ADDR:-127.0.0.1} so it isn't reachable off-host without a proxy.

HTTPS with the bundled Caddy

Uncomment the caddy service in docker-compose.yml and edit the Caddyfile with your domain — Caddy obtains and renews a Let's Encrypt certificate automatically. Requirements: a public domain pointing at the host, ports 80 + 443 open.

ssh.yourdomain.com {
    reverse_proxy warden:8080 {
        # Forward the real client IP (replace, don't append, so a client-supplied
        # X-Forwarded-For cannot survive into the chain and spoof the rate-limiter).
        header_up X-Real-IP {remote_host}
        header_up X-Forwarded-For {remote_host}
        flush_interval -1          # snappier WebSocket streaming
    }
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains"
        X-Content-Type-Options    nosniff
        X-Frame-Options           SAMEORIGIN
        Referrer-Policy           strict-origin-when-cross-origin
    }
}

Then set APP_URL=https://ssh.yourdomain.com, RP_ID=ssh.yourdomain.com, SECURE_COOKIES=true, and TRUST_PROXY=true.

Split DB deployments

App↔DB traffic uses sslmode=disable in Compose because both containers share a private Docker network and the DB publishes no host port. If you run the database on a separate host, provision a server cert and set sslmode=require (or verify-full) so the DB password and encrypted columns aren't sent in cleartext.

Database Schema #

Schema is applied at startup via CREATE TABLE IF NOT EXISTS + ALTER TABLE … ADD COLUMN IF NOT EXISTS — there is no separate migration tool. All of it lives in db.go:schema().

TablePurpose
usersAccounts (email, role user/admin/admin_only, disabled).
passkeysWebAuthn credentials (sign counter persisted on login).
magic_tokensInvite / reset / setup one-time tokens (24 h).
http_sessionsBrowser cookies — 7-day rolling, absolute 30-day cap.
ssh_configs / ecs_configsSaved connection profiles (SSH secrets encrypted; ECS refs an account_id).
aws_accountsAdmin-managed AWS creds (secret AES-GCM encrypted), shared across users.
ssh_sessionsAudit log of all terminal sessions (ECS too) + recording_path/recording_size.
session_sharesLive share invitations + control_requested.
environments / environment_resources / environment_approversAccess-control configuration.
access_grantsTime-boxed access requests / grants (audit trail).

HTTP API Reference #

All /api/* routes require an authenticated session cookie; /api/admin/* requires an admin (or admin_only) role. State-changing requests are CSRF-guarded. WebSocket endpoints are origin-checked and connection-rate-limited.

WebSockets & pages

MethodPathDescription
GET/wsTerminal WebSocket — connect or reattach (resumeSessionId). Auth + connection-rate-limited.
GET/ws/share/{id}Attach to a shared session as a subscriber (view or control).
GET/ · /login · /adminTerminal UI · auth/invite page · admin panel (admin-gated).

Auth & user

MethodPathDescription
POST/api/auth/register/begin · /finishWebAuthn registration (rate-limited).
POST/api/auth/login/begin · /finishWebAuthn login (rate-limited).
POST/api/auth/logoutEnd the browser session.
GET/api/auth/validate-tokenValidate an invite / reset / setup token.
GET/api/user/me · /api/user/passkeysCurrent user; list own passkeys (DELETE /{id} to remove).

Connections — SSH / ECS / AWS

MethodPathDescription
GETPOST/api/configsList / create saved SSH configs (GET/PUT/DEL /{id}).
GET/api/ecs/tasksList running ECS tasks for an account/cluster (rate-limited).
GETPOST/api/ecs-configsList / create saved ECS configs (DEL /{id}).
GET/api/aws-accountsList AWS accounts (names/metadata only — no secrets).
GETPOST/api/admin/aws-accountsAdmin: full CRUD over AWS accounts (PUT/DEL /{id}).

Sharing

MethodPathDescription
GET/api/usersList users to share with.
POST/api/shareCreate a share invitation.
GET/api/share/pending · /control-requestsPending share invites; pending control requests.
POST/api/share/{id}/accept · /declineRespond to a share invite (DEL /{id} to revoke).
POST/api/share/{id}/request-control · /grant-control · /deny-controlLive view→control upgrade flow.

Environments & access grants

MethodPathDescription
GET/api/environments · /restrictedList environments; list restricted ones the user can request.
POST/api/admin/environmentsAdmin: create environments (PUT/DEL /{id}, resources, approvers).
POST/api/access/requestsRequest access to an environment (GET /my, DEL /requests/{id}).
GET/api/access/pendingApprover: list pending approvals.
POST/api/access/{id}/approve · /deny · /revokeApprove / deny / revoke a grant.
GETPOST/api/admin/grantsAdmin: list all grants; create a direct grant.

Sessions & admin

MethodPathDescription
GET/api/sessions/activeList the user's active (running) sessions.
POST/api/sessions/{id}/endEnd a running session (kills the PTY).
GET/api/admin/usersAdmin: user CRUD (GET/PATCH/DEL /{id}).
POST/api/admin/invite · /users/{id}/reset-passkeyInvite a user; reset a user's passkey.
GET/api/admin/sessions · /{id}/recordingAdmin: session audit log; fetch a recording for playback/download.