diff --git a/docs/operations.md b/docs/operations.md index 03e404c..acfa0f2 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -4,7 +4,7 @@ What reading byre's code won't tell you: external setup, vendor and platform qui ## Data -State lives in the directory given by `byre serve --data`, by default `~/Library/Application Support/byre`: `byre.db` (SQLite, WAL), task worktrees under `worktrees/`, uploaded images under `blobs/`. Task branches are `byre/` in the project's repo. +State lives in the directory given by `byre serve --data`, by default `~/Library/Application Support/byre`: `byre.db` (SQLite, WAL), task worktrees under `worktrees/`, uploaded images under `blobs/`. Task branches are `byre/` in the project's repo. This directory is private to the service's OS user (0700), including on existing installations: it contains transcripts, credentials and local agent listener bearer URLs. Other processes running as that same OS user remain trusted. Migrations are the files in `internal/store/migrations/` (`NNN-name.sql`), plus Go data migrations in `goMigrations`. The number only orders; a migration is identified by its name. A migration's SQL is fixed once applied anywhere: write it out in full rather than sharing Go constants that later change. byre is deployed from task branches that each add their own migrations, so after a merge a migration can land before others the database has already applied, and it still runs. Give a new migration a name no other branch will use, and make any data migration safe to run twice. `sqlite3 byre.db 'SELECT * FROM migrations'` lists what has been applied. @@ -26,7 +26,7 @@ Tailscale identifies the connecting Mac, including requests from a web page open A new node needs signing in: `curl /api/tailnet` (`byre check` prints the loopback address) shows `NeedsLogin` and the `auth_url`, and the app on the same Mac shows it as **Sign in to Tailscale**. The tailnet must have MagicDNS and HTTPS certificates turned on (the DNS page of Tailscale's admin console); without certificates byre logs `tailnet has no https certificates` and serves only the loopback address until it is restarted. -Each preview gets its own port on byre's node rather than a path under byre's origin, since path-prefix proxying breaks apps that use absolute paths. +Each preview gets its own port on byre's node rather than a path under byre's origin, since path-prefix proxying breaks apps that use absolute paths. Previews use the same user-only tailnet trust boundary as the API; tagged nodes are excluded. ## Releases diff --git a/internal/agents/preview.go b/internal/agents/preview.go index 97636c6..6da5472 100644 --- a/internal/agents/preview.go +++ b/internal/agents/preview.go @@ -131,6 +131,10 @@ func (m *Manager) servePreview(port int) error { http.Error(w, err.Error(), http.StatusForbidden) return } + if who.Node.IsTagged() { + http.Error(w, "tagged nodes are not users", http.StatusForbidden) + return + } r.Header.Set("Tailscale-User-Login", who.UserProfile.LoginName) r.Header.Set("Tailscale-User-Name", who.UserProfile.DisplayName) proxy.ServeHTTP(w, r) diff --git a/internal/server/server.go b/internal/server/server.go index 7513768..1910948 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -42,10 +42,15 @@ type Server struct { type ctxKey struct{} func New(dataDir string, portFirst int, ts *tsnet.Server) (*Server, error) { - err := os.MkdirAll(dataDir, 0o755) + err := os.MkdirAll(dataDir, 0o700) if err != nil { return nil, err } + // Also protect existing installations: descendants include transcripts, + // credentials and the bearer URLs used by local agent listeners. + if err := os.Chmod(dataDir, 0o700); err != nil { + return nil, err + } terminals, err := terminal.New(filepath.Join(dataDir, "zsh")) if err != nil {