docs: mark shared access features experimental (#3428)

Co-authored-by: Kandev Agent <agent@kandev.dev>
This commit is contained in:
João Salavisa
2026-09-05 23:27:48 +01:00
committed by GitHub
parent d74e85883d
commit 5d2abaecd2
16 changed files with 71 additions and 44 deletions
+1 -1
View File
@@ -79,7 +79,7 @@ These protocols are separate:
- **REST and WebSocket** are backend/client and agentctl control surfaces.
- **MCP** supplies Kandev and profile-configured tools to an agent. It is not an agent runtime adapter.
agentctl hosts task MCP endpoints and relays tool calls over the agent stream to backend handlers. The backend also exposes external MCP routes. Kandev currently adds no user-auth middleware to those external routes; do not expose them on an untrusted network without binding, proxy authentication, and scoped credentials. See [Automation and MCP](automation-and-mcp.md).
agentctl hosts task MCP endpoints and relays tool calls over the agent stream to backend handlers. The backend also exposes external MCP routes. Those routes remain open while authentication is disabled; when the experimental authentication feature is enabled, they require an authenticated identity and carry that user into dispatch. External clients use personal access tokens, while same-origin browser tooling can use a session. Do not expose them on an untrusted network without binding, TLS, and scoped credentials. See [Automation and MCP](automation-and-mcp.md).
## Events and persistence
+10 -7
View File
@@ -1,13 +1,14 @@
---
title: "Authentication & Users"
description: "Enable opt-in authentication, manage users and invites, and use personal access tokens on a shared Kandev server."
status: experimental
---
# Authentication & Users
Kandev ships as a single-user local tool with authentication **disabled**: nothing changes for laptop installs. When several people share one Kandev server, enable authentication to give each person their own account and their own private workspaces.
Authentication is a **runtime feature toggle**: the same system as the other feature flags, so there is no separate "Authentication" configuration page.
Authentication is an **experimental runtime feature toggle** and is disabled in every shipped profile. It uses the same system as the other feature flags, so there is no separate "Authentication" configuration page.
## Quick checklist
@@ -22,7 +23,7 @@ Authentication is a **runtime feature toggle**: the same system as the other fea
- Everyone signs in with email + password. Browser sessions last 30 days (sliding) and can be revoked from `Settings > Account`. The signed-in user is shown in the bottom-left of the sidebar, with a log-out menu.
- **Workspaces become per-user by default.** You only see workspaces you own, including their tasks, sessions, repositories, terminals, previews, and live updates. Existing data is assigned to the admin created during setup. A workspace can then be shared with colleagues on purpose: see [Team Access](team-access.md).
- Secrets are per-user. A **Global** secret is user-global across that user's workspaces; a **Workspace** secret belongs to one of their workspaces. With authentication disabled, Global is install-global. Executors and agent profiles remain shared across the instance, so they can reference Global secrets only; repositories may bind Global or same-workspace secrets.
- Admins manage users and instance settings, but do **not** see other users' private workspaces. Admin is a management role, not a visibility role.
- With Organizations off, admins manage users and instance settings but do **not** see other users' private workspaces. With Organizations on, org admins manage organization-scoped resources and the separate instance operator manages organizations; neither role grants workspace visibility. The experimental multi-org path still has tenant-boundary gaps in user and system administration; see [Limitations](#limitations).
- Programmatic clients (external MCP, scripts) authenticate with personal access tokens.
## Sharing work with a team
@@ -47,15 +48,15 @@ A server that listens on non-loopback interfaces with authentication disabled lo
## Users and invites
`Settings > System > Users` (admin only):
`Settings > Access Control > Users` (admin only):
- **Invite links**: mint a tokenized URL (`/invite?token=…`) and share it out of band. Optional pinned email, member or admin role, single use, 7-day default expiry. No email server needed.
- **Direct creation**: create an account with a password yourself.
- **Disable / role changes**: disabling a user immediately revokes their sessions and tokens. The last active admin cannot be demoted or disabled.
Roles: `admin` (user management, authentication settings, destructive system operations, feature toggles) and `member` (everything else, scoped to their own workspaces).
Roles are `admin`, `member`, and `guest`. With Organizations off, an admin manages users and system settings. With Organizations on, membership roles are associated with an organization and the instance operator tier is separate, but the current Users API does not consistently enforce the organization boundary; see [Limitations](#limitations). A guest reaches only explicitly shared workspaces. The current Users page changes accounts between admin and member; assign the API-only guest role as described in [Team Access](team-access.md#current-limits).
Install-wide data in `Settings > System > Data & Logs` and `Settings > System > Storage` follows the same split. Members can read the database stats, the backup listing, and the storage usage, policy, run history, and quarantine contents. Creating, downloading, restoring, and deleting a backup is admin only, because a backup is a copy of the whole database and downloading one would export every user's workspaces. Changing storage settings, adopting a Go cache, and running an analysis, cleanup, or quarantine restore/purge are admin only for the same reason database vacuum, optimize, and reset are: they act on the whole install.
With Organizations off, install-wide data in `Settings > System > Data & Logs` and `Settings > System > Storage` follows the same admin/member split. Members can read the database stats, the backup listing, and the storage usage, policy, run history, and quarantine contents. Creating, downloading, restoring, and deleting a backup is admin only, because a backup is a copy of the whole database and downloading one would export every user's workspaces. Changing storage settings, adopting a Go cache, and running an analysis, cleanup, or quarantine restore/purge are admin only for the same reason database vacuum, optimize, and reset are: they act on the whole install. With Organizations enabled, org admins currently retain these install-wide capabilities; they are not yet restricted to the instance operator.
## Personal access tokens
@@ -85,13 +86,15 @@ A custom `auth.cookieName` (see [configuration](configuration.md#authentication-
## What is isolated
When authentication is on, everything in a workspace is private to its owner and returns "not found" to anyone else, even if they know the ID: workspaces, tasks, workflows, sessions, plans, walkthroughs, terminals, VS Code, port previews, git snapshots, Workspace secrets, repository bindings, **and the workspace's third-party integration settings (GitHub/GitLab/Jira/Linear/Sentry/Azure) and automations**. A user's Global secrets are also private to that user. Admins manage users but do not see other users' workspaces or secrets.
When authentication is on, guarded workspace reads and actions are available only to its owner and users who can reach it through [Team Access](team-access.md). Other users receive "not found" even if they know the ID. This covers workspaces, tasks, workflows, sessions, plans, walkthroughs, terminals, VS Code, port previews, git snapshots, Workspace secrets, repository bindings, **and the workspace's third-party integration settings (GitHub/GitLab/Jira/Linear/Sentry/Azure) and automations**. A user's Global secrets are also private to that user. Admin and operator management authority does not reveal workspaces or secrets they cannot otherwise reach.
Shared across the instance (by design): executors, agent profiles, environments, editors, prompts, and system pages.
## Limitations
- **Multi-org user administration is not fully tenant-scoped.** With Organizations enabled, the current Users API lists accounts across organizations and accepts role or status changes for accounts outside the caller's organization. Treat org-admin access to user management as install-wide until this gap is closed.
- **Multi-org system administration is not operator-exclusive.** Backup, database, and storage mutations, along with runtime feature-toggle changes, currently require the org-admin settings scope, not the instance-operator tier. An org admin can therefore perform install-wide operations, including downloading backups that contain other organizations' data.
- **WebSocket notification routing is not a strict access boundary.** Workspace-aware fan-out normally narrows recipients by reach, but the regular path falls back to global delivery if reach and owner resolution both fail. See [WebSocket API](websocket-api.md#emitted-notifications-and-recipients).
- **Filesystem and agent credentials are not isolated.** Worktrees and repositories live under one `~/.kandev` tree owned by the OS user running the backend, and agent CLI logins (`gh auth`, `claude login`, provider API keys) authenticate as that OS user, so all app-users share the same on-disk agent credentials, and anyone with shell access to the server can read all files. Authentication isolates users' kandev *data* at the application layer, not the filesystem or per-user agent auth. For hard isolation of agent credentials, run a separate kandev instance per user (or use OS-level access control / sandboxed executors).
- One owner per workspace, no sharing or team workspaces yet.
- Local accounts only for now; the account model is ready for OIDC/SSO later.
- Authentication does not replace TLS. Terminate HTTPS in front of Kandev (the session cookie is marked `Secure` when the request arrives over TLS or `X-Forwarded-Proto: https`).
+4 -1
View File
@@ -480,6 +480,8 @@ Copying this entire file is unnecessary and can freeze old defaults in a deploym
| Key | Environment lock | Production default | Effect |
|---|---|---|---|
| `features.auth` | `KANDEV_FEATURES_AUTH` | off | Experimental authentication, users, per-user workspaces, and team access. |
| `features.multiTenancy` | `KANDEV_FEATURES_MULTI_TENANCY` | off | Experimental organizations above authenticated users. Requires `features.auth`; startup is refused otherwise. |
| `features.dynamicAgentRouting` | `KANDEV_FEATURES_DYNAMIC_AGENT_ROUTING` | off | Experimental dynamic profiles with ordered provider-error fallback. |
| `features.canvases` | `KANDEV_FEATURES_CANVASES` | off | Experimental agent-authored isolated web-app canvases for tasks and workspaces. High risk. |
| `features.officeSessionIdentity` | `KANDEV_FEATURES_OFFICE_SESSION_IDENTITY` | off | Experimental Office participant sessions. Enable only after the `(task_id, agent_profile_id)` unique index is available. |
@@ -508,7 +510,7 @@ the rollout is complete, then remove the live flag and move its key and
environment variable to the runtime registry's append-only retired identities.
Plugins are part of the base product and are not a runtime toggle.
The source checkout's `make dev` activates the embedded development profile, which enables Office, debug surfaces, ACP logging, and a mock agent; authentication and Claude background prompt handoff remain opt-in. Installed `run`/desktop builds select the safe production profile unless the environment explicitly opts in. E2E mock variables and routes are test-only and must never be enabled on a public deployment.
The source checkout's `make dev` activates the embedded development profile, which enables Office, debug surfaces, ACP logging, and a mock agent; authentication, organizations, and Claude background prompt handoff remain opt-in. Installed `run`/desktop builds select the safe production profile unless the environment explicitly opts in. E2E mock variables and routes are test-only and must never be enabled on a public deployment.
## Credentials and product settings
The **Unread Messages** preference in **Settings > Preferences > Task Behavior** controls the Slack-style **New** divider in session transcripts. It defaults off for each user, persists with user settings, and takes effect immediately. Enabling it also allows that user's active transcript view to advance the session read cursor.
@@ -594,6 +596,7 @@ no public YAML key:
`KANDEV_MOCK_LINEAR`, and `AGENTCTL_AUTO_APPROVE_PERMISSIONS`.
- Runtime flags and diagnostics: `KANDEV_FEATURES_OFFICE`,
`KANDEV_FEATURES_AUTH`,
`KANDEV_FEATURES_MULTI_TENANCY`,
`KANDEV_FEATURES_CLAUDE_BACKGROUND_PROMPT_HANDOFF`,
`KANDEV_FEATURES_CLAUDE_MID_TURN_STEERING`,
`KANDEV_DEBUG_AGENT_MESSAGES`, `KANDEV_DEBUG_ACP_MAX_FILES`,
+2 -2
View File
@@ -555,7 +555,7 @@
"user",
"operator"
],
"stability": "beta",
"stability": "experimental",
"docs": [
"authentication"
],
@@ -579,7 +579,7 @@
{
"id": "team-access",
"audiences": ["user", "operator"],
"stability": "beta",
"stability": "experimental",
"docs": ["team-access"],
"sources": [
"apps/backend/internal/authz/scopes.go",
+1 -1
View File
@@ -29,7 +29,7 @@ Open `http://localhost:38429` and follow logs with:
docker logs -f kandev
```
Kandev currently has no built-in multi-user web login or API authorization boundary. `auth.jwtSecret` does not add one. Docker's unqualified `-p 38429:38429` publishes on every host interface, so do not use that form on an untrusted network. Use loopback, a private network/VPN, or an authenticated reverse proxy with TLS.
Kandev ships with its experimental [Authentication & Users](authentication.md) feature disabled, so the default container has no multi-user web login or API authorization boundary. Setting `auth.jwtSecret` does not enable it. Docker's unqualified `-p 38429:38429` publishes on every host interface, so do not use that form on an untrusted network. Use loopback, a private network/VPN, or an authenticated reverse proxy with TLS.
## Published images
+1 -1
View File
@@ -73,7 +73,7 @@ A new relayed tool normally requires:
4. server mode/count, handler, transport, and integration tests;
5. an update to [Automation and MCP](automation-and-mcp.md) when capability changes.
Inject task/session identity from server context instead of trusting arguments. Enforce task/workspace reachability, confirmation for destructive actions, pagination, concurrency behavior, and least-privilege credentials. The backend's external MCP routes currently have no Kandev user-auth middleware; deployment network controls are part of the security boundary.
Inject task/session identity from server context instead of trusting arguments. Enforce task/workspace reachability, confirmation for destructive actions, pagination, concurrency behavior, and least-privilege credentials. External MCP remains open while authentication is disabled; when the experimental authentication feature is enabled, it requires an authenticated identity and carries that user into dispatch. External clients use personal access tokens, while same-origin browser tooling can use a session. Deployment network controls remain part of the security boundary.
## Build a plugin
+15 -2
View File
@@ -27,6 +27,19 @@ Released builds can lag this page. Check **Settings > System > About** or `kande
| In progress | Source, schema, tests, a flag, or a stub exists, but there is no supported production workflow yet. |
| Internal | Development, test, diagnostic, or implementation support rather than an end-user feature. |
<details>
<summary>Authentication and team access</summary>
## Authentication and team access
| Capability | Status | Current boundary |
| -------------------------------------------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Authentication and per-user workspaces](authentication.md) | Experimental | `features.auth` (`KANDEV_FEATURES_AUTH`) is off in the production, development, and E2E profiles. Enabling it requires a restart, puts an unconfigured instance into first-admin setup, and exposes users, personal access tokens, and per-user workspace scoping. |
| [Team access, organization units, roles, and assignees](team-access.md) | Experimental | Team access has no separate release toggle and is available only through the experimental authenticated multi-user path. It shares work within one implicit or explicit organization; it does not by itself create isolation between organizations. |
| [Multiple organizations and tenant isolation](organizations.md) | Experimental | `features.multiTenancy` (`KANDEV_FEATURES_MULTI_TENANCY`) is off in every shipped profile and requires `features.auth`. Normal domain reads are organization-scoped, but user administration, install-wide system operations, and an ordinary WebSocket fan-out fallback have known cross-organization gaps. Filesystem paths, agent CLI credentials, executors, and agent profiles also remain shared; use separate instances for untrusted tenants. |
</details>
<details>
<summary>Tasks, workflows, and coordination</summary>
@@ -121,13 +134,13 @@ Released builds can lag this page. Check **Settings > System > About** or `kande
| External Kandev MCP endpoint | Supported | **Settings > External MCP** exposes `/mcp` with modern `2026-07-28` negotiation and legacy initialize support, plus legacy SSE at `/mcp/sse` with messages at `/mcp/message`. When authentication is enabled, clients use a personal access token on each request; same-origin browser sessions can also authenticate. Keep remote access behind a VPN, firewall, or authenticated TLS proxy. It intentionally omits `stop_task_kandev` along with live-session question, plan, walkthrough, spawn, and message tools. |
| External MCP tool preview | Limited | The endpoint registration is authoritative. The current settings preview omits several registered tools and should not be used as a complete allow-list. |
| Office MCP and autonomous coordination | In progress | Office-only document, team, routine, and coordinator behavior is feature-gated and is not part of the supported external or regular task MCP contract. Automation sessions have a fixed workspace-scoped coordinator surface with trusted principal resolution and self-target denial. |
| CLI/browser runtime, Linux/macOS service, Docker deployment, and remote hosting | Dependency-bound | Platform, filesystem, browser, systemd/launchd, container, reverse-proxy, and network configuration determine availability. The web app, API, WebSocket, and external MCP add no Kandev user-login boundary; protect the whole remotely reachable origin. |
| CLI/browser runtime, Linux/macOS service, Docker deployment, and remote hosting | Dependency-bound | Platform, filesystem, browser, systemd/launchd, container, reverse-proxy, and network configuration determine availability. Authentication is experimental and disabled by default; without it, the web app, API, WebSocket, and external MCP have no user-login boundary. Protect the whole remotely reachable origin. |
| Native desktop app | Dependency-bound | macOS, Windows, and Linux artifacts and a Tauri shell are implemented. Signing trust and update behavior differ by platform and release artifact. |
| Kubernetes control-plane deployment guide | Experimental | The checked-in ConfigMap, PVC, Deployment, Service, and Ingress are one single-replica deployment approach, not a Helm chart, operator, or supported high-availability topology. The separate opt-in executor RBAC example is never applied by the documented control-plane deployment path. |
| Statistics and host resource metrics | Supported | Workspace stats and on-demand CPU/memory/disk display are shipped. Open **Stats** from the desktop sidebar or from **Menu → Utilities → Stats** on phones. Execution-environment metrics depend on executor/runtime support. |
| SQLite backups, database maintenance, status, and logs | Supported | Backups use SQLite `VACUUM INTO` snapshots in `backups/` beside the configured database file. The default is `data/backups/`; Kandev does not move snapshots from another directory. Retention, free space, file permissions, and off-host copies remain operator responsibilities. |
| Update checking and applying updates | Dependency-bound | Checking is shipped. Package-manager installs update through their package manager; backend self-update is service-install-specific; desktop updater support varies by artifact/platform. |
| Feature Toggle settings | Supported | The page exposes Office, Dynamic agent routing, canvases, and Debug overrides, environment locks, and restart-required state. Debug is high risk because logs/endpoints can expose prompt, file, and tool data. A toggle being present does not promote its target feature to supported. |
| Feature Toggle settings | Supported | The page exposes Office, Authentication, Organizations, Dynamic agent routing, canvases, Claude experiments, and Debug overrides, environment locks, and restart-required state. Debug is high risk because logs/endpoints can expose prompt, file, and tool data. A toggle being present does not promote its target feature to supported. |
| Office mode | In progress | Production defaults `KANDEV_FEATURES_OFFICE=false`; development and E2E enable it for implementation/testing. Its routes, agents, labels, documents, dependencies, routines, skills, routing, costs, and approvals may change between releases. |
| Plugin system | Supported | The plugin system ships in the base product with no feature flag: install/manage plugins, spawn plugin backends, and load native UI bundles. Loaded plugin code runs with backend privileges, so install only plugins you trust; manifest schema, capabilities, and host API may still change between releases. |
| Agent-authored canvases and isolated web apps | In progress | `features.canvases` is experimental, high risk, restart-required, and off in the `prod`, `dev`, and `e2e` profiles. The public contract covers packaged web apps, sandboxing, permission review, task-to-workspace promotion, Quick Chat editing, and artifact recovery. It is not a supported production workflow yet. See [Agent-authored Canvases](canvases.md). |
+1 -1
View File
@@ -62,7 +62,7 @@ When a provider retargets the pull request, Kandev refreshes the stored target a
comparison. Selecting a task base branch or removing the owning pull-request association clears the
explicit target. A PR with incomplete fork identity is not guessed or applied to another repository.
These UI operations enter through Kandev's `/ws` endpoint, which currently has no backend authentication. Anyone who can reach an unprotected backend can invoke destructive Git actions with the executor's permissions. Keep Kandev on loopback or behind an authenticated, origin-protected reverse proxy; see [WebSocket API](websocket-api.md).
These UI operations enter through Kandev's `/ws` endpoint. With authentication disabled, anyone who can reach an unprotected backend receives the synthetic administrator identity and can invoke destructive Git actions with the executor's permissions. Experimental authentication adds user and workspace authorization, but it does not restrict the executor's filesystem or credentials. Keep Kandev on loopback or behind an authenticated, origin-protected TLS proxy; see [WebSocket API](websocket-api.md).
Credentials are resolved where `agentctl` runs. A host SSH agent, credential helper, `gh` login, or `az` login is not automatically available inside every Docker, SSH, or remote executor. Give the executor only the repository access it needs and test with a disposable branch. See [Executors](executors.md) for executor-specific credential handling.
+1 -1
View File
@@ -424,7 +424,7 @@ Tune from observed startup time. Keep liveness on `/health` and readiness on `/r
## Ingress and exposure
Kandev has no built-in user-auth boundary. Do not expose the example Ingress publicly until an authenticated gateway and TLS are in place.
Kandev ships with its experimental authentication boundary disabled. Do not expose the example Ingress publicly until authentication and TLS are in place; use an authenticated gateway when relying on a supported external identity boundary.
Before applying `k8s/ingress.yaml`:
+1 -1
View File
@@ -124,7 +124,7 @@ Except for a missing configuration, a completed force-sync request returns HTTP
Deleting a config releases every previously managed entity to unmanaged ownership (edits made to them afterward are no longer reverted by a future run) before removing the configuration row. If release fails partway, the configuration is retained and the failure names the entity it stopped at; retrying the delete resumes from there rather than repeating work already done.
> **Network security:** The HTTP API is unauthenticated and can read or change sync configuration with the backend's stored credentials. Keep the backend on loopback or behind an authenticated, origin-protected reverse proxy before exposing it.
> **Network security:** With authentication disabled, the HTTP API is open and can read or change sync configuration with the backend's stored credentials. Experimental authentication requires a session or personal access token, but does not replace TLS. Keep the backend on loopback or behind an authenticated, origin-protected TLS proxy before exposing it.
## Troubleshooting
+1 -1
View File
@@ -563,5 +563,5 @@ drawer mirrors it as the saved left sequence followed by the saved right sequenc
- [Configuration](configuration.md); paths, database, logging, NATS, Docker, and security-sensitive environment variables
- [Executors](executors.md); runtime lifecycle, credentials, cleanup, and isolation boundaries
- [Git operations](git-operations.md); branches, worktrees, push, and pull-request behavior
- [Automation and MCP](automation-and-mcp.md); external MCP routes and their current unauthenticated trust boundary
- [Automation and MCP](automation-and-mcp.md); external MCP authentication and deployment trust boundaries
- [Windows support](windows-support.md); Windows-native limitations and supported alternatives
+17 -14
View File
@@ -1,15 +1,16 @@
---
title: "Organizations"
description: "Run one Kandev server for several independent teams or customers, with a hard boundary between them."
description: "Evaluate experimental multi-organization scoping on one Kandev server, including its current isolation gaps."
status: experimental
---
# Organizations
An organization is a **tenant**: a boundary above users. Two organizations on the same Kandev server cannot see each other's workspaces, tasks, sessions, secrets or accounts, and no role on either side can cross it.
An organization is an experimental **tenant identifier** above users. Normal authorized request paths use it to scope workspaces, tasks, sessions, and secrets. The current implementation is not yet a hard boundary between mutually untrusting tenants: user administration and install-wide system operations have cross-organization gaps, and ordinary WebSocket notification fan-out can fail open on resolution errors. See [Current limits](#current-limits).
Use organizations when one server serves groups that must stay separate: two teams that should not read each other's code, an agency and its clients, or a hosted deployment. If everyone on your server is one team, you do not need this. Use [Team Access](team-access.md) instead, which is about sharing *within* one organization.
Use organizations to evaluate tenant-aware behavior or organize trusted groups on one server. If groups must be isolated from one another, run separate Kandev instances until the current gaps are closed. If everyone on your server is one team, use [Team Access](team-access.md) instead, which is about sharing *within* one organization.
> Organizations require [authentication](authentication.md). Enabling them with authentication off is refused at startup, because a tenant boundary with nobody to belong to it is not a boundary.
Organizations are an **experimental runtime feature** and are disabled in every shipped profile. They require experimental [authentication](authentication.md); enabling organizations with authentication off is refused at startup, because a tenant boundary with nobody to belong to it is not a boundary.
## Quick checklist
@@ -17,7 +18,7 @@ Use organizations when one server serves groups that must stay separate: two tea
2. Enable **Organizations** in `Settings > System > Feature Toggles` and restart.
3. Everything you already had lands in one default organization; the first admin becomes the instance operator.
4. Create further organizations in `Settings > Access Control > Organizations` and give each one its first administrator.
5. From then on, each organization administers itself.
5. Review the current isolation gaps before adding organizations that do not trust one another.
## Enabling
@@ -41,10 +42,10 @@ Nothing becomes visible to anyone new. The migration is idempotent, so a second
| Tier | Manages | Can read your workspaces? |
|---|---|---|
| **Instance operator** | Organizations, feature toggles, instance configuration | **No** |
| **Organization admin** | Users, invites, settings and shared configuration inside one organization | Only what team access already grants them |
| **Instance operator** | Organization lifecycle and first administrators | **No** |
| **Organization admin** | Users, invites, settings and shared configuration; current user and system routes have install-wide gaps | Only what team access already grants them |
The operator tier is an *administration* tier, not a visibility one. An operator creates, suspends and deletes organizations, and holds no permission inside any of them, including their own. Their access to their own workspaces comes from being an ordinary member of their own organization, exactly like everyone else.
The operator tier is an *administration* tier, not a visibility one. An operator creates, suspends and deletes organizations. The operator bit by itself grants no permission inside an organization; access to the operator's own workspaces comes from that account's organization and team membership, exactly like everyone else. Install-wide backup, database, storage, and feature-toggle operations are not yet restricted to this tier; org admins currently retain them.
## Managing organizations
@@ -54,7 +55,7 @@ The operator tier is an *administration* tier, not a visibility one. An operator
### Creating one
Create the organization, then give it a first administrator. That second step is not optional bookkeeping: an ordinary admin can only create accounts inside their own tenant, so a brand-new organization has no way to get its first user. This operator-only path is what breaks that circularity, and after it the organization is self-sufficient.
Create the organization, then give it a first administrator. That second step is not optional bookkeeping: direct account creation inherits the creating admin's organization, so a brand-new organization has no way to get its first user. This operator-only path breaks that circularity. The new admin can then create accounts in that organization, subject to the user-management gaps below.
![A dialog titled "First administrator for Acme Corp" with display name, email and password fields, explaining that an administrator can only create accounts in their own organization so a new organization needs its first one created here.](../screenshots/tenancy-first-admin.png)
@@ -90,14 +91,14 @@ Two organizations on one server. Ana is in Acme; her team's board is shared with
![Acme's kanban board seen by Bruno Costa, showing three Acme tasks.](../screenshots/tenancy-acme-board.png)
Gary is an administrator of Globex on the **same server**. He sees his own organization and nothing else. Acme's org-visible workspace is not merely hidden from him, it does not exist as far as his session is concerned.
Gary is an administrator of Globex on the **same server**. In workspace and task views, he sees his own organization. Acme's org-visible workspace is not merely hidden from him; guarded domain reads return not found. This example does not cover the Users, System, or WebSocket notification gaps listed below.
![Globex's kanban board seen by Gary Vasquez, showing only the Globex Secret Project workspace and one Globex task.](../screenshots/tenancy-globex-board.png)
## How the boundary is enforced
## How normal domain access is scoped
- **Your organization comes from your account, never from the request.** No route, payload, header or WebSocket frame may name an organization, so no request can move a caller between tenants. There is no organization switcher, and one person belongs to exactly one organization; someone who needs two uses two accounts.
- **The tenant check runs before any role.** It is not a permission that a role can outrank: a workspace in another organization is unreachable by an org admin, by the instance operator, and even by a stale membership row.
- **Your organization comes from your account, never from the request.** Guarded domain routes do not accept an organization selector that moves the caller between tenants. There is no organization switcher, and one person belongs to exactly one organization; someone who needs two uses two accounts.
- **The tenant check runs before any workspace role.** For guarded workspace reads and actions, a workspace in another organization is unreachable by an org admin, by the instance operator, and even by a stale membership row.
- **It fails closed.** An account somehow carrying no organization reaches nothing while the feature is on, rather than matching everything.
- **New accounts inherit their creator's organization.** Admin-created users and invite links both carry the minting admin's organization, so accepting an invite can never place someone in a tenant the inviter cannot see.
- **Email is unique across the whole server**, not per organization. One address is one person, which is why signing in needs no organization picker.
@@ -106,6 +107,8 @@ Gary is an administrator of Globex on the **same server**. He sees his own organ
These are real and worth knowing before you put two untrusting groups on one server.
- **Do not use one instance as a hard boundary between untrusted tenants yet.** The org-admin Users API currently lists accounts across organizations and accepts cross-org role or status changes. Install-wide backup, database, storage, and runtime-feature mutations also use the org-admin settings scope rather than the instance-operator tier, so an org admin can affect other tenants or download a backup containing their data.
- **Ordinary WebSocket notification fan-out is not fail-closed.** Workspace-aware delivery normally narrows recipients by reach, but its regular path falls back to global delivery if reach and owner resolution both fail. Selected sensitive notifications use a fail-closed path; the general notification stream does not yet provide a hard tenant boundary. See [WebSocket API](websocket-api.md#emitted-notifications-and-recipients).
- **Secrets are scoped per organization, but not encrypted per organization.**
Reads are org-scoped, so one organization cannot fetch another's secret
through the API. At rest, every secret on the instance is sealed under a
@@ -116,7 +119,7 @@ These are real and worth knowing before you put two untrusting groups on one ser
- **Executors and agent profiles are still instance-wide.** They are shared configuration, and a shared agent profile means a shared provider credential.
- **No per-organization billing, quotas or usage metering.** Suspension is the only lever.
- **No per-organization export or backup.** Backups are instance-wide and contain every organization.
- **No cross-organization anything**, by design: no shared workspaces, no moving a user or workspace between organizations, no cross-organization search.
- **No supported cross-organization collaboration**, by design: no shared workspaces, no moving a user or workspace between organizations, and no cross-organization search.
- **Users cannot be moved between organizations.** Moving a person would leave their workspaces behind in the old tenant.
## Turning it off
+6 -1
View File
@@ -1,12 +1,15 @@
---
title: "Team Access"
description: "Share Kandev workspaces with colleagues using organization units, member roles, and scoped permissions."
status: experimental
---
# Team Access
Kandev starts private: with [authentication](authentication.md) enabled, each person sees only their own workspaces. **Team access** is how a team on a shared server gets shared boards instead, without falling back to a single login everyone knows.
Team access is **experimental** because it is available only through the experimental authenticated multi-user path. It has no separate release toggle: enabling `features.auth` is the opt-in boundary for this path.
The whole model is one idea: **workspaces live in a tree, and so do people.**
- An **organization unit** holds child units, workspaces, and members. Your departments and your teams are both units; they differ only in depth.
@@ -106,6 +109,8 @@ Two role tiers combine. Your **org role** is on your account; your **workspace r
An admin is a **management role, not a reach role**. An admin reaches a workspace because they are in a unit above it, exactly like any member, and never reaches anything in someone else's personal unit.
With Organizations enabled, the current Users API is not fully tenant-scoped: an org admin can list accounts and change role or status outside their organization. The workspace reach rules above still apply, but do not treat `org.members.manage` as a hard tenant boundary yet. See [Organizations](organizations.md#current-limits).
**Workspace roles**
| Role | Read board & transcripts | Edit tasks | Prompt / stop agents | Terminal, shell, previews | Manage members, secrets, settings, delete |
@@ -176,7 +181,7 @@ Every guarded action names a scope. These strings appear in the API and are what
## Current limits
- **"Organization" means one tenant.** With the Organizations feature off there is exactly one, implicitly, and its root unit covers everyone on the server. To run one server for several independent teams or customers, see [Organizations](organizations.md).
- **The `guest` role has no UI yet.** `Settings > Users` is not ported to the SPA, so a guest is assigned through the API: `PATCH /api/v1/users/{id}` with `{"role": "guest"}`.
- **The `guest` role cannot be assigned in the UI yet.** `Settings > Access Control > Users` can change accounts between admin and member only, so assign a guest through the API: `PATCH /api/v1/users/{id}` with `{"role": "guest"}`.
- **Roles are fixed.** There is no custom-role builder; scopes are the extension point and adding one is a code change.
- **No standalone groups.** A unit answers "who is in this team", so a set of people that ignores the tree, like every site reliability engineer across three departments, is not modelled yet.
- **No presence.** There are no cursors, typing indicators, or "who else is viewing" markers.
+8 -8
View File
@@ -30,15 +30,15 @@ An action constant alone is not evidence that an action is registered or emitted
1. Prefer the UI, CLI, MCP tools, or documented HTTP routes for supported integrations.
2. If you need the WebSocket, connect to `/ws` and send one JSON request per frame.
3. Correlate responses by `id`, refetch after reconnects, and treat notifications as invalidation hints.
4. Protect the endpoint: it has no client authentication boundary.
4. Protect the endpoint. Authentication is experimental and disabled by default, so an ordinary install has no client authentication boundary.
## Security and network boundary
The current `/ws` upgrade handler does **not authenticate clients**. It reads `?token=` or the `Authorization` header but does not validate or use the value; JWT validation is still a code TODO. The default backend host is `0.0.0.0`, so a default process can listen on every interface even though examples use `localhost`.
The experimental [Authentication & Users](authentication.md) feature is disabled by default. In that mode, `/ws` gives every client the synthetic single-user administrator identity. When authentication is enabled, the upgrade requires a valid browser session, bearer token, or `?token=<PAT>` fallback for clients that cannot send headers. The default backend host is `0.0.0.0`, so a default process can listen on every interface even though examples use `localhost`.
The raw gateway rejects every action whose name starts with `mcp.` using a `FORBIDDEN` error before the shared dispatcher runs. This prevents raw clients from forging task or session identity that trusted MCP adapters inject; it is not user authentication. Internal MCP actions remain available through their mode-scoped MCP adapters and trusted in-process dispatch paths.
Treat every client that can reach the backend as fully trusted for the remaining WebSocket surface. Actions can create and delete data, start agents and shells, read and change files, run Git operations, reveal stored secrets, and invoke configured integrations. Do not expose port `38429` directly to an untrusted LAN or the internet. Bind to loopback, firewall the port, or put Kandev behind an authenticated reverse proxy that terminates TLS and restricts access. See [Configuration](configuration.md) and [Run as a Service](run-as-a-service.md).
With authentication disabled, treat every client that can reach the backend as fully trusted. Actions can create and delete data, start agents and shells, read and change files, run Git operations, reveal stored secrets, and invoke configured integrations. Authentication does not replace TLS or isolate shared executors, agent credentials, and the host filesystem. Do not expose port `38429` directly to an untrusted LAN or the internet. Bind to loopback, firewall the port, or put Kandev behind an authenticated reverse proxy that terminates TLS and restricts access. See [Configuration](configuration.md) and [Run as a Service](run-as-a-service.md).
The upgrade does enforce an origin policy for browser clients:
@@ -47,7 +47,7 @@ The upgrade does enforce an origin policy for browser clients:
- Different loopback names or addresses are accepted when both origin and request hosts are loopback, such as `localhost` and `127.0.0.1` on different ports.
- Origins must be well-formed `http` or `https` origins with no path, query, fragment, or user information. Other cross-site origins are rejected.
When proxying, preserve a request host that matches the public page's origin and forward WebSocket upgrades. Do not rely on the ignored token parameter for access control.
When proxying, preserve a request host that matches the public page's origin and forward WebSocket upgrades. The origin policy ignores `?token=` when deciding whether to accept an origin, but the authentication layer still accepts it as a PAT fallback when authentication is enabled. Prefer the bearer header where the client supports it, and do not treat a query PAT as a substitute for TLS or the origin policy.
## Wire envelope
@@ -685,9 +685,9 @@ These registrations back Kandev's agent/MCP bridge. The subset registered in a p
The following catalog lists actions with current non-test emission paths. It intentionally excludes constants for which no active emitter was found, including the old `acp.*` compatibility constants, `permission.requested`, `input.requested`, `agent.updated`, and `office.activity.created`. Permission and clarification state currently arrives through session message records instead.
### Global broadcasts
### Shared broadcaster notifications
Their normal live event path broadcasts to every connected client, which must filter by IDs in the payload. Session subscribe/focus hydration can also send selected state actions directly to the requesting client.
Their normal live event path uses a shared broadcaster. With authentication disabled, or when an event has no workspace context, it sends the event to every connected client. With authentication enabled and workspace reach resolves successfully, workspace-carrying events are narrowed to the permitted readers. The regular workspace broadcaster currently falls back to global delivery if both reach and owner resolution fail; only selected sensitive event paths use the fail-closed variant. Do not rely on notification fan-out as a tenant-isolation boundary while this experimental limitation remains. Session subscribe/focus hydration can also send selected state actions directly to the requesting client.
```text
workspace.created
@@ -843,14 +843,14 @@ File changes are batched for up to 100 ms and flushed immediately at 50 entries.
| subscribed run | `run.event.appended` | Future events only; there is no replay cursor. |
| metrics subscribers | `system.metrics.updated` | Live resource snapshot; collection interest follows subscribers. |
Routing is an efficiency mechanism, not an access-control boundary. The server does not authenticate resource ownership, global messages can contain IDs for other workspaces, and a client can request arbitrary subscription IDs.
Routing is an efficiency mechanism, not the access-control boundary. With authentication enabled, the server resolves the caller's identity, checks workspace subscriptions, and normally filters workspace fan-out by reach; domain authorization still decides which actions the caller may perform. Because ordinary fan-out can fall back to global delivery on resolution errors as described above, use authorized read requests as the source of truth. With authentication disabled, the synthetic administrator retains the original single-user reach.
</details>
## Reconnect and troubleshooting
- **Upgrade returns 403:** inspect the browser `Origin` and proxy `Host`. The hostnames must match exactly or both be loopback; ports may differ. A scheme other than `http`/`https` or an origin containing a path is rejected.
- **Connection works locally but is unsafe remotely:** this is expected with the current unauthenticated handler and `0.0.0.0` default. Add a protected proxy or bind/firewall the backend before allowing network access.
- **Connection works locally but is unsafe remotely:** this is expected with authentication disabled and the `0.0.0.0` default. Enable authentication deliberately and add TLS, or bind/firewall the backend behind a protected proxy before allowing network access.
- **Request times out but the mutation happened:** the response may have been dropped or the socket may have closed while the server-side handler continued. Query current state before deciding whether to retry.
- **Notifications stop or state looks stale:** reconnect, resubscribe, and refetch. Check whether the client is consuming frames quickly enough to avoid the 256-frame drop-new queue.
- **Session stream is missing:** send `session.subscribe`; for an actively displayed session also send `session.focus`. Verify the payload's `session_id` matches exactly.
+1 -1
View File
@@ -26,7 +26,7 @@ Open **Settings → Workspaces → select a workspace → Workflows**.
Export does not download a file or change the workflow. Import creates new workflows; it never overwrites a same-named workflow. Delete an unwanted imported workflow through the normal workflow settings flow.
> **Network security:** The HTTP routes are unauthenticated. Keep the backend on loopback or behind an authenticated, origin-protected reverse proxy before exposing them to a network.
> **Network security:** With authentication disabled, these HTTP routes are open. Experimental authentication requires a session or personal access token, but does not replace TLS. Keep the backend on loopback or behind an authenticated, origin-protected TLS proxy before exposing them to a network.
<details>
<summary>HTTP format, fields, and reconciliation details</summary>
+1 -1
View File
@@ -131,7 +131,7 @@ A valid fetch that returns no supported files is different: it is an empty desir
Repository listing or file-download failures fail the run before apply. Per-workspace locking serializes sync, configuration changes, and removal, so two requests cannot interleave their changes.
> **Network security:** The HTTP API is unauthenticated and can read or change sync configuration with the backend's stored credentials. Keep the backend on loopback or behind an authenticated, origin-protected reverse proxy before exposing it.
> **Network security:** With authentication disabled, the HTTP API is open and can read or change sync configuration with the backend's stored credentials. Experimental authentication requires a session or personal access token, but does not replace TLS. Keep the backend on loopback or behind an authenticated, origin-protected TLS proxy before exposing it.
<details>
<summary>HTTP API, reconciliation, and cleanup details</summary>