Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/deploy-worker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,16 @@ jobs:
done
pnpm exec wrangler deploy --secrets-file "$secrets_file"

- name: Repeat tenant isolation backfill
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
run: |
for attempt in 1 2 3; do
pnpm deploy:tenant-backfill && exit 0
sleep $((attempt * 5))
done
pnpm deploy:tenant-backfill

- name: Ensure domains
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_DNS_API_TOKEN }}
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@
- Add atomically claimed recurring card intervals with constant-time catch-up, crash-recoverable leases, scheduler/API proof, and coalescing for active or capacity-blocked runs, thanks @Jhacarreiro.
- Reuse `@openclaw/libterminal` for terminal protocol codecs, Worker relays, Ghostty assets, and browser hub transport while keeping Crabfleet authorization and session policy local.
- Update `@openclaw/libterminal` to 0.3.1 for terminal lifecycle, Worker asset generation, and package-validation fixes.
- Add private-by-default tenant isolation for cards and sessions, trusted-proxy automatic onboarding, stable owner identities, expiring named viewer/controller grants with UI/API revocation, and current authorization checks across terminal, desktop, diagnostics, checkpoints, logs, transcripts, metadata, cleanup, and child-session paths.
- Revalidate named principals against current authentication policy, migrate only unambiguous legacy subjects, reject subjectless private control approvals, and scope Fleet policy totals to visible sessions.
- Bridge the tenant-isolation cutover with legacy-writer triggers and an idempotent post-deploy backfill, retaining the triggers until a later rollout has drained old Worker requests.
- Keep teardown revocation available, bind OpenClaw replay identity and Sandbox refresh to stable owners, conceal hidden terminal state, and render destructive controls only from server-computed per-session authority.
- Keep shared-mode live terminal reads behind control or a named grant, revision-fence concurrent grant revocation, and preserve owner-validated legacy OpenClaw replays.
- Bind OpenClaw crabboxes and GitHub Actions sessions to explicit stable human owners in private tenancy, retain service authority only across validated OpenClaw lineage, and keep bootstrap ownership stable across token rotation.
- Fix local Vite development with `@openclaw/libterminal` by reserving Worker-served Ghostty aliases for production builds and serving the local WASM, icons, and logo without generated placeholders.
- Keep the mobile navigation and named-access dialog within narrow viewports.
- Keep each agent credential scoped to its authenticated session and direct children instead of inheriting access to every session owned by the same human.
- Fix automated Worker deployments by converging the app Custom Domain with the DNS-scoped deployment token instead of requiring zone-route access from the Worker token.

## 0.2.0 - 2026-06-15
Expand Down
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Crabfleet gives OpenClaw maintainers a fleet dashboard where every Codex crabbox
- **Diff previews.** Card tiles show changed files and totals; the run drawer shows a compact Codiff-style patch view.
- **Multi-runtime policy.** Auto-select between the Container and Crabbox adapter surfaces based on card overrides, repo workflow defaults, and task requirements.
- **Allowlist controls.** Restrict access to OpenClaw org members and specific repos through admin-managed allowlists.
- **Private tenant isolation.** New deployments show each user only their own cards and sessions unless the owner creates a named grant, a delegated-control lease, or a public read-only link.
- **Session history.** D1-backed card/run events plus periodically refreshed R2 event, transcript, and summary snapshots with terminal finalization guarantees.
- **Repo workflow config.** Owners can evaluate `CRABBOX.md` per repo and use it for runtime and merge defaults.

Expand Down Expand Up @@ -89,10 +90,10 @@ POST /api/openclaw/action-sessions
Authorization: Bearer CRABBOX_OPENCLAW_TOKEN
Content-Type: application/json

{"workKey":"openclaw/crabfleet:pr:42","workKind":"pr_repair","repo":"openclaw/crabfleet","branch":"fix/pr-42","sourceUrl":"https://github.com/openclaw/crabfleet/pull/42","runUrl":"https://github.com/openclaw/crabfleet/actions/runs/123","purpose":"repair PR 42","summary":"starting repair"}
{"workKey":"openclaw/crabfleet:pr:42","workKind":"pr_repair","repo":"openclaw/crabfleet","branch":"fix/pr-42","owner":"operator@example.test","sourceUrl":"https://github.com/openclaw/crabfleet/pull/42","runUrl":"https://github.com/openclaw/crabfleet/actions/runs/123","purpose":"repair PR 42","summary":"starting repair"}
```

The response contains `{session, agentToken, runnerPtyUrl, browserUrl}`. `runnerPtyUrl` includes the rotated session-scoped query credential and works directly with Node's global `WebSocket`:
The response contains `{session, agentToken, runnerPtyUrl, browserUrl}`. Private-tenancy deployments require `owner` to resolve to one active Crabfleet user; the stable subject owns browser visibility while the OpenClaw service retains lifecycle authority for its session. `runnerPtyUrl` includes the rotated session-scoped query credential and works directly with Node's global `WebSocket`:

```js
const terminal = new WebSocket(runnerPtyUrl);
Expand Down Expand Up @@ -149,6 +150,7 @@ merge:
- Bootstrap token for admin setup and recovery
- Short-lived D1-backed sessions; users reauthenticate after expiry
- Role-based access control (owner, maintainer, viewer)
- Private-by-default tenant isolation with time-limited named viewer/controller grants

