Files
Privatae 4a5c310482
ci / personal-data (push) Successful in 5s
ci / test (push) Successful in 4m6s
ci / fuzz (push) Failing after 2m58s
bastion 0.58.1
2026-10-29 21:20:20 +00:00

5.4 KiB

Tunnel and lanes

Tunnel

A userspace WireGuard tunnel (gVisor netstack) scoped so this machine can reach only its own host. No TUN device is created and run does not need root. The tunnel is dialled from this machine. Its WireGuard UDP port, on every address at a port the system picks when the tunnel comes up, drops every packet that does not come from the host's key. Inside the tunnel, the bastion accepts only on gated forwards: ollama (11434), an OpenAI-compatible server (18080) and a PII sterilizer (18081) under the inference capability family, the last two only when configured; one port per service-slot server (18100-18199); and one per coder run (18200-18299). Nothing else listens.

Git lane

A loopback HTTP proxy plus a git-credential helper, so your local git reaches git.privatae.ai through your host. privatae-bastion git-setup prints the three config lines: the proxy, a reset of the credential-helper chain for that URL (so no other helper caches your token on this machine), and the helper itself.

Worker lanes

Outbound-only reverse relays to the host: egress, management, harness, and session. The management lane runs a fixed-argv allowlist. Ops that touch your files are dedicated Go handlers with no shell. Two of them start a program: text extraction invokes a local tool named in a fixed table (pdftotext, ffprobe, and the like) from a fixed argv template, and the caller supplies only the tool's name and the document; dcn_grant_sync starts the local search agent's Python.

Session lane

An interactive terminal that runs on this machine and streams to the host over the same reverse WebSocket the other lanes use. No inbound port, no SSH server. It is your own act in your Terminal app, and needs no standing grant from the host (0.39.0): on a machine anchored to a host, session:open takes your approval from your device or this console, bound to that one session; on a local machine the pairing's caps are the authorization. A grant the host still presents is verified as before, and a deny in it refuses. session:open runs as an unprivileged user when the daemon is root. session:root runs as root only when the daemon is root and you approved that specific session here with the enrolled second factor. Every session is logged locally (output only) and recorded in the pairing's audit file. Not available on Windows.

Root commands (0.49.0). The anchor host can also run one command at a time as root, with no terminal and no approval per command, once you have granted it here:

sudo -H privatae-bastion root-exec grant --host <anchor host> --for 30d   # asks for the code enrolled here
privatae-bastion root-exec [--host <host>]                               # what is granted, until when
privatae-bastion root-exec revoke [--host <host>]                        # take it back

The grant names one host (the anchor; a member pairing never gets it) and this machine, ends at the time you chose, at most 30 days after you gave it (--for 31d is refused; grant again before it ends), and is written only by that command with the code; nothing the host sends can create, widen or extend it. A grant given before 0.55.0, when the longest term was a year, ends 30 days after it was given. Each command runs as /bin/sh -c in its own process group, with a deadline (10 minutes unless the host asks for less or more, never more than 8 hours) and with stdout and stderr returned up to a size cap (256 KiB each unless the host asks, at most 4 MiB); a cut output keeps its start and its end and says how many bytes were dropped. At the deadline the whole group gets SIGTERM, then SIGKILL 2 seconds later, so a child that ignores SIGTERM goes too. The bastion must run as root, the session family switched off here stops it, and a quarantined pairing runs nothing. These are checked again every 2 seconds while a command runs: a revoke, the grant's expiry, the switch or a quarantine stops a command already running, and its answer says why. Every command is written whole, with its output (up to 64 MiB), to the context store's sessions/<id>.log, and recorded at start and stop in the audit file with its sha256, exit and byte counts; a refused, malformed or failed one gets an audit row too. Not available on Windows.

The bastion also records where each root command connects. On Linux the command starts in a cgroup of its own, and an nftables table the bastion creates for that command records every connection the command and its children open; the table only records (it never allows or drops anything) and is removed when the command ends, or at the next start if the bastion died mid-command. Destinations outside the platform's own hosts are returned to the host as untrusted_connections, and the host then treats that output as untrusted and ends root for the turn. List the platform's own hosts in platform_hosts.json in the bastion's config folder:

{"hosts": ["2001:db8::/32", "203.0.113.0/24", "git.example.org"]}

With no list, every connection that leaves this machine counts as outside; loopback never does. A command whose connections could not be watched is reported as if it had connected out. What it cannot see: connections another program makes on the command's behalf, such as dockerd pulling an image for docker build, or a service systemctl starts. A root command can also leave its cgroup or delete the table on purpose; a missing table reads as "not watched", but a command that leaves its cgroup is not detected.