Gitea - lien « Code source » hardcodé (REPO_SOURCE_URL) vers le dépôt, toujours visible - retrait complet du réglage configurable (api.ts, route+store settings, SettingsView, i18n, help, tests) Découverte des dépôts - aucune racine de scan par défaut → pas de scan au premier démarrage (clean install) - corrige la découverte des dépôts à l'INTÉRIEUR d'une racine qui est elle-même un repo (depth 0 = conteneur de scan, on descend ; depth > 0 = feuille) Sécurité « enterprise-deployable » - en-têtes HTTP durcis (CSP, X-Frame-Options, nosniff, Referrer-Policy, HSTS conditionnel, no-store API), header Server retiré - permissions DB 0o600 / dossier de données 0o700 ; error handler sanitisé ; garde Content-Type sur les mutations - chiffrement au repos AES-256-GCM des secrets (server_secret, clé privée VAPID) via SecretBox - journal d'audit (migration #7) + endpoint /audit-logs + RGPD export/effacement + UI Réglages - SECURITY.md, docs/ENTERPRISE_DEPLOYMENT.md, sections README EN/FR, SBOM CycloneDX en CI CI - pack-smoke packe depuis le contexte du package (cd packages/server) au lieu de « npm pack -w » : corrige l'embarquement de @arboretum/shared dans le tarball Purge des données personnelles du dépôt public - suppression de spikes/s4-discovery/result.json, reformulation du VERDICT - chemin de test générique, anonymisation des 5 fixtures de dialogues
4.4 KiB
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.tsrefuses 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_atis tracked. Review and rotate tokens periodically. - Encryption-at-rest key management. With no
ARBORETUM_SECRET_KEYset, the encryption key lives indataDir/secret.key(0o600) next to the database — this protects a leaked database copy (backup, WAL) but not a fulldataDircompromise. For strong protection, setARBORETUM_SECRET_KEYand store it separately from database backups. Full-DB SQLCipher is intentionally avoided (it breaks thenpxprebuilt 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.