## Deployment

Expand Down Expand Up @@ -197,6 +199,8 @@ The Crabbox namespace cutover intentionally has no old-name compatibility. Exist
- `CRABFLEET_TRUSTED_PROXY_PUBLIC_ORIGIN` – Optional browser-visible HTTPS origin required on mutations and WebSocket upgrades; defaults to `CRABFLEET_TRUSTED_PROXY_ORIGIN`
- `CRABFLEET_TRUSTED_PROXY_SECRET` – Shared secret required on `X-Crabfleet-Proxy-Secret` for trusted reverse-proxy identity
- `CRABFLEET_TRUSTED_USER_HEADER` – Optional trusted identity header name, default `X-Authenticated-User`; the proxy must remove caller-supplied copies before injecting it
- `CRABFLEET_TRUSTED_PROXY_AUTO_ROLE` – Optional `viewer` or `maintainer` role for valid trusted-proxy identities without individual allowlist entries; other values fail closed. Use `maintainer` when every authenticated tenant should be able to create its own work.
- `CRABFLEET_TENANCY_MODE` – Optional `private` or `shared`; defaults to `private`. Private mode scopes cards and sessions to their stable owner subject plus explicit, unexpired session grants. Bootstrap token rotation preserves one stable bootstrap owner subject. `shared` restores the legacy team-wide visibility model and should be an intentional deployment choice.
- `GITHUB_CLIENT_ID` – GitHub OAuth app client ID (optional)
- `GITHUB_CLIENT_SECRET` – GitHub OAuth app secret (optional)
- `GITHUB_REDIRECT_URI` – Optional authoritative GitHub OAuth callback URL; when set it must be an absolute HTTPS URL with no credentials, query, or fragment and the exact `/auth/github/callback` path. Requests on another host restart login on this configured origin. When absent, the callback defaults to the HTTPS request origin (or literal-loopback HTTP for local development).
Expand Down Expand Up @@ -368,7 +372,7 @@ curl -fsS https://crabfleet.openclaw.ai/api/openclaw/crabboxes \
-d '{"owner":"@steipete","repo":"openclaw/crabfleet","prompt":"prep the meeting follow-up"}'
```

The created crabbox appears in the fleet grid under the requested owner. Provisioning follows normal interactive-session routing: built-in Sandbox for Container or the versioned adapter for Crabbox.
The created crabbox appears in the fleet grid under the requested owner. In private tenancy, `owner` must resolve to one active Crabfleet user by login, email, or stable subject. Provisioning follows normal interactive-session routing: built-in Sandbox for Container or the versioned adapter for Crabbox.

### Project Structure

Expand Down Expand Up @@ -412,6 +416,8 @@ Full documentation available at [docs.crabfleet.ai](https://docs.crabfleet.ai):

- All state-changing operations require authentication
- Repo operations require allowlist membership
- Cards and sessions are tenant-private by default; global roles do not bypass another tenant's session boundary
- Named session grants are owner-managed, time-limited, and independently scoped to read-only or terminal-control access
- Merge policy is stored as intent; Crabfleet does not currently perform merges
- Runtime tokens are scoped and short-lived
- Secrets never logged or stored in D1/R2
Expand Down
37 changes: 32 additions & 5 deletions docs/admin.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ The Admin drawer manages user/team access, enabled repos, card policy defaults,

- Create and move cards.
- Start/pulse/stall card attempts.
- Create, attach, share, control, delete, and clean up visible interactive sessions.
- Create cards and interactive sessions for their tenant.
- Attach, share, control, delete, and clean up sessions they own or can access at the required level.
- Take over active card attempts when the runtime descriptor advertises takeover.

Maintainers cannot edit org policy or allowlists.
Expand All @@ -33,9 +34,29 @@ Maintainers cannot edit org policy or allowlists.
- Read Board and Fleet state.
- Open session logs.
- Use a public session share link.
- Use a current named viewer or controller grant.
- Request delegated terminal control where enabled.

Viewers cannot create cards or sessions or mutate policy. Terminal subscription additionally requires session ownership, a valid share token, or an approved control grant.
Viewers cannot create cards or sessions or mutate policy. Terminal subscription additionally requires session ownership, a current named grant, a valid share token, or an approved control lease.

## Tenant Isolation

`CRABFLEET_TENANCY_MODE` defaults to `private`.

In private mode:

- cards are visible only to their stable owner subject;
- sessions are visible only to their owner, a user with an unexpired named grant, or the current delegated controller;
- `maintainer` and `owner` roles do not bypass another tenant's card or session boundary;
- only the session owner can manage named grants, sharing, checkpoints, metadata, and lifecycle;
- an exact internal service creator retains lifecycle and terminal authority over its own session and validated descendants in that service-owned OpenClaw lineage, without granting another human tenant visibility;
- named `viewer` grants allow state, logs, transcript, and read-only terminal output;
- named `controller` grants add terminal input, diagnostics, clipboard, and desktop access;
- a public share link remains read-only and can be disabled independently.

Named owners and grants resolve only users admitted by the current allowlist or configured trusted-proxy automatic role. A stale `users.allowed` value does not authorize a principal. Private mode never falls back to mutable legacy owner/controller labels; an unresolved legacy control request must be denied rather than approved.

Set `CRABFLEET_TENANCY_MODE=shared` only to retain the legacy team-wide visibility model. Shared mode keeps role-based maintainer/owner management semantics.

## Access Control

Expand Down Expand Up @@ -64,7 +85,7 @@ GitHub OAuth refreshes org/team membership at login. The strongest matching role
owner > maintainer > viewer
```

