machine-bridge-mcp 0.3.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SECURITY.md CHANGED
@@ -2,83 +2,133 @@
2
2
 
3
3
  ## Supported versions
4
4
 
5
- Security fixes are applied to the latest released version. Upgrade before reporting an issue that is already fixed on `main`.
5
+ Security fixes are applied to the latest released version. Upgrade before reporting an issue already fixed on `main`.
6
6
 
7
7
  ## Reporting a vulnerability
8
8
 
9
- Do not open a public issue for an undisclosed vulnerability. Use GitHub's private vulnerability reporting feature for this repository. Include:
9
+ Do not open a public issue for an undisclosed vulnerability. Use GitHub private vulnerability reporting for this repository. Include:
10
10
 
11
11
  - affected version and operating system;
12
- - deployment mode and relevant policy flags;
13
- - a minimal reproduction;
12
+ - remote or stdio transport;
13
+ - selected profile and relevant flags;
14
+ - minimal reproduction;
14
15
  - expected and observed impact;
15
- - whether credentials, filesystem data, or command execution were exposed.
16
+ - whether credentials, filesystem data, process authority, or network access were exposed.
16
17
 
17
- Do not include live MCP passwords, daemon secrets, OAuth tokens, Cloudflare credentials, private keys, or unrelated local files. Rotate any credential used in a reproduction.
18
+ Do not include live MCP passwords, daemon secrets, OAuth tokens, Cloudflare credentials, private keys, or unrelated local files. Rotate credentials used in a reproduction.
18
19
 
19
- ## Trust model
20
+ ## Core trust model
20
21
 
21
- The following components are trusted:
22
+ Trusted components are:
22
23
 
23
- 1. The local user account running the daemon.
24
- 2. The installed package/source checkout.
25
- 3. The user's Cloudflare account and deployed Worker.
26
- 4. An MCP client that has completed OAuth authorization with the connection password.
24
+ 1. the local OS user running the runtime;
25
+ 2. the installed package/source checkout and its dependencies;
26
+ 3. for remote mode, the user's Cloudflare account and deployed Worker;
27
+ 4. an MCP client explicitly authorized through OAuth or launched locally through trusted stdio configuration.
27
28
 
28
- The Worker is a relay and authorization boundary. The local daemon is the filesystem and command-execution boundary. A successfully authorized MCP client can invoke every tool enabled by the daemon policy and can receive the resulting file contents and command output.
29
+ An authorized client can invoke every tool exposed by the selected profile and receive its results.
29
30
 
30
- This project does not provide a sandbox for shell commands. `exec_command` has the operating-system permissions of the local user. Workspace path confinement limits filesystem tools, but it cannot constrain commands executed by the shell. Use `--no-exec` when shell access is unnecessary.
31
+ The Worker is a remote authentication and relay boundary. The local runtime is the filesystem and process boundary. Stdio bypasses the Worker and relies on local process/configuration trust.
31
32
 
32
- ## Primary risks and controls
33
+ ## Profiles are capability sets, not sandboxes
33
34
 
34
- ### Credential disclosure
35
+ - `review` exposes read-only workspace/Git/image tools.
36
+ - `edit` additionally exposes write, exact edit, and patch tools.
37
+ - `agent` additionally exposes direct argv execution and process sessions.
38
+ - `full` additionally exposes arbitrary shell commands.
35
39
 
36
- Local state contains the MCP password and daemon secret. State, lock, temporary secret, and service-log files use owner-only permissions where the platform supports them. State writes are atomic. Logs recursively and fully redact common credential fields and known token formats.
40
+ `run_process` avoids shell parsing, which removes one injection class, but an executable such as `node`, `python`, a package manager, compiler, test runner, or repository script can execute arbitrary code. Direct mode therefore has effectively broad local-user authority once an attacker controls argv or executed code.
37
41
 
38
- Terminal output can intentionally display the MCP password. Avoid recording first-run output in shared shell history, CI logs, screen recordings, or support tickets. Use `--no-print-credentials` for recorded sessions.
42
+ `exec_command` has both executable authority and shell expansion. Use `--no-exec` or `review`/`edit` when process execution is unnecessary.
39
43
 
40
- ### Filesystem exposure
44
+ For untrusted repositories or instructions, run the bridge inside a disposable VM/container or under a dedicated low-privilege OS account. On macOS and Windows, this external isolation is especially important. The project does not claim an in-process OS sandbox.
41
45
 
42
- Filesystem tools are confined to the canonical selected workspace by default, including symbolic-link resolution. `--unrestricted-paths` exposes absolute and parent paths and should be used only for a deliberate, short-lived session.
46
+ ## Filesystem exposure
43
47
 
44
- Sensitive filenames are not automatically blocked inside the workspace. Select the narrowest practical workspace and use `--no-write` for review-only sessions.
48
+ Direct filesystem tools are confined to the canonical selected workspace by default, including symbolic-link resolution. `--unrestricted-paths` expands this boundary and should be used only deliberately.
45
49
 
