Files
arboretum/SECURITY.md
Johan LEROY 7bb274446c feat: clean install (Gitea en dur, scan off) + durcissement sécurité entreprise
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

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 16:44:25 +02:00

62 lines
4.4 KiB
Markdown

# 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`](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.