Trusted-proxy assertions cannot claim team entries; proxy users need a direct login/email allowlist entry.
Trusted-proxy assertions cannot claim team entries. Proxy users need a direct login/email allowlist entry unless `CRABFLEET_TRUSTED_PROXY_AUTO_ROLE` is configured.

### Repositories

Expand Down Expand Up @@ -169,6 +190,7 @@ Optional:

- `CRABFLEET_TRUSTED_PROXY_PUBLIC_ORIGIN`
- `CRABFLEET_TRUSTED_USER_HEADER` (default `X-Authenticated-User`)
- `CRABFLEET_TRUSTED_PROXY_AUTO_ROLE` (`viewer` or `maintainer` only)

The proxy must:

Expand All @@ -179,7 +201,7 @@ The proxy must:
5. inject `X-Crabfleet-Proxy-Secret`;
6. preserve browser `Origin`.

Crabfleet requires exact backend origin and constant-time shared-secret proof, then applies the normal direct allowlist and role. Unsafe methods and WebSocket upgrades also require the exact browser-visible origin. Missing, partial, malformed, or unexpected assertions fail closed.
Crabfleet requires exact backend origin and constant-time shared-secret proof, then applies the normal direct allowlist and role. When `CRABFLEET_TRUSTED_PROXY_AUTO_ROLE` is set, a valid asserted identity that has no matching allowlist entry is admitted with that role and persisted as an active user; an allowlist match still wins. `owner`, malformed, and unexpected automatic-role values fail closed. Unsafe methods and WebSocket upgrades also require the exact browser-visible origin. Missing, partial, malformed, or unexpected assertions fail closed.

Authenticated proxy requests have asserted identity, proxy secret, local cookie, `Authorization`, and `Proxy-Authorization` stripped before app or terminal routing. Service-token routes keep their own scoped auth model.

Expand All @@ -192,10 +214,15 @@ Proxy-only identity cannot link SSH keys. Use a separate OAuth-capable origin th
`CRABBOX_BOOTSTRAP_TOKEN` is owner break-glass access for initial setup or OAuth recovery.

- Session lifetime: one hour.
- Tenant ownership uses one stable bootstrap subject, so rotating the token does not orphan bootstrap-owned cards or sessions.
- Store in 1Password.
- Do not use for routine onboarding.
- Open `/app?auth=token` when GitHub auto-login is enabled.

The tenant-isolation migration backfills stable card owners, session owners, and delegated-control subjects only when a legacy subject, login, or email resolves to exactly one active tenant subject. Rotated bootstrap rows collapse to `bootstrap:owner`; ambiguous or unresolved legacy actors remain unbound and fail closed in private mode.

The migration installs compatibility triggers for writes from the previous Worker during cutover. The standard deploy command and GitHub workflow deploy the new Worker and run `pnpm deploy:tenant-backfill`, but deliberately leave those triggers installed so late writes from drained Worker isolates remain protected. During a later deployment or maintenance window—never the migration rollout itself—run `pnpm deploy:tenant-finalize` to repeat the backfill and remove the triggers after the old Worker generation has fully drained.

### Scoped Service Auth

- SSH gateway: `CRABFLEET_SSH_GATEWAY_TOKEN`.
Expand Down Expand Up @@ -331,7 +358,7 @@ Verify:
1. request URL origin equals `CRABFLEET_TRUSTED_PROXY_ORIGIN`;
2. browser mutation/WebSocket `Origin` equals the public origin;
3. both identity and secret headers are present;
4. the asserted direct identity is allowlisted;
4. the asserted direct identity is allowlisted or a valid automatic role is configured;
5. the backend is not reachable around the proxy.

### Repo Missing
Expand Down
Loading