Dendrite documentation
Everything you need to install, run, and secure Dendrite, from your first sign-in to air-gapped production deployment.
What's new in 0.18
0.18 is the licence and supply-chain release.
- Agents verify the server - the server has an identity key that agents pin, and the agent control stream is sealed after a handshake both sides sign. A proxy that ends TLS can no longer read or change commands. See Agent channel security.
- Admission headers - every agent call carries a signed, single-use stamp; in
enforcemode anything else gets a bare 404. - Failover between servers - agents can find their servers through DNS SRV records and fail over by priority.
- Signed releases - every release is signed with the production key
upd-2026-02, and agents install a pushed update only when the signed release manifest lists it. See Updates. - Licensing - a server without a licence runs as the free Community tier, licence tiers are enforced, and licensed servers check in with the licensing server. See Licensing & tiers.
- Fix - recordings no longer fail when the retention purge runs as they start.
0.18.1 makes the server hard to wedge and fixes two security advisories:
- No more hangs - an agent that stops reading its commands is disconnected instead of blocking the server; Redis, LDAP, licence checks and update downloads time out; and a watchdog logs an error if the server ever stops running tasks.
- Memory freed - closed terminals release their output, and a browser that goes silent for 60 seconds is disconnected (
session.leavewithreason=timed_out). - Directory users on SSSD hosts - the agent resolves users and their groups through the host's
getent, so sessions run as directory users that SSSD or LDAP serves. - Security - rustls-webpki and webpki-roots updated for RUSTSEC-2026-0381 and 0382, and identity events now have their own SIEM category.
0.17 added access grants, session recording and replay, audit export to SIEMs, the file transfer policy, per-agent identity and updates in place.
server-identity.key with the licence. 0.17 hosts carry only the old release key, so install again from https://dendrite.sh/get rather than running dendrite update (see Updates). Servers without a licence become Community: activate a licence first if they use LDAP, single sign-on, file transfer, recording or the analytics, or need more than 5 users or 5 hosts.
dendrite update has not yet been tried on a live Linux server, so run it with --dry-run first.
Installation
Current release: v0.18.1 (alpha; patch and build numbers increment automatically in CI).
What you need
- Server: one Linux host, x86_64 or aarch64 (including Huawei Kunpeng 920 and other ARMv8.2 servers with 4K or 64K page kernels), with systemd. AlmaLinux, RHEL, Rocky, openEuler, Debian and Ubuntu work; the binaries are static, so the distribution version does not matter. No container runtime.
- Root (sudo), and
dnforapt-getunless Redis and nginx are already installed. - A DNS name for the server, for example
dendrite.example.com, and about 500 MB free on/usr/localand/var/lib. - Hosts: Linux or macOS, running the Dendrite agent.
- Users: a modern browser. Nothing to install.
Install the server
curl -fsSL https://dendrite.sh/get | sudo sh
The installer picks the binary for the host, checks it against SHA256SUMS and the release signature, and starts the setup wizard, one screen at a time:
- Host checks - systemd, Redis, nginx, ports, SELinux, firewalld and disk space. Nothing changes yet.
- Role - choose Server.
- Address - the public URL, the listen address (keep
127.0.0.1behind nginx) and the port (8051). - TLS - a self-signed certificate (setup makes a small local CA and prints its fingerprint), your own certificate and key, or no TLS behind your own proxy.
- Session store - Redis on this host (installed with a generated password, bound to 127.0.0.1), or an existing Redis.
- Licence - optional. Without one, the server runs as the free Community tier (see Licensing & tiers).
- Admin account - username, display name and a password of at least 12 characters.
- Sign-in - passkeys optional, or required for administrators.
- Review - the plan. Nothing changes until you press enter.
Setup then installs Redis, nginx, the binaries and the web interface, writes /etc/dendrite/server.env and the systemd units, creates the admin account, starts the services and checks /healthz and /readyz. Add --dry-run to see the plan without changing anything, or -s install server --yes plus the setup flags for an unattended install (dendrite setup --help lists them). Running the installer again on an installed host updates it (see Updates).
Behind a corporate proxy or a private CA, set CURL_EXTRA='--cacert /path/to/ca.pem' and the installer passes it to curl.
Air-gapped install
Copy the all-architectures bundle from the release page to the host. Nothing is downloaded:
tar -xzf dendrite-bundle-<version>.tar.gz
cd dendrite-<version>
./dendrite verify
sudo ./dendrite setup
Enrol a host
On the sessions page, select the key icon (Generate join token) and copy the one-time token. Then, on the host:
curl -sSf https://dendrite.example.com/get | sudo sh -s install client --join <token>
The agent is always downloaded from your own server, and the join token carries the server's identity fingerprint, which the agent pins. With a self-signed certificate, add -k to the first download and pin the CA fingerprint that the server's setup printed with --trust-ca <fingerprint>; the agent setup checks the CA against it before trusting anything. See Hosts, agents & updates.
Quickstart
From a fresh Linux host to your first shared session in under 10 minutes.
1. Install the server
curl -fsSL https://dendrite.sh/get | sudo sh
Follow the wizard (see Installation). Most of the time goes on installing Redis and nginx.
2. Sign in
Open your public URL and sign in with the admin account. Then add a passkey to it: Admin → Users → your account. To sign people in with your identity provider or directory, connect it on Admin → Identity (see Sign-in & single sign-on).
3. Add a host
On the sessions page, select the key icon to generate a join token, then run on the host:
curl -sSf https://dendrite.example.com/get | sudo sh -s install client --join <token>
The host appears in the host vault when its agent connects.
4. Start a session
Select New session, search the host vault (or paste user@hostname), give the session a title and select Connect.
5. Share it
Open the session's share panel on the sessions page, invite a username, and choose Can type or View only and how long the access lasts. Turn on Require approval to join to have people ask first.
Hosting & joining sessions
Every session runs on a host - a machine running the Dendrite agent. The shell, files, and environment all belong to that host; the server relays the session and never executes commands on your behalf.
- Host vault - a search-first list of registered hosts; or paste
user@hostnameto connect directly. - Session dashboard - your active sessions on the home screen; select one to rejoin. Sessions keep running on the host when you disconnect, survive server restarts, and resume where you left off, scrollback included, from any browser.
- Runs as you - on hosts whose accounts come from your directory, sessions of directory users run as that user, not as root. Each terminal start is audited with the account it runs as.
- Collaboration - several terminals on one shared canvas, live cursors, and an integrated chat panel.
- Latency panel - per-hop network latency, so participants can tell connection issues from performance issues.
- Invites and sharing - invite teammates from the share panel, or into a running session; each invite becomes an access grant.
- Participant controls - the owner can make one participant view-only, or kick them. A kick closes the connection on the server and revokes their grant.
- Transcripts - each participant can record and download their own transcript; terminal escape sequences are stripped from it.
- Leaving - closing your browser detaches you; the session keeps running until its owner closes it.
Sharing a terminal from the command line
dendrite-agent --server https://dendrite.example.com
This shares a terminal of the current machine through your server and prints the link. --enable-readers prints separate view-only and edit links. The client has no default server: --server (or DENDRITE_SERVER) is required. In 0.17, signed-in users cannot open these sessions on servers with sign-in enabled (a known issue); start sessions from the dashboard instead.
Sharing & access grants
An access grant records one person's access to one session: view or edit, who granted it, how (an invite or an approved join request), and when it expires or was revoked. A grant starts when the person accepts the invite.
- Share panel - on the sessions page, invite a username and choose Can type or View only, and how long: no expiry, 1 hour, 4 hours, 8 hours, 1 day or 7 days. The panel lists every member with their access and expiry, and Remove revokes it.
- Just-in-time defaults - an approved join request grants edit access for 4 hours. The server's
--jit-grant-ttl-secs(DENDRITE_JIT_GRANT_TTL_SECS) changes the default; 0 means no expiry. - Durable - grants survive reconnects and server restarts.
- Enforced continuously - a view grant makes every connection read-only. When a grant expires, the connection ends with "Your access to this session has expired". When one is revoked, every connection of that person ends at once, and they need a new invite to come back, even to a session that does not require approval.
- Fails closed - if the server cannot read the access store, it refuses the connection rather than guess.
- Audited -
grant.created,grant.revokedandgrant.expired, plussession.join(role, access mode, client address) andsession.leavewith the duration and why the connection ended:closed,disconnected,timed_out(a browser silent for 60 seconds),kicked,expired,session_closedorerror.
View-only mode
View-only means watching without typing: no keystrokes, no new terminals, no file transfer, and no browsing of the host's files. The server enforces it, so it is not a client courtesy.
- Whole session - the owner opens Terminal Settings → View only. Only the owner can type; everyone else observes. Attached clients lose input at once, with no reconnection.
- One person - from the participant list, the owner can make a single participant view-only. The restriction follows the user, not the connection, so reconnecting does not lift it, and it survives server restarts. Each change is audited as
session.user_view_only. - From the start - invite someone with View only in the share panel.
File transfer
Enterprise. Drag files into the terminal. Each session has its own file store on the server: uploads go there, and the server sends files on to the host when you ask it to. The browser never talks to the agent, and the agent writes only files the server hands it.
- Uploads and downloads over HTTP - the browser uploads a file as one request with real progress, and downloads go through the browser's own download manager. Download links work once and expire after two minutes.
- Deduplicated - a file already in the session's store (same SHA-256) is not uploaded again.
- Send and pull - send a stored file to the host's terminal directory, or pull a file from the host into the store. The agent moves each file on its own stream, so a large transfer never delays terminal output, and writes it atomically.
- File browser - lists the session's files and the host's directories, for members with write access. Session files are removed when the session closes; the audit trail keeps each file's hash.
- Per-direction policy - administrators choose who may upload, download, pull from a host and send to a host (members, the session owner only, or nobody), and block file types such as
exe, dll, ps1. The server enforces it on every request and audits refusals asfile.denied. - Malware scanning - with
DENDRITE_FILE_SCANpointing at a ClamAV daemon, every upload and every file pulled from a host is scanned before it is kept. Infected files are refused and audited. If the scanner cannot be reached, files are refused (fail closed, the default) or let through with an audit event (fail open). - Integrity - a file streamed from a host is checked against the hash the host announced, so a corrupted download fails visibly.
- Limits - transfer can be switched off, and the size cap set (50 MB by default, up to 100 MB), on the dashboard's Features page.
- Audited -
file.upload,file.download,file.send,file.pull,file.delete, and blocked or failed transfers, with the requesting username.
Terminal settings
Open Terminal Settings from any session to customise:
- Name - your display name, from your account or directory.
- Colour palette - theme for terminal text; ships with popular palettes including VS Code Dark. Per-user, and never affects other participants.
- Scrollback - lines of previous output kept in the window (default 5000).
- View only - the server-enforced lock described in View-only mode (session owners).
Sign-in & single sign-on
Dendrite signs people in with your organisation's identity provider, your directory, or its own local accounts - or all three at once. Local accounts and passkeys work on every tier, LDAP from Small Teams, and single sign-on on Enterprise. The sign-in page shows one button per single sign-on provider and the password form for local and directory accounts.
- Single sign-on (OpenID Connect) - Microsoft Entra ID, Okta, Google Workspace, Keycloak, Auth0 and other OpenID Connect providers. Users get your provider's multi-factor authentication, and Dendrite never sees their passwords.
- Directory (LDAP) - Active Directory over LDAPS, and OpenLDAP, FreeIPA and Red Hat IdM with STARTTLS or LDAPS. The server certificate is verified by default, a custom CA is accepted, nested groups are supported, and several directory servers can be listed for failover. No directory passwords are stored.
- Local accounts - held by the server, with Argon2id-hashed passwords and optional passkeys. Keep them for teams without a directory and for break-glass access.
Where to configure
Use the server environment (DENDRITE_OIDC_*, DENDRITE_LDAP_*) or /etc/dendrite/auth.toml for configuration management, or Admin → Identity to add, edit, test, enable and disable providers with no restart. Changes on the Identity page are audited, and client secrets and service passwords are write-only. Set DENDRITE_PUBLIC_URL first, then check the configuration:
dendrite-server --identity-check
How single sign-on is secured
- Authorisation code flow with PKCE (S256), a fresh single-use
stateandnoncefor every sign-in, and a strict redirect URI. - ID tokens are checked against the provider's published keys (asymmetric algorithms only), with
iss,aud,exp,nbfandiatvalidated. - Each federated user is bound to the provider and its subject, so another account cannot take over a username.
- Sign-in attempts are rate-limited per client address.
Provisioning and lifecycle
Groups (or app roles) map to Dendrite roles. Federated users are created at their first sign-in, or only when an administrator adds them if you turn just-in-time provisioning off. Directory sign-ins check the directory each time someone signs in. To cut someone off at once, disable them on Admin → Users; that applies to their next request.
Passkeys
Local accounts can sign in with a passkey (WebAuthn/FIDO2): Touch ID, Windows Hello, Android, a password manager, or a security key such as a YubiKey. Single sign-on and directory users rely on their provider's multi-factor policy instead.
- Phishing-resistant - passkeys belong to your server's domain (the host of
DENDRITE_PUBLIC_URL), so they cannot be used on a lookalike site. - Passwordless or second factor - by default a user can sign in with the passkey alone; turn passwordless off to make the passkey a second factor after the password.
- Required for administrators - optionally, administrators must sign in with a passkey (
DENDRITE_PASSKEY_REQUIRE_FOR_ADMINS, or the Identity page). - Enrolment - users are prompted to add a passkey; administrators add theirs on Admin → Users.
Roles
Every user holds one of five platform roles. The role is checked on every request, so a change applies at once.
| Role | Can |
|---|---|
viewer | Sign in and see their own sessions. |
vendor | Join sessions they are invited to. Cannot create sessions or register hosts; a session they ask for goes to an admin for approval. |
operator | Create sessions and register hosts. |
auditor | Open the dashboard's Audit, Recordings and Sign-ins sections, and export the audit log. |
admin | Everything, including the whole dashboard. |
Mapping groups to roles
Single sign-on providers map groups or app roles to roles on the Identity page. For LDAP, set the group search base and map group DNs to roles; the highest matching role wins:
DENDRITE_LDAP_GROUP_BASE_DN='ou=groups,dc=example,dc=com'
DENDRITE_ROLE_GROUPS='admin=cn=dendrite-admins,ou=groups,dc=example,dc=com,auditor=cn=dendrite-auditors,ou=groups,dc=example,dc=com'
The same settings are available as dendrite-server flags (--ldap-group-base-dn, --role-groups). --admin-group (DENDRITE_ADMIN_GROUP) is a shorthand that makes the listed directory usernames admins.
Changing roles at runtime
On Admin → Users, administrators set the role of a local account, pin a role for a directory or single sign-on user (it wins over the group mapping), or disable a user. Every change is audited as role.changed.
Just-in-time approvals
Access in Dendrite is granted for the session that needs it, for as long as it is needed - not permanently.
- Turn on Require approval to join for a session in its share panel. Anyone without a grant then asks for access, with a reason, and the owner sees the request with Approve and Deny.
- An approval sends the person an invite, which becomes an access grant: edit access for 4 hours by default. Over the API, the approver can grant view-only access or another duration for one approval.
- The server enforces access on the WebSocket itself: nobody attaches before a decision, and an expired or revoked grant disconnects the person at once.
- Vendors cannot create sessions. When a vendor asks for a session on a host, an administrator approves or declines it; on approval the session opens and the approving administrator is invited to it.
- Decisions are audited as
jit.approvedandjit.denied, and vendor session requests assession.request.
Vendors & guests
The vendor role
Third parties and contractors sign in with an account that holds the vendor role - a local account, or a directory or single sign-on group mapped to vendor.
- Invited sessions only - vendors join the sessions they are invited to, and cannot create sessions or register hosts.
- Time-boxed - invite a vendor with view-only access for an hour or a day; when the grant expires, the server disconnects them.
- Session requests - a vendor's request for a session goes to an administrator for approval (see Just-in-time approvals).
- Dangerous-command audit - when it is turned on, commands that match your patterns in vendor and operator terminals are audited as
terminal.command.dangerous.
Anonymous view-only guests (Enterprise)
On Enterprise-licensed servers, with anonymous view-only join turned on in the dashboard, a session owner can copy a read-only link for someone with no account. The guest picks a display name and appears as Name (guest). Guests can only watch: no typing, no new terminals, no file transfer and no chat. Guest tickets are single-use and expire after 10 minutes, and every attach is audited as anon.attach. Guests cannot join sessions that require approval.
Admin dashboard
Administrators get a management surface at /admin:
- Overview - activity at a glance (analytics, Professional and up).
- Sessions - live sessions; end one from here.
- Users - local accounts, role assignment and overrides, disabling users, and passkey enrolment.
- Identity - single sign-on and directory providers, and the passkey policy.
- Audit - a filterable, paginated audit trail with CSV export.
- Recordings - replay recorded sessions (see Session recording).
- Audit export - SIEM outputs and each one's chain status.
- Hosts - agents, their versions, agent keys, whether each channel is sealed, the server's fingerprint, enrolment tokens, revocation and updates.
- Sign-ins - sign-in successes, failures and sources (analytics, Professional and up).
- Features - live toggles with no restart: file transfer and its policy, blocked types and scanner status, size caps, recording mode and input capture, blank on blur, clipboard guard, screenshot detection, watermark, dangerous-command audit and guest links. Every change is audited as
feature.changed. - Licence - activate or import a licence, and see its state, the last check with the licensing server and what it found.
Administration works on every tier, Community included. Users with the auditor role see only Audit, Recordings and Sign-ins.
Audit logging & SIEM
Dendrite records administrative and session events on the server:
- Sign-ins and failures, including passkey sign-ins, with a dedicated sign-in view
- Session creation and closure, and each terminal start with the account it runs as
- Invites, join requests and decisions, access grants, and joins and leaves with the reason a connection ended (
invite.sent,jit.approved,grant.created,session.join,session.leave,session.kick) - File transfers with the requesting username, plus policy and scanner refusals (
file.upload,file.download,file.denied,file.upload.blocked) - Recordings, and every replay and export of one (
recording.start,recording.stop,recording.view,recording.export) - Dangerous commands and screenshot attempts (
terminal.command.dangerous,screen.capture) - Agent enrolment, authentication failures, revocation, updates, sealed-channel violations and refused admissions (
agent.protocol_violation,agent.admission.refused) - Licence findings: an unofficial build or a licence running in two places (
licence.not_genuine,licence.clone_suspected) - Role changes, user and identity provider changes, feature changes and licence actions
Each event has a stable ID, a category and a severity. Secrets are redacted, and values that carry user input are quoted, so a file named x sha256=0000 cannot pose as a second field in your SIEM.
Streaming to a SIEM
Configure outputs on the dashboard's Audit export page or with DENDRITE_AUDIT_* settings. Delivery is at least once: each output has its own queue and disk spool, and retries with the same event ID until its collector accepts the events:
- syslog (RFC 5424 over UDP, TCP or TLS) in JSON, CEF or LEEF 2.0 - for Microsoft Sentinel, QRadar and ArcSight
- Splunk HEC, Elastic and OpenSearch (ECS documents), webhooks (HMAC-signed) and TAXII 2.1 (STIX 2.1)
- Logstash over the Beats protocol, waiting for acknowledgements
- Rotating NDJSON files for Filebeat, Elastic Agent, Fluent Bit or Vector
Ready-made Filebeat and Logstash configurations ship in the release's deploy/ directory.
Tamper evidence
Every output keeps its own SHA-256 hash chain over the events it sent, optionally signed with an HMAC key (DENDRITE_AUDIT_CHAIN_KEY). audit.heartbeat events (every 5 minutes by default) prove the pipeline is alive, and audit_export.dropped reports any events an output lost. A standalone script checks exported files offline and reports gaps, changes and forgeries:
$ scripts/verify-audit-chain.py --sink siem-file --key-file /etc/dendrite/chain.key \
/var/log/dendrite/audit.ndjson.1 /var/log/dendrite/audit.ndjson
Session recording
Enterprise. Dendrite can record every session for later replay by auditors and admins. A recording holds what the server relays anyway: terminal output, terminal sizes, terminals opening and closing, and participants joining and leaving.
- Modes - set recording on Admin → Features.
onrecords every session;requiredalso refuses a session that cannot be recorded, and closes one whose recording fails. Turning recording on also starts recording the sessions already running. - Keystrokes, if you choose - record input also records what participants type, including passwords typed at prompts. Check the employee-monitoring rules that apply to you before turning it on.
- Participants are told - everyone in a recorded session sees a Recording badge.
- Encrypted - terminal data stays encrypted with the session key, which is stored wrapped with a separate recording key. Back up the recording key, and keep it away from the recordings.
- Tamper-evident - recordings are hash-chained and sealed, and the chain head is in the
recording.stopaudit event, so your SIEM holds an independent copy.scripts/verify-recording.pychecks recordings offline. - Replay - the Recordings page plays, pauses, seeks and speeds up to 16×, skips idle time, and shows a timeline of who joined and what was typed, with an integrity verdict and asciicast export for asciinema. Every replay and export is audited.
- Storage - recordings are kept for 90 days by default (
DENDRITE_RECORDING_RETENTION_DAYS;0keeps them), up to 1 GB each (DENDRITE_RECORDING_MAX_MB), in/var/lib/dendrite/recordings.
Hosts, agents & updates
Join tokens
Enrol a host in one command (see Enrol a host). The token is short-lived and single-use, carries the server URL and the server's identity fingerprint, and is redeemed over a dedicated API - never on a command line - for the agent secret, written to /etc/dendrite/agent.env (mode 600). The agent runs as the dendrite-agent systemd service, or a launch daemon on macOS.
Per-agent identity
Each agent creates an Ed25519 key on its first start, and enrolment binds that key to the host's ID. The agent proves it holds the key on every connection, which works behind a TLS-terminating proxy. Administrators see every key on the Hosts page and can revoke any of them. DENDRITE_AGENT_AUTH chooses shared (shared secret only), both (the default, for moving a running fleet) or key (enrolled keys only).
Commands from the server to an agent carry request IDs and deadlines and are acknowledged, so a command that fails or times out is reported instead of lost, and a session opens only for the server's own request.
Updates
Run the installer again, or dendrite update, on a server or host. It installs the new binaries (keeping the old one as .previous), restarts, and checks health: a server that does not become healthy, or an agent that does not start, is rolled back. Configuration, keys and accounts stay as they are; --reconfigure runs the full setup instead, and --dry-run prints the plan.
$ sudo dendrite update --dry-run
$ sudo dendrite update
Signed releases. Every release is signed with the production key upd-2026-02 (public key T26unKGEfimO19d9aF55n0vgP8BRSj5IjPHkSXfEhX4=), and the installer and dendrite verify check it. The earlier test key, upd-2026-01, is no longer trusted.
From 0.17. 0.17 carries only the old key, so dendrite update on a 0.17 host stops with "SHA256SUMS is signed with key upd-2026-02, which this binary does not carry". Install again from https://dendrite.sh/get (installing over an install updates it), or pass the new key once:
$ sudo DENDRITE_UPDATE_VERIFY_KEYS=upd-2026-02=T26unKGEfimO19d9aF55n0vgP8BRSj5IjPHkSXfEhX4= dendrite update
Agent updates pushed by the server. When an agent connects with an older version than the server, the server pushes the matching agent binary (at most once an hour per host, or straight away with Update on the Hosts page). The agent installs it only when the vendor-signed release manifest lists it, keeps the previous binary, and restarts into the new one. The server publishes the manifest when its update channel is on (--update-channel stable); where it is off, and on macOS agents, update with dendrite update. --allow-unsigned-updates (DENDRITE_AGENT_ALLOW_UNSIGNED_UPDATES) restores the old behaviour, for servers you trust as much as their agents.
Server self-updates are off by default (--update-channel none): servers update when you run the installer or dendrite update. Update servers before agents.
The Dendrite names
Since 0.17, Dendrite installs under its own names. Hosts installed earlier keep working, and dendrite update moves them, leaving links at the old paths and the old service names as aliases.
| Now | Before | |
|---|---|---|
| Binaries | dendrite-server, dendrite-agent | sshx-server, sshx |
| Services | dendrite-server, dendrite-agent | sshx-server, sshx-agent |
| Settings | /etc/dendrite | /etc/sshx |
| State | /var/lib/dendrite | /var/lib/sshx |
| Variables | DENDRITE_* | SSHX_* (still read, and win when both are set) |
Restarts and health
On SIGTERM the server drains active connections within five seconds. /healthz and /readyz serve orchestration and load balancers, and sessions are snapshotted to Redis and restored after a restart.
Traffic identity
Dendrite components send a dendrite/<version> user agent, and the server adds X-Dendrite-Node, X-Dendrite-Version and X-Request-Id to its responses, so edge monitoring can identify and correlate traffic. These are attribution signals, not security controls.
Agent channel security
An agent runs terminals as root and receives session keys on its control stream, so from 0.18 the agent checks that it is talking to your server, and nobody in between can read or change the stream - not even a corporate proxy whose CA the host trusts.
The server's identity
The server holds an Ed25519 identity key, server-identity.key in /var/lib/dendrite, made on its first start. Agents pin it: join tokens carry the fingerprint and the installer writes it as DENDRITE_SERVER_FINGERPRINT (comma-separated SHA256:... values). An agent with no pin trusts the first server it reaches and records it. The Hosts page shows the server's fingerprint.
Back up server-identity.key with the licence. With a new key, agents that pinned the old one refuse the server. Servers behind one address share the file (DENDRITE_SERVER_IDENTITY_KEY_FILE).
The sealed control stream
- Handshake - the agent and the server exchange X25519 key shares, and each signs the transcript (both shares, both nonces, the host ID) with its own identity key. A proxy that relays the handshake cannot swap a share.
- Sealing - every message after the handshake travels as a numbered AES-256-GCM frame, with one key per direction. A frame that does not open, or comes out of order, ends the stream and is audited as
agent.protocol_violation. - Older agents keep a plain stream, shown as "TLS only" on the Hosts page. An agent with a pin refuses a server that does not prove its identity, so update servers first.
Admission headers
Every call from a 0.18 agent carries a signed dendrite-admission stamp - by the agent's key, the shared secret, or its one-time enrolment token - that is good for one call path, five minutes and one use. DENDRITE_AGENT_ADMISSION on the server decides what happens to calls without one:
log(default) - they pass and are logged, so older agents keep working while a fleet updates;enforce- they get a bare 404 before any gRPC code runs, and are audited asagent.admission.refused; a scanner sees nothing at the agent endpoint;off- they are not checked.
Move to enforce once every agent runs 0.18.
Discovery and failover
With DENDRITE_DISCOVERY_DOMAIN, an agent looks up _dendrite._tcp.<domain> before each connection and tries the servers named there, lowest priority first and weighted within a priority, then its configured server:
_dendrite._tcp.example.com. 300 IN SRV 10 60 443 dendrite-a.example.com.
_dendrite._tcp.example.com. 300 IN SRV 10 40 443 dendrite-b.example.com.
_dendrite._tcp.example.com. 300 IN SRV 20 0 443 dendrite-dr.example.com.
DNS is only a hint: every server must prove an identity the agent pins, and a discovered server is never trusted on first use, so a spoofed record leads nowhere. Servers named by one domain share the identity key, and each needs a TLS certificate for its own name. Agents connect over TCP port 443 only.
Licensing & tiers
A server runs under a licence issued by KazLabs, or without one as the free Community tier. The server enforces the features its licence lists.
| Tier | Users | Hosts | Adds |
|---|---|---|---|
| Community | 5 | 5 | Live sessions, just-in-time access, view-only and invites |
| Small Teams | 15 | 25 | LDAP |
| Professional | 50 | 50 | Analytics: the overview, stats and sign-in history |
| Enterprise | Unlimited | Unlimited | Single sign-on, session recording, file transfer, guest links and offline licences |
- Administration on every tier - users, hosts, sessions, the audit log, features, updates and the licence page work on Community too.
- Caps - past a tier's limits, new users and hosts are refused; existing ones keep working. Recordings made under a tier with recording stay readable after a downgrade.
- Activation - activate with
--licence-idand--licence-key(or in setup), or import a licence on the dashboard's Licence page.
States
- Valid - in date and checked recently.
- Grace - expired, or not checked for 72 hours, within the licence's grace days. Nothing is refused yet; it is logged.
- Restricted - grace is over, or the licence was revoked: new sessions, users and hosts are refused.
Running sessions are never ended by a state change, and the local admin can always sign in.
Checks with the licensing server
A licensed server checks in at startup and then every 24 hours, so it needs outbound HTTPS to the KazLabs licensing server at least every 72 hours. Each check carries the user and host counts, a fresh nonce, a per-process boot ID and the SHA-256 of the running binary, and the licensing server's reply is signed: a fake or replayed reply changes nothing. Setting the system clock back does not extend a licence. Offline licences (Enterprise) need no checks and depend only on their expiry - use them for air-gapped sites.
The licensing server also reports an unofficial build or a licence running in two places at once; the server audits both and shows them on the Licence page.
Self-hosted & air-gapped
Dendrite always runs on your own infrastructure:
- Self-hosted - the server runs in your VPC or on bare metal as a systemd service behind nginx; agents and browsers connect over your network.
- Air-gapped - install from the all-architectures bundle (see Air-gapped install), and agents download from your own server. Run without a licence (Community) or with an Enterprise offline licence: both need no connection to KazLabs. Other licences check in with the licensing server at least every 72 hours (see Licensing & tiers).
- Telco / CII readiness - single sign-on and directory authentication, least-privilege session controls, session recording and tamper-evident audit logging are designed for regulated operators.
Contact sales@dendrite.sh to scope a deployment.
Security architecture
Dendrite is built around a simple rule: share a shell, not your secrets.
- Authenticated access - every participant signs in through single sign-on, a directory or a local account; the only exception is the Enterprise read-only guest link, which is off by default.
- Token security - one-time WebSocket tickets, 15-minute access tokens, and 30-day refresh cookies with rotation; sign-in attempts are rate-limited.
- Encryption - terminal data is encrypted with a per-session key and travels over TLS. So that you can sign in and resume a session from any browser, the server keeps the keys of sessions started from the dashboard in Redis, which requires a password and listens on localhost only. Keep TLS in front of the server.
- Host-side execution - commands run only on the host you chose; the server never executes commands on your behalf.
- Server-side enforcement - view-only, access grants, approvals and the file policy are enforced on the server, not by client behaviour, and access checks fail closed.
- Just-in-time access - access is granted per session and expires, avoiding standing access.
- Per-agent identity - every agent proves its own key on every connection, and any key can be revoked.
- Tamper evidence - audit outputs and session recordings are hash-chained and can be verified offline.
- Browser deterrents - blank on blur, clipboard guard, screenshot detection and session watermarks, each switchable at runtime. A browser cannot prevent operating-system screenshots, so detection means logging the attempt.
- Agents verify the server - agents pin the server's identity key, and the control stream is sealed (X25519, Ed25519, AES-256-GCM), so a proxy that ends TLS can neither read nor change commands. Admission stamps let the server turn away anything else with a bare 404.
- Signed releases - every release is signed (
upd-2026-02); installers check the signature andSHA256SUMSbefore running anything, and agents install a pushed update only when the signed release manifest lists it. - Licence integrity - release builds check the licence signature twice, independently, and trust only the keys they carry; the licensing server's replies are signed.
- No third-party defaults - the command-line client never falls back to a public relay, and the web interface loads nothing from outside servers. The SSHx MIT licence and open-source notices are behind the (?) on the sessions page.
- ISO 27001 alignment - the security documentation set covers controls alignment, a risk register, and a remediation plan.
Report security issues to security@dendrite.sh.
Troubleshooting
A host is missing from the vault
Check that the agent runs on the host (systemctl status dendrite-agent) and appears on Admin → Hosts. If its key was revoked, enrol it again with a new join token.
A join token doesn't work
Join tokens are single-use and short-lived by design. Generate a fresh one on the sessions page. With a self-signed server certificate, pass --trust-ca <fingerprint> and -k on the first download.
The installer fails behind a corporate proxy
For private CAs, set CURL_EXTRA='--cacert /path/to/ca.pem' before running the install command. Add --dry-run to see the plan; setup logs every command to /var/log/dendrite-setup.log.
dendrite update says SHA256SUMS is signed with key upd-2026-02
The host runs 0.17, which carries only the old release key. Install again from https://dendrite.sh/get, or run dendrite update once with DENDRITE_UPDATE_VERIFY_KEYS=upd-2026-02=T26unKGEfimO19d9aF55n0vgP8BRSj5IjPHkSXfEhX4= (see Updates).
A pinned agent refuses the server
The server's identity key changed, or the agent reached a server that does not prove its identity (an older one). Restore server-identity.key from your backup, or set the new fingerprint (shown on the Hosts page) in the agent's DENDRITE_SERVER_FINGERPRINT. Update servers before agents.
New users or hosts are refused
The server is at its tier's limits (5 users and 5 hosts without a licence), or its licence is restricted. The Licence page shows the state and the last check; a licensed server must reach the licensing server at least every 72 hours.
An update did not apply
A server that does not become healthy after an update is rolled back to the previous version. The error names the version that failed; see journalctl -u dendrite-server -n 50.
Sign-in fails
Run dendrite-server --identity-check and look at the Identity page for problems. The sign-in page shows an error code, and the failure reason is in the audit log. For LDAP, check that the user is in a group mapped to a role, or that the provider's default role is not none.
A collaborator can still type during view-only
View-only is enforced by the server. Check that the toggle applied in Terminal Settings, or that the person was made view-only in the participant list, and that the server runs 0.17 or later, where a per-user restriction survives reconnects.
Someone was disconnected from a session
Their access grant expired or was revoked, or they were kicked. The session.leave audit event gives the reason. Invite them again from the share panel to restore access.
A signed-in user can't open a command-line session
A known issue: on servers with sign-in enabled, sessions started from the command line show "does not exist" to signed-in users. Start the session from the dashboard instead.
A macOS agent opens no terminals
A known issue: on macOS, the agent starts terminals only when it runs as root. The installer's launch daemon runs it as root.
My terminal colours look wrong
Open Terminal Settings and pick a palette. Palettes are per-user and don't affect other participants.
FAQ
Do collaborators need to install anything?
No - a browser is enough. Only the hosts that run sessions need the agent.
What's in the free Community edition?
Up to 5 users and 5 hosts, live shared sessions and chat, just-in-time access, view-only and invites - free forever, and no licence needed. See Licensing & tiers and pricing.
Is Dendrite self-hosted?
Yes. Dendrite runs on your own servers, and can run fully air-gapped. For regulated and critical-infrastructure deployments, talk to sales@dendrite.sh.
Do you support single sign-on?
Yes, on Enterprise, through OpenID Connect: Microsoft Entra ID, Okta, Google Workspace, Keycloak, Auth0 and other OpenID Connect providers. SAML is not supported; use your provider's OpenID Connect interface, or LDAP.
Which platforms are supported?
The server runs on Linux (x86_64 and aarch64) with systemd. Agents run on Linux and macOS. Users need only a browser.