Skip to content
Closed
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
9 changes: 5 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ WORKSPACE_SERVICE_TOKEN=
# Optional: HTTP server port (default: 8080)
PORT=8080

# Optional: Root directory where workspace is mounted (default: /workspace)
WORKSPACE_ROOT=/workspace
# Optional: Absolute OpenClaw root mount path (default: /openclaw-config)
CONFIG_ROOT=/openclaw-config

# Optional: Subdirectory within WORKSPACE_ROOT to expose (default: workspace)
WORKSPACE_SUBDIR=workspace
# Optional: Main workspace folder under CONFIG_ROOT (default: workspace)
# Must be a single folder name (no /, \\, ., ..)
MAIN_WORKSPACE_DIR=workspace

# Optional: Comma-separated symlink prefixes to remap (default: /home/node/.openclaw)
SYMLINK_REMAP_PREFIXES=/home/node/.openclaw
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/gitleaks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ jobs:
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_LICENSE_KEY: A17B1B-97A03F-6EECE6-BAE41F-65FBAF-V3
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ npm-debug.log
.DS_Store
*.log
coverage/
.idea
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- Split workspace/config filesystem roots and enforce config-root + main-workspace-dir path law
- `/workspace/*` virtual mapping for the main workspace
- Claude Code configuration and project rules

### Changed

- Docker publish workflow hardened for multi-platform builds and SHA prefix handling
- Documentation clarified for read/write mounts and `WORKSPACE_SUBDIR` defaults

### Fixed

- Dockerfile now includes the application source directory in image builds

### Security

- Switched Gitleaks license key to an organization secret

## [0.1.0] - 2026-03-03

- Initial release
Expand Down
44 changes: 31 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ Lightweight HTTP service that exposes OpenClaw workspace files over REST API. Th

## Security

> **This service can read, write, and delete files on the mounted workspace volume. Treat it as a privileged internal API.**
> **This service can read, write, and delete files under the mounted OpenClaw root. Treat it as a privileged internal API.**

- **Authentication is required** — `WORKSPACE_SERVICE_TOKEN` must be set. The service will refuse to start without it.
- **Never expose port 8080 to the public internet** — use a VPN, private network, or Kubernetes `ClusterIP` service.
- Always use a strong, randomly generated bearer token (`openssl rand -hex 32`).
- The service runs as a non-root user inside the container.
- Path traversal protection is built-in and cannot be bypassed via the API.
- Mount workspace volumes as read-only (`:ro`) when write access is not required.
- For normal MosBot usage, mount the OpenClaw root read-write so Projects/Skills/Docs and config edits can succeed.

See [SECURITY.md](SECURITY.md) for the full threat model and vulnerability reporting process.

Expand All @@ -38,9 +38,10 @@ services:
image: ghcr.io/bymosbot/mosbot-workspace-service:latest
environment:
WORKSPACE_SERVICE_TOKEN: your-secure-token # required
WORKSPACE_ROOT: /workspace
CONFIG_ROOT: /openclaw-config
MAIN_WORKSPACE_DIR: workspace
volumes:
- openclaw-workspace:/workspace:ro
- /path/to/.openclaw:/openclaw-config
ports:
- "8080:8080"
```
Expand All @@ -51,27 +52,44 @@ services:
docker run -d \
--name mosbot-workspace \
-e WORKSPACE_SERVICE_TOKEN=your-secure-token \
-e WORKSPACE_ROOT=/workspace \
-v /path/to/openclaw/workspace:/workspace:ro \
-e CONFIG_ROOT=/openclaw-config \
-e MAIN_WORKSPACE_DIR=workspace \
-v /path/to/.openclaw:/openclaw-config \
-p 8080:8080 \
ghcr.io/bymosbot/mosbot-workspace-service:latest
```

For full MosBot integration (agent discovery via `openclaw.json` + Projects/Skills/Docs CRUD), use
a read-write mount for `CONFIG_ROOT`.

## Environment Variables