46
- ### Command execution
50
+ Returned paths are relative by default to reduce username and directory-layout disclosure. `--absolute-paths` changes metadata exposure without increasing direct access authority.
47
51
 
48
- `exec_command` is enabled by default but can be disabled with `--no-exec`. Commands run with a minimal environment unless `--full-env` is supplied. Minimal environment handling reduces accidental environment-secret disclosure; it does not sandbox filesystem, network, process, or credential-store access available to the user account.
52
+ Sensitive-looking names are not blocked inside the workspace. Files such as `.env`, private keys, credentials, database dumps, and production configuration remain readable when they are in the selected workspace. Choose the narrowest practical workspace.
49
53
 
50
- ### OAuth and public endpoints
54
+ Processes are not confined by the filesystem-tool resolver. They can access paths, networks, processes, credential stores, and devices available to the local user.
51
55
 
52
- OAuth uses authorization codes, PKCE S256, exact redirect/resource binding, hashed access-token storage, expiring codes/tokens, and a version value that revokes existing tokens after rotation. Client registration, codes, tokens, pending calls, body sizes, and failed password attempts are bounded.
56
+ ## Mutation integrity
53
57
 
54
- The authorization page displays the validated client name and redirect URI. Do not enter the connection password unless you initiated the connection and recognize both values. Browser-originated requests are same-origin by default; exact additional origins require `MBM_ALLOWED_ORIGINS` and receive explicit CORS headers. Loopback redirect URIs do not grant browser-origin access.
58
+ Writes are bounded, reject symbolic-link/non-regular destinations, use same-directory staging, and commit atomically per file. Create-only commit fails if a concurrent destination appears. Expected hashes and exact edits reduce accidental stale overwrites.
55
59
 
56
- Public health and metadata endpoints do not expose live daemon policy or connection status. The daemon handshake does not upload the local workspace name, path hash, or process ID. Authenticated `server_info` exposes only bounded operational metadata needed to diagnose the relay.
60
+ Patch operations prevalidate all paths/content, reject canonical collisions, recheck source hashes and destination absence, serialize bridge mutations, maintain backups, and roll back on ordinary commit errors.
57
61
 
58
- ### Relay and denial of service
62
+ These controls do not defend against a malicious process running under the same user racing filesystem metadata, nor do they make a multi-directory patch power-loss atomic.
59
63
 
60
- Only one daemon socket is active. New connections replace old ones. Pending calls are socket-bound and concurrency-limited. Request and output sizes are bounded. Dynamic registration is limited globally and per keyed-HMAC source identity; the source IP is not stored directly and inactive clients expire.
64
+ ## Credential exposure
61
65
 
62
- These controls reduce accidental exhaustion and simple abuse. The CLI uses the package-installed Wrangler binary and does not fall back to downloading an unpinned command through `npx`. They do not replace Cloudflare account protections, rate-limiting/WAF rules, or cost alerts for an internet-facing deployment.
66
+ Local state contains the MCP connection password and daemon secret. State, lock, temporary secret, runtime, and service-log files use owner-only permissions where supported. State writes are atomic. Logs recursively redact known credential fields and token formats and neutralize control characters.
67
+
68
+ First-run or explicit credential output can intentionally display the MCP password. Avoid shared terminal logs, shell recordings, screenshots, CI output, or support tickets. Use `--no-print-credentials` for recorded sessions.
69
+
70
+ Minimal process environment replaces HOME, temp, and common cache paths and does not pass arbitrary parent variables. It reduces accidental environment-secret leakage; it does not prevent code from explicitly accessing known resources. `--full-env` materially increases exposure.
71
+
72
+ ## OAuth and public endpoints
73
+
74
+ Remote mode uses authorization code flow with PKCE S256, exact redirect/resource/client binding, expiring authorization codes and access tokens, hashed token storage, token-version revocation, and bounded dynamic client registration.
75
+
76
+ The authorization page displays the validated client name and redirect URI. Enter the connection password only after initiating the connection and recognizing both values.
77
+
78
+ Password failures and registrations are limited by deployment-keyed HMAC source identity. Browser requests are same-origin unless an exact origin is listed in `MBM_ALLOWED_ORIGINS`; loopback OAuth redirect permission does not grant browser-origin access.
79
+
80
+ Public health and metadata do not expose live workspace or daemon status. The daemon handshake omits workspace path/name/hash and process ID.
81
+
82
+ ## Relay and denial of service
83
+
84
+ Only one authenticated daemon is active. Candidates have a handshake deadline and cannot displace the current daemon before success. Pending calls are socket-bound, client-request-bound, concurrency-limited, size-limited, and timed out. Duplicate in-flight request IDs for one access token are rejected.
85
+
86
+ Request bodies, WebSocket messages, tool outputs, traversals, sessions, stdin writes, OAuth records, clients, codes, tokens, and failure identities are bounded. Disconnect/replacement terminates active child process trees.
87
+
88
+ These controls reduce accidental exhaustion and simple abuse. They do not replace Cloudflare account MFA, WAF/rate limits, usage alerts, or cost controls for an internet-facing Worker.
89
+
90
+ ## Process sessions
91
+
92
+ Process-session IDs are random and valid only inside one runtime. Output buffers and session counts are bounded. Sessions are killed on runtime stop or remote connection loss/replacement and expire from retained state after exit.
93
+
94
+ Sessions use pipes, not a PTY. Do not assume terminal-oriented programs will behave safely or correctly. Process output may contain secrets; it is returned to the authorized client but is not intentionally written to operational logs.
95
+
96
+ ## Images and rich content
97
+
98
+ `view_image` accepts only signature-validated PNG, JPEG, GIF, and WebP under the size cap. SVG is intentionally excluded because it is active document content rather than a simple raster image. Image bytes pass through the Worker in remote mode and are visible to the authorized MCP client.
99
+
100
+ ## Logs and privacy
101
+
102
+ Operational logs record coarse metadata and error classes, not tool arguments, command text, stdin, file/patch contents, or outputs. Unexpected Worker exceptions are reduced to error classes. Git author email is omitted from `git_log` unless explicitly requested.
103
+
104
+ No logging policy can prevent data from being returned to a client that explicitly invokes an enabled tool. The Worker necessarily relays remote tool arguments and results; this is not end-to-end encryption against the user's Cloudflare execution environment.
63
105
 
