Server (harnessd)

Hardening checklist

A checklist for running harnessd in production, from the VM and network to accounts, secrets and sandboxes.

Use this list when you first deploy harnessd and when you review it. harnessd check enforces the items marked enforced; the rest are up to you.

The host#

  • A dedicated VM for SI Fabrik. Nothing else runs there.
  • Rootless Docker for the harness user (DOCKER_HOST=unix:///run/user/<uid>/docker.sock). Membership in the docker group is root-equivalent.
  • The hardened systemd unit from deploy/harnessd.service: ProtectSystem=strict, NoNewPrivileges, an empty capability set, a system-call filter, UMask=0077, and /etc/harness read-only. Keep its SystemCallFilter=setpriority and SystemCallErrorNumber=EPERM lines; without them the server is killed with SIGSYS.
  • ExecStartPre=harnessd check, so a failing configuration never serves.
  • OS and Docker updates applied; agent images rebuilt regularly.
  • A current harnessd. Before per-task sandbox networks, every sandbox shared one network on which the server's bridge address was reachable, including sshd.

The network#

  • listen on loopback (127.0.0.1:8443), reached through an SSH tunnel. See Connect the app to a server. Enforced: no 0.0.0.0 without allow_all_interfaces.
  • tools_listen on the Docker bridge on Linux (for example 172.17.0.1:7879), never every interface (enforced). On Linux, harnessd warns if it's loopback, because containers can't reach it there. On a Mac with Docker Desktop, 127.0.0.1 is right. Sandboxes themselves can't reach the server's addresses: their task networks have no address for the host, and only the egress proxy forwards to the tools listener.
  • A cloud firewall or security group that opens only SSH (22), restricted to the admins' addresses (/32) or your VPN.
  • The API port isn't reachable from the internet. If 8443 is open anyway, its source is restricted to specific IPs.

TLS#

  • TLS 1.3. Enforced, unless loopback behind a proxy. A self-signed certificate is fine through the SSH tunnel.
  • tls.key at mode 0600. Enforced.
  • The fingerprint (harnessd fingerprint) handed to users over a trusted channel, and again after every certificate renewal.

Accounts#

  • At least one admin. Enforced.
  • auth.require_totp_for_admins: true, and TOTP for every admin. Enforced when on.
  • TOTP for operators too.
  • Least privilege: viewer for people who only watch, operator for people who run tasks, few admins.
  • session.absolute_hours of 12 or less; harnessd check warns above 24.
  • Departing users disabled or removed (harnessd user disable <name>).

Secrets#

  • secrets.env and every projects[].secrets_file at mode 0600, owned by harness. Enforced.
  • A fine-grained HARNESS_GITHUB_TOKEN limited to the project repositories, with Contents and Pull requests read/write only.
  • A LiteLLM virtual key with a budget as HARNESS_GATEWAY_API_KEY, or per-project keys.
  • Deploy keys per repository rather than a personal SSH key.
  • No secret values in any WORKFLOW.md.

Projects and sandboxes#

  • Every project sandboxed (Docker or Cloudflare), with no host hooks, docker.extra_args, docker.network or writable root. Enforced by the sandbox floor: such a project pauses with sandbox_policy.
  • sandbox.network.mode: allowlist (the default) everywhere; short allow lists.
  • linked_tasks_state left at Backlog, so people approve tasks filed by other projects' agents.
  • Host-wide limits: set: max_concurrent_agents and daily_budget_usd.
  • Per-project limits: with a cost cap per task.

Monitoring#

  • harnessd audit verify run regularly, and the audit log shipped off the host.
  • The journal watched for level=error lines, such as credentials_rejected and failed preflights.
  • Failed-login spikes (bad_credentials, locked_out) alerted on.

See also#