Skip to content

Security Model

What this protects against, and what it does not.

The command parser is the attack surface

Reachable by any stranger on nine networks. Deliberately tiny: twelve commands, none privileged, everything else silently dropped.

Notes are flattened (control characters stripped, whitespace collapsed, truncated at 200 characters) before they can reach an outbound message, so a note cannot inject extra lines into a broadcast. Endpoint addresses reject control characters.

Administration is not on the network

There is no admin command, no socket, no API. partylinepagerctl requires host access. Host access is the admin credential.

Full image exception

The full image exposes an admin terminal via ttyd, reachable over the LAN. This is gated by HTTP basic auth (TTYD_CREDENTIAL) and optionally a self-signed cert, not by host access. The compose deployment is unaffected and keeps the original guarantee.

The Docker socket is root

The party line hooks drive docker compose, and the compose file hands the daemon /var/run/docker.sock. Anyone who compromises the bot owns the box.

There is no escape from this: the docker group is root, a sudo rule is root, a privileged helper is root.

Mitigation: point [provider.tor] at a script that reaches a separate host over SSH. The hook contract doesn't care how.

A web-only instance needs no Docker socket: leave compose/docker-compose.docker-socket.yml and the provider overlays out of your list. See Compose Files.

Web room security

A web room has no second factor. The URL is the credential.

  • Default: 160 bits of OS CSPRNG (won't be guessed)
  • static_slug: trades that guarantee for a stable link. Treat it as public to anyone who has ever seen it.
  • The room is hosted by whoever runs base_url. The default public MiroTalk instance sees who connects. Self-host if that matters.
  • The partyline pager cannot evict anyone: the TTL frees the instance to open the next room, it doesn't end the call.

Credentials are inline by default

The broadcast carries the onion address and secret (or room URL) in the message body. Every subscriber's chat provider sees them.

If your threat model doesn't survive that:

  • Use creds_delivery = "link" for one-time Yopass links
  • Run with signups = "closed" and a Matrix-only roster (E2EE)
  • Prefer Matrix for carrying secrets

Secrets

256 bits from the OS CSPRNG, rendered as base32 (no case ambiguity, no 0/O confusion). Generated by the partyline pager, not by each hook, so secret quality is decided in one audited place.

  • Passed to hooks on stdin
  • Written by the shipped hook with umask 077
  • Deleted at teardown along with the onion key

Metadata

The roster is a social graph in a flat file on a host reachable from nine chat networks. Consider what that file looks like after a breach.

Quiet-hour drops are deliberately not recorded, so the daemon never accumulates a list of who was asleep at 03:00.

Not Arti

The Tor Project does not recommend Arti's onion services for production use or anything requiring privacy. The hook drives the C Tor daemon.