64
106
  ## Hardening checklist
65
107
 
66
- For a high-sensitivity workspace:
108
+ For sensitive review:
109
+
110
+ ```sh
111
+ machine-mcp --workspace /narrow/project --profile review --no-print-credentials
112
+ ```
113
+
114
+ For controlled editing without execution:
67
115
 
68
- ```zsh
69
- machine-mcp --workspace /narrow/project --no-write --no-exec --no-print-credentials
116
+ ```sh
117
+ machine-mcp --workspace /narrow/project --profile edit --no-print-credentials
70
118
  ```
71
119
 
72
120
  Also:
73
121
 
74
- - keep the local OS and Node.js supported and patched;
75
- - enable MFA on the Cloudflare and GitHub accounts;
76
- - do not set `MBM_ALLOWED_ORIGINS` broadly;
77
- - avoid `--full-env` and `--unrestricted-paths`;
122
+ - patch the OS and use supported Node.js releases;
123
+ - enable MFA on Cloudflare, GitHub, and npm accounts;
124
+ - do not configure broad CORS origins;
125
+ - avoid `--full-env`, `--unrestricted-paths`, and `--absolute-paths` unless required;
126
+ - inspect client names and OAuth redirect URIs;
78
127
  - rotate secrets after suspected disclosure;
79
- - inspect `machine-mcp status` and `machine-mcp service status`;
80
- - uninstall the Worker and local state when the bridge is no longer needed.
128
+ - inspect `status`, `doctor`, and service status;
129
+ - remove the Worker and state when remote access is no longer needed;
130
+ - use external OS isolation for untrusted code.
81
131
 
82
132
  ## Out of scope
83
133
 
84
- The project cannot prevent an authorized MCP client from requesting sensitive data that the enabled tools can access. It also cannot make arbitrary shell commands safe. Model-level prompt injection and unsafe operator approval remain outside the transport's security guarantees; use narrow workspaces and restrictive policy flags as the primary controls.
134
+ The project cannot prevent an authorized client from requesting data accessible to enabled tools, make arbitrary local executables safe, identify all sensitive content, or neutralize model prompt injection. Operator approval and narrow capability selection remain primary controls.
@@ -1,102 +1,169 @@
1
1
  # Architecture
2
2
 
3
+ ## Design goal
4
+
5
+ The system separates three questions that are often conflated:
6
+
7
+ 1. **Who may request a tool?** Remote OAuth or local process ownership.
8
+ 2. **Which tools exist?** The selected local policy profile.
9
+ 3. **What authority does a tool have?** Canonical workspace boundaries for direct filesystem tools and local-user authority for processes.
10
+
11
+ No transport is treated as a sandbox. Both transports invoke the same local runtime.
12
+
3
13
  ## Components
4
14
 
5
- ### CLI
15
+ ### Shared tool catalog
6
16
 
7
- The CLI selects a canonical workspace, manages per-workspace state and secrets, serializes startup/deploy/rotation work with a startup lock, deploys the Worker, installs an optional login service, acquires the daemon lock, and starts the daemon. Worker deployment is content-hashed; a hash is persisted only after the expected Worker version passes health checks.
17
+ `src/shared/tool-catalog.json` is the single source of truth for tool names, descriptions, input schemas, annotations, and policy availability. The Worker and stdio runtime import it directly. A catalog test rejects duplicate names, unknown availability classes, open schemas, missing annotations, profile drift, and a second hand-maintained Worker catalog.
8
18
 
9
- ### Cloudflare Worker and Durable Object
19
+ ### CLI and state layer
20
+
21
+ The CLI canonicalizes workspaces, resolves policy profiles, maintains per-workspace state and credentials, serializes startup/deploy/rotation with locks, deploys the Worker, installs optional platform-native autostart, and starts either remote daemon or stdio mode.
10
22
 
11
- All requests for one deployed Worker route to a named Durable Object. OAuth metadata mutations are serialized through an explicit in-object critical section so concurrent registration, authorization, token exchange, and token verification cannot overwrite one another. The object owns:
23
+ A canonical workspace receives an independent profile, Worker name, secret set, daemon/startup locks, and state file. State schema version 3 removes obsolete local API state and records the expanded policy model.
12
24
 
13
- - OAuth client, authorization-code, access-token, and throttling metadata;
14
- - the active daemon WebSocket and a minimal attachment containing connection time, enabled tools, and policy booleans;
15
- - the bounded in-memory map of pending daemon calls.
25
+ ### Local runtime
16
26
 
17
- The daemon attachment deliberately omits the local workspace name, path hash, and process ID.
27
+ `LocalDaemon` is transport-independent despite its historical name. It owns:
18
28
 
19
- The Worker authenticates MCP HTTP requests and converts `tools/call` requests into daemon WebSocket messages. It does not have local filesystem or process access.
29
+ - canonical path resolution and display-path privacy;
30
+ - file, text search, image, patch, and Git operations;
31
+ - direct and shell process execution;
32
+ - process-session buffers and stdin lifecycle;
33
+ - mutation serialization;
34
+ - child-process tracking and cancellation;
35
+ - output, traversal, concurrency, and time limits.
20
36
 
21
- ### Local daemon
37
+ Remote mode attaches WebSocket connection/reconnect behavior. Stdio mode invokes the same runtime directly.
22
38
 
23
- The daemon validates that the Worker endpoint is a credential-free HTTPS origin, then creates an outbound authenticated WebSocket. It advertises only tools enabled by policy, validates every incoming call envelope, resolves filesystem paths, executes local operations, and returns bounded results.
39
+ ### Stdio MCP server
24
40
 
25
- ### Autostart provider
41
+ The stdio server implements newline-delimited JSON-RPC over stdin/stdout. It negotiates supported MCP versions, advertises policy-filtered tools, returns text plus structured content, supports native image content, maps cancellation notifications to runtime call IDs, and sends logs only to stderr.
42
+
43
+ ### Cloudflare Worker and Durable Object
26
44
 
27
- The service layer emits platform-native definitions for launchd, systemd user services, or Windows Scheduled Tasks. It stores no credential in the service definition; the daemon loads credentials from owner-only local state.
45
+ All requests for a deployed Worker route to one named Durable Object. It owns:
46
+
47
+ - OAuth clients, authorization codes, hashed access-token records, and throttling metadata;
48
+ - one active authenticated daemon WebSocket plus bounded candidate sockets;
49
+ - policy/tool metadata attached to the active socket;
50
+ - a bounded in-memory map of pending daemon calls.
51
+
52
+ The Worker verifies OAuth, validates MCP envelopes and optional protocol headers, converts `tools/call` into WebSocket messages, correlates cancellation by access-token hash and JSON-RPC ID, and formats text/structured/image results. It has no local filesystem or process API.
53
+
54
+ The daemon attachment deliberately omits workspace path/name/hash and process ID. Explicit authenticated tools may return workspace metadata according to local path-display policy.
55
+
56
+ ### Autostart layer
57
+
58
+ The service layer emits launchd, systemd-user, or Windows Scheduled Task definitions. Credentials are not embedded in service definitions; the daemon loads owner-only state. The exact policy is stored in owner-only state; platform service definitions contain only the workspace and state-root selectors.
28
59
 
29
60
  ## Trust boundaries
30
61
 
31
62
  ```mermaid