| Variable | Default | Description |
| ----------------------------------- | ---------------------- | --------------------------------------------------------------------------------------------------- |
| `PORT` | `8080` | HTTP server port |
| `WORKSPACE_ROOT` | `/workspace` | Root directory where workspace is mounted |
| `WORKSPACE_SUBDIR` | `workspace` | Subdirectory within `WORKSPACE_ROOT` to expose (prevents browsing the entire filesystem) |
| `CONFIG_ROOT` | `/openclaw-config` | Absolute OpenClaw root mount containing config, shared dirs, and agent workspaces |
| `MAIN_WORKSPACE_DIR` | `workspace` | Main workspace directory name under `CONFIG_ROOT` (single folder name only; no `/`, `\`, `.`, `..`) |
| `WORKSPACE_SERVICE_TOKEN` | — | **Required.** Bearer token for authentication. The service will not start without this. |
| `SYMLINK_REMAP_PREFIXES` | `/home/node/.openclaw` | Comma-separated list of symlink prefixes to remap (for cross-container symlinks) |
| `WORKSPACE_SERVICE_ALLOW_ANONYMOUS` | — | Set to `true` to disable auth requirement. **For local development only. Never use in production.** |

> **Deprecated aliases** (still accepted for backward compatibility):
>
> - `WORKSPACE_PATH` → use `WORKSPACE_ROOT` instead
> - `AUTH_TOKEN` → use `WORKSPACE_SERVICE_TOKEN` instead
Removed and no longer honored: `WORKSPACE_FS_ROOT`, `CONFIG_FS_ROOT`, `WORKSPACE_ROOT`,
`WORKSPACE_SUBDIR`, `WORKSPACE_PATH`, `AUTH_TOKEN`.

## Filesystem and Virtual Path Contract

Given `CONFIG_ROOT=/openclaw-config` and `MAIN_WORKSPACE_DIR=workspace`:

- Main workspace filesystem root: `/openclaw-config/workspace`
- Sub-agent workspaces: `/openclaw-config/workspace-<agent>`
- Shared directories: `/openclaw-config/projects`, `/openclaw-config/skills`, `/openclaw-config/docs`

Routing rules:

- Config-root paths: `/openclaw.json`, `/org-chart.json`, `/projects/**`, `/skills/**`, `/docs/**`, `/workspace-<agent>/**`
- Main workspace canonical paths: `/workspace` and `/workspace/**` (mapped to `CONFIG_ROOT/MAIN_WORKSPACE_DIR`)

Canonical main workspace virtual path is `/workspace`.

## API Endpoints

Expand All @@ -95,7 +113,7 @@ Returns workspace accessibility status.
### List Files

```bash
GET /files?path=/&recursive=false
GET /files?path=/workspace&recursive=false
Authorization: Bearer <token>
```

Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
Key risks to be aware of:

- **File write/delete access**: The `POST /files`, `PUT /files`, and `DELETE /files` endpoints can modify or remove files on the mounted workspace volume. Always use a strong `WORKSPACE_SERVICE_TOKEN` and restrict network access.
- **Path traversal**: Built-in path traversal protection rejects requests that escape the configured `WORKSPACE_ROOT`/`WORKSPACE_SUBDIR`. Do not disable or weaken this check.
- **Path traversal**: Built-in path traversal protection rejects requests that escape `CONFIG_ROOT` or `CONFIG_ROOT/<MAIN_WORKSPACE_DIR>`. Do not disable or weaken this check.
- **Symlink following**: The service follows symlinks to support cross-container paths. Ensure the workspace volume only contains trusted content.
- **Token exposure**: Never log or expose `WORKSPACE_SERVICE_TOKEN` in application logs, metrics, or error responses.

Expand Down
5 changes: 3 additions & 2 deletions SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ docker build -t mosbot-workspace-service:test .
docker run -d \
--name mosbot-workspace-test \
-e WORKSPACE_SERVICE_TOKEN=test-token \
-e WORKSPACE_ROOT=/workspace \
-v /tmp/test-workspace:/workspace \
-e CONFIG_ROOT=/openclaw-config \
-e MAIN_WORKSPACE_DIR=workspace \
-v /tmp/test-config:/openclaw-config \
-p 8080:8080 \
mosbot-workspace-service:test

Expand Down
20 changes: 13 additions & 7 deletions __tests__/auth.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,20 @@ const { createApp } = require("../src/app");

describe("Authentication middleware", () => {
let tmpDir;
let workspaceRoot;
let configRoot;
let app;
const TOKEN = "test-token-abc123";

beforeAll(async () => {
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "ws-auth-test-"));
// Create a minimal workspace structure
await fs.mkdir(path.join(tmpDir, "workspace"), { recursive: true });
await fs.writeFile(path.join(tmpDir, "workspace", "hello.txt"), "hello");
configRoot = path.join(tmpDir, "config-root");
workspaceRoot = path.join(configRoot, "workspace");

await fs.mkdir(configRoot, { recursive: true });
await fs.mkdir(workspaceRoot, { recursive: true });
await fs.writeFile(path.join(workspaceRoot, "hello.txt"), "hello");
await fs.writeFile(path.join(configRoot, "openclaw.json"), "{}");
});

afterAll(async () => {
Expand All @@ -25,8 +31,8 @@ describe("Authentication middleware", () => {
describe("when token is configured", () => {
beforeAll(() => {
app = createApp({
workspaceRoot: tmpDir,
workspaceSubdir: "workspace",
configRoot,
mainWorkspaceDir: "workspace",
token: TOKEN,
symlinkRemapPrefixes: [],
});
Expand Down Expand Up @@ -68,8 +74,8 @@ describe("Authentication middleware", () => {
describe("when no token is configured (anonymous mode)", () => {
beforeAll(() => {
app = createApp({
workspaceRoot: tmpDir,
workspaceSubdir: "workspace",
configRoot,
mainWorkspaceDir: "workspace",
token: undefined,
symlinkRemapPrefixes: [],
});
Expand Down
Loading