diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..e00a38f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,59 @@ +name: Bug report +description: Report a reproducible problem in chat2shell +title: '[Bug]: ' +body: + - type: markdown + attributes: + value: | + Thanks for reporting a bug. Please remove credentials, tokens, private paths, and other sensitive data from logs or examples. + For security vulnerabilities, follow https://github.com/nbsp1221/chat2shell/blob/main/SECURITY.md instead of opening a public issue. + - type: textarea + id: problem + attributes: + label: What happened? + description: Describe the behavior you observed and why it is a problem. + placeholder: A clear, concise description of the bug. + validations: + required: true + - type: textarea + id: reproduction + attributes: + label: Reproduction + description: Provide the smallest sequence of commands or MCP calls that reproduces the issue. + placeholder: | + 1. Run ... + 2. Call ... + 3. Observe ... + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected behavior + description: What did you expect to happen? + validations: + required: true + - type: textarea + id: environment + attributes: + label: Environment + description: Include the relevant versions. Use `not applicable` where needed. + value: | + - OS: + - Node.js: + - pnpm: + - Docker Sandboxes (`sbx`): + - chat2shell commit/version: + validations: + required: true + - type: textarea + id: logs + attributes: + label: Relevant logs or output + description: Optional. Redact credentials, tokens, private paths, and other sensitive values before posting. + render: shell + - type: textarea + id: additional + attributes: + label: Additional context + description: Anything else that would help reproduce or understand the issue. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..0086358 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..2571e67 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,34 @@ +name: Feature request +description: Propose a concrete improvement to chat2shell +title: '[Feature]: ' +body: + - type: markdown + attributes: + value: | + chat2shell intentionally keeps its scope small. Start with the problem and workflow rather than a preferred implementation. + - type: textarea + id: problem + attributes: + label: Problem + description: What workflow is difficult or impossible today? + placeholder: Describe the user problem and when it occurs. + validations: + required: true + - type: textarea + id: proposal + attributes: + label: Proposed outcome + description: What should become possible? Implementation details are optional. + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + description: What can you do today, and why is it insufficient? + - type: textarea + id: scope + attributes: + label: Scope and boundary impact + description: Does this change host access, sandbox authority, persistence, networking, authentication, or the public MCP contract? + placeholder: No boundary impact / explain the affected boundary. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..c3d3d53 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,13 @@ +## Summary + +- What problem does this change solve? +- What behavior or implementation changed? + +## Verification + +- [ ] `pnpm check` +- [ ] `pnpm test:e2e` when the change crosses the real sandbox boundary + +## Notes + +Describe any security, lifecycle, compatibility, or rollout considerations that reviewers should know. Remove this section when it is not relevant. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..80b1529 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,19 @@ +# Code of Conduct + +chat2shell aims to be a useful, technically rigorous, and welcoming open-source project. + +## Expected behavior + +When participating in issues, pull requests, reviews, or discussions: + +- be respectful and assume good intent; +- critique ideas and code rather than people; +- provide enough technical context for others to understand a claim; +- accept that maintainers may decline work that does not fit the project's scope; +- avoid harassment, discrimination, threats, or deliberate disruption. + +## Project moderation + +Maintainers may edit, hide, lock, or remove contributions that violate these expectations, and may restrict participation when behavior repeatedly disrupts the project. + +For sensitive conduct concerns, avoid posting personal details publicly. Use a private GitHub communication channel when one is available, or open a minimal issue asking the maintainer for a private contact path without including sensitive details. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..efb3354 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,75 @@ +# Contributing to chat2shell + +Thanks for taking the time to contribute. + +chat2shell is intentionally small and security-sensitive. Prefer focused changes that solve a concrete problem without expanding the execution boundary unnecessarily. + +## Before you start + +- Use the bug report template for reproducible defects. +- Use the feature request template to explain the problem before proposing a large new capability. +- For substantial behavior or security-boundary changes, open an issue first so the design can be discussed before implementation. +- Do not report security vulnerabilities in public issues. See [SECURITY.md](./SECURITY.md). +- Follow the project [Code of Conduct](./CODE_OF_CONDUCT.md) when participating. + +## Development setup + +Requirements: + +- Node.js 24+ +- pnpm 11.23.0 +- Docker Sandboxes (`sbx`) only when running real E2E tests + +Install dependencies: + +```bash +pnpm install --frozen-lockfile +``` + +Run the normal quality gate: + +```bash +pnpm check +``` + +`pnpm check` verifies formatting, linting, TypeScript, unit/integration tests, and the production bundle. + +## Tests + +The test suite is intentionally separated by boundary: + +- `pnpm test:unit` — small code units with no external resources +- `pnpm test:integration` — multiple chat2shell components using local resources only +- `pnpm test:e2e` — real Docker Sandbox microVMs, CodexPro, private Docker, and port exposure + +Normal CI runs unit and integration tests. Run E2E tests on a trusted host when changing sandbox lifecycle, workspace modes, CodexPro routing, Bash sessions, or port exposure. + +See [test/README.md](./test/README.md) for the canonical test taxonomy. + +## Pull requests + +Keep pull requests small enough to review as one coherent change. + +A good pull request should: + +1. Explain the user or maintainer problem being solved. +2. Avoid unrelated refactors. +3. Add or update tests for behavior changes. +4. Keep README/architecture/security documentation in sync when public behavior or trust boundaries change. +5. Pass `pnpm check` before submission. + +The repository uses Gitmoji-style subjects in its history. Keep titles concise and describe the aggregate change; maintainers may squash commits when merging. + +## Documentation ownership + +Avoid duplicating system invariants across documents: + +- `README.md` is the user-facing landing page and quick start. +- `docs/architecture.md` is the canonical technical description of trust boundaries and runtime behavior. +- `SECURITY.md` defines vulnerability-reporting guidance and security scope. +- `test/README.md` defines test boundaries. +- `ROADMAP.md` contains future product direction, not current guarantees. + +## Scope discipline + +chat2shell deliberately does not try to be a general-purpose orchestrator. New abstractions, background services, persistence layers, or policy systems should have a demonstrated need before they are added. diff --git a/README.md b/README.md index 018d49b..94fc514 100644 --- a/README.md +++ b/README.md @@ -1,225 +1,204 @@ -# chat2shell +
Give ChatGPT a real shell and private Docker engine without exposing your host shell or host Docker daemon.
-## Architecture + -```text -ChatGPT conversations - -> OpenAI Secure MCP Tunnel - -> chat2shell MCP control plane (host, loopback only) - -> SQLite workspace, approval, and sandbox registry - -> one Docker Sandbox microVM per sandbox_id - -> one foreground CodexPro process - -> one approved workspace - -> one private Docker Engine -``` - -The host process exposes six management tools, eighteen relevant CodexPro tools, and two Bash session controls. -Every tool that operates inside a sandbox requires `sandbox_id`. chat2shell forwards ordinary CodexPro calls into the selected microVM and adapts Bash calls into bounded MCP requests without changing where commands execute. -Calls to the same sandbox are serialized, while different conversations can reuse the same stable ID returned by `sandbox_list`. - -The static contract deliberately excludes CodexPro's generic supertool, self-test, and workspace-switching tool because they duplicate visible tools or bypass the sandbox's assigned workspace. CodexPro is installed only in the sandbox template; the host application does not import or execute it. - -## Current policy - -This section describes the user-visible authority, workspace, network, credential, and lifecycle guarantees enforced by chat2shell. ++ Quick start · + Architecture · + Security · + Contributing · + Roadmap +
-### Authority +## What is chat2shell? -- CodexPro never runs on the host. -- ChatGPT cannot invoke raw `sbx`, host shell commands, sudo, or the host Docker socket. -- A sandbox receives full shell and sudo-equivalent freedom only inside its microVM, including its own Docker Engine. -- Bash is unrestricted inside the sandbox. Commands can modify sandbox files, install packages, access the network, and control the sandbox's private Docker Engine. -- chat2shell does not ask for local approval for ordinary sandbox work. Creating a new host-backed workspace is the only local approval boundary. -- The server has no authentication and treats every request as `local-owner`. Secure MCP Tunnel is the recommended transport. Any other exposure must provide its own authentication and access control; never expose the MCP endpoint directly to an untrusted network. +`chat2shell` is a lightweight MCP control plane that gives ChatGPT a capable development environment inside disposable [Docker Sandbox](https://docs.docker.com/ai/sandboxes/) microVMs. -### Workspaces +Each sandbox gets its own shell, approved workspace, CodexPro process, and private Docker Engine. The host shell and host Docker daemon stay outside the execution boundary. -- A managed workspace is the only host path mounted automatically. -- An arbitrary `workspace_path` creates a pending approval and never mounts the path by itself. -- Host paths must already exist and resolve strictly below `CHAT2SHELL_ALLOWED_HOST_ROOTS`. Paths containing `.aws`, `.azure`, `.config`, `.docker`, `.gnupg`, `.kube`, `.local`, `.secrets`, or `.ssh` are rejected. -- `clone` is the default mode for host repositories and keeps edits in a private VM clone. -- `direct` provides read-write access to exactly one locally approved host directory. -- A host workspace approval is stored and reused; chat2shell does not ask again for the same path and mode. -- One workspace can have one running sandbox. Repeating `sandbox_create` for it reuses that sandbox. -- Managed workspace and state directories use owner-only permissions. The SQLite database file uses mode `0600`. +### Why use it? -### Network and credentials +- **Capable by default** — run shell commands, install packages, start servers, and use Docker inside the sandbox. +- **Isolated from the host** — ChatGPT never receives raw host shell, host sudo, or host Docker access. +- **Explicit workspace access** — arbitrary host paths require approval; clone mode keeps edits private by default. +- **Built for agent workflows** — stable sandbox/workspace IDs, long-running Bash sessions, and port exposure work across conversations. -- General outbound network access follows Docker Sandboxes behavior. -- chat2shell does not add, remove, or override Docker Sandboxes network policy. -- `sandbox_expose` publishes one sandbox TCP port on an automatically assigned port on every host IPv4 interface. It is never called automatically, adds no authentication or expiration, and relies on the sandboxed service listening on `0.0.0.0`. -- Repeating `sandbox_expose` for the same sandbox port returns the existing mapping. The mapping disappears when the sandbox is removed. -- Traffic through an exposed port does not count as a tool call and does not renew the sandbox inactivity deadline. -- Docker's built-in MCP gateway may exist inside a shell sandbox, but chat2shell and CodexPro do not connect to it. -- CodexPro endpoints use random bearer tokens and dynamically allocated loopback ports. -- The internal bearer token is stored in the owner-only SQLite state file and is never returned through MCP. -- Tunnel credentials remain outside the npm package. chat2shell reads the tunnel ID and gives tunnel-client the key file path without exposing either value through MCP. - -### Lifecycle and failure - -Calling `sandbox_create` without a path creates two independent identities: +## How it works ```text -sandbox_id: sbx_... -workspace_id: ws_... -workspace: ~/.chat2shell/workspaces/ws_... +ChatGPT + │ + │ MCP + ▼ +Secure MCP Tunnel + │ + ▼ +chat2shell (host, loopback only) + │ + ├─ workspace / approval / sandbox registry + │ + └─ Docker Sandbox microVM + ├─ approved workspace + ├─ CodexPro + ├─ unrestricted sandbox shell + └─ private Docker Engine ``` -CodexPro runs as one foreground `sbx exec` session owned by chat2shell. That session keeps the microVM running; there is no second supervisor and no automatic restart. - -The complete automatic lifetime policy is intentionally small: - -- A sandbox is removed after 24 hours without a tool call. -- A sandbox that keeps receiving tool calls has no maximum lifetime. -- A managed workspace is retained for 30 days after its sandbox is removed. -- After 30 days, the managed workspace is moved to `~/.chat2shell/trash`. - -Every tool call that reaches a running sandbox counts as activity, whether it succeeds or fails. Expiration is checked between calls and never interrupts a command already running. The trash directory is not emptied automatically. Host workspaces are outside chat2shell's ownership and are never moved or deleted. +The diagram is intentionally simplified. See [Architecture](./docs/architecture.md) for the trust boundaries, lifecycle rules, Bash session contract, and workspace model. -Cleanup checks run once per minute. Sandbox resources use Docker Sandboxes defaults. The outer MCP server accepts request bodies up to 20 MiB. +## Prerequisites -Bash has no execution timeout unless `timeout_ms` is explicitly provided. `bash` always returns a `session_id` and waits up to `yield_time_ms`, which defaults to 10 seconds and accepts at most 60 seconds. If command launch succeeds but the initial status/output snapshot cannot be read, `bash` preserves the session and conservatively returns `status: running` with no output so the caller can recover with `bash_poll`. `bash_poll` waits for new output, process exit, or its own `yield_time_ms` expiry; that wait also defaults to 10 seconds and accepts at most 60 seconds. It returns only new combined stdout/stderr. Poll again while `status` is `running` or `has_more_output` is true. `bash_stop` sends SIGTERM followed by SIGKILL after 1.5 seconds if necessary. chat2shell does not redact Bash output: everything printed inside the sandbox is visible to the MCP client. Sensitive data must be controlled by the files and credentials explicitly made available to the sandbox. Bash sessions exist only in their sandbox and disappear when that sandbox is removed. They are not recovered after a chat2shell restart, because restart reconciliation removes the old sandbox. +- **Node.js 24+** +- **Docker Sandboxes** (`sbx`) +- For ChatGPT access: **OpenAI Secure MCP Tunnel** access and the tunnel client configured for your account -If CodexPro becomes unavailable, the sandbox changes to `failed`. `sandbox_list` shows it, and the user must destroy it before creating a replacement. chat2shell does not guess how to recover it. +## Quick start -Restarting chat2shell invalidates existing sandboxes because their foreground sessions belonged to the old controller. On the next start, chat2shell removes those microVMs and reports their records as `failed`. Reboot persistence is not implemented. - -Destroying an active sandbox follows the same workspace policy, so a managed workspace can be attached to a new sandbox with the same `workspace_id` during its 30-day retention period. - -## Install and run - -Requirements are Node.js 24 or newer and [Docker Sandboxes](https://docs.docker.com/ai/sandboxes/) (`sbx`). Tunnel mode additionally requires Secure MCP Tunnel access, its client, a tunnel ID, and a key file. - -Install the CLI and prepare its pinned CodexPro sandbox template: +### 1. Install chat2shell ```bash npm install --global chat2shell -chat2shell setup ``` -`setup` checks the required local tunnel files and Docker Sandboxes installation. It creates `chat2shell-codexpro:0.30.0` only when that template does not already exist. The template contains CodexPro and its npm dependencies, but no workspace, application source, credentials, or tunnel secret. +### 2. Prepare the sandbox template -Run chat2shell in the foreground: +Start without a tunnel first to verify the local runtime: ```bash -chat2shell serve +CHAT2SHELL_ENABLE_TUNNEL=0 chat2shell setup ``` -The `serve` process owns both the loopback MCP gateway and tunnel-client. It first reconciles sandbox state, then opens the gateway and starts the tunnel. It has no startup timeout, daemon mode, automatic restart, or service installation. Use `Ctrl+C` to stop an interactive process or let an external service manager supervise the same foreground command. - -Inspect a running instance from another terminal: - -```bash -chat2shell status -``` +`setup` checks Docker Sandboxes and creates the pinned `chat2shell-codexpro:0.30.0` template when needed. -Run locally without opening the tunnel: +### 3. Start the MCP server ```bash -CHAT2SHELL_ENABLE_TUNNEL=0 chat2shell setup CHAT2SHELL_ENABLE_TUNNEL=0 chat2shell serve ``` -The MCP endpoint binds to loopback by default. If you publish it through a reverse proxy, another tunnel, or a non-loopback bind, you are responsible for authenticating and restricting that route. - -Update an npm installation through the package manager that owns it: +In another terminal: ```bash -npm update --global chat2shell +CHAT2SHELL_ENABLE_TUNNEL=0 chat2shell status ``` -chat2shell intentionally has no self-update command. +The local MCP endpoint binds to loopback by default. + +### 4. Connect ChatGPT -## Development +Follow OpenAI's [Secure MCP Tunnel guide](https://developers.openai.com/api/docs/guides/secure-mcp-tunnels), configure the tunnel client, tunnel ID, and key file, then run: ```bash -pnpm install -pnpm check -pnpm test:e2e +chat2shell setup +chat2shell serve ``` -`pnpm check` is the normal development and CI quality gate: formatting, linting, typechecking, unit and integration tests, and the production bundle. It deliberately excludes real Docker Sandbox E2E tests. Run `pnpm test:e2e` on a trusted host with `sbx` and the local CodexPro template installed. See [`test/README.md`](./test/README.md) for the test boundaries and individual commands. +By default chat2shell expects: -## Host workspace approval +```text +Tunnel client ~/.local/bin/tunnel-client +Tunnel ID ~/.secrets/tunnel-client/tunnel-id +Tunnel key ~/.secrets/tunnel-client/key +``` -When ChatGPT requests a new host path, `sandbox_create` returns an `approval_id` instead of creating a sandbox. -Review and decide it locally: +Override these locations with environment variables when your setup differs. See [Configuration](#configuration). -```bash -chat2shell approval list -chat2shell approval approve approval_... -chat2shell approval reject approval_... -``` +## Example workflow -A host operator can also register a path directly: +Once connected, ChatGPT can create an isolated workspace and use the returned `sandbox_id` for subsequent tools: -```bash -chat2shell workspace add /path/to/repository --mode clone -chat2shell workspace add /path/to/repository --mode direct -chat2shell workspace list +```text +sandbox_create + -> bash / read / write / search / ... + -> bash_poll for long-running commands + -> sandbox_expose to preview a web service + -> sandbox_destroy when the environment is no longer needed ``` -After approval, call `sandbox_create` with the returned `workspace_id`. -Use `direct` only when immediate edits to the host checkout are intended. -Full bash inside a direct sandbox can modify every file in that approved directory, including repository metadata such as `.git`. -Unexported changes in a private clone disappear when its sandbox is destroyed, so commit and fetch them before deletion. +A typical long-running command looks like: -## MCP workflow +```text +bash + -> { session_id: "bash_...", status: "running", output: "..." } -```json -{ "workspace_mode": "managed" } +bash_poll + -> { sandbox_id: "sbx_...", session_id: "bash_..." } ``` -Pass the returned sandbox ID to every CodexPro tool: +## Workspace modes -```json -{ "sandbox_id": "sbx_...", "command": "pnpm test" } -``` +| Mode | Host interaction | Best for | +| --------- | ------------------------------------------------ | --------------------------------------------------- | +| `managed` | chat2shell-owned persistent workspace | Disposable or standalone agent work | +| `clone` | Private clone of an approved host repository | Safe default for existing repositories | +| `direct` | Read/write access to one approved host directory | Work that must immediately affect the host checkout | -Long commands use the same `bash` tool. A running result includes a session ID for later output or termination: +`clone` is the default for approved host repositories. Use `direct` only when you intentionally want sandbox commands to modify the approved host directory. -```text -bash -> { "session_id": "bash_...", "status": "running", "output": "..." } -bash_poll -> { "sandbox_id": "sbx_...", "session_id": "bash_...", "yield_time_ms": 10000 } -bash_stop -> { "sandbox_id": "sbx_...", "session_id": "bash_..." } -``` +## Security model -To view a web application, start it on every sandbox interface and expose its port: +chat2shell is designed around a simple boundary: **the agent is powerful inside the microVM, not on the host.** -```text -bash -> pnpm dev --host 0.0.0.0 -sandbox_expose -> { "sandbox_id": "sbx_...", "port": 3000 } -``` +- CodexPro and unrestricted Bash run inside Docker Sandboxes, never directly on the host. +- Host paths are not mounted unless they are managed by chat2shell or explicitly approved. +- The MCP server has no built-in authentication and binds to loopback by default. Do not expose it directly to an untrusted network. +- `sandbox_expose` publishes a sandbox port without adding authentication; treat the exposed service accordingly. +- Tunnel credentials and internal CodexPro bearer tokens are not returned through MCP. -Connect to the returned `hostPort` through any network path that already reaches the host. chat2shell does not discover host addresses, create URLs, or manage a reverse proxy. +Read [Architecture](./docs/architecture.md) for the canonical technical model and [Security](./SECURITY.md) for vulnerability reporting and expected security boundaries. -Other conversations connected to the same private app can find and reuse it: +## CLI ```text -sandbox_list -> sandbox_get -> read/search/bash/... with sandbox_id +chat2shell setup Check prerequisites and prepare the sandbox template +chat2shell serve Run the MCP gateway and tunnel client in the foreground +chat2shell status Show service, MCP, and tunnel readiness +chat2shell workspace list List known workspaces +chat2shell workspace add