32
63
  flowchart LR
33
- C[Remote MCP client] -->|HTTPS + OAuth bearer token| W[Worker]
34
- W -->|Durable Object storage| O[OAuth metadata]
35
- W -->|authenticated WebSocket tool call| D[Local daemon]
36
- D -->|workspace-scoped file operations| F[Selected workspace]
37
- D -->|optional shell execution| P[User processes / OS]
38
- L[CLI] --> W
39
- L --> D
40
- L --> S[Autostart provider]
64
+ C[Remote MCP client] -->|HTTPS + OAuth bearer token| W[Worker / Durable Object]
65
+ W -->|authenticated bounded WebSocket calls| R[Local runtime]
66
+ L[Local MCP client] -->|stdio JSON-RPC| R
67
+ R -->|canonical workspace tools| F[Selected workspace]
68
+ R -->|optional direct/shell processes| P[Local user / OS / network]
69
+ CLI[CLI + state] --> W
70
+ CLI --> R
71
+ CLI --> S[Autostart provider]
41
72
  ```
42
73
 
43
- The OAuth boundary determines which remote client may call tools. The daemon policy determines which tools exist. The workspace path resolver determines the default filesystem boundary. Shell execution is intentionally outside the filesystem-tool sandbox and inherits the local user's authority.
74
+ Remote OAuth determines which client may call tools. Local stdio access relies on the local process and configuration boundary. Policy determines which tools are advertised. Canonical resolution limits direct filesystem tools. Processes retain local-user authority and can escape workspace constraints through their own code or system calls.
44
75
 
45
- ## Request lifecycle
76
+ ## Remote request lifecycle
46
77
 
47
- 1. The MCP client discovers OAuth and protected-resource metadata.
78
+ 1. The MCP client discovers protected-resource and authorization-server metadata.
48
79
  2. It dynamically registers bounded redirect metadata.
49
- 3. The authorization request is validated before a password form is shown.
50
- 4. The user verifies the client and redirect URI and supplies the connection password.
51
- 5. A five-minute authorization code bound to client, redirect, resource, scope, and PKCE challenge is created.
52
- 6. A valid verifier exchanges the code for a hashed, expiring access-token record.
53
- 7. An authenticated `tools/call` is assigned a random call ID and bound to the current daemon socket.
54
- 8. The daemon validates the envelope, executes the enabled tool, and returns a bounded result.
55
- 9. The Durable Object accepts the result only from the same socket that received the call.
80
+ 3. The Worker validates authorization parameters before displaying a password form.
81
+ 4. The user verifies client name and redirect URI and enters the connection password.
82
+ 5. The Worker creates a five-minute code bound to client, redirect, resource, scope, and PKCE challenge.
83
+ 6. A valid verifier exchanges the one-time code for an expiring bearer token; only its hash is stored.
84
+ 7. The MCP client initializes and negotiates a supported protocol version.
85
+ 8. `tools/list` is derived from the active daemon handshake; without a daemon, only `server_info` is advertised.
86
+ 9. `tools/call` receives a random relay call ID and is bound to the current socket and authenticated client request key.
87
+ 10. The runtime validates policy and arguments, executes the tool, and returns a bounded result.
88
+ 11. The Durable Object accepts a result only from the socket that received that call.
89
+ 12. A matching cancellation notification removes the pending call, tells the daemon to cancel, and terminates any child processes bound to it.
90
+
91
+ Duplicate in-flight JSON-RPC IDs for the same access token are rejected so cancellation cannot ambiguously target multiple calls.
92
+
93
+ ## Stdio request lifecycle
94
+
95
+ 1. The local client launches `machine-mcp stdio` with a workspace and profile.
96
+ 2. The server negotiates one of the supported MCP versions.
97
+ 3. Tool discovery is generated from the same catalog and policy used by remote mode.
98
+ 4. Each call receives an internal random call ID used only for cancellation and process tracking.
99
+ 5. Results are emitted as JSON-RPC on stdout; logs remain on stderr.
100
+ 6. Duplicate in-flight request IDs are rejected.
101
+ 7. Closing stdin cancels pending calls, terminates active processes, and removes the isolated runtime directory.
102
+
103
+ ## Filesystem resolution and privacy
104
+
105
+ The workspace is canonicalized once. Existing targets use `realpath`; the result must remain inside the workspace unless unrestricted mode is explicit. New targets walk to the nearest existing ancestor and validate its canonical path.
106
+
107
+ Results use workspace-relative paths by default. Error strings redact canonical and common platform-alias forms of workspace, runtime, and home paths. Absolute paths are available only through an explicit display policy or unrestricted mode.
108
+
109
+ Symbolic-link destinations and non-regular write targets are rejected. Recursive walkers do not follow symbolic-link directories.
110
+
111
+ ## Mutation model
112
+
113
+ All bridge mutations are serialized in one runtime queue.
114
+
115
+ ### Whole-file write
116
+
117
+ A same-directory temporary file is created with restrictive permissions. Existing mode is preserved when replacing a regular file. Expected hashes are rechecked immediately before commit. Create-only uses a hard-link commit that fails atomically if the destination appeared concurrently.
118
+
119
+ ### Exact edit
120
+
121
+ The current UTF-8 content is hashed. The old fragment must exist and, unless `replace_all` is set, occur exactly once. The resulting file is bounded and committed only if the original hash is unchanged.
122
+
123
+ ### Patch transaction
124
+
125
+ The patch parser accepts a strict Begin/End envelope with add, update, move, and delete operations. It rejects malformed hunks, absent or ambiguous context, duplicate textual paths, and different paths that resolve to the same filesystem location.
126
+
127
+ Before commit, all sources and targets are resolved and validated, updated content is computed, and temporary files are staged. Source hashes and destination absence are rechecked. Existing sources are renamed to backups, staged files are committed, and any failure rolls back committed operations in reverse order. Backups are deleted only after full success.
56
128
 
57
- Browser requests are same-origin unless an exact origin is configured. Configured origins receive bounded CORS preflight and response headers; loopback OAuth callback permission is evaluated separately and never acts as a browser-origin wildcard.
129
+ This is a process-level transaction, not a filesystem-wide atomic transaction across multiple directories. Sudden power loss can still interrupt a multi-file commit; retained backup/temp naming makes recovery identifiable.
58
130
 
59
- ## Filesystem resolution
131
+ ## Process model
60
132
 
61
- The workspace is canonicalized at daemon construction. Existing read/list/search/Git targets are canonicalized with `realpath`; the canonical result must remain inside the workspace unless unrestricted mode is explicit. New write targets walk to the nearest existing ancestor and validate that ancestor, preventing a missing-path write through an escaping symbolic-link directory.
133
+ `run_process` and process sessions use argv arrays and do not invoke a shell. `exec_command` invokes the platform shell and is available only in `shell` mode. Both inherit local-user OS authority.
62
134
 
63
- Writes use a same-directory temporary file and rename, preserving the existing mode when replacing a regular file. Bridge Git commands disable repository-configured external diff, text conversion, and filesystem-monitor hooks to avoid executing repository-local helpers during status/diff inspection. Symbolic-link destinations and non-regular targets are rejected.
135
+ Minimal environment mode creates private runtime HOME, temporary, and cache directories and passes only a small set of path/locale/platform variables. It reduces accidental credential inheritance but cannot prevent explicit access to known filesystem paths, credential stores, network services, or other user resources.
64
136
 
65
- ## Reconnect and replacement
137
+ One-shot calls have bounded output and timeouts. Process sessions retain bounded byte buffers with monotonic offsets, accept bounded stdin, support short output/exit waits, and are capped per runtime. Valid UTF-8 is returned as text; byte slices that are not valid UTF-8 also include lossless base64 data. Session IDs are random. Sessions are memory-only and are killed on runtime stop, remote disconnect, or daemon replacement.
66
138
 
67
- The daemon sends heartbeats and reconnects after a close using bounded exponential backoff with jitter. The handshake advertises only enabled tools and policy; it does not transmit a workspace path/name/hash or stable daemon identifier. Closing the current relay socket terminates active child processes so disconnected callers cannot leave long-running commands behind. The Worker permits one active daemon. A replacement remains a bounded candidate until it completes the authenticated `hello` handshake; its deadline is enforced by a Durable Object alarm that survives WebSocket hibernation; only then is the older active socket closed. Pending calls retain their originating socket reference. Closing an old socket rejects only calls assigned to that socket and cannot affect calls sent to the replacement. Active child processes associated with a lost/replaced connection receive termination with forced escalation when needed.
139
+ Child processes run in a separate process group where supported. Timeout, cancellation, disconnect, and replacement send termination to the process tree, with forced escalation for timeout/disconnect paths.
68
140
 
69
- ## Limits
141
+ ## Daemon reconnect and replacement
70
142
 
71
- Limits are defense-in-depth and are intentionally below platform maxima:
143
+ The daemon sends heartbeats and reconnects with bounded exponential backoff and jitter. Only one socket is active. A new connection is a bounded candidate until it authenticates and completes `hello`; a Durable Object alarm enforces the deadline across hibernation. Only a completed candidate replaces the old socket.
72
144
 
73
- - Worker MCP body: configurable, default 8 MiB, hard cap 16 MiB.
74
- - OAuth body: 64 KiB.
75
- - Local WebSocket message: 8 MiB.
76
- - `write_file` content: 5 MiB.
77
- - `exec_command` command text: 64 KiB.
78
- - Direct directory results: 10,000 entries and 4 MiB of path metadata.
79
- - Recursive walks: 200,000 visited entries; path-list results are capped at 4 MiB.
80
- - Captured process output: 512 KiB per stream by default.
81
- - Local simultaneous tool calls: 16.
82
- - Worker pending daemon calls: 32.
83
- - Command timeout: 1-600 seconds.
84
- - Dynamic clients, redirect URIs, codes, tokens, failed login identities, and per-client records: bounded constants in Worker source.
145
+ Pending calls retain their originating socket reference. A stale socket cannot complete or cancel replacement-socket calls. Closing a socket rejects only calls assigned to it.
85
146
 
86
147
  ## Persistence
87
148
 
88
- The local state schema is versioned. A custom state root is adopted only when empty or when it contains a recognizable legacy layout; legacy text markers are migrated, and removal validates the marker, contents, canonical path, active locks, and workspace/source-tree exclusions. JSON state and global config are written to owner-only temporary files, flushed, and atomically renamed. A malformed state file is renamed to a bounded corrupt backup before a clean state object is reconstructed. State roots carry an owner-only marker; uninstall validates the target and refuses dangerous or unrecognized recursive deletion.
149
+ Local state and global config are owner-only, versioned, and written through temporary files, flushes, and atomic rename. Malformed state becomes a bounded `.corrupt-*` backup. Custom roots are adopted only when empty or recognizable as legacy Machine Bridge state.
89
150
 
90
- OAuth metadata is pruned on access. Expired codes and tokens, old throttling records, and inactive clients without active credentials are removed. Registration and password-throttling identities use a deployment-keyed HMAC of the Cloudflare source address rather than storing an address or reversible unsalted hash.
151
+ Removal validates the state marker, canonical target, known contents, active locks, filesystem root/home/current/package/workspace/source exclusions, and Worker deletion outcome before recursive deletion.
91
152
 
92
- Cross-origin browser access is denied unless the exact origin is configured; allowed origins receive bounded preflight and response headers. Same-origin requests remain allowed.
153
+ OAuth metadata is pruned on access. Expired codes/tokens, old throttling records, and inactive clients without active credentials are removed. Source identities are deployment-keyed HMAC values, not stored source addresses or reversible unsalted hashes.
93
154
 
94
- ## Observability and privacy
155
+ ## Observability
95
156
 
96
- Public health output contains only server identity and version. Worker observability uses a 10% head-sampling rate. Live daemon details are available through the authenticated `server_info` tool. Local structured logging redacts sensitive key names and known credential formats, bounds nested values, handles cycles, and escapes control characters. Doctor output does not echo Wrangler account identity when authentication succeeds.
157
+ Public health exposes only server identity and version. Authenticated `server_info` exposes bounded runtime status. Local operational logs record tool identity, shortened random call ID, duration, outcome, and coarse error class, but not arguments or outputs. Worker unexpected-error logs contain endpoint and error class, not raw exception text.
97
158
 
98
- Service logs are owner-only where supported and are tail-trimmed before daemon startup. Cloudflare observability sampling defaults to 10% rather than retaining every invocation. This is size control, not an audit log. The project deliberately does not log file contents, command strings, OAuth passwords, access tokens, or daemon secrets as structured operational events.
159
+ Cloudflare sampling is size control rather than an audit log. The project intentionally does not claim complete forensic logging.
99
160
 
100
- ## Removed local API
161
+ ## Explicit non-goals
101
162
 
102
- The experimental local OpenAI-compatible `/v1` API and MCP sampling proxy were removed. They introduced a second local request surface and depended on client-side sampling behavior that was not a reliable backend contract. The supported interface is the authenticated Remote MCP endpoint.
163
+ - operating-system sandboxing of arbitrary executables;
164
+ - preventing an authorized client from requesting data available to enabled tools;
165
+ - automatically deciding which workspace files are sensitive;
166
+ - surviving daemon restart with process sessions;
167
+ - PTY/terminal emulation;
168
+ - model-level prompt-injection prevention;
169
+ - multi-user tenancy in one Worker deployment.
@@ -0,0 +1,72 @@
1
+ # Client configuration
2
+
3
+ ## Choosing a transport
4
+
5
+ Use remote HTTPS/OAuth when the client cannot launch a local stdio process or must reach the machine from another device. Use stdio when the client runs on the same machine and supports local MCP processes.
6
+
7
+ | Client class | Recommended transport | Reason |
8
+ |---|---|---|
9
+ | ChatGPT remote connector / Developer Mode | Remote Worker | Public HTTPS endpoint and OAuth authorization |
10
+ | Claude Desktop | stdio | Local process transport; no cloud relay required |
11
+ | Cursor | stdio | Local process transport and direct workspace lifecycle |
12
+ | Codex CLI | stdio | Local MCP server configuration |
13
+ | Other MCP clients | stdio when local; remote when necessary | Minimize the trust and network boundary |
14
+
15
+ ## Generate configuration
16
+
17
+ ```sh
18
+ machine-mcp client-config --client all --workspace /path/to/project --profile agent
19
+ ```
20
+
21
+ The generated command uses the current Node executable and installed entry script as absolute paths so it does not depend on GUI application PATH configuration.
22
+
23
+ ## JSON stdio configuration
24
+
25
+ Claude Desktop, Cursor, and many generic clients use a shape similar to:
26
+
27
+ ```json
28
+ {
29
+ "mcpServers": {
30
+ "machine-bridge": {
31
+ "command": "/absolute/path/to/node",
32
+ "args": [
33
+ "/absolute/path/to/machine-mcp.mjs",
34
+ "stdio",
35
+ "--workspace",
36
+ "/path/to/project",
37
+ "--profile",
38
+ "agent"
39
+ ]
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ Use the exact output of `client-config`; application-specific configuration locations change over time.
46
+
47
+ ## Codex CLI TOML
48
+
49
+ ```toml
50
+ [mcp_servers.machine_bridge]
51
+ command = "/absolute/path/to/node"
52
+ args = ["/absolute/path/to/machine-mcp.mjs", "stdio", "--workspace", "/path/to/project", "--profile", "agent"]
53
+ ```
54
+
55
+ ## ChatGPT remote connection
56
+
57
+ Run:
58
+
59
+ ```sh
60
+ machine-mcp --workspace /path/to/project --profile review
61
+ ```
62
+
63
+ Enter the printed `/mcp` URL in the remote MCP connector. During OAuth authorization, verify the displayed client name and redirect URI before entering the connection password.
64
+
65
+ ## Profile guidance
66
+
67
+ - Begin with `review` for an unfamiliar client or repository.
68
+ - Use `edit` when the client needs deterministic file mutation but no process execution.
69
+ - Use `agent` for normal coding work. It permits direct executables and process sessions, which remain powerful.
70
+ - Use `full` only when shell syntax is required and the client/instructions are trusted.
71
+
72
+ A client configuration is an authorization decision. Anyone who controls an authorized client can invoke every tool exposed by its selected profile.
@@ -0,0 +1,81 @@
1
+ # Operations
2
+
3
+ ## Health and diagnosis
4
+
5
+ ```sh
6
+ machine-mcp status
7
+ machine-mcp doctor
8
+ machine-mcp service status
9
+ ```
10
+
11
+ `status` prints redacted profile state and verifies the deployed Worker version. `doctor` checks Node.js, the package-installed Wrangler binary, Cloudflare login, and Worker health. Public `/healthz` output contains only server identity and version; daemon details require an authenticated `server_info` call.
12
+
13
+ ## Logs
14
+
15
+ Remote autostart logs are stored under the state root in `logs/daemon.out.log` and `logs/daemon.err.log`. Files are owner-only where supported and tail-trimmed before daemon startup.
16
+
17
+ Structured operational events may include:
18
+
19
+ - component and severity;
20
+ - tool name;
21
+ - shortened random call identifier;
22
+ - duration and success/failure;
23
+ - coarse error class;
24
+ - connection/reconnect status.
25
+
26
+ They intentionally omit:
27
+
28
+ - file contents and image data;
29
+ - write, edit, or patch payloads;
30
+ - command argv, shell text, stdin, stdout, and stderr;
31
+ - MCP connection passwords, daemon secrets, authorization codes, and access tokens;
32
+ - full request bodies.
33
+
34
+ Unexpected Worker failures log endpoint path and error class rather than raw exception messages. Cloudflare observability is sampled and is not a complete audit log.
35
+
36
+ ## Reconnect and replacement
37
+
38
+ The daemon sends heartbeats and reconnects with bounded exponential backoff and jitter. A new socket remains a candidate until it authenticates and sends a valid `hello`; only then does it replace the previous daemon.
39
+
40
+ Pending calls are bound to the socket that received them. Results from another socket are ignored. A lost or replaced socket rejects only its own pending calls and terminates locally tracked child process trees. Process sessions are in-memory and do not survive daemon restart or replacement.
41
+
42
+ ## Limits
43
+
44
+ Defense-in-depth limits include:
45
+
46
+ - Worker MCP body: 8 MiB by default, hard cap 16 MiB;
47
+ - OAuth body: 64 KiB;
48
+ - daemon WebSocket message: 8 MiB;
49
+ - text writes and patch envelopes: 5 MiB;
50
+ - images: 4 MiB before base64 encoding;
51
+ - shell/argv envelope: 64 KiB;
52
+ - captured one-shot output: 512 KiB per stream by default;
53
+ - process-session retained output: 1 MiB per stream, with lossless base64 fallback for non-UTF-8 slices;
54
+ - process sessions: 8 retained per runtime;
55
+ - process stdin write: 64 KiB per call;
56
+ - local simultaneous tool calls: 16;
57
+ - Worker pending daemon calls: 32;
58
+ - command timeout: 1–600 seconds;
59
+ - process-session read wait: at most 30 seconds;
60
+ - direct directory result: 10,000 entries and 4 MiB of path metadata;
61
+ - recursive walk: 200,000 visited entries.
62
+
63
+ ## Upgrade behavior
64
+
65
+ Version 0.4 changes the default for newly selected workspaces to `review`. Existing state retains its previous write and execution settings. To intentionally migrate an old profile:
66
+
67
+ ```sh
68
+ machine-mcp --workspace /path/to/project --profile agent
69
+ ```
70
+
71
+ A remote policy change is saved locally, propagated in the daemon handshake, and persisted in the autostart definition.
72
+
73
+ ## Incident response
74
+
75
+ After suspected credential or client compromise:
76
+
77
+ 1. stop foreground and autostart daemons;
78
+ 2. run `machine-mcp rotate-secrets --no-print-credentials`;
79
+ 3. restart without broad flags and redeploy;
80
+ 4. inspect Cloudflare account access, Worker configuration, local state permissions, and service logs;
81
+ 5. remove the Worker and local state if continued remote access is unnecessary.