Skip to content
dsh.fish
Bundle

dsh-multi-tenant-projects

Project-scoped one-shot users for DSH: project/user dual workspaces with symlink isolation, cwd session bucketing, soft guard, and a built-in admin UI. Best-effort isolation (honnet gens) — not a hard security boundary.

Source
king-bcolor
stars
18 stars
License
MIT
Updated
Updated 3 hours ago

Readme

# dsh-multi-tenant-projects

**[简体中文](README.zh-CN.md)** | English

Multi-tenant "Projects & Users" for a single [DeepSeek Harness (DSH)](https://github.com/topics/dsh-plugin) instance: bind real workspace directories to *projects*, hand out per-user symlinked workspaces inside each project, and gate the Web UI behind password login — so several people can share one DSH deployment without seeing each other's sessions or files.

> **Threat model — read this first.** This plugin is a *soft* boundary, not a hard security perimeter. The cwd filter is a query projection, browser tokens can be forged by a technical user, and the agent-layer isolation is prompt-level. For anything exposed to the public internet, put a real boundary (reverse-proxy auth, ngrok basic auth, VPN…) in front of DSH and treat this plugin as convenience isolation between *trusted-ish* teammates.

## Features

- **Projects** bound to real directories (auto-created, or bind an existing workspace path via the host-native directory picker).
- **One-shot users** per project with password login; Bearer tokens with sha256 fingerprints, TTL, and instant invalidation when a user is disabled.
- **Same-name users across projects** — storage key is `<project>/<user>`; log in as `project/user` when a bare name is ambiguous.
- **Per-user workspace**: a real directory whose entries are symlinks to the project's files; new project entries can be re-synced (`admin/sync`).
- **Login gate**: a full-frame login card while the guard is armed and no valid token is stored (fails open if the plugin API itself is broken).
- **Restricted UI for normal users**: sidebar shows only their own cwd-bucketed sessions (with durable titles — cold sessions no longer fall back to the directory name), settings entry and workspace switcher are shadowed away, the hero picker offers only their own workspace, auto-connected on login.
- **Permission lock**: every normal-user session is pinned to **workspace-write** and `/permission` switching is refused; the composer access-mode chip is frozen at *Workspace Write* (admins keep the full menu).
- **System-prompt guard, two layers**: a host-injected `受限会话守则` section in the system prompt itself (never disclose anything outside the user's workspace, never run boundary-probing commands, refuse cross-boundary requests even when asked) plus a per-workspace `AGENTS.md` baseline that is auto-refreshed on sync.
- **Admin console** in Settings → *Projects & Users*: create/list projects and users, disable users, one-shot token handoff, directory binding, sync links.
- **Sign-out badge** in the sidebar footer for both admins and users.

## Install

**One-liner** (recommended) — the published repo ships pre-built artifacts (`lib/`, `dist/`) and only pure-JS runtime deps, so no local build is needed and your Node version doesn't matter beyond what DSH itself requires:

```bash
dsh plugin --profile web add github:king-bcolor/dsh-multi-tenant-projects
```

Restart DSH (`dsh web`) and look for `projects: 就绪(root=…, guard=true)` in the log. To pin a release, append a tag: `github:king-bcolor/dsh-multi-tenant-projects#v0.1.0`.

Updating later: re-run the same command (or `pnpm update dsh-multi-tenant-projects` in `~/.dsh/profiles/web`), then restart.

<details>
<summary>From a local checkout (development)</summary>

```bash
git clone https://github.com/king-bcolor/dsh-multi-tenant-projects.git
cd dsh-multi-tenant-projects
npm install && npm run build && npm test

# link the working tree into your DSH web profile (edits + rebuild need a dsh restart)
dsh plugin --profile web add link:$(pwd)
```

</details>

The plugin also ships a Settings page (**Settings → dsh-multi-tenant-projects**) with its configuration:

| Key | Default | Meaning |
|---|---|---|
| `adminPassword` | `admin` | Bootstrap admin password, seeded only when the user store is empty |
| `guardEnabled` | `true` | Arm the login gate |
| `agentsRules` | `[]` | Extra rules appended to every user workspace's `AGENTS.md` |

## Quick start

### 1. Sign in as admin

Open the DSH Web UI. The login gate asks for credentials — the bootstrap admin is `admin` / the `adminPassword` you configured (default `admin`; change it).

### 2. Create a project and bind a workspace

Settings → **Projects & Users**:

- **Project name** — slugified (`My App` → `my-app`).
- **Workspace path** (optional) — leave empty to auto-create `<DSH home>/projects-ws/<project>`, or click **Browse…** to bind an existing directory with the host-native picker (manual absolute-path input works on browse-only hosts).

### 3. Create users under the project

Still in the console: pick the project, choose a **user name** and a **password**, submit. Behind the scenes each user gets:

- a real workspace `…/projects-ws/<project>-<user>/` whose entries are symlinks to the project workspace's files;
- a freshly rendered `AGENTS.md` with the workspace guard rules;
- credentials usable at the login gate (`project/user` or the bare name).

### 4. Same-name users across projects

Usernames are unique *within* a project, so `alpha/alice` and `beta/alice` can coexist. Login resolution:

- A **bare name** works while it is unique across all projects.
- As soon as two projects own the same name, use the **`project/user`** form (`alpha/alice`); the bare name is rejected with a disambiguation hint. The admin console's user actions always carry the composite key.

### 5. Admin vs normal user — what's different

| Surface | Admin | Normal user |
|---|---|---|
| Login | `admin` (bootstrap) or Settings sign-in | `project/user` + password |
| Sidebar | All workspaces & sessions | **Only their own workspace's sessions** (cwd-bucketed), durable titles |
| Workspace switcher / hero picker | Full stock picker | Shadowed — the one legal workspace is **auto-selected** on login |
| Settings | Full stock settings + *Projects & Users* console | Settings entry hidden |
| Permission mode | Full menu (`/permission`, composer chip) | **Pinned to workspace-write**; chip frozen at *Workspace Write*; switching refused |
| Agent instructions | Stock | System-prompt guard section + per-workspace `AGENTS.md` boundary rules |
| Sidebar footer | Sign-out badge | Identity badge + sign-out (clears token, hard reload) |
| API surface | `/projects/api/admin/*` | `/projects/api/my/sessions` etc. (token-scoped) |

## Development

```bash
npm test        # vitest, 152 tests (node + jsdom)
npm run build   # tsdown + tsc build outputs
```

Layout: `src/` host half (service, HTTP API, nested plugins) + client half (`src/client/`, React slots); `doc/` holds the full Chinese design docs.

## Known limitations

- cwd filtering is a projection, not an enforcement point — a determined user can bypass the front-end guard;
- prompt-level agent isolation is a soft constraint;
- single-admin model; no token-revocation UI (disabling a user invalidates all their tokens);
- changes to the client half need a `dsh web` restart to reach the browser.

## License

MIT

Install

dsh plugin --profile web add github:king-bcolor/dsh-multi-tenant-projects

Profile: web

  • This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source