Repository Public
1# @cr/server23Fastify server of Claude Remote: authentication, REST + WebSocket API under `/api/v1`, static web4app, Postgres. It never spawns processes and never touches project files: everything goes to5`apps/runtime` over its unix socket. See `docs/CONTRACT.md` (binding) for the API table.67## Environment89| Variable | Default | Notes |10|---|---|---|11| `NODE_ENV` | development | `production` enforces the four requirements marked * |12| `CR_PORT` / `CR_HOST` | 8620 / 127.0.0.1 | |13| `CR_PUBLIC_ORIGIN` * | http://localhost:8620 | exact origin; https in production. Cookie scope, Origin check, WebAuthn RP ID (= hostname) |14| `DATABASE_URL` * | postgres://localhost/claude_remote_dev | |15| `CR_SECRET_KEY` * | dev: generated into `$CR_DATA_DIR/dev-secret-key` | 64 hex chars; AES-256-GCM key for TOTP secrets |16| `CR_RUNTIME_TOKEN` * | – | ≥ 32 chars, same value as the runtime |17| `CR_RUNTIME_SOCKET` | `$CR_DATA_DIR/run/runtime.sock` | |18| `CR_DATA_DIR` | `~/claude-remote-data` | holds `setup-token` (0600) until the administrator exists |19| `CR_WORKSPACE_ROOTS` | `~/workspace` | `label=/abs` or `/abs`, comma separated; must match the runtime |20| `CR_TRUSTED_PROXIES` | `127.0.0.1,::1,10.67.0.1` | IPs/CIDRs whose `X-Forwarded-For` is honoured (tunnel gateway) |21| `CR_EXEC_HOSTS` | empty | node aliases allowed as task `host` (besides `local`) |22| `CR_TERM_BUFFER_BYTES` | 4 MiB | WebSocket backpressure high-water mark |23| `CR_AGENTD_URL` / `CR_AGENTD_TOKEN` | – | cluster telemetry; unset ⇒ host-only overview, health UNKNOWN |24| `CR_NODE_ALIAS` | runtime hostname (short) | alias of this host in agentd (`isHost`, edges) |25| `CR_LOCAL_MODELS` | – | JSON array `{id,name,host,engine,endpoint}`; unset ⇒ empty list |26| `CR_SKILLS_REGISTRY_URL` | – | e.g. `https://skills.spboucher.ai`; unset ⇒ `{connected:false}` |27| `CR_WEB_DIR` | `../web/out` | static export; missing ⇒ API only |28| `CR_LOG_LEVEL` | info | pino JSON |2930## Runtime integration notes3132- Runtime errors with status 400/403/404/409/413/415/422/429/507 are forwarded to the browser with33 their `code`, `message` and `details` (e.g. `PUT /projects/:id/file` → 409 `conflict` with34 `details: {sha256, mtimeMs}` of the file on disk; upload → 409 `exists`).35- `PUT /projects/:id/file` answers a full `FileContent` (the server adds `content`).36- `POST /projects/:id/upload?path=&overwrite=1` — without `overwrite=1` an existing destination is a37 409 `exists`; parent directories are created.38- `GET /projects/:id/git/diff?path=&staged=1` → `{diff, truncated}`; `GET /projects/:id/search` accepts `path=`.39- `GET /mcp` lists the configuration only (status `unknown`, fast). `?live=1` adds live statuses40 (`claude mcp list`, up to 45 s cold, cached 60 s by the runtime); `&refresh=1` bypasses the cache.41- `POST /skills/op`: `install` = `{projectId, path, replace?}` (copied into the user's skills);42 `enable|disable|remove` = `{name, kind, scope, projectId?}` (`projectId` required for project scope).43- `session.exit` without an exit code (tmux session vanished) is resolved against the runtime:44 gone ⇒ RECOVERABLE (STOPPING ⇒ STOPPED), exactly like the reconciler.4546## Run4748```bash49pnpm --filter @cr/shared build && pnpm --filter @cr/server build50DATABASE_URL=… node dist/migrate.js # applies migrations/*.sql (idempotent, advisory-locked)51node dist/main.js # also migrates on start52```5354First run: read the one-time token on the host (`cat $CR_DATA_DIR/setup-token`; it is never55logged), open the site, create the administrator, then enrol TOTP or a passkey. A second factor56is mandatory: until one exists the session can only reach the enrolment endpoints.5758## Security model in one page5960- Route auth levels (`config.auth`, default `full`): `public` (GET /health only), `preauth`61 (auth/status, setup, login, passkey login), `restricted` (valid session, second factor62 pending: status, logout, TOTP step, enrolment), `full`. One global `onRequest` guard63 (`src/security/guard.ts`) enforces them, also for WebSocket upgrades. `http.test.ts` iterates64 the live route table and fails if a route is reachable unauthenticated without being listed.65- Mutations: `X-CSRF-Token` (from `GET /auth/status`) + `Origin` = `CR_PUBLIC_ORIGIN`.66 Pre-auth POSTs: Origin + `application/json`.67- Sessions: random 32-byte cookie token (`__Host-cr_sid`; `cr_sid` only outside production on68 http://localhost), sha256 stored, idle 12 h, absolute 30 d, 15 min before the second factor,69 rotated on upgrade.70- Request rate limits (per minute, `config.rateLimit`, route option `config.rate`): credential71 checks (`/auth/login`, `/auth/setup`, `/auth/totp`, passkey login options/verify) share a strict72 per-IP bucket (20); anonymous `GET /auth/status` has its own per-IP bucket (240) and counts in73 the caller's session bucket when the cookie is a valid session; `GET /health` has its own per-IP74 bucket (600); everything else is per session cookie (600). `rate-limit.test.ts`.75- Throttling in `login_attempts`: user 5, IP 10, global 100 failures / 15 min; lock 60 s doubling76 per extra failure (cap 15 min). A correct password alone never clears the counter.77- WebAuthn challenges are server-side and single-use (5 min): in `auth_sessions.pending` with a78 session, otherwise in `preauth_challenges` keyed by the hash of the `__Host-cr_pre` cookie.79- Lockout responses: HTTP 429, `Retry-After`, body `{error:{code:'locked',details:{retryAfterSec}}, ok:false, next:'done', retryAfterSec}`.80- Not implemented in this milestone: Web Push (setting stored only), installing from the skills81 registry (501).8283## Tests8485```bash86npx vitest run # needs local Postgres with createdb/dropdb on PATH87```8889Each test file creates its own database `cr_test_<random>`, migrates it and drops it afterwards;90`src/integration/real-runtime.test.ts` starts the REAL runtime (child process, own tmux socket,91needs `tmux` and `claude` on PATH; never sends a prompt, never touches `~/.claude`); for the other files92the runtime is `src/testkit/fake-runtime.ts` (Fastify on a temp unix socket: echo PTY attach,93`/events`, in-memory files). `src/testkit/soft-authenticator.ts` is a software passkey (ES256,94real signatures) used to exercise WebAuthn verification.95