Files
arboretum/SECURITY.md
Johan LEROY 65ef616867
Some checks failed
CI / Build & test (Node 24) (push) Has been cancelled
CI / Pack & boot smoke (Node 22) (push) Has been cancelled
CI / No em/en dashes (push) Has been cancelled
CI / Build & test (Node 22) (push) Has been cancelled
Deploy site (production) / build-and-deploy (push) Successful in 20s
chore(typo): retire tous les tirets cadratins/demi-cadratins + garde CI
Remplace les 547 tirets cadratins (U+2014) et demi-cadratins (U+2013) des fichiers versionnés par la ponctuation contextuelle adaptée (point médian, deux-points, virgule, parenthèses ; tiret simple pour les plages), sur 122 fichiers (appli, vitrine, doc, tests, workflows, scripts).

Ajoute le job CI « lint-dashes » (git grep -P) qui échoue si un tiret cadratin/demi-cadratin réapparaît, hors logo binaire et captures brutes du terminal (fidélité des fixtures de détection de dialogue).
2026-07-17 16:44:00 +02:00

4.4 KiB

Security Policy

Arboretum is a self-hosted daemon that serves a web dashboard to drive git worktrees and the Claude Code sessions running on them. A web terminal is remote code execution by design: that is the product, not a bug. Arboretum's security model is therefore built on structural guards (loopback-only binding, authenticated access, strict Origin checks) far more than on cryptography alone.

This document describes the threat model, the controls that are implemented, the deliberate trade-offs, and how to report a vulnerability. For hardening a deployment in a regulated environment, see docs/ENTERPRISE_DEPLOYMENT.md.

Threat model

  • Single-user by design. Arboretum runs on the owner's machine and is meant for one operator. There is no multi-tenant isolation and no RBAC, and none is claimed.
  • Loopback by default. The server binds 127.0.0.1; config.ts refuses any non-loopback bind unless you pass --i-know-this-exposes-a-terminal. Remote access is expected via Tailscale Serve (TLS + tailnet identity), never by opening a port.
  • The terminal is RCE. Anyone who can authenticate can run code. The controls below exist to make sure only the authenticated operator reaches it, and that the surrounding surface (cookies, headers, data at rest) is hardened.

Implemented controls

Area Control Where
Network Loopback-only default; non-loopback refused without explicit flag packages/server/src/config.ts
AuthN Global guard on all /api/** and /ws (only login is public, and rate-limited) packages/server/src/app.ts
AuthN Tokens stored hashed (SHA-256), compared in constant time; bootstrap token shown once packages/server/src/auth/service.ts
Sessions Cookie is an HMAC-SHA256 signed payload, HttpOnly + SameSite=Strict, Secure when HTTPS packages/server/src/routes/auth.ts
CSRF / WS Strict Origin check on every /api/** and /ws request (anti cross-site WS hijacking) packages/server/src/app.ts
CSRF Mutations carrying a body must be application/json packages/server/src/app.ts
Rate limit Global login rate limit with exponential backoff (not per-IP, Tailscale fronts everything as 127.0.0.1) packages/server/src/auth/service.ts
HTTP headers CSP, X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy, Permissions-Policy, conditional HSTS, Cache-Control: no-store on API; Server header stripped packages/server/src/app.ts
Injection All SQL is parameterized; all git calls use execFile (no shell); path-traversal guards packages/server/src/**
Data at rest DB file/dir forced to 0o600/0o700; sensitive secrets (server secret, VAPID private key) encrypted with AES-256-GCM packages/server/src/db/index.ts, core/secret-box.ts
Audit Persistent audit log of sensitive mutations (tokens, settings, secrets, push, groups) packages/server/src/core/audit-log.ts
Privacy GDPR export (/api/v1/data/export) and erasure (/api/v1/data/delete-my-data) packages/server/src/routes/data.ts
Install Runs as a user service (systemd user unit / launchd LaunchAgent), never root packages/server/src/cli/install.ts

Deliberate trade-offs

  • Long-lived API tokens. Tokens do not expire by age (CLI automation stability) but can be revoked instantly, and last_used_at is tracked. Review and rotate tokens periodically.
  • Encryption-at-rest key management. With no ARBORETUM_SECRET_KEY set, the encryption key lives in dataDir/secret.key (0o600) next to the database: this protects a leaked database copy (backup, WAL) but not a full dataDir compromise. For strong protection, set ARBORETUM_SECRET_KEY and store it separately from database backups. Full-DB SQLCipher is intentionally avoided (it breaks the npx prebuilt portability).
  • No multi-user model. If you need multiple operators with isolation, Arboretum is not the right tool.

Reporting a vulnerability

Please report security issues privately: do not open a public issue.

  • Email: security@johanleroy.fr (or contact@johanleroy.fr).
  • Include a description, affected version, and reproduction steps.
  • Expect an acknowledgement within 7 days and a coordinated disclosure window of up to 90 days.

Thank you for helping keep Arboretum users safe.